summaryrefslogtreecommitdiff
path: root/uriloader
diff options
context:
space:
mode:
Diffstat (limited to 'uriloader')
-rw-r--r--uriloader/base/moz.build33
-rw-r--r--uriloader/base/nsCURILoader.idl49
-rw-r--r--uriloader/base/nsDocLoader.cpp1516
-rw-r--r--uriloader/base/nsDocLoader.h335
-rw-r--r--uriloader/base/nsIContentHandler.idl35
-rw-r--r--uriloader/base/nsIDocumentLoader.idl36
-rw-r--r--uriloader/base/nsITransfer.idl106
-rw-r--r--uriloader/base/nsIURIContentListener.idl135
-rw-r--r--uriloader/base/nsIURILoader.idl140
-rw-r--r--uriloader/base/nsIWebProgress.idl153
-rw-r--r--uriloader/base/nsIWebProgressListener.idl425
-rw-r--r--uriloader/base/nsIWebProgressListener2.idl69
-rw-r--r--uriloader/base/nsURILoader.cpp966
-rw-r--r--uriloader/base/nsURILoader.h59
-rw-r--r--uriloader/exthandler/ContentHandlerService.cpp169
-rw-r--r--uriloader/exthandler/ContentHandlerService.h52
-rw-r--r--uriloader/exthandler/ExternalHelperAppChild.cpp127
-rw-r--r--uriloader/exthandler/ExternalHelperAppChild.h45
-rw-r--r--uriloader/exthandler/ExternalHelperAppParent.cpp512
-rw-r--r--uriloader/exthandler/ExternalHelperAppParent.h119
-rw-r--r--uriloader/exthandler/HandlerServiceChild.h15
-rw-r--r--uriloader/exthandler/HandlerServiceParent.cpp270
-rw-r--r--uriloader/exthandler/HandlerServiceParent.h31
-rw-r--r--uriloader/exthandler/PExternalHelperApp.ipdl29
-rw-r--r--uriloader/exthandler/PHandlerService.ipdl42
-rw-r--r--uriloader/exthandler/android/nsAndroidHandlerApp.cpp91
-rw-r--r--uriloader/exthandler/android/nsAndroidHandlerApp.h33
-rw-r--r--uriloader/exthandler/android/nsExternalSharingAppService.cpp61
-rw-r--r--uriloader/exthandler/android/nsExternalSharingAppService.h28
-rw-r--r--uriloader/exthandler/android/nsExternalURLHandlerService.cpp27
-rw-r--r--uriloader/exthandler/android/nsExternalURLHandlerService.h27
-rw-r--r--uriloader/exthandler/android/nsMIMEInfoAndroid.cpp427
-rw-r--r--uriloader/exthandler/android/nsMIMEInfoAndroid.h60
-rw-r--r--uriloader/exthandler/android/nsOSHelperAppService.cpp67
-rw-r--r--uriloader/exthandler/android/nsOSHelperAppService.h40
-rw-r--r--uriloader/exthandler/gonk/nsOSHelperAppService.cpp56
-rw-r--r--uriloader/exthandler/gonk/nsOSHelperAppService.h39
-rw-r--r--uriloader/exthandler/mac/nsDecodeAppleFile.cpp389
-rw-r--r--uriloader/exthandler/mac/nsDecodeAppleFile.h118
-rw-r--r--uriloader/exthandler/mac/nsLocalHandlerAppMac.h26
-rw-r--r--uriloader/exthandler/mac/nsLocalHandlerAppMac.mm84
-rw-r--r--uriloader/exthandler/mac/nsMIMEInfoMac.h34
-rw-r--r--uriloader/exthandler/mac/nsMIMEInfoMac.mm114
-rw-r--r--uriloader/exthandler/mac/nsOSHelperAppService.h48
-rw-r--r--uriloader/exthandler/mac/nsOSHelperAppService.mm569
-rw-r--r--uriloader/exthandler/moz.build139
-rw-r--r--uriloader/exthandler/nsCExternalHandlerService.idl58
-rw-r--r--uriloader/exthandler/nsContentHandlerApp.cpp79
-rw-r--r--uriloader/exthandler/nsContentHandlerApp.h30
-rw-r--r--uriloader/exthandler/nsDBusHandlerApp.cpp177
-rw-r--r--uriloader/exthandler/nsDBusHandlerApp.h33
-rw-r--r--uriloader/exthandler/nsExternalHelperAppService.cpp2926
-rw-r--r--uriloader/exthandler/nsExternalHelperAppService.h498
-rw-r--r--uriloader/exthandler/nsExternalProtocolHandler.cpp561
-rw-r--r--uriloader/exthandler/nsExternalProtocolHandler.h38
-rw-r--r--uriloader/exthandler/nsHandlerService.js1429
-rw-r--r--uriloader/exthandler/nsHandlerService.manifest2
-rw-r--r--uriloader/exthandler/nsIContentDispatchChooser.idl45
-rw-r--r--uriloader/exthandler/nsIExternalHelperAppService.idl154
-rw-r--r--uriloader/exthandler/nsIExternalProtocolService.idl135
-rw-r--r--uriloader/exthandler/nsIExternalSharingAppService.idl28
-rw-r--r--uriloader/exthandler/nsIExternalURLHandlerService.idl26
-rw-r--r--uriloader/exthandler/nsIHandlerService.idl117
-rw-r--r--uriloader/exthandler/nsIHelperAppLauncherDialog.idl89
-rw-r--r--uriloader/exthandler/nsLocalHandlerApp.cpp179
-rw-r--r--uriloader/exthandler/nsLocalHandlerApp.h60
-rw-r--r--uriloader/exthandler/nsMIMEInfoImpl.cpp435
-rw-r--r--uriloader/exthandler/nsMIMEInfoImpl.h196
-rw-r--r--uriloader/exthandler/nsWebHandlerApp.js172
-rw-r--r--uriloader/exthandler/nsWebHandlerApp.manifest2
-rw-r--r--uriloader/exthandler/tests/Makefile.in11
-rw-r--r--uriloader/exthandler/tests/WriteArgument.cpp24
-rw-r--r--uriloader/exthandler/tests/mochitest/browser.ini8
-rw-r--r--uriloader/exthandler/tests/mochitest/browser_download_always_ask_preferred_app.js19
-rw-r--r--uriloader/exthandler/tests/mochitest/browser_remember_download_option.js49
-rw-r--r--uriloader/exthandler/tests/mochitest/browser_web_protocol_handlers.js76
-rw-r--r--uriloader/exthandler/tests/mochitest/handlerApp.xhtml30
-rw-r--r--uriloader/exthandler/tests/mochitest/handlerApps.js110
-rw-r--r--uriloader/exthandler/tests/mochitest/head.js105
-rw-r--r--uriloader/exthandler/tests/mochitest/mochitest.ini10
-rw-r--r--uriloader/exthandler/tests/mochitest/protocolHandler.html16
-rw-r--r--uriloader/exthandler/tests/mochitest/test_handlerApps.xhtml12
-rw-r--r--uriloader/exthandler/tests/mochitest/test_unsafeBidiChars.xhtml77
-rw-r--r--uriloader/exthandler/tests/mochitest/unsafeBidiFileName.sjs14
-rw-r--r--uriloader/exthandler/tests/mochitest/unsafeBidi_chromeScript.js28
-rw-r--r--uriloader/exthandler/tests/moz.build24
-rw-r--r--uriloader/exthandler/tests/unit/head_handlerService.js163
-rw-r--r--uriloader/exthandler/tests/unit/mailcap2
-rw-r--r--uriloader/exthandler/tests/unit/tail_handlerService.js5
-rw-r--r--uriloader/exthandler/tests/unit/test_badMIMEType.js26
-rw-r--r--uriloader/exthandler/tests/unit/test_getTypeFromExtension_ext_to_type_mapping.js53
-rw-r--r--uriloader/exthandler/tests/unit/test_getTypeFromExtension_with_empty_Content_Type.js186
-rw-r--r--uriloader/exthandler/tests/unit/test_handlerService.js470
-rw-r--r--uriloader/exthandler/tests/unit/test_punycodeURIs.js126
-rw-r--r--uriloader/exthandler/tests/unit/xpcshell.ini15
-rw-r--r--uriloader/exthandler/tests/unit_ipc/test_encoding.js231
-rw-r--r--uriloader/exthandler/tests/unit_ipc/xpcshell.ini8
-rw-r--r--uriloader/exthandler/uikit/nsLocalHandlerAppUIKit.h32
-rw-r--r--uriloader/exthandler/uikit/nsLocalHandlerAppUIKit.mm17
-rw-r--r--uriloader/exthandler/uikit/nsMIMEInfoUIKit.h37
-rw-r--r--uriloader/exthandler/uikit/nsMIMEInfoUIKit.mm20
-rw-r--r--uriloader/exthandler/uikit/nsOSHelperAppService.h52
-rw-r--r--uriloader/exthandler/uikit/nsOSHelperAppService.mm64
-rw-r--r--uriloader/exthandler/unix/nsExternalSharingAppService.h31
-rw-r--r--uriloader/exthandler/unix/nsGNOMERegistry.cpp109
-rw-r--r--uriloader/exthandler/unix/nsGNOMERegistry.h28
-rw-r--r--uriloader/exthandler/unix/nsMIMEInfoUnix.cpp136
-rw-r--r--uriloader/exthandler/unix/nsMIMEInfoUnix.h32
-rw-r--r--uriloader/exthandler/unix/nsOSHelperAppService.cpp1537
-rw-r--r--uriloader/exthandler/unix/nsOSHelperAppService.h128
-rw-r--r--uriloader/exthandler/win/nsMIMEInfoWin.cpp883
-rw-r--r--uriloader/exthandler/win/nsMIMEInfoWin.h71
-rw-r--r--uriloader/exthandler/win/nsOSHelperAppService.cpp626
-rw-r--r--uriloader/exthandler/win/nsOSHelperAppService.h65
-rw-r--r--uriloader/moz.build11
-rw-r--r--uriloader/prefetch/OfflineCacheUpdateChild.cpp528
-rw-r--r--uriloader/prefetch/OfflineCacheUpdateChild.h94
-rw-r--r--uriloader/prefetch/OfflineCacheUpdateGlue.cpp228
-rw-r--r--uriloader/prefetch/OfflineCacheUpdateGlue.h80
-rw-r--r--uriloader/prefetch/OfflineCacheUpdateParent.cpp294
-rw-r--r--uriloader/prefetch/OfflineCacheUpdateParent.h65
-rw-r--r--uriloader/prefetch/POfflineCacheUpdate.ipdl28
-rw-r--r--uriloader/prefetch/moz.build45
-rw-r--r--uriloader/prefetch/nsCPrefetchService.h52
-rw-r--r--uriloader/prefetch/nsIOfflineCacheUpdate.idl292
-rw-r--r--uriloader/prefetch/nsIPrefetchService.idl37
-rw-r--r--uriloader/prefetch/nsOfflineCacheUpdate.cpp2471
-rw-r--r--uriloader/prefetch/nsOfflineCacheUpdate.h381
-rw-r--r--uriloader/prefetch/nsOfflineCacheUpdateService.cpp736
-rw-r--r--uriloader/prefetch/nsPrefetchService.cpp931
-rw-r--r--uriloader/prefetch/nsPrefetchService.h121
131 files changed, 27574 insertions, 0 deletions
diff --git a/uriloader/base/moz.build b/uriloader/base/moz.build
new file mode 100644
index 0000000000..0d0f9a1c67
--- /dev/null
+++ b/uriloader/base/moz.build
@@ -0,0 +1,33 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+XPIDL_SOURCES += [
+ 'nsCURILoader.idl',
+ 'nsIContentHandler.idl',
+ 'nsIDocumentLoader.idl',
+ 'nsITransfer.idl',
+ 'nsIURIContentListener.idl',
+ 'nsIURILoader.idl',
+ 'nsIWebProgress.idl',
+ 'nsIWebProgressListener.idl',
+ 'nsIWebProgressListener2.idl',
+]
+
+XPIDL_MODULE = 'uriloader'
+
+EXPORTS += [
+ 'nsDocLoader.h',
+ 'nsURILoader.h',
+]
+
+UNIFIED_SOURCES += [
+ 'nsDocLoader.cpp',
+ 'nsURILoader.cpp',
+]
+
+FINAL_LIBRARY = 'xul'
diff --git a/uriloader/base/nsCURILoader.idl b/uriloader/base/nsCURILoader.idl
new file mode 100644
index 0000000000..5d9d8f0131
--- /dev/null
+++ b/uriloader/base/nsCURILoader.idl
@@ -0,0 +1,49 @@
+/* -*- Mode: IDL; tab-width: 3; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIURILoader.idl"
+
+/*
+nsCURILoader implements:
+-------------------------
+nsIURILoader
+*/
+
+%{ C++
+// {9F6D5D40-90E7-11d3-AF93-00A024FFC08C} -
+#define NS_URI_LOADER_CID \
+{ 0x9f6d5d40, 0x90e7, 0x11d3, { 0xaf, 0x80, 0x00, 0xa0, 0x24, 0xff, 0xc0, 0x8c } }
+#define NS_URI_LOADER_CONTRACTID \
+"@mozilla.org/uriloader;1"
+
+/* 057b04d0-0ccf-11d2-beba-00805f8a66dc */
+#define NS_DOCUMENTLOADER_SERVICE_CID \
+ { 0x057b04d0, 0x0ccf, 0x11d2,{0xbe, 0xba, 0x00, 0x80, 0x5f, 0x8a, 0x66, 0xdc}}
+
+#define NS_DOCUMENTLOADER_SERVICE_CONTRACTID \
+"@mozilla.org/docloaderservice;1"
+
+#define NS_CONTENT_HANDLER_CONTRACTID "@mozilla.org/uriloader/content-handler;1"
+#define NS_CONTENT_HANDLER_CONTRACTID_PREFIX NS_CONTENT_HANDLER_CONTRACTID "?type="
+
+/**
+ * A category where content listeners can register. The name of the entry must
+ * be the content that this listener wants to handle, the value must be a
+ * contract ID for the listener. It will be created using createInstance (not
+ * getService).
+ *
+ * Listeners added this way are tried after the initial target of the load and
+ * after explicitly registered listeners (nsIURILoader::registerContentListener).
+ *
+ * These listeners must implement at least nsIURIContentListener (and
+ * nsISupports).
+ *
+ * @see nsICategoryManager
+ * @see nsIURIContentListener
+ */
+#define NS_CONTENT_LISTENER_CATEGORYMANAGER_ENTRY "external-uricontentlisteners"
+
+%}
diff --git a/uriloader/base/nsDocLoader.cpp b/uriloader/base/nsDocLoader.cpp
new file mode 100644
index 0000000000..69885b93f8
--- /dev/null
+++ b/uriloader/base/nsDocLoader.cpp
@@ -0,0 +1,1516 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nspr.h"
+#include "mozilla/Logging.h"
+
+#include "nsDocLoader.h"
+#include "nsCURILoader.h"
+#include "nsNetUtil.h"
+#include "nsIHttpChannel.h"
+#include "nsIWebProgressListener2.h"
+
+#include "nsIServiceManager.h"
+#include "nsXPIDLString.h"
+
+#include "nsIURL.h"
+#include "nsCOMPtr.h"
+#include "nscore.h"
+#include "nsWeakPtr.h"
+#include "nsAutoPtr.h"
+#include "nsQueryObject.h"
+
+#include "nsIDOMWindow.h"
+
+#include "nsIStringBundle.h"
+#include "nsIScriptSecurityManager.h"
+
+#include "nsITransport.h"
+#include "nsISocketTransport.h"
+#include "nsIDocShell.h"
+#include "nsIDOMDocument.h"
+#include "nsIDocument.h"
+#include "nsPresContext.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+
+using mozilla::DebugOnly;
+using mozilla::LogLevel;
+
+static NS_DEFINE_CID(kThisImplCID, NS_THIS_DOCLOADER_IMPL_CID);
+
+//
+// Log module for nsIDocumentLoader logging...
+//
+// To enable logging (see mozilla/Logging.h for full details):
+//
+// set MOZ_LOG=DocLoader:5
+// set MOZ_LOG_FILE=debug.log
+//
+// this enables LogLevel::Debug level information and places all output in
+// the file 'debug.log'.
+//
+mozilla::LazyLogModule gDocLoaderLog("DocLoader");
+
+
+#if defined(DEBUG)
+void GetURIStringFromRequest(nsIRequest* request, nsACString &name)
+{
+ if (request)
+ request->GetName(name);
+ else
+ name.AssignLiteral("???");
+}
+#endif /* DEBUG */
+
+
+
+void
+nsDocLoader::RequestInfoHashInitEntry(PLDHashEntryHdr* entry,
+ const void* key)
+{
+ // Initialize the entry with placement new
+ new (entry) nsRequestInfo(key);
+}
+
+void
+nsDocLoader::RequestInfoHashClearEntry(PLDHashTable* table,
+ PLDHashEntryHdr* entry)
+{
+ nsRequestInfo* info = static_cast<nsRequestInfo *>(entry);
+ info->~nsRequestInfo();
+}
+
+// this is used for mListenerInfoList.Contains()
+template <>
+class nsDefaultComparator <nsDocLoader::nsListenerInfo, nsIWebProgressListener*> {
+ public:
+ bool Equals(const nsDocLoader::nsListenerInfo& aInfo,
+ nsIWebProgressListener* const& aListener) const {
+ nsCOMPtr<nsIWebProgressListener> listener =
+ do_QueryReferent(aInfo.mWeakListener);
+ return aListener == listener;
+ }
+};
+
+/* static */ const PLDHashTableOps nsDocLoader::sRequestInfoHashOps =
+{
+ PLDHashTable::HashVoidPtrKeyStub,
+ PLDHashTable::MatchEntryStub,
+ PLDHashTable::MoveEntryStub,
+ nsDocLoader::RequestInfoHashClearEntry,
+ nsDocLoader::RequestInfoHashInitEntry
+};
+
+nsDocLoader::nsDocLoader()
+ : mParent(nullptr),
+ mCurrentSelfProgress(0),
+ mMaxSelfProgress(0),
+ mCurrentTotalProgress(0),
+ mMaxTotalProgress(0),
+ mRequestInfoHash(&sRequestInfoHashOps, sizeof(nsRequestInfo)),
+ mCompletedTotalProgress(0),
+ mIsLoadingDocument(false),
+ mIsRestoringDocument(false),
+ mDontFlushLayout(false),
+ mIsFlushingLayout(false)
+{
+ ClearInternalProgress();
+
+ MOZ_LOG(gDocLoaderLog, LogLevel::Debug,
+ ("DocLoader:%p: created.\n", this));
+}
+
+nsresult
+nsDocLoader::SetDocLoaderParent(nsDocLoader *aParent)
+{
+ mParent = aParent;
+ return NS_OK;
+}
+
+nsresult
+nsDocLoader::Init()
+{
+ nsresult rv = NS_NewLoadGroup(getter_AddRefs(mLoadGroup), this);
+ if (NS_FAILED(rv)) return rv;
+
+ MOZ_LOG(gDocLoaderLog, LogLevel::Debug,
+ ("DocLoader:%p: load group %x.\n", this, mLoadGroup.get()));
+
+ return NS_OK;
+}
+
+nsDocLoader::~nsDocLoader()
+{
+ /*
+ |ClearWeakReferences()| here is intended to prevent people holding weak references
+ from re-entering this destructor since |QueryReferent()| will |AddRef()| me, and the
+ subsequent |Release()| will try to destroy me. At this point there should be only
+ weak references remaining (otherwise, we wouldn't be getting destroyed).
+
+ An alternative would be incrementing our refcount (consider it a compressed flag
+ saying "Don't re-destroy."). I haven't yet decided which is better. [scc]
+ */
+ // XXXbz now that NS_IMPL_RELEASE stabilizes by setting refcount to 1, is
+ // this needed?
+ ClearWeakReferences();
+
+ Destroy();
+
+ MOZ_LOG(gDocLoaderLog, LogLevel::Debug,
+ ("DocLoader:%p: deleted.\n", this));
+}
+
+
+/*
+ * Implementation of ISupports methods...
+ */
+NS_IMPL_ADDREF(nsDocLoader)
+NS_IMPL_RELEASE(nsDocLoader)
+
+NS_INTERFACE_MAP_BEGIN(nsDocLoader)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIRequestObserver)
+ NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
+ NS_INTERFACE_MAP_ENTRY(nsIDocumentLoader)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+ NS_INTERFACE_MAP_ENTRY(nsIWebProgress)
+ NS_INTERFACE_MAP_ENTRY(nsIProgressEventSink)
+ NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
+ NS_INTERFACE_MAP_ENTRY(nsIChannelEventSink)
+ NS_INTERFACE_MAP_ENTRY(nsISecurityEventSink)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsPriority)
+ if (aIID.Equals(kThisImplCID))
+ foundInterface = static_cast<nsIDocumentLoader *>(this);
+ else
+NS_INTERFACE_MAP_END
+
+
+/*
+ * Implementation of nsIInterfaceRequestor methods...
+ */
+NS_IMETHODIMP nsDocLoader::GetInterface(const nsIID& aIID, void** aSink)
+{
+ nsresult rv = NS_ERROR_NO_INTERFACE;
+
+ NS_ENSURE_ARG_POINTER(aSink);
+
+ if(aIID.Equals(NS_GET_IID(nsILoadGroup))) {
+ *aSink = mLoadGroup;
+ NS_IF_ADDREF((nsISupports*)*aSink);
+ rv = NS_OK;
+ } else {
+ rv = QueryInterface(aIID, aSink);
+ }
+
+ return rv;
+}
+
+/* static */
+already_AddRefed<nsDocLoader>
+nsDocLoader::GetAsDocLoader(nsISupports* aSupports)
+{
+ RefPtr<nsDocLoader> ret = do_QueryObject(aSupports);
+ return ret.forget();
+}
+
+/* static */
+nsresult
+nsDocLoader::AddDocLoaderAsChildOfRoot(nsDocLoader* aDocLoader)
+{
+ nsresult rv;
+ nsCOMPtr<nsIDocumentLoader> docLoaderService =
+ do_GetService(NS_DOCUMENTLOADER_SERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<nsDocLoader> rootDocLoader = GetAsDocLoader(docLoaderService);
+ NS_ENSURE_TRUE(rootDocLoader, NS_ERROR_UNEXPECTED);
+
+ return rootDocLoader->AddChildLoader(aDocLoader);
+}
+
+NS_IMETHODIMP
+nsDocLoader::Stop(void)
+{
+ nsresult rv = NS_OK;
+
+ MOZ_LOG(gDocLoaderLog, LogLevel::Debug,
+ ("DocLoader:%p: Stop() called\n", this));
+
+ NS_OBSERVER_ARRAY_NOTIFY_XPCOM_OBSERVERS(mChildList, nsDocLoader, Stop, ());
+
+ if (mLoadGroup)
+ rv = mLoadGroup->Cancel(NS_BINDING_ABORTED);
+
+ // Don't report that we're flushing layout so IsBusy returns false after a
+ // Stop call.
+ mIsFlushingLayout = false;
+
+ // Clear out mChildrenInOnload. We want to make sure to fire our
+ // onload at this point, and there's no issue with mChildrenInOnload
+ // after this, since mDocumentRequest will be null after the
+ // DocLoaderIsEmpty() call.
+ mChildrenInOnload.Clear();
+
+ // Make sure to call DocLoaderIsEmpty now so that we reset mDocumentRequest,
+ // etc, as needed. We could be getting into here from a subframe onload, in
+ // which case the call to DocLoaderIsEmpty() is coming but hasn't quite
+ // happened yet, Canceling the loadgroup did nothing (because it was already
+ // empty), and we're about to start a new load (which is what triggered this
+ // Stop() call).
+
+ // XXXbz If the child frame loadgroups were requests in mLoadgroup, I suspect
+ // we wouldn't need the call here....
+
+ NS_ASSERTION(!IsBusy(), "Shouldn't be busy here");
+ DocLoaderIsEmpty(false);
+
+ return rv;
+}
+
+
+bool
+nsDocLoader::IsBusy()
+{
+ nsresult rv;
+
+ //
+ // A document loader is busy if either:
+ //
+ // 1. One of its children is in the middle of an onload handler. Note that
+ // the handler may have already removed this child from mChildList!
+ // 2. It is currently loading a document and either has parts of it still
+ // loading, or has a busy child docloader.
+ // 3. It's currently flushing layout in DocLoaderIsEmpty().
+ //
+
+ if (mChildrenInOnload.Count() || mIsFlushingLayout) {
+ return true;
+ }
+
+ /* Is this document loader busy? */
+ if (!mIsLoadingDocument) {
+ return false;
+ }
+
+ bool busy;
+ rv = mLoadGroup->IsPending(&busy);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+ if (busy) {
+ return true;
+ }
+
+ /* check its child document loaders... */
+ uint32_t count = mChildList.Length();
+ for (uint32_t i=0; i < count; i++) {
+ nsIDocumentLoader* loader = ChildAt(i);
+
+ // This is a safe cast, because we only put nsDocLoader objects into the
+ // array
+ if (loader && static_cast<nsDocLoader*>(loader)->IsBusy())
+ return true;
+ }
+
+ return false;
+}
+
+NS_IMETHODIMP
+nsDocLoader::GetContainer(nsISupports** aResult)
+{
+ NS_ADDREF(*aResult = static_cast<nsIDocumentLoader*>(this));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocLoader::GetLoadGroup(nsILoadGroup** aResult)
+{
+ nsresult rv = NS_OK;
+
+ if (nullptr == aResult) {
+ rv = NS_ERROR_NULL_POINTER;
+ } else {
+ *aResult = mLoadGroup;
+ NS_IF_ADDREF(*aResult);
+ }
+ return rv;
+}
+
+void
+nsDocLoader::Destroy()
+{
+ Stop();
+
+ // Remove the document loader from the parent list of loaders...
+ if (mParent)
+ {
+ DebugOnly<nsresult> rv = mParent->RemoveChildLoader(this);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "RemoveChildLoader failed");
+ }
+
+ // Release all the information about network requests...
+ ClearRequestInfoHash();
+
+ mListenerInfoList.Clear();
+ mListenerInfoList.Compact();
+
+ mDocumentRequest = nullptr;
+
+ if (mLoadGroup)
+ mLoadGroup->SetGroupObserver(nullptr);
+
+ DestroyChildren();
+}
+
+void
+nsDocLoader::DestroyChildren()
+{
+ uint32_t count = mChildList.Length();
+ // if the doc loader still has children...we need to enumerate the
+ // children and make them null out their back ptr to the parent doc
+ // loader
+ for (uint32_t i=0; i < count; i++)
+ {
+ nsIDocumentLoader* loader = ChildAt(i);
+
+ if (loader) {
+ // This is a safe cast, as we only put nsDocLoader objects into the
+ // array
+ DebugOnly<nsresult> rv =
+ static_cast<nsDocLoader*>(loader)->SetDocLoaderParent(nullptr);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "SetDocLoaderParent failed");
+ }
+ }
+ mChildList.Clear();
+}
+
+NS_IMETHODIMP
+nsDocLoader::OnStartRequest(nsIRequest *request, nsISupports *aCtxt)
+{
+ // called each time a request is added to the group.
+
+ if (MOZ_LOG_TEST(gDocLoaderLog, LogLevel::Debug)) {
+ nsAutoCString name;
+ request->GetName(name);
+
+ uint32_t count = 0;
+ if (mLoadGroup)
+ mLoadGroup->GetActiveCount(&count);
+
+ MOZ_LOG(gDocLoaderLog, LogLevel::Debug,
+ ("DocLoader:%p: OnStartRequest[%p](%s) mIsLoadingDocument=%s, %u active URLs",
+ this, request, name.get(),
+ (mIsLoadingDocument ? "true" : "false"),
+ count));
+ }
+
+ bool bJustStartedLoading = false;
+
+ nsLoadFlags loadFlags = 0;
+ request->GetLoadFlags(&loadFlags);
+
+ if (!mIsLoadingDocument && (loadFlags & nsIChannel::LOAD_DOCUMENT_URI)) {
+ bJustStartedLoading = true;
+ mIsLoadingDocument = true;
+ ClearInternalProgress(); // only clear our progress if we are starting a new load....
+ }
+
+ //
+ // Create a new nsRequestInfo for the request that is starting to
+ // load...
+ //
+ AddRequestInfo(request);
+
+ //
+ // Only fire a doStartDocumentLoad(...) if the document loader
+ // has initiated a load... Otherwise, this notification has
+ // resulted from a request being added to the load group.
+ //
+ if (mIsLoadingDocument) {
+ if (loadFlags & nsIChannel::LOAD_DOCUMENT_URI) {
+ //
+ // Make sure that the document channel is null at this point...
+ // (unless its been redirected)
+ //
+ NS_ASSERTION((loadFlags & nsIChannel::LOAD_REPLACE) ||
+ !(mDocumentRequest.get()),
+ "Overwriting an existing document channel!");
+
+ // This request is associated with the entire document...
+ mDocumentRequest = request;
+ mLoadGroup->SetDefaultLoadRequest(request);
+
+ // Only fire the start document load notification for the first
+ // document URI... Do not fire it again for redirections
+ //
+ if (bJustStartedLoading) {
+ // Update the progress status state
+ mProgressStateFlags = nsIWebProgressListener::STATE_START;
+
+ // Fire the start document load notification
+ doStartDocumentLoad();
+ return NS_OK;
+ }
+ }
+ }
+
+ NS_ASSERTION(!mIsLoadingDocument || mDocumentRequest,
+ "mDocumentRequest MUST be set for the duration of a page load!");
+
+ doStartURLLoad(request);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocLoader::OnStopRequest(nsIRequest *aRequest,
+ nsISupports *aCtxt,
+ nsresult aStatus)
+{
+ nsresult rv = NS_OK;
+
+ if (MOZ_LOG_TEST(gDocLoaderLog, LogLevel::Debug)) {
+ nsAutoCString name;
+ aRequest->GetName(name);
+
+ uint32_t count = 0;
+ if (mLoadGroup)
+ mLoadGroup->GetActiveCount(&count);
+
+ MOZ_LOG(gDocLoaderLog, LogLevel::Debug,
+ ("DocLoader:%p: OnStopRequest[%p](%s) status=%x mIsLoadingDocument=%s, %u active URLs",
+ this, aRequest, name.get(),
+ aStatus, (mIsLoadingDocument ? "true" : "false"),
+ count));
+ }
+
+ bool bFireTransferring = false;
+
+ //
+ // Set the Maximum progress to the same value as the current progress.
+ // Since the URI has finished loading, all the data is there. Also,
+ // this will allow a more accurate estimation of the max progress (in case
+ // the old value was unknown ie. -1)
+ //
+ nsRequestInfo *info = GetRequestInfo(aRequest);
+ if (info) {
+ // Null out mLastStatus now so we don't find it when looking for
+ // status from now on. This destroys the nsStatusInfo and hence
+ // removes it from our list.
+ info->mLastStatus = nullptr;
+
+ int64_t oldMax = info->mMaxProgress;
+
+ info->mMaxProgress = info->mCurrentProgress;
+
+ //
+ // If a request whose content-length was previously unknown has just
+ // finished loading, then use this new data to try to calculate a
+ // mMaxSelfProgress...
+ //
+ if ((oldMax < int64_t(0)) && (mMaxSelfProgress < int64_t(0))) {
+ mMaxSelfProgress = CalculateMaxProgress();
+ }
+
+ // As we know the total progress of this request now, save it to be part
+ // of CalculateMaxProgress() result. We need to remove the info from the
+ // hash, see bug 480713.
+ mCompletedTotalProgress += info->mMaxProgress;
+
+ //
+ // Determine whether a STATE_TRANSFERRING notification should be
+ // 'synthesized'.
+ //
+ // If nsRequestInfo::mMaxProgress (as stored in oldMax) and
+ // nsRequestInfo::mCurrentProgress are both 0, then the
+ // STATE_TRANSFERRING notification has not been fired yet...
+ //
+ if ((oldMax == 0) && (info->mCurrentProgress == 0)) {
+ nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
+
+ // Only fire a TRANSFERRING notification if the request is also a
+ // channel -- data transfer requires a nsIChannel!
+ //
+ if (channel) {
+ if (NS_SUCCEEDED(aStatus)) {
+ bFireTransferring = true;
+ }
+ //
+ // If the request failed (for any reason other than being
+ // redirected or retargeted), the TRANSFERRING notification can
+ // still be fired if a HTTP connection was established to a server.
+ //
+ else if (aStatus != NS_BINDING_REDIRECTED &&
+ aStatus != NS_BINDING_RETARGETED) {
+ //
+ // Only if the load has been targeted (see bug 268483)...
+ //
+ uint32_t lf;
+ channel->GetLoadFlags(&lf);
+ if (lf & nsIChannel::LOAD_TARGETED) {
+ nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aRequest));
+ if (httpChannel) {
+ uint32_t responseCode;
+ rv = httpChannel->GetResponseStatus(&responseCode);
+ if (NS_SUCCEEDED(rv)) {
+ //
+ // A valid server status indicates that a connection was
+ // established to the server... So, fire the notification
+ // even though a failure occurred later...
+ //
+ bFireTransferring = true;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (bFireTransferring) {
+ // Send a STATE_TRANSFERRING notification for the request.
+ int32_t flags;
+
+ flags = nsIWebProgressListener::STATE_TRANSFERRING |
+ nsIWebProgressListener::STATE_IS_REQUEST;
+ //
+ // Move the WebProgress into the STATE_TRANSFERRING state if necessary...
+ //
+ if (mProgressStateFlags & nsIWebProgressListener::STATE_START) {
+ mProgressStateFlags = nsIWebProgressListener::STATE_TRANSFERRING;
+
+ // Send STATE_TRANSFERRING for the document too...
+ flags |= nsIWebProgressListener::STATE_IS_DOCUMENT;
+ }
+
+ FireOnStateChange(this, aRequest, flags, NS_OK);
+ }
+
+ //
+ // Fire the OnStateChange(...) notification for stop request
+ //
+ doStopURLLoad(aRequest, aStatus);
+
+ // Clear this request out of the hash to avoid bypass of FireOnStateChange
+ // when address of the request is reused.
+ RemoveRequestInfo(aRequest);
+
+ //
+ // Only fire the DocLoaderIsEmpty(...) if the document loader has initiated a
+ // load. This will handle removing the request from our hashtable as needed.
+ //
+ if (mIsLoadingDocument) {
+ nsCOMPtr<nsIDocShell> ds = do_QueryInterface(static_cast<nsIRequestObserver*>(this));
+ bool doNotFlushLayout = false;
+ if (ds) {
+ // Don't do unexpected layout flushes while we're in process of restoring
+ // a document from the bfcache.
+ ds->GetRestoringDocument(&doNotFlushLayout);
+ }
+ DocLoaderIsEmpty(!doNotFlushLayout);
+ }
+
+ return NS_OK;
+}
+
+
+nsresult nsDocLoader::RemoveChildLoader(nsDocLoader* aChild)
+{
+ nsresult rv = mChildList.RemoveElement(aChild) ? NS_OK : NS_ERROR_FAILURE;
+ if (NS_SUCCEEDED(rv)) {
+ rv = aChild->SetDocLoaderParent(nullptr);
+ }
+ return rv;
+}
+
+nsresult nsDocLoader::AddChildLoader(nsDocLoader* aChild)
+{
+ nsresult rv = mChildList.AppendElement(aChild) ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+ if (NS_SUCCEEDED(rv)) {
+ rv = aChild->SetDocLoaderParent(this);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsDocLoader::GetDocumentChannel(nsIChannel ** aChannel)
+{
+ if (!mDocumentRequest) {
+ *aChannel = nullptr;
+ return NS_OK;
+ }
+
+ return CallQueryInterface(mDocumentRequest, aChannel);
+}
+
+
+void nsDocLoader::DocLoaderIsEmpty(bool aFlushLayout)
+{
+ if (mIsLoadingDocument) {
+ /* In the unimagineably rude circumstance that onload event handlers
+ triggered by this function actually kill the window ... ok, it's
+ not unimagineable; it's happened ... this deathgrip keeps this object
+ alive long enough to survive this function call. */
+ nsCOMPtr<nsIDocumentLoader> kungFuDeathGrip(this);
+
+ // Don't flush layout if we're still busy.
+ if (IsBusy()) {
+ return;
+ }
+
+ NS_ASSERTION(!mIsFlushingLayout, "Someone screwed up");
+ NS_ASSERTION(mDocumentRequest, "No Document Request!");
+
+ // The load group for this DocumentLoader is idle. Flush if we need to.
+ if (aFlushLayout && !mDontFlushLayout) {
+ nsCOMPtr<nsIDOMDocument> domDoc = do_GetInterface(GetAsSupports(this));
+ nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc);
+ if (doc) {
+ // We start loads from style resolution, so we need to flush out style
+ // no matter what. If we have user fonts, we also need to flush layout,
+ // since the reflow is what starts font loads.
+ mozFlushType flushType = Flush_Style;
+ nsIPresShell* shell = doc->GetShell();
+ if (shell) {
+ // Be safe in case this presshell is in teardown now
+ nsPresContext* presContext = shell->GetPresContext();
+ if (presContext && presContext->GetUserFontSet()) {
+ flushType = Flush_Layout;
+ }
+ }
+ mDontFlushLayout = mIsFlushingLayout = true;
+ doc->FlushPendingNotifications(flushType);
+ mDontFlushLayout = mIsFlushingLayout = false;
+ }
+ }
+
+ // And now check whether we're really busy; that might have changed with
+ // the layout flush.
+ // Note, mDocumentRequest can be null if the flushing above re-entered this
+ // method.
+ if (!IsBusy() && mDocumentRequest) {
+ // Clear out our request info hash, now that our load really is done and
+ // we don't need it anymore to CalculateMaxProgress().
+ ClearInternalProgress();
+
+ MOZ_LOG(gDocLoaderLog, LogLevel::Debug,
+ ("DocLoader:%p: Is now idle...\n", this));
+
+ nsCOMPtr<nsIRequest> docRequest = mDocumentRequest;
+
+ mDocumentRequest = nullptr;
+ mIsLoadingDocument = false;
+
+ // Update the progress status state - the document is done
+ mProgressStateFlags = nsIWebProgressListener::STATE_STOP;
+
+
+ nsresult loadGroupStatus = NS_OK;
+ mLoadGroup->GetStatus(&loadGroupStatus);
+
+ //
+ // New code to break the circular reference between
+ // the load group and the docloader...
+ //
+ mLoadGroup->SetDefaultLoadRequest(nullptr);
+
+ // Take a ref to our parent now so that we can call DocLoaderIsEmpty() on
+ // it even if our onload handler removes us from the docloader tree.
+ RefPtr<nsDocLoader> parent = mParent;
+
+ // Note that if calling ChildEnteringOnload() on the parent returns false
+ // then calling our onload handler is not safe. That can only happen on
+ // OOM, so that's ok.
+ if (!parent || parent->ChildEnteringOnload(this)) {
+ // Do nothing with our state after firing the
+ // OnEndDocumentLoad(...). The document loader may be loading a *new*
+ // document - if LoadDocument() was called from a handler!
+ //
+ doStopDocumentLoad(docRequest, loadGroupStatus);
+
+ if (parent) {
+ parent->ChildDoneWithOnload(this);
+ }
+ }
+ }
+ }
+}
+
+void nsDocLoader::doStartDocumentLoad(void)
+{
+
+#if defined(DEBUG)
+ nsAutoCString buffer;
+
+ GetURIStringFromRequest(mDocumentRequest, buffer);
+ MOZ_LOG(gDocLoaderLog, LogLevel::Debug,
+ ("DocLoader:%p: ++ Firing OnStateChange for start document load (...)."
+ "\tURI: %s \n",
+ this, buffer.get()));
+#endif /* DEBUG */
+
+ // Fire an OnStatus(...) notification STATE_START. This indicates
+ // that the document represented by mDocumentRequest has started to
+ // load...
+ FireOnStateChange(this,
+ mDocumentRequest,
+ nsIWebProgressListener::STATE_START |
+ nsIWebProgressListener::STATE_IS_DOCUMENT |
+ nsIWebProgressListener::STATE_IS_REQUEST |
+ nsIWebProgressListener::STATE_IS_WINDOW |
+ nsIWebProgressListener::STATE_IS_NETWORK,
+ NS_OK);
+}
+
+void nsDocLoader::doStartURLLoad(nsIRequest *request)
+{
+#if defined(DEBUG)
+ nsAutoCString buffer;
+
+ GetURIStringFromRequest(request, buffer);
+ MOZ_LOG(gDocLoaderLog, LogLevel::Debug,
+ ("DocLoader:%p: ++ Firing OnStateChange start url load (...)."
+ "\tURI: %s\n",
+ this, buffer.get()));
+#endif /* DEBUG */
+
+ FireOnStateChange(this,
+ request,
+ nsIWebProgressListener::STATE_START |
+ nsIWebProgressListener::STATE_IS_REQUEST,
+ NS_OK);
+}
+
+void nsDocLoader::doStopURLLoad(nsIRequest *request, nsresult aStatus)
+{
+#if defined(DEBUG)
+ nsAutoCString buffer;
+
+ GetURIStringFromRequest(request, buffer);
+ MOZ_LOG(gDocLoaderLog, LogLevel::Debug,
+ ("DocLoader:%p: ++ Firing OnStateChange for end url load (...)."
+ "\tURI: %s status=%x\n",
+ this, buffer.get(), aStatus));
+#endif /* DEBUG */
+
+ FireOnStateChange(this,
+ request,
+ nsIWebProgressListener::STATE_STOP |
+ nsIWebProgressListener::STATE_IS_REQUEST,
+ aStatus);
+
+ // Fire a status change message for the most recent unfinished
+ // request to make sure that the displayed status is not outdated.
+ if (!mStatusInfoList.isEmpty()) {
+ nsStatusInfo* statusInfo = mStatusInfoList.getFirst();
+ FireOnStatusChange(this, statusInfo->mRequest,
+ statusInfo->mStatusCode,
+ statusInfo->mStatusMessage.get());
+ }
+}
+
+void nsDocLoader::doStopDocumentLoad(nsIRequest *request,
+ nsresult aStatus)
+{
+#if defined(DEBUG)
+ nsAutoCString buffer;
+
+ GetURIStringFromRequest(request, buffer);
+ MOZ_LOG(gDocLoaderLog, LogLevel::Debug,
+ ("DocLoader:%p: ++ Firing OnStateChange for end document load (...)."
+ "\tURI: %s Status=%x\n",
+ this, buffer.get(), aStatus));
+#endif /* DEBUG */
+
+ // Firing STATE_STOP|STATE_IS_DOCUMENT will fire onload handlers.
+ // Grab our parent chain before doing that so we can still dispatch
+ // STATE_STOP|STATE_IS_WINDW_STATE_IS_NETWORK to them all, even if
+ // the onload handlers rearrange the docshell tree.
+ WebProgressList list;
+ GatherAncestorWebProgresses(list);
+
+ //
+ // Fire an OnStateChange(...) notification indicating the the
+ // current document has finished loading...
+ //
+ int32_t flags = nsIWebProgressListener::STATE_STOP |
+ nsIWebProgressListener::STATE_IS_DOCUMENT;
+ for (uint32_t i = 0; i < list.Length(); ++i) {
+ list[i]->DoFireOnStateChange(this, request, flags, aStatus);
+ }
+
+ //
+ // Fire a final OnStateChange(...) notification indicating the the
+ // current document has finished loading...
+ //
+ flags = nsIWebProgressListener::STATE_STOP |
+ nsIWebProgressListener::STATE_IS_WINDOW |
+ nsIWebProgressListener::STATE_IS_NETWORK;
+ for (uint32_t i = 0; i < list.Length(); ++i) {
+ list[i]->DoFireOnStateChange(this, request, flags, aStatus);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////
+// The following section contains support for nsIWebProgress and related stuff
+////////////////////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP
+nsDocLoader::AddProgressListener(nsIWebProgressListener *aListener,
+ uint32_t aNotifyMask)
+{
+ if (mListenerInfoList.Contains(aListener)) {
+ // The listener is already registered!
+ return NS_ERROR_FAILURE;
+ }
+
+ nsWeakPtr listener = do_GetWeakReference(aListener);
+ if (!listener) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ return mListenerInfoList.AppendElement(nsListenerInfo(listener, aNotifyMask)) ?
+ NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+NS_IMETHODIMP
+nsDocLoader::RemoveProgressListener(nsIWebProgressListener *aListener)
+{
+ return mListenerInfoList.RemoveElement(aListener) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsDocLoader::GetDOMWindow(mozIDOMWindowProxy **aResult)
+{
+ return CallGetInterface(this, aResult);
+}
+
+NS_IMETHODIMP
+nsDocLoader::GetDOMWindowID(uint64_t *aResult)
+{
+ *aResult = 0;
+
+ nsCOMPtr<mozIDOMWindowProxy> window;
+ nsresult rv = GetDOMWindow(getter_AddRefs(window));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsPIDOMWindowOuter> piwindow = nsPIDOMWindowOuter::From(window);
+ NS_ENSURE_STATE(piwindow);
+
+ MOZ_ASSERT(piwindow->IsOuterWindow());
+ *aResult = piwindow->WindowID();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocLoader::GetIsTopLevel(bool *aResult)
+{
+ *aResult = false;
+
+ nsCOMPtr<mozIDOMWindowProxy> window;
+ GetDOMWindow(getter_AddRefs(window));
+ if (window) {
+ nsCOMPtr<nsPIDOMWindowOuter> piwindow = nsPIDOMWindowOuter::From(window);
+ NS_ENSURE_STATE(piwindow);
+
+ nsCOMPtr<nsPIDOMWindowOuter> topWindow = piwindow->GetTop();
+ *aResult = piwindow == topWindow;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocLoader::GetIsLoadingDocument(bool *aIsLoadingDocument)
+{
+ *aIsLoadingDocument = mIsLoadingDocument;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocLoader::GetLoadType(uint32_t *aLoadType)
+{
+ *aLoadType = 0;
+
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+int64_t nsDocLoader::GetMaxTotalProgress()
+{
+ int64_t newMaxTotal = 0;
+
+ uint32_t count = mChildList.Length();
+ for (uint32_t i=0; i < count; i++)
+ {
+ int64_t individualProgress = 0;
+ nsIDocumentLoader* docloader = ChildAt(i);
+ if (docloader)
+ {
+ // Cast is safe since all children are nsDocLoader too
+ individualProgress = ((nsDocLoader *) docloader)->GetMaxTotalProgress();
+ }
+ if (individualProgress < int64_t(0)) // if one of the elements doesn't know it's size
+ // then none of them do
+ {
+ newMaxTotal = int64_t(-1);
+ break;
+ }
+ else
+ newMaxTotal += individualProgress;
+ }
+
+ int64_t progress = -1;
+ if (mMaxSelfProgress >= int64_t(0) && newMaxTotal >= int64_t(0))
+ progress = newMaxTotal + mMaxSelfProgress;
+
+ return progress;
+}
+
+////////////////////////////////////////////////////////////////////////////////////
+// The following section contains support for nsIProgressEventSink which is used to
+// pass progress and status between the actual request and the doc loader. The doc loader
+// then turns around and makes the right web progress calls based on this information.
+////////////////////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP nsDocLoader::OnProgress(nsIRequest *aRequest, nsISupports* ctxt,
+ int64_t aProgress, int64_t aProgressMax)
+{
+ int64_t progressDelta = 0;
+
+ //
+ // Update the RequestInfo entry with the new progress data
+ //
+ if (nsRequestInfo* info = GetRequestInfo(aRequest)) {
+ // Update info->mCurrentProgress before we call FireOnStateChange,
+ // since that can make the "info" pointer invalid.
+ int64_t oldCurrentProgress = info->mCurrentProgress;
+ progressDelta = aProgress - oldCurrentProgress;
+ info->mCurrentProgress = aProgress;
+
+ // suppress sending STATE_TRANSFERRING if this is upload progress (see bug 240053)
+ if (!info->mUploading && (int64_t(0) == oldCurrentProgress) && (int64_t(0) == info->mMaxProgress)) {
+ //
+ // If we receive an OnProgress event from a toplevel channel that the URI Loader
+ // has not yet targeted, then we must suppress the event. This is necessary to
+ // ensure that webprogresslisteners do not get confused when the channel is
+ // finally targeted. See bug 257308.
+ //
+ nsLoadFlags lf = 0;
+ aRequest->GetLoadFlags(&lf);
+ if ((lf & nsIChannel::LOAD_DOCUMENT_URI) && !(lf & nsIChannel::LOAD_TARGETED)) {
+ MOZ_LOG(gDocLoaderLog, LogLevel::Debug,
+ ("DocLoader:%p Ignoring OnProgress while load is not targeted\n", this));
+ return NS_OK;
+ }
+
+ //
+ // This is the first progress notification for the entry. If
+ // (aMaxProgress != -1) then the content-length of the data is known,
+ // so update mMaxSelfProgress... Otherwise, set it to -1 to indicate
+ // that the content-length is no longer known.
+ //
+ if (aProgressMax != -1) {
+ mMaxSelfProgress += aProgressMax;
+ info->mMaxProgress = aProgressMax;
+ } else {
+ mMaxSelfProgress = int64_t(-1);
+ info->mMaxProgress = int64_t(-1);
+ }
+
+ // Send a STATE_TRANSFERRING notification for the request.
+ int32_t flags;
+
+ flags = nsIWebProgressListener::STATE_TRANSFERRING |
+ nsIWebProgressListener::STATE_IS_REQUEST;
+ //
+ // Move the WebProgress into the STATE_TRANSFERRING state if necessary...
+ //
+ if (mProgressStateFlags & nsIWebProgressListener::STATE_START) {
+ mProgressStateFlags = nsIWebProgressListener::STATE_TRANSFERRING;
+
+ // Send STATE_TRANSFERRING for the document too...
+ flags |= nsIWebProgressListener::STATE_IS_DOCUMENT;
+ }
+
+ FireOnStateChange(this, aRequest, flags, NS_OK);
+ }
+
+ // Update our overall current progress count.
+ mCurrentSelfProgress += progressDelta;
+ }
+ //
+ // The request is not part of the load group, so ignore its progress
+ // information...
+ //
+ else {
+#if defined(DEBUG)
+ nsAutoCString buffer;
+
+ GetURIStringFromRequest(aRequest, buffer);
+ MOZ_LOG(gDocLoaderLog, LogLevel::Debug,
+ ("DocLoader:%p OOPS - No Request Info for: %s\n",
+ this, buffer.get()));
+#endif /* DEBUG */
+
+ return NS_OK;
+ }
+
+ //
+ // Fire progress notifications out to any registered nsIWebProgressListeners
+ //
+ FireOnProgressChange(this, aRequest, aProgress, aProgressMax, progressDelta,
+ mCurrentTotalProgress, mMaxTotalProgress);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDocLoader::OnStatus(nsIRequest* aRequest, nsISupports* ctxt,
+ nsresult aStatus, const char16_t* aStatusArg)
+{
+ //
+ // Fire progress notifications out to any registered nsIWebProgressListeners
+ //
+ if (aStatus != NS_OK) {
+ // Remember the current status for this request
+ nsRequestInfo *info;
+ info = GetRequestInfo(aRequest);
+ if (info) {
+ bool uploading = (aStatus == NS_NET_STATUS_WRITING ||
+ aStatus == NS_NET_STATUS_SENDING_TO);
+ // If switching from uploading to downloading (or vice versa), then we
+ // need to reset our progress counts. This is designed with HTTP form
+ // submission in mind, where an upload is performed followed by download
+ // of possibly several documents.
+ if (info->mUploading != uploading) {
+ mCurrentSelfProgress = mMaxSelfProgress = 0;
+ mCurrentTotalProgress = mMaxTotalProgress = 0;
+ mCompletedTotalProgress = 0;
+ info->mUploading = uploading;
+ info->mCurrentProgress = 0;
+ info->mMaxProgress = 0;
+ }
+ }
+
+ nsCOMPtr<nsIStringBundleService> sbs =
+ mozilla::services::GetStringBundleService();
+ if (!sbs)
+ return NS_ERROR_FAILURE;
+ nsXPIDLString msg;
+ nsresult rv = sbs->FormatStatusMessage(aStatus, aStatusArg,
+ getter_Copies(msg));
+ if (NS_FAILED(rv))
+ return rv;
+
+ // Keep around the message. In case a request finishes, we need to make sure
+ // to send the status message of another request to our user to that we
+ // don't display, for example, "Transferring" messages for requests that are
+ // already done.
+ if (info) {
+ if (!info->mLastStatus) {
+ info->mLastStatus = new nsStatusInfo(aRequest);
+ } else {
+ // We're going to move it to the front of the list, so remove
+ // it from wherever it is now.
+ info->mLastStatus->remove();
+ }
+ info->mLastStatus->mStatusMessage = msg;
+ info->mLastStatus->mStatusCode = aStatus;
+ // Put the info at the front of the list
+ mStatusInfoList.insertFront(info->mLastStatus);
+ }
+ FireOnStatusChange(this, aRequest, aStatus, msg);
+ }
+ return NS_OK;
+}
+
+void nsDocLoader::ClearInternalProgress()
+{
+ ClearRequestInfoHash();
+
+ mCurrentSelfProgress = mMaxSelfProgress = 0;
+ mCurrentTotalProgress = mMaxTotalProgress = 0;
+ mCompletedTotalProgress = 0;
+
+ mProgressStateFlags = nsIWebProgressListener::STATE_STOP;
+}
+
+/**
+ * |_code| is executed for every listener matching |_flag|
+ * |listener| should be used inside |_code| as the nsIWebProgressListener var.
+ */
+#define NOTIFY_LISTENERS(_flag, _code) \
+PR_BEGIN_MACRO \
+ nsCOMPtr<nsIWebProgressListener> listener; \
+ ListenerArray::BackwardIterator iter(mListenerInfoList); \
+ while (iter.HasMore()) { \
+ nsListenerInfo &info = iter.GetNext(); \
+ if (!(info.mNotifyMask & (_flag))) { \
+ continue; \
+ } \
+ listener = do_QueryReferent(info.mWeakListener); \
+ if (!listener) { \
+ iter.Remove(); \
+ continue; \
+ } \
+ _code \
+ } \
+ mListenerInfoList.Compact(); \
+PR_END_MACRO
+
+void nsDocLoader::FireOnProgressChange(nsDocLoader *aLoadInitiator,
+ nsIRequest *request,
+ int64_t aProgress,
+ int64_t aProgressMax,
+ int64_t aProgressDelta,
+ int64_t aTotalProgress,
+ int64_t aMaxTotalProgress)
+{
+ if (mIsLoadingDocument) {
+ mCurrentTotalProgress += aProgressDelta;
+ mMaxTotalProgress = GetMaxTotalProgress();
+
+ aTotalProgress = mCurrentTotalProgress;
+ aMaxTotalProgress = mMaxTotalProgress;
+ }
+
+#if defined(DEBUG)
+ nsAutoCString buffer;
+
+ GetURIStringFromRequest(request, buffer);
+ MOZ_LOG(gDocLoaderLog, LogLevel::Debug,
+ ("DocLoader:%p: Progress (%s): curSelf: %d maxSelf: %d curTotal: %d maxTotal %d\n",
+ this, buffer.get(), aProgress, aProgressMax, aTotalProgress, aMaxTotalProgress));
+#endif /* DEBUG */
+
+ NOTIFY_LISTENERS(nsIWebProgress::NOTIFY_PROGRESS,
+ // XXX truncates 64-bit to 32-bit
+ listener->OnProgressChange(aLoadInitiator,request,
+ int32_t(aProgress), int32_t(aProgressMax),
+ int32_t(aTotalProgress), int32_t(aMaxTotalProgress));
+ );
+
+ // Pass the notification up to the parent...
+ if (mParent) {
+ mParent->FireOnProgressChange(aLoadInitiator, request,
+ aProgress, aProgressMax,
+ aProgressDelta,
+ aTotalProgress, aMaxTotalProgress);
+ }
+}
+
+void nsDocLoader::GatherAncestorWebProgresses(WebProgressList& aList)
+{
+ for (nsDocLoader* loader = this; loader; loader = loader->mParent) {
+ aList.AppendElement(loader);
+ }
+}
+
+void nsDocLoader::FireOnStateChange(nsIWebProgress *aProgress,
+ nsIRequest *aRequest,
+ int32_t aStateFlags,
+ nsresult aStatus)
+{
+ WebProgressList list;
+ GatherAncestorWebProgresses(list);
+ for (uint32_t i = 0; i < list.Length(); ++i) {
+ list[i]->DoFireOnStateChange(aProgress, aRequest, aStateFlags, aStatus);
+ }
+}
+
+void nsDocLoader::DoFireOnStateChange(nsIWebProgress * const aProgress,
+ nsIRequest * const aRequest,
+ int32_t &aStateFlags,
+ const nsresult aStatus)
+{
+ //
+ // Remove the STATE_IS_NETWORK bit if necessary.
+ //
+ // The rule is to remove this bit, if the notification has been passed
+ // up from a child WebProgress, and the current WebProgress is already
+ // active...
+ //
+ if (mIsLoadingDocument &&
+ (aStateFlags & nsIWebProgressListener::STATE_IS_NETWORK) &&
+ (this != aProgress)) {
+ aStateFlags &= ~nsIWebProgressListener::STATE_IS_NETWORK;
+ }
+
+ // Add the STATE_RESTORING bit if necessary.
+ if (mIsRestoringDocument)
+ aStateFlags |= nsIWebProgressListener::STATE_RESTORING;
+
+#if defined(DEBUG)
+ nsAutoCString buffer;
+
+ GetURIStringFromRequest(aRequest, buffer);
+ MOZ_LOG(gDocLoaderLog, LogLevel::Debug,
+ ("DocLoader:%p: Status (%s): code: %x\n",
+ this, buffer.get(), aStateFlags));
+#endif /* DEBUG */
+
+ NS_ASSERTION(aRequest, "Firing OnStateChange(...) notification with a NULL request!");
+
+ NOTIFY_LISTENERS(((aStateFlags >> 16) & nsIWebProgress::NOTIFY_STATE_ALL),
+ listener->OnStateChange(aProgress, aRequest, aStateFlags, aStatus);
+ );
+}
+
+
+
+void
+nsDocLoader::FireOnLocationChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ nsIURI *aUri,
+ uint32_t aFlags)
+{
+ NOTIFY_LISTENERS(nsIWebProgress::NOTIFY_LOCATION,
+ MOZ_LOG(gDocLoaderLog, LogLevel::Debug, ("DocLoader [%p] calling %p->OnLocationChange", this, listener.get()));
+ listener->OnLocationChange(aWebProgress, aRequest, aUri, aFlags);
+ );
+
+ // Pass the notification up to the parent...
+ if (mParent) {
+ mParent->FireOnLocationChange(aWebProgress, aRequest, aUri, aFlags);
+ }
+}
+
+void
+nsDocLoader::FireOnStatusChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ nsresult aStatus,
+ const char16_t* aMessage)
+{
+ NOTIFY_LISTENERS(nsIWebProgress::NOTIFY_STATUS,
+ listener->OnStatusChange(aWebProgress, aRequest, aStatus, aMessage);
+ );
+
+ // Pass the notification up to the parent...
+ if (mParent) {
+ mParent->FireOnStatusChange(aWebProgress, aRequest, aStatus, aMessage);
+ }
+}
+
+bool
+nsDocLoader::RefreshAttempted(nsIWebProgress* aWebProgress,
+ nsIURI *aURI,
+ int32_t aDelay,
+ bool aSameURI)
+{
+ /*
+ * Returns true if the refresh may proceed,
+ * false if the refresh should be blocked.
+ */
+ bool allowRefresh = true;
+
+ NOTIFY_LISTENERS(nsIWebProgress::NOTIFY_REFRESH,
+ nsCOMPtr<nsIWebProgressListener2> listener2 =
+ do_QueryReferent(info.mWeakListener);
+ if (!listener2)
+ continue;
+
+ bool listenerAllowedRefresh;
+ nsresult listenerRV = listener2->OnRefreshAttempted(
+ aWebProgress, aURI, aDelay, aSameURI, &listenerAllowedRefresh);
+ if (NS_FAILED(listenerRV))
+ continue;
+
+ allowRefresh = allowRefresh && listenerAllowedRefresh;
+ );
+
+ // Pass the notification up to the parent...
+ if (mParent) {
+ allowRefresh = allowRefresh &&
+ mParent->RefreshAttempted(aWebProgress, aURI, aDelay, aSameURI);
+ }
+
+ return allowRefresh;
+}
+
+nsresult nsDocLoader::AddRequestInfo(nsIRequest *aRequest)
+{
+ if (!mRequestInfoHash.Add(aRequest, mozilla::fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ return NS_OK;
+}
+
+void nsDocLoader::RemoveRequestInfo(nsIRequest *aRequest)
+{
+ mRequestInfoHash.Remove(aRequest);
+}
+
+nsDocLoader::nsRequestInfo* nsDocLoader::GetRequestInfo(nsIRequest* aRequest)
+{
+ return static_cast<nsRequestInfo*>(mRequestInfoHash.Search(aRequest));
+}
+
+void nsDocLoader::ClearRequestInfoHash(void)
+{
+ mRequestInfoHash.Clear();
+}
+
+int64_t nsDocLoader::CalculateMaxProgress()
+{
+ int64_t max = mCompletedTotalProgress;
+ for (auto iter = mRequestInfoHash.Iter(); !iter.Done(); iter.Next()) {
+ auto info = static_cast<const nsRequestInfo*>(iter.Get());
+
+ if (info->mMaxProgress < info->mCurrentProgress) {
+ return int64_t(-1);
+ }
+ max += info->mMaxProgress;
+ }
+ return max;
+}
+
+NS_IMETHODIMP nsDocLoader::AsyncOnChannelRedirect(nsIChannel *aOldChannel,
+ nsIChannel *aNewChannel,
+ uint32_t aFlags,
+ nsIAsyncVerifyRedirectCallback *cb)
+{
+ if (aOldChannel)
+ {
+ nsLoadFlags loadFlags = 0;
+ int32_t stateFlags = nsIWebProgressListener::STATE_REDIRECTING |
+ nsIWebProgressListener::STATE_IS_REQUEST;
+
+ aOldChannel->GetLoadFlags(&loadFlags);
+ // If the document channel is being redirected, then indicate that the
+ // document is being redirected in the notification...
+ if (loadFlags & nsIChannel::LOAD_DOCUMENT_URI)
+ {
+ stateFlags |= nsIWebProgressListener::STATE_IS_DOCUMENT;
+
+#if defined(DEBUG)
+ nsCOMPtr<nsIRequest> request(do_QueryInterface(aOldChannel));
+ NS_ASSERTION(request == mDocumentRequest, "Wrong Document Channel");
+#endif /* DEBUG */
+ }
+
+ OnRedirectStateChange(aOldChannel, aNewChannel, aFlags, stateFlags);
+ FireOnStateChange(this, aOldChannel, stateFlags, NS_OK);
+ }
+
+ cb->OnRedirectVerifyCallback(NS_OK);
+ return NS_OK;
+}
+
+/*
+ * Implementation of nsISecurityEventSink method...
+ */
+
+NS_IMETHODIMP nsDocLoader::OnSecurityChange(nsISupports * aContext,
+ uint32_t aState)
+{
+ //
+ // Fire progress notifications out to any registered nsIWebProgressListeners.
+ //
+
+ nsCOMPtr<nsIRequest> request = do_QueryInterface(aContext);
+ nsIWebProgress* webProgress = static_cast<nsIWebProgress*>(this);
+
+ NOTIFY_LISTENERS(nsIWebProgress::NOTIFY_SECURITY,
+ listener->OnSecurityChange(webProgress, request, aState);
+ );
+
+ // Pass the notification up to the parent...
+ if (mParent) {
+ mParent->OnSecurityChange(aContext, aState);
+ }
+ return NS_OK;
+}
+
+/*
+ * Implementation of nsISupportsPriority methods...
+ *
+ * The priority of the DocLoader _is_ the priority of its LoadGroup.
+ *
+ * XXX(darin): Once we start storing loadgroups in loadgroups, this code will
+ * go away.
+ */
+
+NS_IMETHODIMP nsDocLoader::GetPriority(int32_t *aPriority)
+{
+ nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(mLoadGroup);
+ if (p)
+ return p->GetPriority(aPriority);
+
+ *aPriority = 0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDocLoader::SetPriority(int32_t aPriority)
+{
+ MOZ_LOG(gDocLoaderLog, LogLevel::Debug,
+ ("DocLoader:%p: SetPriority(%d) called\n", this, aPriority));
+
+ nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(mLoadGroup);
+ if (p)
+ p->SetPriority(aPriority);
+
+ NS_OBSERVER_ARRAY_NOTIFY_XPCOM_OBSERVERS(mChildList, nsDocLoader,
+ SetPriority, (aPriority));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDocLoader::AdjustPriority(int32_t aDelta)
+{
+ MOZ_LOG(gDocLoaderLog, LogLevel::Debug,
+ ("DocLoader:%p: AdjustPriority(%d) called\n", this, aDelta));
+
+ nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(mLoadGroup);
+ if (p)
+ p->AdjustPriority(aDelta);
+
+ NS_OBSERVER_ARRAY_NOTIFY_XPCOM_OBSERVERS(mChildList, nsDocLoader,
+ AdjustPriority, (aDelta));
+
+ return NS_OK;
+}
+
+
+
+
+#if 0
+void nsDocLoader::DumpChannelInfo()
+{
+ nsChannelInfo *info;
+ int32_t i, count;
+ int32_t current=0, max=0;
+
+
+ printf("==== DocLoader=%x\n", this);
+
+ count = mChannelInfoList.Count();
+ for(i=0; i<count; i++) {
+ info = (nsChannelInfo *)mChannelInfoList.ElementAt(i);
+
+#if defined(DEBUG)
+ nsAutoCString buffer;
+ nsresult rv = NS_OK;
+ if (info->mURI) {
+ rv = info->mURI->GetSpec(buffer);
+ }
+
+ printf(" [%d] current=%d max=%d [%s]\n", i,
+ info->mCurrentProgress,
+ info->mMaxProgress, buffer.get());
+#endif /* DEBUG */
+
+ current += info->mCurrentProgress;
+ if (max >= 0) {
+ if (info->mMaxProgress < info->mCurrentProgress) {
+ max = -1;
+ } else {
+ max += info->mMaxProgress;
+ }
+ }
+ }
+
+ printf("\nCurrent=%d Total=%d\n====\n", current, max);
+}
+#endif /* 0 */
diff --git a/uriloader/base/nsDocLoader.h b/uriloader/base/nsDocLoader.h
new file mode 100644
index 0000000000..481b1397bc
--- /dev/null
+++ b/uriloader/base/nsDocLoader.h
@@ -0,0 +1,335 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+*/
+
+#ifndef nsDocLoader_h__
+#define nsDocLoader_h__
+
+#include "nsIDocumentLoader.h"
+#include "nsIWebProgress.h"
+#include "nsIWebProgressListener.h"
+#include "nsIRequestObserver.h"
+#include "nsWeakReference.h"
+#include "nsILoadGroup.h"
+#include "nsCOMArray.h"
+#include "nsTObserverArray.h"
+#include "nsString.h"
+#include "nsIChannel.h"
+#include "nsIProgressEventSink.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIChannelEventSink.h"
+#include "nsISecurityEventSink.h"
+#include "nsISupportsPriority.h"
+#include "nsCOMPtr.h"
+#include "PLDHashTable.h"
+#include "nsAutoPtr.h"
+
+#include "mozilla/LinkedList.h"
+
+/****************************************************************************
+ * nsDocLoader implementation...
+ ****************************************************************************/
+
+#define NS_THIS_DOCLOADER_IMPL_CID \
+ { /* b4ec8387-98aa-4c08-93b6-6d23069c06f2 */ \
+ 0xb4ec8387, \
+ 0x98aa, \
+ 0x4c08, \
+ {0x93, 0xb6, 0x6d, 0x23, 0x06, 0x9c, 0x06, 0xf2} \
+ }
+
+class nsDocLoader : public nsIDocumentLoader,
+ public nsIRequestObserver,
+ public nsSupportsWeakReference,
+ public nsIProgressEventSink,
+ public nsIWebProgress,
+ public nsIInterfaceRequestor,
+ public nsIChannelEventSink,
+ public nsISecurityEventSink,
+ public nsISupportsPriority
+{
+public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_THIS_DOCLOADER_IMPL_CID)
+
+ nsDocLoader();
+
+ virtual MOZ_MUST_USE nsresult Init();
+
+ static already_AddRefed<nsDocLoader> GetAsDocLoader(nsISupports* aSupports);
+ // Needed to deal with ambiguous inheritance from nsISupports...
+ static nsISupports* GetAsSupports(nsDocLoader* aDocLoader) {
+ return static_cast<nsIDocumentLoader*>(aDocLoader);
+ }
+
+ // Add aDocLoader as a child to the docloader service.
+ static MOZ_MUST_USE nsresult AddDocLoaderAsChildOfRoot(nsDocLoader* aDocLoader);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIDOCUMENTLOADER
+
+ // nsIProgressEventSink
+ NS_DECL_NSIPROGRESSEVENTSINK
+
+ NS_DECL_NSISECURITYEVENTSINK
+
+ // nsIRequestObserver methods: (for observing the load group)
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSIWEBPROGRESS
+
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSICHANNELEVENTSINK
+ NS_DECL_NSISUPPORTSPRIORITY
+
+ // Implementation specific methods...
+
+ // Remove aChild from our childlist. This nulls out the child's mParent
+ // pointer.
+ MOZ_MUST_USE nsresult RemoveChildLoader(nsDocLoader *aChild);
+ // Add aChild to our child list. This will set aChild's mParent pointer to
+ // |this|.
+ MOZ_MUST_USE nsresult AddChildLoader(nsDocLoader* aChild);
+ nsDocLoader* GetParent() const { return mParent; }
+
+ struct nsListenerInfo {
+ nsListenerInfo(nsIWeakReference *aListener, unsigned long aNotifyMask)
+ : mWeakListener(aListener),
+ mNotifyMask(aNotifyMask)
+ {
+ }
+
+ // Weak pointer for the nsIWebProgressListener...
+ nsWeakPtr mWeakListener;
+
+ // Mask indicating which notifications the listener wants to receive.
+ unsigned long mNotifyMask;
+ };
+
+protected:
+ virtual ~nsDocLoader();
+
+ virtual MOZ_MUST_USE nsresult SetDocLoaderParent(nsDocLoader * aLoader);
+
+ bool IsBusy();
+
+ void Destroy();
+ virtual void DestroyChildren();
+
+ nsIDocumentLoader* ChildAt(int32_t i) {
+ return mChildList.SafeElementAt(i, nullptr);
+ }
+
+ void FireOnProgressChange(nsDocLoader* aLoadInitiator,
+ nsIRequest *request,
+ int64_t aProgress,
+ int64_t aProgressMax,
+ int64_t aProgressDelta,
+ int64_t aTotalProgress,
+ int64_t aMaxTotalProgress);
+
+ // This should be at least 2 long since we'll generally always
+ // have the current page and the global docloader on the ancestor
+ // list. But to deal with frames it's better to make it a bit
+ // longer, and it's always a stack temporary so there's no real
+ // reason not to.
+ typedef AutoTArray<RefPtr<nsDocLoader>, 8> WebProgressList;
+ void GatherAncestorWebProgresses(WebProgressList& aList);
+
+ void FireOnStateChange(nsIWebProgress *aProgress,
+ nsIRequest* request,
+ int32_t aStateFlags,
+ nsresult aStatus);
+
+ // The guts of FireOnStateChange, but does not call itself on our ancestors.
+ // The arguments that are const are const so that we can detect cases when
+ // DoFireOnStateChange wants to propagate changes to the next web progress
+ // at compile time. The ones that are not, are references so that such
+ // changes can be propagated.
+ void DoFireOnStateChange(nsIWebProgress * const aProgress,
+ nsIRequest* const request,
+ int32_t &aStateFlags,
+ const nsresult aStatus);
+
+ void FireOnStatusChange(nsIWebProgress *aWebProgress,
+ nsIRequest *aRequest,
+ nsresult aStatus,
+ const char16_t* aMessage);
+
+ void FireOnLocationChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ nsIURI *aUri,
+ uint32_t aFlags);
+
+ MOZ_MUST_USE bool RefreshAttempted(nsIWebProgress* aWebProgress,
+ nsIURI *aURI,
+ int32_t aDelay,
+ bool aSameURI);
+
+ // this function is overridden by the docshell, it is provided so that we
+ // can pass more information about redirect state (the normal OnStateChange
+ // doesn't get the new channel).
+ // @param aRedirectFlags The flags being sent to OnStateChange that
+ // indicate the type of redirect.
+ // @param aStateFlags The channel flags normally sent to OnStateChange.
+ virtual void OnRedirectStateChange(nsIChannel* aOldChannel,
+ nsIChannel* aNewChannel,
+ uint32_t aRedirectFlags,
+ uint32_t aStateFlags) {}
+
+ void doStartDocumentLoad();
+ void doStartURLLoad(nsIRequest *request);
+ void doStopURLLoad(nsIRequest *request, nsresult aStatus);
+ void doStopDocumentLoad(nsIRequest *request, nsresult aStatus);
+
+ // Inform a parent docloader that aChild is about to call its onload
+ // handler.
+ MOZ_MUST_USE bool ChildEnteringOnload(nsIDocumentLoader* aChild) {
+ // It's ok if we're already in the list -- we'll just be in there twice
+ // and then the RemoveObject calls from ChildDoneWithOnload will remove
+ // us.
+ return mChildrenInOnload.AppendObject(aChild);
+ }
+
+ // Inform a parent docloader that aChild is done calling its onload
+ // handler.
+ void ChildDoneWithOnload(nsIDocumentLoader* aChild) {
+ mChildrenInOnload.RemoveObject(aChild);
+ DocLoaderIsEmpty(true);
+ }
+
+protected:
+ struct nsStatusInfo : public mozilla::LinkedListElement<nsStatusInfo>
+ {
+ nsString mStatusMessage;
+ nsresult mStatusCode;
+ // Weak mRequest is ok; we'll be told if it decides to go away.
+ nsIRequest * const mRequest;
+
+ explicit nsStatusInfo(nsIRequest* aRequest) :
+ mRequest(aRequest)
+ {
+ MOZ_COUNT_CTOR(nsStatusInfo);
+ }
+ ~nsStatusInfo()
+ {
+ MOZ_COUNT_DTOR(nsStatusInfo);
+ }
+ };
+
+ struct nsRequestInfo : public PLDHashEntryHdr
+ {
+ explicit nsRequestInfo(const void* key)
+ : mKey(key), mCurrentProgress(0), mMaxProgress(0), mUploading(false)
+ , mLastStatus(nullptr)
+ {
+ MOZ_COUNT_CTOR(nsRequestInfo);
+ }
+
+ ~nsRequestInfo()
+ {
+ MOZ_COUNT_DTOR(nsRequestInfo);
+ }
+
+ nsIRequest* Request() {
+ return static_cast<nsIRequest*>(const_cast<void*>(mKey));
+ }
+
+ const void* mKey; // Must be first for the PLDHashTable stubs to work
+ int64_t mCurrentProgress;
+ int64_t mMaxProgress;
+ bool mUploading;
+
+ nsAutoPtr<nsStatusInfo> mLastStatus;
+ };
+
+ static void RequestInfoHashInitEntry(PLDHashEntryHdr* entry, const void* key);
+ static void RequestInfoHashClearEntry(PLDHashTable* table, PLDHashEntryHdr* entry);
+
+ // IMPORTANT: The ownership implicit in the following member
+ // variables has been explicitly checked and set using nsCOMPtr
+ // for owning pointers and raw COM interface pointers for weak
+ // (ie, non owning) references. If you add any members to this
+ // class, please make the ownership explicit (pinkerton, scc).
+
+ nsCOMPtr<nsIRequest> mDocumentRequest; // [OWNER] ???compare with document
+
+ nsDocLoader* mParent; // [WEAK]
+
+ typedef nsAutoTObserverArray<nsListenerInfo, 8> ListenerArray;
+ ListenerArray mListenerInfoList;
+
+ nsCOMPtr<nsILoadGroup> mLoadGroup;
+ // We hold weak refs to all our kids
+ nsTObserverArray<nsDocLoader*> mChildList;
+
+ // The following member variables are related to the new nsIWebProgress
+ // feedback interfaces that travis cooked up.
+ int32_t mProgressStateFlags;
+
+ int64_t mCurrentSelfProgress;
+ int64_t mMaxSelfProgress;
+
+ int64_t mCurrentTotalProgress;
+ int64_t mMaxTotalProgress;
+
+ PLDHashTable mRequestInfoHash;
+ int64_t mCompletedTotalProgress;
+
+ mozilla::LinkedList<nsStatusInfo> mStatusInfoList;
+
+ /*
+ * This flag indicates that the loader is loading a document. It is set
+ * from the call to LoadDocument(...) until the OnConnectionsComplete(...)
+ * notification is fired...
+ */
+ bool mIsLoadingDocument;
+
+ /* Flag to indicate that we're in the process of restoring a document. */
+ bool mIsRestoringDocument;
+
+ /* Flag to indicate that we're in the process of flushing layout
+ under DocLoaderIsEmpty() and should not do another flush. */
+ bool mDontFlushLayout;
+
+ /* Flag to indicate whether we should consider ourselves as currently
+ flushing layout for the purposes of IsBusy. For example, if Stop has
+ been called then IsBusy should return false even if we are still
+ flushing. */
+ bool mIsFlushingLayout;
+
+private:
+ static const PLDHashTableOps sRequestInfoHashOps;
+
+ // A list of kids that are in the middle of their onload calls and will let
+ // us know once they're done. We don't want to fire onload for "normal"
+ // DocLoaderIsEmpty calls (those coming from requests finishing in our
+ // loadgroup) unless this is empty.
+ nsCOMArray<nsIDocumentLoader> mChildrenInOnload;
+
+ // DocLoaderIsEmpty should be called whenever the docloader may be empty.
+ // This method is idempotent and does nothing if the docloader is not in
+ // fact empty. This method _does_ make sure that layout is flushed if our
+ // loadgroup has no active requests before checking for "real" emptiness if
+ // aFlushLayout is true.
+ void DocLoaderIsEmpty(bool aFlushLayout);
+
+ int64_t GetMaxTotalProgress();
+
+ nsresult AddRequestInfo(nsIRequest* aRequest);
+ void RemoveRequestInfo(nsIRequest* aRequest);
+ nsRequestInfo *GetRequestInfo(nsIRequest* aRequest);
+ void ClearRequestInfoHash();
+ int64_t CalculateMaxProgress();
+/// void DumpChannelInfo(void);
+
+ // used to clear our internal progress state between loads...
+ void ClearInternalProgress();
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsDocLoader, NS_THIS_DOCLOADER_IMPL_CID)
+
+#endif /* nsDocLoader_h__ */
diff --git a/uriloader/base/nsIContentHandler.idl b/uriloader/base/nsIContentHandler.idl
new file mode 100644
index 0000000000..31ef87a8ba
--- /dev/null
+++ b/uriloader/base/nsIContentHandler.idl
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+interface nsIRequest;
+interface nsIInterfaceRequestor;
+
+[scriptable, uuid(49439df2-b3d2-441c-bf62-866bdaf56fd2)]
+interface nsIContentHandler : nsISupports
+{
+ /**
+ * Tells the content handler to take over handling the content. If this
+ * function succeeds, the URI Loader will leave this request alone, ignoring
+ * progress notifications. Failure of this method will cause the request to be
+ * cancelled, unless the error code is NS_ERROR_WONT_HANDLE_CONTENT (see
+ * below).
+ *
+ * @param aWindowContext
+ * Window context, used to get things like the current nsIDOMWindow
+ * for this request. May be null.
+ * @param aContentType
+ * The content type of aRequest
+ * @param aRequest
+ * A request whose content type is already known.
+ *
+ * @throw NS_ERROR_WONT_HANDLE_CONTENT Indicates that this handler does not
+ * want to handle this content. A different way for handling this
+ * content should be tried.
+ */
+ void handleContent(in string aContentType,
+ in nsIInterfaceRequestor aWindowContext,
+ in nsIRequest aRequest);
+};
diff --git a/uriloader/base/nsIDocumentLoader.idl b/uriloader/base/nsIDocumentLoader.idl
new file mode 100644
index 0000000000..3bd960ac84
--- /dev/null
+++ b/uriloader/base/nsIDocumentLoader.idl
@@ -0,0 +1,36 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+interface nsILoadGroup;
+interface nsIChannel;
+interface nsIURI;
+interface nsIWebProgress;
+interface nsIRequest;
+
+/**
+ * An nsIDocumentLoader is an interface responsible for tracking groups of
+ * loads that belong together (images, external scripts, etc) and subdocuments
+ * (<iframe>, <frame>, etc). It is also responsible for sending
+ * nsIWebProgressListener notifications.
+ * XXXbz this interface should go away, we think...
+ */
+[scriptable, uuid(bbe961ee-59e9-42bb-be50-0331979bb79f)]
+interface nsIDocumentLoader : nsISupports
+{
+ // Stop all loads in the loadgroup of this docloader
+ void stop();
+
+ // XXXbz is this needed? For embedding? What this does is does is not
+ // defined by this interface!
+ readonly attribute nsISupports container;
+
+ // The loadgroup associated with this docloader
+ readonly attribute nsILoadGroup loadGroup;
+
+ // The defaultLoadRequest of the loadgroup associated with this docloader
+ readonly attribute nsIChannel documentChannel;
+};
+
diff --git a/uriloader/base/nsITransfer.idl b/uriloader/base/nsITransfer.idl
new file mode 100644
index 0000000000..da34d4ac49
--- /dev/null
+++ b/uriloader/base/nsITransfer.idl
@@ -0,0 +1,106 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIWebProgressListener2.idl"
+
+interface nsIArray;
+interface nsIURI;
+interface nsICancelable;
+interface nsIMIMEInfo;
+interface nsIFile;
+
+[scriptable, uuid(37ec75d3-97ad-4da8-afaa-eabe5b4afd73)]
+interface nsITransfer : nsIWebProgressListener2 {
+
+ /**
+ * Initializes the transfer with certain properties. This function must
+ * be called prior to accessing any properties on this interface.
+ *
+ * @param aSource The source URI of the transfer. Must not be null.
+ *
+ * @param aTarget The target URI of the transfer. Must not be null.
+ *
+ * @param aDisplayName The user-readable description of the transfer.
+ * Can be empty.
+ *
+ * @param aMIMEInfo The MIME info associated with the target,
+ * including MIME type and helper app when appropriate.
+ * This parameter is optional.
+ *
+ * @param startTime Time when the download started (ie, when the first
+ * response from the server was received)
+ * XXX presumably wbp and exthandler do this differently
+ *
+ * @param aTempFile The location of a temporary file; i.e. a file in which
+ * the received data will be stored, but which is not
+ * equal to the target file. (will be moved to the real
+ * target by the caller, when the download is finished)
+ * May be null.
+ *
+ * @param aCancelable An object that can be used to abort the download.
+ * Must not be null.
+ * Implementations are expected to hold a strong
+ * reference to this object until the download is
+ * finished, at which point they should release the
+ * reference.
+ *
+ * @param aIsPrivate Used to determine the privacy status of the new transfer.
+ * If true, indicates that the transfer was initiated from
+ * a source that desires privacy.
+ */
+ void init(in nsIURI aSource,
+ in nsIURI aTarget,
+ in AString aDisplayName,
+ in nsIMIMEInfo aMIMEInfo,
+ in PRTime startTime,
+ in nsIFile aTempFile,
+ in nsICancelable aCancelable,
+ in boolean aIsPrivate);
+
+ /*
+ * Used to notify the transfer object of the hash of the downloaded file.
+ * Must be called on the main thread, only after the download has finished
+ * successfully.
+ * @param aHash The SHA-256 hash in raw bytes of the downloaded file.
+ */
+ void setSha256Hash(in ACString aHash);
+
+ /*
+ * Used to notify the transfer object of the signature of the downloaded
+ * file. Must be called on the main thread, only after the download has
+ * finished successfully.
+ * @param aSignatureInfo The nsIArray of nsIX509CertList of nsIX509Cert
+ * certificates of the downloaded file.
+ */
+ void setSignatureInfo(in nsIArray aSignatureInfo);
+
+ /*
+ * Used to notify the transfer object of the redirects associated with the
+ * channel that terminated in the downloaded file. Must be called on the
+ * main thread, only after the download has finished successfully.
+ * @param aRedirects The nsIArray of nsIPrincipal of redirected URIs
+ * associated with the downloaded file.
+ */
+ void setRedirects(in nsIArray aRedirects);
+};
+
+%{C++
+/**
+ * A component with this contract ID will be created each time a download is
+ * started, and nsITransfer::Init will be called on it and an observer will be set.
+ *
+ * Notifications of the download progress will happen via
+ * nsIWebProgressListener/nsIWebProgressListener2.
+ *
+ * INTERFACES THAT MUST BE IMPLEMENTED:
+ * nsITransfer
+ * nsIWebProgressListener
+ * nsIWebProgressListener2
+ *
+ * XXX move this to nsEmbedCID.h once the interfaces (and the contract ID) are
+ * frozen.
+ */
+#define NS_TRANSFER_CONTRACTID "@mozilla.org/transfer;1"
+%}
diff --git a/uriloader/base/nsIURIContentListener.idl b/uriloader/base/nsIURIContentListener.idl
new file mode 100644
index 0000000000..9008cb61e0
--- /dev/null
+++ b/uriloader/base/nsIURIContentListener.idl
@@ -0,0 +1,135 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIRequest;
+interface nsIStreamListener;
+interface nsIURI;
+
+/**
+ * nsIURIContentListener is an interface used by components which
+ * want to know (and have a chance to handle) a particular content type.
+ * Typical usage scenarios will include running applications which register
+ * a nsIURIContentListener for each of its content windows with the uri
+ * dispatcher service.
+ */
+[scriptable, uuid(10a28f38-32e8-4c63-8aa1-12eaaebc369a)]
+interface nsIURIContentListener : nsISupports
+{
+ /**
+ * Gives the original content listener first crack at stopping a load before
+ * it happens.
+ *
+ * @param aURI URI that is being opened.
+ *
+ * @return <code>false</code> if the load can continue;
+ * <code>true</code> if the open should be aborted.
+ */
+ boolean onStartURIOpen(in nsIURI aURI);
+
+ /**
+ * Notifies the content listener to hook up an nsIStreamListener capable of
+ * consuming the data stream.
+ *
+ * @param aContentType Content type of the data.
+ * @param aIsContentPreferred Indicates whether the content should be
+ * preferred by this listener.
+ * @param aRequest Request that is providing the data.
+ * @param aContentHandler nsIStreamListener that will consume the data.
+ * This should be set to <code>nullptr</code> if
+ * this content listener can't handle the content
+ * type; in this case, doContent should also fail
+ * (i.e., return failure nsresult).
+ *
+ * @return <code>true</code> if the load should
+ * be aborted and consumer wants to
+ * handle the load completely by itself. This
+ * causes the URI Loader do nothing else...
+ * <code>false</code> if the URI Loader should
+ * continue handling the load and call the
+ * returned streamlistener's methods.
+ */
+ boolean doContent(in ACString aContentType,
+ in boolean aIsContentPreferred,
+ in nsIRequest aRequest,
+ out nsIStreamListener aContentHandler);
+
+ /**
+ * When given a uri to dispatch, if the URI is specified as 'preferred
+ * content' then the uri loader tries to find a preferred content handler
+ * for the content type. The thought is that many content listeners may
+ * be able to handle the same content type if they have to. i.e. the mail
+ * content window can handle text/html just like a browser window content
+ * listener. However, if the user clicks on a link with text/html content,
+ * then the browser window should handle that content and not the mail
+ * window where the user may have clicked the link. This is the difference
+ * between isPreferred and canHandleContent.
+ *
+ * @param aContentType Content type of the data.
+ * @param aDesiredContentType Indicates that aContentType must be converted
+ * to aDesiredContentType before processing the
+ * data. This causes a stream converted to be
+ * inserted into the nsIStreamListener chain.
+ * This argument can be <code>nullptr</code> if
+ * the content should be consumed directly as
+ * aContentType.
+ *
+ * @return <code>true</code> if this is a preferred
+ * content handler for aContentType;
+ * <code>false<code> otherwise.
+ */
+ boolean isPreferred(in string aContentType, out string aDesiredContentType);
+
+ /**
+ * When given a uri to dispatch, if the URI is not specified as 'preferred
+ * content' then the uri loader calls canHandleContent to see if the content
+ * listener is capable of handling the content.
+ *
+ * @param aContentType Content type of the data.
+ * @param aIsContentPreferred Indicates whether the content should be
+ * preferred by this listener.
+ * @param aDesiredContentType Indicates that aContentType must be converted
+ * to aDesiredContentType before processing the
+ * data. This causes a stream converted to be
+ * inserted into the nsIStreamListener chain.
+ * This argument can be <code>nullptr</code> if
+ * the content should be consumed directly as
+ * aContentType.
+ *
+ * @return <code>true</code> if the data can be consumed.
+ * <code>false</code> otherwise.
+ *
+ * Note: I really envision canHandleContent as a method implemented
+ * by the docshell as the implementation is generic to all doc
+ * shells. The isPreferred decision is a decision made by a top level
+ * application content listener that sits at the top of the docshell
+ * hierarchy.
+ */
+ boolean canHandleContent(in string aContentType,
+ in boolean aIsContentPreferred,
+ out string aDesiredContentType);
+
+ /**
+ * The load context associated with a particular content listener.
+ * The URI Loader stores and accesses this value as needed.
+ */
+ attribute nsISupports loadCookie;
+
+ /**
+ * The parent content listener if this particular listener is part of a chain
+ * of content listeners (i.e. a docshell!)
+ *
+ * @note If this attribute is set to an object that implements
+ * nsISupportsWeakReference, the implementation should get the
+ * nsIWeakReference and hold that. Otherwise, the implementation
+ * should not refcount this interface; it should assume that a non
+ * null value is always valid. In that case, the caller is
+ * responsible for explicitly setting this value back to null if the
+ * parent content listener is destroyed.
+ */
+ attribute nsIURIContentListener parentContentListener;
+};
+
diff --git a/uriloader/base/nsIURILoader.idl b/uriloader/base/nsIURILoader.idl
new file mode 100644
index 0000000000..74f21e3639
--- /dev/null
+++ b/uriloader/base/nsIURILoader.idl
@@ -0,0 +1,140 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIURIContentListener;
+interface nsIURI;
+interface nsILoadGroup;
+interface nsIProgressEventSink;
+interface nsIChannel;
+interface nsIRequest;
+interface nsIStreamListener;
+interface nsIInputStream;
+interface nsIInterfaceRequestor;
+
+/**
+ * The uri dispatcher is responsible for taking uri's, determining
+ * the content and routing the opened url to the correct content
+ * handler.
+ *
+ * When you encounter a url you want to open, you typically call
+ * openURI, passing it the content listener for the window the uri is
+ * originating from. The uri dispatcher opens the url to discover the
+ * content type. It then gives the content listener first crack at
+ * handling the content. If it doesn't want it, the dispatcher tries
+ * to hand it off one of the registered content listeners. This allows
+ * running applications the chance to jump in and handle the content.
+ *
+ * If that also fails, then the uri dispatcher goes to the registry
+ * looking for the preferred content handler for the content type
+ * of the uri. The content handler may create an app instance
+ * or it may hand the contents off to a platform specific plugin
+ * or helper app. Or it may hand the url off to an OS registered
+ * application.
+ */
+[scriptable, uuid(8762c4e7-be35-4958-9b81-a05685bb516d)]
+interface nsIURILoader : nsISupports
+{
+ /**
+ * @name Flags for opening URIs.
+ */
+ /* @{ */
+ /**
+ * Should the content be displayed in a container that prefers the
+ * content-type, or will any container do.
+ */
+ const unsigned long IS_CONTENT_PREFERRED = 1 << 0;
+ /**
+ * If this flag is set, only the listener of the specified window context will
+ * be considered for content handling; if it refuses the load, an error will
+ * be indicated.
+ */
+ const unsigned long DONT_RETARGET = 1 << 1;
+ /* @} */
+
+ /**
+ * As applications such as messenger and the browser are instantiated,
+ * they register content listener's with the uri dispatcher corresponding
+ * to content windows within that application.
+ *
+ * Note to self: we may want to optimize things a bit more by requiring
+ * the content types the registered content listener cares about.
+ *
+ * @param aContentListener
+ * The listener to register. This listener must implement
+ * nsISupportsWeakReference.
+ *
+ * @see the nsIURILoader class description
+ */
+ void registerContentListener (in nsIURIContentListener aContentListener);
+ void unRegisterContentListener (in nsIURIContentListener aContentListener);
+
+ /**
+ * OpenURI requires the following parameters.....
+ * @param aChannel
+ * The channel that should be opened. This must not be asyncOpen'd yet!
+ * If a loadgroup is set on the channel, it will get replaced with a
+ * different one.
+ * @param aFlags
+ * Combination (bitwise OR) of the flags specified above. 0 indicates
+ * default handling.
+ * @param aWindowContext
+ * If you are running the url from a doc shell or a web shell, this is
+ * your window context. If you have a content listener you want to
+ * give first crack to, the uri loader needs to be able to get it
+ * from the window context. We will also be using the window context
+ * to get at the progress event sink interface.
+ * <b>Must not be null!</b>
+ */
+ void openURI(in nsIChannel aChannel,
+ in unsigned long aFlags,
+ in nsIInterfaceRequestor aWindowContext);
+
+ /**
+ * Loads data from a channel. This differs from openURI in that the channel
+ * may already be opened, and that it returns a stream listener into which the
+ * caller should pump data. The caller is responsible for opening the channel
+ * and pumping the channel's data into the returned stream listener.
+ *
+ * Note: If the channel already has a loadgroup, it will be replaced with the
+ * window context's load group, or null if the context doesn't have one.
+ *
+ * If the window context's nsIURIContentListener refuses the load immediately
+ * (e.g. in nsIURIContentListener::onStartURIOpen), this method will return
+ * NS_ERROR_WONT_HANDLE_CONTENT. At that point, the caller should probably
+ * cancel the channel if it's already open (this method will not cancel the
+ * channel).
+ *
+ * If flags include DONT_RETARGET, and the content listener refuses the load
+ * during onStartRequest (e.g. in canHandleContent/isPreferred), then the
+ * returned stream listener's onStartRequest method will return
+ * NS_ERROR_WONT_HANDLE_CONTENT.
+ *
+ * @param aChannel
+ * The channel that should be loaded. The channel may already be
+ * opened. It must not be closed (i.e. this must be called before the
+ * channel calls onStopRequest on its stream listener).
+ * @param aFlags
+ * Combination (bitwise OR) of the flags specified above. 0 indicates
+ * default handling.
+ * @param aWindowContext
+ * If you are running the url from a doc shell or a web shell, this is
+ * your window context. If you have a content listener you want to
+ * give first crack to, the uri loader needs to be able to get it
+ * from the window context. We will also be using the window context
+ * to get at the progress event sink interface.
+ * <b>Must not be null!</b>
+ */
+ nsIStreamListener openChannel(in nsIChannel aChannel,
+ in unsigned long aFlags,
+ in nsIInterfaceRequestor aWindowContext);
+
+ /**
+ * Stops an in progress load
+ */
+ void stop(in nsISupports aLoadCookie);
+};
+
diff --git a/uriloader/base/nsIWebProgress.idl b/uriloader/base/nsIWebProgress.idl
new file mode 100644
index 0000000000..9d17d0a4d4
--- /dev/null
+++ b/uriloader/base/nsIWebProgress.idl
@@ -0,0 +1,153 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface mozIDOMWindowProxy;
+interface nsIWebProgressListener;
+
+/**
+ * The nsIWebProgress interface is used to add or remove nsIWebProgressListener
+ * instances to observe the loading of asynchronous requests (usually in the
+ * context of a DOM window).
+ *
+ * nsIWebProgress instances may be arranged in a parent-child configuration,
+ * corresponding to the parent-child configuration of their respective DOM
+ * windows. However, in some cases a nsIWebProgress instance may not have an
+ * associated DOM window. The parent-child relationship of nsIWebProgress
+ * instances is not made explicit by this interface, but the relationship may
+ * exist in some implementations.
+ *
+ * A nsIWebProgressListener instance receives notifications for the
+ * nsIWebProgress instance to which it added itself, and it may also receive
+ * notifications from any nsIWebProgress instances that are children of that
+ * nsIWebProgress instance.
+ */
+[scriptable, uuid(c4d64640-b332-4db6-a2a5-e08566000dc9)]
+interface nsIWebProgress : nsISupports
+{
+ /**
+ * The following flags may be combined to form the aNotifyMask parameter for
+ * the addProgressListener method. They limit the set of events that are
+ * delivered to an nsIWebProgressListener instance.
+ */
+
+ /**
+ * These flags indicate the state transistions to observe, corresponding to
+ * nsIWebProgressListener::onStateChange.
+ *
+ * NOTIFY_STATE_REQUEST
+ * Only receive the onStateChange event if the aStateFlags parameter
+ * includes nsIWebProgressListener::STATE_IS_REQUEST.
+ *
+ * NOTIFY_STATE_DOCUMENT
+ * Only receive the onStateChange event if the aStateFlags parameter
+ * includes nsIWebProgressListener::STATE_IS_DOCUMENT.
+ *
+ * NOTIFY_STATE_NETWORK
+ * Only receive the onStateChange event if the aStateFlags parameter
+ * includes nsIWebProgressListener::STATE_IS_NETWORK.
+ *
+ * NOTIFY_STATE_WINDOW
+ * Only receive the onStateChange event if the aStateFlags parameter
+ * includes nsIWebProgressListener::STATE_IS_WINDOW.
+ *
+ * NOTIFY_STATE_ALL
+ * Receive all onStateChange events.
+ */
+ const unsigned long NOTIFY_STATE_REQUEST = 0x00000001;
+ const unsigned long NOTIFY_STATE_DOCUMENT = 0x00000002;
+ const unsigned long NOTIFY_STATE_NETWORK = 0x00000004;
+ const unsigned long NOTIFY_STATE_WINDOW = 0x00000008;
+ const unsigned long NOTIFY_STATE_ALL = 0x0000000f;
+
+ /**
+ * These flags indicate the other events to observe, corresponding to the
+ * other four methods defined on nsIWebProgressListener.
+ *
+ * NOTIFY_PROGRESS
+ * Receive onProgressChange events.
+ *
+ * NOTIFY_STATUS
+ * Receive onStatusChange events.
+ *
+ * NOTIFY_SECURITY
+ * Receive onSecurityChange events.
+ *
+ * NOTIFY_LOCATION
+ * Receive onLocationChange events.
+ *
+ * NOTIFY_REFRESH
+ * Receive onRefreshAttempted events.
+ * This is defined on nsIWebProgressListener2.
+ */
+ const unsigned long NOTIFY_PROGRESS = 0x00000010;
+ const unsigned long NOTIFY_STATUS = 0x00000020;
+ const unsigned long NOTIFY_SECURITY = 0x00000040;
+ const unsigned long NOTIFY_LOCATION = 0x00000080;
+ const unsigned long NOTIFY_REFRESH = 0x00000100;
+
+ /**
+ * This flag enables all notifications.
+ */
+ const unsigned long NOTIFY_ALL = 0x000001ff;
+
+ /**
+ * Registers a listener to receive web progress events.
+ *
+ * @param aListener
+ * The listener interface to be called when a progress event occurs.
+ * This object must also implement nsISupportsWeakReference.
+ * @param aNotifyMask
+ * The types of notifications to receive.
+ *
+ * @throw NS_ERROR_INVALID_ARG
+ * Indicates that aListener was either null or that it does not
+ * support weak references.
+ * @throw NS_ERROR_FAILURE
+ * Indicates that aListener was already registered.
+ */
+ void addProgressListener(in nsIWebProgressListener aListener,
+ in unsigned long aNotifyMask);
+
+ /**
+ * Removes a previously registered listener of progress events.
+ *
+ * @param aListener
+ * The listener interface previously registered with a call to
+ * addProgressListener.
+ *
+ * @throw NS_ERROR_FAILURE
+ * Indicates that aListener was not registered.
+ */
+ void removeProgressListener(in nsIWebProgressListener aListener);
+
+ /**
+ * The DOM window associated with this nsIWebProgress instance.
+ *
+ * @throw NS_ERROR_FAILURE
+ * Indicates that there is no associated DOM window.
+ */
+ readonly attribute mozIDOMWindowProxy DOMWindow;
+ readonly attribute uint64_t DOMWindowID;
+
+ /**
+ * Indicates whether DOMWindow.top == DOMWindow.
+ */
+ readonly attribute boolean isTopLevel;
+
+ /**
+ * Indicates whether or not a document is currently being loaded
+ * in the context of this nsIWebProgress instance.
+ */
+ readonly attribute boolean isLoadingDocument;
+
+ /**
+ * Contains a load type as specified by the load* constants in
+ * nsIDocShellLoadInfo.idl.
+ */
+ readonly attribute unsigned long loadType;
+};
diff --git a/uriloader/base/nsIWebProgressListener.idl b/uriloader/base/nsIWebProgressListener.idl
new file mode 100644
index 0000000000..714b931a3e
--- /dev/null
+++ b/uriloader/base/nsIWebProgressListener.idl
@@ -0,0 +1,425 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIWebProgress;
+interface nsIRequest;
+interface nsIURI;
+
+/**
+ * The nsIWebProgressListener interface is implemented by clients wishing to
+ * listen in on the progress associated with the loading of asynchronous
+ * requests in the context of a nsIWebProgress instance as well as any child
+ * nsIWebProgress instances. nsIWebProgress.idl describes the parent-child
+ * relationship of nsIWebProgress instances.
+ */
+[scriptable, uuid(a9df523b-efe2-421e-9d8e-3d7f807dda4c)]
+interface nsIWebProgressListener : nsISupports
+{
+ /**
+ * State Transition Flags
+ *
+ * These flags indicate the various states that requests may transition
+ * through as they are being loaded. These flags are mutually exclusive.
+ *
+ * For any given request, onStateChange is called once with the STATE_START
+ * flag, zero or more times with the STATE_TRANSFERRING flag or once with the
+ * STATE_REDIRECTING flag, and then finally once with the STATE_STOP flag.
+ * NOTE: For document requests, a second STATE_STOP is generated (see the
+ * description of STATE_IS_WINDOW for more details).
+ *
+ * STATE_START
+ * This flag indicates the start of a request. This flag is set when a
+ * request is initiated. The request is complete when onStateChange is
+ * called for the same request with the STATE_STOP flag set.
+ *
+ * STATE_REDIRECTING
+ * This flag indicates that a request is being redirected. The request
+ * passed to onStateChange is the request that is being redirected. When a
+ * redirect occurs, a new request is generated automatically to process the
+ * new request. Expect a corresponding STATE_START event for the new
+ * request, and a STATE_STOP for the redirected request.
+ *
+ * STATE_TRANSFERRING
+ * This flag indicates that data for a request is being transferred to an
+ * end consumer. This flag indicates that the request has been targeted,
+ * and that the user may start seeing content corresponding to the request.
+ *
+ * STATE_NEGOTIATING
+ * This flag is not used.
+ *
+ * STATE_STOP
+ * This flag indicates the completion of a request. The aStatus parameter
+ * to onStateChange indicates the final status of the request.
+ */
+ const unsigned long STATE_START = 0x00000001;
+ const unsigned long STATE_REDIRECTING = 0x00000002;
+ const unsigned long STATE_TRANSFERRING = 0x00000004;
+ const unsigned long STATE_NEGOTIATING = 0x00000008;
+ const unsigned long STATE_STOP = 0x00000010;
+
+
+ /**
+ * State Type Flags
+ *
+ * These flags further describe the entity for which the state transition is
+ * occuring. These flags are NOT mutually exclusive (i.e., an onStateChange
+ * event may indicate some combination of these flags).
+ *
+ * STATE_IS_REQUEST
+ * This flag indicates that the state transition is for a request, which
+ * includes but is not limited to document requests. (See below for a
+ * description of document requests.) Other types of requests, such as
+ * requests for inline content (e.g., images and stylesheets) are
+ * considered normal requests.
+ *
+ * STATE_IS_DOCUMENT
+ * This flag indicates that the state transition is for a document request.
+ * This flag is set in addition to STATE_IS_REQUEST. A document request
+ * supports the nsIChannel interface and its loadFlags attribute includes
+ * the nsIChannel::LOAD_DOCUMENT_URI flag.
+ *
+ * A document request does not complete until all requests associated with
+ * the loading of its corresponding document have completed. This includes
+ * other document requests (e.g., corresponding to HTML <iframe> elements).
+ * The document corresponding to a document request is available via the
+ * DOMWindow attribute of onStateChange's aWebProgress parameter.
+ *
+ * STATE_IS_NETWORK
+ * This flag indicates that the state transition corresponds to the start
+ * or stop of activity in the indicated nsIWebProgress instance. This flag
+ * is accompanied by either STATE_START or STATE_STOP, and it may be
+ * combined with other State Type Flags.
+ *
+ * Unlike STATE_IS_WINDOW, this flag is only set when activity within the
+ * nsIWebProgress instance being observed starts or stops. If activity
+ * only occurs in a child nsIWebProgress instance, then this flag will be
+ * set to indicate the start and stop of that activity.
+ *
+ * For example, in the case of navigation within a single frame of a HTML
+ * frameset, a nsIWebProgressListener instance attached to the
+ * nsIWebProgress of the frameset window will receive onStateChange calls
+ * with the STATE_IS_NETWORK flag set to indicate the start and stop of
+ * said navigation. In other words, an observer of an outer window can
+ * determine when activity, that may be constrained to a child window or
+ * set of child windows, starts and stops.
+ *
+ * STATE_IS_WINDOW
+ * This flag indicates that the state transition corresponds to the start
+ * or stop of activity in the indicated nsIWebProgress instance. This flag
+ * is accompanied by either STATE_START or STATE_STOP, and it may be
+ * combined with other State Type Flags.
+ *
+ * This flag is similar to STATE_IS_DOCUMENT. However, when a document
+ * request completes, two onStateChange calls with STATE_STOP are
+ * generated. The document request is passed as aRequest to both calls.
+ * The first has STATE_IS_REQUEST and STATE_IS_DOCUMENT set, and the second
+ * has the STATE_IS_WINDOW flag set (and possibly the STATE_IS_NETWORK flag
+ * set as well -- see above for a description of when the STATE_IS_NETWORK
+ * flag may be set). This second STATE_STOP event may be useful as a way
+ * to partition the work that occurs when a document request completes.
+ */
+ const unsigned long STATE_IS_REQUEST = 0x00010000;
+ const unsigned long STATE_IS_DOCUMENT = 0x00020000;
+ const unsigned long STATE_IS_NETWORK = 0x00040000;
+ const unsigned long STATE_IS_WINDOW = 0x00080000;
+
+
+ /**
+ * State Modifier Flags
+ *
+ * These flags further describe the transition which is occuring. These
+ * flags are NOT mutually exclusive (i.e., an onStateChange event may
+ * indicate some combination of these flags).
+ *
+ * STATE_RESTORING
+ * This flag indicates that the state transition corresponds to the start
+ * or stop of activity for restoring a previously-rendered presentation.
+ * As such, there is no actual network activity associated with this
+ * request, and any modifications made to the document or presentation
+ * when it was originally loaded will still be present.
+ */
+ const unsigned long STATE_RESTORING = 0x01000000;
+
+ /**
+ * State Security Flags
+ *
+ * These flags describe the security state reported by a call to the
+ * onSecurityChange method. These flags are mutually exclusive.
+ *
+ * STATE_IS_INSECURE
+ * This flag indicates that the data corresponding to the request
+ * was received over an insecure channel.
+ *
+ * STATE_IS_BROKEN
+ * This flag indicates an unknown security state. This may mean that the
+ * request is being loaded as part of a page in which some content was
+ * received over an insecure channel.
+ *
+ * STATE_IS_SECURE
+ * This flag indicates that the data corresponding to the request was
+ * received over a secure channel. The degree of security is expressed by
+ * STATE_SECURE_HIGH, STATE_SECURE_MED, or STATE_SECURE_LOW.
+ */
+ const unsigned long STATE_IS_INSECURE = 0x00000004;
+ const unsigned long STATE_IS_BROKEN = 0x00000001;
+ const unsigned long STATE_IS_SECURE = 0x00000002;
+
+ /**
+ * Mixed active content flags
+ *
+ * May be set in addition to the State Security Flags, to indicate that
+ * mixed active content has been encountered.
+ *
+ * STATE_BLOCKED_MIXED_ACTIVE_CONTENT
+ * Mixed active content has been blocked from loading.
+ *
+ * STATE_LOADED_MIXED_ACTIVE_CONTENT
+ * Mixed active content has been loaded. State should be STATE_IS_BROKEN.
+ */
+ const unsigned long STATE_BLOCKED_MIXED_ACTIVE_CONTENT = 0x00000010;
+ const unsigned long STATE_LOADED_MIXED_ACTIVE_CONTENT = 0x00000020;
+
+ /**
+ * Mixed display content flags
+ *
+ * May be set in addition to the State Security Flags, to indicate that
+ * mixed display content has been encountered.
+ *
+ * STATE_BLOCKED_MIXED_DISPLAY_CONTENT
+ * Mixed display content has been blocked from loading.
+ *
+ * STATE_LOADED_MIXED_DISPLAY_CONTENT
+ * Mixed display content has been loaded. State should be STATE_IS_BROKEN.
+ */
+ const unsigned long STATE_BLOCKED_MIXED_DISPLAY_CONTENT = 0x00000100;
+ const unsigned long STATE_LOADED_MIXED_DISPLAY_CONTENT = 0x00000200;
+
+ /**
+ * Tracking content flags
+ *
+ * May be set in addition to the State security Flags, to indicate that
+ * tracking content has been encountered.
+ *
+ * STATE_BLOCKED_TRACKING_CONTENT
+ * Tracking content has been blocked from loading.
+ *
+ * STATE_LOADED_TRACKING_CONTENT
+ * Tracking content has been loaded.
+ */
+ const unsigned long STATE_BLOCKED_TRACKING_CONTENT = 0x00001000;
+ const unsigned long STATE_LOADED_TRACKING_CONTENT = 0x00002000;
+
+ /**
+ * Security Strength Flags
+ *
+ * These flags describe the security strength and accompany STATE_IS_SECURE
+ * in a call to the onSecurityChange method. These flags are mutually
+ * exclusive.
+ *
+ * These flags are not meant to provide a precise description of data
+ * transfer security. These are instead intended as a rough indicator that
+ * may be used to, for example, color code a security indicator or otherwise
+ * provide basic data transfer security feedback to the user.
+ *
+ * STATE_SECURE_HIGH
+ * This flag indicates a high degree of security.
+ *
+ * STATE_SECURE_MED
+ * This flag indicates a medium degree of security.
+ *
+ * STATE_SECURE_LOW
+ * This flag indicates a low degree of security.
+ */
+ const unsigned long STATE_SECURE_HIGH = 0x00040000;
+ const unsigned long STATE_SECURE_MED = 0x00010000;
+ const unsigned long STATE_SECURE_LOW = 0x00020000;
+
+ /**
+ * State bits for EV == Extended Validation == High Assurance
+ *
+ * These flags describe the level of identity verification
+ * in a call to the onSecurityChange method.
+ *
+ * STATE_IDENTITY_EV_TOPLEVEL
+ * The topmost document uses an EV cert.
+ * NOTE: Available since Gecko 1.9
+ */
+
+ const unsigned long STATE_IDENTITY_EV_TOPLEVEL = 0x00100000;
+
+ /**
+ * Broken state flags
+ *
+ * These flags describe the reason of the broken state.
+ *
+ * STATE_USES_SSL_3
+ * The topmost document uses SSL 3.0.
+ *
+ * STATE_USES_WEAK_CRYPTO
+ * The topmost document uses a weak cipher suite such as RC4.
+ *
+ * STATE_CERT_USER_OVERRIDDEN
+ * The user has added a security exception for the site.
+ */
+ const unsigned long STATE_USES_SSL_3 = 0x01000000;
+ const unsigned long STATE_USES_WEAK_CRYPTO = 0x02000000;
+ const unsigned long STATE_CERT_USER_OVERRIDDEN = 0x04000000;
+
+ /**
+ * Notification indicating the state has changed for one of the requests
+ * associated with aWebProgress.
+ *
+ * @param aWebProgress
+ * The nsIWebProgress instance that fired the notification
+ * @param aRequest
+ * The nsIRequest that has changed state.
+ * @param aStateFlags
+ * Flags indicating the new state. This value is a combination of one
+ * of the State Transition Flags and one or more of the State Type
+ * Flags defined above. Any undefined bits are reserved for future
+ * use.
+ * @param aStatus
+ * Error status code associated with the state change. This parameter
+ * should be ignored unless aStateFlags includes the STATE_STOP bit.
+ * The status code indicates success or failure of the request
+ * associated with the state change. NOTE: aStatus may be a success
+ * code even for server generated errors, such as the HTTP 404 error.
+ * In such cases, the request itself should be queried for extended
+ * error information (e.g., for HTTP requests see nsIHttpChannel).
+ */
+ void onStateChange(in nsIWebProgress aWebProgress,
+ in nsIRequest aRequest,
+ in unsigned long aStateFlags,
+ in nsresult aStatus);
+
+ /**
+ * Notification that the progress has changed for one of the requests
+ * associated with aWebProgress. Progress totals are reset to zero when all
+ * requests in aWebProgress complete (corresponding to onStateChange being
+ * called with aStateFlags including the STATE_STOP and STATE_IS_WINDOW
+ * flags).
+ *
+ * @param aWebProgress
+ * The nsIWebProgress instance that fired the notification.
+ * @param aRequest
+ * The nsIRequest that has new progress.
+ * @param aCurSelfProgress
+ * The current progress for aRequest.
+ * @param aMaxSelfProgress
+ * The maximum progress for aRequest.
+ * @param aCurTotalProgress
+ * The current progress for all requests associated with aWebProgress.
+ * @param aMaxTotalProgress
+ * The total progress for all requests associated with aWebProgress.
+ *
+ * NOTE: If any progress value is unknown, or if its value would exceed the
+ * maximum value of type long, then its value is replaced with -1.
+ *
+ * NOTE: If the object also implements nsIWebProgressListener2 and the caller
+ * knows about that interface, this function will not be called. Instead,
+ * nsIWebProgressListener2::onProgressChange64 will be called.
+ */
+ void onProgressChange(in nsIWebProgress aWebProgress,
+ in nsIRequest aRequest,
+ in long aCurSelfProgress,
+ in long aMaxSelfProgress,
+ in long aCurTotalProgress,
+ in long aMaxTotalProgress);
+
+ /**
+ * Flags for onLocationChange
+ *
+ * LOCATION_CHANGE_SAME_DOCUMENT
+ * This flag is on when |aWebProgress| did not load a new document.
+ * For example, the location change is due to an anchor scroll or a
+ * pushState/popState/replaceState.
+ *
+ * LOCATION_CHANGE_ERROR_PAGE
+ * This flag is on when |aWebProgress| redirected from the requested
+ * contents to an internal page to show error status, such as
+ * <about:neterror>, <about:certerror> and so on.
+ *
+ * Generally speaking, |aURI| and |aRequest| are the original data. DOM
+ * |window.location.href| is also the original location, while
+ * |document.documentURI| is the redirected location. Sometimes |aURI| is
+ * <about:blank> and |aRequest| is null when the original data does not
+ + remain.
+ *
+ * |aWebProgress| does NOT set this flag when it did not try to load a new
+ * document. In this case, it should set LOCATION_CHANGE_SAME_DOCUMENT.
+ */
+ const unsigned long LOCATION_CHANGE_SAME_DOCUMENT = 0x00000001;
+ const unsigned long LOCATION_CHANGE_ERROR_PAGE = 0x00000002;
+
+ /**
+ * Called when the location of the window being watched changes. This is not
+ * when a load is requested, but rather once it is verified that the load is
+ * going to occur in the given window. For instance, a load that starts in a
+ * window might send progress and status messages for the new site, but it
+ * will not send the onLocationChange until we are sure that we are loading
+ * this new page here.
+ *
+ * @param aWebProgress
+ * The nsIWebProgress instance that fired the notification.
+ * @param aRequest
+ * The associated nsIRequest. This may be null in some cases.
+ * @param aLocation
+ * The URI of the location that is being loaded.
+ * @param aFlags
+ * This is a value which explains the situation or the reason why
+ * the location has changed.
+ */
+ void onLocationChange(in nsIWebProgress aWebProgress,
+ in nsIRequest aRequest,
+ in nsIURI aLocation,
+ [optional] in unsigned long aFlags);
+
+ /**
+ * Notification that the status of a request has changed. The status message
+ * is intended to be displayed to the user (e.g., in the status bar of the
+ * browser).
+ *
+ * @param aWebProgress
+ * The nsIWebProgress instance that fired the notification.
+ * @param aRequest
+ * The nsIRequest that has new status.
+ * @param aStatus
+ * This value is not an error code. Instead, it is a numeric value
+ * that indicates the current status of the request. This interface
+ * does not define the set of possible status codes. NOTE: Some
+ * status values are defined by nsITransport and nsISocketTransport.
+ * @param aMessage
+ * Localized text corresponding to aStatus.
+ */
+ void onStatusChange(in nsIWebProgress aWebProgress,
+ in nsIRequest aRequest,
+ in nsresult aStatus,
+ in wstring aMessage);
+
+ /**
+ * Notification called for security progress. This method will be called on
+ * security transitions (eg HTTP -> HTTPS, HTTPS -> HTTP, FOO -> HTTPS) and
+ * after document load completion. It might also be called if an error
+ * occurs during network loading.
+ *
+ * @param aWebProgress
+ * The nsIWebProgress instance that fired the notification.
+ * @param aRequest
+ * The nsIRequest that has new security state.
+ * @param aState
+ * A value composed of the Security State Flags and the Security
+ * Strength Flags listed above. Any undefined bits are reserved for
+ * future use.
+ *
+ * NOTE: These notifications will only occur if a security package is
+ * installed.
+ */
+ void onSecurityChange(in nsIWebProgress aWebProgress,
+ in nsIRequest aRequest,
+ in unsigned long aState);
+};
diff --git a/uriloader/base/nsIWebProgressListener2.idl b/uriloader/base/nsIWebProgressListener2.idl
new file mode 100644
index 0000000000..87701f8d2c
--- /dev/null
+++ b/uriloader/base/nsIWebProgressListener2.idl
@@ -0,0 +1,69 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIWebProgressListener.idl"
+
+/**
+ * An extended version of nsIWebProgressListener.
+ */
+[scriptable, uuid(dde39de0-e4e0-11da-8ad9-0800200c9a66)]
+interface nsIWebProgressListener2 : nsIWebProgressListener {
+ /**
+ * Notification that the progress has changed for one of the requests
+ * associated with aWebProgress. Progress totals are reset to zero when all
+ * requests in aWebProgress complete (corresponding to onStateChange being
+ * called with aStateFlags including the STATE_STOP and STATE_IS_WINDOW
+ * flags).
+ *
+ * This function is identical to nsIWebProgressListener::onProgressChange,
+ * except that this function supports 64-bit values.
+ *
+ * @param aWebProgress
+ * The nsIWebProgress instance that fired the notification.
+ * @param aRequest
+ * The nsIRequest that has new progress.
+ * @param aCurSelfProgress
+ * The current progress for aRequest.
+ * @param aMaxSelfProgress
+ * The maximum progress for aRequest.
+ * @param aCurTotalProgress
+ * The current progress for all requests associated with aWebProgress.
+ * @param aMaxTotalProgress
+ * The total progress for all requests associated with aWebProgress.
+ *
+ * NOTE: If any progress value is unknown, then its value is replaced with -1.
+ *
+ * @see nsIWebProgressListener2::onProgressChange64
+ */
+ void onProgressChange64(in nsIWebProgress aWebProgress,
+ in nsIRequest aRequest,
+ in long long aCurSelfProgress,
+ in long long aMaxSelfProgress,
+ in long long aCurTotalProgress,
+ in long long aMaxTotalProgress);
+
+ /**
+ * Notification that a refresh or redirect has been requested in aWebProgress
+ * For example, via a <meta http-equiv="refresh"> or an HTTP Refresh: header
+ *
+ * @param aWebProgress
+ * The nsIWebProgress instance that fired the notification.
+ * @param aRefreshURI
+ * The new URI that aWebProgress has requested redirecting to.
+ * @param aMillis
+ * The delay (in milliseconds) before refresh.
+ * @param aSameURI
+ * True if aWebProgress is requesting a refresh of the
+ * current URI.
+ * False if aWebProgress is requesting a redirection to
+ * a different URI.
+ *
+ * @return True if the refresh may proceed.
+ * False if the refresh should be aborted.
+ */
+ boolean onRefreshAttempted(in nsIWebProgress aWebProgress,
+ in nsIURI aRefreshURI,
+ in long aMillis,
+ in boolean aSameURI);
+};
diff --git a/uriloader/base/nsURILoader.cpp b/uriloader/base/nsURILoader.cpp
new file mode 100644
index 0000000000..69475d68fd
--- /dev/null
+++ b/uriloader/base/nsURILoader.cpp
@@ -0,0 +1,966 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode:nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sts=2 sw=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsURILoader.h"
+#include "nsAutoPtr.h"
+#include "nsIURIContentListener.h"
+#include "nsIContentHandler.h"
+#include "nsILoadGroup.h"
+#include "nsIDocumentLoader.h"
+#include "nsIWebProgress.h"
+#include "nsIWebProgressListener.h"
+#include "nsIIOService.h"
+#include "nsIServiceManager.h"
+#include "nsIStreamListener.h"
+#include "nsIURI.h"
+#include "nsIChannel.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIProgressEventSink.h"
+#include "nsIInputStream.h"
+#include "nsIStreamConverterService.h"
+#include "nsWeakReference.h"
+#include "nsIHttpChannel.h"
+#include "nsIMultiPartChannel.h"
+#include "netCore.h"
+#include "nsCRT.h"
+#include "nsIDocShell.h"
+#include "nsIDocShellTreeItem.h"
+#include "nsIDocShellTreeOwner.h"
+#include "nsIThreadRetargetableStreamListener.h"
+
+#include "nsXPIDLString.h"
+#include "nsString.h"
+#include "nsThreadUtils.h"
+#include "nsReadableUtils.h"
+#include "nsError.h"
+
+#include "nsICategoryManager.h"
+#include "nsCExternalHandlerService.h" // contains contractids for the helper app service
+
+#include "nsIMIMEHeaderParam.h"
+#include "nsNetCID.h"
+
+#include "nsMimeTypes.h"
+
+#include "nsDocLoader.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Preferences.h"
+#include "nsContentUtils.h"
+
+mozilla::LazyLogModule nsURILoader::mLog("URILoader");
+
+#define LOG(args) MOZ_LOG(nsURILoader::mLog, mozilla::LogLevel::Debug, args)
+#define LOG_ERROR(args) MOZ_LOG(nsURILoader::mLog, mozilla::LogLevel::Error, args)
+#define LOG_ENABLED() MOZ_LOG_TEST(nsURILoader::mLog, mozilla::LogLevel::Debug)
+
+#define NS_PREF_DISABLE_BACKGROUND_HANDLING \
+ "security.exthelperapp.disable_background_handling"
+
+/**
+ * The nsDocumentOpenInfo contains the state required when a single
+ * document is being opened in order to discover the content type...
+ * Each instance remains alive until its target URL has been loaded
+ * (or aborted).
+ */
+class nsDocumentOpenInfo final : public nsIStreamListener
+ , public nsIThreadRetargetableStreamListener
+{
+public:
+ // Needed for nsCOMPtr to work right... Don't call this!
+ nsDocumentOpenInfo();
+
+ // Real constructor
+ // aFlags is a combination of the flags on nsIURILoader
+ nsDocumentOpenInfo(nsIInterfaceRequestor* aWindowContext,
+ uint32_t aFlags,
+ nsURILoader* aURILoader);
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ /**
+ * Prepares this object for receiving data. The stream
+ * listener methods of this class must not be called before calling this
+ * method.
+ */
+ nsresult Prepare();
+
+ // Call this (from OnStartRequest) to attempt to find an nsIStreamListener to
+ // take the data off our hands.
+ nsresult DispatchContent(nsIRequest *request, nsISupports * aCtxt);
+
+ // Call this if we need to insert a stream converter from aSrcContentType to
+ // aOutContentType into the StreamListener chain. DO NOT call it if the two
+ // types are the same, since no conversion is needed in that case.
+ nsresult ConvertData(nsIRequest *request,
+ nsIURIContentListener *aListener,
+ const nsACString & aSrcContentType,
+ const nsACString & aOutContentType);
+
+ /**
+ * Function to attempt to use aListener to handle the load. If
+ * true is returned, nothing else needs to be done; if false
+ * is returned, then a different way of handling the load should be
+ * tried.
+ */
+ bool TryContentListener(nsIURIContentListener* aListener,
+ nsIChannel* aChannel);
+
+ // nsIRequestObserver methods:
+ NS_DECL_NSIREQUESTOBSERVER
+
+ // nsIStreamListener methods:
+ NS_DECL_NSISTREAMLISTENER
+
+ // nsIThreadRetargetableStreamListener
+ NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
+protected:
+ ~nsDocumentOpenInfo();
+
+protected:
+ /**
+ * The first content listener to try dispatching data to. Typically
+ * the listener associated with the entity that originated the load.
+ */
+ nsCOMPtr<nsIURIContentListener> m_contentListener;
+
+ /**
+ * The stream listener to forward nsIStreamListener notifications
+ * to. This is set once the load is dispatched.
+ */
+ nsCOMPtr<nsIStreamListener> m_targetStreamListener;
+
+ /**
+ * A pointer to the entity that originated the load. We depend on getting
+ * things like nsIURIContentListeners, nsIDOMWindows, etc off of it.
+ */
+ nsCOMPtr<nsIInterfaceRequestor> m_originalContext;
+
+ /**
+ * IS_CONTENT_PREFERRED is used for the boolean to pass to CanHandleContent
+ * (also determines whether we use CanHandleContent or IsPreferred).
+ * DONT_RETARGET means that we will only try m_originalContext, no other
+ * listeners.
+ */
+ uint32_t mFlags;
+
+ /**
+ * The type of the data we will be trying to dispatch.
+ */
+ nsCString mContentType;
+
+ /**
+ * Reference to the URILoader service so we can access its list of
+ * nsIURIContentListeners.
+ */
+ RefPtr<nsURILoader> mURILoader;
+};
+
+NS_IMPL_ADDREF(nsDocumentOpenInfo)
+NS_IMPL_RELEASE(nsDocumentOpenInfo)
+
+NS_INTERFACE_MAP_BEGIN(nsDocumentOpenInfo)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIRequestObserver)
+ NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
+ NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
+ NS_INTERFACE_MAP_ENTRY(nsIThreadRetargetableStreamListener)
+NS_INTERFACE_MAP_END_THREADSAFE
+
+nsDocumentOpenInfo::nsDocumentOpenInfo()
+{
+ NS_NOTREACHED("This should never be called\n");
+}
+
+nsDocumentOpenInfo::nsDocumentOpenInfo(nsIInterfaceRequestor* aWindowContext,
+ uint32_t aFlags,
+ nsURILoader* aURILoader)
+ : m_originalContext(aWindowContext),
+ mFlags(aFlags),
+ mURILoader(aURILoader)
+{
+}
+
+nsDocumentOpenInfo::~nsDocumentOpenInfo()
+{
+}
+
+nsresult nsDocumentOpenInfo::Prepare()
+{
+ LOG(("[0x%p] nsDocumentOpenInfo::Prepare", this));
+
+ nsresult rv;
+
+ // ask our window context if it has a uri content listener...
+ m_contentListener = do_GetInterface(m_originalContext, &rv);
+ return rv;
+}
+
+NS_IMETHODIMP nsDocumentOpenInfo::OnStartRequest(nsIRequest *request, nsISupports * aCtxt)
+{
+ LOG(("[0x%p] nsDocumentOpenInfo::OnStartRequest", this));
+ MOZ_ASSERT(request);
+ if (!request) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsresult rv = NS_OK;
+
+ //
+ // Deal with "special" HTTP responses:
+ //
+ // - In the case of a 204 (No Content) or 205 (Reset Content) response, do
+ // not try to find a content handler. Return NS_BINDING_ABORTED to cancel
+ // the request. This has the effect of ensuring that the DocLoader does
+ // not try to interpret this as a real request.
+ //
+ nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(request, &rv));
+
+ if (NS_SUCCEEDED(rv)) {
+ uint32_t responseCode = 0;
+
+ rv = httpChannel->GetResponseStatus(&responseCode);
+
+ if (NS_FAILED(rv)) {
+ LOG_ERROR((" Failed to get HTTP response status"));
+
+ // behave as in the canceled case
+ return NS_OK;
+ }
+
+ LOG((" HTTP response status: %d", responseCode));
+
+ if (204 == responseCode || 205 == responseCode) {
+ return NS_BINDING_ABORTED;
+ }
+
+ static bool sLargeAllocationHeaderEnabled = false;
+ static bool sCachedLargeAllocationPref = false;
+ if (!sCachedLargeAllocationPref) {
+ sCachedLargeAllocationPref = true;
+ mozilla::Preferences::AddBoolVarCache(&sLargeAllocationHeaderEnabled,
+ "dom.largeAllocationHeader.enabled");
+ }
+
+ if (sLargeAllocationHeaderEnabled) {
+ // If we have a Large-Allocation header, let's check if we should perform a process switch.
+ nsAutoCString largeAllocationHeader;
+ rv = httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("Large-Allocation"), largeAllocationHeader);
+ if (NS_SUCCEEDED(rv) && nsContentUtils::AttemptLargeAllocationLoad(httpChannel)) {
+ return NS_BINDING_ABORTED;
+ }
+ }
+ }
+
+ //
+ // Make sure that the transaction has succeeded, so far...
+ //
+ nsresult status;
+
+ rv = request->GetStatus(&status);
+
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Unable to get request status!");
+ if (NS_FAILED(rv)) return rv;
+
+ if (NS_FAILED(status)) {
+ LOG_ERROR((" Request failed, status: 0x%08X", rv));
+
+ //
+ // The transaction has already reported an error - so it will be torn
+ // down. Therefore, it is not necessary to return an error code...
+ //
+ return NS_OK;
+ }
+
+ rv = DispatchContent(request, aCtxt);
+
+ LOG((" After dispatch, m_targetStreamListener: 0x%p, rv: 0x%08X", m_targetStreamListener.get(), rv));
+
+ NS_ASSERTION(NS_SUCCEEDED(rv) || !m_targetStreamListener,
+ "Must not have an m_targetStreamListener with a failure return!");
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (m_targetStreamListener)
+ rv = m_targetStreamListener->OnStartRequest(request, aCtxt);
+
+ LOG((" OnStartRequest returning: 0x%08X", rv));
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsDocumentOpenInfo::CheckListenerChain()
+{
+ NS_ASSERTION(NS_IsMainThread(), "Should be on the main thread!");
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener =
+ do_QueryInterface(m_targetStreamListener, &rv);
+ if (retargetableListener) {
+ rv = retargetableListener->CheckListenerChain();
+ }
+ LOG(("[0x%p] nsDocumentOpenInfo::CheckListenerChain %s listener %p rv %x",
+ this, (NS_SUCCEEDED(rv) ? "success" : "failure"),
+ (nsIStreamListener*)m_targetStreamListener, rv));
+ return rv;
+}
+
+NS_IMETHODIMP
+nsDocumentOpenInfo::OnDataAvailable(nsIRequest *request, nsISupports * aCtxt,
+ nsIInputStream * inStr,
+ uint64_t sourceOffset, uint32_t count)
+{
+ // if we have retarged to the end stream listener, then forward the call....
+ // otherwise, don't do anything
+
+ nsresult rv = NS_OK;
+
+ if (m_targetStreamListener)
+ rv = m_targetStreamListener->OnDataAvailable(request, aCtxt, inStr, sourceOffset, count);
+ return rv;
+}
+
+NS_IMETHODIMP nsDocumentOpenInfo::OnStopRequest(nsIRequest *request, nsISupports *aCtxt,
+ nsresult aStatus)
+{
+ LOG(("[0x%p] nsDocumentOpenInfo::OnStopRequest", this));
+
+ if ( m_targetStreamListener)
+ {
+ nsCOMPtr<nsIStreamListener> listener(m_targetStreamListener);
+
+ // If this is a multipart stream, we could get another
+ // OnStartRequest after this... reset state.
+ m_targetStreamListener = nullptr;
+ mContentType.Truncate();
+ listener->OnStopRequest(request, aCtxt, aStatus);
+ }
+
+ // Remember...
+ // In the case of multiplexed streams (such as multipart/x-mixed-replace)
+ // these stream listener methods could be called again :-)
+ //
+ return NS_OK;
+}
+
+nsresult nsDocumentOpenInfo::DispatchContent(nsIRequest *request, nsISupports * aCtxt)
+{
+ LOG(("[0x%p] nsDocumentOpenInfo::DispatchContent for type '%s'", this, mContentType.get()));
+
+ NS_PRECONDITION(!m_targetStreamListener,
+ "Why do we already have a target stream listener?");
+
+ nsresult rv;
+ nsCOMPtr<nsIChannel> aChannel = do_QueryInterface(request);
+ if (!aChannel) {
+ LOG_ERROR((" Request is not a channel. Bailing."));
+ return NS_ERROR_FAILURE;
+ }
+
+ NS_NAMED_LITERAL_CSTRING(anyType, "*/*");
+ if (mContentType.IsEmpty() || mContentType == anyType) {
+ rv = aChannel->GetContentType(mContentType);
+ if (NS_FAILED(rv)) return rv;
+ LOG((" Got type from channel: '%s'", mContentType.get()));
+ }
+
+ bool isGuessFromExt =
+ mContentType.LowerCaseEqualsASCII(APPLICATION_GUESS_FROM_EXT);
+ if (isGuessFromExt) {
+ // Reset to application/octet-stream for now; no one other than the
+ // external helper app service should see APPLICATION_GUESS_FROM_EXT.
+ mContentType = APPLICATION_OCTET_STREAM;
+ aChannel->SetContentType(NS_LITERAL_CSTRING(APPLICATION_OCTET_STREAM));
+ }
+
+ // Check whether the data should be forced to be handled externally. This
+ // could happen because the Content-Disposition header is set so, or, in the
+ // future, because the user has specified external handling for the MIME
+ // type.
+ bool forceExternalHandling = false;
+ uint32_t disposition;
+ rv = aChannel->GetContentDisposition(&disposition);
+
+ bool allowContentDispositionToForceExternalHandling = true;
+
+#ifdef MOZ_B2G
+
+ // On B2G, OMA content files should never be handled by an external handler
+ // (even if the server specifies Content-Disposition: attachment) because the
+ // data should never be stored on an unencrypted form.
+ allowContentDispositionToForceExternalHandling =
+ !mContentType.LowerCaseEqualsASCII("application/vnd.oma.drm.message");
+
+#endif
+
+ if (NS_SUCCEEDED(rv) && (disposition == nsIChannel::DISPOSITION_ATTACHMENT) &&
+ allowContentDispositionToForceExternalHandling) {
+ forceExternalHandling = true;
+ }
+
+ LOG((" forceExternalHandling: %s", forceExternalHandling ? "yes" : "no"));
+
+ // The type or data the contentListener wants.
+ nsXPIDLCString desiredContentType;
+
+ if (!forceExternalHandling)
+ {
+ //
+ // First step: See whether m_contentListener wants to handle this
+ // content type.
+ //
+ if (m_contentListener && TryContentListener(m_contentListener, aChannel)) {
+ LOG((" Success! Our default listener likes this type"));
+ // All done here
+ return NS_OK;
+ }
+
+ // If we aren't allowed to try other listeners, just skip through to
+ // trying to convert the data.
+ if (!(mFlags & nsIURILoader::DONT_RETARGET)) {
+
+ //
+ // Second step: See whether some other registered listener wants
+ // to handle this content type.
+ //
+ int32_t count = mURILoader->m_listeners.Count();
+ nsCOMPtr<nsIURIContentListener> listener;
+ for (int32_t i = 0; i < count; i++) {
+ listener = do_QueryReferent(mURILoader->m_listeners[i]);
+ if (listener) {
+ if (TryContentListener(listener, aChannel)) {
+ LOG((" Found listener registered on the URILoader"));
+ return NS_OK;
+ }
+ } else {
+ // remove from the listener list, reset i and update count
+ mURILoader->m_listeners.RemoveObjectAt(i--);
+ --count;
+ }
+ }
+
+ //
+ // Third step: Try to find a content listener that has not yet had
+ // the chance to register, as it is contained in a not-yet-loaded
+ // module, but which has registered a contract ID.
+ //
+ nsCOMPtr<nsICategoryManager> catman =
+ do_GetService(NS_CATEGORYMANAGER_CONTRACTID);
+ if (catman) {
+ nsXPIDLCString contractidString;
+ rv = catman->GetCategoryEntry(NS_CONTENT_LISTENER_CATEGORYMANAGER_ENTRY,
+ mContentType.get(),
+ getter_Copies(contractidString));
+ if (NS_SUCCEEDED(rv) && !contractidString.IsEmpty()) {
+ LOG((" Listener contractid for '%s' is '%s'",
+ mContentType.get(), contractidString.get()));
+
+ listener = do_CreateInstance(contractidString);
+ LOG((" Listener from category manager: 0x%p", listener.get()));
+
+ if (listener && TryContentListener(listener, aChannel)) {
+ LOG((" Listener from category manager likes this type"));
+ return NS_OK;
+ }
+ }
+ }
+
+ //
+ // Fourth step: try to find an nsIContentHandler for our type.
+ //
+ nsAutoCString handlerContractID (NS_CONTENT_HANDLER_CONTRACTID_PREFIX);
+ handlerContractID += mContentType;
+
+ nsCOMPtr<nsIContentHandler> contentHandler =
+ do_CreateInstance(handlerContractID.get());
+ if (contentHandler) {
+ LOG((" Content handler found"));
+ rv = contentHandler->HandleContent(mContentType.get(),
+ m_originalContext, request);
+ // XXXbz returning an error code to represent handling the
+ // content is just bizarre!
+ if (rv != NS_ERROR_WONT_HANDLE_CONTENT) {
+ if (NS_FAILED(rv)) {
+ // The content handler has unexpectedly failed. Cancel the request
+ // just in case the handler didn't...
+ LOG((" Content handler failed. Aborting load"));
+ request->Cancel(rv);
+ }
+ else {
+ LOG((" Content handler taking over load"));
+ }
+
+ return rv;
+ }
+ }
+ } else {
+ LOG((" DONT_RETARGET flag set, so skipped over random other content "
+ "listeners and content handlers"));
+ }
+
+ //
+ // Fifth step: If no listener prefers this type, see if any stream
+ // converters exist to transform this content type into
+ // some other.
+ //
+ // Don't do this if the server sent us a MIME type of "*/*" because they saw
+ // it in our Accept header and got confused.
+ // XXXbz have to be careful here; may end up in some sort of bizarre infinite
+ // decoding loop.
+ if (mContentType != anyType) {
+ rv = ConvertData(request, m_contentListener, mContentType, anyType);
+ if (NS_FAILED(rv)) {
+ m_targetStreamListener = nullptr;
+ } else if (m_targetStreamListener) {
+ // We found a converter for this MIME type. We'll just pump data into it
+ // and let the downstream nsDocumentOpenInfo handle things.
+ LOG((" Converter taking over now"));
+ return NS_OK;
+ }
+ }
+ }
+
+ NS_ASSERTION(!m_targetStreamListener,
+ "If we found a listener, why are we not using it?");
+
+ if (mFlags & nsIURILoader::DONT_RETARGET) {
+ LOG((" External handling forced or (listener not interested and no "
+ "stream converter exists), and retargeting disallowed -> aborting"));
+ return NS_ERROR_WONT_HANDLE_CONTENT;
+ }
+
+ // Before dispatching to the external helper app service, check for an HTTP
+ // error page. If we got one, we don't want to handle it with a helper app,
+ // really.
+ nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(request));
+ if (httpChannel) {
+ bool requestSucceeded;
+ httpChannel->GetRequestSucceeded(&requestSucceeded);
+ if (!requestSucceeded) {
+ // returning error from OnStartRequest will cancel the channel
+ return NS_ERROR_FILE_NOT_FOUND;
+ }
+ }
+
+ // Sixth step:
+ //
+ // All attempts to dispatch this content have failed. Just pass it off to
+ // the helper app service.
+ //
+
+ //
+ // Optionally, we may want to disable background handling by the external
+ // helper application service.
+ //
+ if (mozilla::Preferences::GetBool(NS_PREF_DISABLE_BACKGROUND_HANDLING,
+ false)) {
+ // First, we will ensure that the parent docshell is in an active
+ // state as we will disallow all external application handling unless it is
+ // in the foreground.
+ nsCOMPtr<nsIDocShell> docShell(do_GetInterface(m_originalContext));
+ if (!docShell) {
+ // If we can't perform our security check we definitely don't want to go
+ // any further!
+ LOG(("Failed to get DocShell to ensure it is active before anding off to "
+ "helper app service. Aborting."));
+ return NS_ERROR_FAILURE;
+ }
+
+ // Ensure the DocShell is active before continuing.
+ bool isActive = false;
+ docShell->GetIsActive(&isActive);
+ if (!isActive) {
+ LOG((" Check for active DocShell returned false. Aborting hand off to "
+ "helper app service."));
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+ }
+
+ nsCOMPtr<nsIExternalHelperAppService> helperAppService =
+ do_GetService(NS_EXTERNALHELPERAPPSERVICE_CONTRACTID, &rv);
+ if (helperAppService) {
+ LOG((" Passing load off to helper app service"));
+
+ // Set these flags to indicate that the channel has been targeted and that
+ // we are not using the original consumer.
+ nsLoadFlags loadFlags = 0;
+ request->GetLoadFlags(&loadFlags);
+ request->SetLoadFlags(loadFlags | nsIChannel::LOAD_RETARGETED_DOCUMENT_URI
+ | nsIChannel::LOAD_TARGETED);
+
+ if (isGuessFromExt) {
+ mContentType = APPLICATION_GUESS_FROM_EXT;
+ aChannel->SetContentType(NS_LITERAL_CSTRING(APPLICATION_GUESS_FROM_EXT));
+ }
+
+ rv = helperAppService->DoContent(mContentType,
+ request,
+ m_originalContext,
+ false,
+ nullptr,
+ getter_AddRefs(m_targetStreamListener));
+ if (NS_FAILED(rv)) {
+ request->SetLoadFlags(loadFlags);
+ m_targetStreamListener = nullptr;
+ }
+ }
+
+ NS_ASSERTION(m_targetStreamListener || NS_FAILED(rv),
+ "There is no way we should be successful at this point without a m_targetStreamListener");
+ return rv;
+}
+
+nsresult
+nsDocumentOpenInfo::ConvertData(nsIRequest *request,
+ nsIURIContentListener* aListener,
+ const nsACString& aSrcContentType,
+ const nsACString& aOutContentType)
+{
+ LOG(("[0x%p] nsDocumentOpenInfo::ConvertData from '%s' to '%s'", this,
+ PromiseFlatCString(aSrcContentType).get(),
+ PromiseFlatCString(aOutContentType).get()));
+
+ NS_PRECONDITION(aSrcContentType != aOutContentType,
+ "ConvertData called when the two types are the same!");
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsIStreamConverterService> StreamConvService =
+ do_GetService(NS_STREAMCONVERTERSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ LOG((" Got converter service"));
+
+ // When applying stream decoders, it is necessary to "insert" an
+ // intermediate nsDocumentOpenInfo instance to handle the targeting of
+ // the "final" stream or streams.
+ //
+ // For certain content types (ie. multi-part/x-mixed-replace) the input
+ // stream is split up into multiple destination streams. This
+ // intermediate instance is used to target these "decoded" streams...
+ //
+ RefPtr<nsDocumentOpenInfo> nextLink =
+ new nsDocumentOpenInfo(m_originalContext, mFlags, mURILoader);
+
+ LOG((" Downstream DocumentOpenInfo would be: 0x%p", nextLink.get()));
+
+ // Make sure nextLink starts with the contentListener that said it wanted the
+ // results of this decode.
+ nextLink->m_contentListener = aListener;
+ // Also make sure it has to look for a stream listener to pump data into.
+ nextLink->m_targetStreamListener = nullptr;
+
+ // Make sure that nextLink treats the data as aOutContentType when
+ // dispatching; that way even if the stream converters don't change the type
+ // on the channel we will still do the right thing. If aOutContentType is
+ // */*, that's OK -- that will just indicate to nextLink that it should get
+ // the type off the channel.
+ nextLink->mContentType = aOutContentType;
+
+ // The following call sets m_targetStreamListener to the input end of the
+ // stream converter and sets the output end of the stream converter to
+ // nextLink. As we pump data into m_targetStreamListener the stream
+ // converter will convert it and pass the converted data to nextLink.
+ return StreamConvService->AsyncConvertData(PromiseFlatCString(aSrcContentType).get(),
+ PromiseFlatCString(aOutContentType).get(),
+ nextLink,
+ request,
+ getter_AddRefs(m_targetStreamListener));
+}
+
+bool
+nsDocumentOpenInfo::TryContentListener(nsIURIContentListener* aListener,
+ nsIChannel* aChannel)
+{
+ LOG(("[0x%p] nsDocumentOpenInfo::TryContentListener; mFlags = 0x%x",
+ this, mFlags));
+
+ NS_PRECONDITION(aListener, "Must have a non-null listener");
+ NS_PRECONDITION(aChannel, "Must have a channel");
+
+ bool listenerWantsContent = false;
+ nsXPIDLCString typeToUse;
+
+ if (mFlags & nsIURILoader::IS_CONTENT_PREFERRED) {
+ aListener->IsPreferred(mContentType.get(),
+ getter_Copies(typeToUse),
+ &listenerWantsContent);
+ } else {
+ aListener->CanHandleContent(mContentType.get(), false,
+ getter_Copies(typeToUse),
+ &listenerWantsContent);
+ }
+ if (!listenerWantsContent) {
+ LOG((" Listener is not interested"));
+ return false;
+ }
+
+ if (!typeToUse.IsEmpty() && typeToUse != mContentType) {
+ // Need to do a conversion here.
+
+ nsresult rv = ConvertData(aChannel, aListener, mContentType, typeToUse);
+
+ if (NS_FAILED(rv)) {
+ // No conversion path -- we don't want this listener, if we got one
+ m_targetStreamListener = nullptr;
+ }
+
+ LOG((" Found conversion: %s", m_targetStreamListener ? "yes" : "no"));
+
+ // m_targetStreamListener is now the input end of the converter, and we can
+ // just pump the data in there, if it exists. If it does not, we need to
+ // try other nsIURIContentListeners.
+ return m_targetStreamListener != nullptr;
+ }
+
+ // At this point, aListener wants data of type mContentType. Let 'em have
+ // it. But first, if we are retargeting, set an appropriate flag on the
+ // channel
+ nsLoadFlags loadFlags = 0;
+ aChannel->GetLoadFlags(&loadFlags);
+
+ // Set this flag to indicate that the channel has been targeted at a final
+ // consumer. This load flag is tested in nsDocLoader::OnProgress.
+ nsLoadFlags newLoadFlags = nsIChannel::LOAD_TARGETED;
+
+ nsCOMPtr<nsIURIContentListener> originalListener =
+ do_GetInterface(m_originalContext);
+ if (originalListener != aListener) {
+ newLoadFlags |= nsIChannel::LOAD_RETARGETED_DOCUMENT_URI;
+ }
+ aChannel->SetLoadFlags(loadFlags | newLoadFlags);
+
+ bool abort = false;
+ bool isPreferred = (mFlags & nsIURILoader::IS_CONTENT_PREFERRED) != 0;
+ nsresult rv = aListener->DoContent(mContentType,
+ isPreferred,
+ aChannel,
+ getter_AddRefs(m_targetStreamListener),
+ &abort);
+
+ if (NS_FAILED(rv)) {
+ LOG_ERROR((" DoContent failed"));
+
+ // Unset the RETARGETED_DOCUMENT_URI flag if we set it...
+ aChannel->SetLoadFlags(loadFlags);
+ m_targetStreamListener = nullptr;
+ return false;
+ }
+
+ if (abort) {
+ // Nothing else to do here -- aListener is handling it all. Make
+ // sure m_targetStreamListener is null so we don't do anything
+ // after this point.
+ LOG((" Listener has aborted the load"));
+ m_targetStreamListener = nullptr;
+ }
+
+ NS_ASSERTION(abort || m_targetStreamListener, "DoContent returned no listener?");
+
+ // aListener is handling the load from this point on.
+ return true;
+}
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////
+// Implementation of nsURILoader
+///////////////////////////////////////////////////////////////////////////////////////////////
+
+nsURILoader::nsURILoader()
+{
+}
+
+nsURILoader::~nsURILoader()
+{
+}
+
+NS_IMPL_ADDREF(nsURILoader)
+NS_IMPL_RELEASE(nsURILoader)
+
+NS_INTERFACE_MAP_BEGIN(nsURILoader)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIURILoader)
+ NS_INTERFACE_MAP_ENTRY(nsIURILoader)
+NS_INTERFACE_MAP_END
+
+NS_IMETHODIMP nsURILoader::RegisterContentListener(nsIURIContentListener * aContentListener)
+{
+ nsresult rv = NS_OK;
+
+ nsWeakPtr weakListener = do_GetWeakReference(aContentListener);
+ NS_ASSERTION(weakListener, "your URIContentListener must support weak refs!\n");
+
+ if (weakListener)
+ m_listeners.AppendObject(weakListener);
+
+ return rv;
+}
+
+NS_IMETHODIMP nsURILoader::UnRegisterContentListener(nsIURIContentListener * aContentListener)
+{
+ nsWeakPtr weakListener = do_GetWeakReference(aContentListener);
+ if (weakListener)
+ m_listeners.RemoveObject(weakListener);
+
+ return NS_OK;
+
+}
+
+NS_IMETHODIMP nsURILoader::OpenURI(nsIChannel *channel,
+ uint32_t aFlags,
+ nsIInterfaceRequestor *aWindowContext)
+{
+ NS_ENSURE_ARG_POINTER(channel);
+
+ if (LOG_ENABLED()) {
+ nsCOMPtr<nsIURI> uri;
+ channel->GetURI(getter_AddRefs(uri));
+ nsAutoCString spec;
+ uri->GetAsciiSpec(spec);
+ LOG(("nsURILoader::OpenURI for %s", spec.get()));
+ }
+
+ nsCOMPtr<nsIStreamListener> loader;
+ nsresult rv = OpenChannel(channel,
+ aFlags,
+ aWindowContext,
+ false,
+ getter_AddRefs(loader));
+
+ if (NS_SUCCEEDED(rv)) {
+ // this method is not complete!!! Eventually, we should first go
+ // to the content listener and ask them for a protocol handler...
+ // if they don't give us one, we need to go to the registry and get
+ // the preferred protocol handler.
+
+ // But for now, I'm going to let necko do the work for us....
+ rv = channel->AsyncOpen(loader, nullptr);
+
+ // no content from this load - that's OK.
+ if (rv == NS_ERROR_NO_CONTENT) {
+ LOG((" rv is NS_ERROR_NO_CONTENT -- doing nothing"));
+ rv = NS_OK;
+ }
+ } else if (rv == NS_ERROR_WONT_HANDLE_CONTENT) {
+ // Not really an error, from this method's point of view
+ rv = NS_OK;
+ }
+ return rv;
+}
+
+nsresult nsURILoader::OpenChannel(nsIChannel* channel,
+ uint32_t aFlags,
+ nsIInterfaceRequestor* aWindowContext,
+ bool aChannelIsOpen,
+ nsIStreamListener** aListener)
+{
+ NS_ASSERTION(channel, "Trying to open a null channel!");
+ NS_ASSERTION(aWindowContext, "Window context must not be null");
+
+ if (LOG_ENABLED()) {
+ nsCOMPtr<nsIURI> uri;
+ channel->GetURI(getter_AddRefs(uri));
+ nsAutoCString spec;
+ uri->GetAsciiSpec(spec);
+ LOG(("nsURILoader::OpenChannel for %s", spec.get()));
+ }
+
+ // Let the window context's uriListener know that the open is starting. This
+ // gives that window a chance to abort the load process.
+ nsCOMPtr<nsIURIContentListener> winContextListener(do_GetInterface(aWindowContext));
+ if (winContextListener) {
+ nsCOMPtr<nsIURI> uri;
+ channel->GetURI(getter_AddRefs(uri));
+ if (uri) {
+ bool doAbort = false;
+ winContextListener->OnStartURIOpen(uri, &doAbort);
+
+ if (doAbort) {
+ LOG((" OnStartURIOpen aborted load"));
+ return NS_ERROR_WONT_HANDLE_CONTENT;
+ }
+ }
+ }
+
+ // we need to create a DocumentOpenInfo object which will go ahead and open
+ // the url and discover the content type....
+ RefPtr<nsDocumentOpenInfo> loader =
+ new nsDocumentOpenInfo(aWindowContext, aFlags, this);
+
+ // Set the correct loadgroup on the channel
+ nsCOMPtr<nsILoadGroup> loadGroup(do_GetInterface(aWindowContext));
+
+ if (!loadGroup) {
+ // XXXbz This context is violating what we'd like to be the new uriloader
+ // api.... Set up a nsDocLoader to handle the loadgroup for this context.
+ // This really needs to go away!
+ nsCOMPtr<nsIURIContentListener> listener(do_GetInterface(aWindowContext));
+ if (listener) {
+ nsCOMPtr<nsISupports> cookie;
+ listener->GetLoadCookie(getter_AddRefs(cookie));
+ if (!cookie) {
+ RefPtr<nsDocLoader> newDocLoader = new nsDocLoader();
+ nsresult rv = newDocLoader->Init();
+ if (NS_FAILED(rv))
+ return rv;
+ rv = nsDocLoader::AddDocLoaderAsChildOfRoot(newDocLoader);
+ if (NS_FAILED(rv))
+ return rv;
+ cookie = nsDocLoader::GetAsSupports(newDocLoader);
+ listener->SetLoadCookie(cookie);
+ }
+ loadGroup = do_GetInterface(cookie);
+ }
+ }
+
+ // If the channel is pending, then we need to remove it from its current
+ // loadgroup
+ nsCOMPtr<nsILoadGroup> oldGroup;
+ channel->GetLoadGroup(getter_AddRefs(oldGroup));
+ if (aChannelIsOpen && !SameCOMIdentity(oldGroup, loadGroup)) {
+ // It is important to add the channel to the new group before
+ // removing it from the old one, so that the load isn't considered
+ // done as soon as the request is removed.
+ loadGroup->AddRequest(channel, nullptr);
+
+ if (oldGroup) {
+ oldGroup->RemoveRequest(channel, nullptr, NS_BINDING_RETARGETED);
+ }
+ }
+
+ channel->SetLoadGroup(loadGroup);
+
+ // prepare the loader for receiving data
+ nsresult rv = loader->Prepare();
+ if (NS_SUCCEEDED(rv))
+ NS_ADDREF(*aListener = loader);
+ return rv;
+}
+
+NS_IMETHODIMP nsURILoader::OpenChannel(nsIChannel* channel,
+ uint32_t aFlags,
+ nsIInterfaceRequestor* aWindowContext,
+ nsIStreamListener** aListener)
+{
+ bool pending;
+ if (NS_FAILED(channel->IsPending(&pending))) {
+ pending = false;
+ }
+
+ return OpenChannel(channel, aFlags, aWindowContext, pending, aListener);
+}
+
+NS_IMETHODIMP nsURILoader::Stop(nsISupports* aLoadCookie)
+{
+ nsresult rv;
+ nsCOMPtr<nsIDocumentLoader> docLoader;
+
+ NS_ENSURE_ARG_POINTER(aLoadCookie);
+
+ docLoader = do_GetInterface(aLoadCookie, &rv);
+ if (docLoader) {
+ rv = docLoader->Stop();
+ }
+ return rv;
+}
+
diff --git a/uriloader/base/nsURILoader.h b/uriloader/base/nsURILoader.h
new file mode 100644
index 0000000000..2c5648dbae
--- /dev/null
+++ b/uriloader/base/nsURILoader.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsURILoader_h__
+#define nsURILoader_h__
+
+#include "nsCURILoader.h"
+#include "nsISupportsUtils.h"
+#include "nsCOMArray.h"
+#include "nsCOMPtr.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsString.h"
+#include "nsIWeakReference.h"
+#include "mozilla/Attributes.h"
+
+#include "mozilla/Logging.h"
+
+class nsDocumentOpenInfo;
+
+class nsURILoader final : public nsIURILoader
+{
+public:
+ NS_DECL_NSIURILOADER
+ NS_DECL_ISUPPORTS
+
+ nsURILoader();
+
+protected:
+ ~nsURILoader();
+
+ /**
+ * Equivalent to nsIURILoader::openChannel, but allows specifying whether the
+ * channel is opened already.
+ */
+ MOZ_MUST_USE nsresult OpenChannel(nsIChannel* channel,
+ uint32_t aFlags,
+ nsIInterfaceRequestor* aWindowContext,
+ bool aChannelOpen,
+ nsIStreamListener** aListener);
+
+ /**
+ * we shouldn't need to have an owning ref count on registered
+ * content listeners because they are supposed to unregister themselves
+ * when they go away. This array stores weak references
+ */
+ nsCOMArray<nsIWeakReference> m_listeners;
+
+ /**
+ * Logging. The module is called "URILoader"
+ */
+ static mozilla::LazyLogModule mLog;
+
+ friend class nsDocumentOpenInfo;
+};
+
+#endif /* nsURILoader_h__ */
diff --git a/uriloader/exthandler/ContentHandlerService.cpp b/uriloader/exthandler/ContentHandlerService.cpp
new file mode 100644
index 0000000000..75575a7304
--- /dev/null
+++ b/uriloader/exthandler/ContentHandlerService.cpp
@@ -0,0 +1,169 @@
+#include "ContentHandlerService.h"
+#include "HandlerServiceChild.h"
+#include "ContentChild.h"
+#include "nsIMutableArray.h"
+#include "nsIMIMEInfo.h"
+
+using mozilla::dom::ContentChild;
+using mozilla::dom::PHandlerServiceChild;
+using mozilla::dom::HandlerInfo;
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_ISUPPORTS(ContentHandlerService, nsIHandlerService)
+
+ContentHandlerService::ContentHandlerService()
+{
+}
+
+nsresult
+ContentHandlerService::Init()
+{
+ if (!XRE_IsContentProcess()) {
+ return NS_ERROR_FAILURE;
+ }
+ ContentChild* cpc = ContentChild::GetSingleton();
+
+ mHandlerServiceChild = static_cast<HandlerServiceChild*>(cpc->SendPHandlerServiceConstructor());
+ return NS_OK;
+}
+
+void
+ContentHandlerService::nsIHandlerInfoToHandlerInfo(nsIHandlerInfo* aInfo,
+ HandlerInfo* aHandlerInfo)
+{
+ nsCString type;
+ aInfo->GetType(type);
+ nsCOMPtr<nsIMIMEInfo> mimeInfo = do_QueryInterface(aInfo);
+ bool isMIMEInfo = !!mimeInfo;
+ nsString description;
+ aInfo->GetDescription(description);
+ bool alwaysAskBeforeHandling;
+ aInfo->GetAlwaysAskBeforeHandling(&alwaysAskBeforeHandling);
+ nsCOMPtr<nsIHandlerApp> app;
+ aInfo->GetPreferredApplicationHandler(getter_AddRefs(app));
+ nsString name;
+ nsString detailedDescription;
+ if (app) {
+ app->GetName(name);
+ app->GetDetailedDescription(detailedDescription);
+ }
+ HandlerApp happ(name, detailedDescription);
+ nsTArray<HandlerApp> happs;
+ nsCOMPtr<nsIMutableArray> apps;
+ aInfo->GetPossibleApplicationHandlers(getter_AddRefs(apps));
+ if (apps) {
+ unsigned int length;
+ apps->GetLength(&length);
+ for (unsigned int i = 0; i < length; i++) {
+ apps->QueryElementAt(i, NS_GET_IID(nsIHandlerApp), getter_AddRefs(app));
+ app->GetName(name);
+ app->GetDetailedDescription(detailedDescription);
+ happs.AppendElement(HandlerApp(name, detailedDescription));
+ }
+ }
+ nsHandlerInfoAction action;
+ aInfo->GetPreferredAction(&action);
+ HandlerInfo info(type, isMIMEInfo, description, alwaysAskBeforeHandling, happ, happs, action);
+ *aHandlerInfo = info;
+}
+
+
+NS_IMETHODIMP RemoteHandlerApp::GetName(nsAString & aName)
+{
+ aName.Assign(mAppChild.name());
+ return NS_OK;
+}
+
+NS_IMETHODIMP RemoteHandlerApp::SetName(const nsAString & aName)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP RemoteHandlerApp::GetDetailedDescription(nsAString & aDetailedDescription)
+{
+ aDetailedDescription.Assign(mAppChild.detailedDescription());
+ return NS_OK;
+}
+
+NS_IMETHODIMP RemoteHandlerApp::SetDetailedDescription(const nsAString & aDetailedDescription)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP RemoteHandlerApp::Equals(nsIHandlerApp *aHandlerApp, bool *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP RemoteHandlerApp::LaunchWithURI(nsIURI *aURI, nsIInterfaceRequestor *aWindowContext)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMPL_ISUPPORTS(RemoteHandlerApp, nsIHandlerApp)
+
+static inline void CopyHanderInfoTonsIHandlerInfo(HandlerInfo info, nsIHandlerInfo* aHandlerInfo)
+{
+ HandlerApp preferredApplicationHandler = info.preferredApplicationHandler();
+ nsCOMPtr<nsIHandlerApp> preferredApp(new RemoteHandlerApp(preferredApplicationHandler));
+ aHandlerInfo->SetPreferredApplicationHandler(preferredApp);
+ nsCOMPtr<nsIMutableArray> possibleHandlers;
+ aHandlerInfo->GetPossibleApplicationHandlers(getter_AddRefs(possibleHandlers));
+ possibleHandlers->AppendElement(preferredApp, false);
+}
+ContentHandlerService::~ContentHandlerService()
+{
+}
+
+NS_IMETHODIMP ContentHandlerService::Enumerate(nsISimpleEnumerator * *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP ContentHandlerService::FillHandlerInfo(nsIHandlerInfo *aHandlerInfo, const nsACString & aOverrideType)
+{
+ HandlerInfo info;
+ nsIHandlerInfoToHandlerInfo(aHandlerInfo, &info);
+ mHandlerServiceChild->SendFillHandlerInfo(info, nsCString(aOverrideType), &info);
+ CopyHanderInfoTonsIHandlerInfo(info, aHandlerInfo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP ContentHandlerService::Store(nsIHandlerInfo *aHandlerInfo)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP ContentHandlerService::Exists(nsIHandlerInfo *aHandlerInfo, bool *_retval)
+{
+ HandlerInfo info;
+ nsIHandlerInfoToHandlerInfo(aHandlerInfo, &info);
+ mHandlerServiceChild->SendExists(info, _retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP ContentHandlerService::Remove(nsIHandlerInfo *aHandlerInfo)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+
+NS_IMETHODIMP ContentHandlerService::GetTypeFromExtension(const nsACString & aFileExtension, nsACString & _retval)
+{
+ nsCString* cachedType = nullptr;
+ if (!!mExtToTypeMap.Get(aFileExtension, &cachedType) && !!cachedType) {
+ _retval.Assign(*cachedType);
+ return NS_OK;
+ }
+ nsCString type;
+ mHandlerServiceChild->SendGetTypeFromExtension(nsCString(aFileExtension), &type);
+ _retval.Assign(type);
+ mExtToTypeMap.Put(nsCString(aFileExtension), new nsCString(type));
+
+ return NS_OK;
+}
+
+}
+}
diff --git a/uriloader/exthandler/ContentHandlerService.h b/uriloader/exthandler/ContentHandlerService.h
new file mode 100644
index 0000000000..e39e89bc90
--- /dev/null
+++ b/uriloader/exthandler/ContentHandlerService.h
@@ -0,0 +1,52 @@
+#ifndef ContentHandlerService_h
+#define ContentHandlerService_h
+
+#include "nsIHandlerService.h"
+#include "nsClassHashtable.h"
+#include "HandlerServiceChild.h"
+#include "nsIMIMEInfo.h"
+
+#define NS_CONTENTHANDLERSERVICE_CID \
+ {0xc4b6fb7c, 0xbfb1, 0x49dc, {0xa6, 0x5f, 0x03, 0x57, 0x96, 0x52, 0x4b, 0x53}}
+
+namespace mozilla {
+namespace dom {
+
+class PHandlerServiceChild;
+
+class ContentHandlerService : public nsIHandlerService
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIHANDLERSERVICE
+
+ ContentHandlerService();
+ MOZ_MUST_USE nsresult Init();
+ static void nsIHandlerInfoToHandlerInfo(nsIHandlerInfo* aInfo, HandlerInfo* aHandlerInfo);
+
+private:
+ virtual ~ContentHandlerService();
+ RefPtr<HandlerServiceChild> mHandlerServiceChild;
+ nsClassHashtable<nsCStringHashKey, nsCString> mExtToTypeMap;
+};
+
+class RemoteHandlerApp : public nsIHandlerApp
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIHANDLERAPP
+
+ explicit RemoteHandlerApp(HandlerApp aAppChild) : mAppChild(aAppChild)
+ {
+ }
+private:
+ virtual ~RemoteHandlerApp()
+ {
+ }
+ HandlerApp mAppChild;
+};
+
+
+}
+}
+#endif
diff --git a/uriloader/exthandler/ExternalHelperAppChild.cpp b/uriloader/exthandler/ExternalHelperAppChild.cpp
new file mode 100644
index 0000000000..584e596b9f
--- /dev/null
+++ b/uriloader/exthandler/ExternalHelperAppChild.cpp
@@ -0,0 +1,127 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ExternalHelperAppChild.h"
+#include "mozilla/net/ChannelDiverterChild.h"
+#include "nsIDivertableChannel.h"
+#include "nsIInputStream.h"
+#include "nsIFTPChannel.h"
+#include "nsIRequest.h"
+#include "nsIResumableChannel.h"
+#include "nsNetUtil.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_ISUPPORTS(ExternalHelperAppChild,
+ nsIStreamListener,
+ nsIRequestObserver)
+
+ExternalHelperAppChild::ExternalHelperAppChild()
+ : mStatus(NS_OK)
+{
+}
+
+ExternalHelperAppChild::~ExternalHelperAppChild()
+{
+}
+
+//-----------------------------------------------------------------------------
+// nsIStreamListener
+//-----------------------------------------------------------------------------
+NS_IMETHODIMP
+ExternalHelperAppChild::OnDataAvailable(nsIRequest *request,
+ nsISupports *ctx,
+ nsIInputStream *input,
+ uint64_t offset,
+ uint32_t count)
+{
+ if (NS_FAILED(mStatus))
+ return mStatus;
+
+ nsCString data;
+ nsresult rv = NS_ReadInputStreamToString(input, data, count);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (!SendOnDataAvailable(data, offset, count))
+ return NS_ERROR_UNEXPECTED;
+
+ return NS_OK;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// nsIRequestObserver
+//////////////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP
+ExternalHelperAppChild::OnStartRequest(nsIRequest *request, nsISupports *ctx)
+{
+ nsCOMPtr<nsIDivertableChannel> divertable = do_QueryInterface(request);
+ if (divertable) {
+ return DivertToParent(divertable, request);
+ }
+
+ nsresult rv = mHandler->OnStartRequest(request, ctx);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_UNEXPECTED);
+
+ nsCString entityID;
+ nsCOMPtr<nsIResumableChannel> resumable(do_QueryInterface(request));
+ if (resumable) {
+ resumable->GetEntityID(entityID);
+ }
+ SendOnStartRequest(entityID);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ExternalHelperAppChild::OnStopRequest(nsIRequest *request,
+ nsISupports *ctx,
+ nsresult status)
+{
+ // mHandler can be null if we diverted the request to the parent
+ if (mHandler) {
+ nsresult rv = mHandler->OnStopRequest(request, ctx, status);
+ SendOnStopRequest(status);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_UNEXPECTED);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+ExternalHelperAppChild::DivertToParent(nsIDivertableChannel *divertable,
+ nsIRequest *request)
+{
+ // nsIDivertable must know about content conversions before being diverted.
+ MOZ_ASSERT(mHandler);
+ mHandler->MaybeApplyDecodingForExtension(request);
+
+ mozilla::net::ChannelDiverterChild *diverter = nullptr;
+ nsresult rv = divertable->DivertToParent(&diverter);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ MOZ_ASSERT(diverter);
+
+ if (SendDivertToParentUsing(diverter)) {
+ mHandler->DidDivertRequest(request);
+ mHandler = nullptr;
+ return NS_OK;
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+bool
+ExternalHelperAppChild::RecvCancel(const nsresult& aStatus)
+{
+ mStatus = aStatus;
+ return true;
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/uriloader/exthandler/ExternalHelperAppChild.h b/uriloader/exthandler/ExternalHelperAppChild.h
new file mode 100644
index 0000000000..eecca01e61
--- /dev/null
+++ b/uriloader/exthandler/ExternalHelperAppChild.h
@@ -0,0 +1,45 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_ExternalHelperAppChild_h
+#define mozilla_dom_ExternalHelperAppChild_h
+
+#include "mozilla/dom/PExternalHelperAppChild.h"
+#include "nsExternalHelperAppService.h"
+#include "nsIStreamListener.h"
+
+class nsIDivertableChannel;
+
+namespace mozilla {
+namespace dom {
+
+class ExternalHelperAppChild : public PExternalHelperAppChild
+ , public nsIStreamListener
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIREQUESTOBSERVER
+
+ ExternalHelperAppChild();
+
+ // Give the listener a real nsExternalAppHandler to complete processing on
+ // the child.
+ void SetHandler(nsExternalAppHandler *handler) { mHandler = handler; }
+
+ virtual bool RecvCancel(const nsresult& aStatus) override;
+private:
+ virtual ~ExternalHelperAppChild();
+ MOZ_MUST_USE nsresult DivertToParent(nsIDivertableChannel *divertable, nsIRequest *request);
+
+ RefPtr<nsExternalAppHandler> mHandler;
+ nsresult mStatus;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_ExternalHelperAppChild_h
diff --git a/uriloader/exthandler/ExternalHelperAppParent.cpp b/uriloader/exthandler/ExternalHelperAppParent.cpp
new file mode 100644
index 0000000000..a8ddc54c84
--- /dev/null
+++ b/uriloader/exthandler/ExternalHelperAppParent.cpp
@@ -0,0 +1,512 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/DebugOnly.h"
+
+#include "ExternalHelperAppParent.h"
+#include "nsIContent.h"
+#include "nsCExternalHandlerService.h"
+#include "nsIExternalHelperAppService.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/TabParent.h"
+#include "nsIBrowserDOMWindow.h"
+#include "nsStringStream.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "nsNetUtil.h"
+#include "nsIDocument.h"
+#include "mozilla/net/ChannelDiverterParent.h"
+
+#include "mozilla/Unused.h"
+
+using namespace mozilla::ipc;
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_ISUPPORTS_INHERITED(ExternalHelperAppParent,
+ nsHashPropertyBag,
+ nsIRequest,
+ nsIChannel,
+ nsIMultiPartChannel,
+ nsIPrivateBrowsingChannel,
+ nsIResumableChannel,
+ nsIStreamListener,
+ nsIExternalHelperAppParent)
+
+ExternalHelperAppParent::ExternalHelperAppParent(
+ const OptionalURIParams& uri,
+ const int64_t& aContentLength,
+ const bool& aWasFileChannel)
+ : mURI(DeserializeURI(uri))
+ , mPending(false)
+#ifdef DEBUG
+ , mDiverted(false)
+#endif
+ , mIPCClosed(false)
+ , mLoadFlags(0)
+ , mStatus(NS_OK)
+ , mContentLength(aContentLength)
+ , mWasFileChannel(aWasFileChannel)
+{
+}
+
+void
+ExternalHelperAppParent::Init(ContentParent *parent,
+ const nsCString& aMimeContentType,
+ const nsCString& aContentDispositionHeader,
+ const uint32_t& aContentDispositionHint,
+ const nsString& aContentDispositionFilename,
+ const bool& aForceSave,
+ const OptionalURIParams& aReferrer,
+ PBrowserParent* aBrowser)
+{
+ nsCOMPtr<nsIExternalHelperAppService> helperAppService =
+ do_GetService(NS_EXTERNALHELPERAPPSERVICE_CONTRACTID);
+ NS_ASSERTION(helperAppService, "No Helper App Service!");
+
+ nsCOMPtr<nsIURI> referrer = DeserializeURI(aReferrer);
+ if (referrer)
+ SetPropertyAsInterface(NS_LITERAL_STRING("docshell.internalReferrer"), referrer);
+
+ mContentDispositionHeader = aContentDispositionHeader;
+ if (!mContentDispositionHeader.IsEmpty()) {
+ NS_GetFilenameFromDisposition(mContentDispositionFilename,
+ mContentDispositionHeader,
+ mURI);
+ mContentDisposition =
+ NS_GetContentDispositionFromHeader(mContentDispositionHeader, this);
+ }
+ else {
+ mContentDisposition = aContentDispositionHint;
+ mContentDispositionFilename = aContentDispositionFilename;
+ }
+
+ nsCOMPtr<nsIInterfaceRequestor> window;
+ if (aBrowser) {
+ TabParent* tabParent = TabParent::GetFrom(aBrowser);
+ if (tabParent->GetOwnerElement())
+ window = do_QueryInterface(tabParent->GetOwnerElement()->OwnerDoc()->GetWindow());
+
+ bool isPrivate = false;
+ nsCOMPtr<nsILoadContext> loadContext = tabParent->GetLoadContext();
+ loadContext->GetUsePrivateBrowsing(&isPrivate);
+ SetPrivate(isPrivate);
+ }
+
+ helperAppService->DoContent(aMimeContentType, this, window,
+ aForceSave, nullptr,
+ getter_AddRefs(mListener));
+}
+
+void
+ExternalHelperAppParent::ActorDestroy(ActorDestroyReason why)
+{
+ mIPCClosed = true;
+}
+
+void
+ExternalHelperAppParent::Delete()
+{
+ if (!mIPCClosed) {
+ Unused << Send__delete__(this);
+ }
+}
+
+bool
+ExternalHelperAppParent::RecvOnStartRequest(const nsCString& entityID)
+{
+ MOZ_ASSERT(!mDiverted, "child forwarding callbacks after request was diverted");
+
+ mEntityID = entityID;
+ mPending = true;
+ mStatus = mListener->OnStartRequest(this, nullptr);
+ return true;
+}
+
+bool
+ExternalHelperAppParent::RecvOnDataAvailable(const nsCString& data,
+ const uint64_t& offset,
+ const uint32_t& count)
+{
+ if (NS_FAILED(mStatus))
+ return true;
+
+ MOZ_ASSERT(!mDiverted, "child forwarding callbacks after request was diverted");
+ MOZ_ASSERT(mPending, "must be pending!");
+
+ nsCOMPtr<nsIInputStream> stringStream;
+ DebugOnly<nsresult> rv = NS_NewByteInputStream(getter_AddRefs(stringStream), data.get(), count, NS_ASSIGNMENT_DEPEND);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "failed to create dependent string!");
+ mStatus = mListener->OnDataAvailable(this, nullptr, stringStream, offset, count);
+
+ return true;
+}
+
+bool
+ExternalHelperAppParent::RecvOnStopRequest(const nsresult& code)
+{
+ MOZ_ASSERT(!mDiverted, "child forwarding callbacks after request was diverted");
+
+ mPending = false;
+ mListener->OnStopRequest(this, nullptr,
+ (NS_SUCCEEDED(code) && NS_FAILED(mStatus)) ? mStatus : code);
+ Delete();
+ return true;
+}
+
+bool
+ExternalHelperAppParent::RecvDivertToParentUsing(PChannelDiverterParent* diverter)
+{
+ MOZ_ASSERT(diverter);
+ auto p = static_cast<mozilla::net::ChannelDiverterParent*>(diverter);
+ p->DivertTo(this);
+#ifdef DEBUG
+ mDiverted = true;
+#endif
+ Unused << p->Send__delete__(p);
+ return true;
+}
+
+//
+// nsIStreamListener
+//
+
+NS_IMETHODIMP
+ExternalHelperAppParent::OnDataAvailable(nsIRequest *request,
+ nsISupports *ctx,
+ nsIInputStream *input,
+ uint64_t offset,
+ uint32_t count)
+{
+ MOZ_ASSERT(mDiverted);
+ return mListener->OnDataAvailable(request, ctx, input, offset, count);
+}
+
+NS_IMETHODIMP
+ExternalHelperAppParent::OnStartRequest(nsIRequest *request, nsISupports *ctx)
+{
+ MOZ_ASSERT(mDiverted);
+ return mListener->OnStartRequest(request, ctx);
+}
+
+NS_IMETHODIMP
+ExternalHelperAppParent::OnStopRequest(nsIRequest *request,
+ nsISupports *ctx,
+ nsresult status)
+{
+ MOZ_ASSERT(mDiverted);
+ nsresult rv = mListener->OnStopRequest(request, ctx, status);
+ Delete();
+ return rv;
+}
+
+ExternalHelperAppParent::~ExternalHelperAppParent()
+{
+}
+
+//
+// nsIRequest implementation...
+//
+
+NS_IMETHODIMP
+ExternalHelperAppParent::GetName(nsACString& aResult)
+{
+ if (!mURI) {
+ aResult.Truncate();
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ mURI->GetAsciiSpec(aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ExternalHelperAppParent::IsPending(bool *aResult)
+{
+ *aResult = mPending;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ExternalHelperAppParent::GetStatus(nsresult *aResult)
+{
+ *aResult = mStatus;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ExternalHelperAppParent::Cancel(nsresult aStatus)
+{
+ mStatus = aStatus;
+ Unused << SendCancel(aStatus);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ExternalHelperAppParent::Suspend()
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ExternalHelperAppParent::Resume()
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+//
+// nsIChannel implementation
+//
+
+NS_IMETHODIMP
+ExternalHelperAppParent::GetOriginalURI(nsIURI * *aURI)
+{
+ NS_IF_ADDREF(*aURI = mURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ExternalHelperAppParent::SetOriginalURI(nsIURI *aURI)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ExternalHelperAppParent::GetURI(nsIURI **aURI)
+{
+ NS_IF_ADDREF(*aURI = mURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ExternalHelperAppParent::Open(nsIInputStream **aResult)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ExternalHelperAppParent::Open2(nsIInputStream** aStream)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ExternalHelperAppParent::AsyncOpen(nsIStreamListener *aListener,
+ nsISupports *aContext)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ExternalHelperAppParent::AsyncOpen2(nsIStreamListener *aListener)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+
+NS_IMETHODIMP
+ExternalHelperAppParent::GetLoadFlags(nsLoadFlags *aLoadFlags)
+{
+ *aLoadFlags = mLoadFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ExternalHelperAppParent::SetLoadFlags(nsLoadFlags aLoadFlags)
+{
+ mLoadFlags = aLoadFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ExternalHelperAppParent::GetLoadGroup(nsILoadGroup* *aLoadGroup)
+{
+ *aLoadGroup = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ExternalHelperAppParent::SetLoadGroup(nsILoadGroup* aLoadGroup)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ExternalHelperAppParent::GetOwner(nsISupports* *aOwner)
+{
+ *aOwner = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ExternalHelperAppParent::SetOwner(nsISupports* aOwner)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ExternalHelperAppParent::GetLoadInfo(nsILoadInfo* *aLoadInfo)
+{
+ *aLoadInfo = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ExternalHelperAppParent::SetLoadInfo(nsILoadInfo* aLoadInfo)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ExternalHelperAppParent::GetNotificationCallbacks(nsIInterfaceRequestor* *aCallbacks)
+{
+ *aCallbacks = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ExternalHelperAppParent::SetNotificationCallbacks(nsIInterfaceRequestor* aCallbacks)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ExternalHelperAppParent::GetSecurityInfo(nsISupports * *aSecurityInfo)
+{
+ *aSecurityInfo = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ExternalHelperAppParent::GetContentType(nsACString& aContentType)
+{
+ aContentType.Truncate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ExternalHelperAppParent::SetContentType(const nsACString& aContentType)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ExternalHelperAppParent::GetContentCharset(nsACString& aContentCharset)
+{
+ aContentCharset.Truncate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ExternalHelperAppParent::SetContentCharset(const nsACString& aContentCharset)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ExternalHelperAppParent::GetContentDisposition(uint32_t *aContentDisposition)
+{
+ // NB: mContentDisposition may or may not be set to a non UINT32_MAX value in
+ // nsExternalHelperAppService::DoContentContentProcessHelper
+ if (mContentDispositionHeader.IsEmpty() && mContentDisposition == UINT32_MAX)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ *aContentDisposition = mContentDisposition;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ExternalHelperAppParent::SetContentDisposition(uint32_t aContentDisposition)
+{
+ mContentDisposition = aContentDisposition;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ExternalHelperAppParent::GetContentDispositionFilename(nsAString& aContentDispositionFilename)
+{
+ if (mContentDispositionFilename.IsEmpty())
+ return NS_ERROR_NOT_AVAILABLE;
+
+ aContentDispositionFilename = mContentDispositionFilename;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ExternalHelperAppParent::SetContentDispositionFilename(const nsAString& aContentDispositionFilename)
+{
+ mContentDispositionFilename = aContentDispositionFilename;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ExternalHelperAppParent::GetContentDispositionHeader(nsACString& aContentDispositionHeader)
+{
+ if (mContentDispositionHeader.IsEmpty())
+ return NS_ERROR_NOT_AVAILABLE;
+
+ aContentDispositionHeader = mContentDispositionHeader;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ExternalHelperAppParent::GetContentLength(int64_t *aContentLength)
+{
+ if (mContentLength < 0)
+ *aContentLength = -1;
+ else
+ *aContentLength = mContentLength;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ExternalHelperAppParent::SetContentLength(int64_t aContentLength)
+{
+ mContentLength = aContentLength;
+ return NS_OK;
+}
+
+//
+// nsIResumableChannel implementation
+//
+
+NS_IMETHODIMP
+ExternalHelperAppParent::ResumeAt(uint64_t startPos, const nsACString& entityID)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ExternalHelperAppParent::GetEntityID(nsACString& aEntityID)
+{
+ aEntityID = mEntityID;
+ return NS_OK;
+}
+
+//
+// nsIMultiPartChannel implementation
+//
+
+NS_IMETHODIMP
+ExternalHelperAppParent::GetBaseChannel(nsIChannel* *aChannel)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ExternalHelperAppParent::GetPartID(uint32_t* aPartID)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ExternalHelperAppParent::GetIsLastPart(bool* aIsLastPart)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/uriloader/exthandler/ExternalHelperAppParent.h b/uriloader/exthandler/ExternalHelperAppParent.h
new file mode 100644
index 0000000000..752da996d6
--- /dev/null
+++ b/uriloader/exthandler/ExternalHelperAppParent.h
@@ -0,0 +1,119 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/PExternalHelperAppParent.h"
+#include "nsIChannel.h"
+#include "nsIMultiPartChannel.h"
+#include "nsIResumableChannel.h"
+#include "nsIStreamListener.h"
+#include "nsHashPropertyBag.h"
+#include "PrivateBrowsingChannel.h"
+
+namespace IPC {
+class URI;
+} // namespace IPC
+
+namespace mozilla {
+
+namespace ipc {
+class OptionalURIParams;
+} // namespace ipc
+
+namespace net {
+class PChannelDiverterParent;
+} // namespace net
+
+namespace dom {
+
+#define NS_IEXTERNALHELPERAPPPARENT_IID \
+{ 0x127a01bc, 0x2a49, 0x46a8, \
+ { 0x8c, 0x63, 0x4b, 0x5d, 0x3c, 0xa4, 0x07, 0x9c } }
+
+class nsIExternalHelperAppParent : public nsISupports
+{
+public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_IEXTERNALHELPERAPPPARENT_IID)
+
+ /**
+ * Returns true if this fake channel represented a file channel in the child.
+ */
+ virtual bool WasFileChannel() = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIExternalHelperAppParent, NS_IEXTERNALHELPERAPPPARENT_IID)
+
+class ContentParent;
+class PBrowserParent;
+
+class ExternalHelperAppParent : public PExternalHelperAppParent
+ , public nsHashPropertyBag
+ , public nsIChannel
+ , public nsIMultiPartChannel
+ , public nsIResumableChannel
+ , public nsIStreamListener
+ , public net::PrivateBrowsingChannel<ExternalHelperAppParent>
+ , public nsIExternalHelperAppParent
+{
+ typedef mozilla::ipc::OptionalURIParams OptionalURIParams;
+
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIREQUEST
+ NS_DECL_NSICHANNEL
+ NS_DECL_NSIMULTIPARTCHANNEL
+ NS_DECL_NSIRESUMABLECHANNEL
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIREQUESTOBSERVER
+
+ bool RecvOnStartRequest(const nsCString& entityID) override;
+ bool RecvOnDataAvailable(const nsCString& data,
+ const uint64_t& offset,
+ const uint32_t& count) override;
+ bool RecvOnStopRequest(const nsresult& code) override;
+
+ bool RecvDivertToParentUsing(PChannelDiverterParent* diverter) override;
+
+ bool WasFileChannel() override {
+ return mWasFileChannel;
+ }
+
+ ExternalHelperAppParent(const OptionalURIParams& uri, const int64_t& contentLength,
+ const bool& wasFileChannel);
+ void Init(ContentParent *parent,
+ const nsCString& aMimeContentType,
+ const nsCString& aContentDisposition,
+ const uint32_t& aContentDispositionHint,
+ const nsString& aContentDispositionFilename,
+ const bool& aForceSave,
+ const OptionalURIParams& aReferrer,
+ PBrowserParent* aBrowser);
+
+protected:
+ virtual ~ExternalHelperAppParent();
+
+ virtual void ActorDestroy(ActorDestroyReason why) override;
+ void Delete();
+
+private:
+ nsCOMPtr<nsIStreamListener> mListener;
+ nsCOMPtr<nsIURI> mURI;
+ bool mPending;
+#ifdef DEBUG
+ bool mDiverted;
+#endif
+ bool mIPCClosed;
+ nsLoadFlags mLoadFlags;
+ nsresult mStatus;
+ int64_t mContentLength;
+ bool mWasFileChannel;
+ uint32_t mContentDisposition;
+ nsString mContentDispositionFilename;
+ nsCString mContentDispositionHeader;
+ nsCString mEntityID;
+};
+
+} // namespace dom
+} // namespace mozilla
diff --git a/uriloader/exthandler/HandlerServiceChild.h b/uriloader/exthandler/HandlerServiceChild.h
new file mode 100644
index 0000000000..6ef6833a84
--- /dev/null
+++ b/uriloader/exthandler/HandlerServiceChild.h
@@ -0,0 +1,15 @@
+#ifndef handler_service_child_h
+#define handler_service_child_h
+
+#include "mozilla/dom/PHandlerServiceChild.h"
+
+class HandlerServiceChild final : public mozilla::dom::PHandlerServiceChild
+{
+ public:
+ NS_INLINE_DECL_REFCOUNTING(HandlerServiceChild)
+ HandlerServiceChild() {}
+ private:
+ virtual ~HandlerServiceChild() {}
+};
+
+#endif
diff --git a/uriloader/exthandler/HandlerServiceParent.cpp b/uriloader/exthandler/HandlerServiceParent.cpp
new file mode 100644
index 0000000000..3f9de04a8d
--- /dev/null
+++ b/uriloader/exthandler/HandlerServiceParent.cpp
@@ -0,0 +1,270 @@
+#include "HandlerServiceParent.h"
+#include "nsIHandlerService.h"
+#include "nsIMIMEInfo.h"
+#include "ContentHandlerService.h"
+
+using mozilla::dom::HandlerInfo;
+using mozilla::dom::HandlerApp;
+using mozilla::dom::ContentHandlerService;
+using mozilla::dom::RemoteHandlerApp;
+
+namespace {
+
+class ProxyHandlerInfo final : public nsIHandlerInfo {
+public:
+ explicit ProxyHandlerInfo(const HandlerInfo& aHandlerInfo);
+ NS_DECL_ISUPPORTS;
+ NS_DECL_NSIHANDLERINFO;
+protected:
+ ~ProxyHandlerInfo() {}
+ HandlerInfo mHandlerInfo;
+ nsHandlerInfoAction mPrefAction;
+ nsCOMPtr<nsIMutableArray> mPossibleApps;
+};
+
+NS_IMPL_ISUPPORTS(ProxyHandlerInfo, nsIHandlerInfo)
+
+ProxyHandlerInfo::ProxyHandlerInfo(const HandlerInfo& aHandlerInfo) : mHandlerInfo(aHandlerInfo), mPossibleApps(do_CreateInstance(NS_ARRAY_CONTRACTID))
+{
+ for (auto& happ : aHandlerInfo.possibleApplicationHandlers()) {
+ mPossibleApps->AppendElement(new RemoteHandlerApp(happ), false);
+ }
+}
+
+/* readonly attribute ACString type; */
+NS_IMETHODIMP ProxyHandlerInfo::GetType(nsACString & aType)
+{
+ aType.Assign(mHandlerInfo.type());
+ return NS_OK;
+}
+
+/* attribute AString description; */
+NS_IMETHODIMP ProxyHandlerInfo::GetDescription(nsAString & aDescription)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+NS_IMETHODIMP ProxyHandlerInfo::SetDescription(const nsAString & aDescription)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* attribute nsIHandlerApp preferredApplicationHandler; */
+NS_IMETHODIMP ProxyHandlerInfo::GetPreferredApplicationHandler(nsIHandlerApp * *aPreferredApplicationHandler)
+{
+ *aPreferredApplicationHandler = new RemoteHandlerApp(mHandlerInfo.preferredApplicationHandler());
+ NS_IF_ADDREF(*aPreferredApplicationHandler);
+ return NS_OK;
+}
+
+NS_IMETHODIMP ProxyHandlerInfo::SetPreferredApplicationHandler(nsIHandlerApp *aApp)
+{
+ nsString name;
+ nsString detailedDescription;
+ if (aApp) {
+ aApp->GetName(name);
+ aApp->GetDetailedDescription(detailedDescription);
+ }
+ HandlerApp happ(name, detailedDescription);
+ mHandlerInfo = HandlerInfo(mHandlerInfo.type(),
+ mHandlerInfo.isMIMEInfo(),
+ mHandlerInfo.description(),
+ mHandlerInfo.alwaysAskBeforeHandling(),
+ happ,
+ mHandlerInfo.possibleApplicationHandlers(),
+ mHandlerInfo.preferredAction());
+ return NS_OK;
+}
+
+/* readonly attribute nsIMutableArray possibleApplicationHandlers; */
+NS_IMETHODIMP ProxyHandlerInfo::GetPossibleApplicationHandlers(nsIMutableArray * *aPossibleApplicationHandlers)
+{
+ *aPossibleApplicationHandlers = mPossibleApps;
+ NS_IF_ADDREF(*aPossibleApplicationHandlers);
+ return NS_OK;
+}
+
+/* readonly attribute boolean hasDefaultHandler; */
+NS_IMETHODIMP ProxyHandlerInfo::GetHasDefaultHandler(bool *aHasDefaultHandler)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* readonly attribute AString defaultDescription; */
+NS_IMETHODIMP ProxyHandlerInfo::GetDefaultDescription(nsAString & aDefaultDescription)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* void launchWithURI (in nsIURI aURI, [optional] in nsIInterfaceRequestor aWindowContext); */
+NS_IMETHODIMP ProxyHandlerInfo::LaunchWithURI(nsIURI *aURI, nsIInterfaceRequestor *aWindowContext)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* attribute ProxyHandlerInfoAction preferredAction; */
+NS_IMETHODIMP ProxyHandlerInfo::GetPreferredAction(nsHandlerInfoAction *aPreferredAction)
+{
+ *aPreferredAction = mPrefAction;
+ return NS_OK;
+}
+NS_IMETHODIMP ProxyHandlerInfo::SetPreferredAction(nsHandlerInfoAction aPreferredAction)
+{
+ mHandlerInfo = HandlerInfo(mHandlerInfo.type(),
+ mHandlerInfo.isMIMEInfo(),
+ mHandlerInfo.description(),
+ mHandlerInfo.alwaysAskBeforeHandling(),
+ mHandlerInfo.preferredApplicationHandler(),
+ mHandlerInfo.possibleApplicationHandlers(),
+ aPreferredAction);
+ mPrefAction = aPreferredAction;
+ return NS_OK;
+}
+
+/* attribute boolean alwaysAskBeforeHandling; */
+NS_IMETHODIMP ProxyHandlerInfo::GetAlwaysAskBeforeHandling(bool *aAlwaysAskBeforeHandling)
+{
+ *aAlwaysAskBeforeHandling = mHandlerInfo.alwaysAskBeforeHandling();
+ return NS_OK;
+}
+NS_IMETHODIMP ProxyHandlerInfo::SetAlwaysAskBeforeHandling(bool aAlwaysAskBeforeHandling)
+{
+ mHandlerInfo = HandlerInfo(mHandlerInfo.type(),
+ mHandlerInfo.isMIMEInfo(),
+ mHandlerInfo.description(),
+ aAlwaysAskBeforeHandling,
+ mHandlerInfo.preferredApplicationHandler(),
+ mHandlerInfo.possibleApplicationHandlers(),
+ mHandlerInfo.preferredAction());
+ return NS_OK;
+}
+
+
+class ProxyMIMEInfo : public nsIMIMEInfo
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMIMEINFO
+ NS_FORWARD_NSIHANDLERINFO(mProxyHandlerInfo->);
+
+ explicit ProxyMIMEInfo(HandlerInfo aHandlerInfo) : mProxyHandlerInfo(new ProxyHandlerInfo(aHandlerInfo)) {}
+
+private:
+ virtual ~ProxyMIMEInfo() {}
+ nsCOMPtr<nsIHandlerInfo> mProxyHandlerInfo;
+
+protected:
+ /* additional members */
+};
+
+NS_IMPL_ISUPPORTS(ProxyMIMEInfo, nsIMIMEInfo, nsIHandlerInfo)
+
+/* nsIUTF8StringEnumerator getFileExtensions (); */
+NS_IMETHODIMP ProxyMIMEInfo::GetFileExtensions(nsIUTF8StringEnumerator * *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* void setFileExtensions (in AUTF8String aExtensions); */
+NS_IMETHODIMP ProxyMIMEInfo::SetFileExtensions(const nsACString & aExtensions)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* boolean extensionExists (in AUTF8String aExtension); */
+NS_IMETHODIMP ProxyMIMEInfo::ExtensionExists(const nsACString & aExtension, bool *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* void appendExtension (in AUTF8String aExtension); */
+NS_IMETHODIMP ProxyMIMEInfo::AppendExtension(const nsACString & aExtension)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* attribute AUTF8String primaryExtension; */
+NS_IMETHODIMP ProxyMIMEInfo::GetPrimaryExtension(nsACString & aPrimaryExtension)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP ProxyMIMEInfo::SetPrimaryExtension(const nsACString & aPrimaryExtension)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* readonly attribute ACString MIMEType; */
+NS_IMETHODIMP ProxyMIMEInfo::GetMIMEType(nsACString & aMIMEType)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* boolean equals (in nsIMIMEInfo aMIMEInfo); */
+NS_IMETHODIMP ProxyMIMEInfo::Equals(nsIMIMEInfo *aMIMEInfo, bool *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* readonly attribute nsIArray possibleLocalHandlers; */
+NS_IMETHODIMP ProxyMIMEInfo::GetPossibleLocalHandlers(nsIArray * *aPossibleLocalHandlers)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* void launchWithFile (in nsIFile aFile); */
+NS_IMETHODIMP ProxyMIMEInfo::LaunchWithFile(nsIFile *aFile)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+static already_AddRefed<nsIHandlerInfo> WrapHandlerInfo(const HandlerInfo& aHandlerInfo) {
+ nsCOMPtr<nsIHandlerInfo> info;
+ if (aHandlerInfo.isMIMEInfo()) {
+ info = new ProxyMIMEInfo(aHandlerInfo);
+ } else {
+ info = new ProxyHandlerInfo(aHandlerInfo);
+ }
+ return info.forget();
+}
+
+} // anonymous namespace
+
+HandlerServiceParent::HandlerServiceParent()
+{
+}
+
+HandlerServiceParent::~HandlerServiceParent()
+{
+}
+
+bool HandlerServiceParent::RecvFillHandlerInfo(const HandlerInfo& aHandlerInfoData,
+ const nsCString& aOverrideType,
+ HandlerInfo* handlerInfoData)
+{
+ nsCOMPtr<nsIHandlerInfo> info(WrapHandlerInfo(aHandlerInfoData));
+ nsCOMPtr<nsIHandlerService> handlerSvc = do_GetService(NS_HANDLERSERVICE_CONTRACTID);
+ handlerSvc->FillHandlerInfo(info, aOverrideType);
+ ContentHandlerService::nsIHandlerInfoToHandlerInfo(info, handlerInfoData);
+ return true;
+}
+
+bool HandlerServiceParent::RecvExists(const HandlerInfo& aHandlerInfo,
+ bool* exists)
+{
+ nsCOMPtr<nsIHandlerInfo> info(WrapHandlerInfo(aHandlerInfo));
+ nsCOMPtr<nsIHandlerService> handlerSvc = do_GetService(NS_HANDLERSERVICE_CONTRACTID);
+ handlerSvc->Exists(info, exists);
+ return true;
+}
+
+bool HandlerServiceParent::RecvGetTypeFromExtension(const nsCString& aFileExtension,
+ nsCString* type)
+{
+ nsCOMPtr<nsIHandlerService> handlerSvc = do_GetService(NS_HANDLERSERVICE_CONTRACTID);
+ handlerSvc->GetTypeFromExtension(aFileExtension, *type);
+ return true;
+}
+
+void HandlerServiceParent::ActorDestroy(ActorDestroyReason aWhy)
+{
+}
diff --git a/uriloader/exthandler/HandlerServiceParent.h b/uriloader/exthandler/HandlerServiceParent.h
new file mode 100644
index 0000000000..37f5860187
--- /dev/null
+++ b/uriloader/exthandler/HandlerServiceParent.h
@@ -0,0 +1,31 @@
+#ifndef handler_service_parent_h
+#define handler_service_parent_h
+
+#include "mozilla/dom/PHandlerServiceParent.h"
+#include "nsIMIMEInfo.h"
+
+class nsIHandlerApp;
+
+class HandlerServiceParent final : public mozilla::dom::PHandlerServiceParent
+{
+ public:
+ HandlerServiceParent();
+ NS_INLINE_DECL_REFCOUNTING(HandlerServiceParent)
+
+ private:
+ virtual ~HandlerServiceParent();
+ virtual void ActorDestroy(ActorDestroyReason aWhy) override;
+
+
+ virtual bool RecvFillHandlerInfo(const HandlerInfo& aHandlerInfoData,
+ const nsCString& aOverrideType,
+ HandlerInfo* handlerInfoData) override;
+ virtual bool RecvExists(const HandlerInfo& aHandlerInfo,
+ bool* exits) override;
+
+ virtual bool RecvGetTypeFromExtension(const nsCString& aFileExtension,
+ nsCString* type) override;
+
+};
+
+#endif
diff --git a/uriloader/exthandler/PExternalHelperApp.ipdl b/uriloader/exthandler/PExternalHelperApp.ipdl
new file mode 100644
index 0000000000..c7d526c19d
--- /dev/null
+++ b/uriloader/exthandler/PExternalHelperApp.ipdl
@@ -0,0 +1,29 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PContent;
+include protocol PChannelDiverter;
+
+namespace mozilla {
+namespace dom {
+
+protocol PExternalHelperApp
+{
+ manager PContent;
+
+parent:
+ async OnStartRequest(nsCString entityID);
+ async OnDataAvailable(nsCString data, uint64_t offset, uint32_t count);
+ async OnStopRequest(nsresult code);
+
+ async DivertToParentUsing(PChannelDiverter diverter);
+
+child:
+ async Cancel(nsresult aStatus);
+ async __delete__();
+};
+
+
+} // namespace dom
+} // namespace mozilla
diff --git a/uriloader/exthandler/PHandlerService.ipdl b/uriloader/exthandler/PHandlerService.ipdl
new file mode 100644
index 0000000000..bcaab60ac3
--- /dev/null
+++ b/uriloader/exthandler/PHandlerService.ipdl
@@ -0,0 +1,42 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PContent;
+
+namespace mozilla {
+namespace dom {
+
+struct HandlerApp {
+ nsString name;
+ nsString detailedDescription;
+};
+
+struct HandlerInfo {
+ nsCString type;
+ bool isMIMEInfo;
+ nsString description;
+ bool alwaysAskBeforeHandling;
+ HandlerApp preferredApplicationHandler;
+ HandlerApp[] possibleApplicationHandlers;
+ long preferredAction;
+};
+
+sync protocol PHandlerService
+{
+ manager PContent;
+
+parent:
+ sync FillHandlerInfo(HandlerInfo aHandlerInfoData,
+ nsCString aOverrideType)
+ returns (HandlerInfo handlerInfoData);
+ sync Exists(HandlerInfo aHandlerInfo)
+ returns (bool exists);
+ sync GetTypeFromExtension(nsCString aFileExtension)
+ returns (nsCString type);
+ async __delete__();
+};
+
+
+} // namespace dom
+} // namespace mozilla
diff --git a/uriloader/exthandler/android/nsAndroidHandlerApp.cpp b/uriloader/exthandler/android/nsAndroidHandlerApp.cpp
new file mode 100644
index 0000000000..4c7ffff484
--- /dev/null
+++ b/uriloader/exthandler/android/nsAndroidHandlerApp.cpp
@@ -0,0 +1,91 @@
+/* -*- Mode: c++; c-basic-offset: 2; tab-width: 20; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAndroidHandlerApp.h"
+#include "GeneratedJNIWrappers.h"
+
+using namespace mozilla;
+
+
+NS_IMPL_ISUPPORTS(nsAndroidHandlerApp, nsIHandlerApp, nsISharingHandlerApp)
+
+nsAndroidHandlerApp::nsAndroidHandlerApp(const nsAString& aName,
+ const nsAString& aDescription,
+ const nsAString& aPackageName,
+ const nsAString& aClassName,
+ const nsACString& aMimeType,
+ const nsAString& aAction) :
+mName(aName), mDescription(aDescription), mPackageName(aPackageName),
+ mClassName(aClassName), mMimeType(aMimeType), mAction(aAction)
+{
+}
+
+nsAndroidHandlerApp::~nsAndroidHandlerApp()
+{
+}
+
+NS_IMETHODIMP
+nsAndroidHandlerApp::GetName(nsAString & aName)
+{
+ aName.Assign(mName);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAndroidHandlerApp::SetName(const nsAString & aName)
+{
+ mName.Assign(aName);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAndroidHandlerApp::GetDetailedDescription(nsAString & aDescription)
+{
+ aDescription.Assign(mDescription);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAndroidHandlerApp::SetDetailedDescription(const nsAString & aDescription)
+{
+ mDescription.Assign(aDescription);
+
+ return NS_OK;
+}
+
+// XXX Workaround for bug 986975 to maintain the existing broken semantics
+template<>
+struct nsISharingHandlerApp::COMTypeInfo<nsAndroidHandlerApp, void> {
+ static const nsIID kIID;
+};
+const nsIID nsISharingHandlerApp::COMTypeInfo<nsAndroidHandlerApp, void>::kIID = NS_IHANDLERAPP_IID;
+
+NS_IMETHODIMP
+nsAndroidHandlerApp::Equals(nsIHandlerApp *aHandlerApp, bool *aRetval)
+{
+ nsCOMPtr<nsAndroidHandlerApp> aApp = do_QueryInterface(aHandlerApp);
+ *aRetval = aApp && aApp->mName.Equals(mName) &&
+ aApp->mDescription.Equals(mDescription);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAndroidHandlerApp::LaunchWithURI(nsIURI *aURI, nsIInterfaceRequestor *aWindowContext)
+{
+ nsCString uriSpec;
+ aURI->GetSpec(uriSpec);
+ return java::GeckoAppShell::OpenUriExternal(
+ uriSpec, mMimeType, mPackageName, mClassName,
+ mAction, EmptyString()) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsAndroidHandlerApp::Share(const nsAString & data, const nsAString & title)
+{
+ return java::GeckoAppShell::OpenUriExternal(
+ data, mMimeType, mPackageName, mClassName,
+ mAction, EmptyString()) ? NS_OK : NS_ERROR_FAILURE;
+}
+
diff --git a/uriloader/exthandler/android/nsAndroidHandlerApp.h b/uriloader/exthandler/android/nsAndroidHandlerApp.h
new file mode 100644
index 0000000000..bb38724638
--- /dev/null
+++ b/uriloader/exthandler/android/nsAndroidHandlerApp.h
@@ -0,0 +1,33 @@
+/* -*- Mode: c++; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAndroidHandlerApp_h
+#define nsAndroidHandlerApp_h
+
+#include "nsMIMEInfoImpl.h"
+#include "nsIExternalSharingAppService.h"
+
+class nsAndroidHandlerApp : public nsISharingHandlerApp {
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIHANDLERAPP
+ NS_DECL_NSISHARINGHANDLERAPP
+
+ nsAndroidHandlerApp(const nsAString& aName, const nsAString& aDescription,
+ const nsAString& aPackageName,
+ const nsAString& aClassName,
+ const nsACString& aMimeType, const nsAString& aAction);
+
+private:
+ virtual ~nsAndroidHandlerApp();
+
+ nsString mName;
+ nsString mDescription;
+ nsString mPackageName;
+ nsString mClassName;
+ nsCString mMimeType;
+ nsString mAction;
+};
+#endif
diff --git a/uriloader/exthandler/android/nsExternalSharingAppService.cpp b/uriloader/exthandler/android/nsExternalSharingAppService.cpp
new file mode 100644
index 0000000000..f4f8a7013c
--- /dev/null
+++ b/uriloader/exthandler/android/nsExternalSharingAppService.cpp
@@ -0,0 +1,61 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsExternalSharingAppService.h"
+
+#include "mozilla/ModuleUtils.h"
+#include "nsIClassInfoImpl.h"
+
+#include "AndroidBridge.h"
+#include "nsArrayUtils.h"
+#include "nsISupportsUtils.h"
+#include "nsComponentManagerUtils.h"
+
+using namespace mozilla;
+
+NS_IMPL_ISUPPORTS(nsExternalSharingAppService, nsIExternalSharingAppService)
+
+nsExternalSharingAppService::nsExternalSharingAppService()
+{
+}
+
+nsExternalSharingAppService::~nsExternalSharingAppService()
+{
+}
+
+NS_IMETHODIMP
+nsExternalSharingAppService::ShareWithDefault(const nsAString & data,
+ const nsAString & mime,
+ const nsAString & title)
+{
+ NS_NAMED_LITERAL_STRING(sendAction, "android.intent.action.SEND");
+ const nsString emptyString = EmptyString();
+ return java::GeckoAppShell::OpenUriExternal(data,
+ mime, emptyString, emptyString, sendAction, title) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsExternalSharingAppService::GetSharingApps(const nsAString & aMIMEType,
+ uint32_t *aLen,
+ nsISharingHandlerApp ***aHandlers)
+{
+ nsresult rv;
+ NS_NAMED_LITERAL_STRING(sendAction, "android.intent.action.SEND");
+ nsCOMPtr<nsIMutableArray> array = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!AndroidBridge::Bridge())
+ return NS_OK;
+ AndroidBridge::Bridge()->GetHandlersForMimeType(aMIMEType, array,
+ nullptr, sendAction);
+ array->GetLength(aLen);
+ *aHandlers =
+ static_cast<nsISharingHandlerApp**>(moz_xmalloc(sizeof(nsISharingHandlerApp*)
+ * *aLen));
+ for (uint32_t i = 0; i < *aLen; i++) {
+ rv = array->QueryElementAt(i, NS_GET_IID(nsISharingHandlerApp),
+ (void**)(*aHandlers + i));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return NS_OK;
+}
diff --git a/uriloader/exthandler/android/nsExternalSharingAppService.h b/uriloader/exthandler/android/nsExternalSharingAppService.h
new file mode 100644
index 0000000000..a1e2e43637
--- /dev/null
+++ b/uriloader/exthandler/android/nsExternalSharingAppService.h
@@ -0,0 +1,28 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NS_EXTERNAL_SHARING_APP_SERVICE_H
+#define NS_EXTERNAL_SHARING_APP_SERVICE_H
+#include "nsIExternalSharingAppService.h"
+
+
+#define NS_EXTERNALSHARINGAPPSERVICE_CID \
+ {0x93e2c46e, 0x0011, 0x434b, \
+ {0x81, 0x2e, 0xb6, 0xf3, 0xa8, 0x1e, 0x2a, 0x58}}
+
+class nsExternalSharingAppService final
+ : public nsIExternalSharingAppService
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIEXTERNALSHARINGAPPSERVICE
+
+ nsExternalSharingAppService();
+
+private:
+ ~nsExternalSharingAppService();
+
+};
+
+#endif /*NS_EXTERNAL_SHARING_APP_SERVICE_H */
diff --git a/uriloader/exthandler/android/nsExternalURLHandlerService.cpp b/uriloader/exthandler/android/nsExternalURLHandlerService.cpp
new file mode 100644
index 0000000000..f417b5b9f9
--- /dev/null
+++ b/uriloader/exthandler/android/nsExternalURLHandlerService.cpp
@@ -0,0 +1,27 @@
+/* -*- Mode: c++; c-basic-offset: 2; tab-width: 20; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsExternalURLHandlerService.h"
+#include "nsMIMEInfoAndroid.h"
+
+NS_IMPL_ISUPPORTS(nsExternalURLHandlerService, nsIExternalURLHandlerService)
+
+nsExternalURLHandlerService::nsExternalURLHandlerService()
+{
+}
+
+nsExternalURLHandlerService::~nsExternalURLHandlerService()
+{
+}
+
+NS_IMETHODIMP
+nsExternalURLHandlerService::GetURLHandlerInfoFromOS(nsIURI *aURL,
+ bool *found,
+ nsIHandlerInfo **info)
+{
+ nsCString uriSpec;
+ aURL->GetSpec(uriSpec);
+ return nsMIMEInfoAndroid::GetMimeInfoForURL(uriSpec, found, info);
+}
diff --git a/uriloader/exthandler/android/nsExternalURLHandlerService.h b/uriloader/exthandler/android/nsExternalURLHandlerService.h
new file mode 100644
index 0000000000..f2618c7e6f
--- /dev/null
+++ b/uriloader/exthandler/android/nsExternalURLHandlerService.h
@@ -0,0 +1,27 @@
+/* -*- Mode: c++; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NSEXTERNALURLHANDLERSERVICE_H
+#define NSEXTERNALURLHANDLERSERVICE_H
+
+#include "nsIExternalURLHandlerService.h"
+
+// {4BF1F8EF-D947-4BA3-9CD3-8C9A54A63A1C}
+#define NS_EXTERNALURLHANDLERSERVICE_CID \
+ {0x4bf1f8ef, 0xd947, 0x4ba3, {0x9c, 0xd3, 0x8c, 0x9a, 0x54, 0xa6, 0x3a, 0x1c}}
+
+class nsExternalURLHandlerService final
+ : public nsIExternalURLHandlerService
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIEXTERNALURLHANDLERSERVICE
+
+ nsExternalURLHandlerService();
+private:
+ ~nsExternalURLHandlerService();
+};
+
+#endif // NSEXTERNALURLHANDLERSERVICE_H
diff --git a/uriloader/exthandler/android/nsMIMEInfoAndroid.cpp b/uriloader/exthandler/android/nsMIMEInfoAndroid.cpp
new file mode 100644
index 0000000000..ee9bc8570d
--- /dev/null
+++ b/uriloader/exthandler/android/nsMIMEInfoAndroid.cpp
@@ -0,0 +1,427 @@
+/* -*- Mode: c++; c-basic-offset: 2; tab-width: 20; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "nsMIMEInfoAndroid.h"
+#include "AndroidBridge.h"
+#include "nsAndroidHandlerApp.h"
+#include "nsArrayUtils.h"
+#include "nsISupportsUtils.h"
+#include "nsStringEnumerator.h"
+#include "nsNetUtil.h"
+
+using namespace mozilla;
+
+NS_IMPL_ISUPPORTS(nsMIMEInfoAndroid, nsIMIMEInfo, nsIHandlerInfo)
+
+NS_IMETHODIMP
+nsMIMEInfoAndroid::LaunchDefaultWithFile(nsIFile* aFile)
+{
+ return LaunchWithFile(aFile);
+}
+
+NS_IMETHODIMP
+nsMIMEInfoAndroid::LoadUriInternal(nsIURI * aURI)
+{
+ nsCString uriSpec;
+ aURI->GetSpec(uriSpec);
+
+ nsCString uriScheme;
+ aURI->GetScheme(uriScheme);
+
+ nsAutoString mimeType;
+ if (mType.Equals(uriScheme) || mType.Equals(uriSpec)) {
+ mimeType = EmptyString();
+ } else {
+ mimeType = NS_ConvertUTF8toUTF16(mType);
+ }
+
+ if (java::GeckoAppShell::OpenUriExternal(
+ NS_ConvertUTF8toUTF16(uriSpec), mimeType, EmptyString(),
+ EmptyString(), EmptyString(), EmptyString())) {
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+bool
+nsMIMEInfoAndroid::GetMimeInfoForMimeType(const nsACString& aMimeType,
+ nsMIMEInfoAndroid** aMimeInfo)
+{
+ RefPtr<nsMIMEInfoAndroid> info = new nsMIMEInfoAndroid(aMimeType);
+ mozilla::AndroidBridge* bridge = mozilla::AndroidBridge::Bridge();
+ // we don't have access to the bridge, so just assume we can handle
+ // the mime type for now and let the system deal with it
+ if (!bridge){
+ info.forget(aMimeInfo);
+ return false;
+ }
+
+ nsIHandlerApp* systemDefault = nullptr;
+
+ if (!IsUTF8(aMimeType, true))
+ return false;
+
+ NS_ConvertUTF8toUTF16 mimeType(aMimeType);
+
+ bridge->GetHandlersForMimeType(mimeType,
+ info->mHandlerApps, &systemDefault);
+
+ if (systemDefault)
+ info->mPrefApp = systemDefault;
+
+ nsAutoCString fileExt;
+ bridge->GetExtensionFromMimeType(aMimeType, fileExt);
+ info->SetPrimaryExtension(fileExt);
+
+ uint32_t len;
+ info->mHandlerApps->GetLength(&len);
+ if (len == 1) {
+ info.forget(aMimeInfo);
+ return false;
+ }
+
+ info.forget(aMimeInfo);
+ return true;
+}
+
+bool
+nsMIMEInfoAndroid::GetMimeInfoForFileExt(const nsACString& aFileExt,
+ nsMIMEInfoAndroid **aMimeInfo)
+{
+ nsCString mimeType;
+ if (mozilla::AndroidBridge::Bridge())
+ mozilla::AndroidBridge::Bridge()->
+ GetMimeTypeFromExtensions(aFileExt, mimeType);
+
+ // "*/*" means that the bridge didn't know.
+ if (mimeType.Equals(nsDependentCString("*/*"), nsCaseInsensitiveCStringComparator()))
+ return false;
+
+ bool found = GetMimeInfoForMimeType(mimeType, aMimeInfo);
+ (*aMimeInfo)->SetPrimaryExtension(aFileExt);
+ return found;
+}
+
+/**
+ * Returns MIME info for the aURL, which may contain the whole URL or only a protocol
+ */
+nsresult
+nsMIMEInfoAndroid::GetMimeInfoForURL(const nsACString &aURL,
+ bool *found,
+ nsIHandlerInfo **info)
+{
+ nsMIMEInfoAndroid *mimeinfo = new nsMIMEInfoAndroid(aURL);
+ NS_ADDREF(*info = mimeinfo);
+ *found = true;
+
+ mozilla::AndroidBridge* bridge = mozilla::AndroidBridge::Bridge();
+ if (!bridge) {
+ // we don't have access to the bridge, so just assume we can handle
+ // the protocol for now and let the system deal with it
+ return NS_OK;
+ }
+
+ nsIHandlerApp* systemDefault = nullptr;
+ bridge->GetHandlersForURL(NS_ConvertUTF8toUTF16(aURL),
+ mimeinfo->mHandlerApps, &systemDefault);
+
+ if (systemDefault)
+ mimeinfo->mPrefApp = systemDefault;
+
+
+ nsAutoCString fileExt;
+ nsAutoCString mimeType;
+ mimeinfo->GetType(mimeType);
+ bridge->GetExtensionFromMimeType(mimeType, fileExt);
+ mimeinfo->SetPrimaryExtension(fileExt);
+
+ uint32_t len;
+ mimeinfo->mHandlerApps->GetLength(&len);
+ if (len == 1) {
+ // Code that calls this requires an object regardless if the OS has
+ // something for us, so we return the empty object.
+ *found = false;
+ return NS_OK;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoAndroid::GetType(nsACString& aType)
+{
+ aType.Assign(mType);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoAndroid::GetDescription(nsAString& aDesc)
+{
+ aDesc.Assign(mDescription);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoAndroid::SetDescription(const nsAString& aDesc)
+{
+ mDescription.Assign(aDesc);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoAndroid::GetPreferredApplicationHandler(nsIHandlerApp** aApp)
+{
+ *aApp = mPrefApp;
+ NS_IF_ADDREF(*aApp);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoAndroid::SetPreferredApplicationHandler(nsIHandlerApp* aApp)
+{
+ mPrefApp = aApp;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoAndroid::GetPossibleApplicationHandlers(nsIMutableArray **aHandlerApps)
+{
+ if (!mHandlerApps)
+ mHandlerApps = do_CreateInstance(NS_ARRAY_CONTRACTID);
+
+ if (!mHandlerApps)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ *aHandlerApps = mHandlerApps;
+ NS_IF_ADDREF(*aHandlerApps);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoAndroid::GetHasDefaultHandler(bool* aHasDefault)
+{
+ uint32_t len;
+ *aHasDefault = false;
+ if (!mHandlerApps)
+ return NS_OK;
+
+ if (NS_FAILED(mHandlerApps->GetLength(&len)))
+ return NS_OK;
+
+ if (len == 0)
+ return NS_OK;
+
+ *aHasDefault = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoAndroid::GetDefaultDescription(nsAString& aDesc)
+{
+ aDesc.Assign(EmptyString());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoAndroid::LaunchWithURI(nsIURI* aURI, nsIInterfaceRequestor* req)
+{
+ return mPrefApp->LaunchWithURI(aURI, req);
+}
+
+NS_IMETHODIMP
+nsMIMEInfoAndroid::GetPreferredAction(nsHandlerInfoAction* aPrefAction)
+{
+ *aPrefAction = mPrefAction;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoAndroid::SetPreferredAction(nsHandlerInfoAction aPrefAction)
+{
+ mPrefAction = aPrefAction;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoAndroid::GetAlwaysAskBeforeHandling(bool* aAlwaysAsk)
+{
+ *aAlwaysAsk = mAlwaysAsk;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoAndroid::SetAlwaysAskBeforeHandling(bool aAlwaysAsk)
+{
+ mAlwaysAsk = aAlwaysAsk;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoAndroid::GetFileExtensions(nsIUTF8StringEnumerator** aResult)
+{
+ return NS_NewUTF8StringEnumerator(aResult, &mExtensions, this);
+}
+
+NS_IMETHODIMP
+nsMIMEInfoAndroid::SetFileExtensions(const nsACString & aExtensions)
+{
+ mExtensions.Clear();
+ nsCString extList(aExtensions);
+
+ int32_t breakLocation = -1;
+ while ( (breakLocation = extList.FindChar(',')) != -1)
+ {
+ mExtensions.AppendElement(Substring(extList.get(), extList.get() + breakLocation));
+ extList.Cut(0, breakLocation + 1);
+ }
+ if (!extList.IsEmpty())
+ mExtensions.AppendElement(extList);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoAndroid::ExtensionExists(const nsACString & aExtension, bool *aRetVal)
+{
+ NS_ASSERTION(!aExtension.IsEmpty(), "no extension");
+
+ nsCString mimeType;
+ if (mozilla::AndroidBridge::Bridge()) {
+ mozilla::AndroidBridge::Bridge()->
+ GetMimeTypeFromExtensions(aExtension, mimeType);
+ }
+
+ // "*/*" means the bridge didn't find anything (i.e., extension doesn't exist).
+ *aRetVal = !mimeType.Equals(nsDependentCString("*/*"), nsCaseInsensitiveCStringComparator());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoAndroid::AppendExtension(const nsACString & aExtension)
+{
+ mExtensions.AppendElement(aExtension);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoAndroid::GetPrimaryExtension(nsACString & aPrimaryExtension)
+{
+ if (!mExtensions.Length())
+ return NS_ERROR_NOT_INITIALIZED;
+
+ aPrimaryExtension = mExtensions[0];
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoAndroid::SetPrimaryExtension(const nsACString & aExtension)
+{
+ uint32_t extCount = mExtensions.Length();
+ uint8_t i;
+ bool found = false;
+ for (i=0; i < extCount; i++) {
+ const nsCString& ext = mExtensions[i];
+ if (ext.Equals(aExtension, nsCaseInsensitiveCStringComparator())) {
+ found = true;
+ break;
+ }
+ }
+ if (found) {
+ mExtensions.RemoveElementAt(i);
+ }
+
+ mExtensions.InsertElementAt(0, aExtension);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoAndroid::GetMIMEType(nsACString & aMIMEType)
+{
+ aMIMEType.Assign(mType);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoAndroid::Equals(nsIMIMEInfo *aMIMEInfo, bool *aRetVal)
+{
+ if (!aMIMEInfo) return NS_ERROR_NULL_POINTER;
+
+ nsAutoCString type;
+ nsresult rv = aMIMEInfo->GetMIMEType(type);
+ if (NS_FAILED(rv)) return rv;
+
+ *aRetVal = mType.Equals(type);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoAndroid::GetPossibleLocalHandlers(nsIArray * *aPossibleLocalHandlers)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoAndroid::LaunchWithFile(nsIFile *aFile)
+{
+ nsCOMPtr<nsIURI> uri;
+ NS_NewFileURI(getter_AddRefs(uri), aFile);
+ return LoadUriInternal(uri);
+}
+
+nsMIMEInfoAndroid::nsMIMEInfoAndroid(const nsACString& aMIMEType) :
+ mType(aMIMEType), mAlwaysAsk(true),
+ mPrefAction(nsIMIMEInfo::useHelperApp)
+{
+ mPrefApp = new nsMIMEInfoAndroid::SystemChooser(this);
+ nsresult rv;
+ mHandlerApps = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ mHandlerApps->AppendElement(mPrefApp, false);
+}
+
+NS_IMPL_ISUPPORTS(nsMIMEInfoAndroid::SystemChooser, nsIHandlerApp)
+
+
+nsresult nsMIMEInfoAndroid::SystemChooser::GetName(nsAString & aName) {
+ aName.AssignLiteral(u"Android chooser");
+ return NS_OK;
+}
+
+nsresult
+nsMIMEInfoAndroid::SystemChooser::SetName(const nsAString&) {
+ return NS_OK;
+}
+
+nsresult
+nsMIMEInfoAndroid::SystemChooser::GetDetailedDescription(nsAString & aDesc) {
+ aDesc.AssignLiteral(u"Android's default handler application chooser");
+ return NS_OK;
+}
+
+nsresult
+nsMIMEInfoAndroid::SystemChooser::SetDetailedDescription(const nsAString&) {
+ return NS_OK;
+}
+
+// XXX Workaround for bug 986975 to maintain the existing broken semantics
+template<>
+struct nsIHandlerApp::COMTypeInfo<nsMIMEInfoAndroid::SystemChooser, void> {
+ static const nsIID kIID;
+};
+const nsIID nsIHandlerApp::COMTypeInfo<nsMIMEInfoAndroid::SystemChooser, void>::kIID = NS_IHANDLERAPP_IID;
+
+nsresult
+nsMIMEInfoAndroid::SystemChooser::Equals(nsIHandlerApp *aHandlerApp, bool *aRetVal) {
+ nsCOMPtr<nsMIMEInfoAndroid::SystemChooser> info = do_QueryInterface(aHandlerApp);
+ if (info)
+ return mOuter->Equals(info->mOuter, aRetVal);
+ *aRetVal = false;
+ return NS_OK;
+}
+
+nsresult
+nsMIMEInfoAndroid::SystemChooser::LaunchWithURI(nsIURI* aURI, nsIInterfaceRequestor*)
+{
+ return mOuter->LoadUriInternal(aURI);
+}
diff --git a/uriloader/exthandler/android/nsMIMEInfoAndroid.h b/uriloader/exthandler/android/nsMIMEInfoAndroid.h
new file mode 100644
index 0000000000..569d715bdf
--- /dev/null
+++ b/uriloader/exthandler/android/nsMIMEInfoAndroid.h
@@ -0,0 +1,60 @@
+/* -*- Mode: c++; c-basic-offset: 2; tab-width: 20; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMIMEInfoAndroid_h
+#define nsMIMEInfoAndroid_h
+
+#include "nsMIMEInfoImpl.h"
+#include "nsIMutableArray.h"
+#include "nsAndroidHandlerApp.h"
+
+class nsMIMEInfoAndroid final : public nsIMIMEInfo
+{
+public:
+ static MOZ_MUST_USE bool
+ GetMimeInfoForMimeType(const nsACString& aMimeType,
+ nsMIMEInfoAndroid** aMimeInfo);
+ static MOZ_MUST_USE bool
+ GetMimeInfoForFileExt(const nsACString& aFileExt,
+ nsMIMEInfoAndroid** aMimeInfo);
+
+ static MOZ_MUST_USE nsresult
+ GetMimeInfoForURL(const nsACString &aURL, bool *found,
+ nsIHandlerInfo **info);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMIMEINFO
+ NS_DECL_NSIHANDLERINFO
+
+ nsMIMEInfoAndroid(const nsACString& aMIMEType);
+
+private:
+ ~nsMIMEInfoAndroid() {}
+
+ virtual MOZ_MUST_USE nsresult LaunchDefaultWithFile(nsIFile* aFile);
+ virtual MOZ_MUST_USE nsresult LoadUriInternal(nsIURI *aURI);
+ nsCOMPtr<nsIMutableArray> mHandlerApps;
+ nsCString mType;
+ nsTArray<nsCString> mExtensions;
+ bool mAlwaysAsk;
+ nsHandlerInfoAction mPrefAction;
+ nsString mDescription;
+ nsCOMPtr<nsIHandlerApp> mPrefApp;
+
+public:
+ class SystemChooser final : public nsIHandlerApp {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIHANDLERAPP
+ SystemChooser(nsMIMEInfoAndroid* aOuter): mOuter(aOuter) {}
+
+ private:
+ ~SystemChooser() {}
+
+ nsMIMEInfoAndroid* mOuter;
+ };
+};
+
+#endif /* nsMIMEInfoAndroid_h */
diff --git a/uriloader/exthandler/android/nsOSHelperAppService.cpp b/uriloader/exthandler/android/nsOSHelperAppService.cpp
new file mode 100644
index 0000000000..3a170dcf6d
--- /dev/null
+++ b/uriloader/exthandler/android/nsOSHelperAppService.cpp
@@ -0,0 +1,67 @@
+/* -*- Mode: c++; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsOSHelperAppService.h"
+#include "nsMIMEInfoAndroid.h"
+#include "AndroidBridge.h"
+
+nsOSHelperAppService::nsOSHelperAppService() : nsExternalHelperAppService()
+{
+}
+
+nsOSHelperAppService::~nsOSHelperAppService()
+{
+}
+
+already_AddRefed<nsIMIMEInfo>
+nsOSHelperAppService::GetMIMEInfoFromOS(const nsACString& aMIMEType,
+ const nsACString& aFileExt,
+ bool* aFound)
+{
+ RefPtr<nsMIMEInfoAndroid> mimeInfo;
+ *aFound = false;
+ if (!aMIMEType.IsEmpty())
+ *aFound =
+ nsMIMEInfoAndroid::GetMimeInfoForMimeType(aMIMEType,
+ getter_AddRefs(mimeInfo));
+ if (!*aFound)
+ *aFound =
+ nsMIMEInfoAndroid::GetMimeInfoForFileExt(aFileExt,
+ getter_AddRefs(mimeInfo));
+
+ // Code that calls this requires an object regardless if the OS has
+ // something for us, so we return the empty object.
+ if (!*aFound)
+ mimeInfo = new nsMIMEInfoAndroid(aMIMEType);
+
+ return mimeInfo.forget();
+}
+
+nsresult
+nsOSHelperAppService::OSProtocolHandlerExists(const char* aScheme,
+ bool* aExists)
+{
+ *aExists = mozilla::AndroidBridge::Bridge()->GetHandlersForURL(NS_ConvertUTF8toUTF16(aScheme));
+ return NS_OK;
+}
+
+nsresult nsOSHelperAppService::GetProtocolHandlerInfoFromOS(const nsACString &aScheme,
+ bool *found,
+ nsIHandlerInfo **info)
+{
+ return nsMIMEInfoAndroid::GetMimeInfoForURL(aScheme, found, info);
+}
+
+nsIHandlerApp*
+nsOSHelperAppService::CreateAndroidHandlerApp(const nsAString& aName,
+ const nsAString& aDescription,
+ const nsAString& aPackageName,
+ const nsAString& aClassName,
+ const nsACString& aMimeType,
+ const nsAString& aAction)
+{
+ return new nsAndroidHandlerApp(aName, aDescription, aPackageName,
+ aClassName, aMimeType, aAction);
+}
diff --git a/uriloader/exthandler/android/nsOSHelperAppService.h b/uriloader/exthandler/android/nsOSHelperAppService.h
new file mode 100644
index 0000000000..4f3623894f
--- /dev/null
+++ b/uriloader/exthandler/android/nsOSHelperAppService.h
@@ -0,0 +1,40 @@
+/* -*- Mode: c++; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsOSHelperAppService_h
+#define nsOSHelperAppService_h
+
+#include "nsCExternalHandlerService.h"
+#include "nsExternalHelperAppService.h"
+
+class nsOSHelperAppService : public nsExternalHelperAppService
+{
+public:
+ nsOSHelperAppService();
+ virtual ~nsOSHelperAppService();
+
+ virtual already_AddRefed<nsIMIMEInfo>
+ GetMIMEInfoFromOS(const nsACString& aMIMEType,
+ const nsACString& aFileExt,
+ bool* aFound);
+
+ virtual MOZ_MUST_USE nsresult
+ OSProtocolHandlerExists(const char* aScheme,
+ bool* aExists);
+
+ NS_IMETHOD GetProtocolHandlerInfoFromOS(const nsACString &aScheme,
+ bool *found,
+ nsIHandlerInfo **_retval);
+
+ static nsIHandlerApp*
+ CreateAndroidHandlerApp(const nsAString& aName,
+ const nsAString& aDescription,
+ const nsAString& aPackageName,
+ const nsAString& aClassName,
+ const nsACString& aMimeType,
+ const nsAString& aAction = EmptyString());
+};
+
+#endif /* nsOSHelperAppService_h */
diff --git a/uriloader/exthandler/gonk/nsOSHelperAppService.cpp b/uriloader/exthandler/gonk/nsOSHelperAppService.cpp
new file mode 100644
index 0000000000..d1342ec181
--- /dev/null
+++ b/uriloader/exthandler/gonk/nsOSHelperAppService.cpp
@@ -0,0 +1,56 @@
+/* -*- Mode: c++; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- */
+/* Copyright 2012 Mozilla Foundation and Mozilla contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "nsOSHelperAppService.h"
+#include "nsMIMEInfoImpl.h"
+
+class nsGonkMIMEInfo : public nsMIMEInfoImpl {
+public:
+ nsGonkMIMEInfo(const nsACString& aMIMEType) : nsMIMEInfoImpl(aMIMEType) { }
+
+protected:
+ virtual nsresult LoadUriInternal(nsIURI *aURI) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+};
+
+nsOSHelperAppService::nsOSHelperAppService() : nsExternalHelperAppService()
+{
+}
+
+nsOSHelperAppService::~nsOSHelperAppService()
+{
+}
+
+already_AddRefed<nsIMIMEInfo>
+nsOSHelperAppService::GetMIMEInfoFromOS(const nsACString& aMIMEType,
+ const nsACString& aFileExt,
+ bool* aFound)
+{
+ *aFound = false;
+ // Even if we return false for aFound, we need to return a working
+ // nsIMIMEInfo implementation that will be used by the caller.
+ RefPtr<nsGonkMIMEInfo> mimeInfo = new nsGonkMIMEInfo(aMIMEType);
+ return mimeInfo.forget();
+}
+
+nsresult
+nsOSHelperAppService::OSProtocolHandlerExists(const char* aScheme,
+ bool* aExists)
+{
+ *aExists = false;
+ return NS_OK;
+}
diff --git a/uriloader/exthandler/gonk/nsOSHelperAppService.h b/uriloader/exthandler/gonk/nsOSHelperAppService.h
new file mode 100644
index 0000000000..99a280bfcc
--- /dev/null
+++ b/uriloader/exthandler/gonk/nsOSHelperAppService.h
@@ -0,0 +1,39 @@
+/* -*- Mode: c++; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- */
+/* Copyright 2012 Mozilla Foundation and Mozilla contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef nsOSHelperAppService_h
+#define nsOSHelperAppService_h
+
+#include "nsCExternalHandlerService.h"
+#include "nsExternalHelperAppService.h"
+
+class nsOSHelperAppService : public nsExternalHelperAppService
+{
+public:
+ nsOSHelperAppService();
+ virtual ~nsOSHelperAppService();
+
+ virtual already_AddRefed<nsIMIMEInfo>
+ GetMIMEInfoFromOS(const nsACString& aMIMEType,
+ const nsACString& aFileExt,
+ bool* aFound);
+
+ virtual MOZ_MUST_USE nsresult
+ OSProtocolHandlerExists(const char* aScheme,
+ bool* aExists);
+};
+
+#endif /* nsOSHelperAppService_h */
diff --git a/uriloader/exthandler/mac/nsDecodeAppleFile.cpp b/uriloader/exthandler/mac/nsDecodeAppleFile.cpp
new file mode 100644
index 0000000000..1a428a62db
--- /dev/null
+++ b/uriloader/exthandler/mac/nsDecodeAppleFile.cpp
@@ -0,0 +1,389 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsDecodeAppleFile.h"
+#include "prmem.h"
+#include "nsCRT.h"
+
+
+NS_IMPL_ADDREF(nsDecodeAppleFile)
+NS_IMPL_RELEASE(nsDecodeAppleFile)
+
+NS_INTERFACE_MAP_BEGIN(nsDecodeAppleFile)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIOutputStream)
+ NS_INTERFACE_MAP_ENTRY(nsIOutputStream)
+NS_INTERFACE_MAP_END_THREADSAFE
+
+nsDecodeAppleFile::nsDecodeAppleFile()
+{
+ m_state = parseHeaders;
+ m_dataBufferLength = 0;
+ m_dataBuffer = (unsigned char*) PR_MALLOC(MAX_BUFFERSIZE);
+ m_entries = nullptr;
+ m_rfRefNum = -1;
+ m_totalDataForkWritten = 0;
+ m_totalResourceForkWritten = 0;
+ m_headerOk = false;
+
+ m_comment[0] = 0;
+ memset(&m_dates, 0, sizeof(m_dates));
+ memset(&m_finderInfo, 0, sizeof(m_dates));
+ memset(&m_finderExtraInfo, 0, sizeof(m_dates));
+}
+
+nsDecodeAppleFile::~nsDecodeAppleFile()
+{
+
+ PR_FREEIF(m_dataBuffer);
+ if (m_entries)
+ delete [] m_entries;
+}
+
+NS_IMETHODIMP nsDecodeAppleFile::Initialize(nsIOutputStream *output, nsIFile *file)
+{
+ m_output = output;
+
+ nsCOMPtr<nsILocalFileMac> macFile = do_QueryInterface(file);
+ macFile->GetTargetFSSpec(&m_fsFileSpec);
+
+ m_offset = 0;
+ m_dataForkOffset = 0;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDecodeAppleFile::Close(void)
+{
+ nsresult rv;
+ rv = m_output->Close();
+
+ int32_t i;
+
+ if (m_rfRefNum != -1)
+ FSClose(m_rfRefNum);
+
+ /* Check if the file is complete and if it's the case, write file attributes */
+ if (m_headerOk)
+ {
+ bool dataOk = true; /* It's ok if the file doesn't have a datafork, therefore set it to true by default. */
+ if (m_headers.magic == APPLESINGLE_MAGIC)
+ {
+ for (i = 0; i < m_headers.entriesCount; i ++)
+ if (ENT_DFORK == m_entries[i].id)
+ {
+ dataOk = (bool)(m_totalDataForkWritten == m_entries[i].length);
+ break;
+ }
+ }
+
+ bool resourceOk = FALSE;
+ for (i = 0; i < m_headers.entriesCount; i ++)
+ if (ENT_RFORK == m_entries[i].id)
+ {
+ resourceOk = (bool)(m_totalResourceForkWritten == m_entries[i].length);
+ break;
+ }
+
+ if (dataOk && resourceOk)
+ {
+ HFileInfo *fpb;
+ CInfoPBRec cipbr;
+
+ fpb = (HFileInfo *) &cipbr;
+ fpb->ioVRefNum = m_fsFileSpec.vRefNum;
+ fpb->ioDirID = m_fsFileSpec.parID;
+ fpb->ioNamePtr = m_fsFileSpec.name;
+ fpb->ioFDirIndex = 0;
+ PBGetCatInfoSync(&cipbr);
+
+ /* set finder info */
+ memcpy(&fpb->ioFlFndrInfo, &m_finderInfo, sizeof (FInfo));
+ memcpy(&fpb->ioFlXFndrInfo, &m_finderExtraInfo, sizeof (FXInfo));
+ fpb->ioFlFndrInfo.fdFlags &= 0xfc00; /* clear flags maintained by finder */
+
+ /* set file dates */
+ fpb->ioFlCrDat = m_dates.create - CONVERT_TIME;
+ fpb->ioFlMdDat = m_dates.modify - CONVERT_TIME;
+ fpb->ioFlBkDat = m_dates.backup - CONVERT_TIME;
+
+ /* update file info */
+ fpb->ioDirID = fpb->ioFlParID;
+ PBSetCatInfoSync(&cipbr);
+
+ /* set comment */
+ IOParam vinfo;
+ GetVolParmsInfoBuffer vp;
+ DTPBRec dtp;
+
+ memset((void *) &vinfo, 0, sizeof (vinfo));
+ vinfo.ioVRefNum = fpb->ioVRefNum;
+ vinfo.ioBuffer = (Ptr) &vp;
+ vinfo.ioReqCount = sizeof (vp);
+ if (PBHGetVolParmsSync((HParmBlkPtr) &vinfo) == noErr && ((vp.vMAttrib >> bHasDesktopMgr) & 1))
+ {
+ memset((void *) &dtp, 0, sizeof (dtp));
+ dtp.ioVRefNum = fpb->ioVRefNum;
+ if (PBDTGetPath(&dtp) == noErr)
+ {
+ dtp.ioDTBuffer = (Ptr) &m_comment[1];
+ dtp.ioNamePtr = fpb->ioNamePtr;
+ dtp.ioDirID = fpb->ioDirID;
+ dtp.ioDTReqCount = m_comment[0];
+ if (PBDTSetCommentSync(&dtp) == noErr)
+ PBDTFlushSync(&dtp);
+ }
+ }
+ }
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsDecodeAppleFile::Flush(void)
+{
+ return m_output->Flush();
+}
+
+NS_IMETHODIMP nsDecodeAppleFile::WriteFrom(nsIInputStream *inStr, uint32_t count, uint32_t *_retval)
+{
+ return m_output->WriteFrom(inStr, count, _retval);
+}
+
+NS_IMETHODIMP nsDecodeAppleFile::WriteSegments(nsReadSegmentFun reader, void * closure, uint32_t count, uint32_t *_retval)
+{
+ return m_output->WriteSegments(reader, closure, count, _retval);
+}
+
+NS_IMETHODIMP nsDecodeAppleFile::IsNonBlocking(bool *aNonBlocking)
+{
+ return m_output->IsNonBlocking(aNonBlocking);
+}
+
+NS_IMETHODIMP nsDecodeAppleFile::Write(const char *buffer, uint32_t bufferSize, uint32_t* writeCount)
+{
+ /* WARNING: to simplify my life, I presume that I should get all appledouble headers in the first block,
+ else I would have to implement a buffer */
+
+ const char * buffPtr = buffer;
+ uint32_t dataCount;
+ int32_t i;
+ nsresult rv = NS_OK;
+
+ *writeCount = 0;
+
+ while (bufferSize > 0 && NS_SUCCEEDED(rv))
+ {
+ switch (m_state)
+ {
+ case parseHeaders :
+ dataCount = sizeof(ap_header) - m_dataBufferLength;
+ if (dataCount > bufferSize)
+ dataCount = bufferSize;
+ memcpy(&m_dataBuffer[m_dataBufferLength], buffPtr, dataCount);
+ m_dataBufferLength += dataCount;
+
+ if (m_dataBufferLength == sizeof(ap_header))
+ {
+ memcpy(&m_headers, m_dataBuffer, sizeof(ap_header));
+
+ /* Check header to be sure we are dealing with the right kind of data, else just write it to the data fork. */
+ if ((m_headers.magic == APPLEDOUBLE_MAGIC || m_headers.magic == APPLESINGLE_MAGIC) &&
+ m_headers.version == VERSION && m_headers.entriesCount)
+ {
+ /* Just to be sure, the filler must contains only 0 */
+ for (i = 0; i < 4 && m_headers.fill[i] == 0L; i ++)
+ ;
+ if (i == 4)
+ m_state = parseEntries;
+ }
+ m_dataBufferLength = 0;
+
+ if (m_state == parseHeaders)
+ {
+ dataCount = 0;
+ m_state = parseWriteThrough;
+ }
+ }
+ break;
+
+ case parseEntries :
+ if (!m_entries)
+ {
+ m_entries = new ap_entry[m_headers.entriesCount];
+ if (!m_entries)
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ uint32_t entriesSize = sizeof(ap_entry) * m_headers.entriesCount;
+ dataCount = entriesSize - m_dataBufferLength;
+ if (dataCount > bufferSize)
+ dataCount = bufferSize;
+ memcpy(&m_dataBuffer[m_dataBufferLength], buffPtr, dataCount);
+ m_dataBufferLength += dataCount;
+
+ if (m_dataBufferLength == entriesSize)
+ {
+ for (i = 0; i < m_headers.entriesCount; i ++)
+ {
+ memcpy(&m_entries[i], &m_dataBuffer[i * sizeof(ap_entry)], sizeof(ap_entry));
+ if (m_headers.magic == APPLEDOUBLE_MAGIC)
+ {
+ uint32_t offset = m_entries[i].offset + m_entries[i].length;
+ if (offset > m_dataForkOffset)
+ m_dataForkOffset = offset;
+ }
+ }
+ m_headerOk = true;
+ m_state = parseLookupPart;
+ }
+ break;
+
+ case parseLookupPart :
+ /* which part are we parsing? */
+ m_currentPartID = -1;
+ for (i = 0; i < m_headers.entriesCount; i ++)
+ if (m_offset == m_entries[i].offset && m_entries[i].length)
+ {
+ m_currentPartID = m_entries[i].id;
+ m_currentPartLength = m_entries[i].length;
+ m_currentPartCount = 0;
+
+ switch (m_currentPartID)
+ {
+ case ENT_DFORK : m_state = parseDataFork; break;
+ case ENT_RFORK : m_state = parseResourceFork; break;
+
+ case ENT_COMMENT :
+ case ENT_DATES :
+ case ENT_FINFO :
+ m_dataBufferLength = 0;
+ m_state = parsePart;
+ break;
+
+ default : m_state = parseSkipPart; break;
+ }
+ break;
+ }
+
+ if (m_currentPartID == -1)
+ {
+ /* maybe is the datafork of an appledouble file? */
+ if (m_offset == m_dataForkOffset)
+ {
+ m_currentPartID = ENT_DFORK;
+ m_currentPartLength = -1;
+ m_currentPartCount = 0;
+ m_state = parseDataFork;
+ }
+ else
+ dataCount = 1;
+ }
+ break;
+
+ case parsePart :
+ dataCount = m_currentPartLength - m_dataBufferLength;
+ if (dataCount > bufferSize)
+ dataCount = bufferSize;
+ memcpy(&m_dataBuffer[m_dataBufferLength], buffPtr, dataCount);
+ m_dataBufferLength += dataCount;
+
+ if (m_dataBufferLength == m_currentPartLength)
+ {
+ switch (m_currentPartID)
+ {
+ case ENT_COMMENT :
+ m_comment[0] = m_currentPartLength > 255 ? 255 : m_currentPartLength;
+ memcpy(&m_comment[1], buffPtr, m_comment[0]);
+ break;
+ case ENT_DATES :
+ if (m_currentPartLength == sizeof(m_dates))
+ memcpy(&m_dates, buffPtr, m_currentPartLength);
+ break;
+ case ENT_FINFO :
+ if (m_currentPartLength == (sizeof(m_finderInfo) + sizeof(m_finderExtraInfo)))
+ {
+ memcpy(&m_finderInfo, buffPtr, sizeof(m_finderInfo));
+ memcpy(&m_finderExtraInfo, buffPtr + sizeof(m_finderInfo), sizeof(m_finderExtraInfo));
+ }
+ break;
+ }
+ m_state = parseLookupPart;
+ }
+ break;
+
+ case parseSkipPart :
+ dataCount = m_currentPartLength - m_currentPartCount;
+ if (dataCount > bufferSize)
+ dataCount = bufferSize;
+ else
+ m_state = parseLookupPart;
+ break;
+
+ case parseDataFork :
+ if (m_headers.magic == APPLEDOUBLE_MAGIC)
+ dataCount = bufferSize;
+ else
+ {
+ dataCount = m_currentPartLength - m_currentPartCount;
+ if (dataCount > bufferSize)
+ dataCount = bufferSize;
+ else
+ m_state = parseLookupPart;
+ }
+
+ if (m_output)
+ {
+ uint32_t writeCount;
+ rv = m_output->Write((const char *)buffPtr, dataCount, &writeCount);
+ if (dataCount != writeCount)
+ rv = NS_ERROR_FAILURE;
+ m_totalDataForkWritten += dataCount;
+ }
+
+ break;
+
+ case parseResourceFork :
+ dataCount = m_currentPartLength - m_currentPartCount;
+ if (dataCount > bufferSize)
+ dataCount = bufferSize;
+ else
+ m_state = parseLookupPart;
+
+ if (m_rfRefNum == -1)
+ {
+ if (noErr != FSpOpenRF(&m_fsFileSpec, fsWrPerm, &m_rfRefNum))
+ return NS_ERROR_FAILURE;
+ }
+
+ long count = dataCount;
+ if (noErr != FSWrite(m_rfRefNum, &count, buffPtr) || count != dataCount)
+ return NS_ERROR_FAILURE;
+ m_totalResourceForkWritten += dataCount;
+ break;
+
+ case parseWriteThrough :
+ dataCount = bufferSize;
+ if (m_output)
+ {
+ uint32_t writeCount;
+ rv = m_output->Write((const char *)buffPtr, dataCount, &writeCount);
+ if (dataCount != writeCount)
+ rv = NS_ERROR_FAILURE;
+ }
+ break;
+ }
+
+ if (dataCount)
+ {
+ *writeCount += dataCount;
+ bufferSize -= dataCount;
+ buffPtr += dataCount;
+ m_currentPartCount += dataCount;
+ m_offset += dataCount;
+ dataCount = 0;
+ }
+ }
+
+ return rv;
+}
diff --git a/uriloader/exthandler/mac/nsDecodeAppleFile.h b/uriloader/exthandler/mac/nsDecodeAppleFile.h
new file mode 100644
index 0000000000..cea2d701ec
--- /dev/null
+++ b/uriloader/exthandler/mac/nsDecodeAppleFile.h
@@ -0,0 +1,118 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsDecodeAppleFile_h__
+#define nsDecodeAppleFile_h__
+
+#include "nscore.h"
+#include "nsCOMPtr.h"
+#include "nsIFile.h"
+#include "nsILocalFileMac.h"
+#include "nsIOutputStream.h"
+
+/*
+** applefile definitions used
+*/
+#if PRAGMA_STRUCT_ALIGN
+ #pragma options align=mac68k
+#endif
+
+#define APPLESINGLE_MAGIC 0x00051600L
+#define APPLEDOUBLE_MAGIC 0x00051607L
+#define VERSION 0x00020000
+
+#define NUM_ENTRIES 6
+
+#define ENT_DFORK 1L
+#define ENT_RFORK 2L
+#define ENT_NAME 3L
+#define ENT_COMMENT 4L
+#define ENT_DATES 8L
+#define ENT_FINFO 9L
+
+#define CONVERT_TIME 1265437696L
+
+/*
+** data type used in the header decoder.
+*/
+typedef struct ap_header
+{
+ int32_t magic;
+ int32_t version;
+ int32_t fill[4];
+ int16_t entriesCount;
+
+} ap_header;
+
+typedef struct ap_entry
+{
+ int32_t id;
+ int32_t offset;
+ int32_t length;
+
+} ap_entry;
+
+typedef struct ap_dates
+{
+ int32_t create, modify, backup, access;
+
+} ap_dates;
+
+#if PRAGMA_STRUCT_ALIGN
+ #pragma options align=reset
+#endif
+
+/*
+**Error codes
+*/
+enum {
+ errADNotEnoughData = -12099,
+ errADNotSupported,
+ errADBadVersion
+};
+
+
+class nsDecodeAppleFile : public nsIOutputStream
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOUTPUTSTREAM
+
+ nsDecodeAppleFile();
+ virtual ~nsDecodeAppleFile();
+
+ MOZ_MUST_USE nsresult Initialize(nsIOutputStream *output, nsIFile *file);
+
+private:
+ #define MAX_BUFFERSIZE 1024
+ enum ParserState {parseHeaders, parseEntries, parseLookupPart, parsePart, parseSkipPart,
+ parseDataFork, parseResourceFork, parseWriteThrough};
+
+ nsCOMPtr<nsIOutputStream> m_output;
+ FSSpec m_fsFileSpec;
+ SInt16 m_rfRefNum;
+
+ unsigned char * m_dataBuffer;
+ int32_t m_dataBufferLength;
+ ParserState m_state;
+ ap_header m_headers;
+ ap_entry * m_entries;
+ int32_t m_offset;
+ int32_t m_dataForkOffset;
+ int32_t m_totalDataForkWritten;
+ int32_t m_totalResourceForkWritten;
+ bool m_headerOk;
+
+ int32_t m_currentPartID;
+ int32_t m_currentPartLength;
+ int32_t m_currentPartCount;
+
+ Str255 m_comment;
+ ap_dates m_dates;
+ FInfo m_finderInfo;
+ FXInfo m_finderExtraInfo;
+};
+
+#endif
diff --git a/uriloader/exthandler/mac/nsLocalHandlerAppMac.h b/uriloader/exthandler/mac/nsLocalHandlerAppMac.h
new file mode 100644
index 0000000000..402ec52953
--- /dev/null
+++ b/uriloader/exthandler/mac/nsLocalHandlerAppMac.h
@@ -0,0 +1,26 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NSLOCALHANDLERAPPMAC_H_
+#define NSLOCALHANDLERAPPMAC_H_
+
+#include "nsLocalHandlerApp.h"
+
+class nsLocalHandlerAppMac : public nsLocalHandlerApp {
+
+ public:
+ nsLocalHandlerAppMac() { }
+
+ nsLocalHandlerAppMac(const char16_t *aName, nsIFile *aExecutable)
+ : nsLocalHandlerApp(aName, aExecutable) {}
+
+ nsLocalHandlerAppMac(const nsAString & aName, nsIFile *aExecutable)
+ : nsLocalHandlerApp(aName, aExecutable) {}
+ virtual ~nsLocalHandlerAppMac() { }
+
+ NS_IMETHOD LaunchWithURI(nsIURI* aURI, nsIInterfaceRequestor* aWindowContext);
+ NS_IMETHOD GetName(nsAString& aName);
+};
+
+#endif /*NSLOCALHANDLERAPPMAC_H_*/
diff --git a/uriloader/exthandler/mac/nsLocalHandlerAppMac.mm b/uriloader/exthandler/mac/nsLocalHandlerAppMac.mm
new file mode 100644
index 0000000000..fd633ab726
--- /dev/null
+++ b/uriloader/exthandler/mac/nsLocalHandlerAppMac.mm
@@ -0,0 +1,84 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#import <CoreFoundation/CoreFoundation.h>
+#import <ApplicationServices/ApplicationServices.h>
+
+#include "nsObjCExceptions.h"
+#include "nsLocalHandlerAppMac.h"
+#include "nsILocalFileMac.h"
+#include "nsIURI.h"
+
+// We override this to make sure app bundles display their pretty name (without .app suffix)
+NS_IMETHODIMP nsLocalHandlerAppMac::GetName(nsAString& aName)
+{
+ if (mExecutable) {
+ nsCOMPtr<nsILocalFileMac> macFile = do_QueryInterface(mExecutable);
+ if (macFile) {
+ bool isPackage;
+ (void)macFile->IsPackage(&isPackage);
+ if (isPackage)
+ return macFile->GetBundleDisplayName(aName);
+ }
+ }
+
+ return nsLocalHandlerApp::GetName(aName);
+}
+
+/**
+ * mostly copy/pasted from nsMacShellService.cpp (which is in browser/,
+ * so we can't depend on it here). This code probably really wants to live
+ * somewhere more central (see bug 389922).
+ */
+NS_IMETHODIMP
+nsLocalHandlerAppMac::LaunchWithURI(nsIURI *aURI,
+ nsIInterfaceRequestor *aWindowContext)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ nsresult rv;
+ nsCOMPtr<nsILocalFileMac> lfm(do_QueryInterface(mExecutable, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ CFURLRef appURL;
+ rv = lfm->GetCFURL(&appURL);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsAutoCString uriSpec;
+ aURI->GetAsciiSpec(uriSpec);
+
+ const UInt8* uriString = reinterpret_cast<const UInt8*>(uriSpec.get());
+ CFURLRef uri = ::CFURLCreateWithBytes(NULL, uriString, uriSpec.Length(),
+ kCFStringEncodingUTF8, NULL);
+ if (!uri) {
+ ::CFRelease(appURL);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ CFArrayRef uris = ::CFArrayCreate(NULL, reinterpret_cast<const void**>(&uri),
+ 1, NULL);
+ if (!uris) {
+ ::CFRelease(uri);
+ ::CFRelease(appURL);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ LSLaunchURLSpec launchSpec;
+ launchSpec.appURL = appURL;
+ launchSpec.itemURLs = uris;
+ launchSpec.passThruParams = NULL;
+ launchSpec.launchFlags = kLSLaunchDefaults;
+ launchSpec.asyncRefCon = NULL;
+
+ OSErr err = ::LSOpenFromURLSpec(&launchSpec, NULL);
+
+ ::CFRelease(uris);
+ ::CFRelease(uri);
+ ::CFRelease(appURL);
+
+ return err != noErr ? NS_ERROR_FAILURE : NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
diff --git a/uriloader/exthandler/mac/nsMIMEInfoMac.h b/uriloader/exthandler/mac/nsMIMEInfoMac.h
new file mode 100644
index 0000000000..298357f757
--- /dev/null
+++ b/uriloader/exthandler/mac/nsMIMEInfoMac.h
@@ -0,0 +1,34 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMIMEInfoMac_h_
+#define nsMIMEInfoMac_h_
+
+#include "nsMIMEInfoImpl.h"
+
+class nsMIMEInfoMac : public nsMIMEInfoImpl {
+ public:
+ explicit nsMIMEInfoMac(const char* aMIMEType = "") : nsMIMEInfoImpl(aMIMEType) {}
+ explicit nsMIMEInfoMac(const nsACString& aMIMEType) : nsMIMEInfoImpl(aMIMEType) {}
+ nsMIMEInfoMac(const nsACString& aType, HandlerClass aClass) :
+ nsMIMEInfoImpl(aType, aClass) {}
+
+ NS_IMETHOD LaunchWithFile(nsIFile* aFile);
+ protected:
+ virtual MOZ_MUST_USE nsresult LoadUriInternal(nsIURI *aURI);
+#ifdef DEBUG
+ virtual MOZ_MUST_USE nsresult LaunchDefaultWithFile(nsIFile* aFile) {
+ NS_NOTREACHED("do not call this method, use LaunchWithFile");
+ return NS_ERROR_UNEXPECTED;
+ }
+#endif
+ static MOZ_MUST_USE nsresult OpenApplicationWithURI(nsIFile *aApplication,
+ const nsCString& aURI);
+
+ NS_IMETHOD GetDefaultDescription(nsAString& aDefaultDescription);
+
+};
+
+
+#endif
diff --git a/uriloader/exthandler/mac/nsMIMEInfoMac.mm b/uriloader/exthandler/mac/nsMIMEInfoMac.mm
new file mode 100644
index 0000000000..64d3a82dea
--- /dev/null
+++ b/uriloader/exthandler/mac/nsMIMEInfoMac.mm
@@ -0,0 +1,114 @@
+/* -*- Mode: C++; tab-width: 3; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#import <ApplicationServices/ApplicationServices.h>
+
+#include "nsObjCExceptions.h"
+#include "nsMIMEInfoMac.h"
+#include "nsILocalFileMac.h"
+#include "nsIFileURL.h"
+
+// We override this to make sure app bundles display their pretty name (without .app suffix)
+NS_IMETHODIMP nsMIMEInfoMac::GetDefaultDescription(nsAString& aDefaultDescription)
+{
+ if (mDefaultApplication) {
+ nsCOMPtr<nsILocalFileMac> macFile = do_QueryInterface(mDefaultApplication);
+ if (macFile) {
+ bool isPackage;
+ (void)macFile->IsPackage(&isPackage);
+ if (isPackage)
+ return macFile->GetBundleDisplayName(aDefaultDescription);
+ }
+ }
+
+ return nsMIMEInfoImpl::GetDefaultDescription(aDefaultDescription);
+}
+
+NS_IMETHODIMP
+nsMIMEInfoMac::LaunchWithFile(nsIFile *aFile)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ nsCOMPtr<nsIFile> application;
+ nsresult rv;
+
+ NS_ASSERTION(mClass == eMIMEInfo, "only MIME infos are currently allowed"
+ "to pass content by value");
+
+ if (mPreferredAction == useHelperApp) {
+
+ // we don't yet support passing content by value (rather than reference)
+ // to web apps. at some point, we will probably want to.
+ nsCOMPtr<nsILocalHandlerApp> localHandlerApp =
+ do_QueryInterface(mPreferredApplication, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = localHandlerApp->GetExecutable(getter_AddRefs(application));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ } else if (mPreferredAction == useSystemDefault) {
+ application = mDefaultApplication;
+ }
+ else
+ return NS_ERROR_INVALID_ARG;
+
+ // if we've already got an app, just QI so we have the launchWithDoc method
+ nsCOMPtr<nsILocalFileMac> app;
+ if (application) {
+ app = do_QueryInterface(application, &rv);
+ if (NS_FAILED(rv)) return rv;
+ } else {
+ // otherwise ask LaunchServices for an app directly
+ nsCOMPtr<nsILocalFileMac> tempFile = do_QueryInterface(aFile, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ FSRef tempFileRef;
+ tempFile->GetFSRef(&tempFileRef);
+
+ FSRef appFSRef;
+ if (::LSGetApplicationForItem(&tempFileRef, kLSRolesAll, &appFSRef, nullptr) == noErr)
+ {
+ app = (do_CreateInstance("@mozilla.org/file/local;1"));
+ if (!app) return NS_ERROR_FAILURE;
+ app->InitWithFSRef(&appFSRef);
+ } else {
+ return NS_ERROR_FAILURE;
+ }
+ }
+ return app->LaunchWithDoc(aFile, false);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+nsresult
+nsMIMEInfoMac::LoadUriInternal(nsIURI *aURI)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ NS_ENSURE_ARG_POINTER(aURI);
+
+ nsresult rv = NS_ERROR_FAILURE;
+
+ nsAutoCString uri;
+ aURI->GetSpec(uri);
+ if (!uri.IsEmpty()) {
+ CFURLRef myURLRef = ::CFURLCreateWithBytes(kCFAllocatorDefault,
+ (const UInt8*)uri.get(),
+ strlen(uri.get()),
+ kCFStringEncodingUTF8,
+ NULL);
+ if (myURLRef) {
+ OSStatus status = ::LSOpenCFURLRef(myURLRef, NULL);
+ if (status == noErr)
+ rv = NS_OK;
+ ::CFRelease(myURLRef);
+ }
+ }
+
+ return rv;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
diff --git a/uriloader/exthandler/mac/nsOSHelperAppService.h b/uriloader/exthandler/mac/nsOSHelperAppService.h
new file mode 100644
index 0000000000..7371e1f42d
--- /dev/null
+++ b/uriloader/exthandler/mac/nsOSHelperAppService.h
@@ -0,0 +1,48 @@
+/* -*- Mode: C++; tab-width: 3; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsOSHelperAppService_h__
+#define nsOSHelperAppService_h__
+
+// The OS helper app service is a subclass of nsExternalHelperAppService and is implemented on each
+// platform. It contains platform specific code for finding helper applications for a given mime type
+// in addition to launching those applications. This is the Mac version.
+
+#include "nsExternalHelperAppService.h"
+#include "nsCExternalHandlerService.h"
+#include "nsMIMEInfoImpl.h"
+#include "nsCOMPtr.h"
+
+class nsOSHelperAppService : public nsExternalHelperAppService
+{
+public:
+ nsOSHelperAppService();
+ virtual ~nsOSHelperAppService();
+
+ // override nsIExternalProtocolService methods
+ NS_IMETHOD GetApplicationDescription(const nsACString& aScheme, nsAString& _retval);
+
+ // method overrides --> used to hook the mime service into internet config....
+ NS_IMETHOD GetFromTypeAndExtension(const nsACString& aType, const nsACString& aFileExt, nsIMIMEInfo ** aMIMEInfo);
+ already_AddRefed<nsIMIMEInfo> GetMIMEInfoFromOS(const nsACString& aMIMEType, const nsACString& aFileExt, bool * aFound);
+ NS_IMETHOD GetProtocolHandlerInfoFromOS(const nsACString &aScheme,
+ bool *found,
+ nsIHandlerInfo **_retval);
+
+ // GetFileTokenForPath must be implemented by each platform.
+ // platformAppPath --> a platform specific path to an application that we got out of the
+ // rdf data source. This can be a mac file spec, a unix path or a windows path depending on the platform
+ // aFile --> an nsIFile representation of that platform application path.
+ virtual MOZ_MUST_USE nsresult GetFileTokenForPath(const char16_t * platformAppPath, nsIFile ** aFile);
+
+ MOZ_MUST_USE nsresult OSProtocolHandlerExists(const char * aScheme,
+ bool * aHandlerExists);
+
+private:
+ uint32_t mPermissions;
+};
+
+#endif // nsOSHelperAppService_h__
diff --git a/uriloader/exthandler/mac/nsOSHelperAppService.mm b/uriloader/exthandler/mac/nsOSHelperAppService.mm
new file mode 100644
index 0000000000..00f12f5448
--- /dev/null
+++ b/uriloader/exthandler/mac/nsOSHelperAppService.mm
@@ -0,0 +1,569 @@
+/* -*- Mode: C++; tab-width: 3; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include "nsOSHelperAppService.h"
+#include "nsObjCExceptions.h"
+#include "nsISupports.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsXPIDLString.h"
+#include "nsIURL.h"
+#include "nsIFile.h"
+#include "nsILocalFileMac.h"
+#include "nsMimeTypes.h"
+#include "nsIStringBundle.h"
+#include "nsIPromptService.h"
+#include "nsMemory.h"
+#include "nsCRT.h"
+#include "nsMIMEInfoMac.h"
+#include "nsEmbedCID.h"
+
+#import <CoreFoundation/CoreFoundation.h>
+#import <ApplicationServices/ApplicationServices.h>
+
+// chrome URL's
+#define HELPERAPPLAUNCHER_BUNDLE_URL "chrome://global/locale/helperAppLauncher.properties"
+#define BRAND_BUNDLE_URL "chrome://branding/locale/brand.properties"
+
+using mozilla::LogLevel;
+
+/* This is an undocumented interface (in the Foundation framework) that has
+ * been stable since at least 10.2.8 and is still present on SnowLeopard.
+ * Furthermore WebKit has three public methods (in WebKitSystemInterface.h)
+ * that are thin wrappers around this interface's last three methods. So
+ * it's unlikely to change anytime soon. Now that we're no longer using
+ * Internet Config Services, this is the only way to look up a MIME type
+ * from an extension, or vice versa.
+ */
+@class NSURLFileTypeMappingsInternal;
+
+@interface NSURLFileTypeMappings : NSObject
+{
+ NSURLFileTypeMappingsInternal *_internal;
+}
+
++ (NSURLFileTypeMappings*)sharedMappings;
+- (NSString*)MIMETypeForExtension:(NSString*)aString;
+- (NSString*)preferredExtensionForMIMEType:(NSString*)aString;
+- (NSArray*)extensionsForMIMEType:(NSString*)aString;
+@end
+
+nsOSHelperAppService::nsOSHelperAppService() : nsExternalHelperAppService()
+{
+ mode_t mask = umask(0777);
+ umask(mask);
+ mPermissions = 0666 & ~mask;
+}
+
+nsOSHelperAppService::~nsOSHelperAppService()
+{}
+
+nsresult nsOSHelperAppService::OSProtocolHandlerExists(const char * aProtocolScheme, bool * aHandlerExists)
+{
+ // CFStringCreateWithBytes() can fail even if we're not out of memory --
+ // for example if the 'bytes' parameter is something very wierd (like "ÿÿ~"
+ // aka "\xFF\xFF~"), or possibly if it can't be interpreted as using what's
+ // specified in the 'encoding' parameter. See bug 548719.
+ CFStringRef schemeString = ::CFStringCreateWithBytes(kCFAllocatorDefault,
+ (const UInt8*)aProtocolScheme,
+ strlen(aProtocolScheme),
+ kCFStringEncodingUTF8,
+ false);
+ if (schemeString) {
+ // LSCopyDefaultHandlerForURLScheme() can fail to find the default handler
+ // for aProtocolScheme when it's never been explicitly set (using
+ // LSSetDefaultHandlerForURLScheme()). For example, Safari is the default
+ // handler for the "http" scheme on a newly installed copy of OS X. But
+ // this (presumably) wasn't done using LSSetDefaultHandlerForURLScheme(),
+ // so LSCopyDefaultHandlerForURLScheme() will fail to find Safari. To get
+ // around this we use LSCopyAllHandlersForURLScheme() instead -- which seems
+ // never to fail.
+ // http://lists.apple.com/archives/Carbon-dev/2007/May/msg00349.html
+ // http://www.realsoftware.com/listarchives/realbasic-nug/2008-02/msg00119.html
+ CFArrayRef handlerArray = ::LSCopyAllHandlersForURLScheme(schemeString);
+ *aHandlerExists = !!handlerArray;
+ if (handlerArray)
+ ::CFRelease(handlerArray);
+ ::CFRelease(schemeString);
+ } else {
+ *aHandlerExists = false;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsOSHelperAppService::GetApplicationDescription(const nsACString& aScheme, nsAString& _retval)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ nsresult rv = NS_ERROR_NOT_AVAILABLE;
+
+ CFStringRef schemeCFString =
+ ::CFStringCreateWithBytes(kCFAllocatorDefault,
+ (const UInt8 *)PromiseFlatCString(aScheme).get(),
+ aScheme.Length(),
+ kCFStringEncodingUTF8,
+ false);
+
+ if (schemeCFString) {
+ CFStringRef lookupCFString = ::CFStringCreateWithFormat(NULL, NULL, CFSTR("%@:"), schemeCFString);
+
+ if (lookupCFString) {
+ CFURLRef lookupCFURL = ::CFURLCreateWithString(NULL, lookupCFString, NULL);
+
+ if (lookupCFURL) {
+ CFURLRef appCFURL = NULL;
+ OSStatus theErr = ::LSGetApplicationForURL(lookupCFURL, kLSRolesAll, NULL, &appCFURL);
+
+ if (theErr == noErr) {
+ CFBundleRef handlerBundle = ::CFBundleCreate(NULL, appCFURL);
+
+ if (handlerBundle) {
+ // Get the human-readable name of the default handler bundle
+ CFStringRef bundleName =
+ (CFStringRef)::CFBundleGetValueForInfoDictionaryKey(handlerBundle,
+ kCFBundleNameKey);
+
+ if (bundleName) {
+ AutoTArray<UniChar, 255> buffer;
+ CFIndex bundleNameLength = ::CFStringGetLength(bundleName);
+ buffer.SetLength(bundleNameLength);
+ ::CFStringGetCharacters(bundleName, CFRangeMake(0, bundleNameLength),
+ buffer.Elements());
+ _retval.Assign(reinterpret_cast<char16_t*>(buffer.Elements()), bundleNameLength);
+ rv = NS_OK;
+ }
+
+ ::CFRelease(handlerBundle);
+ }
+
+ ::CFRelease(appCFURL);
+ }
+
+ ::CFRelease(lookupCFURL);
+ }
+
+ ::CFRelease(lookupCFString);
+ }
+
+ ::CFRelease(schemeCFString);
+ }
+
+ return rv;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+nsresult nsOSHelperAppService::GetFileTokenForPath(const char16_t * aPlatformAppPath, nsIFile ** aFile)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ nsresult rv;
+ nsCOMPtr<nsILocalFileMac> localFile (do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ CFURLRef pathAsCFURL;
+ CFStringRef pathAsCFString = ::CFStringCreateWithCharacters(NULL,
+ reinterpret_cast<const UniChar*>(aPlatformAppPath),
+ NS_strlen(aPlatformAppPath));
+ if (!pathAsCFString)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ if (::CFStringGetCharacterAtIndex(pathAsCFString, 0) == '/') {
+ // we have a Posix path
+ pathAsCFURL = ::CFURLCreateWithFileSystemPath(nullptr, pathAsCFString,
+ kCFURLPOSIXPathStyle, false);
+ if (!pathAsCFURL) {
+ ::CFRelease(pathAsCFString);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+ else {
+ // if it doesn't start with a / it's not an absolute Posix path
+ // let's check if it's a HFS path left over from old preferences
+
+ // If it starts with a ':' char, it's not an absolute HFS path
+ // so bail for that, and also if it's empty
+ if (::CFStringGetLength(pathAsCFString) == 0 ||
+ ::CFStringGetCharacterAtIndex(pathAsCFString, 0) == ':')
+ {
+ ::CFRelease(pathAsCFString);
+ return NS_ERROR_FILE_UNRECOGNIZED_PATH;
+ }
+
+ pathAsCFURL = ::CFURLCreateWithFileSystemPath(nullptr, pathAsCFString,
+ kCFURLHFSPathStyle, false);
+ if (!pathAsCFURL) {
+ ::CFRelease(pathAsCFString);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ rv = localFile->InitWithCFURL(pathAsCFURL);
+ ::CFRelease(pathAsCFString);
+ ::CFRelease(pathAsCFURL);
+ if (NS_FAILED(rv))
+ return rv;
+ *aFile = localFile;
+ NS_IF_ADDREF(*aFile);
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP nsOSHelperAppService::GetFromTypeAndExtension(const nsACString& aType, const nsACString& aFileExt, nsIMIMEInfo ** aMIMEInfo)
+{
+ return nsExternalHelperAppService::GetFromTypeAndExtension(aType, aFileExt, aMIMEInfo);
+}
+
+// Returns the MIME types an application bundle explicitly claims to handle.
+// Returns NULL if aAppRef doesn't explicitly claim to handle any MIME types.
+// If the return value is non-NULL, the caller is responsible for freeing it.
+// This isn't necessarily the same as the MIME types the application bundle
+// is registered to handle in the Launch Services database. (For example
+// the Preview application is normally registered to handle the application/pdf
+// MIME type, even though it doesn't explicitly claim to handle *any* MIME
+// types in its Info.plist. This is probably because Preview does explicitly
+// claim to handle the com.adobe.pdf UTI, and Launch Services somehow
+// translates this into a claim to support the application/pdf MIME type.
+// Launch Services doesn't provide any APIs (documented or undocumented) to
+// query which MIME types a given application is registered to handle. So any
+// app that wants this information (e.g. the Default Apps pref pane) needs to
+// iterate through the entire Launch Services database -- a process which can
+// take several seconds.)
+static CFArrayRef GetMIMETypesHandledByApp(FSRef *aAppRef)
+{
+ CFURLRef appURL = ::CFURLCreateFromFSRef(kCFAllocatorDefault, aAppRef);
+ if (!appURL) {
+ return NULL;
+ }
+ CFDictionaryRef infoDict = ::CFBundleCopyInfoDictionaryForURL(appURL);
+ ::CFRelease(appURL);
+ if (!infoDict) {
+ return NULL;
+ }
+ CFTypeRef cfObject = ::CFDictionaryGetValue(infoDict, CFSTR("CFBundleDocumentTypes"));
+ if (!cfObject || (::CFGetTypeID(cfObject) != ::CFArrayGetTypeID())) {
+ ::CFRelease(infoDict);
+ return NULL;
+ }
+
+ CFArrayRef docTypes = static_cast<CFArrayRef>(cfObject);
+ CFIndex docTypesCount = ::CFArrayGetCount(docTypes);
+ if (docTypesCount == 0) {
+ ::CFRelease(infoDict);
+ return NULL;
+ }
+
+ CFMutableArrayRef mimeTypes =
+ ::CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
+ for (CFIndex i = 0; i < docTypesCount; ++i) {
+ cfObject = ::CFArrayGetValueAtIndex(docTypes, i);
+ if (!cfObject || (::CFGetTypeID(cfObject) != ::CFDictionaryGetTypeID())) {
+ continue;
+ }
+ CFDictionaryRef typeDict = static_cast<CFDictionaryRef>(cfObject);
+
+ // When this key is present (on OS X 10.5 and later), its contents
+ // take precedence over CFBundleTypeMIMETypes (and CFBundleTypeExtensions
+ // and CFBundleTypeOSTypes).
+ cfObject = ::CFDictionaryGetValue(typeDict, CFSTR("LSItemContentTypes"));
+ if (cfObject && (::CFGetTypeID(cfObject) == ::CFArrayGetTypeID())) {
+ continue;
+ }
+
+ cfObject = ::CFDictionaryGetValue(typeDict, CFSTR("CFBundleTypeMIMETypes"));
+ if (!cfObject || (::CFGetTypeID(cfObject) != ::CFArrayGetTypeID())) {
+ continue;
+ }
+ CFArrayRef mimeTypeHolder = static_cast<CFArrayRef>(cfObject);
+ CFArrayAppendArray(mimeTypes, mimeTypeHolder,
+ ::CFRangeMake(0, ::CFArrayGetCount(mimeTypeHolder)));
+ }
+
+ ::CFRelease(infoDict);
+ if (!::CFArrayGetCount(mimeTypes)) {
+ ::CFRelease(mimeTypes);
+ mimeTypes = NULL;
+ }
+ return mimeTypes;
+}
+
+// aMIMEType and aFileExt might not match, If they don't we set *aFound to
+// false and return a minimal nsIMIMEInfo structure.
+already_AddRefed<nsIMIMEInfo>
+nsOSHelperAppService::GetMIMEInfoFromOS(const nsACString& aMIMEType,
+ const nsACString& aFileExt,
+ bool * aFound)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSNULL;
+
+ *aFound = false;
+
+ const nsCString& flatType = PromiseFlatCString(aMIMEType);
+ const nsCString& flatExt = PromiseFlatCString(aFileExt);
+
+ MOZ_LOG(mLog, LogLevel::Debug, ("Mac: HelperAppService lookup for type '%s' ext '%s'\n",
+ flatType.get(), flatExt.get()));
+
+ // Create a Mac-specific MIME info so we can use Mac-specific members.
+ RefPtr<nsMIMEInfoMac> mimeInfoMac = new nsMIMEInfoMac(aMIMEType);
+
+ NSAutoreleasePool *localPool = [[NSAutoreleasePool alloc] init];
+
+ OSStatus err;
+ bool haveAppForType = false;
+ bool haveAppForExt = false;
+ bool typeAppIsDefault = false;
+ bool extAppIsDefault = false;
+ FSRef typeAppFSRef;
+ FSRef extAppFSRef;
+
+ CFStringRef cfMIMEType = NULL;
+
+ if (!aMIMEType.IsEmpty()) {
+ CFURLRef appURL = NULL;
+ // CFStringCreateWithCString() can fail even if we're not out of memory --
+ // for example if the 'cStr' parameter is something very weird (like "ÿÿ~"
+ // aka "\xFF\xFF~"), or possibly if it can't be interpreted as using what's
+ // specified in the 'encoding' parameter. See bug 548719.
+ cfMIMEType = ::CFStringCreateWithCString(NULL, flatType.get(),
+ kCFStringEncodingUTF8);
+ if (cfMIMEType) {
+ err = ::LSCopyApplicationForMIMEType(cfMIMEType, kLSRolesAll, &appURL);
+ if ((err == noErr) && appURL && ::CFURLGetFSRef(appURL, &typeAppFSRef)) {
+ haveAppForType = true;
+ MOZ_LOG(mLog, LogLevel::Debug, ("LSCopyApplicationForMIMEType found a default application\n"));
+ }
+ if (appURL) {
+ ::CFRelease(appURL);
+ }
+ }
+ }
+ if (!aFileExt.IsEmpty()) {
+ // CFStringCreateWithCString() can fail even if we're not out of memory --
+ // for example if the 'cStr' parameter is something very wierd (like "ÿÿ~"
+ // aka "\xFF\xFF~"), or possibly if it can't be interpreted as using what's
+ // specified in the 'encoding' parameter. See bug 548719.
+ CFStringRef cfExt = ::CFStringCreateWithCString(NULL, flatExt.get(), kCFStringEncodingUTF8);
+ if (cfExt) {
+ err = ::LSGetApplicationForInfo(kLSUnknownType, kLSUnknownCreator, cfExt,
+ kLSRolesAll, &extAppFSRef, nullptr);
+ if (err == noErr) {
+ haveAppForExt = true;
+ MOZ_LOG(mLog, LogLevel::Debug, ("LSGetApplicationForInfo found a default application\n"));
+ }
+ ::CFRelease(cfExt);
+ }
+ }
+
+ if (haveAppForType && haveAppForExt) {
+ // Do aMIMEType and aFileExt match?
+ if (::FSCompareFSRefs((const FSRef *) &typeAppFSRef, (const FSRef *) &extAppFSRef) == noErr) {
+ typeAppIsDefault = true;
+ *aFound = true;
+ }
+ } else if (haveAppForType) {
+ // If aFileExt isn't empty, it doesn't match aMIMEType.
+ if (aFileExt.IsEmpty()) {
+ typeAppIsDefault = true;
+ *aFound = true;
+ }
+ } else if (haveAppForExt) {
+ // If aMIMEType isn't empty, it doesn't match aFileExt, which should mean
+ // that we haven't found a matching app. But make an exception for an app
+ // that also explicitly claims to handle aMIMEType, or which doesn't claim
+ // to handle any MIME types. This helps work around the following Apple
+ // design flaw:
+ //
+ // Launch Services is somewhat unreliable about registering Apple apps to
+ // handle MIME types. Probably this is because Apple has officially
+ // deprecated support for MIME types (in favor of UTIs). As a result,
+ // most of Apple's own apps don't explicitly claim to handle any MIME
+ // types (instead they claim to handle one or more UTIs). So Launch
+ // Services must contain logic to translate support for a given UTI into
+ // support for one or more MIME types, and it doesn't always do this
+ // correctly. For example DiskImageMounter isn't (by default) registered
+ // to handle the application/x-apple-diskimage MIME type. See bug 675356.
+ //
+ // Apple has also deprecated support for file extensions, and Apple apps
+ // also don't register to handle them. But for some reason Launch Services
+ // is (apparently) better about translating support for a given UTI into
+ // support for one or more file extensions. It's not at all clear why.
+ if (aMIMEType.IsEmpty()) {
+ extAppIsDefault = true;
+ *aFound = true;
+ } else {
+ CFArrayRef extAppMIMETypes = GetMIMETypesHandledByApp(&extAppFSRef);
+ if (extAppMIMETypes) {
+ if (cfMIMEType) {
+ if (::CFArrayContainsValue(extAppMIMETypes,
+ ::CFRangeMake(0, ::CFArrayGetCount(extAppMIMETypes)),
+ cfMIMEType)) {
+ extAppIsDefault = true;
+ *aFound = true;
+ }
+ }
+ ::CFRelease(extAppMIMETypes);
+ } else {
+ extAppIsDefault = true;
+ *aFound = true;
+ }
+ }
+ }
+
+ if (cfMIMEType) {
+ ::CFRelease(cfMIMEType);
+ }
+
+ if (aMIMEType.IsEmpty()) {
+ if (haveAppForExt) {
+ // If aMIMEType is empty and we've found a default app for aFileExt, try
+ // to get the MIME type from aFileExt. (It might also be worth doing
+ // this when aMIMEType isn't empty but haveAppForType is false -- but
+ // the doc for this method says that if we have a MIME type (in
+ // aMIMEType), we need to give it preference.)
+ NSURLFileTypeMappings *map = [NSURLFileTypeMappings sharedMappings];
+ NSString *extStr = [NSString stringWithCString:flatExt.get() encoding:NSASCIIStringEncoding];
+ NSString *typeStr = map ? [map MIMETypeForExtension:extStr] : NULL;
+ if (typeStr) {
+ nsAutoCString mimeType;
+ mimeType.Assign((char *)[typeStr cStringUsingEncoding:NSASCIIStringEncoding]);
+ mimeInfoMac->SetMIMEType(mimeType);
+ haveAppForType = true;
+ } else {
+ // Sometimes the OS won't give us a MIME type for an extension that's
+ // registered with Launch Services and has a default app: For example
+ // Real Player registers itself for the "ogg" extension and for the
+ // audio/x-ogg and application/x-ogg MIME types, but
+ // MIMETypeForExtension returns nil for the "ogg" extension even on
+ // systems where Real Player is installed. This is probably an Apple
+ // bug. But bad things happen if we return an nsIMIMEInfo structure
+ // with an empty MIME type and set *aFound to true. So in this
+ // case we need to set it to false here.
+ haveAppForExt = false;
+ extAppIsDefault = false;
+ *aFound = false;
+ }
+ } else {
+ // Otherwise set the MIME type to a reasonable fallback.
+ mimeInfoMac->SetMIMEType(NS_LITERAL_CSTRING(APPLICATION_OCTET_STREAM));
+ }
+ }
+
+ if (typeAppIsDefault || extAppIsDefault) {
+ if (haveAppForExt)
+ mimeInfoMac->AppendExtension(aFileExt);
+
+ nsCOMPtr<nsILocalFileMac> app(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID));
+ if (!app) {
+ [localPool release];
+ return nullptr;
+ }
+
+ CFStringRef cfAppName = NULL;
+ if (typeAppIsDefault) {
+ app->InitWithFSRef(&typeAppFSRef);
+ ::LSCopyItemAttribute((const FSRef *) &typeAppFSRef, kLSRolesAll,
+ kLSItemDisplayName, (CFTypeRef *) &cfAppName);
+ } else {
+ app->InitWithFSRef(&extAppFSRef);
+ ::LSCopyItemAttribute((const FSRef *) &extAppFSRef, kLSRolesAll,
+ kLSItemDisplayName, (CFTypeRef *) &cfAppName);
+ }
+ if (cfAppName) {
+ AutoTArray<UniChar, 255> buffer;
+ CFIndex appNameLength = ::CFStringGetLength(cfAppName);
+ buffer.SetLength(appNameLength);
+ ::CFStringGetCharacters(cfAppName, CFRangeMake(0, appNameLength),
+ buffer.Elements());
+ nsAutoString appName;
+ appName.Assign(reinterpret_cast<char16_t*>(buffer.Elements()), appNameLength);
+ mimeInfoMac->SetDefaultDescription(appName);
+ ::CFRelease(cfAppName);
+ }
+
+ mimeInfoMac->SetDefaultApplication(app);
+ mimeInfoMac->SetPreferredAction(nsIMIMEInfo::useSystemDefault);
+ } else {
+ mimeInfoMac->SetPreferredAction(nsIMIMEInfo::saveToDisk);
+ }
+
+ nsAutoCString mimeType;
+ mimeInfoMac->GetMIMEType(mimeType);
+ if (*aFound && !mimeType.IsEmpty()) {
+ // If we have a MIME type, make sure its preferred extension is included
+ // in our list.
+ NSURLFileTypeMappings *map = [NSURLFileTypeMappings sharedMappings];
+ NSString *typeStr = [NSString stringWithCString:mimeType.get() encoding:NSASCIIStringEncoding];
+ NSString *extStr = map ? [map preferredExtensionForMIMEType:typeStr] : NULL;
+ if (extStr) {
+ nsAutoCString preferredExt;
+ preferredExt.Assign((char *)[extStr cStringUsingEncoding:NSASCIIStringEncoding]);
+ mimeInfoMac->AppendExtension(preferredExt);
+ }
+
+ CFStringRef cfType = ::CFStringCreateWithCString(NULL, mimeType.get(), kCFStringEncodingUTF8);
+ if (cfType) {
+ CFStringRef cfTypeDesc = NULL;
+ if (::LSCopyKindStringForMIMEType(cfType, &cfTypeDesc) == noErr) {
+ AutoTArray<UniChar, 255> buffer;
+ CFIndex typeDescLength = ::CFStringGetLength(cfTypeDesc);
+ buffer.SetLength(typeDescLength);
+ ::CFStringGetCharacters(cfTypeDesc, CFRangeMake(0, typeDescLength),
+ buffer.Elements());
+ nsAutoString typeDesc;
+ typeDesc.Assign(reinterpret_cast<char16_t*>(buffer.Elements()), typeDescLength);
+ mimeInfoMac->SetDescription(typeDesc);
+ }
+ if (cfTypeDesc) {
+ ::CFRelease(cfTypeDesc);
+ }
+ ::CFRelease(cfType);
+ }
+ }
+
+ MOZ_LOG(mLog, LogLevel::Debug, ("OS gave us: type '%s' found '%i'\n", mimeType.get(), *aFound));
+
+ [localPool release];
+ return mimeInfoMac.forget();
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSNULL;
+}
+
+NS_IMETHODIMP
+nsOSHelperAppService::GetProtocolHandlerInfoFromOS(const nsACString &aScheme,
+ bool *found,
+ nsIHandlerInfo **_retval)
+{
+ NS_ASSERTION(!aScheme.IsEmpty(), "No scheme was specified!");
+
+ nsresult rv = OSProtocolHandlerExists(nsPromiseFlatCString(aScheme).get(),
+ found);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsMIMEInfoMac *handlerInfo =
+ new nsMIMEInfoMac(aScheme, nsMIMEInfoBase::eProtocolInfo);
+ NS_ENSURE_TRUE(handlerInfo, NS_ERROR_OUT_OF_MEMORY);
+ NS_ADDREF(*_retval = handlerInfo);
+
+ if (!*found) {
+ // Code that calls this requires an object regardless if the OS has
+ // something for us, so we return the empty object.
+ return NS_OK;
+ }
+
+ nsAutoString desc;
+ rv = GetApplicationDescription(aScheme, desc);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "GetApplicationDescription failed");
+ handlerInfo->SetDefaultDescription(desc);
+
+ return NS_OK;
+}
+
diff --git a/uriloader/exthandler/moz.build b/uriloader/exthandler/moz.build
new file mode 100644
index 0000000000..6a3ca08af6
--- /dev/null
+++ b/uriloader/exthandler/moz.build
@@ -0,0 +1,139 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+TEST_DIRS += ['tests']
+
+XPIDL_SOURCES += [
+ 'nsCExternalHandlerService.idl',
+ 'nsIContentDispatchChooser.idl',
+ 'nsIExternalHelperAppService.idl',
+ 'nsIExternalProtocolService.idl',
+ 'nsIExternalSharingAppService.idl',
+ 'nsIExternalURLHandlerService.idl',
+ 'nsIHandlerService.idl',
+ 'nsIHelperAppLauncherDialog.idl',
+]
+
+XPIDL_MODULE = 'exthandler'
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
+ osdir = 'win'
+ LOCAL_INCLUDES += ['win']
+elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ osdir = 'mac'
+elif CONFIG['MOZ_WIDGET_TOOLKIT'] in ('android', 'gonk', 'uikit'):
+ osdir = CONFIG['MOZ_WIDGET_TOOLKIT']
+else:
+ osdir = 'unix'
+
+EXPORTS += [
+ osdir + '/nsOSHelperAppService.h'
+]
+
+EXPORTS += [
+ 'ContentHandlerService.h',
+ 'nsExternalHelperAppService.h',
+]
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android':
+ EXPORTS += [ '%s/%s' % (osdir, f) for f in [
+ 'nsExternalSharingAppService.h',
+ 'nsExternalURLHandlerService.h',
+ ]]
+
+EXPORTS.mozilla.dom += [
+ 'ExternalHelperAppChild.h',
+ 'ExternalHelperAppParent.h',
+]
+
+UNIFIED_SOURCES += [
+ 'ContentHandlerService.cpp',
+ 'ExternalHelperAppChild.cpp',
+ 'ExternalHelperAppParent.cpp',
+ 'HandlerServiceParent.cpp',
+ 'nsExternalHelperAppService.cpp',
+ 'nsExternalProtocolHandler.cpp',
+ 'nsLocalHandlerApp.cpp',
+ 'nsMIMEInfoImpl.cpp',
+]
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ UNIFIED_SOURCES += [
+ 'mac/nsLocalHandlerAppMac.mm',
+ 'mac/nsMIMEInfoMac.mm',
+ 'mac/nsOSHelperAppService.mm',
+ ]
+elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'uikit':
+ UNIFIED_SOURCES += [
+ 'uikit/nsLocalHandlerAppUIKit.mm',
+ 'uikit/nsMIMEInfoUIKit.mm',
+ 'uikit/nsOSHelperAppService.mm',
+ ]
+else:
+ # These files can't be built in unified mode because they redefine LOG.
+ SOURCES += [
+ osdir + '/nsOSHelperAppService.cpp',
+ ]
+ if CONFIG['GNU_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow']
+
+if 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT']:
+ UNIFIED_SOURCES += [
+ 'unix/nsGNOMERegistry.cpp',
+ 'unix/nsMIMEInfoUnix.cpp',
+ ]
+elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android':
+ UNIFIED_SOURCES += [
+ 'android/nsAndroidHandlerApp.cpp',
+ 'android/nsExternalSharingAppService.cpp',
+ 'android/nsExternalURLHandlerService.cpp',
+ 'android/nsMIMEInfoAndroid.cpp',
+ ]
+elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
+ UNIFIED_SOURCES += [
+ 'win/nsMIMEInfoWin.cpp',
+ ]
+
+if CONFIG['MOZ_ENABLE_DBUS']:
+ UNIFIED_SOURCES += [
+ 'nsDBusHandlerApp.cpp',
+ ]
+
+if CONFIG['MOZ_ENABLE_CONTENTACTION']:
+ UNIFIED_SOURCES += [
+ 'nsContentHandlerApp.cpp',
+ ]
+
+EXTRA_COMPONENTS += [
+ 'nsHandlerService.js',
+ 'nsHandlerService.manifest',
+ 'nsWebHandlerApp.js',
+ 'nsWebHandlerApp.manifest',
+]
+
+IPDL_SOURCES += [
+ 'PExternalHelperApp.ipdl',
+ 'PHandlerService.ipdl',
+]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
+
+LOCAL_INCLUDES += [
+ '/dom/base',
+ '/dom/ipc',
+ '/netwerk/base',
+ '/netwerk/protocol/http',
+]
+
+if CONFIG['MOZ_ENABLE_DBUS']:
+ CXXFLAGS += CONFIG['TK_CFLAGS']
+ CXXFLAGS += CONFIG['MOZ_DBUS_CFLAGS']
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('gtk2', 'gtk3'):
+ CXXFLAGS += CONFIG['TK_CFLAGS']
+ CXXFLAGS += CONFIG['MOZ_DBUS_GLIB_CFLAGS']
diff --git a/uriloader/exthandler/nsCExternalHandlerService.idl b/uriloader/exthandler/nsCExternalHandlerService.idl
new file mode 100644
index 0000000000..6aa25a01db
--- /dev/null
+++ b/uriloader/exthandler/nsCExternalHandlerService.idl
@@ -0,0 +1,58 @@
+/* -*- Mode: IDL; tab-width: 3; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIExternalHelperAppService.idl"
+
+/*
+nsCExternalHelperApp implements:
+-------------------------
+nsIExternalHelperAppService
+*/
+
+%{ C++
+
+/* A7F800E0-4306-11d4-98D0-001083010E9B */
+#define NS_EXTERNALHELPERAPPSERVICE_CID \
+ { 0xa7f800e0, 0x4306, 0x11d4, { 0x98, 0xd0, 0x0, 0x10, 0x83, 0x1, 0xe, 0x9b } }
+
+#define NS_EXTERNALHELPERAPPSERVICE_CONTRACTID \
+"@mozilla.org/uriloader/external-helper-app-service;1"
+
+#define NS_HANDLERSERVICE_CONTRACTID \
+"@mozilla.org/uriloader/handler-service;1"
+
+#define NS_EXTERNALPROTOCOLSERVICE_CONTRACTID \
+"@mozilla.org/uriloader/external-protocol-service;1"
+
+#define NS_MIMESERVICE_CONTRACTID \
+"@mozilla.org/mime;1"
+
+#define NS_EXTERNALPROTOCOLHANDLER_CID \
+{ 0xbd6390c8, 0xfbea, 0x11d4, {0x98, 0xf6, 0x0, 0x10, 0x83, 0x1, 0xe, 0x9b } }
+
+/* 9fa83ce7-d0ab-4ed3-938e-afafee435670 */
+#define NS_BLOCKEDEXTERNALPROTOCOLHANDLER_CID \
+{ 0x9fa83ce7, 0xd0ab, 0x4ed3, {0x93, 0x8e, 0xaf, 0xaf, 0xee, 0x43, 0x56, 0x70 } }
+
+/* bc0017e3-2438-47be-a567-41db58f17627 */
+#define NS_LOCALHANDLERAPP_CID \
+{ 0xbc0017e3, 0x2438, 0x47be, {0xa5, 0x67, 0x41, 0xdb, 0x58, 0xf1, 0x76, 0x27 } }
+
+/*6c3c274b-4cbf-4bb5-a635-05ad2cbb6535*/
+#define NS_DBUSHANDLERAPP_CID \
+{ 0x6c3c274b, 0x4cbf, 0x4bb5, {0xa6, 0x35, 0x05, 0xad, 0x2c, 0xbb, 0x65, 0x35 } }
+
+#define NS_DBUSHANDLERAPP_CONTRACTID \
+"@mozilla.org/uriloader/dbus-handler-app;1"
+
+#define NS_LOCALHANDLERAPP_CONTRACTID \
+"@mozilla.org/uriloader/local-handler-app;1"
+
+#define NS_WEBHANDLERAPP_CONTRACTID \
+"@mozilla.org/uriloader/web-handler-app;1"
+
+%}
+
diff --git a/uriloader/exthandler/nsContentHandlerApp.cpp b/uriloader/exthandler/nsContentHandlerApp.cpp
new file mode 100644
index 0000000000..ce3f7b90f5
--- /dev/null
+++ b/uriloader/exthandler/nsContentHandlerApp.cpp
@@ -0,0 +1,79 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim:expandtab:shiftwidth=2:tabstop=2:cin:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsContentHandlerApp.h"
+#include "nsIURI.h"
+#include "nsIClassInfoImpl.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+
+#define NS_CONTENTHANDLER_CID \
+{ 0x43ec2c82, 0xb9db, 0x4835, {0x80, 0x3f, 0x64, 0xc9, 0x72, 0x5a, 0x70, 0x28 } }
+
+NS_IMPL_CLASSINFO(nsContentHandlerApp, nullptr, 0, NS_CONTENTHANDLER_CID)
+NS_IMPL_ISUPPORTS_CI(nsContentHandlerApp, nsIHandlerApp)
+
+nsContentHandlerApp::nsContentHandlerApp(nsString aName, nsCString aType,
+ ContentAction::Action& aAction) :
+ mName(aName),
+ mType(aType),
+ mAction(aAction)
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsIHandlerInfo
+
+NS_IMETHODIMP nsContentHandlerApp::GetName(nsAString& aName)
+{
+ aName.Assign(mName);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsContentHandlerApp::SetName(const nsAString& aName)
+{
+ mName.Assign(aName);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsContentHandlerApp::Equals(nsIHandlerApp *aHandlerApp, bool *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsContentHandlerApp::GetDetailedDescription(nsAString& aDetailedDescription)
+{
+ aDetailedDescription.Assign(mDetailedDescription);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsContentHandlerApp::SetDetailedDescription(const nsAString& aDetailedDescription)
+{
+ mDetailedDescription.Assign(aDetailedDescription);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsContentHandlerApp::LaunchWithURI(nsIURI *aURI,
+ nsIInterfaceRequestor *aWindowContext)
+{
+ nsAutoCString spec;
+ nsresult rv = aURI->GetAsciiSpec(spec);
+ NS_ENSURE_SUCCESS(rv,rv);
+ const char* url = spec.get();
+
+ QList<ContentAction::Action> actions =
+ ContentAction::Action::actionsForFile(QUrl(url), QString(mType.get()));
+ for (int i = 0; i < actions.size(); ++i) {
+ if (actions[i].name() == QString((QChar*)mName.get(), mName.Length())) {
+ actions[i].trigger();
+ break;
+ }
+ }
+
+ return NS_OK;
+}
+
diff --git a/uriloader/exthandler/nsContentHandlerApp.h b/uriloader/exthandler/nsContentHandlerApp.h
new file mode 100644
index 0000000000..8ace9a2cd4
--- /dev/null
+++ b/uriloader/exthandler/nsContentHandlerApp.h
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim:expandtab:shiftwidth=2:tabstop=2:cin:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __nsContentHandlerAppImpl_h__
+#define __nsContentHandlerAppImpl_h__
+
+#include <contentaction/contentaction.h>
+#include "nsString.h"
+#include "nsIMIMEInfo.h"
+
+class nsContentHandlerApp : public nsIHandlerApp
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIHANDLERAPP
+
+ nsContentHandlerApp(nsString aName, nsCString aType, ContentAction::Action& aAction);
+ virtual ~nsContentHandlerApp() { }
+
+protected:
+ nsString mName;
+ nsCString mType;
+ nsString mDetailedDescription;
+
+ ContentAction::Action mAction;
+};
+#endif
diff --git a/uriloader/exthandler/nsDBusHandlerApp.cpp b/uriloader/exthandler/nsDBusHandlerApp.cpp
new file mode 100644
index 0000000000..bed2ecd989
--- /dev/null
+++ b/uriloader/exthandler/nsDBusHandlerApp.cpp
@@ -0,0 +1,177 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim:expandtab:shiftwidth=2:tabstop=2:cin:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <dbus/dbus.h>
+#include "mozilla/ipc/DBusConnectionDelete.h"
+#include "mozilla/ipc/DBusMessageRefPtr.h"
+#include "nsDBusHandlerApp.h"
+#include "nsIURI.h"
+#include "nsIClassInfoImpl.h"
+#include "nsCOMPtr.h"
+#include "nsCExternalHandlerService.h"
+
+// XXX why does nsMIMEInfoImpl have a threadsafe nsISupports? do we need one
+// here too?
+NS_IMPL_CLASSINFO(nsDBusHandlerApp, nullptr, 0, NS_DBUSHANDLERAPP_CID)
+NS_IMPL_ISUPPORTS_CI(nsDBusHandlerApp, nsIDBusHandlerApp, nsIHandlerApp)
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsIHandlerApp
+
+NS_IMETHODIMP nsDBusHandlerApp::GetName(nsAString& aName)
+{
+ aName.Assign(mName);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDBusHandlerApp::SetName(const nsAString & aName)
+{
+ mName.Assign(aName);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDBusHandlerApp::SetDetailedDescription(const nsAString & aDescription)
+{
+ mDetailedDescription.Assign(aDescription);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDBusHandlerApp::GetDetailedDescription(nsAString& aDescription)
+{
+ aDescription.Assign(mDetailedDescription);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDBusHandlerApp::Equals(nsIHandlerApp *aHandlerApp, bool *_retval)
+{
+ NS_ENSURE_ARG_POINTER(aHandlerApp);
+
+ // If the handler app isn't a dbus handler app, then it's not the same app.
+ nsCOMPtr<nsIDBusHandlerApp> dbusHandlerApp = do_QueryInterface(aHandlerApp);
+ if (!dbusHandlerApp) {
+ *_retval = false;
+ return NS_OK;
+ }
+ nsAutoCString service;
+ nsAutoCString method;
+
+ nsresult rv = dbusHandlerApp->GetService(service);
+ if (NS_FAILED(rv)) {
+ *_retval = false;
+ return NS_OK;
+ }
+ rv = dbusHandlerApp->GetMethod(method);
+ if (NS_FAILED(rv)) {
+ *_retval = false;
+ return NS_OK;
+ }
+
+ *_retval = service.Equals(mService) && method.Equals(mMethod);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDBusHandlerApp::LaunchWithURI(nsIURI *aURI,
+ nsIInterfaceRequestor *aWindowContext)
+{
+ nsAutoCString spec;
+ nsresult rv = aURI->GetAsciiSpec(spec);
+ NS_ENSURE_SUCCESS(rv,rv);
+ const char* uri = spec.get();
+
+ DBusError err;
+ dbus_error_init(&err);
+
+ mozilla::UniquePtr<DBusConnection, mozilla::DBusConnectionDelete>
+ connection(dbus_bus_get_private(DBUS_BUS_SESSION, &err));
+
+ if (dbus_error_is_set(&err)) {
+ dbus_error_free(&err);
+ return NS_ERROR_FAILURE;
+ }
+ if (nullptr == connection) {
+ return NS_ERROR_FAILURE;
+ }
+ dbus_connection_set_exit_on_disconnect(connection.get(),false);
+
+ RefPtr<DBusMessage> msg = already_AddRefed<DBusMessage>(
+ dbus_message_new_method_call(mService.get(),
+ mObjpath.get(),
+ mInterface.get(),
+ mMethod.get()));
+
+ if (!msg) {
+ return NS_ERROR_FAILURE;
+ }
+ dbus_message_set_no_reply(msg, true);
+
+ DBusMessageIter iter;
+ dbus_message_iter_init_append(msg, &iter);
+ dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &uri);
+
+ if (dbus_connection_send(connection.get(), msg, nullptr)) {
+ dbus_connection_flush(connection.get());
+ } else {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsIDBusHandlerApp
+
+NS_IMETHODIMP nsDBusHandlerApp::GetService(nsACString & aService)
+{
+ aService.Assign(mService);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDBusHandlerApp::SetService(const nsACString & aService)
+{
+ mService.Assign(aService);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDBusHandlerApp::GetMethod(nsACString & aMethod)
+{
+ aMethod.Assign(mMethod);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDBusHandlerApp::SetMethod(const nsACString & aMethod)
+{
+ mMethod.Assign(aMethod);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDBusHandlerApp::GetDBusInterface(nsACString & aInterface)
+{
+ aInterface.Assign(mInterface);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDBusHandlerApp::SetDBusInterface(const nsACString & aInterface)
+{
+ mInterface.Assign(aInterface);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDBusHandlerApp::GetObjectPath(nsACString & aObjpath)
+{
+ aObjpath.Assign(mObjpath);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDBusHandlerApp::SetObjectPath(const nsACString & aObjpath)
+{
+ mObjpath.Assign(aObjpath);
+ return NS_OK;
+}
+
+
diff --git a/uriloader/exthandler/nsDBusHandlerApp.h b/uriloader/exthandler/nsDBusHandlerApp.h
new file mode 100644
index 0000000000..a2835d98f2
--- /dev/null
+++ b/uriloader/exthandler/nsDBusHandlerApp.h
@@ -0,0 +1,33 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim:expandtab:shiftwidth=2:tabstop=2:cin:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __nsDBusHandlerAppImpl_h__
+#define __nsDBusHandlerAppImpl_h__
+
+#include "nsString.h"
+#include "nsIMIMEInfo.h"
+
+class nsDBusHandlerApp : public nsIDBusHandlerApp
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIHANDLERAPP
+ NS_DECL_NSIDBUSHANDLERAPP
+
+ nsDBusHandlerApp() { }
+
+protected:
+ virtual ~nsDBusHandlerApp() { }
+
+ nsString mName;
+ nsString mDetailedDescription;
+ nsCString mService;
+ nsCString mMethod;
+ nsCString mInterface;
+ nsCString mObjpath;
+
+};
+#endif
diff --git a/uriloader/exthandler/nsExternalHelperAppService.cpp b/uriloader/exthandler/nsExternalHelperAppService.cpp
new file mode 100644
index 0000000000..51a7ee0f6a
--- /dev/null
+++ b/uriloader/exthandler/nsExternalHelperAppService.cpp
@@ -0,0 +1,2926 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim:expandtab:shiftwidth=2:tabstop=2:cin:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "base/basictypes.h"
+
+/* This must occur *after* base/basictypes.h to avoid typedefs conflicts. */
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Base64.h"
+
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/TabChild.h"
+#include "nsXULAppAPI.h"
+
+#include "nsExternalHelperAppService.h"
+#include "nsCExternalHandlerService.h"
+#include "nsIURI.h"
+#include "nsIURL.h"
+#include "nsIFile.h"
+#include "nsIFileURL.h"
+#include "nsIChannel.h"
+#include "nsIDirectoryService.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsICategoryManager.h"
+#include "nsDependentSubstring.h"
+#include "nsXPIDLString.h"
+#include "nsUnicharUtils.h"
+#include "nsIStringEnumerator.h"
+#include "nsMemory.h"
+#include "nsIStreamListener.h"
+#include "nsIMIMEService.h"
+#include "nsILoadGroup.h"
+#include "nsIWebProgressListener.h"
+#include "nsITransfer.h"
+#include "nsReadableUtils.h"
+#include "nsIRequest.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsThreadUtils.h"
+#include "nsAutoPtr.h"
+#include "nsIMutableArray.h"
+
+// used to access our datastore of user-configured helper applications
+#include "nsIHandlerService.h"
+#include "nsIMIMEInfo.h"
+#include "nsIRefreshURI.h" // XXX needed to redirect according to Refresh: URI
+#include "nsIDocumentLoader.h" // XXX needed to get orig. channel and assoc. refresh uri
+#include "nsIHelperAppLauncherDialog.h"
+#include "nsIContentDispatchChooser.h"
+#include "nsNetUtil.h"
+#include "nsIPrivateBrowsingChannel.h"
+#include "nsIIOService.h"
+#include "nsNetCID.h"
+
+#include "nsMimeTypes.h"
+// used for header disposition information.
+#include "nsIHttpChannel.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIEncodedChannel.h"
+#include "nsIMultiPartChannel.h"
+#include "nsIFileChannel.h"
+#include "nsIObserverService.h" // so we can be a profile change observer
+#include "nsIPropertyBag2.h" // for the 64-bit content length
+
+#ifdef XP_MACOSX
+#include "nsILocalFileMac.h"
+#endif
+
+#include "nsIPluginHost.h" // XXX needed for ext->type mapping (bug 233289)
+#include "nsPluginHost.h"
+#include "nsEscape.h"
+
+#include "nsIStringBundle.h" // XXX needed to localize error msgs
+#include "nsIPrompt.h"
+
+#include "nsITextToSubURI.h" // to unescape the filename
+#include "nsIMIMEHeaderParam.h"
+
+#include "nsIWindowWatcher.h"
+
+#include "nsIDownloadHistory.h" // to mark downloads as visited
+#include "nsDocShellCID.h"
+
+#include "nsCRT.h"
+#include "nsLocalHandlerApp.h"
+
+#include "nsIRandomGenerator.h"
+
+#include "ContentChild.h"
+#include "nsXULAppAPI.h"
+#include "nsPIDOMWindow.h"
+#include "nsIDocShellTreeOwner.h"
+#include "nsIDocShellTreeItem.h"
+#include "ExternalHelperAppChild.h"
+
+#ifdef XP_WIN
+#include "nsWindowsHelpers.h"
+#endif
+
+#ifdef MOZ_WIDGET_ANDROID
+#include "FennecJNIWrappers.h"
+#endif
+
+#include "mozilla/Preferences.h"
+#include "mozilla/ipc/URIUtils.h"
+
+using namespace mozilla;
+using namespace mozilla::ipc;
+
+// Download Folder location constants
+#define NS_PREF_DOWNLOAD_DIR "browser.download.dir"
+#define NS_PREF_DOWNLOAD_FOLDERLIST "browser.download.folderList"
+enum {
+ NS_FOLDER_VALUE_DESKTOP = 0
+, NS_FOLDER_VALUE_DOWNLOADS = 1
+, NS_FOLDER_VALUE_CUSTOM = 2
+};
+
+LazyLogModule nsExternalHelperAppService::mLog("HelperAppService");
+
+// Using level 3 here because the OSHelperAppServices use a log level
+// of LogLevel::Debug (4), and we want less detailed output here
+// Using 3 instead of LogLevel::Warning because we don't output warnings
+#undef LOG
+#define LOG(args) MOZ_LOG(nsExternalHelperAppService::mLog, mozilla::LogLevel::Info, args)
+#define LOG_ENABLED() MOZ_LOG_TEST(nsExternalHelperAppService::mLog, mozilla::LogLevel::Info)
+
+static const char NEVER_ASK_FOR_SAVE_TO_DISK_PREF[] =
+ "browser.helperApps.neverAsk.saveToDisk";
+static const char NEVER_ASK_FOR_OPEN_FILE_PREF[] =
+ "browser.helperApps.neverAsk.openFile";
+
+// Helper functions for Content-Disposition headers
+
+/**
+ * Given a URI fragment, unescape it
+ * @param aFragment The string to unescape
+ * @param aURI The URI from which this fragment is taken. Only its character set
+ * will be used.
+ * @param aResult [out] Unescaped string.
+ */
+static nsresult UnescapeFragment(const nsACString& aFragment, nsIURI* aURI,
+ nsAString& aResult)
+{
+ // First, we need a charset
+ nsAutoCString originCharset;
+ nsresult rv = aURI->GetOriginCharset(originCharset);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Now, we need the unescaper
+ nsCOMPtr<nsITextToSubURI> textToSubURI = do_GetService(NS_ITEXTTOSUBURI_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return textToSubURI->UnEscapeURIForUI(originCharset, aFragment, aResult);
+}
+
+/**
+ * UTF-8 version of UnescapeFragment.
+ * @param aFragment The string to unescape
+ * @param aURI The URI from which this fragment is taken. Only its character set
+ * will be used.
+ * @param aResult [out] Unescaped string, UTF-8 encoded.
+ * @note It is safe to pass the same string for aFragment and aResult.
+ * @note When this function fails, aResult will not be modified.
+ */
+static nsresult UnescapeFragment(const nsACString& aFragment, nsIURI* aURI,
+ nsACString& aResult)
+{
+ nsAutoString result;
+ nsresult rv = UnescapeFragment(aFragment, aURI, result);
+ if (NS_SUCCEEDED(rv))
+ CopyUTF16toUTF8(result, aResult);
+ return rv;
+}
+
+/**
+ * Given a channel, returns the filename and extension the channel has.
+ * This uses the URL and other sources (nsIMultiPartChannel).
+ * Also gives back whether the channel requested external handling (i.e.
+ * whether Content-Disposition: attachment was sent)
+ * @param aChannel The channel to extract the filename/extension from
+ * @param aFileName [out] Reference to the string where the filename should be
+ * stored. Empty if it could not be retrieved.
+ * WARNING - this filename may contain characters which the OS does not
+ * allow as part of filenames!
+ * @param aExtension [out] Reference to the string where the extension should
+ * be stored. Empty if it could not be retrieved. Stored in UTF-8.
+ * @param aAllowURLExtension (optional) Get the extension from the URL if no
+ * Content-Disposition header is present. Default is true.
+ * @retval true The server sent Content-Disposition:attachment or equivalent
+ * @retval false Content-Disposition: inline or no content-disposition header
+ * was sent.
+ */
+static bool GetFilenameAndExtensionFromChannel(nsIChannel* aChannel,
+ nsString& aFileName,
+ nsCString& aExtension,
+ bool aAllowURLExtension = true)
+{
+ aExtension.Truncate();
+ /*
+ * If the channel is an http or part of a multipart channel and we
+ * have a content disposition header set, then use the file name
+ * suggested there as the preferred file name to SUGGEST to the
+ * user. we shouldn't actually use that without their
+ * permission... otherwise just use our temp file
+ */
+ bool handleExternally = false;
+ uint32_t disp;
+ nsresult rv = aChannel->GetContentDisposition(&disp);
+ if (NS_SUCCEEDED(rv))
+ {
+ aChannel->GetContentDispositionFilename(aFileName);
+ if (disp == nsIChannel::DISPOSITION_ATTACHMENT)
+ handleExternally = true;
+ }
+
+ // If the disposition header didn't work, try the filename from nsIURL
+ nsCOMPtr<nsIURI> uri;
+ aChannel->GetURI(getter_AddRefs(uri));
+ nsCOMPtr<nsIURL> url(do_QueryInterface(uri));
+ if (url && aFileName.IsEmpty())
+ {
+ if (aAllowURLExtension) {
+ url->GetFileExtension(aExtension);
+ UnescapeFragment(aExtension, url, aExtension);
+
+ // Windows ignores terminating dots. So we have to as well, so
+ // that our security checks do "the right thing"
+ // In case the aExtension consisted only of the dot, the code below will
+ // extract an aExtension from the filename
+ aExtension.Trim(".", false);
+ }
+
+ // try to extract the file name from the url and use that as a first pass as the
+ // leaf name of our temp file...
+ nsAutoCString leafName;
+ url->GetFileName(leafName);
+ if (!leafName.IsEmpty())
+ {
+ rv = UnescapeFragment(leafName, url, aFileName);
+ if (NS_FAILED(rv))
+ {
+ CopyUTF8toUTF16(leafName, aFileName); // use escaped name
+ }
+ }
+ }
+
+ // Extract Extension, if we have a filename; otherwise,
+ // truncate the string
+ if (aExtension.IsEmpty()) {
+ if (!aFileName.IsEmpty())
+ {
+ // Windows ignores terminating dots. So we have to as well, so
+ // that our security checks do "the right thing"
+ aFileName.Trim(".", false);
+
+ // XXX RFindCharInReadable!!
+ nsAutoString fileNameStr(aFileName);
+ int32_t idx = fileNameStr.RFindChar(char16_t('.'));
+ if (idx != kNotFound)
+ CopyUTF16toUTF8(StringTail(fileNameStr, fileNameStr.Length() - idx - 1), aExtension);
+ }
+ }
+
+
+ return handleExternally;
+}
+
+/**
+ * Obtains the directory to use. This tends to vary per platform, and
+ * needs to be consistent throughout our codepaths. For platforms where
+ * helper apps use the downloads directory, this should be kept in
+ * sync with nsDownloadManager.cpp
+ *
+ * Optionally skip availability of the directory and storage.
+ */
+static nsresult GetDownloadDirectory(nsIFile **_directory,
+ bool aSkipChecks = false)
+{
+ nsCOMPtr<nsIFile> dir;
+#ifdef XP_MACOSX
+ // On OS X, we first try to get the users download location, if it's set.
+ switch (Preferences::GetInt(NS_PREF_DOWNLOAD_FOLDERLIST, -1)) {
+ case NS_FOLDER_VALUE_DESKTOP:
+ (void) NS_GetSpecialDirectory(NS_OS_DESKTOP_DIR, getter_AddRefs(dir));
+ break;
+ case NS_FOLDER_VALUE_CUSTOM:
+ {
+ Preferences::GetComplex(NS_PREF_DOWNLOAD_DIR,
+ NS_GET_IID(nsIFile),
+ getter_AddRefs(dir));
+ if (!dir) break;
+
+ // If we're not checking for availability we're done.
+ if (aSkipChecks) {
+ dir.forget(_directory);
+ return NS_OK;
+ }
+
+ // We have the directory, and now we need to make sure it exists
+ bool dirExists = false;
+ (void) dir->Exists(&dirExists);
+ if (dirExists) break;
+
+ nsresult rv = dir->Create(nsIFile::DIRECTORY_TYPE, 0755);
+ if (NS_FAILED(rv)) {
+ dir = nullptr;
+ break;
+ }
+ }
+ break;
+ case NS_FOLDER_VALUE_DOWNLOADS:
+ // This is just the OS default location, so fall out
+ break;
+ }
+
+ if (!dir) {
+ // If not, we default to the OS X default download location.
+ nsresult rv = NS_GetSpecialDirectory(NS_OSX_DEFAULT_DOWNLOAD_DIR,
+ getter_AddRefs(dir));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+#elif defined(ANDROID)
+ // We ask Java for the temporary download directory. The directory will be
+ // different depending on whether we have the permission to write to the
+ // public download directory or not.
+ // In the case where we do not have the permission we will start the
+ // download to the app cache directory and later move it to the final
+ // destination after prompting for the permission.
+ jni::String::LocalRef downloadDir;
+ if (jni::IsFennec()) {
+ downloadDir = java::DownloadsIntegration::GetTemporaryDownloadDirectory();
+ }
+
+ nsresult rv;
+ if (downloadDir) {
+ nsCOMPtr<nsIFile> ldir;
+ rv = NS_NewNativeLocalFile(downloadDir->ToCString(),
+ true, getter_AddRefs(ldir));
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ dir = do_QueryInterface(ldir);
+
+ // If we're not checking for availability we're done.
+ if (aSkipChecks) {
+ dir.forget(_directory);
+ return NS_OK;
+ }
+ }
+ else {
+ return NS_ERROR_FAILURE;
+ }
+#else
+ // On all other platforms, we default to the systems temporary directory.
+ nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(dir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+#if defined(XP_UNIX)
+ // Ensuring that only the current user can read the file names we end up
+ // creating. Note that Creating directories with specified permission only
+ // supported on Unix platform right now. That's why above if exists.
+
+ uint32_t permissions;
+ rv = dir->GetPermissions(&permissions);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (permissions != PR_IRWXU) {
+ const char* userName = PR_GetEnv("USERNAME");
+ if (!userName || !*userName) {
+ userName = PR_GetEnv("USER");
+ }
+ if (!userName || !*userName) {
+ userName = PR_GetEnv("LOGNAME");
+ }
+ if (!userName || !*userName) {
+ userName = "mozillaUser";
+ }
+
+ nsAutoString userDir;
+ userDir.AssignLiteral("mozilla_");
+ userDir.AppendASCII(userName);
+ userDir.ReplaceChar(FILE_PATH_SEPARATOR FILE_ILLEGAL_CHARACTERS, '_');
+
+ int counter = 0;
+ bool pathExists;
+ nsCOMPtr<nsIFile> finalPath;
+
+ while (true) {
+ nsAutoString countedUserDir(userDir);
+ countedUserDir.AppendInt(counter, 10);
+ dir->Clone(getter_AddRefs(finalPath));
+ finalPath->Append(countedUserDir);
+
+ rv = finalPath->Exists(&pathExists);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (pathExists) {
+ // If this path has the right permissions, use it.
+ rv = finalPath->GetPermissions(&permissions);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Ensuring the path is writable by the current user.
+ bool isWritable;
+ rv = finalPath->IsWritable(&isWritable);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (permissions == PR_IRWXU && isWritable) {
+ dir = finalPath;
+ break;
+ }
+ }
+
+ rv = finalPath->Create(nsIFile::DIRECTORY_TYPE, PR_IRWXU);
+ if (NS_SUCCEEDED(rv)) {
+ dir = finalPath;
+ break;
+ }
+ else if (rv != NS_ERROR_FILE_ALREADY_EXISTS) {
+ // Unexpected error.
+ return rv;
+ }
+
+ counter++;
+ }
+ }
+
+#endif
+#endif
+
+ NS_ASSERTION(dir, "Somehow we didn't get a download directory!");
+ dir.forget(_directory);
+ return NS_OK;
+}
+
+/**
+ * Structure for storing extension->type mappings.
+ * @see defaultMimeEntries
+ */
+struct nsDefaultMimeTypeEntry {
+ const char* mMimeType;
+ const char* mFileExtension;
+};
+
+/**
+ * Default extension->mimetype mappings. These are not overridable.
+ * If you add types here, make sure they are lowercase, or you'll regret it.
+ */
+static const nsDefaultMimeTypeEntry defaultMimeEntries[] =
+{
+ // The following are those extensions that we're asked about during startup,
+ // sorted by order used
+ { IMAGE_GIF, "gif" },
+ { TEXT_XML, "xml" },
+ { APPLICATION_RDF, "rdf" },
+ { TEXT_XUL, "xul" },
+ { IMAGE_PNG, "png" },
+ // -- end extensions used during startup
+ { TEXT_CSS, "css" },
+ { IMAGE_JPEG, "jpeg" },
+ { IMAGE_JPEG, "jpg" },
+ { IMAGE_SVG_XML, "svg" },
+ { TEXT_HTML, "html" },
+ { TEXT_HTML, "htm" },
+ { APPLICATION_XPINSTALL, "xpi" },
+ { "application/xhtml+xml", "xhtml" },
+ { "application/xhtml+xml", "xht" },
+ { TEXT_PLAIN, "txt" },
+ { VIDEO_OGG, "ogv" },
+ { VIDEO_OGG, "ogg" },
+ { APPLICATION_OGG, "ogg" },
+ { AUDIO_OGG, "oga" },
+ { AUDIO_OGG, "opus" },
+ { APPLICATION_PDF, "pdf" },
+ { VIDEO_WEBM, "webm" },
+ { AUDIO_WEBM, "webm" },
+#if defined(MOZ_WMF)
+ { VIDEO_MP4, "mp4" },
+ { AUDIO_MP4, "m4a" },
+ { AUDIO_MP3, "mp3" },
+#endif
+#ifdef MOZ_RAW
+ { VIDEO_RAW, "yuv" }
+#endif
+};
+
+/**
+ * This is a small private struct used to help us initialize some
+ * default mime types.
+ */
+struct nsExtraMimeTypeEntry {
+ const char* mMimeType;
+ const char* mFileExtensions;
+ const char* mDescription;
+};
+
+#ifdef XP_MACOSX
+#define MAC_TYPE(x) x
+#else
+#define MAC_TYPE(x) 0
+#endif
+
+/**
+ * This table lists all of the 'extra' content types that we can deduce from particular
+ * file extensions. These entries also ensure that we provide a good descriptive name
+ * when we encounter files with these content types and/or extensions. These can be
+ * overridden by user helper app prefs.
+ * If you add types here, make sure they are lowercase, or you'll regret it.
+ */
+static const nsExtraMimeTypeEntry extraMimeEntries[] =
+{
+#if defined(XP_MACOSX) // don't define .bin on the mac...use internet config to look that up...
+ { APPLICATION_OCTET_STREAM, "exe,com", "Binary File" },
+#else
+ { APPLICATION_OCTET_STREAM, "exe,com,bin", "Binary File" },
+#endif
+ { APPLICATION_GZIP2, "gz", "gzip" },
+ { "application/x-arj", "arj", "ARJ file" },
+ { "application/rtf", "rtf", "Rich Text Format File" },
+ { APPLICATION_XPINSTALL, "xpi", "XPInstall Install" },
+ { APPLICATION_PDF, "pdf", "Portable Document Format" },
+ { APPLICATION_POSTSCRIPT, "ps,eps,ai", "Postscript File" },
+ { APPLICATION_XJAVASCRIPT, "js", "Javascript Source File" },
+ { APPLICATION_XJAVASCRIPT, "jsm", "Javascript Module Source File" },
+#ifdef MOZ_WIDGET_ANDROID
+ { "application/vnd.android.package-archive", "apk", "Android Package" },
+#endif
+ { IMAGE_ART, "art", "ART Image" },
+ { IMAGE_BMP, "bmp", "BMP Image" },
+ { IMAGE_GIF, "gif", "GIF Image" },
+ { IMAGE_ICO, "ico,cur", "ICO Image" },
+ { IMAGE_JPEG, "jpeg,jpg,jfif,pjpeg,pjp", "JPEG Image" },
+ { IMAGE_PNG, "png", "PNG Image" },
+ { IMAGE_APNG, "apng", "APNG Image" },
+ { IMAGE_TIFF, "tiff,tif", "TIFF Image" },
+ { IMAGE_XBM, "xbm", "XBM Image" },
+ { IMAGE_SVG_XML, "svg", "Scalable Vector Graphics" },
+ { MESSAGE_RFC822, "eml", "RFC-822 data" },
+ { TEXT_PLAIN, "txt,text", "Text File" },
+ { TEXT_HTML, "html,htm,shtml,ehtml", "HyperText Markup Language" },
+ { "application/xhtml+xml", "xhtml,xht", "Extensible HyperText Markup Language" },
+ { APPLICATION_MATHML_XML, "mml", "Mathematical Markup Language" },
+ { APPLICATION_RDF, "rdf", "Resource Description Framework" },
+ { TEXT_XUL, "xul", "XML-Based User Interface Language" },
+ { TEXT_XML, "xml,xsl,xbl", "Extensible Markup Language" },
+ { TEXT_CSS, "css", "Style Sheet" },
+ { TEXT_VCARD, "vcf,vcard", "Contact Information" },
+ { VIDEO_OGG, "ogv", "Ogg Video" },
+ { VIDEO_OGG, "ogg", "Ogg Video" },
+ { APPLICATION_OGG, "ogg", "Ogg Video"},
+ { AUDIO_OGG, "oga", "Ogg Audio" },
+ { AUDIO_OGG, "opus", "Opus Audio" },
+#ifdef MOZ_WIDGET_GONK
+ { AUDIO_AMR, "amr", "Adaptive Multi-Rate Audio" },
+ { AUDIO_FLAC, "flac", "FLAC Audio" },
+ { VIDEO_AVI, "avi", "Audio Video Interleave" },
+ { VIDEO_AVI, "divx", "Audio Video Interleave" },
+ { VIDEO_MPEG_TS, "ts", "MPEG Transport Stream" },
+ { VIDEO_MPEG_TS, "m2ts", "MPEG-2 Transport Stream" },
+ { VIDEO_MATROSKA, "mkv", "MATROSKA VIDEO" },
+ { AUDIO_MATROSKA, "mka", "MATROSKA AUDIO" },
+#endif
+ { VIDEO_WEBM, "webm", "Web Media Video" },
+ { AUDIO_WEBM, "webm", "Web Media Audio" },
+ { AUDIO_MP3, "mp3", "MPEG Audio" },
+ { VIDEO_MP4, "mp4", "MPEG-4 Video" },
+ { AUDIO_MP4, "m4a", "MPEG-4 Audio" },
+ { VIDEO_RAW, "yuv", "Raw YUV Video" },
+ { AUDIO_WAV, "wav", "Waveform Audio" },
+ { VIDEO_3GPP, "3gpp,3gp", "3GPP Video" },
+ { VIDEO_3GPP2,"3g2", "3GPP2 Video" },
+#ifdef MOZ_WIDGET_GONK
+ // The AUDIO_3GPP has to come after the VIDEO_3GPP entry because the Gallery
+ // app on Firefox OS depends on the "3gp" extension mapping to the
+ // "video/3gpp" MIME type.
+ { AUDIO_3GPP, "3gpp,3gp", "3GPP Audio" },
+ { AUDIO_3GPP2, "3g2", "3GPP2 Audio" },
+#endif
+ { AUDIO_MIDI, "mid", "Standard MIDI Audio" }
+};
+
+#undef MAC_TYPE
+
+/**
+ * File extensions for which decoding should be disabled.
+ * NOTE: These MUST be lower-case and ASCII.
+ */
+static const nsDefaultMimeTypeEntry nonDecodableExtensions[] = {
+ { APPLICATION_GZIP, "gz" },
+ { APPLICATION_GZIP, "tgz" },
+ { APPLICATION_ZIP, "zip" },
+ { APPLICATION_COMPRESS, "z" },
+ { APPLICATION_GZIP, "svgz" }
+};
+
+NS_IMPL_ISUPPORTS(
+ nsExternalHelperAppService,
+ nsIExternalHelperAppService,
+ nsPIExternalAppLauncher,
+ nsIExternalProtocolService,
+ nsIMIMEService,
+ nsIObserver,
+ nsISupportsWeakReference)
+
+nsExternalHelperAppService::nsExternalHelperAppService()
+{
+}
+nsresult nsExternalHelperAppService::Init()
+{
+ // Add an observer for profile change
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (!obs)
+ return NS_ERROR_FAILURE;
+
+ nsresult rv = obs->AddObserver(this, "profile-before-change", true);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return obs->AddObserver(this, "last-pb-context-exited", true);
+}
+
+nsExternalHelperAppService::~nsExternalHelperAppService()
+{
+}
+
+
+nsresult
+nsExternalHelperAppService::DoContentContentProcessHelper(const nsACString& aMimeContentType,
+ nsIRequest *aRequest,
+ nsIInterfaceRequestor *aContentContext,
+ bool aForceSave,
+ nsIInterfaceRequestor *aWindowContext,
+ nsIStreamListener ** aStreamListener)
+{
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_GetInterface(aContentContext);
+ NS_ENSURE_STATE(window);
+
+ // We need to get a hold of a ContentChild so that we can begin forwarding
+ // this data to the parent. In the HTTP case, this is unfortunate, since
+ // we're actually passing data from parent->child->parent wastefully, but
+ // the Right Fix will eventually be to short-circuit those channels on the
+ // parent side based on some sort of subscription concept.
+ using mozilla::dom::ContentChild;
+ using mozilla::dom::ExternalHelperAppChild;
+ ContentChild *child = ContentChild::GetSingleton();
+ if (!child) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCString disp;
+ nsCOMPtr<nsIURI> uri;
+ int64_t contentLength = -1;
+ bool wasFileChannel = false;
+ uint32_t contentDisposition = -1;
+ nsAutoString fileName;
+
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
+ if (channel) {
+ channel->GetURI(getter_AddRefs(uri));
+ channel->GetContentLength(&contentLength);
+ channel->GetContentDisposition(&contentDisposition);
+ channel->GetContentDispositionFilename(fileName);
+ channel->GetContentDispositionHeader(disp);
+
+ nsCOMPtr<nsIFileChannel> fileChan(do_QueryInterface(aRequest));
+ wasFileChannel = fileChan != nullptr;
+ }
+
+
+ nsCOMPtr<nsIURI> referrer;
+ NS_GetReferrerFromChannel(channel, getter_AddRefs(referrer));
+
+ OptionalURIParams uriParams, referrerParams;
+ SerializeURI(uri, uriParams);
+ SerializeURI(referrer, referrerParams);
+
+ // Now we build a protocol for forwarding our data to the parent. The
+ // protocol will act as a listener on the child-side and create a "real"
+ // helperAppService listener on the parent-side, via another call to
+ // DoContent.
+ mozilla::dom::PExternalHelperAppChild *pc =
+ child->SendPExternalHelperAppConstructor(uriParams,
+ nsCString(aMimeContentType),
+ disp, contentDisposition,
+ fileName, aForceSave,
+ contentLength, wasFileChannel,
+ referrerParams,
+ mozilla::dom::TabChild::GetFrom(window));
+ ExternalHelperAppChild *childListener = static_cast<ExternalHelperAppChild *>(pc);
+
+ NS_ADDREF(*aStreamListener = childListener);
+
+ uint32_t reason = nsIHelperAppLauncherDialog::REASON_CANTHANDLE;
+
+ RefPtr<nsExternalAppHandler> handler =
+ new nsExternalAppHandler(nullptr, EmptyCString(), aContentContext, aWindowContext, this,
+ fileName, reason, aForceSave);
+ if (!handler) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ childListener->SetHandler(handler);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsExternalHelperAppService::DoContent(const nsACString& aMimeContentType,
+ nsIRequest *aRequest,
+ nsIInterfaceRequestor *aContentContext,
+ bool aForceSave,
+ nsIInterfaceRequestor *aWindowContext,
+ nsIStreamListener ** aStreamListener)
+{
+ if (XRE_IsContentProcess()) {
+ return DoContentContentProcessHelper(aMimeContentType, aRequest, aContentContext,
+ aForceSave, aWindowContext, aStreamListener);
+ }
+
+ nsAutoString fileName;
+ nsAutoCString fileExtension;
+ uint32_t reason = nsIHelperAppLauncherDialog::REASON_CANTHANDLE;
+ uint32_t contentDisposition = -1;
+
+ // Get the file extension and name that we will need later
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
+ nsCOMPtr<nsIURI> uri;
+ int64_t contentLength = -1;
+ if (channel) {
+ channel->GetURI(getter_AddRefs(uri));
+ channel->GetContentLength(&contentLength);
+ channel->GetContentDisposition(&contentDisposition);
+ channel->GetContentDispositionFilename(fileName);
+
+ // Check if we have a POST request, in which case we don't want to use
+ // the url's extension
+ bool allowURLExt = true;
+ nsCOMPtr<nsIHttpChannel> httpChan = do_QueryInterface(channel);
+ if (httpChan) {
+ nsAutoCString requestMethod;
+ httpChan->GetRequestMethod(requestMethod);
+ allowURLExt = !requestMethod.EqualsLiteral("POST");
+ }
+
+ // Check if we had a query string - we don't want to check the URL
+ // extension if a query is present in the URI
+ // If we already know we don't want to check the URL extension, don't
+ // bother checking the query
+ if (uri && allowURLExt) {
+ nsCOMPtr<nsIURL> url = do_QueryInterface(uri);
+
+ if (url) {
+ nsAutoCString query;
+
+ // We only care about the query for HTTP and HTTPS URLs
+ nsresult rv;
+ bool isHTTP, isHTTPS;
+ rv = uri->SchemeIs("http", &isHTTP);
+ if (NS_FAILED(rv)) {
+ isHTTP = false;
+ }
+ rv = uri->SchemeIs("https", &isHTTPS);
+ if (NS_FAILED(rv)) {
+ isHTTPS = false;
+ }
+ if (isHTTP || isHTTPS) {
+ url->GetQuery(query);
+ }
+
+ // Only get the extension if the query is empty; if it isn't, then the
+ // extension likely belongs to a cgi script and isn't helpful
+ allowURLExt = query.IsEmpty();
+ }
+ }
+ // Extract name & extension
+ bool isAttachment = GetFilenameAndExtensionFromChannel(channel, fileName,
+ fileExtension,
+ allowURLExt);
+ LOG(("Found extension '%s' (filename is '%s', handling attachment: %i)",
+ fileExtension.get(), NS_ConvertUTF16toUTF8(fileName).get(),
+ isAttachment));
+ if (isAttachment) {
+ reason = nsIHelperAppLauncherDialog::REASON_SERVERREQUEST;
+ }
+ }
+
+ LOG(("HelperAppService::DoContent: mime '%s', extension '%s'\n",
+ PromiseFlatCString(aMimeContentType).get(), fileExtension.get()));
+
+ // We get the mime service here even though we're the default implementation
+ // of it, so it's possible to override only the mime service and not need to
+ // reimplement the whole external helper app service itself.
+ nsCOMPtr<nsIMIMEService> mimeSvc(do_GetService(NS_MIMESERVICE_CONTRACTID));
+ NS_ENSURE_TRUE(mimeSvc, NS_ERROR_FAILURE);
+
+ // Try to find a mime object by looking at the mime type/extension
+ nsCOMPtr<nsIMIMEInfo> mimeInfo;
+ if (aMimeContentType.Equals(APPLICATION_GUESS_FROM_EXT, nsCaseInsensitiveCStringComparator())) {
+ nsAutoCString mimeType;
+ if (!fileExtension.IsEmpty()) {
+ mimeSvc->GetFromTypeAndExtension(EmptyCString(), fileExtension, getter_AddRefs(mimeInfo));
+ if (mimeInfo) {
+ mimeInfo->GetMIMEType(mimeType);
+
+ LOG(("OS-Provided mime type '%s' for extension '%s'\n",
+ mimeType.get(), fileExtension.get()));
+ }
+ }
+
+ if (fileExtension.IsEmpty() || mimeType.IsEmpty()) {
+ // Extension lookup gave us no useful match
+ mimeSvc->GetFromTypeAndExtension(NS_LITERAL_CSTRING(APPLICATION_OCTET_STREAM), fileExtension,
+ getter_AddRefs(mimeInfo));
+ mimeType.AssignLiteral(APPLICATION_OCTET_STREAM);
+ }
+
+ if (channel) {
+ channel->SetContentType(mimeType);
+ }
+
+ // Don't overwrite SERVERREQUEST
+ if (reason == nsIHelperAppLauncherDialog::REASON_CANTHANDLE) {
+ reason = nsIHelperAppLauncherDialog::REASON_TYPESNIFFED;
+ }
+ } else {
+ mimeSvc->GetFromTypeAndExtension(aMimeContentType, fileExtension,
+ getter_AddRefs(mimeInfo));
+ }
+ LOG(("Type/Ext lookup found 0x%p\n", mimeInfo.get()));
+
+ // No mimeinfo -> we can't continue. probably OOM.
+ if (!mimeInfo) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ *aStreamListener = nullptr;
+ // We want the mimeInfo's primary extension to pass it to
+ // nsExternalAppHandler
+ nsAutoCString buf;
+ mimeInfo->GetPrimaryExtension(buf);
+
+ nsExternalAppHandler * handler = new nsExternalAppHandler(mimeInfo,
+ buf,
+ aContentContext,
+ aWindowContext,
+ this,
+ fileName,
+ reason,
+ aForceSave);
+ if (!handler) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ NS_ADDREF(*aStreamListener = handler);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsExternalHelperAppService::ApplyDecodingForExtension(const nsACString& aExtension,
+ const nsACString& aEncodingType,
+ bool *aApplyDecoding)
+{
+ *aApplyDecoding = true;
+ uint32_t i;
+ for(i = 0; i < ArrayLength(nonDecodableExtensions); ++i) {
+ if (aExtension.LowerCaseEqualsASCII(nonDecodableExtensions[i].mFileExtension) &&
+ aEncodingType.LowerCaseEqualsASCII(nonDecodableExtensions[i].mMimeType)) {
+ *aApplyDecoding = false;
+ break;
+ }
+ }
+ return NS_OK;
+}
+
+nsresult nsExternalHelperAppService::GetFileTokenForPath(const char16_t * aPlatformAppPath,
+ nsIFile ** aFile)
+{
+ nsDependentString platformAppPath(aPlatformAppPath);
+ // First, check if we have an absolute path
+ nsIFile* localFile = nullptr;
+ nsresult rv = NS_NewLocalFile(platformAppPath, true, &localFile);
+ if (NS_SUCCEEDED(rv)) {
+ *aFile = localFile;
+ bool exists;
+ if (NS_FAILED((*aFile)->Exists(&exists)) || !exists) {
+ NS_RELEASE(*aFile);
+ return NS_ERROR_FILE_NOT_FOUND;
+ }
+ return NS_OK;
+ }
+
+
+ // Second, check if file exists in mozilla program directory
+ rv = NS_GetSpecialDirectory(NS_XPCOM_CURRENT_PROCESS_DIR, aFile);
+ if (NS_SUCCEEDED(rv)) {
+ rv = (*aFile)->Append(platformAppPath);
+ if (NS_SUCCEEDED(rv)) {
+ bool exists = false;
+ rv = (*aFile)->Exists(&exists);
+ if (NS_SUCCEEDED(rv) && exists)
+ return NS_OK;
+ }
+ NS_RELEASE(*aFile);
+ }
+
+
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////
+// begin external protocol service default implementation...
+//////////////////////////////////////////////////////////////////////////////////////////////////////
+NS_IMETHODIMP nsExternalHelperAppService::ExternalProtocolHandlerExists(const char * aProtocolScheme,
+ bool * aHandlerExists)
+{
+ nsCOMPtr<nsIHandlerInfo> handlerInfo;
+ nsresult rv = GetProtocolHandlerInfo(nsDependentCString(aProtocolScheme),
+ getter_AddRefs(handlerInfo));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // See if we have any known possible handler apps for this
+ nsCOMPtr<nsIMutableArray> possibleHandlers;
+ handlerInfo->GetPossibleApplicationHandlers(getter_AddRefs(possibleHandlers));
+
+ uint32_t length;
+ possibleHandlers->GetLength(&length);
+ if (length) {
+ *aHandlerExists = true;
+ return NS_OK;
+ }
+
+ // if not, fall back on an os-based handler
+ return OSProtocolHandlerExists(aProtocolScheme, aHandlerExists);
+}
+
+NS_IMETHODIMP nsExternalHelperAppService::IsExposedProtocol(const char * aProtocolScheme, bool * aResult)
+{
+ // check the per protocol setting first. it always takes precedence.
+ // if not set, then use the global setting.
+
+ nsAutoCString prefName("network.protocol-handler.expose.");
+ prefName += aProtocolScheme;
+ bool val;
+ if (NS_SUCCEEDED(Preferences::GetBool(prefName.get(), &val))) {
+ *aResult = val;
+ return NS_OK;
+ }
+
+ // by default, no protocol is exposed. i.e., by default all link clicks must
+ // go through the external protocol service. most applications override this
+ // default behavior.
+ *aResult =
+ Preferences::GetBool("network.protocol-handler.expose-all", false);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsExternalHelperAppService::LoadUrl(nsIURI * aURL)
+{
+ return LoadURI(aURL, nullptr);
+}
+
+static const char kExternalProtocolPrefPrefix[] = "network.protocol-handler.external.";
+static const char kExternalProtocolDefaultPref[] = "network.protocol-handler.external-default";
+
+NS_IMETHODIMP
+nsExternalHelperAppService::LoadURI(nsIURI *aURI,
+ nsIInterfaceRequestor *aWindowContext)
+{
+ NS_ENSURE_ARG_POINTER(aURI);
+
+ if (XRE_IsContentProcess()) {
+ URIParams uri;
+ SerializeURI(aURI, uri);
+
+ nsCOMPtr<nsITabChild> tabChild(do_GetInterface(aWindowContext));
+ mozilla::dom::ContentChild::GetSingleton()->
+ SendLoadURIExternal(uri, static_cast<dom::TabChild*>(tabChild.get()));
+ return NS_OK;
+ }
+
+ nsAutoCString spec;
+ aURI->GetSpec(spec);
+
+ if (spec.Find("%00") != -1)
+ return NS_ERROR_MALFORMED_URI;
+
+ spec.ReplaceSubstring("\"", "%22");
+ spec.ReplaceSubstring("`", "%60");
+
+ nsCOMPtr<nsIIOService> ios(do_GetIOService());
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = ios->NewURI(spec, nullptr, nullptr, getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString scheme;
+ uri->GetScheme(scheme);
+ if (scheme.IsEmpty())
+ return NS_OK; // must have a scheme
+
+ // Deny load if the prefs say to do so
+ nsAutoCString externalPref(kExternalProtocolPrefPrefix);
+ externalPref += scheme;
+ bool allowLoad = false;
+ if (NS_FAILED(Preferences::GetBool(externalPref.get(), &allowLoad))) {
+ // no scheme-specific value, check the default
+ if (NS_FAILED(Preferences::GetBool(kExternalProtocolDefaultPref,
+ &allowLoad))) {
+ return NS_OK; // missing default pref
+ }
+ }
+
+ if (!allowLoad) {
+ return NS_OK; // explicitly denied
+ }
+
+ nsCOMPtr<nsIHandlerInfo> handler;
+ rv = GetProtocolHandlerInfo(scheme, getter_AddRefs(handler));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsHandlerInfoAction preferredAction;
+ handler->GetPreferredAction(&preferredAction);
+ bool alwaysAsk = true;
+ handler->GetAlwaysAskBeforeHandling(&alwaysAsk);
+
+ // if we are not supposed to ask, and the preferred action is to use
+ // a helper app or the system default, we just launch the URI.
+ if (!alwaysAsk && (preferredAction == nsIHandlerInfo::useHelperApp ||
+ preferredAction == nsIHandlerInfo::useSystemDefault))
+ return handler->LaunchWithURI(uri, aWindowContext);
+
+ nsCOMPtr<nsIContentDispatchChooser> chooser =
+ do_CreateInstance("@mozilla.org/content-dispatch-chooser;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return chooser->Ask(handler, aWindowContext, uri,
+ nsIContentDispatchChooser::REASON_CANNOT_HANDLE);
+}
+
+NS_IMETHODIMP nsExternalHelperAppService::GetApplicationDescription(const nsACString& aScheme, nsAString& _retval)
+{
+ // this method should only be implemented by each OS specific implementation of this service.
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////
+// Methods related to deleting temporary files on exit
+//////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/* static */
+nsresult
+nsExternalHelperAppService::DeleteTemporaryFileHelper(nsIFile * aTemporaryFile,
+ nsCOMArray<nsIFile> &aFileList)
+{
+ bool isFile = false;
+
+ // as a safety measure, make sure the nsIFile is really a file and not a directory object.
+ aTemporaryFile->IsFile(&isFile);
+ if (!isFile) return NS_OK;
+
+ aFileList.AppendObject(aTemporaryFile);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsExternalHelperAppService::DeleteTemporaryFileOnExit(nsIFile* aTemporaryFile)
+{
+ return DeleteTemporaryFileHelper(aTemporaryFile, mTemporaryFilesList);
+}
+
+NS_IMETHODIMP
+nsExternalHelperAppService::DeleteTemporaryPrivateFileWhenPossible(nsIFile* aTemporaryFile)
+{
+ return DeleteTemporaryFileHelper(aTemporaryFile, mTemporaryPrivateFilesList);
+}
+
+void nsExternalHelperAppService::ExpungeTemporaryFilesHelper(nsCOMArray<nsIFile> &fileList)
+{
+ int32_t numEntries = fileList.Count();
+ nsIFile* localFile;
+ for (int32_t index = 0; index < numEntries; index++)
+ {
+ localFile = fileList[index];
+ if (localFile) {
+ // First make the file writable, since the temp file is probably readonly.
+ localFile->SetPermissions(0600);
+ localFile->Remove(false);
+ }
+ }
+
+ fileList.Clear();
+}
+
+void nsExternalHelperAppService::ExpungeTemporaryFiles()
+{
+ ExpungeTemporaryFilesHelper(mTemporaryFilesList);
+}
+
+void nsExternalHelperAppService::ExpungeTemporaryPrivateFiles()
+{
+ ExpungeTemporaryFilesHelper(mTemporaryPrivateFilesList);
+}
+
+static const char kExternalWarningPrefPrefix[] =
+ "network.protocol-handler.warn-external.";
+static const char kExternalWarningDefaultPref[] =
+ "network.protocol-handler.warn-external-default";
+
+NS_IMETHODIMP
+nsExternalHelperAppService::GetProtocolHandlerInfo(const nsACString &aScheme,
+ nsIHandlerInfo **aHandlerInfo)
+{
+ // XXX enterprise customers should be able to turn this support off with a
+ // single master pref (maybe use one of the "exposed" prefs here?)
+
+ bool exists;
+ nsresult rv = GetProtocolHandlerInfoFromOS(aScheme, &exists, aHandlerInfo);
+ if (NS_FAILED(rv)) {
+ // Either it knows nothing, or we ran out of memory
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIHandlerService> handlerSvc = do_GetService(NS_HANDLERSERVICE_CONTRACTID);
+ if (handlerSvc) {
+ bool hasHandler = false;
+ (void) handlerSvc->Exists(*aHandlerInfo, &hasHandler);
+ if (hasHandler) {
+ rv = handlerSvc->FillHandlerInfo(*aHandlerInfo, EmptyCString());
+ if (NS_SUCCEEDED(rv))
+ return NS_OK;
+ }
+ }
+
+ return SetProtocolHandlerDefaults(*aHandlerInfo, exists);
+}
+
+NS_IMETHODIMP
+nsExternalHelperAppService::GetProtocolHandlerInfoFromOS(const nsACString &aScheme,
+ bool *found,
+ nsIHandlerInfo **aHandlerInfo)
+{
+ // intended to be implemented by the subclass
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsExternalHelperAppService::SetProtocolHandlerDefaults(nsIHandlerInfo *aHandlerInfo,
+ bool aOSHandlerExists)
+{
+ // this type isn't in our database, so we've only got an OS default handler,
+ // if one exists
+
+ if (aOSHandlerExists) {
+ // we've got a default, so use it
+ aHandlerInfo->SetPreferredAction(nsIHandlerInfo::useSystemDefault);
+
+ // whether or not to ask the user depends on the warning preference
+ nsAutoCString scheme;
+ aHandlerInfo->GetType(scheme);
+
+ nsAutoCString warningPref(kExternalWarningPrefPrefix);
+ warningPref += scheme;
+ bool warn;
+ if (NS_FAILED(Preferences::GetBool(warningPref.get(), &warn))) {
+ // no scheme-specific value, check the default
+ warn = Preferences::GetBool(kExternalWarningDefaultPref, true);
+ }
+ aHandlerInfo->SetAlwaysAskBeforeHandling(warn);
+ } else {
+ // If no OS default existed, we set the preferred action to alwaysAsk.
+ // This really means not initialized (i.e. there's no available handler)
+ // to all the code...
+ aHandlerInfo->SetPreferredAction(nsIHandlerInfo::alwaysAsk);
+ }
+
+ return NS_OK;
+}
+
+// XPCOM profile change observer
+NS_IMETHODIMP
+nsExternalHelperAppService::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *someData )
+{
+ if (!strcmp(aTopic, "profile-before-change")) {
+ ExpungeTemporaryFiles();
+ } else if (!strcmp(aTopic, "last-pb-context-exited")) {
+ ExpungeTemporaryPrivateFiles();
+ }
+ return NS_OK;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////
+// begin external app handler implementation
+//////////////////////////////////////////////////////////////////////////////////////////////////////
+
+NS_IMPL_ADDREF(nsExternalAppHandler)
+NS_IMPL_RELEASE(nsExternalAppHandler)
+
+NS_INTERFACE_MAP_BEGIN(nsExternalAppHandler)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIStreamListener)
+ NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
+ NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
+ NS_INTERFACE_MAP_ENTRY(nsIHelperAppLauncher)
+ NS_INTERFACE_MAP_ENTRY(nsICancelable)
+ NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
+ NS_INTERFACE_MAP_ENTRY(nsIBackgroundFileSaverObserver)
+NS_INTERFACE_MAP_END_THREADSAFE
+
+nsExternalAppHandler::nsExternalAppHandler(nsIMIMEInfo * aMIMEInfo,
+ const nsCSubstring& aTempFileExtension,
+ nsIInterfaceRequestor* aContentContext,
+ nsIInterfaceRequestor* aWindowContext,
+ nsExternalHelperAppService *aExtProtSvc,
+ const nsAString& aSuggestedFilename,
+ uint32_t aReason, bool aForceSave)
+: mMimeInfo(aMIMEInfo)
+, mContentContext(aContentContext)
+, mWindowContext(aWindowContext)
+, mWindowToClose(nullptr)
+, mSuggestedFileName(aSuggestedFilename)
+, mForceSave(aForceSave)
+, mCanceled(false)
+, mShouldCloseWindow(false)
+, mStopRequestIssued(false)
+, mReason(aReason)
+, mContentLength(-1)
+, mProgress(0)
+, mSaver(nullptr)
+, mDialogProgressListener(nullptr)
+, mTransfer(nullptr)
+, mRequest(nullptr)
+, mExtProtSvc(aExtProtSvc)
+{
+
+ // make sure the extention includes the '.'
+ if (!aTempFileExtension.IsEmpty() && aTempFileExtension.First() != '.')
+ mTempFileExtension = char16_t('.');
+ AppendUTF8toUTF16(aTempFileExtension, mTempFileExtension);
+
+ // replace platform specific path separator and illegal characters to avoid any confusion
+ mSuggestedFileName.ReplaceChar(KNOWN_PATH_SEPARATORS FILE_ILLEGAL_CHARACTERS, '_');
+ mTempFileExtension.ReplaceChar(KNOWN_PATH_SEPARATORS FILE_ILLEGAL_CHARACTERS, '_');
+
+ // Remove unsafe bidi characters which might have spoofing implications (bug 511521).
+ const char16_t unsafeBidiCharacters[] = {
+ char16_t(0x061c), // Arabic Letter Mark
+ char16_t(0x200e), // Left-to-Right Mark
+ char16_t(0x200f), // Right-to-Left Mark
+ char16_t(0x202a), // Left-to-Right Embedding
+ char16_t(0x202b), // Right-to-Left Embedding
+ char16_t(0x202c), // Pop Directional Formatting
+ char16_t(0x202d), // Left-to-Right Override
+ char16_t(0x202e), // Right-to-Left Override
+ char16_t(0x2066), // Left-to-Right Isolate
+ char16_t(0x2067), // Right-to-Left Isolate
+ char16_t(0x2068), // First Strong Isolate
+ char16_t(0x2069), // Pop Directional Isolate
+ char16_t(0)
+ };
+ mSuggestedFileName.ReplaceChar(unsafeBidiCharacters, '_');
+ mTempFileExtension.ReplaceChar(unsafeBidiCharacters, '_');
+
+ // Make sure extension is correct.
+ EnsureSuggestedFileName();
+
+ mBufferSize = Preferences::GetUint("network.buffer.cache.size", 4096);
+}
+
+nsExternalAppHandler::~nsExternalAppHandler()
+{
+ MOZ_ASSERT(!mSaver, "Saver should hold a reference to us until deleted");
+}
+
+void
+nsExternalAppHandler::DidDivertRequest(nsIRequest *request)
+{
+ MOZ_ASSERT(XRE_IsContentProcess(), "in child process");
+ // Remove our request from the child loadGroup
+ RetargetLoadNotifications(request);
+ MaybeCloseWindow();
+}
+
+NS_IMETHODIMP nsExternalAppHandler::SetWebProgressListener(nsIWebProgressListener2 * aWebProgressListener)
+{
+ // This is always called by nsHelperDlg.js. Go ahead and register the
+ // progress listener. At this point, we don't have mTransfer.
+ mDialogProgressListener = aWebProgressListener;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsExternalAppHandler::GetTargetFile(nsIFile** aTarget)
+{
+ if (mFinalFileDestination)
+ *aTarget = mFinalFileDestination;
+ else
+ *aTarget = mTempFile;
+
+ NS_IF_ADDREF(*aTarget);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsExternalAppHandler::GetTargetFileIsExecutable(bool *aExec)
+{
+ // Use the real target if it's been set
+ if (mFinalFileDestination)
+ return mFinalFileDestination->IsExecutable(aExec);
+
+ // Otherwise, use the stored executable-ness of the temporary
+ *aExec = mTempFileIsExecutable;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsExternalAppHandler::GetTimeDownloadStarted(PRTime* aTime)
+{
+ *aTime = mTimeDownloadStarted;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsExternalAppHandler::GetContentLength(int64_t *aContentLength)
+{
+ *aContentLength = mContentLength;
+ return NS_OK;
+}
+
+void nsExternalAppHandler::RetargetLoadNotifications(nsIRequest *request)
+{
+ // we are going to run the downloading of the helper app in our own little docloader / load group context.
+ // so go ahead and force the creation of a load group and doc loader for us to use...
+ nsCOMPtr<nsIChannel> aChannel = do_QueryInterface(request);
+ if (!aChannel)
+ return;
+
+ // we need to store off the original (pre redirect!) channel that initiated the load. We do
+ // this so later on, we can pass any refresh urls associated with the original channel back to the
+ // window context which started the whole process. More comments about that are listed below....
+ // HACK ALERT: it's pretty bogus that we are getting the document channel from the doc loader.
+ // ideally we should be able to just use mChannel (the channel we are extracting content from) or
+ // the default load channel associated with the original load group. Unfortunately because
+ // a redirect may have occurred, the doc loader is the only one with a ptr to the original channel
+ // which is what we really want....
+
+ // Note that we need to do this before removing aChannel from the loadgroup,
+ // since that would mess with the original channel on the loader.
+ nsCOMPtr<nsIDocumentLoader> origContextLoader =
+ do_GetInterface(mContentContext);
+ if (origContextLoader) {
+ origContextLoader->GetDocumentChannel(getter_AddRefs(mOriginalChannel));
+ }
+
+ bool isPrivate = NS_UsePrivateBrowsing(aChannel);
+
+ nsCOMPtr<nsILoadGroup> oldLoadGroup;
+ aChannel->GetLoadGroup(getter_AddRefs(oldLoadGroup));
+
+ if(oldLoadGroup) {
+ oldLoadGroup->RemoveRequest(request, nullptr, NS_BINDING_RETARGETED);
+ }
+
+ aChannel->SetLoadGroup(nullptr);
+ aChannel->SetNotificationCallbacks(nullptr);
+
+ nsCOMPtr<nsIPrivateBrowsingChannel> pbChannel = do_QueryInterface(aChannel);
+ if (pbChannel) {
+ pbChannel->SetPrivate(isPrivate);
+ }
+}
+
+/**
+ * Make mTempFileExtension contain an extension exactly when its previous value
+ * is different from mSuggestedFileName's extension, so that it can be appended
+ * to mSuggestedFileName and form a valid, useful leaf name.
+ * This is required so that the (renamed) temporary file has the correct extension
+ * after downloading to make sure the OS will launch the application corresponding
+ * to the MIME type (which was used to calculate mTempFileExtension). This prevents
+ * a cgi-script named foobar.exe that returns application/zip from being named
+ * foobar.exe and executed as an executable file. It also blocks content that
+ * a web site might provide with a content-disposition header indicating
+ * filename="foobar.exe" from being downloaded to a file with extension .exe
+ * and executed.
+ */
+void nsExternalAppHandler::EnsureSuggestedFileName()
+{
+ // Make sure there is a mTempFileExtension (not "" or ".").
+ // Remember that mTempFileExtension will always have the leading "."
+ // (the check for empty is just to be safe).
+ if (mTempFileExtension.Length() > 1)
+ {
+ // Get mSuggestedFileName's current extension.
+ nsAutoString fileExt;
+ int32_t pos = mSuggestedFileName.RFindChar('.');
+ if (pos != kNotFound)
+ mSuggestedFileName.Right(fileExt, mSuggestedFileName.Length() - pos);
+
+ // Now, compare fileExt to mTempFileExtension.
+ if (fileExt.Equals(mTempFileExtension, nsCaseInsensitiveStringComparator()))
+ {
+ // Matches -> mTempFileExtension can be empty
+ mTempFileExtension.Truncate();
+ }
+ }
+}
+
+nsresult nsExternalAppHandler::SetUpTempFile(nsIChannel * aChannel)
+{
+ // First we need to try to get the destination directory for the temporary
+ // file.
+ nsresult rv = GetDownloadDirectory(getter_AddRefs(mTempFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // At this point, we do not have a filename for the temp file. For security
+ // purposes, this cannot be predictable, so we must use a cryptographic
+ // quality PRNG to generate one.
+ // We will request raw random bytes, and transform that to a base64 string,
+ // as all characters from the base64 set are acceptable for filenames. For
+ // each three bytes of random data, we will get four bytes of ASCII. Request
+ // a bit more, to be safe, and truncate to the length we want in the end.
+
+ const uint32_t wantedFileNameLength = 8;
+ const uint32_t requiredBytesLength =
+ static_cast<uint32_t>((wantedFileNameLength + 1) / 4 * 3);
+
+ nsCOMPtr<nsIRandomGenerator> rg =
+ do_GetService("@mozilla.org/security/random-generator;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint8_t *buffer;
+ rv = rg->GenerateRandomBytes(requiredBytesLength, &buffer);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString tempLeafName;
+ nsDependentCSubstring randomData(reinterpret_cast<const char*>(buffer), requiredBytesLength);
+ rv = Base64Encode(randomData, tempLeafName);
+ free(buffer);
+ buffer = nullptr;
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ tempLeafName.Truncate(wantedFileNameLength);
+
+ // Base64 characters are alphanumeric (a-zA-Z0-9) and '+' and '/', so we need
+ // to replace illegal characters -- notably '/'
+ tempLeafName.ReplaceChar(KNOWN_PATH_SEPARATORS FILE_ILLEGAL_CHARACTERS, '_');
+
+ // now append our extension.
+ nsAutoCString ext;
+ mMimeInfo->GetPrimaryExtension(ext);
+ if (!ext.IsEmpty()) {
+ ext.ReplaceChar(KNOWN_PATH_SEPARATORS FILE_ILLEGAL_CHARACTERS, '_');
+ if (ext.First() != '.')
+ tempLeafName.Append('.');
+ tempLeafName.Append(ext);
+ }
+
+ // We need to temporarily create a dummy file with the correct
+ // file extension to determine the executable-ness, so do this before adding
+ // the extra .part extension.
+ nsCOMPtr<nsIFile> dummyFile;
+ rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(dummyFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Set the file name without .part
+ rv = dummyFile->Append(NS_ConvertUTF8toUTF16(tempLeafName));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = dummyFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Store executable-ness then delete
+ dummyFile->IsExecutable(&mTempFileIsExecutable);
+ dummyFile->Remove(false);
+
+ // Add an additional .part to prevent the OS from running this file in the
+ // default application.
+ tempLeafName.AppendLiteral(".part");
+
+ rv = mTempFile->Append(NS_ConvertUTF8toUTF16(tempLeafName));
+ // make this file unique!!!
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mTempFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Now save the temp leaf name, minus the ".part" bit, so we can use it later.
+ // This is a bit broken in the case when createUnique actually had to append
+ // some numbers, because then we now have a filename like foo.bar-1.part and
+ // we'll end up with foo.bar-1.bar as our final filename if we end up using
+ // this. But the other options are all bad too.... Ideally we'd have a way
+ // to tell createUnique to put its unique marker before the extension that
+ // comes before ".part" or something.
+ rv = mTempFile->GetLeafName(mTempLeafName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ENSURE_TRUE(StringEndsWith(mTempLeafName, NS_LITERAL_STRING(".part")),
+ NS_ERROR_UNEXPECTED);
+
+ // Strip off the ".part" from mTempLeafName
+ mTempLeafName.Truncate(mTempLeafName.Length() - ArrayLength(".part") + 1);
+
+ MOZ_ASSERT(!mSaver, "Output file initialization called more than once!");
+ mSaver = do_CreateInstance(NS_BACKGROUNDFILESAVERSTREAMLISTENER_CONTRACTID,
+ &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mSaver->SetObserver(this);
+ if (NS_FAILED(rv)) {
+ mSaver = nullptr;
+ return rv;
+ }
+
+ rv = mSaver->EnableSha256();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mSaver->EnableSignatureInfo();
+ NS_ENSURE_SUCCESS(rv, rv);
+ LOG(("Enabled hashing and signature verification"));
+
+ rv = mSaver->SetTarget(mTempFile, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return rv;
+}
+
+void
+nsExternalAppHandler::MaybeApplyDecodingForExtension(nsIRequest *aRequest)
+{
+ MOZ_ASSERT(aRequest);
+
+ nsCOMPtr<nsIEncodedChannel> encChannel = do_QueryInterface(aRequest);
+ if (!encChannel) {
+ return;
+ }
+
+ // Turn off content encoding conversions if needed
+ bool applyConversion = true;
+
+ // First, check to see if conversion is already disabled. If so, we
+ // have nothing to do here.
+ encChannel->GetApplyConversion(&applyConversion);
+ if (!applyConversion) {
+ return;
+ }
+
+ nsCOMPtr<nsIURL> sourceURL(do_QueryInterface(mSourceUrl));
+ if (sourceURL)
+ {
+ nsAutoCString extension;
+ sourceURL->GetFileExtension(extension);
+ if (!extension.IsEmpty())
+ {
+ nsCOMPtr<nsIUTF8StringEnumerator> encEnum;
+ encChannel->GetContentEncodings(getter_AddRefs(encEnum));
+ if (encEnum)
+ {
+ bool hasMore;
+ nsresult rv = encEnum->HasMore(&hasMore);
+ if (NS_SUCCEEDED(rv) && hasMore)
+ {
+ nsAutoCString encType;
+ rv = encEnum->GetNext(encType);
+ if (NS_SUCCEEDED(rv) && !encType.IsEmpty())
+ {
+ MOZ_ASSERT(mExtProtSvc);
+ mExtProtSvc->ApplyDecodingForExtension(extension, encType,
+ &applyConversion);
+ }
+ }
+ }
+ }
+ }
+
+ encChannel->SetApplyConversion( applyConversion );
+ return;
+}
+
+NS_IMETHODIMP nsExternalAppHandler::OnStartRequest(nsIRequest *request, nsISupports * aCtxt)
+{
+ NS_PRECONDITION(request, "OnStartRequest without request?");
+
+ // Set mTimeDownloadStarted here as the download has already started and
+ // we want to record the start time before showing the filepicker.
+ mTimeDownloadStarted = PR_Now();
+
+ mRequest = request;
+
+ nsCOMPtr<nsIChannel> aChannel = do_QueryInterface(request);
+
+ nsresult rv;
+
+ nsCOMPtr<nsIFileChannel> fileChan(do_QueryInterface(request));
+ mIsFileChannel = fileChan != nullptr;
+ if (!mIsFileChannel) {
+ // It's possible that this request came from the child process and the
+ // file channel actually lives there. If this returns true, then our
+ // mSourceUrl will be an nsIFileURL anyway.
+ nsCOMPtr<dom::nsIExternalHelperAppParent> parent(do_QueryInterface(request));
+ mIsFileChannel = parent && parent->WasFileChannel();
+ }
+
+ // Get content length
+ if (aChannel) {
+ aChannel->GetContentLength(&mContentLength);
+ }
+
+ nsCOMPtr<nsIPropertyBag2> props(do_QueryInterface(request, &rv));
+ // Determine whether a new window was opened specifically for this request
+ if (props) {
+ bool tmp = false;
+ props->GetPropertyAsBool(NS_LITERAL_STRING("docshell.newWindowTarget"),
+ &tmp);
+ mShouldCloseWindow = tmp;
+ }
+
+ // Now get the URI
+ if (aChannel) {
+ aChannel->GetURI(getter_AddRefs(mSourceUrl));
+ }
+
+ // retarget all load notifications to our docloader instead of the original window's docloader...
+ RetargetLoadNotifications(request);
+
+ // Check to see if there is a refresh header on the original channel.
+ if (mOriginalChannel) {
+ nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(mOriginalChannel));
+ if (httpChannel) {
+ nsAutoCString refreshHeader;
+ httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("refresh"),
+ refreshHeader);
+ if (!refreshHeader.IsEmpty()) {
+ mShouldCloseWindow = false;
+ }
+ }
+ }
+
+ // Close the underlying DOMWindow if there is no refresh header
+ // and it was opened specifically for the download
+ MaybeCloseWindow();
+
+ // In an IPC setting, we're allowing the child process, here, to make
+ // decisions about decoding the channel (e.g. decompression). It will
+ // still forward the decoded (uncompressed) data back to the parent.
+ // Con: Uncompressed data means more IPC overhead.
+ // Pros: ExternalHelperAppParent doesn't need to implement nsIEncodedChannel.
+ // Parent process doesn't need to expect CPU time on decompression.
+ MaybeApplyDecodingForExtension(aChannel);
+
+ // At this point, the child process has done everything it can usefully do
+ // for OnStartRequest.
+ if (XRE_IsContentProcess()) {
+ return NS_OK;
+ }
+
+ rv = SetUpTempFile(aChannel);
+ if (NS_FAILED(rv)) {
+ nsresult transferError = rv;
+
+ rv = CreateFailedTransfer(aChannel && NS_UsePrivateBrowsing(aChannel));
+ if (NS_FAILED(rv)) {
+ LOG(("Failed to create transfer to report failure."
+ "Will fallback to prompter!"));
+ }
+
+ mCanceled = true;
+ request->Cancel(transferError);
+
+ nsAutoString path;
+ if (mTempFile)
+ mTempFile->GetPath(path);
+
+ SendStatusChange(kWriteError, transferError, request, path);
+
+ return NS_OK;
+ }
+
+ // Inform channel it is open on behalf of a download to prevent caching.
+ nsCOMPtr<nsIHttpChannelInternal> httpInternal = do_QueryInterface(aChannel);
+ if (httpInternal) {
+ httpInternal->SetChannelIsForDownload(true);
+ }
+
+ // now that the temp file is set up, find out if we need to invoke a dialog
+ // asking the user what they want us to do with this content...
+
+ // We can get here for three reasons: "can't handle", "sniffed type", or
+ // "server sent content-disposition:attachment". In the first case we want
+ // to honor the user's "always ask" pref; in the other two cases we want to
+ // honor it only if the default action is "save". Opening attachments in
+ // helper apps by default breaks some websites (especially if the attachment
+ // is one part of a multipart document). Opening sniffed content in helper
+ // apps by default introduces security holes that we'd rather not have.
+
+ // So let's find out whether the user wants to be prompted. If he does not,
+ // check mReason and the preferred action to see what we should do.
+
+ bool alwaysAsk = true;
+ mMimeInfo->GetAlwaysAskBeforeHandling(&alwaysAsk);
+ if (alwaysAsk) {
+ // But we *don't* ask if this mimeInfo didn't come from
+ // our user configuration datastore and the user has said
+ // at some point in the distant past that they don't
+ // want to be asked. The latter fact would have been
+ // stored in pref strings back in the old days.
+
+ bool mimeTypeIsInDatastore = false;
+ nsCOMPtr<nsIHandlerService> handlerSvc = do_GetService(NS_HANDLERSERVICE_CONTRACTID);
+ if (handlerSvc) {
+ handlerSvc->Exists(mMimeInfo, &mimeTypeIsInDatastore);
+ }
+ if (!handlerSvc || !mimeTypeIsInDatastore) {
+ nsAutoCString MIMEType;
+ mMimeInfo->GetMIMEType(MIMEType);
+ if (!GetNeverAskFlagFromPref(NEVER_ASK_FOR_SAVE_TO_DISK_PREF, MIMEType.get())) {
+ // Don't need to ask after all.
+ alwaysAsk = false;
+ // Make sure action matches pref (save to disk).
+ mMimeInfo->SetPreferredAction(nsIMIMEInfo::saveToDisk);
+ } else if (!GetNeverAskFlagFromPref(NEVER_ASK_FOR_OPEN_FILE_PREF, MIMEType.get())) {
+ // Don't need to ask after all.
+ alwaysAsk = false;
+ }
+ }
+ }
+
+ int32_t action = nsIMIMEInfo::saveToDisk;
+ mMimeInfo->GetPreferredAction( &action );
+
+ // OK, now check why we're here
+ if (!alwaysAsk && mReason != nsIHelperAppLauncherDialog::REASON_CANTHANDLE) {
+ // Force asking if we're not saving. See comment back when we fetched the
+ // alwaysAsk boolean for details.
+ alwaysAsk = (action != nsIMIMEInfo::saveToDisk);
+ }
+
+ // if we were told that we _must_ save to disk without asking, all the stuff
+ // before this is irrelevant; override it
+ if (mForceSave) {
+ alwaysAsk = false;
+ action = nsIMIMEInfo::saveToDisk;
+ }
+
+ if (alwaysAsk)
+ {
+ // Display the dialog
+ mDialog = do_CreateInstance(NS_HELPERAPPLAUNCHERDLG_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // this will create a reference cycle (the dialog holds a reference to us as
+ // nsIHelperAppLauncher), which will be broken in Cancel or CreateTransfer.
+ rv = mDialog->Show(this, GetDialogParent(), mReason);
+
+ // what do we do if the dialog failed? I guess we should call Cancel and abort the load....
+ }
+ else
+ {
+
+ // We need to do the save/open immediately, then.
+#ifdef XP_WIN
+ /* We need to see whether the file we've got here could be
+ * executable. If it could, we had better not try to open it!
+ * We can skip this check, though, if we have a setting to open in a
+ * helper app.
+ * This code mirrors the code in
+ * nsExternalAppHandler::LaunchWithApplication so that what we
+ * test here is as close as possible to what will really be
+ * happening if we decide to execute
+ */
+ nsCOMPtr<nsIHandlerApp> prefApp;
+ mMimeInfo->GetPreferredApplicationHandler(getter_AddRefs(prefApp));
+ if (action != nsIMIMEInfo::useHelperApp || !prefApp) {
+ nsCOMPtr<nsIFile> fileToTest;
+ GetTargetFile(getter_AddRefs(fileToTest));
+ if (fileToTest) {
+ bool isExecutable;
+ rv = fileToTest->IsExecutable(&isExecutable);
+ if (NS_FAILED(rv) || isExecutable) { // checking NS_FAILED, because paranoia is good
+ action = nsIMIMEInfo::saveToDisk;
+ }
+ } else { // Paranoia is good here too, though this really should not happen
+ NS_WARNING("GetDownloadInfo returned a null file after the temp file has been set up! ");
+ action = nsIMIMEInfo::saveToDisk;
+ }
+ }
+
+#endif
+ if (action == nsIMIMEInfo::useHelperApp ||
+ action == nsIMIMEInfo::useSystemDefault) {
+ rv = LaunchWithApplication(nullptr, false);
+ } else {
+ rv = SaveToDisk(nullptr, false);
+ }
+ }
+
+ return NS_OK;
+}
+
+// Convert error info into proper message text and send OnStatusChange
+// notification to the dialog progress listener or nsITransfer implementation.
+void nsExternalAppHandler::SendStatusChange(ErrorType type, nsresult rv, nsIRequest *aRequest, const nsAFlatString &path)
+{
+ nsAutoString msgId;
+ switch (rv) {
+ case NS_ERROR_OUT_OF_MEMORY:
+ // No memory
+ msgId.AssignLiteral("noMemory");
+ break;
+
+ case NS_ERROR_FILE_DISK_FULL:
+ case NS_ERROR_FILE_NO_DEVICE_SPACE:
+ // Out of space on target volume.
+ msgId.AssignLiteral("diskFull");
+ break;
+
+ case NS_ERROR_FILE_READ_ONLY:
+ // Attempt to write to read/only file.
+ msgId.AssignLiteral("readOnly");
+ break;
+
+ case NS_ERROR_FILE_ACCESS_DENIED:
+ if (type == kWriteError) {
+ // Attempt to write without sufficient permissions.
+#if defined(ANDROID)
+ // On Android (and Gonk), this means the SD card is present but
+ // unavailable (read-only).
+ msgId.AssignLiteral("SDAccessErrorCardReadOnly");
+#else
+ msgId.AssignLiteral("accessError");
+#endif
+ } else {
+ msgId.AssignLiteral("launchError");
+ }
+ break;
+
+ case NS_ERROR_FILE_NOT_FOUND:
+ case NS_ERROR_FILE_TARGET_DOES_NOT_EXIST:
+ case NS_ERROR_FILE_UNRECOGNIZED_PATH:
+ // Helper app not found, let's verify this happened on launch
+ if (type == kLaunchError) {
+ msgId.AssignLiteral("helperAppNotFound");
+ break;
+ }
+#if defined(ANDROID)
+ else if (type == kWriteError) {
+ // On Android (and Gonk), this means the SD card is missing (not in
+ // SD slot).
+ msgId.AssignLiteral("SDAccessErrorCardMissing");
+ break;
+ }
+#endif
+ MOZ_FALLTHROUGH;
+
+ default:
+ // Generic read/write/launch error message.
+ switch (type) {
+ case kReadError:
+ msgId.AssignLiteral("readError");
+ break;
+ case kWriteError:
+ msgId.AssignLiteral("writeError");
+ break;
+ case kLaunchError:
+ msgId.AssignLiteral("launchError");
+ break;
+ }
+ break;
+ }
+
+ MOZ_LOG(nsExternalHelperAppService::mLog, LogLevel::Error,
+ ("Error: %s, type=%i, listener=0x%p, transfer=0x%p, rv=0x%08X\n",
+ NS_LossyConvertUTF16toASCII(msgId).get(), type, mDialogProgressListener.get(), mTransfer.get(), rv));
+
+ MOZ_LOG(nsExternalHelperAppService::mLog, LogLevel::Error,
+ (" path='%s'\n", NS_ConvertUTF16toUTF8(path).get()));
+
+ // Get properties file bundle and extract status string.
+ nsCOMPtr<nsIStringBundleService> stringService =
+ mozilla::services::GetStringBundleService();
+ if (stringService) {
+ nsCOMPtr<nsIStringBundle> bundle;
+ if (NS_SUCCEEDED(stringService->CreateBundle("chrome://global/locale/nsWebBrowserPersist.properties",
+ getter_AddRefs(bundle)))) {
+ nsXPIDLString msgText;
+ const char16_t *strings[] = { path.get() };
+ if (NS_SUCCEEDED(bundle->FormatStringFromName(msgId.get(), strings, 1,
+ getter_Copies(msgText)))) {
+ if (mDialogProgressListener) {
+ // We have a listener, let it handle the error.
+ mDialogProgressListener->OnStatusChange(nullptr, (type == kReadError) ? aRequest : nullptr, rv, msgText);
+ } else if (mTransfer) {
+ mTransfer->OnStatusChange(nullptr, (type == kReadError) ? aRequest : nullptr, rv, msgText);
+ } else if (XRE_IsParentProcess()) {
+ // We don't have a listener. Simply show the alert ourselves.
+ nsresult qiRv;
+ nsCOMPtr<nsIPrompt> prompter(do_GetInterface(GetDialogParent(), &qiRv));
+ nsXPIDLString title;
+ bundle->FormatStringFromName(u"title",
+ strings,
+ 1,
+ getter_Copies(title));
+
+ MOZ_LOG(nsExternalHelperAppService::mLog, LogLevel::Debug,
+ ("mContentContext=0x%p, prompter=0x%p, qi rv=0x%08X, title='%s', msg='%s'",
+ mContentContext.get(),
+ prompter.get(),
+ qiRv,
+ NS_ConvertUTF16toUTF8(title).get(),
+ NS_ConvertUTF16toUTF8(msgText).get()));
+
+ // If we didn't have a prompter we will try and get a window
+ // instead, get it's docshell and use it to alert the user.
+ if (!prompter) {
+ nsCOMPtr<nsPIDOMWindowOuter> window(do_GetInterface(GetDialogParent()));
+ if (!window || !window->GetDocShell()) {
+ return;
+ }
+
+ prompter = do_GetInterface(window->GetDocShell(), &qiRv);
+
+ MOZ_LOG(nsExternalHelperAppService::mLog, LogLevel::Debug,
+ ("No prompter from mContentContext, using DocShell, " \
+ "window=0x%p, docShell=0x%p, " \
+ "prompter=0x%p, qi rv=0x%08X",
+ window.get(),
+ window->GetDocShell(),
+ prompter.get(),
+ qiRv));
+
+ // If we still don't have a prompter, there's nothing else we
+ // can do so just return.
+ if (!prompter) {
+ MOZ_LOG(nsExternalHelperAppService::mLog, LogLevel::Error,
+ ("No prompter from DocShell, no way to alert user"));
+ return;
+ }
+ }
+
+ // We should always have a prompter at this point.
+ prompter->Alert(title, msgText);
+ }
+ }
+ }
+ }
+}
+
+NS_IMETHODIMP
+nsExternalAppHandler::OnDataAvailable(nsIRequest *request, nsISupports * aCtxt,
+ nsIInputStream * inStr,
+ uint64_t sourceOffset, uint32_t count)
+{
+ nsresult rv = NS_OK;
+ // first, check to see if we've been canceled....
+ if (mCanceled || !mSaver) {
+ // then go cancel our underlying channel too
+ return request->Cancel(NS_BINDING_ABORTED);
+ }
+
+ // read the data out of the stream and write it to the temp file.
+ if (count > 0) {
+ mProgress += count;
+
+ nsCOMPtr<nsIStreamListener> saver = do_QueryInterface(mSaver);
+ rv = saver->OnDataAvailable(request, aCtxt, inStr, sourceOffset, count);
+ if (NS_SUCCEEDED(rv)) {
+ // Send progress notification.
+ if (mTransfer) {
+ mTransfer->OnProgressChange64(nullptr, request, mProgress,
+ mContentLength, mProgress,
+ mContentLength);
+ }
+ } else {
+ // An error occurred, notify listener.
+ nsAutoString tempFilePath;
+ if (mTempFile) {
+ mTempFile->GetPath(tempFilePath);
+ }
+ SendStatusChange(kReadError, rv, request, tempFilePath);
+
+ // Cancel the download.
+ Cancel(rv);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsExternalAppHandler::OnStopRequest(nsIRequest *request, nsISupports *aCtxt,
+ nsresult aStatus)
+{
+ LOG(("nsExternalAppHandler::OnStopRequest\n"
+ " mCanceled=%d, mTransfer=0x%p, aStatus=0x%08X\n",
+ mCanceled, mTransfer.get(), aStatus));
+
+ mStopRequestIssued = true;
+
+ // Cancel if the request did not complete successfully.
+ if (!mCanceled && NS_FAILED(aStatus)) {
+ // Send error notification.
+ nsAutoString tempFilePath;
+ if (mTempFile)
+ mTempFile->GetPath(tempFilePath);
+ SendStatusChange( kReadError, aStatus, request, tempFilePath );
+
+ Cancel(aStatus);
+ }
+
+ // first, check to see if we've been canceled....
+ if (mCanceled || !mSaver) {
+ return NS_OK;
+ }
+
+ return mSaver->Finish(NS_OK);
+}
+
+NS_IMETHODIMP
+nsExternalAppHandler::OnTargetChange(nsIBackgroundFileSaver *aSaver,
+ nsIFile *aTarget)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsExternalAppHandler::OnSaveComplete(nsIBackgroundFileSaver *aSaver,
+ nsresult aStatus)
+{
+ LOG(("nsExternalAppHandler::OnSaveComplete\n"
+ " aSaver=0x%p, aStatus=0x%08X, mCanceled=%d, mTransfer=0x%p\n",
+ aSaver, aStatus, mCanceled, mTransfer.get()));
+
+ if (!mCanceled) {
+ // Save the hash and signature information
+ (void)mSaver->GetSha256Hash(mHash);
+ (void)mSaver->GetSignatureInfo(getter_AddRefs(mSignatureInfo));
+
+ // Free the reference that the saver keeps on us, even if we couldn't get
+ // the hash.
+ mSaver = nullptr;
+
+ // Save the redirect information.
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(mRequest);
+ if (channel) {
+ nsCOMPtr<nsILoadInfo> loadInfo = channel->GetLoadInfo();
+ if (loadInfo) {
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIMutableArray> redirectChain =
+ do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ LOG(("nsExternalAppHandler: Got %u redirects\n", loadInfo->RedirectChain().Length()));
+ for (nsIPrincipal* principal : loadInfo->RedirectChain()) {
+ redirectChain->AppendElement(principal, false);
+ }
+ mRedirects = redirectChain;
+ }
+ }
+
+ if (NS_FAILED(aStatus)) {
+ nsAutoString path;
+ mTempFile->GetPath(path);
+
+ // It may happen when e10s is enabled that there will be no transfer
+ // object available to communicate status as expected by the system.
+ // Let's try and create a temporary transfer object to take care of this
+ // for us, we'll fall back to using the prompt service if we absolutely
+ // have to.
+ if (!mTransfer) {
+ // We don't care if this fails.
+ CreateFailedTransfer(channel && NS_UsePrivateBrowsing(channel));
+ }
+
+ SendStatusChange(kWriteError, aStatus, nullptr, path);
+ if (!mCanceled)
+ Cancel(aStatus);
+ return NS_OK;
+ }
+ }
+
+ // Notify the transfer object that we are done if the user has chosen an
+ // action. If the user hasn't chosen an action, the progress listener
+ // (nsITransfer) will be notified in CreateTransfer.
+ if (mTransfer) {
+ NotifyTransfer(aStatus);
+ }
+
+ return NS_OK;
+}
+
+void nsExternalAppHandler::NotifyTransfer(nsresult aStatus)
+{
+ MOZ_ASSERT(NS_IsMainThread(), "Must notify on main thread");
+ MOZ_ASSERT(mTransfer, "We must have an nsITransfer");
+
+ LOG(("Notifying progress listener"));
+
+ if (NS_SUCCEEDED(aStatus)) {
+ (void)mTransfer->SetSha256Hash(mHash);
+ (void)mTransfer->SetSignatureInfo(mSignatureInfo);
+ (void)mTransfer->SetRedirects(mRedirects);
+ (void)mTransfer->OnProgressChange64(nullptr, nullptr, mProgress,
+ mContentLength, mProgress, mContentLength);
+ }
+
+ (void)mTransfer->OnStateChange(nullptr, nullptr,
+ nsIWebProgressListener::STATE_STOP |
+ nsIWebProgressListener::STATE_IS_REQUEST |
+ nsIWebProgressListener::STATE_IS_NETWORK, aStatus);
+
+ // This nsITransfer object holds a reference to us (we are its observer), so
+ // we need to release the reference to break a reference cycle (and therefore
+ // to prevent leaking). We do this even if the previous calls failed.
+ mTransfer = nullptr;
+}
+
+NS_IMETHODIMP nsExternalAppHandler::GetMIMEInfo(nsIMIMEInfo ** aMIMEInfo)
+{
+ *aMIMEInfo = mMimeInfo;
+ NS_ADDREF(*aMIMEInfo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsExternalAppHandler::GetSource(nsIURI ** aSourceURI)
+{
+ NS_ENSURE_ARG(aSourceURI);
+ *aSourceURI = mSourceUrl;
+ NS_IF_ADDREF(*aSourceURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsExternalAppHandler::GetSuggestedFileName(nsAString& aSuggestedFileName)
+{
+ aSuggestedFileName = mSuggestedFileName;
+ return NS_OK;
+}
+
+nsresult nsExternalAppHandler::CreateTransfer()
+{
+ LOG(("nsExternalAppHandler::CreateTransfer"));
+
+ MOZ_ASSERT(NS_IsMainThread(), "Must create transfer on main thread");
+ // We are back from the helper app dialog (where the user chooses to save or
+ // open), but we aren't done processing the load. in this case, throw up a
+ // progress dialog so the user can see what's going on.
+ // Also, release our reference to mDialog. We don't need it anymore, and we
+ // need to break the reference cycle.
+ mDialog = nullptr;
+ if (!mDialogProgressListener) {
+ NS_WARNING("The dialog should nullify the dialog progress listener");
+ }
+ nsresult rv;
+
+ // We must be able to create an nsITransfer object. If not, it doesn't matter
+ // much that we can't launch the helper application or save to disk. Work on
+ // a local copy rather than mTransfer until we know we succeeded, to make it
+ // clearer that this function is re-entrant.
+ nsCOMPtr<nsITransfer> transfer = do_CreateInstance(
+ NS_TRANSFER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Initialize the download
+ nsCOMPtr<nsIURI> target;
+ rv = NS_NewFileURI(getter_AddRefs(target), mFinalFileDestination);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(mRequest);
+
+ rv = transfer->Init(mSourceUrl, target, EmptyString(),
+ mMimeInfo, mTimeDownloadStarted, mTempFile, this,
+ channel && NS_UsePrivateBrowsing(channel));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Now let's add the download to history
+ nsCOMPtr<nsIDownloadHistory> dh(do_GetService(NS_DOWNLOADHISTORY_CONTRACTID));
+ if (dh) {
+ if (channel && !NS_UsePrivateBrowsing(channel)) {
+ nsCOMPtr<nsIURI> referrer;
+ NS_GetReferrerFromChannel(channel, getter_AddRefs(referrer));
+
+ dh->AddDownload(mSourceUrl, referrer, mTimeDownloadStarted, target);
+ }
+ }
+
+ // If we were cancelled since creating the transfer, just return. It is
+ // always ok to return NS_OK if we are cancelled. Callers of this function
+ // must call Cancel if CreateTransfer fails, but there's no need to cancel
+ // twice.
+ if (mCanceled) {
+ return NS_OK;
+ }
+ rv = transfer->OnStateChange(nullptr, mRequest,
+ nsIWebProgressListener::STATE_START |
+ nsIWebProgressListener::STATE_IS_REQUEST |
+ nsIWebProgressListener::STATE_IS_NETWORK, NS_OK);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mCanceled) {
+ return NS_OK;
+ }
+
+ mRequest = nullptr;
+ // Finally, save the transfer to mTransfer.
+ mTransfer = transfer;
+ transfer = nullptr;
+
+ // While we were bringing up the progress dialog, we actually finished
+ // processing the url. If that's the case then mStopRequestIssued will be
+ // true and OnSaveComplete has been called.
+ if (mStopRequestIssued && !mSaver && mTransfer) {
+ NotifyTransfer(NS_OK);
+ }
+
+ return rv;
+}
+
+nsresult nsExternalAppHandler::CreateFailedTransfer(bool aIsPrivateBrowsing)
+{
+ nsresult rv;
+ nsCOMPtr<nsITransfer> transfer =
+ do_CreateInstance(NS_TRANSFER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If we don't have a download directory we're kinda screwed but it's OK
+ // we'll still report the error via the prompter.
+ nsCOMPtr<nsIFile> pseudoFile;
+ rv = GetDownloadDirectory(getter_AddRefs(pseudoFile), true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Append the default suggested filename. If the user restarts the transfer
+ // we will re-trigger a filename check anyway to ensure that it is unique.
+ rv = pseudoFile->Append(mSuggestedFileName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURI> pseudoTarget;
+ rv = NS_NewFileURI(getter_AddRefs(pseudoTarget), pseudoFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = transfer->Init(mSourceUrl, pseudoTarget, EmptyString(),
+ mMimeInfo, mTimeDownloadStarted, nullptr, this,
+ aIsPrivateBrowsing);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Our failed transfer is ready.
+ mTransfer = transfer.forget();
+
+ return NS_OK;
+}
+
+nsresult nsExternalAppHandler::SaveDestinationAvailable(nsIFile * aFile)
+{
+ if (aFile)
+ ContinueSave(aFile);
+ else
+ Cancel(NS_BINDING_ABORTED);
+
+ return NS_OK;
+}
+
+void nsExternalAppHandler::RequestSaveDestination(const nsAFlatString &aDefaultFile, const nsAFlatString &aFileExtension)
+{
+ // Display the dialog
+ // XXX Convert to use file picker? No, then embeddors could not do any sort of
+ // "AutoDownload" w/o showing a prompt
+ nsresult rv = NS_OK;
+ if (!mDialog) {
+ // Get helper app launcher dialog.
+ mDialog = do_CreateInstance(NS_HELPERAPPLAUNCHERDLG_CONTRACTID, &rv);
+ if (rv != NS_OK) {
+ Cancel(NS_BINDING_ABORTED);
+ return;
+ }
+ }
+
+ // we want to explicitly unescape aDefaultFile b4 passing into the dialog. we can't unescape
+ // it because the dialog is implemented by a JS component which doesn't have a window so no unescape routine is defined...
+
+ // Now, be sure to keep |this| alive, and the dialog
+ // If we don't do this, users that close the helper app dialog while the file
+ // picker is up would cause Cancel() to be called, and the dialog would be
+ // released, which would release this object too, which would crash.
+ // See Bug 249143
+ RefPtr<nsExternalAppHandler> kungFuDeathGrip(this);
+ nsCOMPtr<nsIHelperAppLauncherDialog> dlg(mDialog);
+
+ rv = dlg->PromptForSaveToFileAsync(this,
+ GetDialogParent(),
+ aDefaultFile.get(),
+ aFileExtension.get(),
+ mForceSave);
+ if (NS_FAILED(rv)) {
+ Cancel(NS_BINDING_ABORTED);
+ }
+}
+
+// SaveToDisk should only be called by the helper app dialog which allows
+// the user to say launch with application or save to disk. It doesn't actually
+// perform the save, it just prompts for the destination file name.
+NS_IMETHODIMP nsExternalAppHandler::SaveToDisk(nsIFile * aNewFileLocation, bool aRememberThisPreference)
+{
+ if (mCanceled)
+ return NS_OK;
+
+ mMimeInfo->SetPreferredAction(nsIMIMEInfo::saveToDisk);
+
+ if (!aNewFileLocation) {
+ if (mSuggestedFileName.IsEmpty())
+ RequestSaveDestination(mTempLeafName, mTempFileExtension);
+ else
+ {
+ nsAutoString fileExt;
+ int32_t pos = mSuggestedFileName.RFindChar('.');
+ if (pos >= 0)
+ mSuggestedFileName.Right(fileExt, mSuggestedFileName.Length() - pos);
+ if (fileExt.IsEmpty())
+ fileExt = mTempFileExtension;
+
+ RequestSaveDestination(mSuggestedFileName, fileExt);
+ }
+ } else {
+ ContinueSave(aNewFileLocation);
+ }
+
+ return NS_OK;
+}
+nsresult nsExternalAppHandler::ContinueSave(nsIFile * aNewFileLocation)
+{
+ if (mCanceled)
+ return NS_OK;
+
+ NS_PRECONDITION(aNewFileLocation, "Must be called with a non-null file");
+
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIFile> fileToUse = do_QueryInterface(aNewFileLocation);
+ mFinalFileDestination = do_QueryInterface(fileToUse);
+
+ // Move what we have in the final directory, but append .part
+ // to it, to indicate that it's unfinished. Do not call SetTarget on the
+ // saver if we are done (Finish has been called) but OnSaverComplete has not
+ // been called.
+ if (mFinalFileDestination && mSaver && !mStopRequestIssued)
+ {
+ nsCOMPtr<nsIFile> movedFile;
+ mFinalFileDestination->Clone(getter_AddRefs(movedFile));
+ if (movedFile) {
+ // Get the old leaf name and append .part to it
+ nsAutoString name;
+ mFinalFileDestination->GetLeafName(name);
+ name.AppendLiteral(".part");
+ movedFile->SetLeafName(name);
+
+ rv = mSaver->SetTarget(movedFile, true);
+ if (NS_FAILED(rv)) {
+ nsAutoString path;
+ mTempFile->GetPath(path);
+ SendStatusChange(kWriteError, rv, nullptr, path);
+ Cancel(rv);
+ return NS_OK;
+ }
+
+ mTempFile = movedFile;
+ }
+ }
+
+ // The helper app dialog has told us what to do and we have a final file
+ // destination.
+ rv = CreateTransfer();
+ // If we fail to create the transfer, Cancel.
+ if (NS_FAILED(rv)) {
+ Cancel(rv);
+ return rv;
+ }
+
+ // now that the user has chosen the file location to save to, it's okay to fire the refresh tag
+ // if there is one. We don't want to do this before the save as dialog goes away because this dialog
+ // is modal and we do bad things if you try to load a web page in the underlying window while a modal
+ // dialog is still up.
+ ProcessAnyRefreshTags();
+
+ return NS_OK;
+}
+
+
+// LaunchWithApplication should only be called by the helper app dialog which
+// allows the user to say launch with application or save to disk. It doesn't
+// actually perform launch with application.
+NS_IMETHODIMP nsExternalAppHandler::LaunchWithApplication(nsIFile * aApplication, bool aRememberThisPreference)
+{
+ if (mCanceled)
+ return NS_OK;
+
+ // user has chosen to launch using an application, fire any refresh tags now...
+ ProcessAnyRefreshTags();
+
+ if (mMimeInfo && aApplication) {
+ PlatformLocalHandlerApp_t *handlerApp =
+ new PlatformLocalHandlerApp_t(EmptyString(), aApplication);
+ mMimeInfo->SetPreferredApplicationHandler(handlerApp);
+ }
+
+ // Now check if the file is local, in which case we won't bother with saving
+ // it to a temporary directory and just launch it from where it is
+ nsCOMPtr<nsIFileURL> fileUrl(do_QueryInterface(mSourceUrl));
+ if (fileUrl && mIsFileChannel) {
+ Cancel(NS_BINDING_ABORTED);
+ nsCOMPtr<nsIFile> file;
+ nsresult rv = fileUrl->GetFile(getter_AddRefs(file));
+
+ if (NS_SUCCEEDED(rv)) {
+ rv = mMimeInfo->LaunchWithFile(file);
+ if (NS_SUCCEEDED(rv))
+ return NS_OK;
+ }
+ nsAutoString path;
+ if (file)
+ file->GetPath(path);
+ // If we get here, an error happened
+ SendStatusChange(kLaunchError, rv, nullptr, path);
+ return rv;
+ }
+
+ // Now that the user has elected to launch the downloaded file with a helper
+ // app, we're justified in removing the 'salted' name. We'll rename to what
+ // was specified in mSuggestedFileName after the download is done prior to
+ // launching the helper app. So that any existing file of that name won't be
+ // overwritten we call CreateUnique(). Also note that we use the same
+ // directory as originally downloaded so nsDownload can rename in place
+ // later.
+ nsCOMPtr<nsIFile> fileToUse;
+ (void) GetDownloadDirectory(getter_AddRefs(fileToUse));
+
+ if (mSuggestedFileName.IsEmpty()) {
+ // Keep using the leafname of the temp file, since we're just starting a helper
+ mSuggestedFileName = mTempLeafName;
+ }
+
+#ifdef XP_WIN
+ fileToUse->Append(mSuggestedFileName + mTempFileExtension);
+#else
+ fileToUse->Append(mSuggestedFileName);
+#endif
+
+ nsresult rv = fileToUse->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
+ if(NS_SUCCEEDED(rv)) {
+ mFinalFileDestination = do_QueryInterface(fileToUse);
+ // launch the progress window now that the user has picked the desired action.
+ rv = CreateTransfer();
+ if (NS_FAILED(rv)) {
+ Cancel(rv);
+ }
+ } else {
+ // Cancel the download and report an error. We do not want to end up in
+ // a state where it appears that we have a normal download that is
+ // pointing to a file that we did not actually create.
+ nsAutoString path;
+ mTempFile->GetPath(path);
+ SendStatusChange(kWriteError, rv, nullptr, path);
+ Cancel(rv);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsExternalAppHandler::Cancel(nsresult aReason)
+{
+ NS_ENSURE_ARG(NS_FAILED(aReason));
+
+ if (mCanceled) {
+ return NS_OK;
+ }
+ mCanceled = true;
+
+ if (mSaver) {
+ // We are still writing to the target file. Give the saver a chance to
+ // close the target file, then notify the transfer object if necessary in
+ // the OnSaveComplete callback.
+ mSaver->Finish(aReason);
+ mSaver = nullptr;
+ } else {
+ if (mStopRequestIssued && mTempFile) {
+ // This branch can only happen when the user cancels the helper app dialog
+ // when the request has completed. The temp file has to be removed here,
+ // because mSaver has been released at that time with the temp file left.
+ (void)mTempFile->Remove(false);
+ }
+
+ // Notify the transfer object that the download has been canceled, if the
+ // user has already chosen an action and we didn't notify already.
+ if (mTransfer) {
+ NotifyTransfer(aReason);
+ }
+ }
+
+ // Break our reference cycle with the helper app dialog (set up in
+ // OnStartRequest)
+ mDialog = nullptr;
+
+ mRequest = nullptr;
+
+ // Release the listener, to break the reference cycle with it (we are the
+ // observer of the listener).
+ mDialogProgressListener = nullptr;
+
+ return NS_OK;
+}
+
+void nsExternalAppHandler::ProcessAnyRefreshTags()
+{
+ // one last thing, try to see if the original window context supports a refresh interface...
+ // Sometimes, when you download content that requires an external handler, there is
+ // a refresh header associated with the download. This refresh header points to a page
+ // the content provider wants the user to see after they download the content. How do we
+ // pass this refresh information back to the caller? For now, try to get the refresh URI
+ // interface. If the window context where the request originated came from supports this
+ // then we can force it to process the refresh information (if there is any) from this channel.
+ if (mContentContext && mOriginalChannel) {
+ nsCOMPtr<nsIRefreshURI> refreshHandler (do_GetInterface(mContentContext));
+ if (refreshHandler) {
+ refreshHandler->SetupRefreshURI(mOriginalChannel);
+ }
+ mOriginalChannel = nullptr;
+ }
+}
+
+bool nsExternalAppHandler::GetNeverAskFlagFromPref(const char * prefName, const char * aContentType)
+{
+ // Search the obsolete pref strings.
+ nsAdoptingCString prefCString = Preferences::GetCString(prefName);
+ if (prefCString.IsEmpty()) {
+ // Default is true, if not found in the pref string.
+ return true;
+ }
+
+ NS_UnescapeURL(prefCString);
+ nsACString::const_iterator start, end;
+ prefCString.BeginReading(start);
+ prefCString.EndReading(end);
+ return !CaseInsensitiveFindInReadable(nsDependentCString(aContentType),
+ start, end);
+}
+
+nsresult nsExternalAppHandler::MaybeCloseWindow()
+{
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_GetInterface(mContentContext);
+ NS_ENSURE_STATE(window);
+
+ if (mShouldCloseWindow) {
+ // Reset the window context to the opener window so that the dependent
+ // dialogs have a parent
+ nsCOMPtr<nsPIDOMWindowOuter> opener = window->GetOpener();
+
+ if (opener && !opener->Closed()) {
+ mContentContext = do_GetInterface(opener);
+
+ // Now close the old window. Do it on a timer so that we don't run
+ // into issues trying to close the window before it has fully opened.
+ NS_ASSERTION(!mTimer, "mTimer was already initialized once!");
+ mTimer = do_CreateInstance("@mozilla.org/timer;1");
+ if (!mTimer) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mTimer->InitWithCallback(this, 0, nsITimer::TYPE_ONE_SHOT);
+ mWindowToClose = window;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsExternalAppHandler::Notify(nsITimer* timer)
+{
+ NS_ASSERTION(mWindowToClose, "No window to close after timer fired");
+
+ mWindowToClose->Close();
+ mWindowToClose = nullptr;
+ mTimer = nullptr;
+
+ return NS_OK;
+}
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// The following section contains our nsIMIMEService implementation and related methods.
+//
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// nsIMIMEService methods
+NS_IMETHODIMP nsExternalHelperAppService::GetFromTypeAndExtension(const nsACString& aMIMEType, const nsACString& aFileExt, nsIMIMEInfo **_retval)
+{
+ NS_PRECONDITION(!aMIMEType.IsEmpty() ||
+ !aFileExt.IsEmpty(),
+ "Give me something to work with");
+ LOG(("Getting mimeinfo from type '%s' ext '%s'\n",
+ PromiseFlatCString(aMIMEType).get(), PromiseFlatCString(aFileExt).get()));
+
+ *_retval = nullptr;
+
+ // OK... we need a type. Get one.
+ nsAutoCString typeToUse(aMIMEType);
+ if (typeToUse.IsEmpty()) {
+ nsresult rv = GetTypeFromExtension(aFileExt, typeToUse);
+ if (NS_FAILED(rv))
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // We promise to only send lower case mime types to the OS
+ ToLowerCase(typeToUse);
+
+ // (1) Ask the OS for a mime info
+ bool found;
+ *_retval = GetMIMEInfoFromOS(typeToUse, aFileExt, &found).take();
+ LOG(("OS gave back 0x%p - found: %i\n", *_retval, found));
+ // If we got no mimeinfo, something went wrong. Probably lack of memory.
+ if (!*_retval)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ // (2) Now, let's see if we can find something in our datastore
+ // This will not overwrite the OS information that interests us
+ // (i.e. default application, default app. description)
+ nsresult rv;
+ nsCOMPtr<nsIHandlerService> handlerSvc = do_GetService(NS_HANDLERSERVICE_CONTRACTID);
+ if (handlerSvc) {
+ bool hasHandler = false;
+ (void) handlerSvc->Exists(*_retval, &hasHandler);
+ if (hasHandler) {
+ rv = handlerSvc->FillHandlerInfo(*_retval, EmptyCString());
+ LOG(("Data source: Via type: retval 0x%08x\n", rv));
+ } else {
+ rv = NS_ERROR_NOT_AVAILABLE;
+ }
+
+ found = found || NS_SUCCEEDED(rv);
+
+ if (!found || NS_FAILED(rv)) {
+ // No type match, try extension match
+ if (!aFileExt.IsEmpty()) {
+ nsAutoCString overrideType;
+ rv = handlerSvc->GetTypeFromExtension(aFileExt, overrideType);
+ if (NS_SUCCEEDED(rv) && !overrideType.IsEmpty()) {
+ // We can't check handlerSvc->Exists() here, because we have a
+ // overideType. That's ok, it just results in some console noise.
+ // (If there's no handler for the override type, it throws)
+ rv = handlerSvc->FillHandlerInfo(*_retval, overrideType);
+ LOG(("Data source: Via ext: retval 0x%08x\n", rv));
+ found = found || NS_SUCCEEDED(rv);
+ }
+ }
+ }
+ }
+
+ // (3) No match yet. Ask extras.
+ if (!found) {
+ rv = NS_ERROR_FAILURE;
+#ifdef XP_WIN
+ /* XXX Gross hack to wallpaper over the most common Win32
+ * extension issues caused by the fix for bug 116938. See bug
+ * 120327, comment 271 for why this is needed. Not even sure we
+ * want to remove this once we have fixed all this stuff to work
+ * right; any info we get from extras on this type is pretty much
+ * useless....
+ */
+ if (!typeToUse.Equals(APPLICATION_OCTET_STREAM, nsCaseInsensitiveCStringComparator()))
+#endif
+ rv = FillMIMEInfoForMimeTypeFromExtras(typeToUse, *_retval);
+ LOG(("Searched extras (by type), rv 0x%08X\n", rv));
+ // If that didn't work out, try file extension from extras
+ if (NS_FAILED(rv) && !aFileExt.IsEmpty()) {
+ rv = FillMIMEInfoForExtensionFromExtras(aFileExt, *_retval);
+ LOG(("Searched extras (by ext), rv 0x%08X\n", rv));
+ }
+ // If that still didn't work, set the file description to "ext File"
+ if (NS_FAILED(rv) && !aFileExt.IsEmpty()) {
+ // XXXzpao This should probably be localized
+ nsAutoCString desc(aFileExt);
+ desc.AppendLiteral(" File");
+ (*_retval)->SetDescription(NS_ConvertASCIItoUTF16(desc));
+ LOG(("Falling back to 'File' file description\n"));
+ }
+ }
+
+ // Finally, check if we got a file extension and if yes, if it is an
+ // extension on the mimeinfo, in which case we want it to be the primary one
+ if (!aFileExt.IsEmpty()) {
+ bool matches = false;
+ (*_retval)->ExtensionExists(aFileExt, &matches);
+ LOG(("Extension '%s' matches mime info: %i\n", PromiseFlatCString(aFileExt).get(), matches));
+ if (matches)
+ (*_retval)->SetPrimaryExtension(aFileExt);
+ }
+
+ if (LOG_ENABLED()) {
+ nsAutoCString type;
+ (*_retval)->GetMIMEType(type);
+
+ nsAutoCString ext;
+ (*_retval)->GetPrimaryExtension(ext);
+ LOG(("MIME Info Summary: Type '%s', Primary Ext '%s'\n", type.get(), ext.get()));
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsExternalHelperAppService::GetTypeFromExtension(const nsACString& aFileExt,
+ nsACString& aContentType)
+{
+ // OK. We want to try the following sources of mimetype information, in this order:
+ // 1. defaultMimeEntries array
+ // 2. OS-provided information
+ // 3. our "extras" array
+ // 4. Information from plugins
+ // 5. The "ext-to-type-mapping" category
+ // Note that, we are intentionally not looking at the handler service, because
+ // that can be affected by websites, which leads to undesired behavior.
+
+ // Early return if called with an empty extension parameter
+ if (aFileExt.IsEmpty()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // First of all, check our default entries
+ for (auto& entry : defaultMimeEntries) {
+ if (aFileExt.LowerCaseEqualsASCII(entry.mFileExtension)) {
+ aContentType = entry.mMimeType;
+ return NS_OK;
+ }
+ }
+
+ // Ask OS.
+ if (GetMIMETypeFromOSForExtension(aFileExt, aContentType)) {
+ return NS_OK;
+ }
+
+ // Check extras array.
+ bool found = GetTypeFromExtras(aFileExt, aContentType);
+ if (found) {
+ return NS_OK;
+ }
+
+ // Try the plugins
+ RefPtr<nsPluginHost> pluginHost = nsPluginHost::GetInst();
+ if (pluginHost &&
+ pluginHost->HavePluginForExtension(aFileExt, aContentType)) {
+ return NS_OK;
+ }
+
+ // Let's see if an extension added something
+ nsCOMPtr<nsICategoryManager> catMan(
+ do_GetService("@mozilla.org/categorymanager;1"));
+ if (catMan) {
+ // The extension in the category entry is always stored as lowercase
+ nsAutoCString lowercaseFileExt(aFileExt);
+ ToLowerCase(lowercaseFileExt);
+ // Read the MIME type from the category entry, if available
+ nsXPIDLCString type;
+ nsresult rv = catMan->GetCategoryEntry("ext-to-type-mapping",
+ lowercaseFileExt.get(),
+ getter_Copies(type));
+ if (NS_SUCCEEDED(rv)) {
+ aContentType = type;
+ return NS_OK;
+ }
+ }
+
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP nsExternalHelperAppService::GetPrimaryExtension(const nsACString& aMIMEType, const nsACString& aFileExt, nsACString& _retval)
+{
+ NS_ENSURE_ARG(!aMIMEType.IsEmpty());
+
+ nsCOMPtr<nsIMIMEInfo> mi;
+ nsresult rv = GetFromTypeAndExtension(aMIMEType, aFileExt, getter_AddRefs(mi));
+ if (NS_FAILED(rv))
+ return rv;
+
+ return mi->GetPrimaryExtension(_retval);
+}
+
+NS_IMETHODIMP nsExternalHelperAppService::GetTypeFromURI(nsIURI *aURI, nsACString& aContentType)
+{
+ NS_ENSURE_ARG_POINTER(aURI);
+ nsresult rv = NS_ERROR_NOT_AVAILABLE;
+ aContentType.Truncate();
+
+ // First look for a file to use. If we have one, we just use that.
+ nsCOMPtr<nsIFileURL> fileUrl = do_QueryInterface(aURI);
+ if (fileUrl) {
+ nsCOMPtr<nsIFile> file;
+ rv = fileUrl->GetFile(getter_AddRefs(file));
+ if (NS_SUCCEEDED(rv)) {
+ rv = GetTypeFromFile(file, aContentType);
+ if (NS_SUCCEEDED(rv)) {
+ // we got something!
+ return rv;
+ }
+ }
+ }
+
+ // Now try to get an nsIURL so we don't have to do our own parsing
+ nsCOMPtr<nsIURL> url = do_QueryInterface(aURI);
+ if (url) {
+ nsAutoCString ext;
+ rv = url->GetFileExtension(ext);
+ if (NS_FAILED(rv))
+ return rv;
+ if (ext.IsEmpty())
+ return NS_ERROR_NOT_AVAILABLE;
+
+ UnescapeFragment(ext, url, ext);
+
+ return GetTypeFromExtension(ext, aContentType);
+ }
+
+ // no url, let's give the raw spec a shot
+ nsAutoCString specStr;
+ rv = aURI->GetSpec(specStr);
+ if (NS_FAILED(rv))
+ return rv;
+ UnescapeFragment(specStr, aURI, specStr);
+
+ // find the file extension (if any)
+ int32_t extLoc = specStr.RFindChar('.');
+ int32_t specLength = specStr.Length();
+ if (-1 != extLoc &&
+ extLoc != specLength - 1 &&
+ // nothing over 20 chars long can be sanely considered an
+ // extension.... Dat dere would be just data.
+ specLength - extLoc < 20)
+ {
+ return GetTypeFromExtension(Substring(specStr, extLoc + 1), aContentType);
+ }
+
+ // We found no information; say so.
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP nsExternalHelperAppService::GetTypeFromFile(nsIFile* aFile, nsACString& aContentType)
+{
+ NS_ENSURE_ARG_POINTER(aFile);
+ nsresult rv;
+
+ // Get the Extension
+ nsAutoString fileName;
+ rv = aFile->GetLeafName(fileName);
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString fileExt;
+ if (!fileName.IsEmpty())
+ {
+ int32_t len = fileName.Length();
+ for (int32_t i = len; i >= 0; i--)
+ {
+ if (fileName[i] == char16_t('.'))
+ {
+ CopyUTF16toUTF8(fileName.get() + i + 1, fileExt);
+ break;
+ }
+ }
+ }
+
+ if (fileExt.IsEmpty())
+ return NS_ERROR_FAILURE;
+
+ return GetTypeFromExtension(fileExt, aContentType);
+}
+
+nsresult nsExternalHelperAppService::FillMIMEInfoForMimeTypeFromExtras(
+ const nsACString& aContentType, nsIMIMEInfo * aMIMEInfo)
+{
+ NS_ENSURE_ARG( aMIMEInfo );
+
+ NS_ENSURE_ARG( !aContentType.IsEmpty() );
+
+ // Look for default entry with matching mime type.
+ nsAutoCString MIMEType(aContentType);
+ ToLowerCase(MIMEType);
+ int32_t numEntries = ArrayLength(extraMimeEntries);
+ for (int32_t index = 0; index < numEntries; index++)
+ {
+ if ( MIMEType.Equals(extraMimeEntries[index].mMimeType) )
+ {
+ // This is the one. Set attributes appropriately.
+ aMIMEInfo->SetFileExtensions(nsDependentCString(extraMimeEntries[index].mFileExtensions));
+ aMIMEInfo->SetDescription(NS_ConvertASCIItoUTF16(extraMimeEntries[index].mDescription));
+ return NS_OK;
+ }
+ }
+
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+nsresult nsExternalHelperAppService::FillMIMEInfoForExtensionFromExtras(
+ const nsACString& aExtension, nsIMIMEInfo * aMIMEInfo)
+{
+ nsAutoCString type;
+ bool found = GetTypeFromExtras(aExtension, type);
+ if (!found)
+ return NS_ERROR_NOT_AVAILABLE;
+ return FillMIMEInfoForMimeTypeFromExtras(type, aMIMEInfo);
+}
+
+bool nsExternalHelperAppService::GetTypeFromExtras(const nsACString& aExtension, nsACString& aMIMEType)
+{
+ NS_ASSERTION(!aExtension.IsEmpty(), "Empty aExtension parameter!");
+
+ // Look for default entry with matching extension.
+ nsDependentCString::const_iterator start, end, iter;
+ int32_t numEntries = ArrayLength(extraMimeEntries);
+ for (int32_t index = 0; index < numEntries; index++)
+ {
+ nsDependentCString extList(extraMimeEntries[index].mFileExtensions);
+ extList.BeginReading(start);
+ extList.EndReading(end);
+ iter = start;
+ while (start != end)
+ {
+ FindCharInReadable(',', iter, end);
+ if (Substring(start, iter).Equals(aExtension,
+ nsCaseInsensitiveCStringComparator()))
+ {
+ aMIMEType = extraMimeEntries[index].mMimeType;
+ return true;
+ }
+ if (iter != end) {
+ ++iter;
+ }
+ start = iter;
+ }
+ }
+
+ return false;
+}
+
+bool
+nsExternalHelperAppService::GetMIMETypeFromOSForExtension(const nsACString& aExtension, nsACString& aMIMEType)
+{
+ bool found = false;
+ nsCOMPtr<nsIMIMEInfo> mimeInfo = GetMIMEInfoFromOS(EmptyCString(), aExtension, &found);
+ return found && mimeInfo && NS_SUCCEEDED(mimeInfo->GetMIMEType(aMIMEType));
+}
diff --git a/uriloader/exthandler/nsExternalHelperAppService.h b/uriloader/exthandler/nsExternalHelperAppService.h
new file mode 100644
index 0000000000..ceec66661d
--- /dev/null
+++ b/uriloader/exthandler/nsExternalHelperAppService.h
@@ -0,0 +1,498 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsExternalHelperAppService_h__
+#define nsExternalHelperAppService_h__
+
+#include "mozilla/Logging.h"
+#include "prtime.h"
+
+#include "nsIExternalHelperAppService.h"
+#include "nsIExternalProtocolService.h"
+#include "nsIWebProgressListener2.h"
+#include "nsIHelperAppLauncherDialog.h"
+
+#include "nsIMIMEInfo.h"
+#include "nsIMIMEService.h"
+#include "nsIStreamListener.h"
+#include "nsIFile.h"
+#include "nsIFileStreams.h"
+#include "nsIOutputStream.h"
+#include "nsString.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIChannel.h"
+#include "nsITimer.h"
+#include "nsIBackgroundFileSaver.h"
+
+#include "nsIHandlerService.h"
+#include "nsCOMPtr.h"
+#include "nsIObserver.h"
+#include "nsCOMArray.h"
+#include "nsWeakReference.h"
+#include "nsIPrompt.h"
+#include "nsAutoPtr.h"
+#include "mozilla/Attributes.h"
+
+class nsExternalAppHandler;
+class nsIMIMEInfo;
+class nsITransfer;
+class nsPIDOMWindowOuter;
+
+/**
+ * The helper app service. Responsible for handling content that Mozilla
+ * itself can not handle
+ */
+class nsExternalHelperAppService
+: public nsIExternalHelperAppService,
+ public nsPIExternalAppLauncher,
+ public nsIExternalProtocolService,
+ public nsIMIMEService,
+ public nsIObserver,
+ public nsSupportsWeakReference
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIEXTERNALHELPERAPPSERVICE
+ NS_DECL_NSPIEXTERNALAPPLAUNCHER
+ NS_DECL_NSIEXTERNALPROTOCOLSERVICE
+ NS_DECL_NSIMIMESERVICE
+ NS_DECL_NSIOBSERVER
+
+ nsExternalHelperAppService();
+
+ /**
+ * Initializes internal state. Will be called automatically when
+ * this service is first instantiated.
+ */
+ MOZ_MUST_USE nsresult Init();
+
+ /**
+ * Given a mimetype and an extension, looks up a mime info from the OS.
+ * The mime type is given preference. This function follows the same rules
+ * as nsIMIMEService::GetFromTypeAndExtension.
+ * This is supposed to be overridden by the platform-specific
+ * nsOSHelperAppService!
+ * @param aFileExt The file extension; may be empty. UTF-8 encoded.
+ * @param [out] aFound
+ * Should be set to true if the os has a mapping, to
+ * false otherwise. Must not be null.
+ * @return A MIMEInfo. This function must return a MIMEInfo object if it
+ * can allocate one. The only justifiable reason for not
+ * returning one is an out-of-memory error.
+ * If null, the value of aFound is unspecified.
+ */
+ virtual already_AddRefed<nsIMIMEInfo> GetMIMEInfoFromOS(const nsACString& aMIMEType,
+ const nsACString& aFileExt,
+ bool * aFound) = 0;
+
+ /**
+ * Given a string identifying an application, create an nsIFile representing
+ * it. This function should look in $PATH for the application.
+ * The base class implementation will first try to interpret platformAppPath
+ * as an absolute path, and if that fails it will look for a file next to the
+ * mozilla executable. Subclasses can override this method if they want a
+ * different behaviour.
+ * @param platformAppPath A platform specific path to an application that we
+ * got out of the rdf data source. This can be a mac
+ * file spec, a unix path or a windows path depending
+ * on the platform
+ * @param aFile [out] An nsIFile representation of that platform
+ * application path.
+ */
+ virtual nsresult GetFileTokenForPath(const char16_t * platformAppPath,
+ nsIFile ** aFile);
+
+ virtual nsresult OSProtocolHandlerExists(const char *aScheme,
+ bool *aExists) = 0;
+
+ /**
+ * Given an extension, get a MIME type string. If not overridden by
+ * the OS-specific nsOSHelperAppService, will call into GetMIMEInfoFromOS
+ * with an empty mimetype.
+ * @return true if we successfully found a mimetype.
+ */
+ virtual bool GetMIMETypeFromOSForExtension(const nsACString& aExtension,
+ nsACString& aMIMEType);
+
+protected:
+ virtual ~nsExternalHelperAppService();
+
+ /**
+ * Searches the "extra" array of MIMEInfo objects for an object
+ * with a specific type. If found, it will modify the passed-in
+ * MIMEInfo. Otherwise, it will return an error and the MIMEInfo
+ * will be untouched.
+ * @param aContentType The type to search for.
+ * @param aMIMEInfo [inout] The mime info, if found
+ */
+ nsresult FillMIMEInfoForMimeTypeFromExtras(
+ const nsACString& aContentType, nsIMIMEInfo * aMIMEInfo);
+ /**
+ * Searches the "extra" array of MIMEInfo objects for an object
+ * with a specific extension.
+ *
+ * Does not change the MIME Type of the MIME Info.
+ *
+ * @see FillMIMEInfoForMimeTypeFromExtras
+ */
+ nsresult FillMIMEInfoForExtensionFromExtras(
+ const nsACString& aExtension, nsIMIMEInfo * aMIMEInfo);
+
+ /**
+ * Searches the "extra" array for a MIME type, and gets its extension.
+ * @param aExtension The extension to search for
+ * @param aMIMEType [out] The found MIME type.
+ * @return true if the extension was found, false otherwise.
+ */
+ bool GetTypeFromExtras(const nsACString& aExtension,
+ nsACString& aMIMEType);
+
+ /**
+ * Logging Module. Usage: set MOZ_LOG=HelperAppService:level, where level
+ * should be 2 for errors, 3 for debug messages from the cross- platform
+ * nsExternalHelperAppService, and 4 for os-specific debug messages.
+ */
+ static mozilla::LazyLogModule mLog;
+
+ // friend, so that it can access the nspr log module.
+ friend class nsExternalAppHandler;
+
+ /**
+ * Helper function for ExpungeTemporaryFiles and ExpungeTemporaryPrivateFiles
+ */
+ static void ExpungeTemporaryFilesHelper(nsCOMArray<nsIFile> &fileList);
+ /**
+ * Helper function for DeleteTemporaryFileOnExit and DeleteTemporaryPrivateFileWhenPossible
+ */
+ static nsresult DeleteTemporaryFileHelper(nsIFile* aTemporaryFile,
+ nsCOMArray<nsIFile> &aFileList);
+ /**
+ * Functions related to the tempory file cleanup service provided by
+ * nsExternalHelperAppService
+ */
+ void ExpungeTemporaryFiles();
+ /**
+ * Functions related to the tempory file cleanup service provided by
+ * nsExternalHelperAppService (for the temporary files added during
+ * the private browsing mode)
+ */
+ void ExpungeTemporaryPrivateFiles();
+
+ /**
+ * Array for the files that should be deleted
+ */
+ nsCOMArray<nsIFile> mTemporaryFilesList;
+ /**
+ * Array for the files that should be deleted (for the temporary files
+ * added during the private browsing mode)
+ */
+ nsCOMArray<nsIFile> mTemporaryPrivateFilesList;
+
+private:
+ nsresult DoContentContentProcessHelper(const nsACString& aMimeContentType,
+ nsIRequest *aRequest,
+ nsIInterfaceRequestor *aContentContext,
+ bool aForceSave,
+ nsIInterfaceRequestor *aWindowContext,
+ nsIStreamListener ** aStreamListener);
+};
+
+/**
+ * An external app handler is just a small little class that presents itself as
+ * a nsIStreamListener. It saves the incoming data into a temp file. The handler
+ * is bound to an application when it is created. When it receives an
+ * OnStopRequest it launches the application using the temp file it has
+ * stored the data into. We create a handler every time we have to process
+ * data using a helper app.
+ */
+class nsExternalAppHandler final : public nsIStreamListener,
+ public nsIHelperAppLauncher,
+ public nsITimerCallback,
+ public nsIBackgroundFileSaverObserver
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSIHELPERAPPLAUNCHER
+ NS_DECL_NSICANCELABLE
+ NS_DECL_NSITIMERCALLBACK
+ NS_DECL_NSIBACKGROUNDFILESAVEROBSERVER
+
+ /**
+ * @param aMIMEInfo MIMEInfo object, representing the type of the
+ * content that should be handled
+ * @param aFileExtension The extension we need to append to our temp file,
+ * INCLUDING the ".". e.g. .mp3
+ * @param aContentContext dom Window context, as passed to DoContent.
+ * @param aWindowContext Top level window context used in dialog parenting,
+ * as passed to DoContent. This parameter may be null,
+ * in which case dialogs will be parented to
+ * aContentContext.
+ * @param mExtProtSvc nsExternalHelperAppService on creation
+ * @param aFileName The filename to use
+ * @param aReason A constant from nsIHelperAppLauncherDialog indicating
+ * why the request is handled by a helper app.
+ */
+ nsExternalAppHandler(nsIMIMEInfo * aMIMEInfo, const nsCSubstring& aFileExtension,
+ nsIInterfaceRequestor * aContentContext,
+ nsIInterfaceRequestor * aWindowContext,
+ nsExternalHelperAppService * aExtProtSvc,
+ const nsAString& aFilename,
+ uint32_t aReason, bool aForceSave);
+
+ /**
+ * Clean up after the request was diverted to the parent process.
+ */
+ void DidDivertRequest(nsIRequest *request);
+
+ /**
+ * Apply content conversions if needed.
+ */
+ void MaybeApplyDecodingForExtension(nsIRequest *request);
+
+protected:
+ ~nsExternalAppHandler();
+
+ nsIInterfaceRequestor* GetDialogParent() {
+ return mWindowContext ? mWindowContext : mContentContext;
+ }
+
+ nsCOMPtr<nsIFile> mTempFile;
+ nsCOMPtr<nsIURI> mSourceUrl;
+ nsString mTempFileExtension;
+ nsString mTempLeafName;
+
+ /**
+ * The MIME Info for this load. Will never be null.
+ */
+ nsCOMPtr<nsIMIMEInfo> mMimeInfo;
+
+ /**
+ * The dom window associated with this request to handle content.
+ */
+ nsCOMPtr<nsIInterfaceRequestor> mContentContext;
+
+ /**
+ * If set, the parent window helper app dialogs and file pickers
+ * should use in parenting. If null, we use mContentContext.
+ */
+ nsCOMPtr<nsIInterfaceRequestor> mWindowContext;
+
+ /**
+ * Used to close the window on a timer, to avoid any exceptions that are
+ * thrown if we try to close the window before it's fully loaded.
+ */
+ nsCOMPtr<nsPIDOMWindowOuter> mWindowToClose;
+ nsCOMPtr<nsITimer> mTimer;
+
+ /**
+ * The following field is set if we were processing an http channel that had
+ * a content disposition header which specified the SUGGESTED file name we
+ * should present to the user in the save to disk dialog.
+ */
+ nsString mSuggestedFileName;
+
+ /**
+ * If set, this handler should forcibly save the file to disk regardless of
+ * MIME info settings or anything else, without ever popping up the
+ * unknown content type handling dialog.
+ */
+ bool mForceSave;
+
+ /**
+ * The canceled flag is set if the user canceled the launching of this
+ * application before we finished saving the data to a temp file.
+ */
+ bool mCanceled;
+
+ /**
+ * This is set based on whether the channel indicates that a new window
+ * was opened specifically for this download. If so, then we
+ * close it.
+ */
+ bool mShouldCloseWindow;
+
+ /**
+ * True if a stop request has been issued.
+ */
+ bool mStopRequestIssued;
+
+ bool mIsFileChannel;
+
+ /**
+ * One of the REASON_ constants from nsIHelperAppLauncherDialog. Indicates the
+ * reason the dialog was shown (unknown content type, server requested it,
+ * etc).
+ */
+ uint32_t mReason;
+
+ /**
+ * Track the executable-ness of the temporary file.
+ */
+ bool mTempFileIsExecutable;
+
+ PRTime mTimeDownloadStarted;
+ int64_t mContentLength;
+ int64_t mProgress; /**< Number of bytes received (for sending progress notifications). */
+
+ /**
+ * When we are told to save the temp file to disk (in a more permament
+ * location) before we are done writing the content to a temp file, then
+ * we need to remember the final destination until we are ready to use it.
+ */
+ nsCOMPtr<nsIFile> mFinalFileDestination;
+
+ uint32_t mBufferSize;
+
+ /**
+ * This object handles saving the data received from the network to a
+ * temporary location first, and then move the file to its final location,
+ * doing all the input/output on a background thread.
+ */
+ nsCOMPtr<nsIBackgroundFileSaver> mSaver;
+
+ /**
+ * Stores the SHA-256 hash associated with the file that we downloaded.
+ */
+ nsAutoCString mHash;
+ /**
+ * Stores the signature information of the downloaded file in an nsIArray of
+ * nsIX509CertList of nsIX509Cert. If the file is unsigned this will be
+ * empty.
+ */
+ nsCOMPtr<nsIArray> mSignatureInfo;
+ /**
+ * Stores the redirect information associated with the channel.
+ */
+ nsCOMPtr<nsIArray> mRedirects;
+ /**
+ * Creates the temporary file for the download and an output stream for it.
+ * Upon successful return, both mTempFile and mSaver will be valid.
+ */
+ nsresult SetUpTempFile(nsIChannel * aChannel);
+ /**
+ * When we download a helper app, we are going to retarget all load
+ * notifications into our own docloader and load group instead of
+ * using the window which initiated the load....RetargetLoadNotifications
+ * contains that information...
+ */
+ void RetargetLoadNotifications(nsIRequest *request);
+ /**
+ * Once the user tells us how they want to dispose of the content
+ * create an nsITransfer so they know what's going on. If this fails, the
+ * caller MUST call Cancel.
+ */
+ nsresult CreateTransfer();
+
+ /**
+ * If we fail to create the necessary temporary file to initiate a transfer
+ * we will report the failure by creating a failed nsITransfer.
+ */
+ nsresult CreateFailedTransfer(bool aIsPrivateBrowsing);
+
+ /*
+ * The following two functions are part of the split of SaveToDisk
+ * to make it async, and works as following:
+ *
+ * SaveToDisk -------> RequestSaveDestination
+ * .
+ * .
+ * v
+ * ContinueSave <------- SaveDestinationAvailable
+ */
+
+ /**
+ * This is called by SaveToDisk to decide what's the final
+ * file destination chosen by the user or by auto-download settings.
+ */
+ void RequestSaveDestination(const nsAFlatString &aDefaultFile,
+ const nsAFlatString &aDefaultFileExt);
+
+ /**
+ * When SaveToDisk is called, it possibly delegates to RequestSaveDestination
+ * to decide the file destination. ContinueSave must then be called when
+ * the final destination is finally known.
+ * @param aFile The file that was chosen as the final destination.
+ * Must not be null.
+ */
+ nsresult ContinueSave(nsIFile* aFile);
+
+ /**
+ * After we're done prompting the user for any information, if the original
+ * channel had a refresh url associated with it (which might point to a
+ * "thank you for downloading" kind of page, then process that....It is safe
+ * to invoke this method multiple times. We'll clear mOriginalChannel after
+ * it's called and this ensures we won't call it again....
+ */
+ void ProcessAnyRefreshTags();
+
+ /**
+ * Notify our nsITransfer object that we are done with the download. This is
+ * always called after the target file has been closed.
+ *
+ * @param aStatus
+ * NS_OK for success, or a failure code if the download failed.
+ * A partially downloaded file may still be available in this case.
+ */
+ void NotifyTransfer(nsresult aStatus);
+
+ /**
+ * Helper routine that searches a pref string for a given mime type
+ */
+ bool GetNeverAskFlagFromPref(const char * prefName, const char * aContentType);
+
+ /**
+ * Helper routine to ensure mSuggestedFileName is "correct";
+ * this ensures that mTempFileExtension only contains an extension when it
+ * is different from mSuggestedFileName's extension.
+ */
+ void EnsureSuggestedFileName();
+
+ typedef enum { kReadError, kWriteError, kLaunchError } ErrorType;
+ /**
+ * Utility function to send proper error notification to web progress listener
+ */
+ void SendStatusChange(ErrorType type, nsresult aStatus, nsIRequest *aRequest, const nsAFlatString &path);
+
+ /**
+ * Closes the window context if it does not have a refresh header
+ * and it never displayed content before the external helper app
+ * service was invoked.
+ */
+ nsresult MaybeCloseWindow();
+
+ /**
+ * Set in nsHelperDlgApp.js. This is always null after the user has chosen an
+ * action.
+ */
+ nsCOMPtr<nsIWebProgressListener2> mDialogProgressListener;
+ /**
+ * Set once the user has chosen an action. This is null after the download
+ * has been canceled or completes.
+ */
+ nsCOMPtr<nsITransfer> mTransfer;
+
+ nsCOMPtr<nsIChannel> mOriginalChannel; /**< in the case of a redirect, this will be the pre-redirect channel. */
+ nsCOMPtr<nsIHelperAppLauncherDialog> mDialog;
+
+ /**
+ * Keep request alive in case when helper non-modal dialog shown.
+ * Thus in OnStopRequest the mRequest will not be set to null (it will be set to null further).
+ */
+ bool mKeepRequestAlive;
+
+ /**
+ * The request that's being loaded. Initialized in OnStartRequest.
+ * Nulled out in OnStopRequest or once we know what we're doing
+ * with the data, whichever happens later.
+ */
+ nsCOMPtr<nsIRequest> mRequest;
+
+ RefPtr<nsExternalHelperAppService> mExtProtSvc;
+};
+
+#endif // nsExternalHelperAppService_h__
diff --git a/uriloader/exthandler/nsExternalProtocolHandler.cpp b/uriloader/exthandler/nsExternalProtocolHandler.cpp
new file mode 100644
index 0000000000..5b3d07b4ce
--- /dev/null
+++ b/uriloader/exthandler/nsExternalProtocolHandler.cpp
@@ -0,0 +1,561 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim:set ts=2 sts=2 sw=2 et cin:
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIURI.h"
+#include "nsIURL.h"
+#include "nsExternalProtocolHandler.h"
+#include "nsXPIDLString.h"
+#include "nsReadableUtils.h"
+#include "nsCOMPtr.h"
+#include "nsContentUtils.h"
+#include "nsIServiceManager.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIStringBundle.h"
+#include "nsIPrefService.h"
+#include "nsIPrompt.h"
+#include "nsNetUtil.h"
+#include "nsContentSecurityManager.h"
+#include "nsExternalHelperAppService.h"
+
+// used to dispatch urls to default protocol handlers
+#include "nsCExternalHandlerService.h"
+#include "nsIExternalProtocolService.h"
+#include "nsIChildChannel.h"
+#include "nsIParentChannel.h"
+
+class nsILoadInfo;
+
+////////////////////////////////////////////////////////////////////////
+// a stub channel implemenation which will map calls to AsyncRead and OpenInputStream
+// to calls in the OS for loading the url.
+////////////////////////////////////////////////////////////////////////
+
+class nsExtProtocolChannel : public nsIChannel,
+ public nsIChildChannel,
+ public nsIParentChannel
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSICHANNEL
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIREQUEST
+ NS_DECL_NSICHILDCHANNEL
+ NS_DECL_NSIPARENTCHANNEL
+
+ nsExtProtocolChannel(nsIURI* aURI, nsILoadInfo* aLoadInfo);
+
+private:
+ virtual ~nsExtProtocolChannel();
+
+ nsresult OpenURL();
+ void Finish(nsresult aResult);
+
+ nsCOMPtr<nsIURI> mUrl;
+ nsCOMPtr<nsIURI> mOriginalURI;
+ nsresult mStatus;
+ nsLoadFlags mLoadFlags;
+ bool mWasOpened;
+ // Set true (as a result of ConnectParent invoked from child process)
+ // when this channel is on the parent process and is being used as
+ // a redirect target channel. It turns AsyncOpen into a no-op since
+ // we do it on the child.
+ bool mConnectedParent;
+
+ nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
+ nsCOMPtr<nsILoadGroup> mLoadGroup;
+ nsCOMPtr<nsILoadInfo> mLoadInfo;
+};
+
+NS_IMPL_ADDREF(nsExtProtocolChannel)
+NS_IMPL_RELEASE(nsExtProtocolChannel)
+
+NS_INTERFACE_MAP_BEGIN(nsExtProtocolChannel)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIRequest)
+ NS_INTERFACE_MAP_ENTRY(nsIChildChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIParentChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
+ NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
+NS_INTERFACE_MAP_END_THREADSAFE
+
+nsExtProtocolChannel::nsExtProtocolChannel(nsIURI* aURI,
+ nsILoadInfo* aLoadInfo)
+ : mUrl(aURI)
+ , mOriginalURI(aURI)
+ , mStatus(NS_OK)
+ , mLoadFlags(nsIRequest::LOAD_NORMAL)
+ , mWasOpened(false)
+ , mConnectedParent(false)
+ , mLoadInfo(aLoadInfo)
+{
+}
+
+nsExtProtocolChannel::~nsExtProtocolChannel()
+{}
+
+NS_IMETHODIMP nsExtProtocolChannel::GetLoadGroup(nsILoadGroup * *aLoadGroup)
+{
+ NS_IF_ADDREF(*aLoadGroup = mLoadGroup);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::SetLoadGroup(nsILoadGroup * aLoadGroup)
+{
+ mLoadGroup = aLoadGroup;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::GetNotificationCallbacks(nsIInterfaceRequestor* *aCallbacks)
+{
+ NS_IF_ADDREF(*aCallbacks = mCallbacks);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::SetNotificationCallbacks(nsIInterfaceRequestor* aCallbacks)
+{
+ mCallbacks = aCallbacks;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsExtProtocolChannel::GetSecurityInfo(nsISupports * *aSecurityInfo)
+{
+ *aSecurityInfo = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::GetOriginalURI(nsIURI* *aURI)
+{
+ NS_ADDREF(*aURI = mOriginalURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::SetOriginalURI(nsIURI* aURI)
+{
+ NS_ENSURE_ARG_POINTER(aURI);
+ mOriginalURI = aURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::GetURI(nsIURI* *aURI)
+{
+ *aURI = mUrl;
+ NS_IF_ADDREF(*aURI);
+ return NS_OK;
+}
+
+nsresult nsExtProtocolChannel::OpenURL()
+{
+ nsresult rv = NS_ERROR_FAILURE;
+ nsCOMPtr<nsIExternalProtocolService> extProtService (do_GetService(NS_EXTERNALPROTOCOLSERVICE_CONTRACTID));
+
+ if (extProtService)
+ {
+#ifdef DEBUG
+ nsAutoCString urlScheme;
+ mUrl->GetScheme(urlScheme);
+ bool haveHandler = false;
+ extProtService->ExternalProtocolHandlerExists(urlScheme.get(), &haveHandler);
+ NS_ASSERTION(haveHandler, "Why do we have a channel for this url if we don't support the protocol?");
+#endif
+
+ nsCOMPtr<nsIInterfaceRequestor> aggCallbacks;
+ rv = NS_NewNotificationCallbacksAggregation(mCallbacks, mLoadGroup,
+ getter_AddRefs(aggCallbacks));
+ if (NS_FAILED(rv)) {
+ goto finish;
+ }
+
+ rv = extProtService->LoadURI(mUrl, aggCallbacks);
+ if (NS_SUCCEEDED(rv)) {
+ // despite success, we need to abort this channel, at the very least
+ // to make it clear to the caller that no on{Start,Stop}Request
+ // should be expected.
+ rv = NS_ERROR_NO_CONTENT;
+ }
+ }
+
+finish:
+ mCallbacks = nullptr;
+ return rv;
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::Open(nsIInputStream **_retval)
+{
+ return OpenURL();
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::Open2(nsIInputStream** aStream)
+{
+ nsCOMPtr<nsIStreamListener> listener;
+ nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return Open(aStream);
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::AsyncOpen(nsIStreamListener *listener, nsISupports *ctxt)
+{
+ if (mConnectedParent) {
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(!mLoadInfo ||
+ mLoadInfo->GetSecurityMode() == 0 ||
+ mLoadInfo->GetInitialSecurityCheckDone() ||
+ (mLoadInfo->GetSecurityMode() == nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL &&
+ nsContentUtils::IsSystemPrincipal(mLoadInfo->LoadingPrincipal())),
+ "security flags in loadInfo but asyncOpen2() not called");
+
+ NS_ENSURE_ARG_POINTER(listener);
+ NS_ENSURE_TRUE(!mWasOpened, NS_ERROR_ALREADY_OPENED);
+
+ mWasOpened = true;
+
+ return OpenURL();
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::AsyncOpen2(nsIStreamListener *aListener)
+{
+ nsCOMPtr<nsIStreamListener> listener = aListener;
+ nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return AsyncOpen(listener, nullptr);
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::GetLoadFlags(nsLoadFlags *aLoadFlags)
+{
+ *aLoadFlags = mLoadFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::SetLoadFlags(nsLoadFlags aLoadFlags)
+{
+ mLoadFlags = aLoadFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::GetContentType(nsACString &aContentType)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::SetContentType(const nsACString &aContentType)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::GetContentCharset(nsACString &aContentCharset)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::SetContentCharset(const nsACString &aContentCharset)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::GetContentDisposition(uint32_t *aContentDisposition)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::SetContentDisposition(uint32_t aContentDisposition)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::GetContentDispositionFilename(nsAString &aContentDispositionFilename)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::SetContentDispositionFilename(const nsAString &aContentDispositionFilename)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::GetContentDispositionHeader(nsACString &aContentDispositionHeader)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::GetContentLength(int64_t * aContentLength)
+{
+ *aContentLength = -1;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsExtProtocolChannel::SetContentLength(int64_t aContentLength)
+{
+ NS_NOTREACHED("SetContentLength");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::GetOwner(nsISupports * *aPrincipal)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::SetOwner(nsISupports * aPrincipal)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::GetLoadInfo(nsILoadInfo **aLoadInfo)
+{
+ NS_IF_ADDREF(*aLoadInfo = mLoadInfo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::SetLoadInfo(nsILoadInfo *aLoadInfo)
+{
+ mLoadInfo = aLoadInfo;
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// From nsIRequest
+////////////////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP nsExtProtocolChannel::GetName(nsACString &result)
+{
+ return mUrl->GetSpec(result);
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::IsPending(bool *result)
+{
+ *result = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::GetStatus(nsresult *status)
+{
+ *status = mStatus;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::Cancel(nsresult status)
+{
+ mStatus = status;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::Suspend()
+{
+ NS_NOTREACHED("Suspend");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::Resume()
+{
+ NS_NOTREACHED("Resume");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+///////////////////////////////////////////////////////////////////////
+// From nsIChildChannel
+//////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP nsExtProtocolChannel::ConnectParent(uint32_t registrarId)
+{
+ mozilla::dom::ContentChild::GetSingleton()->
+ SendExtProtocolChannelConnectParent(registrarId);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::CompleteRedirectSetup(nsIStreamListener *listener,
+ nsISupports *context)
+{
+ // For redirects to external protocols we AsyncOpen on the child
+ // (not the parent) because child channel has the right docshell
+ // (which is needed for the select dialog).
+ return AsyncOpen(listener, context);
+}
+
+///////////////////////////////////////////////////////////////////////
+// From nsIParentChannel (derives from nsIStreamListener)
+//////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP nsExtProtocolChannel::SetParentListener(HttpChannelParentListener* aListener)
+{
+ // This is called as part of the connect parent operation from
+ // ContentParent::RecvExtProtocolChannelConnectParent. Setting
+ // this flag tells this channel to not proceed and makes AsyncOpen
+ // just no-op. Actual operation will happen from the child process
+ // via CompleteRedirectSetup call on the child channel.
+ mConnectedParent = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::NotifyTrackingProtectionDisabled()
+{
+ // nothing to do
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::Delete()
+{
+ // nothing to do
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::OnStartRequest(nsIRequest *aRequest,
+ nsISupports *aContext)
+{
+ // no data is expected
+ MOZ_CRASH("No data expected from external protocol channel");
+ return NS_ERROR_UNEXPECTED;
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::OnStopRequest(nsIRequest *aRequest,
+ nsISupports *aContext,
+ nsresult aStatusCode)
+{
+ // no data is expected
+ MOZ_CRASH("No data expected from external protocol channel");
+ return NS_ERROR_UNEXPECTED;
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::OnDataAvailable(nsIRequest *aRequest,
+ nsISupports *aContext,
+ nsIInputStream *aInputStream,
+ uint64_t aOffset,
+ uint32_t aCount)
+{
+ // no data is expected
+ MOZ_CRASH("No data expected from external protocol channel");
+ return NS_ERROR_UNEXPECTED;
+}
+
+///////////////////////////////////////////////////////////////////////
+// the default protocol handler implementation
+//////////////////////////////////////////////////////////////////////
+
+nsExternalProtocolHandler::nsExternalProtocolHandler()
+{
+ m_schemeName = "default";
+}
+
+
+nsExternalProtocolHandler::~nsExternalProtocolHandler()
+{}
+
+NS_IMPL_ADDREF(nsExternalProtocolHandler)
+NS_IMPL_RELEASE(nsExternalProtocolHandler)
+
+NS_INTERFACE_MAP_BEGIN(nsExternalProtocolHandler)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIProtocolHandler)
+ NS_INTERFACE_MAP_ENTRY(nsIProtocolHandler)
+ NS_INTERFACE_MAP_ENTRY(nsIExternalProtocolHandler)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+NS_INTERFACE_MAP_END_THREADSAFE
+
+NS_IMETHODIMP nsExternalProtocolHandler::GetScheme(nsACString &aScheme)
+{
+ aScheme = m_schemeName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsExternalProtocolHandler::GetDefaultPort(int32_t *aDefaultPort)
+{
+ *aDefaultPort = 0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsExternalProtocolHandler::AllowPort(int32_t port, const char *scheme, bool *_retval)
+{
+ // don't override anything.
+ *_retval = false;
+ return NS_OK;
+}
+// returns TRUE if the OS can handle this protocol scheme and false otherwise.
+bool nsExternalProtocolHandler::HaveExternalProtocolHandler(nsIURI * aURI)
+{
+ MOZ_ASSERT(aURI);
+ nsAutoCString scheme;
+ aURI->GetScheme(scheme);
+
+ nsCOMPtr<nsIExternalProtocolService> extProtSvc(do_GetService(NS_EXTERNALPROTOCOLSERVICE_CONTRACTID));
+ if (!extProtSvc) {
+ return false;
+ }
+
+ bool haveHandler = false;
+ extProtSvc->ExternalProtocolHandlerExists(scheme.get(), &haveHandler);
+ return haveHandler;
+}
+
+NS_IMETHODIMP nsExternalProtocolHandler::GetProtocolFlags(uint32_t *aUritype)
+{
+ // Make it norelative since it is a simple uri
+ *aUritype = URI_NORELATIVE | URI_NOAUTH | URI_LOADABLE_BY_ANYONE |
+ URI_NON_PERSISTABLE | URI_DOES_NOT_RETURN_DATA;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsExternalProtocolHandler::NewURI(const nsACString &aSpec,
+ const char *aCharset, // ignore charset info
+ nsIURI *aBaseURI,
+ nsIURI **_retval)
+{
+ nsresult rv;
+ nsCOMPtr<nsIURI> uri = do_CreateInstance(NS_SIMPLEURI_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = uri->SetSpec(aSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ADDREF(*_retval = uri);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsExternalProtocolHandler::NewChannel2(nsIURI* aURI,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel** aRetval)
+{
+ NS_ENSURE_TRUE(aURI, NS_ERROR_UNKNOWN_PROTOCOL);
+ NS_ENSURE_TRUE(aRetval, NS_ERROR_UNKNOWN_PROTOCOL);
+
+ // Only try to return a channel if we have a protocol handler for the url.
+ // nsOSHelperAppService::LoadUriInternal relies on this to check trustedness
+ // for some platforms at least. (win uses ::ShellExecute and unix uses
+ // gnome_url_show.)
+ if (!HaveExternalProtocolHandler(aURI)) {
+ return NS_ERROR_UNKNOWN_PROTOCOL;
+ }
+
+ nsCOMPtr<nsIChannel> channel = new nsExtProtocolChannel(aURI, aLoadInfo);
+ channel.forget(aRetval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsExternalProtocolHandler::NewChannel(nsIURI *aURI, nsIChannel **_retval)
+{
+ return NewChannel2(aURI, nullptr, _retval);
+}
+
+///////////////////////////////////////////////////////////////////////
+// External protocol handler interface implementation
+//////////////////////////////////////////////////////////////////////
+NS_IMETHODIMP nsExternalProtocolHandler::ExternalAppExistsForScheme(const nsACString& aScheme, bool *_retval)
+{
+ nsCOMPtr<nsIExternalProtocolService> extProtSvc(do_GetService(NS_EXTERNALPROTOCOLSERVICE_CONTRACTID));
+ if (extProtSvc)
+ return extProtSvc->ExternalProtocolHandlerExists(
+ PromiseFlatCString(aScheme).get(), _retval);
+
+ // In case we don't have external protocol service.
+ *_retval = false;
+ return NS_OK;
+}
diff --git a/uriloader/exthandler/nsExternalProtocolHandler.h b/uriloader/exthandler/nsExternalProtocolHandler.h
new file mode 100644
index 0000000000..426c98da18
--- /dev/null
+++ b/uriloader/exthandler/nsExternalProtocolHandler.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsExternalProtocolHandler_h___
+#define nsExternalProtocolHandler_h___
+
+#include "nsIExternalProtocolHandler.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsWeakReference.h"
+#include "nsIExternalProtocolService.h"
+#include "mozilla/Attributes.h"
+
+class nsIURI;
+
+// protocol handlers need to support weak references if we want the netlib nsIOService to cache them.
+class nsExternalProtocolHandler final : public nsIExternalProtocolHandler, public nsSupportsWeakReference
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIPROTOCOLHANDLER
+ NS_DECL_NSIEXTERNALPROTOCOLHANDLER
+
+ nsExternalProtocolHandler();
+
+protected:
+ ~nsExternalProtocolHandler();
+
+ // helper function
+ bool HaveExternalProtocolHandler(nsIURI * aURI);
+ nsCString m_schemeName;
+};
+
+#endif // nsExternalProtocolHandler_h___
+
diff --git a/uriloader/exthandler/nsHandlerService.js b/uriloader/exthandler/nsHandlerService.js
new file mode 100644
index 0000000000..c932f9f5dc
--- /dev/null
+++ b/uriloader/exthandler/nsHandlerService.js
@@ -0,0 +1,1429 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const Ci = Components.interfaces;
+const Cc = Components.classes;
+const Cu = Components.utils;
+const Cr = Components.results;
+
+
+const CLASS_MIMEINFO = "mimetype";
+const CLASS_PROTOCOLINFO = "scheme";
+
+
+// namespace prefix
+const NC_NS = "http://home.netscape.com/NC-rdf#";
+
+// the most recent default handlers that have been injected. Note that
+// this is used to construct an RDF resource, which needs to have NC_NS
+// prepended, since that hasn't been done yet
+const DEFAULT_HANDLERS_VERSION = "defaultHandlersVersion";
+
+// type list properties
+
+const NC_MIME_TYPES = NC_NS + "MIME-types";
+const NC_PROTOCOL_SCHEMES = NC_NS + "Protocol-Schemes";
+
+// content type ("type") properties
+
+// nsIHandlerInfo::type
+const NC_VALUE = NC_NS + "value";
+const NC_DESCRIPTION = NC_NS + "description";
+
+// additional extensions
+const NC_FILE_EXTENSIONS = NC_NS + "fileExtensions";
+
+// references nsIHandlerInfo record
+const NC_HANDLER_INFO = NC_NS + "handlerProp";
+
+// handler info ("info") properties
+
+// nsIHandlerInfo::preferredAction
+const NC_SAVE_TO_DISK = NC_NS + "saveToDisk";
+const NC_HANDLE_INTERNALLY = NC_NS + "handleInternal";
+const NC_USE_SYSTEM_DEFAULT = NC_NS + "useSystemDefault";
+
+// nsIHandlerInfo::alwaysAskBeforeHandling
+const NC_ALWAYS_ASK = NC_NS + "alwaysAsk";
+
+// references nsIHandlerApp records
+const NC_PREFERRED_APP = NC_NS + "externalApplication";
+const NC_POSSIBLE_APP = NC_NS + "possibleApplication";
+
+// handler app ("handler") properties
+
+// nsIHandlerApp::name
+const NC_PRETTY_NAME = NC_NS + "prettyName";
+
+// nsILocalHandlerApp::executable
+const NC_PATH = NC_NS + "path";
+
+// nsIWebHandlerApp::uriTemplate
+const NC_URI_TEMPLATE = NC_NS + "uriTemplate";
+
+// nsIDBusHandlerApp::service
+const NC_SERVICE = NC_NS + "service";
+
+// nsIDBusHandlerApp::method
+const NC_METHOD = NC_NS + "method";
+
+// nsIDBusHandlerApp::objectPath
+const NC_OBJPATH = NC_NS + "objectPath";
+
+// nsIDBusHandlerApp::dbusInterface
+const NC_INTERFACE = NC_NS + "dBusInterface";
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+
+function HandlerService() {
+ this._init();
+}
+
+const HandlerServiceFactory = {
+ _instance: null,
+ createInstance: function (outer, iid) {
+ if (this._instance)
+ return this._instance;
+
+ let processType = Cc["@mozilla.org/xre/runtime;1"].
+ getService(Ci.nsIXULRuntime).processType;
+ if (processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT)
+ return Cr.NS_ERROR_NOT_IMPLEMENTED;
+
+ return (this._instance = new HandlerService());
+ }
+};
+
+HandlerService.prototype = {
+ //**************************************************************************//
+ // XPCOM Plumbing
+
+ classID: Components.ID("{32314cc8-22f7-4f7f-a645-1a45453ba6a6}"),
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIHandlerService]),
+ _xpcom_factory: HandlerServiceFactory,
+
+ //**************************************************************************//
+ // Initialization & Destruction
+
+ _init: function HS__init() {
+ // Observe profile-before-change so we can switch to the datasource
+ // in the new profile when the user changes profiles.
+ this._observerSvc.addObserver(this, "profile-before-change", false);
+
+ // Observe xpcom-shutdown so we can remove these observers
+ // when the application shuts down.
+ this._observerSvc.addObserver(this, "xpcom-shutdown", false);
+
+ // Observe profile-do-change so that non-default profiles get upgraded too
+ this._observerSvc.addObserver(this, "profile-do-change", false);
+
+ // do any necessary updating of the datastore
+ this._updateDB();
+ },
+
+ _updateDB: function HS__updateDB() {
+ try {
+ var defaultHandlersVersion = this._datastoreDefaultHandlersVersion;
+ } catch(ex) {
+ // accessing the datastore failed, we can't update anything
+ return;
+ }
+
+ try {
+ // if we don't have the current version of the default prefs for
+ // this locale, inject any new default handers into the datastore
+ if (defaultHandlersVersion < this._prefsDefaultHandlersVersion) {
+
+ // set the new version first so that if we recurse we don't
+ // call _injectNewDefaults several times
+ this._datastoreDefaultHandlersVersion =
+ this._prefsDefaultHandlersVersion;
+ this._injectNewDefaults();
+ }
+ } catch (ex) {
+ // if injecting the defaults failed, set the version back to the
+ // previous value
+ this._datastoreDefaultHandlersVersion = defaultHandlersVersion;
+ }
+ },
+
+ get _currentLocale() {
+ var chromeRegistry = Cc["@mozilla.org/chrome/chrome-registry;1"].
+ getService(Ci.nsIXULChromeRegistry);
+ var currentLocale = chromeRegistry.getSelectedLocale("global");
+ return currentLocale;
+ },
+
+ _destroy: function HS__destroy() {
+ this._observerSvc.removeObserver(this, "profile-before-change");
+ this._observerSvc.removeObserver(this, "xpcom-shutdown");
+ this._observerSvc.removeObserver(this, "profile-do-change");
+
+ // XXX Should we also null references to all the services that get stored
+ // by our memoizing getters in the Convenience Getters section?
+ },
+
+ _onProfileChange: function HS__onProfileChange() {
+ // Lose our reference to the datasource so we reacquire it
+ // from the new profile the next time we need it.
+ this.__ds = null;
+ },
+
+ _isInHandlerArray: function HS__isInHandlerArray(aArray, aHandler) {
+ var enumerator = aArray.enumerate();
+ while (enumerator.hasMoreElements()) {
+ let handler = enumerator.getNext();
+ handler.QueryInterface(Ci.nsIHandlerApp);
+ if (handler.equals(aHandler))
+ return true;
+ }
+
+ return false;
+ },
+
+ // note that this applies to the current locale only
+ get _datastoreDefaultHandlersVersion() {
+ var version = this._getValue("urn:root", NC_NS + this._currentLocale +
+ "_" + DEFAULT_HANDLERS_VERSION);
+
+ return version ? version : -1;
+ },
+
+ set _datastoreDefaultHandlersVersion(aNewVersion) {
+ return this._setLiteral("urn:root", NC_NS + this._currentLocale + "_" +
+ DEFAULT_HANDLERS_VERSION, aNewVersion);
+ },
+
+ get _prefsDefaultHandlersVersion() {
+ // get handler service pref branch
+ var prefSvc = Cc["@mozilla.org/preferences-service;1"].
+ getService(Ci.nsIPrefService);
+ var handlerSvcBranch = prefSvc.getBranch("gecko.handlerService.");
+
+ // get the version of the preferences for this locale
+ return Number(handlerSvcBranch.
+ getComplexValue("defaultHandlersVersion",
+ Ci.nsIPrefLocalizedString).data);
+ },
+
+ _injectNewDefaults: function HS__injectNewDefaults() {
+ // get handler service pref branch
+ var prefSvc = Cc["@mozilla.org/preferences-service;1"].
+ getService(Ci.nsIPrefService);
+
+ let schemesPrefBranch = prefSvc.getBranch("gecko.handlerService.schemes.");
+ let schemePrefList = schemesPrefBranch.getChildList("");
+
+ var schemes = {};
+
+ // read all the scheme prefs into a hash
+ for (var schemePrefName of schemePrefList) {
+
+ let [scheme, handlerNumber, attribute] = schemePrefName.split(".");
+
+ try {
+ var attrData =
+ schemesPrefBranch.getComplexValue(schemePrefName,
+ Ci.nsIPrefLocalizedString).data;
+ if (!(scheme in schemes))
+ schemes[scheme] = {};
+
+ if (!(handlerNumber in schemes[scheme]))
+ schemes[scheme][handlerNumber] = {};
+
+ schemes[scheme][handlerNumber][attribute] = attrData;
+ } catch (ex) {}
+ }
+
+ let protoSvc = Cc["@mozilla.org/uriloader/external-protocol-service;1"].
+ getService(Ci.nsIExternalProtocolService);
+ for (var scheme in schemes) {
+
+ // This clause is essentially a reimplementation of
+ // nsIExternalProtocolHandlerService.getProtocolHandlerInfo().
+ // Necessary because calling that from here would make XPConnect barf
+ // when getService tried to re-enter the constructor for this
+ // service.
+ let osDefaultHandlerFound = {};
+ let protoInfo = protoSvc.getProtocolHandlerInfoFromOS(scheme,
+ osDefaultHandlerFound);
+
+ if (this.exists(protoInfo))
+ this.fillHandlerInfo(protoInfo, null);
+ else
+ protoSvc.setProtocolHandlerDefaults(protoInfo,
+ osDefaultHandlerFound.value);
+
+ // cache the possible handlers to avoid extra xpconnect traversals.
+ let possibleHandlers = protoInfo.possibleApplicationHandlers;
+
+ for (let handlerNumber in schemes[scheme]) {
+ let handlerPrefs = schemes[scheme][handlerNumber];
+ let handlerApp = Cc["@mozilla.org/uriloader/web-handler-app;1"].
+ createInstance(Ci.nsIWebHandlerApp);
+
+ handlerApp.uriTemplate = handlerPrefs.uriTemplate;
+ handlerApp.name = handlerPrefs.name;
+
+ if (!this._isInHandlerArray(possibleHandlers, handlerApp)) {
+ possibleHandlers.appendElement(handlerApp, false);
+ }
+ }
+
+ this.store(protoInfo);
+ }
+ },
+
+ //**************************************************************************//
+ // nsIObserver
+
+ observe: function HS__observe(subject, topic, data) {
+ switch(topic) {
+ case "profile-before-change":
+ this._onProfileChange();
+ break;
+ case "xpcom-shutdown":
+ this._destroy();
+ break;
+ case "profile-do-change":
+ this._updateDB();
+ break;
+ }
+ },
+
+
+ //**************************************************************************//
+ // nsIHandlerService
+
+ enumerate: function HS_enumerate() {
+ var handlers = Cc["@mozilla.org/array;1"].
+ createInstance(Ci.nsIMutableArray);
+ this._appendHandlers(handlers, CLASS_MIMEINFO);
+ this._appendHandlers(handlers, CLASS_PROTOCOLINFO);
+ return handlers.enumerate();
+ },
+
+ fillHandlerInfo: function HS_fillHandlerInfo(aHandlerInfo, aOverrideType) {
+ var type = aOverrideType || aHandlerInfo.type;
+ var typeID = this._getTypeID(this._getClass(aHandlerInfo), type);
+
+ // Determine whether or not information about this handler is available
+ // in the datastore by looking for its "value" property, which stores its
+ // type and should always be present.
+ if (!this._hasValue(typeID, NC_VALUE))
+ throw Cr.NS_ERROR_NOT_AVAILABLE;
+
+ // Retrieve the human-readable description of the type.
+ if (this._hasValue(typeID, NC_DESCRIPTION))
+ aHandlerInfo.description = this._getValue(typeID, NC_DESCRIPTION);
+
+ // Note: for historical reasons, we don't actually check that the type
+ // record has a "handlerProp" property referencing the info record. It's
+ // unclear whether or not we should start doing this check; perhaps some
+ // legacy datasources don't have such references.
+ var infoID = this._getInfoID(this._getClass(aHandlerInfo), type);
+
+ aHandlerInfo.preferredAction = this._retrievePreferredAction(infoID);
+
+ var preferredHandlerID =
+ this._getPreferredHandlerID(this._getClass(aHandlerInfo), type);
+
+ // Retrieve the preferred handler.
+ // Note: for historical reasons, we don't actually check that the info
+ // record has an "externalApplication" property referencing the preferred
+ // handler record. It's unclear whether or not we should start doing
+ // this check; perhaps some legacy datasources don't have such references.
+ aHandlerInfo.preferredApplicationHandler =
+ this._retrieveHandlerApp(preferredHandlerID);
+
+ // Fill the array of possible handlers with the ones in the datastore.
+ this._fillPossibleHandlers(infoID,
+ aHandlerInfo.possibleApplicationHandlers,
+ aHandlerInfo.preferredApplicationHandler);
+
+ // If we have an "always ask" flag stored in the RDF, always use its
+ // value. Otherwise, use the default value stored in the pref service.
+ var alwaysAsk;
+ if (this._hasValue(infoID, NC_ALWAYS_ASK)) {
+ alwaysAsk = (this._getValue(infoID, NC_ALWAYS_ASK) != "false");
+ } else {
+ var prefSvc = Cc["@mozilla.org/preferences-service;1"].
+ getService(Ci.nsIPrefService);
+ var prefBranch = prefSvc.getBranch("network.protocol-handler.");
+ try {
+ alwaysAsk = prefBranch.getBoolPref("warn-external." + type);
+ } catch (e) {
+ // will throw if pref didn't exist.
+ try {
+ alwaysAsk = prefBranch.getBoolPref("warn-external-default");
+ } catch (e) {
+ // Nothing to tell us what to do, so be paranoid and prompt.
+ alwaysAsk = true;
+ }
+ }
+ }
+ aHandlerInfo.alwaysAskBeforeHandling = alwaysAsk;
+
+ // If the object represents a MIME type handler, then also retrieve
+ // any file extensions.
+ if (aHandlerInfo instanceof Ci.nsIMIMEInfo)
+ for (let fileExtension of this._retrieveFileExtensions(typeID))
+ aHandlerInfo.appendExtension(fileExtension);
+ },
+
+ store: function HS_store(aHandlerInfo) {
+ // FIXME: when we switch from RDF to something with transactions (like
+ // SQLite), enclose the following changes in a transaction so they all
+ // get rolled back if any of them fail and we don't leave the datastore
+ // in an inconsistent state.
+
+ this._ensureRecordsForType(aHandlerInfo);
+ this._storePreferredAction(aHandlerInfo);
+ this._storePreferredHandler(aHandlerInfo);
+ this._storePossibleHandlers(aHandlerInfo);
+ this._storeAlwaysAsk(aHandlerInfo);
+ this._storeExtensions(aHandlerInfo);
+
+ // Write the changes to the database immediately so we don't lose them
+ // if the application crashes.
+ if (this._ds instanceof Ci.nsIRDFRemoteDataSource)
+ this._ds.Flush();
+ },
+
+ exists: function HS_exists(aHandlerInfo) {
+ var found;
+
+ try {
+ var typeID = this._getTypeID(this._getClass(aHandlerInfo), aHandlerInfo.type);
+ found = this._hasLiteralAssertion(typeID, NC_VALUE, aHandlerInfo.type);
+ } catch (e) {
+ // If the RDF threw (eg, corrupt file), treat as nonexistent.
+ found = false;
+ }
+
+ return found;
+ },
+
+ remove: function HS_remove(aHandlerInfo) {
+ var preferredHandlerID =
+ this._getPreferredHandlerID(this._getClass(aHandlerInfo), aHandlerInfo.type);
+ this._removeAssertions(preferredHandlerID);
+
+ var infoID = this._getInfoID(this._getClass(aHandlerInfo), aHandlerInfo.type);
+
+ // Get a list of possible handlers. After we have removed the info record,
+ // we'll check if any other info records reference these handlers, and we'll
+ // remove the handler records that aren't referenced by other info records.
+ var possibleHandlerIDs = [];
+ var possibleHandlerTargets = this._getTargets(infoID, NC_POSSIBLE_APP);
+ while (possibleHandlerTargets.hasMoreElements()) {
+ let possibleHandlerTarget = possibleHandlerTargets.getNext();
+ // Note: possibleHandlerTarget should always be an nsIRDFResource.
+ // The conditional is just here in case of a corrupt RDF datasource.
+ if (possibleHandlerTarget instanceof Ci.nsIRDFResource)
+ possibleHandlerIDs.push(possibleHandlerTarget.ValueUTF8);
+ }
+
+ // Remove the info record.
+ this._removeAssertions(infoID);
+
+ // Now that we've removed the info record, remove any possible handlers
+ // that aren't referenced by other info records.
+ for (let possibleHandlerID of possibleHandlerIDs)
+ if (!this._existsResourceTarget(NC_POSSIBLE_APP, possibleHandlerID))
+ this._removeAssertions(possibleHandlerID);
+
+ var typeID = this._getTypeID(this._getClass(aHandlerInfo), aHandlerInfo.type);
+ this._removeAssertions(typeID);
+
+ // Now that there's no longer a handler for this type, remove the type
+ // from the list of types for which there are known handlers.
+ var typeList = this._ensureAndGetTypeList(this._getClass(aHandlerInfo));
+ var type = this._rdf.GetResource(typeID);
+ var typeIndex = typeList.IndexOf(type);
+ if (typeIndex != -1)
+ typeList.RemoveElementAt(typeIndex, true);
+
+ // Write the changes to the database immediately so we don't lose them
+ // if the application crashes.
+ // XXX If we're removing a bunch of handlers at once, will flushing
+ // after every removal cause a significant performance hit?
+ if (this._ds instanceof Ci.nsIRDFRemoteDataSource)
+ this._ds.Flush();
+ },
+
+ getTypeFromExtension: function HS_getTypeFromExtension(aFileExtension) {
+ var fileExtension = aFileExtension.toLowerCase();
+ var typeID;
+
+ // See bug 1100069 for why we want to fail gracefully and silently here.
+ try {
+ this._ds;
+ } catch (ex) {
+ Components.returnCode = Cr.NS_ERROR_NOT_AVAILABLE;
+ return;
+ }
+
+ if (this._existsLiteralTarget(NC_FILE_EXTENSIONS, fileExtension))
+ typeID = this._getSourceForLiteral(NC_FILE_EXTENSIONS, fileExtension);
+
+ if (typeID && this._hasValue(typeID, NC_VALUE)) {
+ let type = this._getValue(typeID, NC_VALUE);
+ if (type == "")
+ throw Cr.NS_ERROR_FAILURE;
+ return type;
+ }
+
+ return "";
+ },
+
+
+ //**************************************************************************//
+ // Retrieval Methods
+
+ /**
+ * Retrieve the preferred action for the info record with the given ID.
+ *
+ * @param aInfoID {string} the info record ID
+ *
+ * @returns {integer} the preferred action enumeration value
+ */
+ _retrievePreferredAction: function HS__retrievePreferredAction(aInfoID) {
+ if (this._getValue(aInfoID, NC_SAVE_TO_DISK) == "true")
+ return Ci.nsIHandlerInfo.saveToDisk;
+
+ if (this._getValue(aInfoID, NC_USE_SYSTEM_DEFAULT) == "true")
+ return Ci.nsIHandlerInfo.useSystemDefault;
+
+ if (this._getValue(aInfoID, NC_HANDLE_INTERNALLY) == "true")
+ return Ci.nsIHandlerInfo.handleInternally;
+
+ return Ci.nsIHandlerInfo.useHelperApp;
+ },
+
+ /**
+ * Fill an array of possible handlers with the handlers for the given info ID.
+ *
+ * @param aInfoID {string} the ID of the info record
+ * @param aPossibleHandlers {nsIMutableArray} the array of possible handlers
+ * @param aPreferredHandler {nsIHandlerApp} the preferred handler, if any
+ */
+ _fillPossibleHandlers: function HS__fillPossibleHandlers(aInfoID,
+ aPossibleHandlers,
+ aPreferredHandler) {
+ // The set of possible handlers should include the preferred handler,
+ // but legacy datastores (from before we added possible handlers) won't
+ // include the preferred handler, so check if it's included as we build
+ // the list of handlers, and, if it's not included, add it to the list.
+ if (aPreferredHandler)
+ aPossibleHandlers.appendElement(aPreferredHandler, false);
+
+ var possibleHandlerTargets = this._getTargets(aInfoID, NC_POSSIBLE_APP);
+
+ while (possibleHandlerTargets.hasMoreElements()) {
+ let possibleHandlerTarget = possibleHandlerTargets.getNext();
+ if (!(possibleHandlerTarget instanceof Ci.nsIRDFResource))
+ continue;
+
+ let possibleHandlerID = possibleHandlerTarget.ValueUTF8;
+ let possibleHandler = this._retrieveHandlerApp(possibleHandlerID);
+ if (possibleHandler && (!aPreferredHandler ||
+ !possibleHandler.equals(aPreferredHandler)))
+ aPossibleHandlers.appendElement(possibleHandler, false);
+ }
+ },
+
+ /**
+ * Retrieve the handler app object with the given ID.
+ *
+ * @param aHandlerAppID {string} the ID of the handler app to retrieve
+ *
+ * @returns {nsIHandlerApp} the handler app, if any; otherwise null
+ */
+ _retrieveHandlerApp: function HS__retrieveHandlerApp(aHandlerAppID) {
+ var handlerApp;
+
+ // If it has a path, it's a local handler; otherwise, it's a web handler.
+ if (this._hasValue(aHandlerAppID, NC_PATH)) {
+ let executable =
+ this._getFileWithPath(this._getValue(aHandlerAppID, NC_PATH));
+ if (!executable)
+ return null;
+
+ handlerApp = Cc["@mozilla.org/uriloader/local-handler-app;1"].
+ createInstance(Ci.nsILocalHandlerApp);
+ handlerApp.executable = executable;
+ }
+ else if (this._hasValue(aHandlerAppID, NC_URI_TEMPLATE)) {
+ let uriTemplate = this._getValue(aHandlerAppID, NC_URI_TEMPLATE);
+ if (!uriTemplate)
+ return null;
+
+ handlerApp = Cc["@mozilla.org/uriloader/web-handler-app;1"].
+ createInstance(Ci.nsIWebHandlerApp);
+ handlerApp.uriTemplate = uriTemplate;
+ }
+ else if (this._hasValue(aHandlerAppID, NC_SERVICE)) {
+ let service = this._getValue(aHandlerAppID, NC_SERVICE);
+ if (!service)
+ return null;
+
+ let method = this._getValue(aHandlerAppID, NC_METHOD);
+ if (!method)
+ return null;
+
+ let objpath = this._getValue(aHandlerAppID, NC_OBJPATH);
+ if (!objpath)
+ return null;
+
+ let iface = this._getValue(aHandlerAppID, NC_INTERFACE);
+ if (!iface)
+ return null;
+
+ handlerApp = Cc["@mozilla.org/uriloader/dbus-handler-app;1"].
+ createInstance(Ci.nsIDBusHandlerApp);
+ handlerApp.service = service;
+ handlerApp.method = method;
+ handlerApp.objectPath = objpath;
+ handlerApp.dBusInterface = iface;
+
+ }
+ else
+ return null;
+
+ handlerApp.name = this._getValue(aHandlerAppID, NC_PRETTY_NAME);
+
+ return handlerApp;
+ },
+
+ /*
+ * Retrieve file extensions, if any, for the MIME type with the given type ID.
+ *
+ * @param aTypeID {string} the type record ID
+ */
+ _retrieveFileExtensions: function HS__retrieveFileExtensions(aTypeID) {
+ var fileExtensions = [];
+
+ var fileExtensionTargets = this._getTargets(aTypeID, NC_FILE_EXTENSIONS);
+
+ while (fileExtensionTargets.hasMoreElements()) {
+ let fileExtensionTarget = fileExtensionTargets.getNext();
+ if (fileExtensionTarget instanceof Ci.nsIRDFLiteral &&
+ fileExtensionTarget.Value != "")
+ fileExtensions.push(fileExtensionTarget.Value);
+ }
+
+ return fileExtensions;
+ },
+
+ /**
+ * Get the file with the given path. This is not as simple as merely
+ * initializing a local file object with the path, because the path might be
+ * relative to the current process directory, in which case we have to
+ * construct a path starting from that directory.
+ *
+ * @param aPath {string} a path to a file
+ *
+ * @returns {nsILocalFile} the file, or null if the file does not exist
+ */
+ _getFileWithPath: function HS__getFileWithPath(aPath) {
+ var file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
+
+ try {
+ file.initWithPath(aPath);
+
+ if (file.exists())
+ return file;
+ }
+ catch(ex) {
+ // Note: for historical reasons, we don't actually check to see
+ // if the exception is NS_ERROR_FILE_UNRECOGNIZED_PATH, which is what
+ // nsILocalFile::initWithPath throws when a path is relative.
+
+ file = this._dirSvc.get("XCurProcD", Ci.nsIFile);
+
+ try {
+ file.append(aPath);
+ if (file.exists())
+ return file;
+ }
+ catch(ex) {}
+ }
+
+ return null;
+ },
+
+
+ //**************************************************************************//
+ // Storage Methods
+
+ _storePreferredAction: function HS__storePreferredAction(aHandlerInfo) {
+ var infoID = this._getInfoID(this._getClass(aHandlerInfo), aHandlerInfo.type);
+
+ switch(aHandlerInfo.preferredAction) {
+ case Ci.nsIHandlerInfo.saveToDisk:
+ this._setLiteral(infoID, NC_SAVE_TO_DISK, "true");
+ this._removeTarget(infoID, NC_HANDLE_INTERNALLY);
+ this._removeTarget(infoID, NC_USE_SYSTEM_DEFAULT);
+ break;
+
+ case Ci.nsIHandlerInfo.handleInternally:
+ this._setLiteral(infoID, NC_HANDLE_INTERNALLY, "true");
+ this._removeTarget(infoID, NC_SAVE_TO_DISK);
+ this._removeTarget(infoID, NC_USE_SYSTEM_DEFAULT);
+ break;
+
+ case Ci.nsIHandlerInfo.useSystemDefault:
+ this._setLiteral(infoID, NC_USE_SYSTEM_DEFAULT, "true");
+ this._removeTarget(infoID, NC_SAVE_TO_DISK);
+ this._removeTarget(infoID, NC_HANDLE_INTERNALLY);
+ break;
+
+ // This value is indicated in the datastore either by the absence of
+ // the three properties or by setting them all "false". Of these two
+ // options, the former seems preferable, because it reduces the size
+ // of the RDF file and thus the amount of stuff we have to parse.
+ case Ci.nsIHandlerInfo.useHelperApp:
+ default:
+ this._removeTarget(infoID, NC_SAVE_TO_DISK);
+ this._removeTarget(infoID, NC_HANDLE_INTERNALLY);
+ this._removeTarget(infoID, NC_USE_SYSTEM_DEFAULT);
+ break;
+ }
+ },
+
+ _storePreferredHandler: function HS__storePreferredHandler(aHandlerInfo) {
+ var infoID = this._getInfoID(this._getClass(aHandlerInfo), aHandlerInfo.type);
+ var handlerID =
+ this._getPreferredHandlerID(this._getClass(aHandlerInfo), aHandlerInfo.type);
+
+ var handler = aHandlerInfo.preferredApplicationHandler;
+
+ if (handler) {
+ this._storeHandlerApp(handlerID, handler);
+
+ // Make this app be the preferred app for the handler info.
+ //
+ // Note: nsExternalHelperAppService::FillContentHandlerProperties ignores
+ // this setting and instead identifies the preferred app as the resource
+ // whose URI follows the pattern urn:<class>:externalApplication:<type>.
+ // But the old downloadactions.js code used to set this property, so just
+ // in case there is still some code somewhere that relies on its presence,
+ // we set it here.
+ this._setResource(infoID, NC_PREFERRED_APP, handlerID);
+ }
+ else {
+ // There isn't a preferred handler. Remove the existing record for it,
+ // if any.
+ this._removeTarget(infoID, NC_PREFERRED_APP);
+ this._removeAssertions(handlerID);
+ }
+ },
+
+ /**
+ * Store the list of possible handler apps for the content type represented
+ * by the given handler info object.
+ *
+ * @param aHandlerInfo {nsIHandlerInfo} the handler info object
+ */
+ _storePossibleHandlers: function HS__storePossibleHandlers(aHandlerInfo) {
+ var infoID = this._getInfoID(this._getClass(aHandlerInfo), aHandlerInfo.type);
+
+ // First, retrieve the set of handler apps currently stored for the type,
+ // keeping track of their IDs in a hash that we'll use to determine which
+ // ones are no longer valid and should be removed.
+ var currentHandlerApps = {};
+ var currentHandlerTargets = this._getTargets(infoID, NC_POSSIBLE_APP);
+ while (currentHandlerTargets.hasMoreElements()) {
+ let handlerApp = currentHandlerTargets.getNext();
+ if (handlerApp instanceof Ci.nsIRDFResource) {
+ let handlerAppID = handlerApp.ValueUTF8;
+ currentHandlerApps[handlerAppID] = true;
+ }
+ }
+
+ // Next, store any new handler apps.
+ var newHandlerApps =
+ aHandlerInfo.possibleApplicationHandlers.enumerate();
+ while (newHandlerApps.hasMoreElements()) {
+ let handlerApp =
+ newHandlerApps.getNext().QueryInterface(Ci.nsIHandlerApp);
+ let handlerAppID = this._getPossibleHandlerAppID(handlerApp);
+ if (!this._hasResourceAssertion(infoID, NC_POSSIBLE_APP, handlerAppID)) {
+ this._storeHandlerApp(handlerAppID, handlerApp);
+ this._addResourceAssertion(infoID, NC_POSSIBLE_APP, handlerAppID);
+ }
+ delete currentHandlerApps[handlerAppID];
+ }
+
+ // Finally, remove any old handler apps that aren't being used anymore,
+ // and if those handler apps aren't being used by any other type either,
+ // then completely remove their record from the datastore so we don't
+ // leave it clogged up with information about handler apps we don't care
+ // about anymore.
+ for (let handlerAppID in currentHandlerApps) {
+ this._removeResourceAssertion(infoID, NC_POSSIBLE_APP, handlerAppID);
+ if (!this._existsResourceTarget(NC_POSSIBLE_APP, handlerAppID))
+ this._removeAssertions(handlerAppID);
+ }
+ },
+
+ /**
+ * Store the given handler app.
+ *
+ * Note: the reason this method takes the ID of the handler app in a param
+ * is that the ID is different than it usually is when the handler app
+ * in question is a preferred handler app, so this method can't just derive
+ * the ID of the handler app by calling _getPossibleHandlerAppID, its callers
+ * have to do that for it.
+ *
+ * @param aHandlerAppID {string} the ID of the handler app to store
+ * @param aHandlerApp {nsIHandlerApp} the handler app to store
+ */
+ _storeHandlerApp: function HS__storeHandlerApp(aHandlerAppID, aHandlerApp) {
+ aHandlerApp.QueryInterface(Ci.nsIHandlerApp);
+ this._setLiteral(aHandlerAppID, NC_PRETTY_NAME, aHandlerApp.name);
+
+ // In the case of the preferred handler, the handler ID could have been
+ // used to refer to a different kind of handler in the past (i.e. either
+ // a local hander or a web handler), so if the new handler is a local
+ // handler, then we remove any web handler properties and vice versa.
+ // This is unnecessary but harmless for possible handlers.
+
+ if (aHandlerApp instanceof Ci.nsILocalHandlerApp) {
+ this._setLiteral(aHandlerAppID, NC_PATH, aHandlerApp.executable.path);
+ this._removeTarget(aHandlerAppID, NC_URI_TEMPLATE);
+ this._removeTarget(aHandlerAppID, NC_METHOD);
+ this._removeTarget(aHandlerAppID, NC_SERVICE);
+ this._removeTarget(aHandlerAppID, NC_OBJPATH);
+ this._removeTarget(aHandlerAppID, NC_INTERFACE);
+ }
+ else if(aHandlerApp instanceof Ci.nsIWebHandlerApp){
+ aHandlerApp.QueryInterface(Ci.nsIWebHandlerApp);
+ this._setLiteral(aHandlerAppID, NC_URI_TEMPLATE, aHandlerApp.uriTemplate);
+ this._removeTarget(aHandlerAppID, NC_PATH);
+ this._removeTarget(aHandlerAppID, NC_METHOD);
+ this._removeTarget(aHandlerAppID, NC_SERVICE);
+ this._removeTarget(aHandlerAppID, NC_OBJPATH);
+ this._removeTarget(aHandlerAppID, NC_INTERFACE);
+ }
+ else if(aHandlerApp instanceof Ci.nsIDBusHandlerApp){
+ aHandlerApp.QueryInterface(Ci.nsIDBusHandlerApp);
+ this._setLiteral(aHandlerAppID, NC_SERVICE, aHandlerApp.service);
+ this._setLiteral(aHandlerAppID, NC_METHOD, aHandlerApp.method);
+ this._setLiteral(aHandlerAppID, NC_OBJPATH, aHandlerApp.objectPath);
+ this._setLiteral(aHandlerAppID, NC_INTERFACE, aHandlerApp.dBusInterface);
+ this._removeTarget(aHandlerAppID, NC_PATH);
+ this._removeTarget(aHandlerAppID, NC_URI_TEMPLATE);
+ }
+ else {
+ throw "unknown handler type";
+ }
+
+ },
+
+ _storeAlwaysAsk: function HS__storeAlwaysAsk(aHandlerInfo) {
+ var infoID = this._getInfoID(this._getClass(aHandlerInfo), aHandlerInfo.type);
+ this._setLiteral(infoID,
+ NC_ALWAYS_ASK,
+ aHandlerInfo.alwaysAskBeforeHandling ? "true" : "false");
+ },
+
+ _storeExtensions: function HS__storeExtensions(aHandlerInfo) {
+ if (aHandlerInfo instanceof Ci.nsIMIMEInfo) {
+ var typeID = this._getTypeID(this._getClass(aHandlerInfo), aHandlerInfo.type);
+ var extEnum = aHandlerInfo.getFileExtensions();
+ while (extEnum.hasMore()) {
+ let ext = extEnum.getNext().toLowerCase();
+ if (!this._hasLiteralAssertion(typeID, NC_FILE_EXTENSIONS, ext)) {
+ this._setLiteral(typeID, NC_FILE_EXTENSIONS, ext);
+ }
+ }
+ }
+ },
+
+
+ //**************************************************************************//
+ // Convenience Getters
+
+ // Observer Service
+ __observerSvc: null,
+ get _observerSvc() {
+ if (!this.__observerSvc)
+ this.__observerSvc =
+ Cc["@mozilla.org/observer-service;1"].
+ getService(Ci.nsIObserverService);
+ return this.__observerSvc;
+ },
+
+ // Directory Service
+ __dirSvc: null,
+ get _dirSvc() {
+ if (!this.__dirSvc)
+ this.__dirSvc =
+ Cc["@mozilla.org/file/directory_service;1"].
+ getService(Ci.nsIProperties);
+ return this.__dirSvc;
+ },
+
+ // MIME Service
+ __mimeSvc: null,
+ get _mimeSvc() {
+ if (!this.__mimeSvc)
+ this.__mimeSvc =
+ Cc["@mozilla.org/mime;1"].
+ getService(Ci.nsIMIMEService);
+ return this.__mimeSvc;
+ },
+
+ // Protocol Service
+ __protocolSvc: null,
+ get _protocolSvc() {
+ if (!this.__protocolSvc)
+ this.__protocolSvc =
+ Cc["@mozilla.org/uriloader/external-protocol-service;1"].
+ getService(Ci.nsIExternalProtocolService);
+ return this.__protocolSvc;
+ },
+
+ // RDF Service
+ __rdf: null,
+ get _rdf() {
+ if (!this.__rdf)
+ this.__rdf = Cc["@mozilla.org/rdf/rdf-service;1"].
+ getService(Ci.nsIRDFService);
+ return this.__rdf;
+ },
+
+ // RDF Container Utils
+ __containerUtils: null,
+ get _containerUtils() {
+ if (!this.__containerUtils)
+ this.__containerUtils = Cc["@mozilla.org/rdf/container-utils;1"].
+ getService(Ci.nsIRDFContainerUtils);
+ return this.__containerUtils;
+ },
+
+ // RDF datasource containing content handling config (i.e. mimeTypes.rdf)
+ __ds: null,
+ get _ds() {
+ if (!this.__ds) {
+ var file = this._dirSvc.get("UMimTyp", Ci.nsIFile);
+ // FIXME: make this a memoizing getter if we use it anywhere else.
+ var ioService = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+ var fileHandler = ioService.getProtocolHandler("file").
+ QueryInterface(Ci.nsIFileProtocolHandler);
+ this.__ds =
+ this._rdf.GetDataSourceBlocking(fileHandler.getURLSpecFromFile(file));
+ }
+
+ return this.__ds;
+ },
+
+
+ //**************************************************************************//
+ // Datastore Utils
+
+ /**
+ * Get the string identifying whether this is a MIME or a protocol handler.
+ * This string is used in the URI IDs of various RDF properties.
+ *
+ * @param aHandlerInfo {nsIHandlerInfo} the handler for which to get the class
+ *
+ * @returns {string} the class
+ */
+ _getClass: function HS__getClass(aHandlerInfo) {
+ if (aHandlerInfo instanceof Ci.nsIMIMEInfo)
+ return CLASS_MIMEINFO;
+ else
+ return CLASS_PROTOCOLINFO;
+ },
+
+ /**
+ * Return the unique identifier for a content type record, which stores
+ * the value field plus a reference to the content type's handler info record.
+ *
+ * |urn:<class>:<type>|
+ *
+ * XXX: should this be a property of nsIHandlerInfo?
+ *
+ * @param aClass {string} the class (CLASS_MIMEINFO or CLASS_PROTOCOLINFO)
+ * @param aType {string} the type (a MIME type or protocol scheme)
+ *
+ * @returns {string} the ID
+ */
+ _getTypeID: function HS__getTypeID(aClass, aType) {
+ return "urn:" + aClass + ":" + aType;
+ },
+
+ /**
+ * Return the unique identifier for a handler info record, which stores
+ * the preferredAction and alwaysAsk fields plus a reference to the preferred
+ * handler app. Roughly equivalent to the nsIHandlerInfo interface.
+ *
+ * |urn:<class>:handler:<type>|
+ *
+ * FIXME: the type info record should be merged into the type record,
+ * since there's a one to one relationship between them, and this record
+ * merely stores additional attributes of a content type.
+ *
+ * @param aClass {string} the class (CLASS_MIMEINFO or CLASS_PROTOCOLINFO)
+ * @param aType {string} the type (a MIME type or protocol scheme)
+ *
+ * @returns {string} the ID
+ */
+ _getInfoID: function HS__getInfoID(aClass, aType) {
+ return "urn:" + aClass + ":handler:" + aType;
+ },
+
+ /**
+ * Return the unique identifier for a preferred handler record, which stores
+ * information about the preferred handler for a given content type, including
+ * its human-readable name and the path to its executable (for a local app)
+ * or its URI template (for a web app).
+ *
+ * |urn:<class>:externalApplication:<type>|
+ *
+ * XXX: should this be a property of nsIHandlerApp?
+ *
+ * FIXME: this should be an arbitrary ID, and we should retrieve it from
+ * the datastore for a given content type via the NC:ExternalApplication
+ * property rather than looking for a specific ID, so a handler doesn't
+ * have to change IDs when it goes from being a possible handler to being
+ * the preferred one (once we support possible handlers).
+ *
+ * @param aClass {string} the class (CLASS_MIMEINFO or CLASS_PROTOCOLINFO)
+ * @param aType {string} the type (a MIME type or protocol scheme)
+ *
+ * @returns {string} the ID
+ */
+ _getPreferredHandlerID: function HS__getPreferredHandlerID(aClass, aType) {
+ return "urn:" + aClass + ":externalApplication:" + aType;
+ },
+
+ /**
+ * Return the unique identifier for a handler app record, which stores
+ * information about a possible handler for one or more content types,
+ * including its human-readable name and the path to its executable (for a
+ * local app) or its URI template (for a web app).
+ *
+ * Note: handler app IDs for preferred handlers are different. For those,
+ * see the _getPreferredHandlerID method.
+ *
+ * @param aHandlerApp {nsIHandlerApp} the handler app object
+ */
+ _getPossibleHandlerAppID: function HS__getPossibleHandlerAppID(aHandlerApp) {
+ var handlerAppID = "urn:handler:";
+
+ if (aHandlerApp instanceof Ci.nsILocalHandlerApp)
+ handlerAppID += "local:" + aHandlerApp.executable.path;
+ else if(aHandlerApp instanceof Ci.nsIWebHandlerApp){
+ aHandlerApp.QueryInterface(Ci.nsIWebHandlerApp);
+ handlerAppID += "web:" + aHandlerApp.uriTemplate;
+ }
+ else if(aHandlerApp instanceof Ci.nsIDBusHandlerApp){
+ aHandlerApp.QueryInterface(Ci.nsIDBusHandlerApp);
+ handlerAppID += "dbus:" + aHandlerApp.service + " " + aHandlerApp.method + " " + aHandlerApp.uriTemplate;
+ }else{
+ throw "unknown handler type";
+ }
+
+ return handlerAppID;
+ },
+
+ /**
+ * Get the list of types for the given class, creating the list if it doesn't
+ * already exist. The class can be either CLASS_MIMEINFO or CLASS_PROTOCOLINFO
+ * (i.e. the result of a call to _getClass).
+ *
+ * |urn:<class>s|
+ * |urn:<class>s:root|
+ *
+ * @param aClass {string} the class for which to retrieve a list of types
+ *
+ * @returns {nsIRDFContainer} the list of types
+ */
+ _ensureAndGetTypeList: function HS__ensureAndGetTypeList(aClass) {
+ var source = this._rdf.GetResource("urn:" + aClass + "s");
+ var property =
+ this._rdf.GetResource(aClass == CLASS_MIMEINFO ? NC_MIME_TYPES
+ : NC_PROTOCOL_SCHEMES);
+ var target = this._rdf.GetResource("urn:" + aClass + "s:root");
+
+ // Make sure we have an arc from the source to the target.
+ if (!this._ds.HasAssertion(source, property, target, true))
+ this._ds.Assert(source, property, target, true);
+
+ // Make sure the target is a container.
+ if (!this._containerUtils.IsContainer(this._ds, target))
+ this._containerUtils.MakeSeq(this._ds, target);
+
+ // Get the type list as an RDF container.
+ var typeList = Cc["@mozilla.org/rdf/container;1"].
+ createInstance(Ci.nsIRDFContainer);
+ typeList.Init(this._ds, target);
+
+ return typeList;
+ },
+
+ /**
+ * Make sure there are records in the datasource for the given content type
+ * by creating them if they don't already exist. We have to do this before
+ * storing any specific data, because we can't assume the presence
+ * of the records (the nsIHandlerInfo object might have been created
+ * from the OS), and the records have to all be there in order for the helper
+ * app service to properly construct an nsIHandlerInfo object for the type.
+ *
+ * Based on old downloadactions.js::_ensureMIMERegistryEntry.
+ *
+ * @param aHandlerInfo {nsIHandlerInfo} the type to make sure has a record
+ */
+ _ensureRecordsForType: function HS__ensureRecordsForType(aHandlerInfo) {
+ // Get the list of types.
+ var typeList = this._ensureAndGetTypeList(this._getClass(aHandlerInfo));
+
+ // If there's already a record in the datastore for this type, then we
+ // don't need to do anything more.
+ var typeID = this._getTypeID(this._getClass(aHandlerInfo), aHandlerInfo.type);
+ var type = this._rdf.GetResource(typeID);
+ if (typeList.IndexOf(type) != -1)
+ return;
+
+ // Create a basic type record for this type.
+ typeList.AppendElement(type);
+ this._setLiteral(typeID, NC_VALUE, aHandlerInfo.type);
+
+ // Create a basic info record for this type.
+ var infoID = this._getInfoID(this._getClass(aHandlerInfo), aHandlerInfo.type);
+ this._setLiteral(infoID, NC_ALWAYS_ASK, "false");
+ this._setResource(typeID, NC_HANDLER_INFO, infoID);
+ // XXX Shouldn't we set preferredAction to useSystemDefault?
+ // That's what it is if there's no record in the datastore; why should it
+ // change to useHelperApp just because we add a record to the datastore?
+
+ // Create a basic preferred handler record for this type.
+ // XXX Not sure this is necessary, since preferred handlers are optional,
+ // and nsExternalHelperAppService::FillHandlerInfoForTypeFromDS doesn't seem
+ // to require the record , but downloadactions.js::_ensureMIMERegistryEntry
+ // used to create it, so we'll do the same.
+ var preferredHandlerID =
+ this._getPreferredHandlerID(this._getClass(aHandlerInfo), aHandlerInfo.type);
+ this._setLiteral(preferredHandlerID, NC_PATH, "");
+ this._setResource(infoID, NC_PREFERRED_APP, preferredHandlerID);
+ },
+
+ /**
+ * Append known handlers of the given class to the given array. The class
+ * can be either CLASS_MIMEINFO or CLASS_PROTOCOLINFO.
+ *
+ * @param aHandlers {array} the array of handlers to append to
+ * @param aClass {string} the class for which to append handlers
+ */
+ _appendHandlers: function HS__appendHandlers(aHandlers, aClass) {
+ var typeList = this._ensureAndGetTypeList(aClass);
+ var enumerator = typeList.GetElements();
+
+ while (enumerator.hasMoreElements()) {
+ var element = enumerator.getNext();
+
+ // This should never happen. If it does, that means our datasource
+ // is corrupted with type list entries that point to literal values
+ // instead of resources. If it does happen, let's just do our best
+ // to recover by ignoring this entry and moving on to the next one.
+ if (!(element instanceof Ci.nsIRDFResource))
+ continue;
+
+ // Get the value of the element's NC:value property, which contains
+ // the MIME type or scheme for which we're retrieving a handler info.
+ var type = this._getValue(element.ValueUTF8, NC_VALUE);
+ if (!type)
+ continue;
+
+ var handler;
+ if (typeList.Resource.ValueUTF8 == "urn:mimetypes:root")
+ handler = this._mimeSvc.getFromTypeAndExtension(type, null);
+ else
+ handler = this._protocolSvc.getProtocolHandlerInfo(type);
+
+ aHandlers.appendElement(handler, false);
+ }
+ },
+
+ /**
+ * Whether or not a property of an RDF source has a value.
+ *
+ * @param sourceURI {string} the URI of the source
+ * @param propertyURI {string} the URI of the property
+ * @returns {boolean} whether or not the property has a value
+ */
+ _hasValue: function HS__hasValue(sourceURI, propertyURI) {
+ var source = this._rdf.GetResource(sourceURI);
+ var property = this._rdf.GetResource(propertyURI);
+ return this._ds.hasArcOut(source, property);
+ },
+
+ /**
+ * Get the value of a property of an RDF source.
+ *
+ * @param sourceURI {string} the URI of the source
+ * @param propertyURI {string} the URI of the property
+ * @returns {string} the value of the property
+ */
+ _getValue: function HS__getValue(sourceURI, propertyURI) {
+ var source = this._rdf.GetResource(sourceURI);
+ var property = this._rdf.GetResource(propertyURI);
+
+ var target = this._ds.GetTarget(source, property, true);
+
+ if (!target)
+ return null;
+
+ if (target instanceof Ci.nsIRDFResource)
+ return target.ValueUTF8;
+
+ if (target instanceof Ci.nsIRDFLiteral)
+ return target.Value;
+
+ return null;
+ },
+
+ /**
+ * Get all targets for the property of an RDF source.
+ *
+ * @param sourceURI {string} the URI of the source
+ * @param propertyURI {string} the URI of the property
+ *
+ * @returns {nsISimpleEnumerator} an enumerator of targets
+ */
+ _getTargets: function HS__getTargets(sourceURI, propertyURI) {
+ var source = this._rdf.GetResource(sourceURI);
+ var property = this._rdf.GetResource(propertyURI);
+
+ return this._ds.GetTargets(source, property, true);
+ },
+
+ /**
+ * Set a property of an RDF source to a literal value.
+ *
+ * @param sourceURI {string} the URI of the source
+ * @param propertyURI {string} the URI of the property
+ * @param value {string} the literal value
+ */
+ _setLiteral: function HS__setLiteral(sourceURI, propertyURI, value) {
+ var source = this._rdf.GetResource(sourceURI);
+ var property = this._rdf.GetResource(propertyURI);
+ var target = this._rdf.GetLiteral(value);
+
+ this._setTarget(source, property, target);
+ },
+
+ /**
+ * Set a property of an RDF source to a resource target.
+ *
+ * @param sourceURI {string} the URI of the source
+ * @param propertyURI {string} the URI of the property
+ * @param targetURI {string} the URI of the target
+ */
+ _setResource: function HS__setResource(sourceURI, propertyURI, targetURI) {
+ var source = this._rdf.GetResource(sourceURI);
+ var property = this._rdf.GetResource(propertyURI);
+ var target = this._rdf.GetResource(targetURI);
+
+ this._setTarget(source, property, target);
+ },
+
+ /**
+ * Assert an arc into the RDF datasource if there is no arc with the given
+ * source and property; otherwise, if there is already an existing arc,
+ * change it to point to the given target. _setLiteral and _setResource
+ * call this after converting their string arguments into resources
+ * and literals, and most callers should call one of those two methods
+ * instead of this one.
+ *
+ * @param source {nsIRDFResource} the source
+ * @param property {nsIRDFResource} the property
+ * @param target {nsIRDFNode} the target
+ */
+ _setTarget: function HS__setTarget(source, property, target) {
+ if (this._ds.hasArcOut(source, property)) {
+ var oldTarget = this._ds.GetTarget(source, property, true);
+ this._ds.Change(source, property, oldTarget, target);
+ }
+ else
+ this._ds.Assert(source, property, target, true);
+ },
+
+ /**
+ * Assert that a property of an RDF source has a resource target.
+ *
+ * The difference between this method and _setResource is that this one adds
+ * an assertion even if one already exists, which allows its callers to make
+ * sets of assertions (i.e. to set a property to multiple targets).
+ *
+ * @param sourceURI {string} the URI of the source
+ * @param propertyURI {string} the URI of the property
+ * @param targetURI {string} the URI of the target
+ */
+ _addResourceAssertion: function HS__addResourceAssertion(sourceURI,
+ propertyURI,
+ targetURI) {
+ var source = this._rdf.GetResource(sourceURI);
+ var property = this._rdf.GetResource(propertyURI);
+ var target = this._rdf.GetResource(targetURI);
+
+ this._ds.Assert(source, property, target, true);
+ },
+
+ /**
+ * Remove an assertion with a resource target.
+ *
+ * @param sourceURI {string} the URI of the source
+ * @param propertyURI {string} the URI of the property
+ * @param targetURI {string} the URI of the target
+ */
+ _removeResourceAssertion: function HS__removeResourceAssertion(sourceURI,
+ propertyURI,
+ targetURI) {
+ var source = this._rdf.GetResource(sourceURI);
+ var property = this._rdf.GetResource(propertyURI);
+ var target = this._rdf.GetResource(targetURI);
+
+ this._ds.Unassert(source, property, target);
+ },
+
+ /**
+ * Whether or not a property of an RDF source has a given resource target.
+ *
+ * @param sourceURI {string} the URI of the source
+ * @param propertyURI {string} the URI of the property
+ * @param targetURI {string} the URI of the target
+ *
+ * @returns {boolean} whether or not there is such an assertion
+ */
+ _hasResourceAssertion: function HS__hasResourceAssertion(sourceURI,
+ propertyURI,
+ targetURI) {
+ var source = this._rdf.GetResource(sourceURI);
+ var property = this._rdf.GetResource(propertyURI);
+ var target = this._rdf.GetResource(targetURI);
+
+ return this._ds.HasAssertion(source, property, target, true);
+ },
+
+ /**
+ * Whether or not a property of an RDF source has a given literal value.
+ *
+ * @param sourceURI {string} the URI of the source
+ * @param propertyURI {string} the URI of the property
+ * @param value {string} the literal value
+ *
+ * @returns {boolean} whether or not there is such an assertion
+ */
+ _hasLiteralAssertion: function HS__hasLiteralAssertion(sourceURI,
+ propertyURI,
+ value) {
+ var source = this._rdf.GetResource(sourceURI);
+ var property = this._rdf.GetResource(propertyURI);
+ var target = this._rdf.GetLiteral(value);
+
+ return this._ds.HasAssertion(source, property, target, true);
+ },
+
+ /**
+ * Whether or not there is an RDF source that has the given property set to
+ * the given literal value.
+ *
+ * @param propertyURI {string} the URI of the property
+ * @param value {string} the literal value
+ *
+ * @returns {boolean} whether or not there is a source
+ */
+ _existsLiteralTarget: function HS__existsLiteralTarget(propertyURI, value) {
+ var property = this._rdf.GetResource(propertyURI);
+ var target = this._rdf.GetLiteral(value);
+
+ return this._ds.hasArcIn(target, property);
+ },
+
+ /**
+ * Get the source for a property set to a given literal value.
+ *
+ * @param propertyURI {string} the URI of the property
+ * @param value {string} the literal value
+ */
+ _getSourceForLiteral: function HS__getSourceForLiteral(propertyURI, value) {
+ var property = this._rdf.GetResource(propertyURI);
+ var target = this._rdf.GetLiteral(value);
+
+ var source = this._ds.GetSource(property, target, true);
+ if (source)
+ return source.ValueUTF8;
+
+ return null;
+ },
+
+ /**
+ * Whether or not there is an RDF source that has the given property set to
+ * the given resource target.
+ *
+ * @param propertyURI {string} the URI of the property
+ * @param targetURI {string} the URI of the target
+ *
+ * @returns {boolean} whether or not there is a source
+ */
+ _existsResourceTarget: function HS__existsResourceTarget(propertyURI,
+ targetURI) {
+ var property = this._rdf.GetResource(propertyURI);
+ var target = this._rdf.GetResource(targetURI);
+
+ return this._ds.hasArcIn(target, property);
+ },
+
+ /**
+ * Remove a property of an RDF source.
+ *
+ * @param sourceURI {string} the URI of the source
+ * @param propertyURI {string} the URI of the property
+ */
+ _removeTarget: function HS__removeTarget(sourceURI, propertyURI) {
+ var source = this._rdf.GetResource(sourceURI);
+ var property = this._rdf.GetResource(propertyURI);
+
+ if (this._ds.hasArcOut(source, property)) {
+ var target = this._ds.GetTarget(source, property, true);
+ this._ds.Unassert(source, property, target);
+ }
+ },
+
+ /**
+ * Remove all assertions about a given RDF source.
+ *
+ * Note: not recursive. If some assertions point to other resources,
+ * and you want to remove assertions about those resources too, you need
+ * to do so manually.
+ *
+ * @param sourceURI {string} the URI of the source
+ */
+ _removeAssertions: function HS__removeAssertions(sourceURI) {
+ var source = this._rdf.GetResource(sourceURI);
+ var properties = this._ds.ArcLabelsOut(source);
+
+ while (properties.hasMoreElements()) {
+ let property = properties.getNext();
+ let targets = this._ds.GetTargets(source, property, true);
+ while (targets.hasMoreElements()) {
+ let target = targets.getNext();
+ this._ds.Unassert(source, property, target);
+ }
+ }
+ }
+
+};
+
+//****************************************************************************//
+// More XPCOM Plumbing
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([HandlerService]);
diff --git a/uriloader/exthandler/nsHandlerService.manifest b/uriloader/exthandler/nsHandlerService.manifest
new file mode 100644
index 0000000000..5ed86c7987
--- /dev/null
+++ b/uriloader/exthandler/nsHandlerService.manifest
@@ -0,0 +1,2 @@
+component {32314cc8-22f7-4f7f-a645-1a45453ba6a6} nsHandlerService.js
+contract @mozilla.org/uriloader/handler-service;1 {32314cc8-22f7-4f7f-a645-1a45453ba6a6} process=main \ No newline at end of file
diff --git a/uriloader/exthandler/nsIContentDispatchChooser.idl b/uriloader/exthandler/nsIContentDispatchChooser.idl
new file mode 100644
index 0000000000..7c186deee1
--- /dev/null
+++ b/uriloader/exthandler/nsIContentDispatchChooser.idl
@@ -0,0 +1,45 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIHandlerInfo;
+interface nsIHelperAppLauncher;
+interface nsIURI;
+interface nsIInterfaceRequestor;
+
+/**
+ * This is used to ask a user what they would like to do with a given piece of
+ * content.
+ */
+[scriptable, uuid(456ca3b2-02be-4f97-89a2-08c08d3ad88f)]
+interface nsIContentDispatchChooser : nsISupports {
+ /**
+ * This request is passed to the helper app dialog because Gecko can not
+ * handle content of this type.
+ */
+ const unsigned long REASON_CANNOT_HANDLE = 0;
+
+ /**
+ * Asks the user what to do with the content.
+ *
+ * @param aHander
+ * The interface describing the details of how this content should or
+ * can be handled.
+ * @param aWindowContext
+ * The parent window context to show this chooser. This can be null,
+ * and some implementations may not care about it. Generally, you'll
+ * want to pass an nsIDOMWindow in so the chooser can be properly
+ * parented when opened.
+ * @param aURI
+ * The URI of the resource that we are asking about.
+ * @param aReason
+ * The reason why we are asking (see above).
+ */
+ void ask(in nsIHandlerInfo aHandler,
+ in nsIInterfaceRequestor aWindowContext,
+ in nsIURI aURI,
+ in unsigned long aReason);
+};
+
diff --git a/uriloader/exthandler/nsIExternalHelperAppService.idl b/uriloader/exthandler/nsIExternalHelperAppService.idl
new file mode 100644
index 0000000000..bfdfff5cea
--- /dev/null
+++ b/uriloader/exthandler/nsIExternalHelperAppService.idl
@@ -0,0 +1,154 @@
+/* -*- Mode: C++; tab-width: 3; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsICancelable.idl"
+
+interface nsIURI;
+interface nsIRequest;
+interface nsIStreamListener;
+interface nsIFile;
+interface nsIMIMEInfo;
+interface nsIWebProgressListener2;
+interface nsIInterfaceRequestor;
+
+/**
+ * The external helper app service is used for finding and launching
+ * platform specific external applications for a given mime content type.
+ */
+[scriptable, uuid(1E4F3AE1-B737-431F-A95D-31FA8DA70199)]
+interface nsIExternalHelperAppService : nsISupports
+{
+ /**
+ * Binds an external helper application to a stream listener. The caller
+ * should pump data into the returned stream listener. When the OnStopRequest
+ * is issued, the stream listener implementation will launch the helper app
+ * with this data.
+ * @param aMimeContentType The content type of the incoming data
+ * @param aRequest The request corresponding to the incoming data
+ * @param aContentContext Used in processing content document refresh
+ * headers after target content is downloaded. Note in e10s land
+ * this is likely a CPOW that points to a window in the child process.
+ * @param aForceSave True to always save this content to disk, regardless of
+ * nsIMIMEInfo and other such influences.
+ * @param aWindowContext Used in parenting helper app dialogs, usually
+ * points to the parent browser window. This parameter may be null,
+ * in which case dialogs will be parented to aContentContext.
+ * @return A nsIStreamListener which the caller should pump the data into.
+ */
+ nsIStreamListener doContent (in ACString aMimeContentType,
+ in nsIRequest aRequest,
+ in nsIInterfaceRequestor aContentContext,
+ in boolean aForceSave,
+ [optional] in nsIInterfaceRequestor aWindowContext);
+
+ /**
+ * Returns true if data from a URL with this extension combination
+ * is to be decoded from aEncodingType prior to saving or passing
+ * off to helper apps, false otherwise.
+ */
+ boolean applyDecodingForExtension(in AUTF8String aExtension,
+ in ACString aEncodingType);
+
+};
+
+/**
+ * This is a private interface shared between external app handlers and the platform specific
+ * external helper app service
+ */
+[scriptable, uuid(6613e2e7-feab-4e3a-bb1f-b03200d544ec)]
+interface nsPIExternalAppLauncher : nsISupports
+{
+ /**
+ * mscott --> eventually I should move this into a new service so other
+ * consumers can add temporary files they want deleted on exit.
+ * @param aTemporaryFile A temporary file we should delete on exit.
+ */
+ void deleteTemporaryFileOnExit(in nsIFile aTemporaryFile);
+ /**
+ * Delete a temporary file created inside private browsing mode when
+ * the private browsing mode has ended.
+ */
+ void deleteTemporaryPrivateFileWhenPossible(in nsIFile aTemporaryFile);
+};
+
+/**
+ * A helper app launcher is a small object created to handle the launching
+ * of an external application.
+ *
+ * Note that cancelling the load via the nsICancelable interface will release
+ * the reference to the launcher dialog.
+ */
+[scriptable, uuid(acf2a516-7d7f-4771-8b22-6c4a559c088e)]
+interface nsIHelperAppLauncher : nsICancelable
+{
+ /**
+ * The mime info object associated with the content type this helper app
+ * launcher is currently attempting to load
+ */
+ readonly attribute nsIMIMEInfo MIMEInfo;
+
+ /**
+ * The source uri
+ */
+ readonly attribute nsIURI source;
+
+ /**
+ * The suggested name for this file
+ */
+ readonly attribute AString suggestedFileName;
+
+ /**
+ * Saves the final destination of the file. Does not actually perform the
+ * save.
+ * NOTE: This will release the reference to the
+ * nsIHelperAppLauncherDialog.
+ */
+ void saveToDisk(in nsIFile aNewFileLocation, in boolean aRememberThisPreference);
+
+ /**
+ * Remembers that aApplication should be used to launch this content. Does
+ * not actually launch the application.
+ * NOTE: This will release the reference to the nsIHelperAppLauncherDialog.
+ * @param aApplication nsIFile corresponding to the location of the application to use.
+ * @param aRememberThisPreference TRUE if we should remember this choice.
+ */
+ void launchWithApplication(in nsIFile aApplication, in boolean aRememberThisPreference);
+
+ /**
+ * Callback invoked by nsIHelperAppLauncherDialog::promptForSaveToFileAsync
+ * after the user has chosen a file through the File Picker (or dismissed it).
+ * @param aFile The file that was chosen by the user (or null if dialog was dismissed).
+ */
+ void saveDestinationAvailable(in nsIFile aFile);
+
+ /**
+ * The following methods are used by the progress dialog to get or set
+ * information on the current helper app launcher download.
+ * This reference will be released when the download is finished (after the
+ * listener receives the STATE_STOP notification).
+ */
+ void setWebProgressListener(in nsIWebProgressListener2 aWebProgressListener);
+
+ /**
+ * The file we are saving to
+ */
+ readonly attribute nsIFile targetFile;
+
+ /**
+ * The executable-ness of the target file
+ */
+ readonly attribute boolean targetFileIsExecutable;
+
+ /**
+ * Time when the download started
+ */
+ readonly attribute PRTime timeDownloadStarted;
+
+ /**
+ * The download content length, or -1 if the length is not available.
+ */
+ readonly attribute int64_t contentLength;
+};
diff --git a/uriloader/exthandler/nsIExternalProtocolService.idl b/uriloader/exthandler/nsIExternalProtocolService.idl
new file mode 100644
index 0000000000..44d7560306
--- /dev/null
+++ b/uriloader/exthandler/nsIExternalProtocolService.idl
@@ -0,0 +1,135 @@
+/* -*- Mode: C++; tab-width: 3; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIURI;
+interface nsIFile;
+interface nsIInterfaceRequestor;
+interface nsIHandlerInfo;
+
+/**
+ * The external protocol service is used for finding and launching
+ * web handlers (a la registerProtocolHandler in the HTML5 draft) or
+ * platform-specific applications for handling particular protocols.
+ *
+ * You can ask the external protocol service if it has an external
+ * handler for a given protocol scheme. And you can ask it to load
+ * the url using the default handler.
+ */
+[scriptable, uuid(70f93b7a-3ec6-4bcb-b093-92d9984c9f83)]
+interface nsIExternalProtocolService : nsISupports
+{
+ /**
+ * Check whether a handler for a specific protocol exists. Specifically,
+ * this looks to see whether there are any known possible application handlers
+ * in either the nsIHandlerService datastore or registered with the OS.
+ *
+ * @param aProtocolScheme The scheme from a url: http, ftp, mailto, etc.
+ *
+ * @return true if we have a handler and false otherwise.
+ *
+ * XXX shouldn't aProtocolScheme be an ACString like nsIURI::scheme?
+ */
+ boolean externalProtocolHandlerExists(in string aProtocolScheme);
+
+ /**
+ * Check whether a handler for a specific protocol is "exposed" as a visible
+ * feature of the current application.
+ *
+ * An exposed protocol handler is one that can be used in all contexts. A
+ * non-exposed protocol handler is one that can only be used internally by the
+ * application. For example, a non-exposed protocol would not be loaded by the
+ * application in response to a link click or a X-remote openURL command.
+ * Instead, it would be deferred to the system's external protocol handler.
+ * XXX shouldn't aProtocolScheme be an ACString like nsIURI::scheme?
+ */
+ boolean isExposedProtocol(in string aProtocolScheme);
+
+ /**
+ * Retrieve the handler for the given protocol. If neither the application
+ * nor the OS knows about a handler for the protocol, the object this method
+ * returns will represent a default handler for unknown content.
+ *
+ * @param aProtocolScheme the scheme from a URL: http, ftp, mailto, etc.
+ *
+ * Note: aProtocolScheme should not include a trailing colon, which is part
+ * of the URI syntax, not part of the scheme itself (i.e. pass "mailto" not
+ * "mailto:").
+ *
+ * @return the handler, if any; otherwise a default handler
+ */
+ nsIHandlerInfo getProtocolHandlerInfo(in ACString aProtocolScheme);
+
+ /**
+ * Given a scheme, looks up the protocol info from the OS. This should be
+ * overridden by each OS's implementation.
+ *
+ * @param aScheme The protocol scheme we are looking for.
+ * @param aFound Was an OS default handler for this scheme found?
+ * @return An nsIHanderInfo for the protocol.
+ */
+ nsIHandlerInfo getProtocolHandlerInfoFromOS(in ACString aProtocolScheme,
+ out boolean aFound);
+
+ /**
+ * Set some sane defaults for a protocol handler object.
+ *
+ * @param aHandlerInfo nsIHandlerInfo object, as returned by
+ * getProtocolHandlerInfoFromOS
+ * @param aOSHandlerExists was the object above created for an extant
+ * OS default handler? This is generally the
+ * value of the aFound out param from
+ * getProtocolHandlerInfoFromOS.
+ */
+ void setProtocolHandlerDefaults(in nsIHandlerInfo aHandlerInfo,
+ in boolean aOSHandlerExists);
+
+ /**
+ * Used to load a url via an external protocol handler (if one exists)
+ *
+ * @param aURL The url to load
+ *
+ * @deprecated Use LoadURI instead (See Bug 389565 for removal)
+ */
+ [deprecated] void loadUrl(in nsIURI aURL);
+
+ /**
+ * Used to load a URI via an external application. Might prompt the user for
+ * permission to load the external application.
+ *
+ * @param aURI
+ * The URI to load
+ *
+ * @param aWindowContext
+ * The window to parent the dialog against, and, if a web handler
+ * is chosen, it is loaded in this window as well. This parameter
+ * may be ultimately passed nsIURILoader.openURI in the case of a
+ * web handler, and aWindowContext is null or not present, web
+ * handlers will fail. We need to do better than that; bug 394483
+ * filed in order to track.
+ *
+ * @note Embedders that do not expose the http protocol should not currently
+ * use web-based protocol handlers, as handoff won't work correctly
+ * (bug 394479).
+ */
+ void loadURI(in nsIURI aURI,
+ [optional] in nsIInterfaceRequestor aWindowContext);
+
+ /**
+ * Gets a human-readable description for the application responsible for
+ * handling a specific protocol.
+ *
+ * @param aScheme The scheme to look up. For example, "mms".
+ *
+ * @throw NS_ERROR_NOT_IMPLEMENTED
+ * If getting descriptions for protocol helpers is not supported
+ * @throw NS_ERROR_NOT_AVAILABLE
+ * If no protocol helper exists for this scheme, or if it is not
+ * possible to get a description for it.
+ */
+ AString getApplicationDescription(in AUTF8String aScheme);
+};
diff --git a/uriloader/exthandler/nsIExternalSharingAppService.idl b/uriloader/exthandler/nsIExternalSharingAppService.idl
new file mode 100644
index 0000000000..f58f6981dd
--- /dev/null
+++ b/uriloader/exthandler/nsIExternalSharingAppService.idl
@@ -0,0 +1,28 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIMIMEInfo.idl"
+
+%{C++
+#define NS_EXTERNALSHARINGAPPSERVICE_CONTRACTID "@mozilla.org/uriloader/external-sharing-app-service;1"
+%}
+
+
+[scriptable, uuid(7111f769-53ec-41fd-b314-613661d5b6ba)]
+interface nsISharingHandlerApp : nsIHandlerApp
+{
+ void share(in AString data, [optional] in AString title);
+};
+
+[scriptable, uuid(cf7d04e5-3892-482e-81bb-073dc1c83f76)]
+interface nsIExternalSharingAppService : nsISupports {
+ void shareWithDefault(in AString data, in AString mime,
+ [optional] in AString title);
+
+ void getSharingApps(in AString aMIMEType,
+ [optional] out unsigned long aLen,
+ [array, size_is(aLen), retval] out nsISharingHandlerApp handlerApps);
+};
+
+
diff --git a/uriloader/exthandler/nsIExternalURLHandlerService.idl b/uriloader/exthandler/nsIExternalURLHandlerService.idl
new file mode 100644
index 0000000000..3573497591
--- /dev/null
+++ b/uriloader/exthandler/nsIExternalURLHandlerService.idl
@@ -0,0 +1,26 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIMIMEInfo.idl"
+
+/**
+ * The external URL handler service is used for finding
+ * platform-specific applications for handling particular URLs.
+ */
+
+[scriptable, uuid(56c5c7d3-6fd3-43f8-9429-4397e111453a)]
+interface nsIExternalURLHandlerService : nsISupports
+{
+ /**
+ * Given a URL, looks up the handler info from the OS. This should be
+ * overridden by each OS's implementation.
+ *
+ * @param aURL The URL we are looking for.
+ * @param aFound Was an OS default handler for this URL found?
+ * @return An nsIHanderInfo for the protocol.
+ */
+ nsIHandlerInfo getURLHandlerInfoFromOS(in nsIURI aURL,
+ out boolean aFound);
+
+};
diff --git a/uriloader/exthandler/nsIHandlerService.idl b/uriloader/exthandler/nsIHandlerService.idl
new file mode 100644
index 0000000000..efea43937d
--- /dev/null
+++ b/uriloader/exthandler/nsIHandlerService.idl
@@ -0,0 +1,117 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIHandlerInfo;
+interface nsISimpleEnumerator;
+
+[scriptable, uuid(53f0ad17-ec62-46a1-adbc-efccc06babcd)]
+interface nsIHandlerService : nsISupports
+{
+ /**
+ * Retrieve a list of all handlers in the datastore. This list is not
+ * guaranteed to be in any particular order, and callers should not assume
+ * it will remain in the same order in the future.
+ *
+ * @returns a list of all handlers in the datastore
+ */
+ nsISimpleEnumerator enumerate();
+
+ /**
+ * Fill a handler info object with information from the datastore.
+ *
+ * Note: because of the way the external helper app service currently mixes
+ * OS and user handler info in the same handler info object, this method
+ * takes an existing handler info object (probably retrieved from the OS)
+ * and "fills it in" with information from the datastore, overriding any
+ * existing properties on the object with properties from the datastore.
+ *
+ * Ultimately, however, we're going to separate OS and user handler info
+ * into separate objects, at which point this method should be renamed to
+ * something like "get" or "retrieve", take a class and type (or perhaps
+ * a type whose class can be determined by querying the type, for example
+ * an nsIContentType which is also an nsIMIMEType or an nsIProtocolScheme),
+ * and return a handler info object representing only the user info.
+ *
+ * Note: if you specify an override type, then the service will fill in
+ * the handler info object with information about that type instead of
+ * the type specified by the object's nsIHandlerInfo::type attribute.
+ *
+ * This is useful when you are trying to retrieve information about a MIME
+ * type that doesn't exist in the datastore, but you have a file extension
+ * for that type, and nsIHandlerService::getTypeFromExtension returns another
+ * MIME type that does exist in the datastore and can handle that extension.
+ *
+ * For example, the user clicks on a link, and the content has a MIME type
+ * that isn't in the datastore, but the link has a file extension, and that
+ * extension is associated with another MIME type in the datastore (perhaps
+ * an unofficial MIME type preceded an official one, like with image/x-png
+ * and image/png).
+ *
+ * In that situation, you can call this method to fill in the handler info
+ * object with information about that other type by passing the other type
+ * as the aOverrideType parameter.
+ *
+ * @param aHandlerInfo the handler info object
+ * @param aOverrideType a type to use instead of aHandlerInfo::type
+ *
+ * Note: if there is no information in the datastore about this type,
+ * this method throws NS_ERROR_NOT_AVAILABLE. Callers are encouraged to
+ * check exists() before calling fillHandlerInfo(), to prevent spamming the
+ * console with XPCOM exception errors.
+ */
+ void fillHandlerInfo(in nsIHandlerInfo aHandlerInfo,
+ in ACString aOverrideType);
+
+ /**
+ * Save the preferred action, preferred handler, possible handlers, and
+ * always ask properties of the given handler info object to the datastore.
+ * Updates an existing record or creates a new one if necessary.
+ *
+ * Note: if preferred action is undefined or invalid, then we assume
+ * the default value nsIHandlerInfo::useHelperApp.
+ *
+ * @param aHandlerInfo the handler info object
+ */
+ void store(in nsIHandlerInfo aHandlerInfo);
+
+ /**
+ * Whether or not a record for the given handler info object exists
+ * in the datastore. If the datastore is corrupt (or some other error
+ * is caught in the implementation), false will be returned.
+ *
+ * @param aHandlerInfo a handler info object
+ *
+ * @returns whether or not a record exists
+ */
+ boolean exists(in nsIHandlerInfo aHandlerInfo);
+
+ /**
+ * Remove the given handler info object from the datastore. Deletes all
+ * records associated with the object, including the preferred handler, info,
+ * and type records plus the entry in the list of types, if they exist.
+ * Otherwise, it does nothing and does not return an error.
+ *
+ * @param aHandlerInfo the handler info object
+ */
+ void remove(in nsIHandlerInfo aHandlerInfo);
+
+ /**
+ * Get the MIME type mapped to the given file extension in the datastore.
+ *
+ * XXX If we ever support extension -> protocol scheme mappings, then this
+ * method should work for those as well.
+ *
+ * Note: in general, you should use nsIMIMEService::getTypeFromExtension
+ * to get a MIME type from a file extension, as that method checks a variety
+ * of other sources besides just the datastore. Use this only when you want
+ * to specifically get only the mapping available in the datastore.
+ *
+ * @param aFileExtension the file extension
+ *
+ * @returns the MIME type, if any; otherwise returns an empty string ("").
+ */
+ ACString getTypeFromExtension(in ACString aFileExtension);
+};
diff --git a/uriloader/exthandler/nsIHelperAppLauncherDialog.idl b/uriloader/exthandler/nsIHelperAppLauncherDialog.idl
new file mode 100644
index 0000000000..f8190e744b
--- /dev/null
+++ b/uriloader/exthandler/nsIHelperAppLauncherDialog.idl
@@ -0,0 +1,89 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIHelperAppLauncher;
+interface nsIFile;
+
+/**
+ * This interface is used to display a confirmation dialog before
+ * launching a "helper app" to handle content not handled by
+ * Mozilla.
+ *
+ * Usage: Clients (of which there is one: the nsIExternalHelperAppService
+ * implementation in mozilla/uriloader/exthandler) create an instance of
+ * this interface (using the contract ID) and then call the show() method.
+ *
+ * The dialog is shown non-modally. The implementation of the dialog
+ * will access methods of the nsIHelperAppLauncher passed in to show()
+ * in order to cause a "save to disk" or "open using" action.
+ */
+[scriptable, uuid(bfc739f3-8d75-4034-a6f8-1039a5996bad)]
+interface nsIHelperAppLauncherDialog : nsISupports {
+ /**
+ * This request is passed to the helper app dialog because Gecko can not
+ * handle content of this type.
+ */
+ const unsigned long REASON_CANTHANDLE = 0;
+
+ /**
+ * The server requested external handling.
+ */
+ const unsigned long REASON_SERVERREQUEST = 1;
+
+ /**
+ * Gecko detected that the type sent by the server (e.g. text/plain) does
+ * not match the actual type.
+ */
+ const unsigned long REASON_TYPESNIFFED = 2;
+
+ /**
+ * Show confirmation dialog for launching application (or "save to
+ * disk") for content specified by aLauncher.
+ *
+ * @param aLauncher
+ * A nsIHelperAppLauncher to be invoked when a file is selected.
+ * @param aWindowContext
+ * Window associated with action.
+ * @param aReason
+ * One of the constants from above. It indicates why the dialog is
+ * shown. Implementors should treat unknown reasons like
+ * REASON_CANTHANDLE.
+ */
+ void show(in nsIHelperAppLauncher aLauncher,
+ in nsISupports aWindowContext,
+ in unsigned long aReason);
+
+ /**
+ * Async invoke a save-to-file dialog instead of the full fledged helper app
+ * dialog. When the file is chosen (or the dialog is closed), the callback
+ * in aLauncher (aLauncher.saveDestinationAvailable) is called with the
+ * selected file.
+ *
+ * @param aLauncher
+ * A nsIHelperAppLauncher to be invoked when a file is selected.
+ * @param aWindowContext
+ * Window associated with action.
+ * @param aDefaultFileName
+ * Default file name to provide (can be null)
+ * @param aSuggestedFileExtension
+ * Sugested file extension
+ * @param aForcePrompt
+ * Set to true to force prompting the user for thet file
+ * name/location, otherwise perferences may control if the user is
+ * prompted.
+ */
+ void promptForSaveToFileAsync(in nsIHelperAppLauncher aLauncher,
+ in nsISupports aWindowContext,
+ in wstring aDefaultFileName,
+ in wstring aSuggestedFileExtension,
+ in boolean aForcePrompt);
+};
+
+
+%{C++
+#define NS_HELPERAPPLAUNCHERDLG_CONTRACTID "@mozilla.org/helperapplauncherdialog;1"
+%}
diff --git a/uriloader/exthandler/nsLocalHandlerApp.cpp b/uriloader/exthandler/nsLocalHandlerApp.cpp
new file mode 100644
index 0000000000..f1b65dca21
--- /dev/null
+++ b/uriloader/exthandler/nsLocalHandlerApp.cpp
@@ -0,0 +1,179 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim:expandtab:shiftwidth=2:tabstop=2:cin:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsLocalHandlerApp.h"
+#include "nsIURI.h"
+#include "nsIProcess.h"
+
+// XXX why does nsMIMEInfoImpl have a threadsafe nsISupports? do we need one
+// here too?
+NS_IMPL_ISUPPORTS(nsLocalHandlerApp, nsILocalHandlerApp, nsIHandlerApp)
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsIHandlerApp
+
+NS_IMETHODIMP nsLocalHandlerApp::GetName(nsAString& aName)
+{
+ if (mName.IsEmpty() && mExecutable) {
+ // Don't want to cache this, just in case someone resets the app
+ // without changing the description....
+ mExecutable->GetLeafName(aName);
+ } else {
+ aName.Assign(mName);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsLocalHandlerApp::SetName(const nsAString & aName)
+{
+ mName.Assign(aName);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLocalHandlerApp::SetDetailedDescription(const nsAString & aDescription)
+{
+ mDetailedDescription.Assign(aDescription);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLocalHandlerApp::GetDetailedDescription(nsAString& aDescription)
+{
+ aDescription.Assign(mDetailedDescription);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLocalHandlerApp::Equals(nsIHandlerApp *aHandlerApp, bool *_retval)
+{
+ NS_ENSURE_ARG_POINTER(aHandlerApp);
+
+ *_retval = false;
+
+ // If the handler app isn't a local handler app, then it's not the same app.
+ nsCOMPtr <nsILocalHandlerApp> localHandlerApp = do_QueryInterface(aHandlerApp);
+ if (!localHandlerApp)
+ return NS_OK;
+
+ // If either handler app doesn't have an executable, then they aren't
+ // the same app.
+ nsCOMPtr<nsIFile> executable;
+ nsresult rv = localHandlerApp->GetExecutable(getter_AddRefs(executable));
+ if (NS_FAILED(rv))
+ return rv;
+
+ // Equality for two empty nsIHandlerApp
+ if (!executable && !mExecutable) {
+ *_retval = true;
+ return NS_OK;
+ }
+
+ // At least one is set so they are not equal
+ if (!mExecutable || !executable)
+ return NS_OK;
+
+ // Check the command line parameter list lengths
+ uint32_t len;
+ localHandlerApp->GetParameterCount(&len);
+ if (mParameters.Length() != len)
+ return NS_OK;
+
+ // Check the command line params lists
+ for (uint32_t idx = 0; idx < mParameters.Length(); idx++) {
+ nsAutoString param;
+ if (NS_FAILED(localHandlerApp->GetParameter(idx, param)) ||
+ !param.Equals(mParameters[idx]))
+ return NS_OK;
+ }
+
+ return executable->Equals(mExecutable, _retval);
+}
+
+NS_IMETHODIMP
+nsLocalHandlerApp::LaunchWithURI(nsIURI *aURI,
+ nsIInterfaceRequestor *aWindowContext)
+{
+ // pass the entire URI to the handler.
+ nsAutoCString spec;
+ aURI->GetAsciiSpec(spec);
+ return LaunchWithIProcess(spec);
+}
+
+nsresult
+nsLocalHandlerApp::LaunchWithIProcess(const nsCString& aArg)
+{
+ nsresult rv;
+ nsCOMPtr<nsIProcess> process = do_CreateInstance(NS_PROCESS_CONTRACTID, &rv);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (NS_FAILED(rv = process->Init(mExecutable)))
+ return rv;
+
+ const char *string = aArg.get();
+
+ return process->Run(false, &string, 1);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsILocalHandlerApp
+
+NS_IMETHODIMP
+nsLocalHandlerApp::GetExecutable(nsIFile **aExecutable)
+{
+ NS_IF_ADDREF(*aExecutable = mExecutable);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLocalHandlerApp::SetExecutable(nsIFile *aExecutable)
+{
+ mExecutable = aExecutable;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLocalHandlerApp::GetParameterCount(uint32_t *aParameterCount)
+{
+ *aParameterCount = mParameters.Length();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLocalHandlerApp::ClearParameters()
+{
+ mParameters.Clear();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLocalHandlerApp::AppendParameter(const nsAString & aParam)
+{
+ mParameters.AppendElement(aParam);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLocalHandlerApp::GetParameter(uint32_t parameterIndex, nsAString & _retval)
+{
+ if (mParameters.Length() <= parameterIndex)
+ return NS_ERROR_INVALID_ARG;
+
+ _retval.Assign(mParameters[parameterIndex]);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLocalHandlerApp::ParameterExists(const nsAString & aParam, bool *_retval)
+{
+ *_retval = mParameters.Contains(aParam);
+ return NS_OK;
+}
diff --git a/uriloader/exthandler/nsLocalHandlerApp.h b/uriloader/exthandler/nsLocalHandlerApp.h
new file mode 100644
index 0000000000..30b410ce32
--- /dev/null
+++ b/uriloader/exthandler/nsLocalHandlerApp.h
@@ -0,0 +1,60 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim:expandtab:shiftwidth=2:tabstop=2:cin:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __nsLocalHandlerAppImpl_h__
+#define __nsLocalHandlerAppImpl_h__
+
+#include "nsString.h"
+#include "nsIMIMEInfo.h"
+#include "nsIFile.h"
+#include "nsTArray.h"
+
+class nsLocalHandlerApp : public nsILocalHandlerApp
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIHANDLERAPP
+ NS_DECL_NSILOCALHANDLERAPP
+
+ nsLocalHandlerApp() { }
+
+ nsLocalHandlerApp(const char16_t *aName, nsIFile *aExecutable)
+ : mName(aName), mExecutable(aExecutable) { }
+
+ nsLocalHandlerApp(const nsAString & aName, nsIFile *aExecutable)
+ : mName(aName), mExecutable(aExecutable) { }
+
+protected:
+ virtual ~nsLocalHandlerApp() { }
+
+ nsString mName;
+ nsString mDetailedDescription;
+ nsTArray<nsString> mParameters;
+ nsCOMPtr<nsIFile> mExecutable;
+
+ /**
+ * Launches this application with a single argument (typically either
+ * a file path or a URI spec). This is meant as a helper method for
+ * implementations of (e.g.) LaunchWithURI.
+ *
+ * @param aApp The application to launch (may not be null)
+ * @param aArg The argument to pass on the command line
+ */
+ nsresult LaunchWithIProcess(const nsCString &aArg);
+};
+
+// any platforms that need a platform-specific class instead of just
+// using nsLocalHandlerApp need to add an include and a typedef here.
+#ifdef XP_MACOSX
+# ifndef NSLOCALHANDLERAPPMAC_H_
+# include "mac/nsLocalHandlerAppMac.h"
+typedef nsLocalHandlerAppMac PlatformLocalHandlerApp_t;
+# endif
+#else
+typedef nsLocalHandlerApp PlatformLocalHandlerApp_t;
+#endif
+
+#endif // __nsLocalHandlerAppImpl_h__
diff --git a/uriloader/exthandler/nsMIMEInfoImpl.cpp b/uriloader/exthandler/nsMIMEInfoImpl.cpp
new file mode 100644
index 0000000000..59886e4651
--- /dev/null
+++ b/uriloader/exthandler/nsMIMEInfoImpl.cpp
@@ -0,0 +1,435 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMIMEInfoImpl.h"
+#include "nsXPIDLString.h"
+#include "nsReadableUtils.h"
+#include "nsStringEnumerator.h"
+#include "nsIFile.h"
+#include "nsIFileURL.h"
+#include "nsEscape.h"
+#include "nsIURILoader.h"
+#include "nsCURILoader.h"
+
+// nsISupports methods
+NS_IMPL_ADDREF(nsMIMEInfoBase)
+NS_IMPL_RELEASE(nsMIMEInfoBase)
+
+NS_INTERFACE_MAP_BEGIN(nsMIMEInfoBase)
+ NS_INTERFACE_MAP_ENTRY(nsIHandlerInfo)
+ // This is only an nsIMIMEInfo if it's a MIME handler.
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIMIMEInfo, mClass == eMIMEInfo)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIHandlerInfo)
+NS_INTERFACE_MAP_END_THREADSAFE
+
+// nsMIMEInfoImpl methods
+
+// Constructors for a MIME handler.
+nsMIMEInfoBase::nsMIMEInfoBase(const char *aMIMEType) :
+ mSchemeOrType(aMIMEType),
+ mClass(eMIMEInfo),
+ mPreferredAction(nsIMIMEInfo::saveToDisk),
+ mAlwaysAskBeforeHandling(true)
+{
+}
+
+nsMIMEInfoBase::nsMIMEInfoBase(const nsACString& aMIMEType) :
+ mSchemeOrType(aMIMEType),
+ mClass(eMIMEInfo),
+ mPreferredAction(nsIMIMEInfo::saveToDisk),
+ mAlwaysAskBeforeHandling(true)
+{
+}
+
+// Constructor for a handler that lets the caller specify whether this is a
+// MIME handler or a protocol handler. In the long run, these will be distinct
+// classes (f.e. nsMIMEInfo and nsProtocolInfo), but for now we reuse this class
+// for both and distinguish between the two kinds of handlers via the aClass
+// argument to this method, which can be either eMIMEInfo or eProtocolInfo.
+nsMIMEInfoBase::nsMIMEInfoBase(const nsACString& aType, HandlerClass aClass) :
+ mSchemeOrType(aType),
+ mClass(aClass),
+ mPreferredAction(nsIMIMEInfo::saveToDisk),
+ mAlwaysAskBeforeHandling(true)
+{
+}
+
+nsMIMEInfoBase::~nsMIMEInfoBase()
+{
+}
+
+NS_IMETHODIMP
+nsMIMEInfoBase::GetFileExtensions(nsIUTF8StringEnumerator** aResult)
+{
+ return NS_NewUTF8StringEnumerator(aResult, &mExtensions, this);
+}
+
+NS_IMETHODIMP
+nsMIMEInfoBase::ExtensionExists(const nsACString& aExtension, bool *_retval)
+{
+ NS_ASSERTION(!aExtension.IsEmpty(), "no extension");
+ bool found = false;
+ uint32_t extCount = mExtensions.Length();
+ if (extCount < 1) return NS_OK;
+
+ for (uint8_t i=0; i < extCount; i++) {
+ const nsCString& ext = mExtensions[i];
+ if (ext.Equals(aExtension, nsCaseInsensitiveCStringComparator())) {
+ found = true;
+ break;
+ }
+ }
+
+ *_retval = found;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoBase::GetPrimaryExtension(nsACString& _retval)
+{
+ if (!mExtensions.Length())
+ return NS_ERROR_NOT_INITIALIZED;
+
+ _retval = mExtensions[0];
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoBase::SetPrimaryExtension(const nsACString& aExtension)
+{
+ NS_ASSERTION(!aExtension.IsEmpty(), "no extension");
+ uint32_t extCount = mExtensions.Length();
+ uint8_t i;
+ bool found = false;
+ for (i=0; i < extCount; i++) {
+ const nsCString& ext = mExtensions[i];
+ if (ext.Equals(aExtension, nsCaseInsensitiveCStringComparator())) {
+ found = true;
+ break;
+ }
+ }
+ if (found) {
+ mExtensions.RemoveElementAt(i);
+ }
+
+ mExtensions.InsertElementAt(0, aExtension);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoBase::AppendExtension(const nsACString& aExtension)
+{
+ mExtensions.AppendElement(aExtension);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoBase::GetType(nsACString& aType)
+{
+ if (mSchemeOrType.IsEmpty())
+ return NS_ERROR_NOT_INITIALIZED;
+
+ aType = mSchemeOrType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoBase::GetMIMEType(nsACString& aMIMEType)
+{
+ if (mSchemeOrType.IsEmpty())
+ return NS_ERROR_NOT_INITIALIZED;
+
+ aMIMEType = mSchemeOrType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoBase::GetDescription(nsAString& aDescription)
+{
+ aDescription = mDescription;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoBase::SetDescription(const nsAString& aDescription)
+{
+ mDescription = aDescription;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoBase::Equals(nsIMIMEInfo *aMIMEInfo, bool *_retval)
+{
+ if (!aMIMEInfo) return NS_ERROR_NULL_POINTER;
+
+ nsAutoCString type;
+ nsresult rv = aMIMEInfo->GetMIMEType(type);
+ if (NS_FAILED(rv)) return rv;
+
+ *_retval = mSchemeOrType.Equals(type);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoBase::SetFileExtensions(const nsACString& aExtensions)
+{
+ mExtensions.Clear();
+ nsCString extList( aExtensions );
+
+ int32_t breakLocation = -1;
+ while ( (breakLocation= extList.FindChar(',') )!= -1)
+ {
+ mExtensions.AppendElement(Substring(extList.get(), extList.get() + breakLocation));
+ extList.Cut(0, breakLocation+1 );
+ }
+ if ( !extList.IsEmpty() )
+ mExtensions.AppendElement( extList );
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoBase::GetDefaultDescription(nsAString& aDefaultDescription)
+{
+ aDefaultDescription = mDefaultAppDescription;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoBase::GetPreferredApplicationHandler(nsIHandlerApp ** aPreferredAppHandler)
+{
+ *aPreferredAppHandler = mPreferredApplication;
+ NS_IF_ADDREF(*aPreferredAppHandler);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoBase::SetPreferredApplicationHandler(nsIHandlerApp * aPreferredAppHandler)
+{
+ mPreferredApplication = aPreferredAppHandler;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoBase::GetPossibleApplicationHandlers(nsIMutableArray ** aPossibleAppHandlers)
+{
+ if (!mPossibleApplications)
+ mPossibleApplications = do_CreateInstance(NS_ARRAY_CONTRACTID);
+
+ if (!mPossibleApplications)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ *aPossibleAppHandlers = mPossibleApplications;
+ NS_IF_ADDREF(*aPossibleAppHandlers);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoBase::GetPreferredAction(nsHandlerInfoAction * aPreferredAction)
+{
+ *aPreferredAction = mPreferredAction;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoBase::SetPreferredAction(nsHandlerInfoAction aPreferredAction)
+{
+ mPreferredAction = aPreferredAction;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoBase::GetAlwaysAskBeforeHandling(bool * aAlwaysAsk)
+{
+ *aAlwaysAsk = mAlwaysAskBeforeHandling;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoBase::SetAlwaysAskBeforeHandling(bool aAlwaysAsk)
+{
+ mAlwaysAskBeforeHandling = aAlwaysAsk;
+ return NS_OK;
+}
+
+/* static */
+nsresult
+nsMIMEInfoBase::GetLocalFileFromURI(nsIURI *aURI, nsIFile **aFile)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIFileURL> fileUrl = do_QueryInterface(aURI, &rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIFile> file;
+ rv = fileUrl->GetFile(getter_AddRefs(file));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ file.forget(aFile);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoBase::LaunchWithFile(nsIFile* aFile)
+{
+ nsresult rv;
+
+ // it doesn't make any sense to call this on protocol handlers
+ NS_ASSERTION(mClass == eMIMEInfo,
+ "nsMIMEInfoBase should have mClass == eMIMEInfo");
+
+ if (mPreferredAction == useSystemDefault) {
+ return LaunchDefaultWithFile(aFile);
+ }
+
+ if (mPreferredAction == useHelperApp) {
+ if (!mPreferredApplication)
+ return NS_ERROR_FILE_NOT_FOUND;
+
+ // at the moment, we only know how to hand files off to local handlers
+ nsCOMPtr<nsILocalHandlerApp> localHandler =
+ do_QueryInterface(mPreferredApplication, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> executable;
+ rv = localHandler->GetExecutable(getter_AddRefs(executable));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString path;
+ aFile->GetNativePath(path);
+ return LaunchWithIProcess(executable, path);
+ }
+
+ return NS_ERROR_INVALID_ARG;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoBase::LaunchWithURI(nsIURI* aURI,
+ nsIInterfaceRequestor* aWindowContext)
+{
+ // for now, this is only being called with protocol handlers; that
+ // will change once we get to more general registerContentHandler
+ // support
+ NS_ASSERTION(mClass == eProtocolInfo,
+ "nsMIMEInfoBase should be a protocol handler");
+
+ if (mPreferredAction == useSystemDefault) {
+ return LoadUriInternal(aURI);
+ }
+
+ if (mPreferredAction == useHelperApp) {
+ if (!mPreferredApplication)
+ return NS_ERROR_FILE_NOT_FOUND;
+
+ return mPreferredApplication->LaunchWithURI(aURI, aWindowContext);
+ }
+
+ return NS_ERROR_INVALID_ARG;
+}
+
+void
+nsMIMEInfoBase::CopyBasicDataTo(nsMIMEInfoBase* aOther)
+{
+ aOther->mSchemeOrType = mSchemeOrType;
+ aOther->mDefaultAppDescription = mDefaultAppDescription;
+ aOther->mExtensions = mExtensions;
+}
+
+/* static */
+already_AddRefed<nsIProcess>
+nsMIMEInfoBase::InitProcess(nsIFile* aApp, nsresult* aResult)
+{
+ NS_ASSERTION(aApp, "Unexpected null pointer, fix caller");
+
+ nsCOMPtr<nsIProcess> process = do_CreateInstance(NS_PROCESS_CONTRACTID,
+ aResult);
+ if (NS_FAILED(*aResult))
+ return nullptr;
+
+ *aResult = process->Init(aApp);
+ if (NS_FAILED(*aResult))
+ return nullptr;
+
+ return process.forget();
+}
+
+/* static */
+nsresult
+nsMIMEInfoBase::LaunchWithIProcess(nsIFile* aApp, const nsCString& aArg)
+{
+ nsresult rv;
+ nsCOMPtr<nsIProcess> process = InitProcess(aApp, &rv);
+ if (NS_FAILED(rv))
+ return rv;
+
+ const char *string = aArg.get();
+
+ return process->Run(false, &string, 1);
+}
+
+/* static */
+nsresult
+nsMIMEInfoBase::LaunchWithIProcess(nsIFile* aApp, const nsString& aArg)
+{
+ nsresult rv;
+ nsCOMPtr<nsIProcess> process = InitProcess(aApp, &rv);
+ if (NS_FAILED(rv))
+ return rv;
+
+ const char16_t *string = aArg.get();
+
+ return process->Runw(false, &string, 1);
+}
+
+// nsMIMEInfoImpl implementation
+NS_IMETHODIMP
+nsMIMEInfoImpl::GetDefaultDescription(nsAString& aDefaultDescription)
+{
+ if (mDefaultAppDescription.IsEmpty() && mDefaultApplication) {
+ // Don't want to cache this, just in case someone resets the app
+ // without changing the description....
+ mDefaultApplication->GetLeafName(aDefaultDescription);
+ } else {
+ aDefaultDescription = mDefaultAppDescription;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoImpl::GetHasDefaultHandler(bool * _retval)
+{
+ *_retval = !mDefaultAppDescription.IsEmpty();
+ if (mDefaultApplication) {
+ bool exists;
+ *_retval = NS_SUCCEEDED(mDefaultApplication->Exists(&exists)) && exists;
+ }
+ return NS_OK;
+}
+
+nsresult
+nsMIMEInfoImpl::LaunchDefaultWithFile(nsIFile* aFile)
+{
+ if (!mDefaultApplication)
+ return NS_ERROR_FILE_NOT_FOUND;
+
+ nsAutoCString nativePath;
+ aFile->GetNativePath(nativePath);
+
+ return LaunchWithIProcess(mDefaultApplication, nativePath);
+}
+
+NS_IMETHODIMP
+nsMIMEInfoBase::GetPossibleLocalHandlers(nsIArray **_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
diff --git a/uriloader/exthandler/nsMIMEInfoImpl.h b/uriloader/exthandler/nsMIMEInfoImpl.h
new file mode 100644
index 0000000000..34f2442423
--- /dev/null
+++ b/uriloader/exthandler/nsMIMEInfoImpl.h
@@ -0,0 +1,196 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 sts=4 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef __nsmimeinfoimpl_h___
+#define __nsmimeinfoimpl_h___
+
+#include "nsIMIMEInfo.h"
+#include "nsIAtom.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsIMutableArray.h"
+#include "nsIFile.h"
+#include "nsCOMPtr.h"
+#include "nsIURI.h"
+#include "nsIProcess.h"
+
+/**
+ * UTF8 moz-icon URI string for the default handler application's icon, if
+ * available.
+ */
+#define PROPERTY_DEFAULT_APP_ICON_URL "defaultApplicationIconURL"
+/**
+ * UTF8 moz-icon URI string for the user's preferred handler application's
+ * icon, if available.
+ */
+#define PROPERTY_CUSTOM_APP_ICON_URL "customApplicationIconURL"
+
+/**
+ * Basic implementation of nsIMIMEInfo. Incomplete - it is meant to be
+ * subclassed, and GetHasDefaultHandler as well as LaunchDefaultWithFile need to
+ * be implemented.
+ */
+class nsMIMEInfoBase : public nsIMIMEInfo {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ // I'd use NS_DECL_NSIMIMEINFO, but I don't want GetHasDefaultHandler
+ NS_IMETHOD GetFileExtensions(nsIUTF8StringEnumerator **_retval) override;
+ NS_IMETHOD SetFileExtensions(const nsACString & aExtensions) override;
+ NS_IMETHOD ExtensionExists(const nsACString & aExtension, bool *_retval) override;
+ NS_IMETHOD AppendExtension(const nsACString & aExtension) override;
+ NS_IMETHOD GetPrimaryExtension(nsACString & aPrimaryExtension) override;
+ NS_IMETHOD SetPrimaryExtension(const nsACString & aPrimaryExtension) override;
+ NS_IMETHOD GetType(nsACString & aType) override;
+ NS_IMETHOD GetMIMEType(nsACString & aMIMEType) override;
+ NS_IMETHOD GetDescription(nsAString & aDescription) override;
+ NS_IMETHOD SetDescription(const nsAString & aDescription) override;
+ NS_IMETHOD Equals(nsIMIMEInfo *aMIMEInfo, bool *_retval) override;
+ NS_IMETHOD GetPreferredApplicationHandler(nsIHandlerApp * *aPreferredAppHandler) override;
+ NS_IMETHOD SetPreferredApplicationHandler(nsIHandlerApp * aPreferredAppHandler) override;
+ NS_IMETHOD GetPossibleApplicationHandlers(nsIMutableArray * *aPossibleAppHandlers) override;
+ NS_IMETHOD GetDefaultDescription(nsAString & aDefaultDescription) override;
+ NS_IMETHOD LaunchWithFile(nsIFile *aFile) override;
+ NS_IMETHOD LaunchWithURI(nsIURI *aURI,
+ nsIInterfaceRequestor *aWindowContext) override;
+ NS_IMETHOD GetPreferredAction(nsHandlerInfoAction *aPreferredAction) override;
+ NS_IMETHOD SetPreferredAction(nsHandlerInfoAction aPreferredAction) override;
+ NS_IMETHOD GetAlwaysAskBeforeHandling(bool *aAlwaysAskBeforeHandling) override;
+ NS_IMETHOD SetAlwaysAskBeforeHandling(bool aAlwaysAskBeforeHandling) override;
+ NS_IMETHOD GetPossibleLocalHandlers(nsIArray **_retval) override;
+
+ enum HandlerClass {
+ eMIMEInfo,
+ eProtocolInfo
+ };
+
+ // nsMIMEInfoBase methods
+ explicit nsMIMEInfoBase(const char *aMIMEType = "");
+ explicit nsMIMEInfoBase(const nsACString& aMIMEType);
+ nsMIMEInfoBase(const nsACString& aType, HandlerClass aClass);
+
+ void SetMIMEType(const nsACString & aMIMEType) { mSchemeOrType = aMIMEType; }
+
+ void SetDefaultDescription(const nsString& aDesc) { mDefaultAppDescription = aDesc; }
+
+ /**
+ * Copies basic data of this MIME Info Implementation to the given other
+ * MIME Info. The data consists of the MIME Type, the (default) description,
+ * the MacOS type and creator, and the extension list (this object's
+ * extension list will replace aOther's list, not append to it). This
+ * function also ensures that aOther's primary extension will be the same as
+ * the one of this object.
+ */
+ void CopyBasicDataTo(nsMIMEInfoBase* aOther);
+
+ /**
+ * Return whether this MIMEInfo has any extensions
+ */
+ bool HasExtensions() const { return mExtensions.Length() != 0; }
+
+ protected:
+ virtual ~nsMIMEInfoBase(); // must be virtual, as the the base class's Release should call the subclass's destructor
+
+ /**
+ * Launch the default application for the given file.
+ * For even more control over the launching, override launchWithFile.
+ * Also see the comment about nsIMIMEInfo in general, above.
+ *
+ * @param aFile The file that should be opened
+ */
+ virtual nsresult LaunchDefaultWithFile(nsIFile* aFile) = 0;
+
+ /**
+ * Loads the URI with the OS default app.
+ *
+ * @param aURI The URI to pass off to the OS.
+ */
+ virtual nsresult LoadUriInternal(nsIURI *aURI) = 0;
+
+ static already_AddRefed<nsIProcess> InitProcess(nsIFile* aApp,
+ nsresult* aResult);
+
+ /**
+ * This method can be used to launch the file or URI with a single
+ * argument (typically either a file path or a URI spec). This is
+ * meant as a helper method for implementations of
+ * LaunchWithURI/LaunchDefaultWithFile.
+ *
+ * @param aApp The application to launch (may not be null)
+ * @param aArg The argument to pass on the command line
+ */
+ static nsresult LaunchWithIProcess(nsIFile* aApp,
+ const nsCString &aArg);
+ static nsresult LaunchWithIProcess(nsIFile* aApp,
+ const nsString &aArg);
+
+ /**
+ * Given a file: nsIURI, return the associated nsIFile
+ *
+ * @param aURI the file: URI in question
+ * @param aFile the associated nsIFile (out param)
+ */
+ static nsresult GetLocalFileFromURI(nsIURI *aURI,
+ nsIFile **aFile);
+
+ // member variables
+ nsTArray<nsCString> mExtensions; ///< array of file extensions associated w/ this MIME obj
+ nsString mDescription; ///< human readable description
+ nsCString mSchemeOrType;
+ HandlerClass mClass;
+ nsCOMPtr<nsIHandlerApp> mPreferredApplication;
+ nsCOMPtr<nsIMutableArray> mPossibleApplications;
+ nsHandlerInfoAction mPreferredAction; ///< preferred action to associate with this type
+ nsString mPreferredAppDescription;
+ nsString mDefaultAppDescription;
+ bool mAlwaysAskBeforeHandling;
+};
+
+
+/**
+ * This is a complete implementation of nsIMIMEInfo, and contains all necessary
+ * methods. However, depending on your platform you may want to use a different
+ * way of launching applications. This class stores the default application in a
+ * member variable and provides a function for setting it. For local
+ * applications, launching is done using nsIProcess, native path of the file to
+ * open as first argument.
+ */
+class nsMIMEInfoImpl : public nsMIMEInfoBase {
+ public:
+ explicit nsMIMEInfoImpl(const char *aMIMEType = "") : nsMIMEInfoBase(aMIMEType) {}
+ explicit nsMIMEInfoImpl(const nsACString& aMIMEType) : nsMIMEInfoBase(aMIMEType) {}
+ nsMIMEInfoImpl(const nsACString& aType, HandlerClass aClass) :
+ nsMIMEInfoBase(aType, aClass) {}
+ virtual ~nsMIMEInfoImpl() {}
+
+ // nsIMIMEInfo methods
+ NS_IMETHOD GetHasDefaultHandler(bool *_retval);
+ NS_IMETHOD GetDefaultDescription(nsAString& aDefaultDescription);
+
+ // additional methods
+ /**
+ * Sets the default application. Supposed to be only called by the OS Helper
+ * App Services; the default application is immutable after it is first set.
+ */
+ void SetDefaultApplication(nsIFile* aApp) { if (!mDefaultApplication) mDefaultApplication = aApp; }
+
+ protected:
+ // nsMIMEInfoBase methods
+ /**
+ * The base class implementation is to use LaunchWithIProcess in combination
+ * with mDefaultApplication. Subclasses can override that behaviour.
+ */
+ virtual nsresult LaunchDefaultWithFile(nsIFile* aFile);
+
+ /**
+ * Loads the URI with the OS default app. This should be overridden by each
+ * OS's implementation.
+ */
+ virtual nsresult LoadUriInternal(nsIURI *aURI) = 0;
+
+ nsCOMPtr<nsIFile> mDefaultApplication; ///< default application associated with this type.
+};
+
+#endif //__nsmimeinfoimpl_h___
diff --git a/uriloader/exthandler/nsWebHandlerApp.js b/uriloader/exthandler/nsWebHandlerApp.js
new file mode 100644
index 0000000000..65d600f551
--- /dev/null
+++ b/uriloader/exthandler/nsWebHandlerApp.js
@@ -0,0 +1,172 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+////////////////////////////////////////////////////////////////////////////////
+//// Constants
+
+const Ci = Components.interfaces;
+const Cr = Components.results;
+const Cc = Components.classes;
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsWebHandler class
+
+function nsWebHandlerApp() {}
+
+nsWebHandlerApp.prototype = {
+ //////////////////////////////////////////////////////////////////////////////
+ //// nsWebHandler
+
+ classDescription: "A web handler for protocols and content",
+ classID: Components.ID("8b1ae382-51a9-4972-b930-56977a57919d"),
+ contractID: "@mozilla.org/uriloader/web-handler-app;1",
+
+ _name: null,
+ _detailedDescription: null,
+ _uriTemplate: null,
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// nsIHandlerApp
+
+ get name() {
+ return this._name;
+ },
+
+ set name(aName) {
+ this._name = aName;
+ },
+
+ get detailedDescription() {
+ return this._detailedDescription;
+ },
+
+ set detailedDescription(aDesc) {
+ this._detailedDescription = aDesc;
+ },
+
+ equals: function(aHandlerApp) {
+ if (!aHandlerApp)
+ throw Cr.NS_ERROR_NULL_POINTER;
+
+ if (aHandlerApp instanceof Ci.nsIWebHandlerApp &&
+ aHandlerApp.uriTemplate &&
+ this.uriTemplate &&
+ aHandlerApp.uriTemplate == this.uriTemplate)
+ return true;
+
+ return false;
+ },
+
+ launchWithURI: function nWHA__launchWithURI(aURI, aWindowContext) {
+
+ // XXX need to strip passwd & username from URI to handle, as per the
+ // WhatWG HTML5 draft. nsSimpleURL, which is what we're going to get,
+ // can't do this directly. Ideally, we'd fix nsStandardURL to make it
+ // possible to turn off all of its quirks handling, and use that...
+
+ // encode the URI to be handled
+ var escapedUriSpecToHandle = encodeURIComponent(aURI.spec);
+
+ // insert the encoded URI and create the object version
+ var uriSpecToSend = this.uriTemplate.replace("%s", escapedUriSpecToHandle);
+ var ioService = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+ var uriToSend = ioService.newURI(uriSpecToSend, null, null);
+
+ // if we have a window context, use the URI loader to load there
+ if (aWindowContext) {
+ try {
+ // getInterface throws if the object doesn't implement the given
+ // interface, so this try/catch statement is more of an if.
+ // If aWindowContext refers to a remote docshell, send the load
+ // request to the correct process.
+ aWindowContext.getInterface(Ci.nsIRemoteWindowContext)
+ .openURI(uriToSend);
+ return;
+ } catch (e) {
+ if (e.result != Cr.NS_NOINTERFACE) {
+ throw e;
+ }
+ }
+
+ // create a channel from this URI
+ var channel = NetUtil.newChannel({
+ uri: uriToSend,
+ loadUsingSystemPrincipal: true
+ });
+ channel.loadFlags = Ci.nsIChannel.LOAD_DOCUMENT_URI;
+
+ // load the channel
+ var uriLoader = Cc["@mozilla.org/uriloader;1"].
+ getService(Ci.nsIURILoader);
+ // XXX ideally, whether to pass the IS_CONTENT_PREFERRED flag should be
+ // passed in from above. Practically, the flag is probably a reasonable
+ // default since browsers don't care much, and link click is likely to be
+ // the more interesting case for non-browser apps. See
+ // <https://bugzilla.mozilla.org/show_bug.cgi?id=392957#c9> for details.
+ uriLoader.openURI(channel, Ci.nsIURILoader.IS_CONTENT_PREFERRED,
+ aWindowContext);
+ return;
+ }
+
+ // since we don't have a window context, hand it off to a browser
+ var windowMediator = Cc["@mozilla.org/appshell/window-mediator;1"].
+ getService(Ci.nsIWindowMediator);
+
+ // get browser dom window
+ var browserDOMWin = windowMediator.getMostRecentWindow("navigator:browser")
+ .QueryInterface(Ci.nsIDOMChromeWindow)
+ .browserDOMWindow;
+
+ // if we got an exception, there are several possible reasons why:
+ // a) this gecko embedding doesn't provide an nsIBrowserDOMWindow
+ // implementation (i.e. doesn't support browser-style functionality),
+ // so we need to kick the URL out to the OS default browser. This is
+ // the subject of bug 394479.
+ // b) this embedding does provide an nsIBrowserDOMWindow impl, but
+ // there doesn't happen to be a browser window open at the moment; one
+ // should be opened. It's not clear whether this situation will really
+ // ever occur in real life. If it does, the only API that I can find
+ // that seems reasonably likely to work for most embedders is the
+ // command line handler.
+ // c) something else went wrong
+ //
+ // it's not clear how one would differentiate between the three cases
+ // above, so for now we don't catch the exception
+
+ // openURI
+ browserDOMWin.openURI(uriToSend,
+ null, // no window.opener
+ Ci.nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW,
+ Ci.nsIBrowserDOMWindow.OPEN_NEW);
+
+ return;
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// nsIWebHandlerApp
+
+ get uriTemplate() {
+ return this._uriTemplate;
+ },
+
+ set uriTemplate(aURITemplate) {
+ this._uriTemplate = aURITemplate;
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// nsISupports
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebHandlerApp, Ci.nsIHandlerApp])
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//// Module
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([nsWebHandlerApp]);
+
diff --git a/uriloader/exthandler/nsWebHandlerApp.manifest b/uriloader/exthandler/nsWebHandlerApp.manifest
new file mode 100644
index 0000000000..02b1d02af3
--- /dev/null
+++ b/uriloader/exthandler/nsWebHandlerApp.manifest
@@ -0,0 +1,2 @@
+component {8b1ae382-51a9-4972-b930-56977a57919d} nsWebHandlerApp.js
+contract @mozilla.org/uriloader/web-handler-app;1 {8b1ae382-51a9-4972-b930-56977a57919d}
diff --git a/uriloader/exthandler/tests/Makefile.in b/uriloader/exthandler/tests/Makefile.in
new file mode 100644
index 0000000000..13250c8ab9
--- /dev/null
+++ b/uriloader/exthandler/tests/Makefile.in
@@ -0,0 +1,11 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+include $(topsrcdir)/config/rules.mk
+
+# need the executable for running the xpcshell unit tests
+ifneq (,$(SIMPLE_PROGRAMS))
+libs::
+ $(INSTALL) $(SIMPLE_PROGRAMS) $(DEPTH)/_tests/xpcshell/$(relativesrcdir)/unit
+endif
diff --git a/uriloader/exthandler/tests/WriteArgument.cpp b/uriloader/exthandler/tests/WriteArgument.cpp
new file mode 100644
index 0000000000..2efbed9065
--- /dev/null
+++ b/uriloader/exthandler/tests/WriteArgument.cpp
@@ -0,0 +1,24 @@
+#include <stdio.h>
+#include "prenv.h"
+
+int main(int argc, char* argv[])
+{
+ if (argc != 2)
+ return 1;
+
+ const char* value = PR_GetEnv("WRITE_ARGUMENT_FILE");
+
+ if (!value)
+ return 2;
+
+ FILE* outfile = fopen(value, "w");
+ if (!outfile)
+ return 3;
+
+ // We only need to write out the first argument (no newline).
+ fputs(argv[argc -1], outfile);
+
+ fclose(outfile);
+
+ return 0;
+}
diff --git a/uriloader/exthandler/tests/mochitest/browser.ini b/uriloader/exthandler/tests/mochitest/browser.ini
new file mode 100644
index 0000000000..9647dcf8cd
--- /dev/null
+++ b/uriloader/exthandler/tests/mochitest/browser.ini
@@ -0,0 +1,8 @@
+[DEFAULT]
+head = head.js
+support-files =
+ protocolHandler.html
+
+[browser_download_always_ask_preferred_app.js]
+[browser_remember_download_option.js]
+[browser_web_protocol_handlers.js]
diff --git a/uriloader/exthandler/tests/mochitest/browser_download_always_ask_preferred_app.js b/uriloader/exthandler/tests/mochitest/browser_download_always_ask_preferred_app.js
new file mode 100644
index 0000000000..40f7ef71e2
--- /dev/null
+++ b/uriloader/exthandler/tests/mochitest/browser_download_always_ask_preferred_app.js
@@ -0,0 +1,19 @@
+add_task(function*() {
+ // Create mocked objects for test
+ let launcher = createMockedObjects(false);
+ // Open helper app dialog with mocked launcher
+ let dlg = yield* openHelperAppDialog(launcher);
+ let doc = dlg.document;
+ let location = doc.getElementById("source");
+ let expectedValue = launcher.source.prePath;
+ if (location.value != expectedValue) {
+ info("Waiting for dialog to be populated.");
+ yield BrowserTestUtils.waitForAttribute("value", location, expectedValue);
+ }
+ is(doc.getElementById("mode").selectedItem.id, "open", "Should be opening the file.");
+ ok(!dlg.document.getElementById("openHandler").selectedItem.hidden,
+ "Should not have selected a hidden item.");
+ let helperAppDialogHiddenPromise = BrowserTestUtils.windowClosed(dlg);
+ doc.documentElement.cancelDialog();
+ yield helperAppDialogHiddenPromise;
+});
diff --git a/uriloader/exthandler/tests/mochitest/browser_remember_download_option.js b/uriloader/exthandler/tests/mochitest/browser_remember_download_option.js
new file mode 100644
index 0000000000..996e5ad699
--- /dev/null
+++ b/uriloader/exthandler/tests/mochitest/browser_remember_download_option.js
@@ -0,0 +1,49 @@
+add_task(function*() {
+ // create mocked objects
+ let launcher = createMockedObjects(true);
+
+ // open helper app dialog with mocked launcher
+ let dlg = yield* openHelperAppDialog(launcher);
+
+ let doc = dlg.document;
+
+ // Set remember choice
+ ok(!doc.getElementById("rememberChoice").checked,
+ "Remember choice checkbox should be not checked.");
+ doc.getElementById("rememberChoice").checked = true;
+
+ // Make sure the mock handler information is not in nsIHandlerService
+ ok(!gHandlerSvc.exists(launcher.MIMEInfo), "Should not be in nsIHandlerService.");
+
+ // close the dialog by pushing the ok button.
+ let dialogClosedPromise = BrowserTestUtils.windowClosed(dlg);
+ // Make sure the ok button is enabled, since the ok button might be disabled by
+ // EnableDelayHelper mechanism. Please refer the detailed
+ // https://dxr.mozilla.org/mozilla-central/source/toolkit/components/prompts/src/SharedPromptUtils.jsm#53
+ doc.documentElement.getButton("accept").disabled = false;
+ doc.documentElement.acceptDialog();
+ yield dialogClosedPromise;
+
+ // check the mocked handler information is saved in nsIHandlerService
+ ok(gHandlerSvc.exists(launcher.MIMEInfo), "Should be in nsIHandlerService.");
+ // check the extension.
+ var mimeType = gHandlerSvc.getTypeFromExtension("abc");
+ is(mimeType, launcher.MIMEInfo.type, "Got correct mime type.");
+ var handlerInfos = gHandlerSvc.enumerate();
+ while (handlerInfos.hasMoreElements()) {
+ let handlerInfo = handlerInfos.getNext().QueryInterface(Ci.nsIHandlerInfo);
+ if (handlerInfo.type == launcher.MIMEInfo.type) {
+ // check the alwaysAskBeforeHandling
+ ok(!handlerInfo.alwaysAskBeforeHandling,
+ "Should turn off the always ask.");
+ // check the preferredApplicationHandler
+ ok(handlerInfo.preferredApplicationHandler.equals(
+ launcher.MIMEInfo.preferredApplicationHandler),
+ "Should be equal to the mockedHandlerApp.");
+ // check the perferredAction
+ is(handlerInfo.preferredAction, launcher.MIMEInfo.preferredAction,
+ "Should be equal to Ci.nsIHandlerInfo.useHelperApp.");
+ break;
+ }
+ }
+});
diff --git a/uriloader/exthandler/tests/mochitest/browser_web_protocol_handlers.js b/uriloader/exthandler/tests/mochitest/browser_web_protocol_handlers.js
new file mode 100644
index 0000000000..ac3e662588
--- /dev/null
+++ b/uriloader/exthandler/tests/mochitest/browser_web_protocol_handlers.js
@@ -0,0 +1,76 @@
+let testURL = "http://example.com/browser/" +
+ "uriloader/exthandler/tests/mochitest/protocolHandler.html";
+
+add_task(function*() {
+ // Load a page registering a protocol handler.
+ let browser = gBrowser.selectedBrowser;
+ browser.loadURI(testURL);
+ yield BrowserTestUtils.browserLoaded(browser, testURL);
+
+ // Register the protocol handler by clicking the notificationbar button.
+ let notificationValue = "Protocol Registration: testprotocol";
+ let getNotification = () =>
+ gBrowser.getNotificationBox().getNotificationWithValue(notificationValue);
+ yield BrowserTestUtils.waitForCondition(getNotification);
+ let notification = getNotification();
+ let button =
+ notification.getElementsByClassName("notification-button-default")[0];
+ ok(button, "got registration button");
+ button.click();
+
+ // Set the new handler as default.
+ const protoSvc = Cc["@mozilla.org/uriloader/external-protocol-service;1"].
+ getService(Ci.nsIExternalProtocolService);
+ let protoInfo = protoSvc.getProtocolHandlerInfo("testprotocol");
+ is(protoInfo.preferredAction, protoInfo.useHelperApp,
+ "using a helper application is the preferred action");
+ ok(!protoInfo.preferredApplicationHandler, "no preferred handler is set");
+ let handlers = protoInfo.possibleApplicationHandlers;
+ is(1, handlers.length, "only one handler registered for testprotocol");
+ let handler = handlers.queryElementAt(0, Ci.nsIHandlerApp);
+ ok(handler instanceof Ci.nsIWebHandlerApp, "the handler is a web handler");
+ is(handler.uriTemplate, "https://example.com/foobar?uri=%s",
+ "correct url template")
+ protoInfo.preferredApplicationHandler = handler;
+ protoInfo.alwaysAskBeforeHandling = false;
+ const handlerSvc = Cc["@mozilla.org/uriloader/handler-service;1"].
+ getService(Ci.nsIHandlerService);
+ handlerSvc.store(protoInfo);
+
+ // Middle-click a testprotocol link and check the new tab is correct
+ let link = "#link";
+ const expectedURL = "https://example.com/foobar?uri=testprotocol%3Atest";
+
+ let promiseTabOpened =
+ BrowserTestUtils.waitForNewTab(gBrowser, expectedURL);
+ yield BrowserTestUtils.synthesizeMouseAtCenter(link, {button: 1}, browser);
+ let tab = yield promiseTabOpened;
+ gBrowser.selectedTab = tab;
+ is(gURLBar.value, expectedURL,
+ "the expected URL is displayed in the location bar");
+ yield BrowserTestUtils.removeTab(tab);
+
+ // Shift-click the testprotocol link and check the new window.
+ let newWindowPromise = BrowserTestUtils.waitForNewWindow();
+ yield BrowserTestUtils.synthesizeMouseAtCenter(link, {shiftKey: true},
+ browser);
+ let win = yield newWindowPromise;
+ yield BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
+ yield BrowserTestUtils.waitForCondition(() => win.gBrowser.currentURI.spec == expectedURL);
+ is(win.gURLBar.value, expectedURL,
+ "the expected URL is displayed in the location bar");
+ yield BrowserTestUtils.closeWindow(win);
+
+ // Click the testprotocol link and check the url in the current tab.
+ let loadPromise = BrowserTestUtils.browserLoaded(browser);
+ yield BrowserTestUtils.synthesizeMouseAtCenter(link, {}, browser);
+ yield loadPromise;
+ yield BrowserTestUtils.waitForCondition(() => gURLBar.value != testURL);
+ is(gURLBar.value, expectedURL,
+ "the expected URL is displayed in the location bar");
+
+ // Cleanup.
+ protoInfo.preferredApplicationHandler = null;
+ handlers.removeElementAt(0);
+ handlerSvc.store(protoInfo);
+});
diff --git a/uriloader/exthandler/tests/mochitest/handlerApp.xhtml b/uriloader/exthandler/tests/mochitest/handlerApp.xhtml
new file mode 100644
index 0000000000..83ba0d1a54
--- /dev/null
+++ b/uriloader/exthandler/tests/mochitest/handlerApp.xhtml
@@ -0,0 +1,30 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>Pseudo Web Handler App</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="onLoad()">
+Pseudo Web Handler App
+
+<script class="testbody" type="text/javascript">
+<![CDATA[
+function onLoad() {
+
+ // if we have a window.opener, this must be the windowContext
+ // instance of this test. check that we got the URI right and clean up.
+ if (window.opener) {
+ window.opener.is(location.search,
+ "?uri=" + encodeURIComponent(window.opener.testURI),
+ "uri passed to web-handler app");
+ window.opener.SimpleTest.finish();
+ }
+
+ window.close();
+}
+]]>
+</script>
+
+</body>
+</html>
+
diff --git a/uriloader/exthandler/tests/mochitest/handlerApps.js b/uriloader/exthandler/tests/mochitest/handlerApps.js
new file mode 100644
index 0000000000..597f9442d9
--- /dev/null
+++ b/uriloader/exthandler/tests/mochitest/handlerApps.js
@@ -0,0 +1,110 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// handlerApp.xhtml grabs this for verification purposes via window.opener
+var testURI = "webcal://127.0.0.1/rheeeeet.html";
+
+const Cc = SpecialPowers.Cc;
+
+function test() {
+
+ // set up the web handler object
+ var webHandler = Cc["@mozilla.org/uriloader/web-handler-app;1"].
+ createInstance(SpecialPowers.Ci.nsIWebHandlerApp);
+ webHandler.name = "Test Web Handler App";
+ webHandler.uriTemplate =
+ "http://mochi.test:8888/tests/uriloader/exthandler/tests/mochitest/" +
+ "handlerApp.xhtml?uri=%s";
+
+ // set up the uri to test with
+ var ioService = Cc["@mozilla.org/network/io-service;1"].
+ getService(SpecialPowers.Ci.nsIIOService);
+ var uri = ioService.newURI(testURI, null, null);
+
+ // create a window, and launch the handler in it
+ var newWindow = window.open("", "handlerWindow", "height=300,width=300");
+ var windowContext =
+ SpecialPowers.wrap(newWindow).QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor).
+ getInterface(SpecialPowers.Ci.nsIWebNavigation).
+ QueryInterface(SpecialPowers.Ci.nsIDocShell);
+
+ webHandler.launchWithURI(uri, windowContext);
+
+ // if we get this far without an exception, we've at least partly passed
+ // (remaining check in handlerApp.xhtml)
+ ok(true, "webHandler launchWithURI (existing window/tab) started");
+
+ // make the web browser launch in its own window/tab
+ webHandler.launchWithURI(uri);
+
+ // if we get this far without an exception, we've passed
+ ok(true, "webHandler launchWithURI (new window/tab) test started");
+
+ // set up the local handler object
+ var localHandler = Cc["@mozilla.org/uriloader/local-handler-app;1"].
+ createInstance(SpecialPowers.Ci.nsILocalHandlerApp);
+ localHandler.name = "Test Local Handler App";
+
+ // get a local app that we know will be there and do something sane
+ var osString = Cc["@mozilla.org/xre/app-info;1"].
+ getService(SpecialPowers.Ci.nsIXULRuntime).OS;
+
+ var dirSvc = Cc["@mozilla.org/file/directory_service;1"].
+ getService(SpecialPowers.Ci.nsIDirectoryServiceProvider);
+ if (osString == "WINNT") {
+ var windowsDir = dirSvc.getFile("WinD", {});
+ var exe = windowsDir.clone().QueryInterface(SpecialPowers.Ci.nsILocalFile);
+ exe.appendRelativePath("SYSTEM32\\HOSTNAME.EXE");
+
+ } else if (osString == "Darwin") {
+ var localAppsDir = dirSvc.getFile("LocApp", {});
+ exe = localAppsDir.clone();
+ exe.append("iCal.app"); // lingers after the tests finish, but this seems
+ // seems better than explicitly killing it, since
+ // developers who run the tests locally may well
+ // information in their running copy of iCal
+
+ if (navigator.userAgent.match(/ SeaMonkey\//)) {
+ // SeaMonkey tinderboxes don't like to have iCal lingering (and focused)
+ // on next test suite run(s).
+ todo(false, "On SeaMonkey, testing OS X as generic Unix. (Bug 749872)");
+
+ // assume a generic UNIX variant
+ exe = Cc["@mozilla.org/file/local;1"].
+ createInstance(SpecialPowers.Ci.nsILocalFile);
+ exe.initWithPath("/bin/echo");
+ }
+ } else {
+ // assume a generic UNIX variant
+ exe = Cc["@mozilla.org/file/local;1"].
+ createInstance(SpecialPowers.Ci.nsILocalFile);
+ exe.initWithPath("/bin/echo");
+ }
+
+ localHandler.executable = exe;
+ localHandler.launchWithURI(ioService.newURI(testURI, null, null));
+
+ // if we get this far without an exception, we've passed
+ ok(true, "localHandler launchWithURI test");
+
+ // if we ever decide that killing iCal is the right thing to do, change
+ // the if statement below from "NOTDarwin" to "Darwin"
+ if (osString == "NOTDarwin") {
+
+ var killall = Cc["@mozilla.org/file/local;1"].
+ createInstance(SpecialPowers.Ci.nsILocalFile);
+ killall.initWithPath("/usr/bin/killall");
+
+ var process = Cc["@mozilla.org/process/util;1"].
+ createInstance(SpecialPowers.Ci.nsIProcess);
+ process.init(killall);
+
+ var args = ['iCal'];
+ process.run(false, args, args.length);
+ }
+
+ SimpleTest.waitForExplicitFinish();
+}
+
+test();
diff --git a/uriloader/exthandler/tests/mochitest/head.js b/uriloader/exthandler/tests/mochitest/head.js
new file mode 100644
index 0000000000..dad0493f81
--- /dev/null
+++ b/uriloader/exthandler/tests/mochitest/head.js
@@ -0,0 +1,105 @@
+Components.utils.import("resource://gre/modules/FileUtils.jsm");
+Components.utils.import("resource://gre/modules/Task.jsm");
+
+var gMimeSvc = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService);
+var gHandlerSvc = Cc["@mozilla.org/uriloader/handler-service;1"].getService(Ci.nsIHandlerService);
+
+function createMockedHandlerApp() {
+ // Mock the executable
+ let mockedExecutable = FileUtils.getFile("TmpD", ["mockedExecutable"]);
+ if (!mockedExecutable.exists()) {
+ mockedExecutable.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o755);
+ }
+
+ // Mock the handler app
+ let mockedHandlerApp = Cc["@mozilla.org/uriloader/local-handler-app;1"]
+ .createInstance(Ci.nsILocalHandlerApp);
+ mockedHandlerApp.executable = mockedExecutable;
+ mockedHandlerApp.detailedDescription = "Mocked handler app";
+
+ registerCleanupFunction(function() {
+ // remove the mocked executable from disk.
+ if (mockedExecutable.exists()) {
+ mockedExecutable.remove(true);
+ }
+ });
+
+ return mockedHandlerApp;
+}
+
+function createMockedObjects(createHandlerApp) {
+ // Mock the mime info
+ let internalMockedMIME = gMimeSvc.getFromTypeAndExtension("text/x-test-handler", null);
+ internalMockedMIME.alwaysAskBeforeHandling = true;
+ internalMockedMIME.preferredAction = Ci.nsIHandlerInfo.useHelperApp;
+ internalMockedMIME.appendExtension("abc");
+ if (createHandlerApp) {
+ let mockedHandlerApp = createMockedHandlerApp();
+ internalMockedMIME.description = mockedHandlerApp.detailedDescription;
+ internalMockedMIME.possibleApplicationHandlers.appendElement(mockedHandlerApp, false);
+ internalMockedMIME.preferredApplicationHandler = mockedHandlerApp;
+ }
+
+ // Proxy for the mocked MIME info for faking the read-only attributes
+ let mockedMIME = new Proxy(internalMockedMIME, {
+ get: function (target, property) {
+ switch (property) {
+ case "hasDefaultHandler":
+ return true;
+ case "defaultDescription":
+ return "Default description";
+ default:
+ return target[property];
+ }
+ },
+ });
+
+ // Mock the launcher:
+ let mockedLauncher = {
+ MIMEInfo: mockedMIME,
+ source: Services.io.newURI("http://www.mozilla.org/", null, null),
+ suggestedFileName: "test_download_dialog.abc",
+ targetFileIsExecutable: false,
+ saveToDisk() {},
+ cancel() {},
+ launchWithApplication() {},
+ setWebProgressListener() {},
+ saveDestinationAvailable() {},
+ contentLength: 42,
+ targetFile: null, // never read
+ // PRTime is microseconds since epoch, Date.now() returns milliseconds:
+ timeDownloadStarted: Date.now() * 1000,
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsICancelable, Ci.nsIHelperAppLauncher])
+ };
+
+ registerCleanupFunction(function() {
+ // remove the mocked mime info from database.
+ let mockHandlerInfo = gMimeSvc.getFromTypeAndExtension("text/x-test-handler", null);
+ if (gHandlerSvc.exists(mockHandlerInfo)) {
+ gHandlerSvc.remove(mockHandlerInfo);
+ }
+ });
+
+ return mockedLauncher;
+}
+
+function* openHelperAppDialog(launcher) {
+ let helperAppDialog = Cc["@mozilla.org/helperapplauncherdialog;1"].
+ createInstance(Ci.nsIHelperAppLauncherDialog);
+
+ let helperAppDialogShownPromise = BrowserTestUtils.domWindowOpened();
+ try {
+ helperAppDialog.show(launcher, window, "foopy");
+ } catch (ex) {
+ ok(false, "Trying to show unknownContentType.xul failed with exception: " + ex);
+ Cu.reportError(ex);
+ }
+ let dlg = yield helperAppDialogShownPromise;
+
+ yield BrowserTestUtils.waitForEvent(dlg, "load", false);
+
+ is(dlg.location.href, "chrome://mozapps/content/downloads/unknownContentType.xul",
+ "Got correct dialog");
+
+ return dlg;
+}
diff --git a/uriloader/exthandler/tests/mochitest/mochitest.ini b/uriloader/exthandler/tests/mochitest/mochitest.ini
new file mode 100644
index 0000000000..ae191dc7b3
--- /dev/null
+++ b/uriloader/exthandler/tests/mochitest/mochitest.ini
@@ -0,0 +1,10 @@
+[DEFAULT]
+support-files =
+ handlerApp.xhtml
+ handlerApps.js
+ unsafeBidi_chromeScript.js
+ unsafeBidiFileName.sjs
+
+[test_handlerApps.xhtml]
+skip-if = (toolkit == 'android' || os == 'mac') || e10s # OS X: bug 786938
+[test_unsafeBidiChars.xhtml]
diff --git a/uriloader/exthandler/tests/mochitest/protocolHandler.html b/uriloader/exthandler/tests/mochitest/protocolHandler.html
new file mode 100644
index 0000000000..5a929ba994
--- /dev/null
+++ b/uriloader/exthandler/tests/mochitest/protocolHandler.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Protocol handler</title>
+ <meta content="text/html;charset=utf-8" http-equiv="Content-Type">
+ <meta content="utf-8" http-equiv="encoding">
+ </head>
+ <body>
+ <script type="text/javascript">
+ navigator.registerProtocolHandler("testprotocol",
+ "https://example.com/foobar?uri=%s",
+ "Test Protocol");
+ </script>
+ <a id="link" href="testprotocol:test">testprotocol link</a>
+ </body>
+</html>
diff --git a/uriloader/exthandler/tests/mochitest/test_handlerApps.xhtml b/uriloader/exthandler/tests/mochitest/test_handlerApps.xhtml
new file mode 100644
index 0000000000..e5d73a2320
--- /dev/null
+++ b/uriloader/exthandler/tests/mochitest/test_handlerApps.xhtml
@@ -0,0 +1,12 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>Test for Handler Apps </title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="handlerApps.js"/>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+</body>
+</html>
+
diff --git a/uriloader/exthandler/tests/mochitest/test_unsafeBidiChars.xhtml b/uriloader/exthandler/tests/mochitest/test_unsafeBidiChars.xhtml
new file mode 100644
index 0000000000..fafe0b4c56
--- /dev/null
+++ b/uriloader/exthandler/tests/mochitest/test_unsafeBidiChars.xhtml
@@ -0,0 +1,77 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>Test for Handling of unsafe bidi chars</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<iframe id="test"></iframe>
+<script type="text/javascript">
+<![CDATA[
+
+var unsafeBidiChars = [
+ "\xe2\x80\xaa", // LRE
+ "\xe2\x80\xab", // RLE
+ "\xe2\x80\xac", // PDF
+ "\xe2\x80\xad", // LRO
+ "\xe2\x80\xae" // RLO
+];
+
+var tests = [
+ "{1}.test",
+ "{1}File.test",
+ "Fi{1}le.test",
+ "File{1}.test",
+ "File.{1}test",
+ "File.te{1}st",
+ "File.test{1}",
+ "File.{1}",
+];
+
+function replace(name, x) {
+ return name.replace(/\{1\}/, x);
+}
+
+function sanitize(name) {
+ return replace(name, '_');
+}
+
+add_task(function* () {
+ let url = SimpleTest.getTestFileURL("unsafeBidi_chromeScript.js");
+ let chromeScript = SpecialPowers.loadChromeScript(url);
+
+ for (let test of tests) {
+ for (let char of unsafeBidiChars) {
+ let promiseName = new Promise(function(resolve) {
+ chromeScript.addMessageListener("suggestedFileName",
+ function listener(data) {
+ chromeScript.removeMessageListener("suggestedFileName", listener);
+ resolve(data);
+ });
+ });
+ let name = replace(test, char);
+ let expected = sanitize(test);
+ document.getElementById("test").src =
+ "unsafeBidiFileName.sjs?name=" + encodeURIComponent(name);
+ is((yield promiseName), expected, "got the expected sanitized name");
+ }
+ }
+
+ let promise = new Promise(function(resolve) {
+ chromeScript.addMessageListener("unregistered", function listener() {
+ chromeScript.removeMessageListener("unregistered", listener);
+ resolve();
+ });
+ });
+ chromeScript.sendAsyncMessage("unregister");
+ yield promise;
+
+ chromeScript.destroy();
+});
+
+]]>
+</script>
+</body>
+</html>
diff --git a/uriloader/exthandler/tests/mochitest/unsafeBidiFileName.sjs b/uriloader/exthandler/tests/mochitest/unsafeBidiFileName.sjs
new file mode 100644
index 0000000000..48301be5b4
--- /dev/null
+++ b/uriloader/exthandler/tests/mochitest/unsafeBidiFileName.sjs
@@ -0,0 +1,14 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function handleRequest(request, response) {
+ response.setStatusLine(request.httpVersion, 200, "OK");
+
+ if (!request.queryString.match(/^name=/))
+ return;
+ var name = decodeURIComponent(request.queryString.substring(5));
+
+ response.setHeader("Content-Type", "application/octet-stream; name=\"" + name + "\"");
+ response.setHeader("Content-Disposition", "inline; filename=\"" + name + "\"");
+}
diff --git a/uriloader/exthandler/tests/mochitest/unsafeBidi_chromeScript.js b/uriloader/exthandler/tests/mochitest/unsafeBidi_chromeScript.js
new file mode 100644
index 0000000000..16c82d818e
--- /dev/null
+++ b/uriloader/exthandler/tests/mochitest/unsafeBidi_chromeScript.js
@@ -0,0 +1,28 @@
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+const HELPERAPP_DIALOG_CONTRACT = "@mozilla.org/helperapplauncherdialog;1";
+const HELPERAPP_DIALOG_CID =
+ Components.ID(Cc[HELPERAPP_DIALOG_CONTRACT].number);
+
+const FAKE_CID = Cc["@mozilla.org/uuid-generator;1"].
+ getService(Ci.nsIUUIDGenerator).generateUUID();
+
+function HelperAppLauncherDialog() {}
+HelperAppLauncherDialog.prototype = {
+ show: function(aLauncher, aWindowContext, aReason) {
+ sendAsyncMessage("suggestedFileName", aLauncher.suggestedFileName);
+ },
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIHelperAppLauncherDialog])
+};
+
+var registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
+registrar.registerFactory(FAKE_CID, "", HELPERAPP_DIALOG_CONTRACT,
+ XPCOMUtils._getFactory(HelperAppLauncherDialog));
+
+addMessageListener("unregister", function() {
+ registrar.registerFactory(HELPERAPP_DIALOG_CID, "",
+ HELPERAPP_DIALOG_CONTRACT, null);
+ sendAsyncMessage("unregistered");
+});
diff --git a/uriloader/exthandler/tests/moz.build b/uriloader/exthandler/tests/moz.build
new file mode 100644
index 0000000000..6aadfdc527
--- /dev/null
+++ b/uriloader/exthandler/tests/moz.build
@@ -0,0 +1,24 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+MOCHITEST_MANIFESTS += ['mochitest/mochitest.ini']
+
+XPCSHELL_TESTS_MANIFESTS += ['unit/xpcshell.ini']
+
+BROWSER_CHROME_MANIFESTS += ['mochitest/browser.ini']
+
+# The encoding test is already implemented in the Downloads API by a set of
+# test cases with the string "content_encoding" in their names.
+if not CONFIG['MOZ_JSDOWNLOADS']:
+ XPCSHELL_TESTS_MANIFESTS += ['unit_ipc/xpcshell.ini']
+
+GeckoSimplePrograms([
+ 'WriteArgument',
+], linkage=None)
+
+USE_LIBS += [
+ 'nspr',
+]
diff --git a/uriloader/exthandler/tests/unit/head_handlerService.js b/uriloader/exthandler/tests/unit/head_handlerService.js
new file mode 100644
index 0000000000..8b6803d241
--- /dev/null
+++ b/uriloader/exthandler/tests/unit/head_handlerService.js
@@ -0,0 +1,163 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Inspired by the Places infrastructure in head_bookmarks.js
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cr = Components.results;
+var Cu = Components.utils;
+
+var HandlerServiceTest = {
+ //**************************************************************************//
+ // Convenience Getters
+
+ __dirSvc: null,
+ get _dirSvc() {
+ if (!this.__dirSvc)
+ this.__dirSvc = Cc["@mozilla.org/file/directory_service;1"].
+ getService(Ci.nsIProperties).
+ QueryInterface(Ci.nsIDirectoryService);
+ return this.__dirSvc;
+ },
+
+ __consoleSvc: null,
+ get _consoleSvc() {
+ if (!this.__consoleSvc)
+ this.__consoleSvc = Cc["@mozilla.org/consoleservice;1"].
+ getService(Ci.nsIConsoleService);
+ return this.__consoleSvc;
+ },
+
+
+ //**************************************************************************//
+ // nsISupports
+
+ interfaces: [Ci.nsIDirectoryServiceProvider, Ci.nsISupports],
+
+ QueryInterface: function HandlerServiceTest_QueryInterface(iid) {
+ if (!this.interfaces.some( function(v) { return iid.equals(v) } ))
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ return this;
+ },
+
+
+ //**************************************************************************//
+ // Initialization & Destruction
+
+ init: function HandlerServiceTest_init() {
+ // Register ourselves as a directory provider for the datasource file
+ // if there isn't one registered already.
+ try {
+ this._dirSvc.get("UMimTyp", Ci.nsIFile);
+ } catch (ex) {
+ this._dirSvc.registerProvider(this);
+ this._providerRegistered = true;
+ }
+
+ // Delete the existing datasource file, if any, so we start from scratch.
+ // We also do this after finishing the tests, so there shouldn't be an old
+ // file lying around, but just in case we delete it here as well.
+ this._deleteDatasourceFile();
+
+ // Turn on logging so we can troubleshoot problems with the tests.
+ var prefBranch = Cc["@mozilla.org/preferences-service;1"].
+ getService(Ci.nsIPrefBranch);
+ prefBranch.setBoolPref("browser.contentHandling.log", true);
+ },
+
+ destroy: function HandlerServiceTest_destroy() {
+ // Delete the existing datasource file, if any, so we don't leave test files
+ // lying around and we start from scratch the next time.
+ this._deleteDatasourceFile();
+ // Unregister the directory service provider
+ if (this._providerRegistered)
+ this._dirSvc.unregisterProvider(this);
+ },
+
+
+ //**************************************************************************//
+ // nsIDirectoryServiceProvider
+
+ getFile: function HandlerServiceTest_getFile(property, persistent) {
+ this.log("getFile: requesting " + property);
+
+ persistent.value = true;
+
+ if (property == "UMimTyp") {
+ var datasourceFile = this._dirSvc.get("CurProcD", Ci.nsIFile);
+ datasourceFile.append("mimeTypes.rdf");
+ return datasourceFile;
+ }
+
+ // This causes extraneous errors to show up in the log when the directory
+ // service asks us first for CurProcD and MozBinD. I wish there was a way
+ // to suppress those errors.
+ this.log("the following NS_ERROR_FAILURE exception in " +
+ "nsIDirectoryServiceProvider::getFile is expected, " +
+ "as we don't provide the '" + property + "' file");
+ throw Cr.NS_ERROR_FAILURE;
+ },
+
+
+ //**************************************************************************//
+ // Utilities
+
+ /**
+ * Delete the datasource file.
+ */
+ _deleteDatasourceFile: function HandlerServiceTest__deleteDatasourceFile() {
+ var file = this._dirSvc.get("UMimTyp", Ci.nsIFile);
+ if (file.exists())
+ file.remove(false);
+ },
+
+ /**
+ * Get the contents of the datasource as a serialized string. Useful for
+ * debugging problems with test failures, i.e.:
+ *
+ * HandlerServiceTest.log(HandlerServiceTest.getDatasourceContents());
+ *
+ * @returns {string} the serialized datasource
+ */
+ getDatasourceContents: function HandlerServiceTest_getDatasourceContents() {
+ var rdf = Cc["@mozilla.org/rdf/rdf-service;1"].getService(Ci.nsIRDFService);
+
+ var ioService = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+ var fileHandler = ioService.getProtocolHandler("file").
+ QueryInterface(Ci.nsIFileProtocolHandler);
+ var fileURL = fileHandler.getURLSpecFromFile(this.getDatasourceFile());
+ var ds = rdf.GetDataSourceBlocking(fileURL);
+
+ var outputStream = {
+ data: "",
+ close: function() {},
+ flush: function() {},
+ write: function (buffer,count) {
+ this.data += buffer;
+ return count;
+ },
+ writeFrom: function (stream,count) {},
+ isNonBlocking: false
+ };
+
+ ds.QueryInterface(Components.interfaces.nsIRDFXMLSource);
+ ds.Serialize(outputStream);
+
+ return outputStream.data;
+ },
+
+ /**
+ * Log a message to the console and the test log.
+ */
+ log: function HandlerServiceTest_log(message) {
+ message = "*** HandlerServiceTest: " + message;
+ this._consoleSvc.logStringMessage(message);
+ print(message);
+ }
+
+};
+
+HandlerServiceTest.init();
diff --git a/uriloader/exthandler/tests/unit/mailcap b/uriloader/exthandler/tests/unit/mailcap
new file mode 100644
index 0000000000..dc93ef8042
--- /dev/null
+++ b/uriloader/exthandler/tests/unit/mailcap
@@ -0,0 +1,2 @@
+text/plain; cat '%s'; needsterminal
+text/plain; sed '%s'
diff --git a/uriloader/exthandler/tests/unit/tail_handlerService.js b/uriloader/exthandler/tests/unit/tail_handlerService.js
new file mode 100644
index 0000000000..1d1989127e
--- /dev/null
+++ b/uriloader/exthandler/tests/unit/tail_handlerService.js
@@ -0,0 +1,5 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+HandlerServiceTest.destroy();
diff --git a/uriloader/exthandler/tests/unit/test_badMIMEType.js b/uriloader/exthandler/tests/unit/test_badMIMEType.js
new file mode 100644
index 0000000000..df1202a0f2
--- /dev/null
+++ b/uriloader/exthandler/tests/unit/test_badMIMEType.js
@@ -0,0 +1,26 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function run_test() {
+ // "text/plain" has an 0xFF character appended to it. This means it's an
+ // invalid string, which is tricky to enter using a text editor (I used
+ // emacs' hexl-mode). It also means an ordinary text editor might drop it
+ // or convert it to something that *is* valid (in UTF8). So we measure
+ // its length to make sure this hasn't happened.
+ var badMimeType = "text/plainÿ";
+ do_check_eq(badMimeType.length, 11);
+
+ try {
+ var type = Cc["@mozilla.org/mime;1"].
+ getService(Ci.nsIMIMEService).
+ getFromTypeAndExtension(badMimeType, "txt");
+ } catch (e if (e instanceof Ci.nsIException &&
+ e.result == Cr.NS_ERROR_NOT_AVAILABLE)) {
+ // This is an expected exception, thrown if the type can't be determined
+ } finally {
+ }
+ // Not crashing is good enough
+ do_check_eq(true, true);
+}
diff --git a/uriloader/exthandler/tests/unit/test_getTypeFromExtension_ext_to_type_mapping.js b/uriloader/exthandler/tests/unit/test_getTypeFromExtension_ext_to_type_mapping.js
new file mode 100644
index 0000000000..d83c486bb4
--- /dev/null
+++ b/uriloader/exthandler/tests/unit/test_getTypeFromExtension_ext_to_type_mapping.js
@@ -0,0 +1,53 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Test for bug 508030 <https://bugzilla.mozilla.org/show_bug.cgi?id=508030>:
+ * nsIMIMEService.getTypeFromExtension fails to find a match in the
+ * "ext-to-type-mapping" category if the provided extension is not lowercase.
+ */
+function run_test() {
+ // --- Common services ---
+
+ const mimeService = Cc["@mozilla.org/mime;1"].
+ getService(Ci.nsIMIMEService);
+
+ const categoryManager = Cc["@mozilla.org/categorymanager;1"].
+ getService(Ci.nsICategoryManager);
+
+ // --- Test procedure ---
+
+ const kTestExtension = "testextension";
+ const kTestExtensionMixedCase = "testExtensIon";
+ const kTestMimeType = "application/x-testextension";
+
+ // Ensure that the test extension is not initially recognized by the operating
+ // system or the "ext-to-type-mapping" category.
+ try {
+ // Try and get the MIME type associated with the extension.
+ mimeService.getTypeFromExtension(kTestExtension);
+ // The line above should have thrown an exception.
+ do_throw("nsIMIMEService.getTypeFromExtension succeeded unexpectedly");
+ } catch (e if (e instanceof Ci.nsIException &&
+ e.result == Cr.NS_ERROR_NOT_AVAILABLE)) {
+ // This is an expected exception, thrown if the type can't be determined.
+ // Any other exception would cause the test to fail.
+ }
+
+ // Add a temporary category entry mapping the extension to the MIME type.
+ categoryManager.addCategoryEntry("ext-to-type-mapping", kTestExtension,
+ kTestMimeType, false, true);
+
+ // Check that the mapping is recognized in the simple case.
+ var type = mimeService.getTypeFromExtension(kTestExtension);
+ do_check_eq(type, kTestMimeType);
+
+ // Check that the mapping is recognized even if the extension has mixed case.
+ type = mimeService.getTypeFromExtension(kTestExtensionMixedCase);
+ do_check_eq(type, kTestMimeType);
+
+ // Clean up after ourselves.
+ categoryManager.deleteCategoryEntry("ext-to-type-mapping", kTestExtension, false);
+}
diff --git a/uriloader/exthandler/tests/unit/test_getTypeFromExtension_with_empty_Content_Type.js b/uriloader/exthandler/tests/unit/test_getTypeFromExtension_with_empty_Content_Type.js
new file mode 100644
index 0000000000..1ae2a6fcfa
--- /dev/null
+++ b/uriloader/exthandler/tests/unit/test_getTypeFromExtension_with_empty_Content_Type.js
@@ -0,0 +1,186 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Test for bug 484579 <https://bugzilla.mozilla.org/show_bug.cgi?id=484579>:
+ * nsIMIMEService.getTypeFromExtension may fail unexpectedly on Windows when
+ * "Content Type" is empty in the registry.
+ */
+function run_test() {
+ // --- Preliminary platform check ---
+
+ // If this test is not running on the Windows platform, stop now, before
+ // calling XPCOMUtils.generateQI during the MockWindowsRegKey declaration.
+ if (mozinfo.os != "win")
+ return;
+
+ // --- Modified nsIWindowsRegKey implementation ---
+
+ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+ /**
+ * Constructs a new mock registry key by wrapping the provided object.
+ *
+ * This mock implementation is tailored for this test, and forces consumers
+ * of the readStringValue method to believe that the "Content Type" value of
+ * the ".txt" key under HKEY_CLASSES_ROOT is an empty string.
+ *
+ * The same value read from "HKEY_LOCAL_MACHINE\SOFTWARE\Classes" is not
+ * affected.
+ *
+ * @param aWrappedObject An actual nsIWindowsRegKey implementation.
+ */
+ function MockWindowsRegKey(aWrappedObject) {
+ this._wrappedObject = aWrappedObject;
+
+ // This function creates a forwarding function for wrappedObject
+ function makeForwardingFunction(functionName) {
+ return function() {
+ return aWrappedObject[functionName].apply(aWrappedObject, arguments);
+ }
+ }
+
+ // Forward all the functions that are not explicitly overridden
+ for (var propertyName in aWrappedObject) {
+ if (!(propertyName in this)) {
+ if (typeof aWrappedObject[propertyName] == "function") {
+ this[propertyName] = makeForwardingFunction(propertyName);
+ } else {
+ this[propertyName] = aWrappedObject[propertyName];
+ }
+ }
+ }
+ }
+
+ MockWindowsRegKey.prototype = {
+ // --- Overridden nsISupports interface functions ---
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIWindowsRegKey]),
+
+ // --- Overridden nsIWindowsRegKey interface functions ---
+
+ open: function(aRootKey, aRelPath, aMode) {
+ // Remember the provided root key and path
+ this._rootKey = aRootKey;
+ this._relPath = aRelPath;
+
+ // Create the actual registry key
+ return this._wrappedObject.open(aRootKey, aRelPath, aMode);
+ },
+
+ openChild: function(aRelPath, aMode) {
+ // Open the child key and wrap it
+ var innerKey = this._wrappedObject.openChild(aRelPath, aMode);
+ var key = new MockWindowsRegKey(innerKey);
+
+ // Set the properties of the child key and return it
+ key._rootKey = this._rootKey;
+ key._relPath = this._relPath + aRelPath;
+ return key;
+ },
+
+ createChild: function(aRelPath, aMode) {
+ // Create the child key and wrap it
+ var innerKey = this._wrappedObject.createChild(aRelPath, aMode);
+ var key = new MockWindowsRegKey(innerKey);
+
+ // Set the properties of the child key and return it
+ key._rootKey = this._rootKey;
+ key._relPath = this._relPath + aRelPath;
+ return key;
+ },
+
+ get childCount() {
+ return this._wrappedObject.childCount;
+ },
+
+ get valueCount() {
+ return this._wrappedObject.valueCount;
+ },
+
+ readStringValue: function(aName) {
+ // If this is the key under test, return a fake value
+ if (this._rootKey == Ci.nsIWindowsRegKey.ROOT_KEY_CLASSES_ROOT &&
+ this._relPath.toLowerCase() == ".txt" &&
+ aName.toLowerCase() == "content type") {
+ return "";
+ }
+
+ // Return the real value in the registry
+ return this._wrappedObject.readStringValue(aName);
+ }
+ };
+
+ // --- Mock nsIWindowsRegKey factory ---
+
+ var componentRegistrar = Components.manager.
+ QueryInterface(Ci.nsIComponentRegistrar);
+
+ var originalWindowsRegKeyCID;
+ var mockWindowsRegKeyFactory;
+
+ const kMockCID = Components.ID("{9b23dfe9-296b-4740-ba1c-d39c9a16e55e}");
+ const kWindowsRegKeyContractID = "@mozilla.org/windows-registry-key;1";
+ const kWindowsRegKeyClassName = "nsWindowsRegKey";
+
+ function registerMockWindowsRegKeyFactory() {
+ mockWindowsRegKeyFactory = {
+ createInstance: function(aOuter, aIid) {
+ if (aOuter != null)
+ throw Cr.NS_ERROR_NO_AGGREGATION;
+
+ var innerKey = originalWindowsRegKeyFactory.createInstance(null, aIid);
+ var key = new MockWindowsRegKey(innerKey);
+
+ return key.QueryInterface(aIid);
+ }
+ };
+
+ // Preserve the original factory
+ originalWindowsRegKeyCID = Cc[kWindowsRegKeyContractID].number;
+
+ // Register the mock factory
+ componentRegistrar.registerFactory(
+ kMockCID,
+ "Mock Windows Registry Key Implementation",
+ kWindowsRegKeyContractID,
+ mockWindowsRegKeyFactory
+ );
+ }
+
+ function unregisterMockWindowsRegKeyFactory() {
+ // Free references to the mock factory
+ componentRegistrar.unregisterFactory(
+ kMockCID,
+ mockWindowsRegKeyFactory
+ );
+
+ // Restore the original factory
+ componentRegistrar.registerFactory(
+ Components.ID(originalWindowsRegKeyCID),
+ "",
+ kWindowsRegKeyContractID,
+ null
+ );
+ }
+
+ // --- Test procedure ---
+
+ // Activate the override of the ".txt" file association data in the registry
+ registerMockWindowsRegKeyFactory();
+ try {
+ // Try and get the MIME type associated with the extension. If this
+ // operation does not throw an unexpected exception, the test succeeds.
+ var type = Cc["@mozilla.org/mime;1"].
+ getService(Ci.nsIMIMEService).
+ getTypeFromExtension(".txt");
+ } catch (e if (e instanceof Ci.nsIException &&
+ e.result == Cr.NS_ERROR_NOT_AVAILABLE)) {
+ // This is an expected exception, thrown if the type can't be determined
+ } finally {
+ // Ensure we restore the original factory when the test is finished
+ unregisterMockWindowsRegKeyFactory();
+ }
+}
diff --git a/uriloader/exthandler/tests/unit/test_handlerService.js b/uriloader/exthandler/tests/unit/test_handlerService.js
new file mode 100644
index 0000000000..3facc63aeb
--- /dev/null
+++ b/uriloader/exthandler/tests/unit/test_handlerService.js
@@ -0,0 +1,470 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function run_test() {
+ //**************************************************************************//
+ // Constants
+
+ const handlerSvc = Cc["@mozilla.org/uriloader/handler-service;1"].
+ getService(Ci.nsIHandlerService);
+
+ const mimeSvc = Cc["@mozilla.org/mime;1"].
+ getService(Ci.nsIMIMEService);
+
+ const protoSvc = Cc["@mozilla.org/uriloader/external-protocol-service;1"].
+ getService(Ci.nsIExternalProtocolService);
+
+ const prefSvc = Cc["@mozilla.org/preferences-service;1"].
+ getService(Ci.nsIPrefService);
+
+ const ioService = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+
+ const env = Cc["@mozilla.org/process/environment;1"].
+ getService(Components.interfaces.nsIEnvironment);
+
+ const rootPrefBranch = prefSvc.getBranch("");
+
+ let noMailto = false;
+ if (mozinfo.os == "win") {
+ // Check mailto handler from registry.
+ // If registry entry is nothing, no mailto handler
+ let regSvc = Cc["@mozilla.org/windows-registry-key;1"].
+ createInstance(Ci.nsIWindowsRegKey);
+ try {
+ regSvc.open(regSvc.ROOT_KEY_CLASSES_ROOT,
+ "mailto",
+ regSvc.ACCESS_READ);
+ noMailto = false;
+ } catch (ex) {
+ noMailto = true;
+ }
+ regSvc.close();
+ }
+
+ if (mozinfo.os == "linux") {
+ // Check mailto handler from GIO
+ // If there isn't one, then we have no mailto handler
+ let gIOSvc = Cc["@mozilla.org/gio-service;1"].
+ createInstance(Ci.nsIGIOService);
+ try {
+ gIOSvc.getAppForURIScheme("mailto");
+ noMailto = false;
+ } catch (ex) {
+ noMailto = true;
+ }
+ }
+
+ //**************************************************************************//
+ // Sample Data
+
+ // It doesn't matter whether or not this nsIFile is actually executable,
+ // only that it has a path and exists. Since we don't know any executable
+ // that exists on all platforms (except possibly the application being
+ // tested, but there doesn't seem to be a way to get a reference to that
+ // from the directory service), we use the temporary directory itself.
+ var executable = HandlerServiceTest._dirSvc.get("TmpD", Ci.nsIFile);
+ // XXX We could, of course, create an actual executable in the directory:
+ //executable.append("localhandler");
+ //if (!executable.exists())
+ // executable.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o755);
+
+ var localHandler = {
+ name: "Local Handler",
+ executable: executable,
+ interfaces: [Ci.nsIHandlerApp, Ci.nsILocalHandlerApp, Ci.nsISupports],
+ QueryInterface: function(iid) {
+ if (!this.interfaces.some( function(v) { return iid.equals(v) } ))
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ return this;
+ }
+ };
+
+ var webHandler = Cc["@mozilla.org/uriloader/web-handler-app;1"].
+ createInstance(Ci.nsIWebHandlerApp);
+ webHandler.name = "Web Handler";
+ webHandler.uriTemplate = "http://www.example.com/?%s";
+
+ // FIXME: these tests create and manipulate enough variables that it would
+ // make sense to move each test into its own scope so we don't run the risk
+ // of one test stomping on another's data.
+
+
+ //**************************************************************************//
+ // Test Default Properties
+
+ // Get a handler info for a MIME type that neither the application nor
+ // the OS knows about and make sure its properties are set to the proper
+ // default values.
+
+ var handlerInfo = mimeSvc.getFromTypeAndExtension("nonexistent/type", null);
+
+ // Make sure it's also an nsIHandlerInfo.
+ do_check_true(handlerInfo instanceof Ci.nsIHandlerInfo);
+
+ do_check_eq(handlerInfo.type, "nonexistent/type");
+
+ // Deprecated property, but we should still make sure it's set correctly.
+ do_check_eq(handlerInfo.MIMEType, "nonexistent/type");
+
+ // These properties are the ones the handler service knows how to store.
+ do_check_eq(handlerInfo.preferredAction, Ci.nsIHandlerInfo.saveToDisk);
+ do_check_eq(handlerInfo.preferredApplicationHandler, null);
+ do_check_eq(handlerInfo.possibleApplicationHandlers.length, 0);
+ do_check_true(handlerInfo.alwaysAskBeforeHandling);
+
+ // These properties are initialized to default values by the service,
+ // so we might as well make sure they're initialized to the right defaults.
+ do_check_eq(handlerInfo.description, "");
+ do_check_eq(handlerInfo.hasDefaultHandler, false);
+ do_check_eq(handlerInfo.defaultDescription, "");
+
+ // test some default protocol info properties
+ var haveDefaultHandlersVersion = false;
+ try {
+ // If we have a defaultHandlersVersion pref, then assume that we're in the
+ // firefox tree and that we'll also have default handlers.
+ // Bug 395131 has been filed to make this test work more generically
+ // by providing our own prefs for this test rather than this icky
+ // special casing.
+ rootPrefBranch.getCharPref("gecko.handlerService.defaultHandlersVersion");
+ haveDefaultHandlersVersion = true;
+ } catch (ex) {}
+
+ const kExternalWarningDefault =
+ "network.protocol-handler.warn-external-default";
+ prefSvc.setBoolPref(kExternalWarningDefault, true);
+
+ // XXX add more thorough protocol info property checking
+
+ // no OS default handler exists
+ var protoInfo = protoSvc.getProtocolHandlerInfo("x-moz-rheet");
+ do_check_eq(protoInfo.preferredAction, protoInfo.alwaysAsk);
+ do_check_true(protoInfo.alwaysAskBeforeHandling);
+
+ // OS default exists, injected default does not exist,
+ // explicit warning pref: false
+ const kExternalWarningPrefPrefix = "network.protocol-handler.warn-external.";
+ prefSvc.setBoolPref(kExternalWarningPrefPrefix + "http", false);
+ protoInfo = protoSvc.getProtocolHandlerInfo("http");
+ do_check_eq(0, protoInfo.possibleApplicationHandlers.length);
+ do_check_false(protoInfo.alwaysAskBeforeHandling);
+
+ // OS default exists, injected default does not exist,
+ // explicit warning pref: true
+ prefSvc.setBoolPref(kExternalWarningPrefPrefix + "http", true);
+ protoInfo = protoSvc.getProtocolHandlerInfo("http");
+ // OS handler isn't included in possibleApplicationHandlers, so length is 0
+ // Once they become instances of nsILocalHandlerApp, this number will need
+ // to change.
+ do_check_eq(0, protoInfo.possibleApplicationHandlers.length);
+ do_check_true(protoInfo.alwaysAskBeforeHandling);
+
+ // OS default exists, injected default exists, explicit warning pref: false
+ prefSvc.setBoolPref(kExternalWarningPrefPrefix + "mailto", false);
+ protoInfo = protoSvc.getProtocolHandlerInfo("mailto");
+ if (haveDefaultHandlersVersion)
+ do_check_eq(2, protoInfo.possibleApplicationHandlers.length);
+ else
+ do_check_eq(0, protoInfo.possibleApplicationHandlers.length);
+
+ // Win7+ or Linux's GIO might not have a default mailto: handler
+ if (noMailto)
+ do_check_true(protoInfo.alwaysAskBeforeHandling);
+ else
+ do_check_false(protoInfo.alwaysAskBeforeHandling);
+
+ // OS default exists, injected default exists, explicit warning pref: true
+ prefSvc.setBoolPref(kExternalWarningPrefPrefix + "mailto", true);
+ protoInfo = protoSvc.getProtocolHandlerInfo("mailto");
+ if (haveDefaultHandlersVersion) {
+ do_check_eq(2, protoInfo.possibleApplicationHandlers.length);
+ // Win7+ or Linux's GIO may have no default mailto: handler. Otherwise
+ // alwaysAskBeforeHandling is expected to be false here, because although
+ // the pref is true, the value in RDF is false. The injected mailto handler
+ // carried over the default pref value, and so when we set the pref above
+ // to true it's ignored.
+ if (noMailto)
+ do_check_true(protoInfo.alwaysAskBeforeHandling);
+ else
+ do_check_false(protoInfo.alwaysAskBeforeHandling);
+
+ } else {
+ do_check_eq(0, protoInfo.possibleApplicationHandlers.length);
+ do_check_true(protoInfo.alwaysAskBeforeHandling);
+ }
+
+ if (haveDefaultHandlersVersion) {
+ // Now set the value stored in RDF to true, and the pref to false, to make
+ // sure we still get the right value. (Basically, same thing as above but
+ // with the values reversed.)
+ prefSvc.setBoolPref(kExternalWarningPrefPrefix + "mailto", false);
+ protoInfo.alwaysAskBeforeHandling = true;
+ handlerSvc.store(protoInfo);
+ protoInfo = protoSvc.getProtocolHandlerInfo("mailto");
+ do_check_eq(2, protoInfo.possibleApplicationHandlers.length);
+ do_check_true(protoInfo.alwaysAskBeforeHandling);
+ }
+
+
+ //**************************************************************************//
+ // Test Round-Trip Data Integrity
+
+ // Test round-trip data integrity by setting the properties of the handler
+ // info object to different values, telling the handler service to store the
+ // object, and then retrieving a new info object for the same type and making
+ // sure its properties are identical.
+
+ handlerInfo.preferredAction = Ci.nsIHandlerInfo.useHelperApp;
+ handlerInfo.preferredApplicationHandler = localHandler;
+ handlerInfo.alwaysAskBeforeHandling = false;
+
+ handlerSvc.store(handlerInfo);
+
+ handlerInfo = mimeSvc.getFromTypeAndExtension("nonexistent/type", null);
+
+ do_check_eq(handlerInfo.preferredAction, Ci.nsIHandlerInfo.useHelperApp);
+
+ do_check_neq(handlerInfo.preferredApplicationHandler, null);
+ var preferredHandler = handlerInfo.preferredApplicationHandler;
+ do_check_eq(typeof preferredHandler, "object");
+ do_check_eq(preferredHandler.name, "Local Handler");
+ do_check_true(preferredHandler instanceof Ci.nsILocalHandlerApp);
+ preferredHandler.QueryInterface(Ci.nsILocalHandlerApp);
+ do_check_eq(preferredHandler.executable.path, localHandler.executable.path);
+
+ do_check_false(handlerInfo.alwaysAskBeforeHandling);
+
+ // Make sure the handler service's enumerate method lists all known handlers.
+ var handlerInfo2 = mimeSvc.getFromTypeAndExtension("nonexistent/type2", null);
+ handlerSvc.store(handlerInfo2);
+ var handlerTypes = ["nonexistent/type", "nonexistent/type2"];
+ if (haveDefaultHandlersVersion) {
+ handlerTypes.push("webcal");
+ handlerTypes.push("mailto");
+ handlerTypes.push("irc");
+ handlerTypes.push("ircs");
+ }
+ var handlers = handlerSvc.enumerate();
+ while (handlers.hasMoreElements()) {
+ var handler = handlers.getNext().QueryInterface(Ci.nsIHandlerInfo);
+ do_check_neq(handlerTypes.indexOf(handler.type), -1);
+ handlerTypes.splice(handlerTypes.indexOf(handler.type), 1);
+ }
+ do_check_eq(handlerTypes.length, 0);
+
+ // Make sure the handler service's remove method removes a handler record.
+ handlerSvc.remove(handlerInfo2);
+ handlers = handlerSvc.enumerate();
+ while (handlers.hasMoreElements())
+ do_check_neq(handlers.getNext().QueryInterface(Ci.nsIHandlerInfo).type,
+ handlerInfo2.type);
+
+ // Make sure we can store and retrieve a handler info object with no preferred
+ // handler.
+ var noPreferredHandlerInfo =
+ mimeSvc.getFromTypeAndExtension("nonexistent/no-preferred-handler", null);
+ handlerSvc.store(noPreferredHandlerInfo);
+ noPreferredHandlerInfo =
+ mimeSvc.getFromTypeAndExtension("nonexistent/no-preferred-handler", null);
+ do_check_eq(noPreferredHandlerInfo.preferredApplicationHandler, null);
+
+ // Make sure that the handler service removes an existing handler record
+ // if we store a handler info object with no preferred handler.
+ var removePreferredHandlerInfo =
+ mimeSvc.getFromTypeAndExtension("nonexistent/rem-preferred-handler", null);
+ removePreferredHandlerInfo.preferredApplicationHandler = localHandler;
+ handlerSvc.store(removePreferredHandlerInfo);
+ removePreferredHandlerInfo =
+ mimeSvc.getFromTypeAndExtension("nonexistent/rem-preferred-handler", null);
+ removePreferredHandlerInfo.preferredApplicationHandler = null;
+ handlerSvc.store(removePreferredHandlerInfo);
+ removePreferredHandlerInfo =
+ mimeSvc.getFromTypeAndExtension("nonexistent/rem-preferred-handler", null);
+ do_check_eq(removePreferredHandlerInfo.preferredApplicationHandler, null);
+
+ // Make sure we can store and retrieve a handler info object with possible
+ // handlers. We test both adding and removing handlers.
+
+ // Get a handler info and make sure it has no possible handlers.
+ var possibleHandlersInfo =
+ mimeSvc.getFromTypeAndExtension("nonexistent/possible-handlers", null);
+ do_check_eq(possibleHandlersInfo.possibleApplicationHandlers.length, 0);
+
+ // Store and re-retrieve the handler and make sure it still has no possible
+ // handlers.
+ handlerSvc.store(possibleHandlersInfo);
+ possibleHandlersInfo =
+ mimeSvc.getFromTypeAndExtension("nonexistent/possible-handlers", null);
+ do_check_eq(possibleHandlersInfo.possibleApplicationHandlers.length, 0);
+
+ // Add two handlers, store the object, re-retrieve it, and make sure it has
+ // two handlers.
+ possibleHandlersInfo.possibleApplicationHandlers.appendElement(localHandler,
+ false);
+ possibleHandlersInfo.possibleApplicationHandlers.appendElement(webHandler,
+ false);
+ handlerSvc.store(possibleHandlersInfo);
+ possibleHandlersInfo =
+ mimeSvc.getFromTypeAndExtension("nonexistent/possible-handlers", null);
+ do_check_eq(possibleHandlersInfo.possibleApplicationHandlers.length, 2);
+
+ // Figure out which is the local and which is the web handler and the index
+ // in the array of the local handler, which is the one we're going to remove
+ // to test removal of a handler.
+ var handler1 = possibleHandlersInfo.possibleApplicationHandlers.
+ queryElementAt(0, Ci.nsIHandlerApp);
+ var handler2 = possibleHandlersInfo.possibleApplicationHandlers.
+ queryElementAt(1, Ci.nsIHandlerApp);
+ var localPossibleHandler, webPossibleHandler, localIndex;
+ if (handler1 instanceof Ci.nsILocalHandlerApp)
+ [localPossibleHandler, webPossibleHandler, localIndex] = [handler1,
+ handler2,
+ 0];
+ else
+ [localPossibleHandler, webPossibleHandler, localIndex] = [handler2,
+ handler1,
+ 1];
+ localPossibleHandler.QueryInterface(Ci.nsILocalHandlerApp);
+ webPossibleHandler.QueryInterface(Ci.nsIWebHandlerApp);
+
+ // Make sure the two handlers are the ones we stored.
+ do_check_eq(localPossibleHandler.name, localHandler.name);
+ do_check_true(localPossibleHandler.equals(localHandler));
+ do_check_eq(webPossibleHandler.name, webHandler.name);
+ do_check_true(webPossibleHandler.equals(webHandler));
+
+ // Remove a handler, store the object, re-retrieve it, and make sure
+ // it only has one handler.
+ possibleHandlersInfo.possibleApplicationHandlers.removeElementAt(localIndex);
+ handlerSvc.store(possibleHandlersInfo);
+ possibleHandlersInfo =
+ mimeSvc.getFromTypeAndExtension("nonexistent/possible-handlers", null);
+ do_check_eq(possibleHandlersInfo.possibleApplicationHandlers.length, 1);
+
+ // Make sure the handler is the one we didn't remove.
+ webPossibleHandler = possibleHandlersInfo.possibleApplicationHandlers.
+ queryElementAt(0, Ci.nsIWebHandlerApp);
+ do_check_eq(webPossibleHandler.name, webHandler.name);
+ do_check_true(webPossibleHandler.equals(webHandler));
+
+ //////////////////////////////////////////////////////
+ // handler info command line parameters and equality
+ var localApp = Cc["@mozilla.org/uriloader/local-handler-app;1"].
+ createInstance(Ci.nsILocalHandlerApp);
+ var handlerApp = localApp.QueryInterface(Ci.nsIHandlerApp);
+
+ do_check_true(handlerApp.equals(localApp));
+
+ localApp.executable = executable;
+
+ do_check_eq(0, localApp.parameterCount);
+ localApp.appendParameter("-test1");
+ do_check_eq(1, localApp.parameterCount);
+ localApp.appendParameter("-test2");
+ do_check_eq(2, localApp.parameterCount);
+ do_check_true(localApp.parameterExists("-test1"));
+ do_check_true(localApp.parameterExists("-test2"));
+ do_check_false(localApp.parameterExists("-false"));
+ localApp.clearParameters();
+ do_check_eq(0, localApp.parameterCount);
+
+ var localApp2 = Cc["@mozilla.org/uriloader/local-handler-app;1"].
+ createInstance(Ci.nsILocalHandlerApp);
+
+ localApp2.executable = executable;
+
+ localApp.clearParameters();
+ do_check_true(localApp.equals(localApp2));
+
+ // equal:
+ // cut -d 1 -f 2
+ // cut -d 1 -f 2
+
+ localApp.appendParameter("-test1");
+ localApp.appendParameter("-test2");
+ localApp.appendParameter("-test3");
+ localApp2.appendParameter("-test1");
+ localApp2.appendParameter("-test2");
+ localApp2.appendParameter("-test3");
+ do_check_true(localApp.equals(localApp2));
+
+ // not equal:
+ // cut -d 1 -f 2
+ // cut -f 1 -d 2
+
+ localApp.clearParameters();
+ localApp2.clearParameters();
+
+ localApp.appendParameter("-test1");
+ localApp.appendParameter("-test2");
+ localApp.appendParameter("-test3");
+ localApp2.appendParameter("-test2");
+ localApp2.appendParameter("-test1");
+ localApp2.appendParameter("-test3");
+ do_check_false(localApp2.equals(localApp));
+
+ var str;
+ str = localApp.getParameter(0)
+ do_check_eq(str, "-test1");
+ str = localApp.getParameter(1)
+ do_check_eq(str, "-test2");
+ str = localApp.getParameter(2)
+ do_check_eq(str, "-test3");
+
+ // FIXME: test round trip integrity for a protocol.
+ // FIXME: test round trip integrity for a handler info with a web handler.
+
+ //**************************************************************************//
+ // getTypeFromExtension tests
+
+ // test nonexistent extension
+ var lolType = handlerSvc.getTypeFromExtension("lolcat");
+ do_check_eq(lolType, "");
+
+
+ // add a handler for the extension
+ var lolHandler = mimeSvc.getFromTypeAndExtension("application/lolcat", null);
+
+ do_check_false(lolHandler.extensionExists("lolcat"));
+ lolHandler.preferredAction = Ci.nsIHandlerInfo.useHelperApp;
+ lolHandler.preferredApplicationHandler = localHandler;
+ lolHandler.alwaysAskBeforeHandling = false;
+
+ // store the handler
+ do_check_false(handlerSvc.exists(lolHandler));
+ handlerSvc.store(lolHandler);
+ do_check_true(handlerSvc.exists(lolHandler));
+
+ // Get a file:// string pointing to mimeTypes.rdf
+ var rdfFile = HandlerServiceTest._dirSvc.get("UMimTyp", Ci.nsIFile);
+ var fileHandler = ioService.getProtocolHandler("file").QueryInterface(Ci.nsIFileProtocolHandler);
+ var rdfFileURI = fileHandler.getURLSpecFromFile(rdfFile);
+
+ // Assign a file extenstion to the handler. handlerSvc.store() doesn't
+ // actually store any file extensions added with setFileExtensions(), you
+ // have to wade into RDF muck to do so.
+
+ // Based on toolkit/mozapps/downloads/content/helperApps.js :: addExtension()
+ var gRDF = Cc["@mozilla.org/rdf/rdf-service;1"].getService(Ci.nsIRDFService);
+ var mimeSource = gRDF.GetUnicodeResource("urn:mimetype:application/lolcat");
+ var valueProperty = gRDF.GetUnicodeResource("http://home.netscape.com/NC-rdf#fileExtensions");
+ var mimeLiteral = gRDF.GetLiteral("lolcat");
+
+ var DS = gRDF.GetDataSourceBlocking(rdfFileURI);
+ DS.Assert(mimeSource, valueProperty, mimeLiteral, true);
+
+
+ // test now-existent extension
+ lolType = handlerSvc.getTypeFromExtension("lolcat");
+ do_check_eq(lolType, "application/lolcat");
+
+ // test mailcap entries with needsterminal are ignored on non-Windows non-Mac.
+ if (mozinfo.os != "win" && mozinfo.os != "mac") {
+ env.set('PERSONAL_MAILCAP', do_get_file('mailcap').path);
+ handlerInfo = mimeSvc.getFromTypeAndExtension("text/plain", null);
+ do_check_eq(handlerInfo.preferredAction, Ci.nsIHandlerInfo.useSystemDefault);
+ do_check_eq(handlerInfo.defaultDescription, "sed");
+ }
+}
diff --git a/uriloader/exthandler/tests/unit/test_punycodeURIs.js b/uriloader/exthandler/tests/unit/test_punycodeURIs.js
new file mode 100644
index 0000000000..38622c840a
--- /dev/null
+++ b/uriloader/exthandler/tests/unit/test_punycodeURIs.js
@@ -0,0 +1,126 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Encoded test URI to work on all platforms/independent of file encoding
+const kTestURI = "http://\u65e5\u672c\u8a93.jp/";
+const kExpectedURI = "http://xn--wgv71a309e.jp/";
+const kOutputFile = "result.txt";
+
+// Try several times in case the box we're running on is slow.
+const kMaxCheckExistAttempts = 30; // seconds
+var gCheckExistsAttempts = 0;
+
+const tempDir = do_get_tempdir();
+
+function checkFile() {
+ // This is where we expect the output
+ var tempFile = tempDir.clone();
+ tempFile.append(kOutputFile);
+
+ if (!tempFile.exists()) {
+ if (gCheckExistsAttempts >= kMaxCheckExistAttempts) {
+ do_throw("Expected File " + tempFile.path + " does not exist after " +
+ kMaxCheckExistAttempts + " seconds");
+ }
+ else {
+ ++gCheckExistsAttempts;
+ // Wait a bit longer then try again
+ do_timeout(1000, checkFile);
+ return;
+ }
+ }
+
+ // Now read it
+ var fstream =
+ Components.classes["@mozilla.org/network/file-input-stream;1"]
+ .createInstance(Components.interfaces.nsIFileInputStream);
+ var sstream =
+ Components.classes["@mozilla.org/scriptableinputstream;1"]
+ .createInstance(Components.interfaces.nsIScriptableInputStream);
+ fstream.init(tempFile, -1, 0, 0);
+ sstream.init(fstream);
+
+ // Read the first line only as that's the one we expect WriteArguments
+ // to be writing the argument to.
+ var data = sstream.read(4096);
+
+ sstream.close();
+ fstream.close();
+
+ // Now remove the old file
+ tempFile.remove(false);
+
+ // This currently fails on Mac with an argument like -psn_0_nnnnnn
+ // This seems to be to do with how the executable is called, but I couldn't
+ // find a way around it.
+ // Additionally the lack of OS detection in xpcshell tests sucks, so we'll
+ // have to check for the argument mac gives us.
+ if (data.substring(0, 7) != "-psn_0_")
+ do_check_eq(data, kExpectedURI);
+
+ do_test_finished();
+}
+
+function run_test() {
+ if (mozinfo.os == "mac") {
+ dump("INFO | test_punycodeURIs.js | Skipping test on mac, bug 599475")
+ return;
+ }
+
+ // set up the uri to test with
+ var ioService =
+ Components.classes["@mozilla.org/network/io-service;1"]
+ .getService(Components.interfaces.nsIIOService);
+
+ // set up the local handler object
+ var localHandler =
+ Components.classes["@mozilla.org/uriloader/local-handler-app;1"]
+ .createInstance(Components.interfaces.nsILocalHandlerApp);
+ localHandler.name = "Test Local Handler App";
+
+ // WriteArgument will just dump its arguments to a file for us.
+ var processDir = do_get_cwd();
+ var exe = processDir.clone();
+ exe.append("WriteArgument");
+
+ if (!exe.exists()) {
+ // Maybe we are on windows
+ exe.leafName = "WriteArgument.exe";
+ if (!exe.exists())
+ do_throw("Could not locate the WriteArgument tests executable\n");
+ }
+
+ var outFile = tempDir.clone();
+ outFile.append(kOutputFile);
+
+ // Set an environment variable for WriteArgument to pick up
+ var envSvc =
+ Components.classes["@mozilla.org/process/environment;1"]
+ .getService(Components.interfaces.nsIEnvironment);
+
+ // The Write Argument file needs to know where its libraries are, so
+ // just force the path variable
+ // For mac
+ var greDir = HandlerServiceTest._dirSvc.get("GreD", Components.interfaces.nsIFile);
+
+ envSvc.set("DYLD_LIBRARY_PATH", greDir.path);
+ // For Linux
+ envSvc.set("LD_LIBRARY_PATH", greDir.path);
+ //XXX: handle windows
+
+ // Now tell it where we want the file.
+ envSvc.set("WRITE_ARGUMENT_FILE", outFile.path);
+
+ var uri = ioService.newURI(kTestURI, null, null);
+
+ // Just check we've got these matching, if we haven't there's a problem
+ // with ascii spec or our test case.
+ do_check_eq(uri.asciiSpec, kExpectedURI);
+
+ localHandler.executable = exe;
+ localHandler.launchWithURI(uri);
+
+ do_test_pending();
+ do_timeout(1000, checkFile);
+}
diff --git a/uriloader/exthandler/tests/unit/xpcshell.ini b/uriloader/exthandler/tests/unit/xpcshell.ini
new file mode 100644
index 0000000000..e268ff9c37
--- /dev/null
+++ b/uriloader/exthandler/tests/unit/xpcshell.ini
@@ -0,0 +1,15 @@
+[DEFAULT]
+head = head_handlerService.js
+tail = tail_handlerService.js
+run-sequentially = Bug 912235 - Intermittent failures
+
+[test_getTypeFromExtension_ext_to_type_mapping.js]
+[test_getTypeFromExtension_with_empty_Content_Type.js]
+[test_badMIMEType.js]
+[test_handlerService.js]
+support-files = mailcap
+# Bug 676997: test consistently fails on Android
+fail-if = os == "android"
+[test_punycodeURIs.js]
+# Bug 676997: test consistently fails on Android
+fail-if = os == "android"
diff --git a/uriloader/exthandler/tests/unit_ipc/test_encoding.js b/uriloader/exthandler/tests/unit_ipc/test_encoding.js
new file mode 100644
index 0000000000..a2a00937a1
--- /dev/null
+++ b/uriloader/exthandler/tests/unit_ipc/test_encoding.js
@@ -0,0 +1,231 @@
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+var Cr = Components.results;
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+Cu.import("resource://testing-common/MockRegistrar.js");
+
+do_get_profile();
+
+var DownloadListener = {
+ init: function () {
+ let obs = Services.obs;
+ obs.addObserver(this, "dl-done", true);
+ },
+
+ observe: function (subject, topic, data) {
+ this.onFinished(subject, topic, data);
+ },
+
+ QueryInterface: function (iid) {
+ if (iid.equals(Ci.nsIObserver) ||
+ iid.equals(Ci.nsISupportsWeakReference) ||
+ iid.equals(Ci.nsISupports))
+ return this;
+
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ }
+}
+DownloadListener.init();
+
+function HelperAppDlg() { }
+HelperAppDlg.prototype = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIHelperAppLauncherDialog]),
+ show: function (launcher, ctx, reason, usePrivateUI) {
+ launcher.MIMEInfo.preferredAction = Ci.nsIMIMEInfo.saveToFile;
+ launcher.launchWithApplication(null, false);
+ }
+}
+
+// Override the download-manager-ui to prevent anyone from trying to open
+// a window.
+function DownloadMgrUI() { }
+DownloadMgrUI.prototype = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIDownloadManagerUI]),
+ show: function (ir, aID, reason) { },
+
+ visible: false,
+
+ getAttention: function () { }
+}
+
+function AlertsSVC() { }
+AlertsSVC.prototype = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIAlertsService]),
+ showAlertNotification: function (url, title, text, clickable, cookie, listener, name) { },
+}
+
+MockRegistrar.register("@mozilla.org/helperapplauncherdialog;1",
+ HelperAppDlg);
+MockRegistrar.register("@mozilla.org/download-manager-ui;1",
+ DownloadMgrUI);
+MockRegistrar.register("@mozilla.org/alerts-service;1",
+ AlertsSVC);
+
+function initChildTestEnv()
+{
+ sendCommand(' \
+ const Cc = Components.classes; \
+ const Ci = Components.interfaces; \
+ const Cr = Components.results; \
+ const Cu = Components.utils; \
+ Cu.import("resource://gre/modules/Services.jsm"); \
+ function WindowContext() { } \
+ \
+ WindowContext.prototype = { \
+ getInterface: function (iid) { \
+ if (iid.equals(Ci.nsIInterfaceRequestor) || \
+ iid.equals(Ci.nsIURIContentListener) || \
+ iid.equals(Ci.nsILoadGroup) || \
+ iid.equals(Ci.nsIDocumentLoader) || \
+ iid.equals(Ci.nsIDOMWindow)) \
+ return this; \
+ \
+ throw Cr.NS_ERROR_NO_INTERFACE; \
+ }, \
+ \
+ /* nsIURIContentListener */ \
+ onStartURIOpen: function (uri) { }, \
+ isPreferred: function (type, desiredtype) { return false; }, \
+ \
+ /* nsILoadGroup */ \
+ addRequest: function (request, context) { }, \
+ removeRequest: function (request, context, status) { } \
+ }; \
+ \
+ var ioservice = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);\
+ var uriloader = Cc["@mozilla.org/uriloader;1"].getService(Ci.nsIURILoader);\
+ ');
+}
+
+function testFinisher(endFunc) {
+ let ef = endFunc;
+ return function (file) {
+ ef(file);
+ runNextTest();
+ }
+}
+
+function runChildTestSet(set)
+{
+ DownloadListener.onFinished = testFinisher(set[2]);
+ sendCommand('\
+ let uri = ioservice.newURI("http://localhost:4444' + set[0] + '", null, null); \
+ let channel = NetUtil.newChannel({uri: uri, loadUsingSystemPrincipal: true}); \
+ uriloader.openURI(channel, Ci.nsIURILoader.IS_CONTENT_PREFERRED, new WindowContext()); \
+ ');
+}
+
+var httpserver = null;
+var currentTest = 0;
+function runNextTest()
+{
+ if (currentTest == tests.length) {
+ httpserver.stop(do_test_finished);
+ return;
+ }
+
+ let set = tests[currentTest++];
+ runChildTestSet(set);
+}
+
+const responseBody = [0x1f, 0x8b, 0x08, 0x00, 0x16, 0x5a, 0x8a, 0x48, 0x02,
+ 0x03, 0x2b, 0x49, 0x2d, 0x2e, 0xe1, 0x02, 0x00, 0xc6,
+ 0x35, 0xb9, 0x3b, 0x05, 0x00, 0x00, 0x00];
+
+/*
+ * First test: a file with Content-Type application/x-gzip and Content-Encoding gzip
+ * should not be decoded in a round-trip
+ */
+function testResponse1(metadata, response) {
+ response.setHeader("Content-Type", "application/x-gzip", false);
+ response.setHeader("Content-Encoding", "gzip", false);
+ response.setHeader("Content-Disposition", "attachment", false);
+
+ var bos = Cc["@mozilla.org/binaryoutputstream;1"].createInstance(Ci.nsIBinaryOutputStream);
+ bos.setOutputStream(response.bodyOutputStream);
+ bos.writeByteArray(responseBody, responseBody.length);
+}
+
+function finishTest1(subject, topic, data) {
+ let file = subject.QueryInterface(Ci.nsIDownload).targetFile;
+ do_check_true(file.path.search("test1.gz") != 0);
+ let fis = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(Ci.nsIFileInputStream);
+ fis.init(file, -1, -1, 0);
+ let bis = Cc["@mozilla.org/binaryinputstream;1"].createInstance(Ci.nsIBinaryInputStream);
+ bis.setInputStream(fis);
+ let str = bis.readByteArray(bis.available());
+ do_check_matches(str, responseBody);
+}
+
+/*
+ * Second test: a file with Content-Type text/html and Content-Encoding gzip
+ * should not be decoded in a round-trip, if its filename ends in ".gz".
+ * We specify a Content-disposition header to force it to be saved as a file.
+ */
+function testResponse2(metadata, response) {
+ response.setHeader("Content-Type", "text/html", false);
+ response.setHeader("Content-Encoding", "gzip", false);
+ response.setHeader("Content-Disposition", "attachment", false);
+
+ var bos = Cc["@mozilla.org/binaryoutputstream;1"].createInstance(Ci.nsIBinaryOutputStream);
+ bos.setOutputStream(response.bodyOutputStream);
+ bos.writeByteArray(responseBody, responseBody.length);
+}
+
+function finishTest2(subject, topic, data) {
+ let file = subject.QueryInterface(Ci.nsIDownload).targetFile;
+ do_check_true(file.path.search("test2.gz") != 0);
+ let fis = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(Ci.nsIFileInputStream);
+ fis.init(file, -1, -1, 0);
+ let bis = Cc["@mozilla.org/binaryinputstream;1"].createInstance(Ci.nsIBinaryInputStream);
+ bis.setInputStream(fis);
+ let str = bis.readByteArray(bis.available());
+ do_check_matches(str, responseBody);
+}
+
+function testResponse3(metadata, response) {
+ response.setHeader("Content-Type", "text/html", false);
+ response.setHeader("Content-Encoding", "gzip", false);
+ response.setHeader("Content-Disposition", "attachment", false);
+
+ var bos = Cc["@mozilla.org/binaryoutputstream;1"].createInstance(Ci.nsIBinaryOutputStream);
+ bos.setOutputStream(response.bodyOutputStream);
+ bos.writeByteArray(responseBody, responseBody.length);
+}
+
+function finishTest3(subject, topic, data) {
+ let file = subject.QueryInterface(Ci.nsIDownload).targetFile;
+ do_check_true(file.path.search("test3.txt") != 0);
+ let fis = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(Ci.nsIFileInputStream);
+ fis.init(file, -1, -1, 0);
+ let bis = Cc["@mozilla.org/binaryinputstream;1"].createInstance(Ci.nsIBinaryInputStream);
+ bis.setInputStream(fis);
+ let str = bis.readByteArray(bis.available());
+ let decodedBody = [ 116, 101, 115, 116, 10 ]; // 't','e','s','t','\n'
+ do_check_matches(str, decodedBody);
+}
+
+var tests = [
+ [ "/test1.gz", testResponse1, finishTest1 ],
+ [ "/test2.gz", testResponse2, finishTest2 ],
+ [ "/test3.txt", testResponse3, finishTest3 ],
+];
+
+function run_test() {
+// do_load_child_test_harness();
+ httpserver = new HttpServer();
+ httpserver.start(4444);
+ do_test_pending();
+
+ initChildTestEnv();
+
+ for (let set of tests)
+ httpserver.registerPathHandler(set[0], set[1]);
+
+ runNextTest();
+}
diff --git a/uriloader/exthandler/tests/unit_ipc/xpcshell.ini b/uriloader/exthandler/tests/unit_ipc/xpcshell.ini
new file mode 100644
index 0000000000..962b93decd
--- /dev/null
+++ b/uriloader/exthandler/tests/unit_ipc/xpcshell.ini
@@ -0,0 +1,8 @@
+[DEFAULT]
+head =
+tail =
+
+[test_encoding.js]
+# Bug 676995: test hangs consistently on Android
+# Bug 907732: thunderbird still uses legacy downloads manager.
+skip-if = (os == "android" || buildapp == '../mail')
diff --git a/uriloader/exthandler/uikit/nsLocalHandlerAppUIKit.h b/uriloader/exthandler/uikit/nsLocalHandlerAppUIKit.h
new file mode 100644
index 0000000000..9d5ef31da1
--- /dev/null
+++ b/uriloader/exthandler/uikit/nsLocalHandlerAppUIKit.h
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim:expandtab:shiftwidth=2:tabstop=2:cin:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nslocalhandlerappuikit_h_
+#define nslocalhandlerappuikit_h_
+
+#include "nsLocalHandlerApp.h"
+
+class nsLocalHandlerAppUIKit final : public nsLocalHandlerApp
+{
+public:
+ nsLocalHandlerAppUIKit()
+ {}
+ ~nsLocalHandlerAppUIKit()
+ {}
+
+ nsLocalHandlerAppUIKit(const char16_t* aName, nsIFile* aExecutable)
+ : nsLocalHandlerApp(aName, aExecutable)
+ {}
+
+ nsLocalHandlerAppUIKit(const nsAString& aName, nsIFile* aExecutable)
+ : nsLocalHandlerApp(aName, aExecutable)
+ {}
+
+
+ NS_IMETHOD LaunchWithURI(nsIURI* aURI, nsIInterfaceRequestor* aWindowContext);
+};
+
+#endif /* nslocalhandlerappuikit_h_ */
diff --git a/uriloader/exthandler/uikit/nsLocalHandlerAppUIKit.mm b/uriloader/exthandler/uikit/nsLocalHandlerAppUIKit.mm
new file mode 100644
index 0000000000..afa600721e
--- /dev/null
+++ b/uriloader/exthandler/uikit/nsLocalHandlerAppUIKit.mm
@@ -0,0 +1,17 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim:expandtab:shiftwidth=2:tabstop=2:cin:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#import <CoreFoundation/CoreFoundation.h>
+
+#include "nsLocalHandlerAppUIKit.h"
+#include "nsIURI.h"
+
+NS_IMETHODIMP
+nsLocalHandlerAppUIKit::LaunchWithURI(nsIURI* aURI,
+ nsIInterfaceRequestor* aWindowContext)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
diff --git a/uriloader/exthandler/uikit/nsMIMEInfoUIKit.h b/uriloader/exthandler/uikit/nsMIMEInfoUIKit.h
new file mode 100644
index 0000000000..3f6bc8b42f
--- /dev/null
+++ b/uriloader/exthandler/uikit/nsMIMEInfoUIKit.h
@@ -0,0 +1,37 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim:expandtab:shiftwidth=2:tabstop=2:cin:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMIMEInfoUIKit_h_
+#define nsMIMEInfoUIKit_h_
+
+#include "nsMIMEInfoImpl.h"
+
+class nsMIMEInfoUIKit final : public nsMIMEInfoImpl
+{
+public:
+ explicit nsMIMEInfoUIKit(const nsACString& aMIMEType)
+ : nsMIMEInfoImpl(aMIMEType)
+ {}
+ nsMIMEInfoUIKit(const nsACString& aType, HandlerClass aClass)
+ : nsMIMEInfoImpl(aType, aClass)
+ {}
+
+ NS_IMETHOD LaunchWithFile(nsIFile* aFile);
+
+protected:
+ virtual nsresult LoadUriInternal(nsIURI* aURI);
+#ifdef DEBUG
+ virtual nsresult LaunchDefaultWithFile(nsIFile* aFile)
+ {
+ NS_NOTREACHED("do not call this method, use LaunchWithFile");
+ return NS_ERROR_UNEXPECTED;
+ }
+#endif
+ static nsresult OpenApplicationWithURI(nsIFile* aApplication,
+ const nsCString& aURI);
+};
+
+#endif
diff --git a/uriloader/exthandler/uikit/nsMIMEInfoUIKit.mm b/uriloader/exthandler/uikit/nsMIMEInfoUIKit.mm
new file mode 100644
index 0000000000..4b950dffc4
--- /dev/null
+++ b/uriloader/exthandler/uikit/nsMIMEInfoUIKit.mm
@@ -0,0 +1,20 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim:expandtab:shiftwidth=2:tabstop=2:cin:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMIMEInfoUIKit.h"
+#include "nsIFileURL.h"
+
+NS_IMETHODIMP
+nsMIMEInfoUIKit::LaunchWithFile(nsIFile* aFile)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult
+nsMIMEInfoUIKit::LoadUriInternal(nsIURI* aURI)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
diff --git a/uriloader/exthandler/uikit/nsOSHelperAppService.h b/uriloader/exthandler/uikit/nsOSHelperAppService.h
new file mode 100644
index 0000000000..1f33be0fba
--- /dev/null
+++ b/uriloader/exthandler/uikit/nsOSHelperAppService.h
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim:expandtab:shiftwidth=2:tabstop=2:cin:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsOSHelperAppService_h__
+#define nsOSHelperAppService_h__
+
+// The OS helper app service is a subclass of nsExternalHelperAppService and
+// is implemented on each platform. It contains platform specific code for
+// finding helper applications for a given mime type in addition to launching
+// those applications. This is the UIKit version.
+
+#include "nsExternalHelperAppService.h"
+#include "nsCExternalHandlerService.h"
+#include "nsCOMPtr.h"
+
+class nsOSHelperAppService final : public nsExternalHelperAppService
+{
+public:
+ nsOSHelperAppService();
+ ~nsOSHelperAppService();
+
+ // override nsIExternalProtocolService methods
+ NS_IMETHOD GetApplicationDescription(const nsACString& aScheme,
+ nsAString& _retval);
+
+ // method overrides --> used to hook the mime service into internet config....
+ NS_IMETHOD GetFromTypeAndExtension(const nsACString& aType,
+ const nsACString& aFileExt,
+ nsIMIMEInfo** aMIMEInfo);
+ already_AddRefed<nsIMIMEInfo> GetMIMEInfoFromOS(const nsACString& aMIMEType,
+ const nsACString& aFileExt,
+ bool* aFound);
+ NS_IMETHOD GetProtocolHandlerInfoFromOS(const nsACString& aScheme,
+ bool* found,
+ nsIHandlerInfo** _retval);
+
+ // GetFileTokenForPath must be implemented by each platform.
+ // platformAppPath --> a platform specific path to an application that we got
+ // out of the rdf data source. This can be a mac file
+ // spec, a unix path or a windows path depending on the
+ // platform
+ // aFile --> an nsIFile representation of that platform application path.
+ virtual nsresult GetFileTokenForPath(const char16_t* platformAppPath,
+ nsIFile** aFile);
+
+ nsresult OSProtocolHandlerExists(const char* aScheme, bool* aHandlerExists);
+};
+
+#endif // nsOSHelperAppService_h__
diff --git a/uriloader/exthandler/uikit/nsOSHelperAppService.mm b/uriloader/exthandler/uikit/nsOSHelperAppService.mm
new file mode 100644
index 0000000000..dfee24b2db
--- /dev/null
+++ b/uriloader/exthandler/uikit/nsOSHelperAppService.mm
@@ -0,0 +1,64 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim:expandtab:shiftwidth=2:tabstop=2:cin:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsOSHelperAppService.h"
+
+nsOSHelperAppService::nsOSHelperAppService()
+ : nsExternalHelperAppService()
+{}
+
+nsOSHelperAppService::~nsOSHelperAppService()
+{}
+
+nsresult
+nsOSHelperAppService::OSProtocolHandlerExists(const char* aProtocolScheme,
+ bool* aHandlerExists)
+{
+ *aHandlerExists = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOSHelperAppService::GetApplicationDescription(const nsACString& aScheme,
+ nsAString& _retval)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+nsresult
+nsOSHelperAppService::GetFileTokenForPath(const char16_t* aPlatformAppPath,
+ nsIFile** aFile)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsOSHelperAppService::GetFromTypeAndExtension(const nsACString& aType,
+ const nsACString& aFileExt,
+ nsIMIMEInfo** aMIMEInfo)
+{
+ return nsExternalHelperAppService::GetFromTypeAndExtension(aType,
+ aFileExt,
+ aMIMEInfo);
+}
+
+already_AddRefed<nsIMIMEInfo>
+nsOSHelperAppService::GetMIMEInfoFromOS(const nsACString& aMIMEType,
+ const nsACString& aFileExt,
+ bool* aFound)
+{
+ *aFound = false;
+ return nullptr;
+}
+
+NS_IMETHODIMP
+nsOSHelperAppService::GetProtocolHandlerInfoFromOS(const nsACString& aScheme,
+ bool* found,
+ nsIHandlerInfo** _retval)
+{
+ *found = false;
+ return NS_OK;
+}
diff --git a/uriloader/exthandler/unix/nsExternalSharingAppService.h b/uriloader/exthandler/unix/nsExternalSharingAppService.h
new file mode 100644
index 0000000000..3b1794b5e8
--- /dev/null
+++ b/uriloader/exthandler/unix/nsExternalSharingAppService.h
@@ -0,0 +1,31 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NS_EXTERNAL_SHARING_APP_SERVICE_H
+#define NS_EXTERNAL_SHARING_APP_SERVICE_H
+
+#include "nsIExternalSharingAppService.h"
+#include "nsAutoPtr.h"
+
+class ShareUiInterface;
+
+#define NS_EXTERNALSHARINGAPPSERVICE_CID \
+ {0xea782c90, 0xc6ec, 0x11df, \
+ {0xbd, 0x08, 0x00, 0x20, 0x0c, 0x9a, 0x66, 0x74}}
+
+class nsExternalSharingAppService : public nsIExternalSharingAppService
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIEXTERNALSHARINGAPPSERVICE
+
+ nsExternalSharingAppService();
+
+private:
+ ~nsExternalSharingAppService();
+
+protected:
+ nsAutoPtr<ShareUiInterface> mShareUi;
+};
+#endif
diff --git a/uriloader/exthandler/unix/nsGNOMERegistry.cpp b/uriloader/exthandler/unix/nsGNOMERegistry.cpp
new file mode 100644
index 0000000000..bf71ae913c
--- /dev/null
+++ b/uriloader/exthandler/unix/nsGNOMERegistry.cpp
@@ -0,0 +1,109 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsGNOMERegistry.h"
+#include "nsString.h"
+#include "nsIComponentManager.h"
+#include "nsMIMEInfoUnix.h"
+#include "nsAutoPtr.h"
+#include "nsIGIOService.h"
+
+/* static */ bool
+nsGNOMERegistry::HandlerExists(const char *aProtocolScheme)
+{
+ nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID);
+ if (!giovfs) {
+ return false;
+ }
+
+ nsCOMPtr<nsIGIOMimeApp> app;
+ return NS_SUCCEEDED(giovfs->GetAppForURIScheme(nsDependentCString(aProtocolScheme),
+ getter_AddRefs(app)));
+}
+
+// XXX Check HandlerExists() before calling LoadURL.
+
+/* static */ nsresult
+nsGNOMERegistry::LoadURL(nsIURI *aURL)
+{
+ nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID);
+ if (!giovfs) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return giovfs->ShowURI(aURL);
+}
+
+/* static */ void
+nsGNOMERegistry::GetAppDescForScheme(const nsACString& aScheme,
+ nsAString& aDesc)
+{
+ nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID);
+ if (!giovfs)
+ return;
+
+ nsAutoCString name;
+ nsCOMPtr<nsIGIOMimeApp> app;
+ if (NS_FAILED(giovfs->GetAppForURIScheme(aScheme, getter_AddRefs(app))))
+ return;
+
+ app->GetName(name);
+
+ CopyUTF8toUTF16(name, aDesc);
+}
+
+
+/* static */ already_AddRefed<nsMIMEInfoBase>
+nsGNOMERegistry::GetFromExtension(const nsACString& aFileExt)
+{
+ nsAutoCString mimeType;
+ nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID);
+ if (!giovfs) {
+ return nullptr;
+ }
+
+ // Get the MIME type from the extension, then call GetFromType to
+ // fill in the MIMEInfo.
+ if (NS_FAILED(giovfs->GetMimeTypeFromExtension(aFileExt, mimeType)) ||
+ mimeType.EqualsLiteral("application/octet-stream")) {
+ return nullptr;
+ }
+
+ RefPtr<nsMIMEInfoBase> mi = GetFromType(mimeType);
+ if (mi) {
+ mi->AppendExtension(aFileExt);
+ }
+
+ return mi.forget();
+}
+
+/* static */ already_AddRefed<nsMIMEInfoBase>
+nsGNOMERegistry::GetFromType(const nsACString& aMIMEType)
+{
+ RefPtr<nsMIMEInfoUnix> mimeInfo = new nsMIMEInfoUnix(aMIMEType);
+ NS_ENSURE_TRUE(mimeInfo, nullptr);
+
+ nsAutoCString name;
+ nsAutoCString description;
+
+ nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID);
+ if (!giovfs) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIGIOMimeApp> gioHandlerApp;
+ if (NS_FAILED(giovfs->GetAppForMimeType(aMIMEType, getter_AddRefs(gioHandlerApp))) ||
+ !gioHandlerApp) {
+ return nullptr;
+ }
+ gioHandlerApp->GetName(name);
+ giovfs->GetDescriptionForMimeType(aMIMEType, description);
+
+ mimeInfo->SetDefaultDescription(NS_ConvertUTF8toUTF16(name));
+ mimeInfo->SetPreferredAction(nsIMIMEInfo::useSystemDefault);
+ mimeInfo->SetDescription(NS_ConvertUTF8toUTF16(description));
+
+ return mimeInfo.forget();
+}
diff --git a/uriloader/exthandler/unix/nsGNOMERegistry.h b/uriloader/exthandler/unix/nsGNOMERegistry.h
new file mode 100644
index 0000000000..fe42ee622f
--- /dev/null
+++ b/uriloader/exthandler/unix/nsGNOMERegistry.h
@@ -0,0 +1,28 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsGNOMERegistry_h
+#define nsGNOMERegistry_h
+
+#include "nsIURI.h"
+#include "nsCOMPtr.h"
+
+class nsMIMEInfoBase;
+
+class nsGNOMERegistry
+{
+ public:
+ static bool HandlerExists(const char *aProtocolScheme);
+
+ static nsresult LoadURL(nsIURI *aURL);
+
+ static void GetAppDescForScheme(const nsACString& aScheme,
+ nsAString& aDesc);
+
+ static already_AddRefed<nsMIMEInfoBase> GetFromExtension(const nsACString& aFileExt);
+
+ static already_AddRefed<nsMIMEInfoBase> GetFromType(const nsACString& aMIMEType);
+};
+
+#endif // nsGNOMERegistry_h
diff --git a/uriloader/exthandler/unix/nsMIMEInfoUnix.cpp b/uriloader/exthandler/unix/nsMIMEInfoUnix.cpp
new file mode 100644
index 0000000000..e516608873
--- /dev/null
+++ b/uriloader/exthandler/unix/nsMIMEInfoUnix.cpp
@@ -0,0 +1,136 @@
+/* -*- Mode: C++; tab-width: 3; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMIMEInfoUnix.h"
+#include "nsGNOMERegistry.h"
+#include "nsIGIOService.h"
+#include "nsNetCID.h"
+#include "nsIIOService.h"
+#include "nsAutoPtr.h"
+#ifdef MOZ_ENABLE_DBUS
+#include "nsDBusHandlerApp.h"
+#endif
+
+nsresult
+nsMIMEInfoUnix::LoadUriInternal(nsIURI * aURI)
+{
+ return nsGNOMERegistry::LoadURL(aURI);
+}
+
+NS_IMETHODIMP
+nsMIMEInfoUnix::GetHasDefaultHandler(bool *_retval)
+{
+ // if mDefaultApplication is set, it means the application has been set from
+ // either /etc/mailcap or ${HOME}/.mailcap, in which case we don't want to
+ // give the GNOME answer.
+ if (mDefaultApplication)
+ return nsMIMEInfoImpl::GetHasDefaultHandler(_retval);
+
+ *_retval = false;
+
+ if (mClass == eProtocolInfo) {
+ *_retval = nsGNOMERegistry::HandlerExists(mSchemeOrType.get());
+ } else {
+ RefPtr<nsMIMEInfoBase> mimeInfo = nsGNOMERegistry::GetFromType(mSchemeOrType);
+ if (!mimeInfo) {
+ nsAutoCString ext;
+ nsresult rv = GetPrimaryExtension(ext);
+ if (NS_SUCCEEDED(rv)) {
+ mimeInfo = nsGNOMERegistry::GetFromExtension(ext);
+ }
+ }
+ if (mimeInfo)
+ *_retval = true;
+ }
+
+ if (*_retval)
+ return NS_OK;
+
+#if defined(MOZ_ENABLE_CONTENTACTION)
+ ContentAction::Action action =
+ ContentAction::Action::defaultActionForFile(QUrl(), QString(mSchemeOrType.get()));
+ if (action.isValid()) {
+ *_retval = true;
+ return NS_OK;
+ }
+#endif
+
+ return NS_OK;
+}
+
+nsresult
+nsMIMEInfoUnix::LaunchDefaultWithFile(nsIFile *aFile)
+{
+ // if mDefaultApplication is set, it means the application has been set from
+ // either /etc/mailcap or ${HOME}/.mailcap, in which case we don't want to
+ // give the GNOME answer.
+ if (mDefaultApplication)
+ return nsMIMEInfoImpl::LaunchDefaultWithFile(aFile);
+
+ nsAutoCString nativePath;
+ aFile->GetNativePath(nativePath);
+
+#if defined(MOZ_ENABLE_CONTENTACTION)
+ QUrl uri = QUrl::fromLocalFile(QString::fromUtf8(nativePath.get()));
+ ContentAction::Action action =
+ ContentAction::Action::defaultActionForFile(uri, QString(mSchemeOrType.get()));
+ if (action.isValid()) {
+ action.trigger();
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+#endif
+
+ nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID);
+ if (!giovfs) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // nsGIOMimeApp->Launch wants a URI string instead of local file
+ nsresult rv;
+ nsCOMPtr<nsIIOService> ioservice = do_GetService(NS_IOSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIURI> uri;
+ rv = ioservice->NewFileURI(aFile, getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsAutoCString uriSpec;
+ uri->GetSpec(uriSpec);
+
+ nsCOMPtr<nsIGIOMimeApp> app;
+ if (NS_FAILED(giovfs->GetAppForMimeType(mSchemeOrType, getter_AddRefs(app))) || !app) {
+ return NS_ERROR_FILE_NOT_FOUND;
+ }
+
+ return app->Launch(uriSpec);
+}
+
+#if defined(MOZ_ENABLE_CONTENTACTION)
+NS_IMETHODIMP
+nsMIMEInfoUnix::GetPossibleApplicationHandlers(nsIMutableArray ** aPossibleAppHandlers)
+{
+ if (!mPossibleApplications) {
+ mPossibleApplications = do_CreateInstance(NS_ARRAY_CONTRACTID);
+
+ if (!mPossibleApplications)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ QList<ContentAction::Action> actions =
+ ContentAction::Action::actionsForFile(QUrl(), QString(mSchemeOrType.get()));
+
+ for (int i = 0; i < actions.size(); ++i) {
+ nsContentHandlerApp* app =
+ new nsContentHandlerApp(nsString((char16_t*)actions[i].name().data()),
+ mSchemeOrType, actions[i]);
+ mPossibleApplications->AppendElement(app, false);
+ }
+ }
+
+ *aPossibleAppHandlers = mPossibleApplications;
+ NS_ADDREF(*aPossibleAppHandlers);
+ return NS_OK;
+}
+#endif
+
diff --git a/uriloader/exthandler/unix/nsMIMEInfoUnix.h b/uriloader/exthandler/unix/nsMIMEInfoUnix.h
new file mode 100644
index 0000000000..65f6d996d4
--- /dev/null
+++ b/uriloader/exthandler/unix/nsMIMEInfoUnix.h
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 3; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMIMEInfoUnix_h_
+#define nsMIMEInfoUnix_h_
+
+#include "nsMIMEInfoImpl.h"
+
+class nsMIMEInfoUnix : public nsMIMEInfoImpl
+{
+public:
+ explicit nsMIMEInfoUnix(const char *aMIMEType = "") : nsMIMEInfoImpl(aMIMEType) {}
+ explicit nsMIMEInfoUnix(const nsACString& aMIMEType) : nsMIMEInfoImpl(aMIMEType) {}
+ nsMIMEInfoUnix(const nsACString& aType, HandlerClass aClass) :
+ nsMIMEInfoImpl(aType, aClass) {}
+ static bool HandlerExists(const char *aProtocolScheme);
+
+protected:
+ NS_IMETHOD GetHasDefaultHandler(bool *_retval);
+
+ virtual nsresult LoadUriInternal(nsIURI *aURI);
+
+ virtual nsresult LaunchDefaultWithFile(nsIFile *aFile);
+#if defined(MOZ_ENABLE_CONTENTACTION)
+ NS_IMETHOD GetPossibleApplicationHandlers(nsIMutableArray * *aPossibleAppHandlers);
+#endif
+};
+
+#endif // nsMIMEInfoUnix_h_
diff --git a/uriloader/exthandler/unix/nsOSHelperAppService.cpp b/uriloader/exthandler/unix/nsOSHelperAppService.cpp
new file mode 100644
index 0000000000..84e1cf5b04
--- /dev/null
+++ b/uriloader/exthandler/unix/nsOSHelperAppService.cpp
@@ -0,0 +1,1537 @@
+/* -*- Mode: C++; tab-width: 3; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#if defined(MOZ_ENABLE_CONTENTACTION)
+#include <contentaction/contentaction.h>
+#include <QString>
+#endif
+
+#include "nsOSHelperAppService.h"
+#include "nsMIMEInfoUnix.h"
+#ifdef MOZ_WIDGET_GTK
+#include "nsGNOMERegistry.h"
+#endif
+#include "nsISupports.h"
+#include "nsString.h"
+#include "nsReadableUtils.h"
+#include "nsUnicharUtils.h"
+#include "nsXPIDLString.h"
+#include "nsIURL.h"
+#include "nsIFileStreams.h"
+#include "nsILineInputStream.h"
+#include "nsIFile.h"
+#include "nsIProcess.h"
+#include "nsNetCID.h"
+#include "nsXPCOM.h"
+#include "nsISupportsPrimitives.h"
+#include "nsCRT.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsDirectoryServiceUtils.h"
+#include "prenv.h" // for PR_GetEnv()
+#include "nsAutoPtr.h"
+#include "mozilla/Preferences.h"
+
+using namespace mozilla;
+
+#define LOG(args) MOZ_LOG(mLog, mozilla::LogLevel::Debug, args)
+#define LOG_ENABLED() MOZ_LOG_TEST(mLog, mozilla::LogLevel::Debug)
+
+static nsresult
+FindSemicolon(nsAString::const_iterator& aSemicolon_iter,
+ const nsAString::const_iterator& aEnd_iter);
+static nsresult
+ParseMIMEType(const nsAString::const_iterator& aStart_iter,
+ nsAString::const_iterator& aMajorTypeStart,
+ nsAString::const_iterator& aMajorTypeEnd,
+ nsAString::const_iterator& aMinorTypeStart,
+ nsAString::const_iterator& aMinorTypeEnd,
+ const nsAString::const_iterator& aEnd_iter);
+
+inline bool
+IsNetscapeFormat(const nsACString& aBuffer);
+
+nsOSHelperAppService::nsOSHelperAppService() : nsExternalHelperAppService()
+{
+ mode_t mask = umask(0777);
+ umask(mask);
+ mPermissions = 0666 & ~mask;
+}
+
+nsOSHelperAppService::~nsOSHelperAppService()
+{}
+
+/*
+ * Take a command with all the mailcap escapes in it and unescape it
+ * Ideally this needs the mime type, mime type options, and location of the
+ * temporary file, but this last can't be got from here
+ */
+// static
+nsresult
+nsOSHelperAppService::UnescapeCommand(const nsAString& aEscapedCommand,
+ const nsAString& aMajorType,
+ const nsAString& aMinorType,
+ nsACString& aUnEscapedCommand) {
+ LOG(("-- UnescapeCommand"));
+ LOG(("Command to escape: '%s'\n",
+ NS_LossyConvertUTF16toASCII(aEscapedCommand).get()));
+ // XXX This function will need to get the mime type and various stuff like that being passed in to work properly
+
+ LOG(("UnescapeCommand really needs some work -- it should actually do some unescaping\n"));
+
+ CopyUTF16toUTF8(aEscapedCommand, aUnEscapedCommand);
+ LOG(("Escaped command: '%s'\n",
+ PromiseFlatCString(aUnEscapedCommand).get()));
+ return NS_OK;
+}
+
+/* Put aSemicolon_iter at the first non-escaped semicolon after
+ * aStart_iter but before aEnd_iter
+ */
+
+static nsresult
+FindSemicolon(nsAString::const_iterator& aSemicolon_iter,
+ const nsAString::const_iterator& aEnd_iter) {
+ bool semicolonFound = false;
+ while (aSemicolon_iter != aEnd_iter && !semicolonFound) {
+ switch(*aSemicolon_iter) {
+ case '\\':
+ aSemicolon_iter.advance(2);
+ break;
+ case ';':
+ semicolonFound = true;
+ break;
+ default:
+ ++aSemicolon_iter;
+ break;
+ }
+ }
+ return NS_OK;
+}
+
+static nsresult
+ParseMIMEType(const nsAString::const_iterator& aStart_iter,
+ nsAString::const_iterator& aMajorTypeStart,
+ nsAString::const_iterator& aMajorTypeEnd,
+ nsAString::const_iterator& aMinorTypeStart,
+ nsAString::const_iterator& aMinorTypeEnd,
+ const nsAString::const_iterator& aEnd_iter) {
+ nsAString::const_iterator iter(aStart_iter);
+
+ // skip leading whitespace
+ while (iter != aEnd_iter && nsCRT::IsAsciiSpace(*iter)) {
+ ++iter;
+ }
+
+ if (iter == aEnd_iter) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ aMajorTypeStart = iter;
+
+ // find major/minor separator ('/')
+ while (iter != aEnd_iter && *iter != '/') {
+ ++iter;
+ }
+
+ if (iter == aEnd_iter) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ aMajorTypeEnd = iter;
+
+ // skip '/'
+ ++iter;
+
+ if (iter == aEnd_iter) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ aMinorTypeStart = iter;
+
+ // find end of minor type, delimited by whitespace or ';'
+ while (iter != aEnd_iter && !nsCRT::IsAsciiSpace(*iter) && *iter != ';') {
+ ++iter;
+ }
+
+ aMinorTypeEnd = iter;
+
+ return NS_OK;
+}
+
+// static
+nsresult
+nsOSHelperAppService::GetFileLocation(const char* aPrefName,
+ const char* aEnvVarName,
+ nsAString& aFileLocation) {
+ LOG(("-- GetFileLocation. Pref: '%s' EnvVar: '%s'\n",
+ aPrefName,
+ aEnvVarName));
+ NS_PRECONDITION(aPrefName, "Null pref name passed; don't do that!");
+
+ aFileLocation.Truncate();
+ /* The lookup order is:
+ 1) user pref
+ 2) env var
+ 3) pref
+ */
+ NS_ENSURE_TRUE(Preferences::GetRootBranch(), NS_ERROR_FAILURE);
+
+ /*
+ If we have an env var we should check whether the pref is a user
+ pref. If we do not, we don't care.
+ */
+ if (Preferences::HasUserValue(aPrefName) &&
+ NS_SUCCEEDED(Preferences::GetString(aPrefName, &aFileLocation))) {
+ return NS_OK;
+ }
+
+ if (aEnvVarName && *aEnvVarName) {
+ char* prefValue = PR_GetEnv(aEnvVarName);
+ if (prefValue && *prefValue) {
+ // the pref is in the system charset and it's a filepath... The
+ // natural way to do the charset conversion is by just initing
+ // an nsIFile with the native path and asking it for the Unicode
+ // version.
+ nsresult rv;
+ nsCOMPtr<nsIFile> file(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = file->InitWithNativePath(nsDependentCString(prefValue));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = file->GetPath(aFileLocation);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+ }
+ }
+
+ return Preferences::GetString(aPrefName, &aFileLocation);
+}
+
+
+/* Get the mime.types file names from prefs and look up info in them
+ based on extension */
+// static
+nsresult
+nsOSHelperAppService::LookUpTypeAndDescription(const nsAString& aFileExtension,
+ nsAString& aMajorType,
+ nsAString& aMinorType,
+ nsAString& aDescription,
+ bool aUserData) {
+ LOG(("-- LookUpTypeAndDescription for extension '%s'\n",
+ NS_LossyConvertUTF16toASCII(aFileExtension).get()));
+ nsresult rv = NS_OK;
+ nsAutoString mimeFileName;
+
+ const char* filenamePref = aUserData ?
+ "helpers.private_mime_types_file" : "helpers.global_mime_types_file";
+
+ rv = GetFileLocation(filenamePref, nullptr, mimeFileName);
+ if (NS_SUCCEEDED(rv) && !mimeFileName.IsEmpty()) {
+ rv = GetTypeAndDescriptionFromMimetypesFile(mimeFileName,
+ aFileExtension,
+ aMajorType,
+ aMinorType,
+ aDescription);
+ } else {
+ rv = NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return rv;
+}
+
+inline bool
+IsNetscapeFormat(const nsACString& aBuffer) {
+ return StringBeginsWith(aBuffer, NS_LITERAL_CSTRING("#--Netscape Communications Corporation MIME Information")) ||
+ StringBeginsWith(aBuffer, NS_LITERAL_CSTRING("#--MCOM MIME Information"));
+}
+
+/*
+ * Create a file stream and line input stream for the filename.
+ * Leaves the first line of the file in aBuffer and sets the format to
+ * true for netscape files and false for normail ones
+ */
+// static
+nsresult
+nsOSHelperAppService::CreateInputStream(const nsAString& aFilename,
+ nsIFileInputStream ** aFileInputStream,
+ nsILineInputStream ** aLineInputStream,
+ nsACString& aBuffer,
+ bool * aNetscapeFormat,
+ bool * aMore) {
+ LOG(("-- CreateInputStream"));
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsIFile> file(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv));
+ if (NS_FAILED(rv))
+ return rv;
+ rv = file->InitWithPath(aFilename);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIFileInputStream> fileStream(do_CreateInstance(NS_LOCALFILEINPUTSTREAM_CONTRACTID, &rv));
+ if (NS_FAILED(rv))
+ return rv;
+ rv = fileStream->Init(file, -1, -1, false);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsILineInputStream> lineStream(do_QueryInterface(fileStream, &rv));
+
+ if (NS_FAILED(rv)) {
+ LOG(("Interface trouble in stream land!"));
+ return rv;
+ }
+
+ rv = lineStream->ReadLine(aBuffer, aMore);
+ if (NS_FAILED(rv)) {
+ fileStream->Close();
+ return rv;
+ }
+
+ *aNetscapeFormat = IsNetscapeFormat(aBuffer);
+
+ *aFileInputStream = fileStream;
+ NS_ADDREF(*aFileInputStream);
+ *aLineInputStream = lineStream;
+ NS_ADDREF(*aLineInputStream);
+
+ return NS_OK;
+}
+
+/* Open the file, read the first line, decide what type of file it is,
+ then get info based on extension */
+// static
+nsresult
+nsOSHelperAppService::GetTypeAndDescriptionFromMimetypesFile(const nsAString& aFilename,
+ const nsAString& aFileExtension,
+ nsAString& aMajorType,
+ nsAString& aMinorType,
+ nsAString& aDescription) {
+ LOG(("-- GetTypeAndDescriptionFromMimetypesFile\n"));
+ LOG(("Getting type and description from types file '%s'\n",
+ NS_LossyConvertUTF16toASCII(aFilename).get()));
+ LOG(("Using extension '%s'\n",
+ NS_LossyConvertUTF16toASCII(aFileExtension).get()));
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIFileInputStream> mimeFile;
+ nsCOMPtr<nsILineInputStream> mimeTypes;
+ bool netscapeFormat;
+ nsAutoString buf;
+ nsAutoCString cBuf;
+ bool more = false;
+ rv = CreateInputStream(aFilename, getter_AddRefs(mimeFile), getter_AddRefs(mimeTypes),
+ cBuf, &netscapeFormat, &more);
+
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsAutoString extensions;
+ nsString entry;
+ entry.SetCapacity(100);
+ nsAString::const_iterator majorTypeStart, majorTypeEnd,
+ minorTypeStart, minorTypeEnd,
+ descriptionStart, descriptionEnd;
+
+ do {
+ CopyASCIItoUTF16(cBuf, buf);
+ // read through, building up an entry. If we finish an entry, check for
+ // a match and return out of the loop if we match
+
+ // skip comments and empty lines
+ if (!buf.IsEmpty() && buf.First() != '#') {
+ entry.Append(buf);
+ if (entry.Last() == '\\') {
+ entry.Truncate(entry.Length() - 1);
+ entry.Append(char16_t(' ')); // in case there is no trailing whitespace on this line
+ } else { // we have a full entry
+ LOG(("Current entry: '%s'\n",
+ NS_LossyConvertUTF16toASCII(entry).get()));
+ if (netscapeFormat) {
+ rv = ParseNetscapeMIMETypesEntry(entry,
+ majorTypeStart, majorTypeEnd,
+ minorTypeStart, minorTypeEnd,
+ extensions,
+ descriptionStart, descriptionEnd);
+ if (NS_FAILED(rv)) {
+ // We sometimes get things like RealPlayer appending
+ // "normal" entries to "Netscape" .mime.types files. Try
+ // to handle that. Bug 106381.
+ LOG(("Bogus entry; trying 'normal' mode\n"));
+ rv = ParseNormalMIMETypesEntry(entry,
+ majorTypeStart, majorTypeEnd,
+ minorTypeStart, minorTypeEnd,
+ extensions,
+ descriptionStart, descriptionEnd);
+ }
+ } else {
+ rv = ParseNormalMIMETypesEntry(entry,
+ majorTypeStart, majorTypeEnd,
+ minorTypeStart, minorTypeEnd,
+ extensions,
+ descriptionStart, descriptionEnd);
+ if (NS_FAILED(rv)) {
+ // We sometimes get things like StarOffice prepending
+ // "normal" entries to "Netscape" .mime.types files. Try
+ // to handle that. Bug 136670.
+ LOG(("Bogus entry; trying 'Netscape' mode\n"));
+ rv = ParseNetscapeMIMETypesEntry(entry,
+ majorTypeStart, majorTypeEnd,
+ minorTypeStart, minorTypeEnd,
+ extensions,
+ descriptionStart, descriptionEnd);
+ }
+ }
+
+ if (NS_SUCCEEDED(rv)) { // entry parses
+ nsAString::const_iterator start, end;
+ extensions.BeginReading(start);
+ extensions.EndReading(end);
+ nsAString::const_iterator iter(start);
+
+ while (start != end) {
+ FindCharInReadable(',', iter, end);
+ if (Substring(start, iter).Equals(aFileExtension,
+ nsCaseInsensitiveStringComparator())) {
+ // it's a match. Assign the type and description and run
+ aMajorType.Assign(Substring(majorTypeStart, majorTypeEnd));
+ aMinorType.Assign(Substring(minorTypeStart, minorTypeEnd));
+ aDescription.Assign(Substring(descriptionStart, descriptionEnd));
+ mimeFile->Close();
+ return NS_OK;
+ }
+ if (iter != end) {
+ ++iter;
+ }
+ start = iter;
+ }
+ } else {
+ LOG(("Failed to parse entry: %s\n", NS_LossyConvertUTF16toASCII(entry).get()));
+ }
+ // truncate the entry for the next iteration
+ entry.Truncate();
+ }
+ }
+ if (!more) {
+ rv = NS_ERROR_NOT_AVAILABLE;
+ break;
+ }
+ // read the next line
+ rv = mimeTypes->ReadLine(cBuf, &more);
+ } while (NS_SUCCEEDED(rv));
+
+ mimeFile->Close();
+ return rv;
+}
+
+/* Get the mime.types file names from prefs and look up info in them
+ based on mimetype */
+// static
+nsresult
+nsOSHelperAppService::LookUpExtensionsAndDescription(const nsAString& aMajorType,
+ const nsAString& aMinorType,
+ nsAString& aFileExtensions,
+ nsAString& aDescription) {
+ LOG(("-- LookUpExtensionsAndDescription for type '%s/%s'\n",
+ NS_LossyConvertUTF16toASCII(aMajorType).get(),
+ NS_LossyConvertUTF16toASCII(aMinorType).get()));
+ nsresult rv = NS_OK;
+ nsAutoString mimeFileName;
+
+ rv = GetFileLocation("helpers.private_mime_types_file", nullptr, mimeFileName);
+ if (NS_SUCCEEDED(rv) && !mimeFileName.IsEmpty()) {
+ rv = GetExtensionsAndDescriptionFromMimetypesFile(mimeFileName,
+ aMajorType,
+ aMinorType,
+ aFileExtensions,
+ aDescription);
+ } else {
+ rv = NS_ERROR_NOT_AVAILABLE;
+ }
+ if (NS_FAILED(rv) || aFileExtensions.IsEmpty()) {
+ rv = GetFileLocation("helpers.global_mime_types_file",
+ nullptr, mimeFileName);
+ if (NS_SUCCEEDED(rv) && !mimeFileName.IsEmpty()) {
+ rv = GetExtensionsAndDescriptionFromMimetypesFile(mimeFileName,
+ aMajorType,
+ aMinorType,
+ aFileExtensions,
+ aDescription);
+ } else {
+ rv = NS_ERROR_NOT_AVAILABLE;
+ }
+ }
+ return rv;
+}
+
+/* Open the file, read the first line, decide what type of file it is,
+ then get info based on extension */
+// static
+nsresult
+nsOSHelperAppService::GetExtensionsAndDescriptionFromMimetypesFile(const nsAString& aFilename,
+ const nsAString& aMajorType,
+ const nsAString& aMinorType,
+ nsAString& aFileExtensions,
+ nsAString& aDescription) {
+ LOG(("-- GetExtensionsAndDescriptionFromMimetypesFile\n"));
+ LOG(("Getting extensions and description from types file '%s'\n",
+ NS_LossyConvertUTF16toASCII(aFilename).get()));
+ LOG(("Using type '%s/%s'\n",
+ NS_LossyConvertUTF16toASCII(aMajorType).get(),
+ NS_LossyConvertUTF16toASCII(aMinorType).get()));
+
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIFileInputStream> mimeFile;
+ nsCOMPtr<nsILineInputStream> mimeTypes;
+ bool netscapeFormat;
+ nsAutoCString cBuf;
+ nsAutoString buf;
+ bool more = false;
+ rv = CreateInputStream(aFilename, getter_AddRefs(mimeFile), getter_AddRefs(mimeTypes),
+ cBuf, &netscapeFormat, &more);
+
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsAutoString extensions;
+ nsString entry;
+ entry.SetCapacity(100);
+ nsAString::const_iterator majorTypeStart, majorTypeEnd,
+ minorTypeStart, minorTypeEnd,
+ descriptionStart, descriptionEnd;
+
+ do {
+ CopyASCIItoUTF16(cBuf, buf);
+ // read through, building up an entry. If we finish an entry, check for
+ // a match and return out of the loop if we match
+
+ // skip comments and empty lines
+ if (!buf.IsEmpty() && buf.First() != '#') {
+ entry.Append(buf);
+ if (entry.Last() == '\\') {
+ entry.Truncate(entry.Length() - 1);
+ entry.Append(char16_t(' ')); // in case there is no trailing whitespace on this line
+ } else { // we have a full entry
+ LOG(("Current entry: '%s'\n",
+ NS_LossyConvertUTF16toASCII(entry).get()));
+ if (netscapeFormat) {
+ rv = ParseNetscapeMIMETypesEntry(entry,
+ majorTypeStart, majorTypeEnd,
+ minorTypeStart, minorTypeEnd,
+ extensions,
+ descriptionStart, descriptionEnd);
+
+ if (NS_FAILED(rv)) {
+ // We sometimes get things like RealPlayer appending
+ // "normal" entries to "Netscape" .mime.types files. Try
+ // to handle that. Bug 106381.
+ LOG(("Bogus entry; trying 'normal' mode\n"));
+ rv = ParseNormalMIMETypesEntry(entry,
+ majorTypeStart, majorTypeEnd,
+ minorTypeStart, minorTypeEnd,
+ extensions,
+ descriptionStart, descriptionEnd);
+ }
+ } else {
+ rv = ParseNormalMIMETypesEntry(entry,
+ majorTypeStart, majorTypeEnd,
+ minorTypeStart,
+ minorTypeEnd, extensions,
+ descriptionStart, descriptionEnd);
+
+ if (NS_FAILED(rv)) {
+ // We sometimes get things like StarOffice prepending
+ // "normal" entries to "Netscape" .mime.types files. Try
+ // to handle that. Bug 136670.
+ LOG(("Bogus entry; trying 'Netscape' mode\n"));
+ rv = ParseNetscapeMIMETypesEntry(entry,
+ majorTypeStart, majorTypeEnd,
+ minorTypeStart, minorTypeEnd,
+ extensions,
+ descriptionStart, descriptionEnd);
+ }
+ }
+
+ if (NS_SUCCEEDED(rv) &&
+ Substring(majorTypeStart,
+ majorTypeEnd).Equals(aMajorType,
+ nsCaseInsensitiveStringComparator())&&
+ Substring(minorTypeStart,
+ minorTypeEnd).Equals(aMinorType,
+ nsCaseInsensitiveStringComparator())) {
+ // it's a match
+ aFileExtensions.Assign(extensions);
+ aDescription.Assign(Substring(descriptionStart, descriptionEnd));
+ mimeFile->Close();
+ return NS_OK;
+ } else if (NS_FAILED(rv)) {
+ LOG(("Failed to parse entry: %s\n", NS_LossyConvertUTF16toASCII(entry).get()));
+ }
+
+ entry.Truncate();
+ }
+ }
+ if (!more) {
+ rv = NS_ERROR_NOT_AVAILABLE;
+ break;
+ }
+ // read the next line
+ rv = mimeTypes->ReadLine(cBuf, &more);
+ } while (NS_SUCCEEDED(rv));
+
+ mimeFile->Close();
+ return rv;
+}
+
+/*
+ * This parses a Netscape format mime.types entry. There are two
+ * possible formats:
+ *
+ * type=foo/bar; options exts="baz" description="Some type"
+ *
+ * and
+ *
+ * type=foo/bar; options description="Some type" exts="baz"
+ */
+// static
+nsresult
+nsOSHelperAppService::ParseNetscapeMIMETypesEntry(const nsAString& aEntry,
+ nsAString::const_iterator& aMajorTypeStart,
+ nsAString::const_iterator& aMajorTypeEnd,
+ nsAString::const_iterator& aMinorTypeStart,
+ nsAString::const_iterator& aMinorTypeEnd,
+ nsAString& aExtensions,
+ nsAString::const_iterator& aDescriptionStart,
+ nsAString::const_iterator& aDescriptionEnd) {
+ LOG(("-- ParseNetscapeMIMETypesEntry\n"));
+ NS_ASSERTION(!aEntry.IsEmpty(), "Empty Netscape MIME types entry being parsed.");
+
+ nsAString::const_iterator start_iter, end_iter, match_start, match_end;
+
+ aEntry.BeginReading(start_iter);
+ aEntry.EndReading(end_iter);
+
+ // skip trailing whitespace
+ do {
+ --end_iter;
+ } while (end_iter != start_iter &&
+ nsCRT::IsAsciiSpace(*end_iter));
+ // if we're pointing to a quote, don't advance -- we don't want to
+ // include the quote....
+ if (*end_iter != '"')
+ ++end_iter;
+ match_start = start_iter;
+ match_end = end_iter;
+
+ // Get the major and minor types
+ // First the major type
+ if (! FindInReadable(NS_LITERAL_STRING("type="), match_start, match_end)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ match_start = match_end;
+
+ while (match_end != end_iter &&
+ *match_end != '/') {
+ ++match_end;
+ }
+ if (match_end == end_iter) {
+ return NS_ERROR_FAILURE;
+ }
+
+ aMajorTypeStart = match_start;
+ aMajorTypeEnd = match_end;
+
+ // now the minor type
+ if (++match_end == end_iter) {
+ return NS_ERROR_FAILURE;
+ }
+
+ match_start = match_end;
+
+ while (match_end != end_iter &&
+ !nsCRT::IsAsciiSpace(*match_end) &&
+ *match_end != ';') {
+ ++match_end;
+ }
+ if (match_end == end_iter) {
+ return NS_ERROR_FAILURE;
+ }
+
+ aMinorTypeStart = match_start;
+ aMinorTypeEnd = match_end;
+
+ // ignore everything up to the end of the mime type from here on
+ start_iter = match_end;
+
+ // get the extensions
+ match_start = match_end;
+ match_end = end_iter;
+ if (FindInReadable(NS_LITERAL_STRING("exts="), match_start, match_end)) {
+ nsAString::const_iterator extStart, extEnd;
+
+ if (match_end == end_iter ||
+ (*match_end == '"' && ++match_end == end_iter)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ extStart = match_end;
+ match_start = extStart;
+ match_end = end_iter;
+ if (FindInReadable(NS_LITERAL_STRING("desc=\""), match_start, match_end)) {
+ // exts= before desc=, so we have to find the actual end of the extensions
+ extEnd = match_start;
+ if (extEnd == extStart) {
+ return NS_ERROR_FAILURE;
+ }
+
+ do {
+ --extEnd;
+ } while (extEnd != extStart &&
+ nsCRT::IsAsciiSpace(*extEnd));
+
+ if (extEnd != extStart && *extEnd == '"') {
+ --extEnd;
+ }
+ } else {
+ // desc= before exts=, so we can use end_iter as the end of the extensions
+ extEnd = end_iter;
+ }
+ aExtensions = Substring(extStart, extEnd);
+ } else {
+ // no extensions
+ aExtensions.Truncate();
+ }
+
+ // get the description
+ match_start = start_iter;
+ match_end = end_iter;
+ if (FindInReadable(NS_LITERAL_STRING("desc=\""), match_start, match_end)) {
+ aDescriptionStart = match_end;
+ match_start = aDescriptionStart;
+ match_end = end_iter;
+ if (FindInReadable(NS_LITERAL_STRING("exts="), match_start, match_end)) {
+ // exts= after desc=, so have to find actual end of description
+ aDescriptionEnd = match_start;
+ if (aDescriptionEnd == aDescriptionStart) {
+ return NS_ERROR_FAILURE;
+ }
+
+ do {
+ --aDescriptionEnd;
+ } while (aDescriptionEnd != aDescriptionStart &&
+ nsCRT::IsAsciiSpace(*aDescriptionEnd));
+
+ if (aDescriptionStart != aDescriptionStart && *aDescriptionEnd == '"') {
+ --aDescriptionEnd;
+ }
+ } else {
+ // desc= after exts=, so use end_iter for the description end
+ aDescriptionEnd = end_iter;
+ }
+ } else {
+ // no description
+ aDescriptionStart = start_iter;
+ aDescriptionEnd = start_iter;
+ }
+
+ return NS_OK;
+}
+
+/*
+ * This parses a normal format mime.types entry. The format is:
+ *
+ * major/minor ext1 ext2 ext3
+ */
+// static
+nsresult
+nsOSHelperAppService::ParseNormalMIMETypesEntry(const nsAString& aEntry,
+ nsAString::const_iterator& aMajorTypeStart,
+ nsAString::const_iterator& aMajorTypeEnd,
+ nsAString::const_iterator& aMinorTypeStart,
+ nsAString::const_iterator& aMinorTypeEnd,
+ nsAString& aExtensions,
+ nsAString::const_iterator& aDescriptionStart,
+ nsAString::const_iterator& aDescriptionEnd) {
+ LOG(("-- ParseNormalMIMETypesEntry\n"));
+ NS_ASSERTION(!aEntry.IsEmpty(), "Empty Normal MIME types entry being parsed.");
+
+ nsAString::const_iterator start_iter, end_iter, iter;
+
+ aEntry.BeginReading(start_iter);
+ aEntry.EndReading(end_iter);
+
+ // no description
+ aDescriptionStart = start_iter;
+ aDescriptionEnd = start_iter;
+
+ // skip leading whitespace
+ while (start_iter != end_iter && nsCRT::IsAsciiSpace(*start_iter)) {
+ ++start_iter;
+ }
+ if (start_iter == end_iter) {
+ return NS_ERROR_FAILURE;
+ }
+ // skip trailing whitespace
+ do {
+ --end_iter;
+ } while (end_iter != start_iter && nsCRT::IsAsciiSpace(*end_iter));
+
+ ++end_iter; // point to first whitespace char (or to end of string)
+ iter = start_iter;
+
+ // get the major type
+ if (! FindCharInReadable('/', iter, end_iter))
+ return NS_ERROR_FAILURE;
+
+ nsAString::const_iterator equals_sign_iter(start_iter);
+ if (FindCharInReadable('=', equals_sign_iter, iter))
+ return NS_ERROR_FAILURE; // see bug 136670
+
+ aMajorTypeStart = start_iter;
+ aMajorTypeEnd = iter;
+
+ // get the minor type
+ if (++iter == end_iter) {
+ return NS_ERROR_FAILURE;
+ }
+ start_iter = iter;
+
+ while (iter != end_iter && !nsCRT::IsAsciiSpace(*iter)) {
+ ++iter;
+ }
+ aMinorTypeStart = start_iter;
+ aMinorTypeEnd = iter;
+
+ // get the extensions
+ aExtensions.Truncate();
+ while (iter != end_iter) {
+ while (iter != end_iter && nsCRT::IsAsciiSpace(*iter)) {
+ ++iter;
+ }
+
+ start_iter = iter;
+ while (iter != end_iter && !nsCRT::IsAsciiSpace(*iter)) {
+ ++iter;
+ }
+ aExtensions.Append(Substring(start_iter, iter));
+ if (iter != end_iter) { // not the last extension
+ aExtensions.Append(char16_t(','));
+ }
+ }
+
+ return NS_OK;
+}
+
+// static
+nsresult
+nsOSHelperAppService::LookUpHandlerAndDescription(const nsAString& aMajorType,
+ const nsAString& aMinorType,
+ nsAString& aHandler,
+ nsAString& aDescription,
+ nsAString& aMozillaFlags) {
+
+ // The mailcap lookup is two-pass to handle the case of mailcap files
+ // that have something like:
+ //
+ // text/*; emacs %s
+ // text/rtf; soffice %s
+ //
+ // in that order. We want to pick up "soffice" for text/rtf in such cases
+ nsresult rv = DoLookUpHandlerAndDescription(aMajorType,
+ aMinorType,
+ aHandler,
+ aDescription,
+ aMozillaFlags,
+ true);
+ if (NS_FAILED(rv)) {
+ rv = DoLookUpHandlerAndDescription(aMajorType,
+ aMinorType,
+ aHandler,
+ aDescription,
+ aMozillaFlags,
+ false);
+ }
+
+ // maybe we have an entry for "aMajorType/*"?
+ if (NS_FAILED(rv)) {
+ rv = DoLookUpHandlerAndDescription(aMajorType,
+ NS_LITERAL_STRING("*"),
+ aHandler,
+ aDescription,
+ aMozillaFlags,
+ true);
+ }
+
+ if (NS_FAILED(rv)) {
+ rv = DoLookUpHandlerAndDescription(aMajorType,
+ NS_LITERAL_STRING("*"),
+ aHandler,
+ aDescription,
+ aMozillaFlags,
+ false);
+ }
+
+ return rv;
+}
+
+// static
+nsresult
+nsOSHelperAppService::DoLookUpHandlerAndDescription(const nsAString& aMajorType,
+ const nsAString& aMinorType,
+ nsAString& aHandler,
+ nsAString& aDescription,
+ nsAString& aMozillaFlags,
+ bool aUserData) {
+ LOG(("-- LookUpHandlerAndDescription for type '%s/%s'\n",
+ NS_LossyConvertUTF16toASCII(aMajorType).get(),
+ NS_LossyConvertUTF16toASCII(aMinorType).get()));
+ nsresult rv = NS_OK;
+ nsAutoString mailcapFileName;
+
+ const char * filenamePref = aUserData ?
+ "helpers.private_mailcap_file" : "helpers.global_mailcap_file";
+ const char * filenameEnvVar = aUserData ?
+ "PERSONAL_MAILCAP" : "MAILCAP";
+
+ rv = GetFileLocation(filenamePref, filenameEnvVar, mailcapFileName);
+ if (NS_SUCCEEDED(rv) && !mailcapFileName.IsEmpty()) {
+ rv = GetHandlerAndDescriptionFromMailcapFile(mailcapFileName,
+ aMajorType,
+ aMinorType,
+ aHandler,
+ aDescription,
+ aMozillaFlags);
+ } else {
+ rv = NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return rv;
+}
+
+// static
+nsresult
+nsOSHelperAppService::GetHandlerAndDescriptionFromMailcapFile(const nsAString& aFilename,
+ const nsAString& aMajorType,
+ const nsAString& aMinorType,
+ nsAString& aHandler,
+ nsAString& aDescription,
+ nsAString& aMozillaFlags) {
+
+ LOG(("-- GetHandlerAndDescriptionFromMailcapFile\n"));
+ LOG(("Getting handler and description from mailcap file '%s'\n",
+ NS_LossyConvertUTF16toASCII(aFilename).get()));
+ LOG(("Using type '%s/%s'\n",
+ NS_LossyConvertUTF16toASCII(aMajorType).get(),
+ NS_LossyConvertUTF16toASCII(aMinorType).get()));
+
+ nsresult rv = NS_OK;
+ bool more = false;
+
+ nsCOMPtr<nsIFile> file(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv));
+ if (NS_FAILED(rv))
+ return rv;
+ rv = file->InitWithPath(aFilename);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIFileInputStream> mailcapFile(do_CreateInstance(NS_LOCALFILEINPUTSTREAM_CONTRACTID, &rv));
+ if (NS_FAILED(rv))
+ return rv;
+ rv = mailcapFile->Init(file, -1, -1, false);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsILineInputStream> mailcap (do_QueryInterface(mailcapFile, &rv));
+
+ if (NS_FAILED(rv)) {
+ LOG(("Interface trouble in stream land!"));
+ return rv;
+ }
+
+ nsString entry, buffer;
+ nsAutoCString cBuffer;
+ entry.SetCapacity(128);
+ cBuffer.SetCapacity(80);
+ rv = mailcap->ReadLine(cBuffer, &more);
+ if (NS_FAILED(rv)) {
+ mailcapFile->Close();
+ return rv;
+ }
+
+ do { // return on end-of-file in the loop
+
+ CopyASCIItoUTF16(cBuffer, buffer);
+ if (!buffer.IsEmpty() && buffer.First() != '#') {
+ entry.Append(buffer);
+ if (entry.Last() == '\\') { // entry continues on next line
+ entry.Truncate(entry.Length()-1);
+ entry.Append(char16_t(' ')); // in case there is no trailing whitespace on this line
+ } else { // we have a full entry in entry. Check it for the type
+ LOG(("Current entry: '%s'\n",
+ NS_LossyConvertUTF16toASCII(entry).get()));
+
+ nsAString::const_iterator semicolon_iter,
+ start_iter, end_iter,
+ majorTypeStart, majorTypeEnd,
+ minorTypeStart, minorTypeEnd;
+ entry.BeginReading(start_iter);
+ entry.EndReading(end_iter);
+ semicolon_iter = start_iter;
+ FindSemicolon(semicolon_iter, end_iter);
+ if (semicolon_iter != end_iter) { // we have something resembling a valid entry
+ rv = ParseMIMEType(start_iter, majorTypeStart, majorTypeEnd,
+ minorTypeStart, minorTypeEnd, semicolon_iter);
+ if (NS_SUCCEEDED(rv) &&
+ Substring(majorTypeStart,
+ majorTypeEnd).Equals(aMajorType,
+ nsCaseInsensitiveStringComparator()) &&
+ Substring(minorTypeStart,
+ minorTypeEnd).Equals(aMinorType,
+ nsCaseInsensitiveStringComparator())) { // we have a match
+ bool match = true;
+ ++semicolon_iter; // point at the first char past the semicolon
+ start_iter = semicolon_iter; // handler string starts here
+ FindSemicolon(semicolon_iter, end_iter);
+ while (start_iter != semicolon_iter &&
+ nsCRT::IsAsciiSpace(*start_iter)) {
+ ++start_iter;
+ }
+
+ LOG(("The real handler is: '%s'\n",
+ NS_LossyConvertUTF16toASCII(Substring(start_iter,
+ semicolon_iter)).get()));
+
+ // XXX ugly hack. Just grab the executable name
+ nsAString::const_iterator end_handler_iter = semicolon_iter;
+ nsAString::const_iterator end_executable_iter = start_iter;
+ while (end_executable_iter != end_handler_iter &&
+ !nsCRT::IsAsciiSpace(*end_executable_iter)) {
+ ++end_executable_iter;
+ }
+ // XXX End ugly hack
+
+ aHandler = Substring(start_iter, end_executable_iter);
+
+ nsAString::const_iterator start_option_iter, end_optionname_iter, equal_sign_iter;
+ bool equalSignFound;
+ while (match &&
+ semicolon_iter != end_iter &&
+ ++semicolon_iter != end_iter) { // there are options left and we still match
+ start_option_iter = semicolon_iter;
+ // skip over leading whitespace
+ while (start_option_iter != end_iter &&
+ nsCRT::IsAsciiSpace(*start_option_iter)) {
+ ++start_option_iter;
+ }
+ if (start_option_iter == end_iter) { // nothing actually here
+ break;
+ }
+ semicolon_iter = start_option_iter;
+ FindSemicolon(semicolon_iter, end_iter);
+ equal_sign_iter = start_option_iter;
+ equalSignFound = false;
+ while (equal_sign_iter != semicolon_iter && !equalSignFound) {
+ switch(*equal_sign_iter) {
+ case '\\':
+ equal_sign_iter.advance(2);
+ break;
+ case '=':
+ equalSignFound = true;
+ break;
+ default:
+ ++equal_sign_iter;
+ break;
+ }
+ }
+ end_optionname_iter = start_option_iter;
+ // find end of option name
+ while (end_optionname_iter != equal_sign_iter &&
+ !nsCRT::IsAsciiSpace(*end_optionname_iter)) {
+ ++end_optionname_iter;
+ }
+ nsDependentSubstring optionName(start_option_iter, end_optionname_iter);
+ if (equalSignFound) {
+ // This is an option that has a name and value
+ if (optionName.EqualsLiteral("description")) {
+ aDescription = Substring(++equal_sign_iter, semicolon_iter);
+ } else if (optionName.EqualsLiteral("x-mozilla-flags")) {
+ aMozillaFlags = Substring(++equal_sign_iter, semicolon_iter);
+ } else if (optionName.EqualsLiteral("test")) {
+ nsAutoCString testCommand;
+ rv = UnescapeCommand(Substring(++equal_sign_iter, semicolon_iter),
+ aMajorType,
+ aMinorType,
+ testCommand);
+ if (NS_FAILED(rv))
+ continue;
+ nsCOMPtr<nsIProcess> process = do_CreateInstance(NS_PROCESS_CONTRACTID, &rv);
+ if (NS_FAILED(rv))
+ continue;
+ nsCOMPtr<nsIFile> file(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv));
+ if (NS_FAILED(rv))
+ continue;
+ rv = file->InitWithNativePath(NS_LITERAL_CSTRING("/bin/sh"));
+ if (NS_FAILED(rv))
+ continue;
+ rv = process->Init(file);
+ if (NS_FAILED(rv))
+ continue;
+ const char *args[] = { "-c", testCommand.get() };
+ LOG(("Running Test: %s\n", testCommand.get()));
+ rv = process->Run(true, args, 2);
+ if (NS_FAILED(rv))
+ continue;
+ int32_t exitValue;
+ rv = process->GetExitValue(&exitValue);
+ if (NS_FAILED(rv))
+ continue;
+ LOG(("Exit code: %d\n", exitValue));
+ if (exitValue) {
+ match = false;
+ }
+ }
+ } else {
+ // This is an option that just has a name but no value (eg "copiousoutput")
+ if (optionName.EqualsLiteral("needsterminal")) {
+ match = false;
+ }
+ }
+ }
+
+
+ if (match) { // we did not fail any test clauses; all is good
+ // get out of here
+ mailcapFile->Close();
+ return NS_OK;
+ } else { // pretend that this match never happened
+ aDescription.Truncate();
+ aMozillaFlags.Truncate();
+ aHandler.Truncate();
+ }
+ }
+ }
+ // zero out the entry for the next cycle
+ entry.Truncate();
+ }
+ }
+ if (!more) {
+ rv = NS_ERROR_NOT_AVAILABLE;
+ break;
+ }
+ rv = mailcap->ReadLine(cBuffer, &more);
+ } while (NS_SUCCEEDED(rv));
+ mailcapFile->Close();
+ return rv;
+}
+
+nsresult nsOSHelperAppService::OSProtocolHandlerExists(const char * aProtocolScheme, bool * aHandlerExists)
+{
+ LOG(("-- nsOSHelperAppService::OSProtocolHandlerExists for '%s'\n",
+ aProtocolScheme));
+ *aHandlerExists = false;
+
+#if defined(MOZ_ENABLE_CONTENTACTION)
+ // libcontentaction requires character ':' after scheme
+ ContentAction::Action action =
+ ContentAction::Action::defaultActionForScheme(QString(aProtocolScheme) + ':');
+
+ if (action.isValid())
+ *aHandlerExists = true;
+#endif
+
+#ifdef MOZ_WIDGET_GTK
+ // Check the GNOME registry for a protocol handler
+ *aHandlerExists = nsGNOMERegistry::HandlerExists(aProtocolScheme);
+#endif
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsOSHelperAppService::GetApplicationDescription(const nsACString& aScheme, nsAString& _retval)
+{
+#ifdef MOZ_WIDGET_GTK
+ nsGNOMERegistry::GetAppDescForScheme(aScheme, _retval);
+ return _retval.IsEmpty() ? NS_ERROR_NOT_AVAILABLE : NS_OK;
+#else
+ return NS_ERROR_NOT_AVAILABLE;
+#endif
+}
+
+nsresult nsOSHelperAppService::GetFileTokenForPath(const char16_t * platformAppPath, nsIFile ** aFile)
+{
+ LOG(("-- nsOSHelperAppService::GetFileTokenForPath: '%s'\n",
+ NS_LossyConvertUTF16toASCII(platformAppPath).get()));
+ if (! *platformAppPath) { // empty filename--return error
+ NS_WARNING("Empty filename passed in.");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // first check if the base class implementation finds anything
+ nsresult rv = nsExternalHelperAppService::GetFileTokenForPath(platformAppPath, aFile);
+ if (NS_SUCCEEDED(rv))
+ return rv;
+ // If the reason for failure was that the file doesn't exist, return too
+ // (because it means the path was absolute, and so that we shouldn't search in
+ // the path)
+ if (rv == NS_ERROR_FILE_NOT_FOUND)
+ return rv;
+
+ // If we get here, we really should have a relative path.
+ NS_ASSERTION(*platformAppPath != char16_t('/'), "Unexpected absolute path");
+
+ nsCOMPtr<nsIFile> localFile (do_CreateInstance(NS_LOCAL_FILE_CONTRACTID));
+
+ if (!localFile) return NS_ERROR_NOT_INITIALIZED;
+
+ bool exists = false;
+ // ugly hack. Walk the PATH variable...
+ char* unixpath = PR_GetEnv("PATH");
+ nsAutoCString path(unixpath);
+
+ const char* start_iter = path.BeginReading(start_iter);
+ const char* colon_iter = start_iter;
+ const char* end_iter = path.EndReading(end_iter);
+
+ while (start_iter != end_iter && !exists) {
+ while (colon_iter != end_iter && *colon_iter != ':') {
+ ++colon_iter;
+ }
+ localFile->InitWithNativePath(Substring(start_iter, colon_iter));
+ rv = localFile->AppendRelativePath(nsDependentString(platformAppPath));
+ // Failing AppendRelativePath is a bad thing - it should basically always
+ // succeed given a relative path. Show a warning if it does fail.
+ // To prevent infinite loops when it does fail, return at this point.
+ NS_ENSURE_SUCCESS(rv, rv);
+ localFile->Exists(&exists);
+ if (!exists) {
+ if (colon_iter == end_iter) {
+ break;
+ }
+ ++colon_iter;
+ start_iter = colon_iter;
+ }
+ }
+
+ if (exists) {
+ rv = NS_OK;
+ } else {
+ rv = NS_ERROR_NOT_AVAILABLE;
+ }
+
+ *aFile = localFile;
+ NS_IF_ADDREF(*aFile);
+
+ return rv;
+}
+
+already_AddRefed<nsMIMEInfoBase>
+nsOSHelperAppService::GetFromExtension(const nsCString& aFileExt) {
+ // if the extension is empty, return immediately
+ if (aFileExt.IsEmpty())
+ return nullptr;
+
+ LOG(("Here we do an extension lookup for '%s'\n", aFileExt.get()));
+
+ nsAutoString majorType, minorType,
+ mime_types_description, mailcap_description,
+ handler, mozillaFlags;
+
+ nsresult rv = LookUpTypeAndDescription(NS_ConvertUTF8toUTF16(aFileExt),
+ majorType,
+ minorType,
+ mime_types_description,
+ true);
+
+ if (NS_FAILED(rv) || majorType.IsEmpty()) {
+
+#ifdef MOZ_WIDGET_GTK
+ LOG(("Looking in GNOME registry\n"));
+ RefPtr<nsMIMEInfoBase> gnomeInfo =
+ nsGNOMERegistry::GetFromExtension(aFileExt);
+ if (gnomeInfo) {
+ LOG(("Got MIMEInfo from GNOME registry\n"));
+ return gnomeInfo.forget();
+ }
+#endif
+
+ rv = LookUpTypeAndDescription(NS_ConvertUTF8toUTF16(aFileExt),
+ majorType,
+ minorType,
+ mime_types_description,
+ false);
+ }
+
+ if (NS_FAILED(rv))
+ return nullptr;
+
+ NS_LossyConvertUTF16toASCII asciiMajorType(majorType);
+ NS_LossyConvertUTF16toASCII asciiMinorType(minorType);
+
+ LOG(("Type/Description results: majorType='%s', minorType='%s', description='%s'\n",
+ asciiMajorType.get(),
+ asciiMinorType.get(),
+ NS_LossyConvertUTF16toASCII(mime_types_description).get()));
+
+ if (majorType.IsEmpty() && minorType.IsEmpty()) {
+ // we didn't get a type mapping, so we can't do anything useful
+ return nullptr;
+ }
+
+ nsAutoCString mimeType(asciiMajorType + NS_LITERAL_CSTRING("/") + asciiMinorType);
+ RefPtr<nsMIMEInfoUnix> mimeInfo = new nsMIMEInfoUnix(mimeType);
+
+ mimeInfo->AppendExtension(aFileExt);
+ rv = LookUpHandlerAndDescription(majorType, minorType,
+ handler, mailcap_description,
+ mozillaFlags);
+ LOG(("Handler/Description results: handler='%s', description='%s', mozillaFlags='%s'\n",
+ NS_LossyConvertUTF16toASCII(handler).get(),
+ NS_LossyConvertUTF16toASCII(mailcap_description).get(),
+ NS_LossyConvertUTF16toASCII(mozillaFlags).get()));
+ mailcap_description.Trim(" \t\"");
+ mozillaFlags.Trim(" \t");
+ if (!mime_types_description.IsEmpty()) {
+ mimeInfo->SetDescription(mime_types_description);
+ } else {
+ mimeInfo->SetDescription(mailcap_description);
+ }
+
+ if (NS_SUCCEEDED(rv) && handler.IsEmpty()) {
+ rv = NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIFile> handlerFile;
+ rv = GetFileTokenForPath(handler.get(), getter_AddRefs(handlerFile));
+
+ if (NS_SUCCEEDED(rv)) {
+ mimeInfo->SetDefaultApplication(handlerFile);
+ mimeInfo->SetPreferredAction(nsIMIMEInfo::useSystemDefault);
+ mimeInfo->SetDefaultDescription(handler);
+ }
+ }
+
+ if (NS_FAILED(rv)) {
+ mimeInfo->SetPreferredAction(nsIMIMEInfo::saveToDisk);
+ }
+
+ return mimeInfo.forget();
+}
+
+already_AddRefed<nsMIMEInfoBase>
+nsOSHelperAppService::GetFromType(const nsCString& aMIMEType) {
+ // if the type is empty, return immediately
+ if (aMIMEType.IsEmpty())
+ return nullptr;
+
+ LOG(("Here we do a mimetype lookup for '%s'\n", aMIMEType.get()));
+
+ // extract the major and minor types
+ NS_ConvertASCIItoUTF16 mimeType(aMIMEType);
+ nsAString::const_iterator start_iter, end_iter,
+ majorTypeStart, majorTypeEnd,
+ minorTypeStart, minorTypeEnd;
+
+ mimeType.BeginReading(start_iter);
+ mimeType.EndReading(end_iter);
+
+ // XXX FIXME: add typeOptions parsing in here
+ nsresult rv = ParseMIMEType(start_iter, majorTypeStart, majorTypeEnd,
+ minorTypeStart, minorTypeEnd, end_iter);
+
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ nsDependentSubstring majorType(majorTypeStart, majorTypeEnd);
+ nsDependentSubstring minorType(minorTypeStart, minorTypeEnd);
+
+ // First check the user's private mailcap file
+ nsAutoString mailcap_description, handler, mozillaFlags;
+ DoLookUpHandlerAndDescription(majorType,
+ minorType,
+ handler,
+ mailcap_description,
+ mozillaFlags,
+ true);
+
+ LOG(("Private Handler/Description results: handler='%s', description='%s'\n",
+ NS_LossyConvertUTF16toASCII(handler).get(),
+ NS_LossyConvertUTF16toASCII(mailcap_description).get()));
+
+ // Now look up our extensions
+ nsAutoString extensions, mime_types_description;
+ LookUpExtensionsAndDescription(majorType,
+ minorType,
+ extensions,
+ mime_types_description);
+
+#ifdef MOZ_WIDGET_GTK
+ if (handler.IsEmpty()) {
+ RefPtr<nsMIMEInfoBase> gnomeInfo = nsGNOMERegistry::GetFromType(aMIMEType);
+ if (gnomeInfo) {
+ LOG(("Got MIMEInfo from GNOME registry without extensions; setting them "
+ "to %s\n", NS_LossyConvertUTF16toASCII(extensions).get()));
+
+ NS_ASSERTION(!gnomeInfo->HasExtensions(), "How'd that happen?");
+ gnomeInfo->SetFileExtensions(NS_ConvertUTF16toUTF8(extensions));
+ return gnomeInfo.forget();
+ }
+ }
+#endif
+
+ if (handler.IsEmpty()) {
+ DoLookUpHandlerAndDescription(majorType,
+ minorType,
+ handler,
+ mailcap_description,
+ mozillaFlags,
+ false);
+ }
+
+ if (handler.IsEmpty()) {
+ DoLookUpHandlerAndDescription(majorType,
+ NS_LITERAL_STRING("*"),
+ handler,
+ mailcap_description,
+ mozillaFlags,
+ true);
+ }
+
+ if (handler.IsEmpty()) {
+ DoLookUpHandlerAndDescription(majorType,
+ NS_LITERAL_STRING("*"),
+ handler,
+ mailcap_description,
+ mozillaFlags,
+ false);
+ }
+
+ LOG(("Handler/Description results: handler='%s', description='%s', mozillaFlags='%s'\n",
+ NS_LossyConvertUTF16toASCII(handler).get(),
+ NS_LossyConvertUTF16toASCII(mailcap_description).get(),
+ NS_LossyConvertUTF16toASCII(mozillaFlags).get()));
+
+ mailcap_description.Trim(" \t\"");
+ mozillaFlags.Trim(" \t");
+
+ if (handler.IsEmpty() && extensions.IsEmpty() &&
+ mailcap_description.IsEmpty() && mime_types_description.IsEmpty()) {
+ // No real useful info
+ return nullptr;
+ }
+
+ RefPtr<nsMIMEInfoUnix> mimeInfo = new nsMIMEInfoUnix(aMIMEType);
+
+ mimeInfo->SetFileExtensions(NS_ConvertUTF16toUTF8(extensions));
+ if (! mime_types_description.IsEmpty()) {
+ mimeInfo->SetDescription(mime_types_description);
+ } else {
+ mimeInfo->SetDescription(mailcap_description);
+ }
+
+ rv = NS_ERROR_NOT_AVAILABLE;
+ nsCOMPtr<nsIFile> handlerFile;
+ if (!handler.IsEmpty()) {
+ rv = GetFileTokenForPath(handler.get(), getter_AddRefs(handlerFile));
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ mimeInfo->SetDefaultApplication(handlerFile);
+ mimeInfo->SetPreferredAction(nsIMIMEInfo::useSystemDefault);
+ mimeInfo->SetDefaultDescription(handler);
+ } else {
+ mimeInfo->SetPreferredAction(nsIMIMEInfo::saveToDisk);
+ }
+
+ return mimeInfo.forget();
+}
+
+
+already_AddRefed<nsIMIMEInfo>
+nsOSHelperAppService::GetMIMEInfoFromOS(const nsACString& aType,
+ const nsACString& aFileExt,
+ bool *aFound) {
+ *aFound = true;
+ RefPtr<nsMIMEInfoBase> retval = GetFromType(PromiseFlatCString(aType));
+ bool hasDefault = false;
+ if (retval)
+ retval->GetHasDefaultHandler(&hasDefault);
+ if (!retval || !hasDefault) {
+ RefPtr<nsMIMEInfoBase> miByExt = GetFromExtension(PromiseFlatCString(aFileExt));
+ // If we had no extension match, but a type match, use that
+ if (!miByExt && retval)
+ return retval.forget();
+ // If we had an extension match but no type match, set the mimetype and use
+ // it
+ if (!retval && miByExt) {
+ if (!aType.IsEmpty())
+ miByExt->SetMIMEType(aType);
+ miByExt.swap(retval);
+
+ return retval.forget();
+ }
+ // If we got nothing, make a new mimeinfo
+ if (!retval) {
+ *aFound = false;
+ retval = new nsMIMEInfoUnix(aType);
+ if (retval) {
+ if (!aFileExt.IsEmpty())
+ retval->AppendExtension(aFileExt);
+ }
+
+ return retval.forget();
+ }
+
+ // Copy the attributes of retval (mimeinfo from type) onto miByExt, to
+ // return it
+ // but reset to just collected mDefaultAppDescription (from ext)
+ nsAutoString byExtDefault;
+ miByExt->GetDefaultDescription(byExtDefault);
+ retval->SetDefaultDescription(byExtDefault);
+ retval->CopyBasicDataTo(miByExt);
+
+ miByExt.swap(retval);
+ }
+ return retval.forget();
+}
+
+NS_IMETHODIMP
+nsOSHelperAppService::GetProtocolHandlerInfoFromOS(const nsACString &aScheme,
+ bool *found,
+ nsIHandlerInfo **_retval)
+{
+ NS_ASSERTION(!aScheme.IsEmpty(), "No scheme was specified!");
+
+ nsresult rv = OSProtocolHandlerExists(nsPromiseFlatCString(aScheme).get(),
+ found);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsMIMEInfoUnix *handlerInfo =
+ new nsMIMEInfoUnix(aScheme, nsMIMEInfoBase::eProtocolInfo);
+ NS_ENSURE_TRUE(handlerInfo, NS_ERROR_OUT_OF_MEMORY);
+ NS_ADDREF(*_retval = handlerInfo);
+
+ if (!*found) {
+ // Code that calls this requires an object regardless if the OS has
+ // something for us, so we return the empty object.
+ return NS_OK;
+ }
+
+ nsAutoString desc;
+ GetApplicationDescription(aScheme, desc);
+ handlerInfo->SetDefaultDescription(desc);
+
+ return NS_OK;
+}
diff --git a/uriloader/exthandler/unix/nsOSHelperAppService.h b/uriloader/exthandler/unix/nsOSHelperAppService.h
new file mode 100644
index 0000000000..33eb934ed7
--- /dev/null
+++ b/uriloader/exthandler/unix/nsOSHelperAppService.h
@@ -0,0 +1,128 @@
+/* -*- Mode: C++; tab-width: 3; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsOSHelperAppService_h__
+#define nsOSHelperAppService_h__
+
+// The OS helper app service is a subclass of nsExternalHelperAppService and is implemented on each
+// platform. It contains platform specific code for finding helper applications for a given mime type
+// in addition to launching those applications.
+
+#include "nsExternalHelperAppService.h"
+#include "nsCExternalHandlerService.h"
+#include "nsMIMEInfoImpl.h"
+#include "nsCOMPtr.h"
+
+class nsILineInputStream;
+
+class nsOSHelperAppService : public nsExternalHelperAppService
+{
+public:
+ nsOSHelperAppService();
+ virtual ~nsOSHelperAppService();
+
+ // method overrides for mime.types and mime.info look up steps
+ already_AddRefed<nsIMIMEInfo> GetMIMEInfoFromOS(const nsACString& aMimeType,
+ const nsACString& aFileExt,
+ bool *aFound);
+ NS_IMETHOD GetProtocolHandlerInfoFromOS(const nsACString &aScheme,
+ bool *found,
+ nsIHandlerInfo **_retval);
+
+ // override nsIExternalProtocolService methods
+ nsresult OSProtocolHandlerExists(const char * aProtocolScheme, bool * aHandlerExists);
+ NS_IMETHOD GetApplicationDescription(const nsACString& aScheme, nsAString& _retval);
+
+ // GetFileTokenForPath must be implemented by each platform.
+ // platformAppPath --> a platform specific path to an application that we got out of the
+ // rdf data source. This can be a mac file spec, a unix path or a windows path depending on the platform
+ // aFile --> an nsIFile representation of that platform application path.
+ virtual nsresult GetFileTokenForPath(const char16_t * platformAppPath, nsIFile ** aFile);
+
+protected:
+ already_AddRefed<nsMIMEInfoBase> GetFromType(const nsCString& aMimeType);
+ already_AddRefed<nsMIMEInfoBase> GetFromExtension(const nsCString& aFileExt);
+
+private:
+ uint32_t mPermissions;
+
+ // Helper methods which have to access static members
+ static nsresult UnescapeCommand(const nsAString& aEscapedCommand,
+ const nsAString& aMajorType,
+ const nsAString& aMinorType,
+ nsACString& aUnEscapedCommand);
+ static nsresult GetFileLocation(const char* aPrefName,
+ const char* aEnvVarName,
+ nsAString& aFileLocation);
+ static nsresult LookUpTypeAndDescription(const nsAString& aFileExtension,
+ nsAString& aMajorType,
+ nsAString& aMinorType,
+ nsAString& aDescription,
+ bool aUserData);
+ static nsresult CreateInputStream(const nsAString& aFilename,
+ nsIFileInputStream ** aFileInputStream,
+ nsILineInputStream ** aLineInputStream,
+ nsACString& aBuffer,
+ bool * aNetscapeFormat,
+ bool * aMore);
+
+ static nsresult GetTypeAndDescriptionFromMimetypesFile(const nsAString& aFilename,
+ const nsAString& aFileExtension,
+ nsAString& aMajorType,
+ nsAString& aMinorType,
+ nsAString& aDescription);
+
+ static nsresult LookUpExtensionsAndDescription(const nsAString& aMajorType,
+ const nsAString& aMinorType,
+ nsAString& aFileExtensions,
+ nsAString& aDescription);
+
+ static nsresult GetExtensionsAndDescriptionFromMimetypesFile(const nsAString& aFilename,
+ const nsAString& aMajorType,
+ const nsAString& aMinorType,
+ nsAString& aFileExtensions,
+ nsAString& aDescription);
+
+ static nsresult ParseNetscapeMIMETypesEntry(const nsAString& aEntry,
+ nsAString::const_iterator& aMajorTypeStart,
+ nsAString::const_iterator& aMajorTypeEnd,
+ nsAString::const_iterator& aMinorTypeStart,
+ nsAString::const_iterator& aMinorTypeEnd,
+ nsAString& aExtensions,
+ nsAString::const_iterator& aDescriptionStart,
+ nsAString::const_iterator& aDescriptionEnd);
+
+ static nsresult ParseNormalMIMETypesEntry(const nsAString& aEntry,
+ nsAString::const_iterator& aMajorTypeStart,
+ nsAString::const_iterator& aMajorTypeEnd,
+ nsAString::const_iterator& aMinorTypeStart,
+ nsAString::const_iterator& aMinorTypeEnd,
+ nsAString& aExtensions,
+ nsAString::const_iterator& aDescriptionStart,
+ nsAString::const_iterator& aDescriptionEnd);
+
+ static nsresult LookUpHandlerAndDescription(const nsAString& aMajorType,
+ const nsAString& aMinorType,
+ nsAString& aHandler,
+ nsAString& aDescription,
+ nsAString& aMozillaFlags);
+
+ static nsresult DoLookUpHandlerAndDescription(const nsAString& aMajorType,
+ const nsAString& aMinorType,
+ nsAString& aHandler,
+ nsAString& aDescription,
+ nsAString& aMozillaFlags,
+ bool aUserData);
+
+ static nsresult GetHandlerAndDescriptionFromMailcapFile(const nsAString& aFilename,
+ const nsAString& aMajorType,
+ const nsAString& aMinorType,
+ nsAString& aHandler,
+ nsAString& aDescription,
+ nsAString& aMozillaFlags);
+};
+
+#endif // nsOSHelperAppService_h__
diff --git a/uriloader/exthandler/win/nsMIMEInfoWin.cpp b/uriloader/exthandler/win/nsMIMEInfoWin.cpp
new file mode 100644
index 0000000000..2c7171c877
--- /dev/null
+++ b/uriloader/exthandler/win/nsMIMEInfoWin.cpp
@@ -0,0 +1,883 @@
+/* -*- Mode: C++; tab-width: 3; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsArrayEnumerator.h"
+#include "nsCOMArray.h"
+#include "nsLocalFile.h"
+#include "nsMIMEInfoWin.h"
+#include "nsNetUtil.h"
+#include <windows.h>
+#include <shellapi.h>
+#include "nsAutoPtr.h"
+#include "nsIMutableArray.h"
+#include "nsTArray.h"
+#include "shlobj.h"
+#include "windows.h"
+#include "nsIWindowsRegKey.h"
+#include "nsIProcess.h"
+#include "nsUnicharUtils.h"
+#include "nsITextToSubURI.h"
+#include "nsVariant.h"
+#include "mozilla/UniquePtrExtensions.h"
+
+#define RUNDLL32_EXE L"\\rundll32.exe"
+
+
+NS_IMPL_ISUPPORTS_INHERITED(nsMIMEInfoWin, nsMIMEInfoBase, nsIPropertyBag)
+
+nsMIMEInfoWin::~nsMIMEInfoWin()
+{
+}
+
+nsresult
+nsMIMEInfoWin::LaunchDefaultWithFile(nsIFile* aFile)
+{
+ // Launch the file, unless it is an executable.
+ bool executable = true;
+ aFile->IsExecutable(&executable);
+ if (executable)
+ return NS_ERROR_FAILURE;
+
+ return aFile->Launch();
+}
+
+NS_IMETHODIMP
+nsMIMEInfoWin::LaunchWithFile(nsIFile* aFile)
+{
+ nsresult rv;
+
+ // it doesn't make any sense to call this on protocol handlers
+ NS_ASSERTION(mClass == eMIMEInfo,
+ "nsMIMEInfoBase should have mClass == eMIMEInfo");
+
+ if (mPreferredAction == useSystemDefault) {
+ return LaunchDefaultWithFile(aFile);
+ }
+
+ if (mPreferredAction == useHelperApp) {
+ if (!mPreferredApplication)
+ return NS_ERROR_FILE_NOT_FOUND;
+
+ // at the moment, we only know how to hand files off to local handlers
+ nsCOMPtr<nsILocalHandlerApp> localHandler =
+ do_QueryInterface(mPreferredApplication, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> executable;
+ rv = localHandler->GetExecutable(getter_AddRefs(executable));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString path;
+ aFile->GetPath(path);
+
+ // Deal with local dll based handlers
+ nsCString filename;
+ executable->GetNativeLeafName(filename);
+ if (filename.Length() > 4) {
+ nsCString extension(Substring(filename, filename.Length() - 4, 4));
+
+ if (extension.LowerCaseEqualsLiteral(".dll")) {
+ nsAutoString args;
+
+ // executable is rundll32, everything else is a list of parameters,
+ // including the dll handler.
+ if (!GetDllLaunchInfo(executable, aFile, args, false))
+ return NS_ERROR_INVALID_ARG;
+
+ WCHAR rundll32Path[MAX_PATH + sizeof(RUNDLL32_EXE) / sizeof(WCHAR) + 1] = {L'\0'};
+ if (!GetSystemDirectoryW(rundll32Path, MAX_PATH)) {
+ return NS_ERROR_FILE_NOT_FOUND;
+ }
+ lstrcatW(rundll32Path, RUNDLL32_EXE);
+
+ SHELLEXECUTEINFOW seinfo;
+ memset(&seinfo, 0, sizeof(seinfo));
+ seinfo.cbSize = sizeof(SHELLEXECUTEINFOW);
+ seinfo.fMask = 0;
+ seinfo.hwnd = nullptr;
+ seinfo.lpVerb = nullptr;
+ seinfo.lpFile = rundll32Path;
+ seinfo.lpParameters = args.get();
+ seinfo.lpDirectory = nullptr;
+ seinfo.nShow = SW_SHOWNORMAL;
+ if (ShellExecuteExW(&seinfo))
+ return NS_OK;
+
+ switch ((LONG_PTR)seinfo.hInstApp) {
+ case 0:
+ case SE_ERR_OOM:
+ return NS_ERROR_OUT_OF_MEMORY;
+ case SE_ERR_ACCESSDENIED:
+ return NS_ERROR_FILE_ACCESS_DENIED;
+ case SE_ERR_ASSOCINCOMPLETE:
+ case SE_ERR_NOASSOC:
+ return NS_ERROR_UNEXPECTED;
+ case SE_ERR_DDEBUSY:
+ case SE_ERR_DDEFAIL:
+ case SE_ERR_DDETIMEOUT:
+ return NS_ERROR_NOT_AVAILABLE;
+ case SE_ERR_DLLNOTFOUND:
+ return NS_ERROR_FAILURE;
+ case SE_ERR_SHARE:
+ return NS_ERROR_FILE_IS_LOCKED;
+ default:
+ switch(GetLastError()) {
+ case ERROR_FILE_NOT_FOUND:
+ return NS_ERROR_FILE_NOT_FOUND;
+ case ERROR_PATH_NOT_FOUND:
+ return NS_ERROR_FILE_UNRECOGNIZED_PATH;
+ case ERROR_BAD_FORMAT:
+ return NS_ERROR_FILE_CORRUPTED;
+ }
+
+ }
+ return NS_ERROR_FILE_EXECUTION_FAILED;
+ }
+ }
+ return LaunchWithIProcess(executable, path);
+ }
+
+ return NS_ERROR_INVALID_ARG;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoWin::GetHasDefaultHandler(bool * _retval)
+{
+ // We have a default application if we have a description
+ // We can ShellExecute anything; however, callers are probably interested if
+ // there is really an application associated with this type of file
+ *_retval = !mDefaultAppDescription.IsEmpty();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoWin::GetEnumerator(nsISimpleEnumerator* *_retval)
+{
+ nsCOMArray<nsIVariant> properties;
+
+ nsCOMPtr<nsIVariant> variant;
+ GetProperty(NS_LITERAL_STRING("defaultApplicationIconURL"), getter_AddRefs(variant));
+ if (variant)
+ properties.AppendObject(variant);
+
+ GetProperty(NS_LITERAL_STRING("customApplicationIconURL"), getter_AddRefs(variant));
+ if (variant)
+ properties.AppendObject(variant);
+
+ return NS_NewArrayEnumerator(_retval, properties);
+}
+
+static nsresult GetIconURLVariant(nsIFile* aApplication, nsIVariant* *_retval)
+{
+ nsAutoCString fileURLSpec;
+ NS_GetURLSpecFromFile(aApplication, fileURLSpec);
+ nsAutoCString iconURLSpec; iconURLSpec.AssignLiteral("moz-icon://");
+ iconURLSpec += fileURLSpec;
+ RefPtr<nsVariant> writable(new nsVariant());
+ writable->SetAsAUTF8String(iconURLSpec);
+ writable.forget(_retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInfoWin::GetProperty(const nsAString& aName, nsIVariant* *_retval)
+{
+ nsresult rv;
+ if (mDefaultApplication && aName.EqualsLiteral(PROPERTY_DEFAULT_APP_ICON_URL)) {
+ rv = GetIconURLVariant(mDefaultApplication, _retval);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else if (mPreferredApplication &&
+ aName.EqualsLiteral(PROPERTY_CUSTOM_APP_ICON_URL)) {
+ nsCOMPtr<nsILocalHandlerApp> localHandler =
+ do_QueryInterface(mPreferredApplication, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> executable;
+ rv = localHandler->GetExecutable(getter_AddRefs(executable));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = GetIconURLVariant(executable, _retval);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+// this implementation was pretty much copied verbatime from
+// Tony Robinson's code in nsExternalProtocolWin.cpp
+nsresult
+nsMIMEInfoWin::LoadUriInternal(nsIURI * aURL)
+{
+ nsresult rv = NS_OK;
+
+ // 1. Find the default app for this protocol
+ // 2. Set up the command line
+ // 3. Launch the app.
+
+ // For now, we'll just cheat essentially, check for the command line
+ // then just call ShellExecute()!
+
+ if (aURL)
+ {
+ // extract the url spec from the url
+ nsAutoCString urlSpec;
+ aURL->GetAsciiSpec(urlSpec);
+
+ // Unescape non-ASCII characters in the URL
+ nsAutoCString urlCharset;
+ nsAutoString utf16Spec;
+ rv = aURL->GetOriginCharset(urlCharset);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsITextToSubURI> textToSubURI = do_GetService(NS_ITEXTTOSUBURI_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (NS_FAILED(textToSubURI->UnEscapeNonAsciiURI(urlCharset, urlSpec, utf16Spec))) {
+ CopyASCIItoUTF16(urlSpec, utf16Spec);
+ }
+
+ static const wchar_t cmdVerb[] = L"open";
+ SHELLEXECUTEINFOW sinfo;
+ memset(&sinfo, 0, sizeof(sinfo));
+ sinfo.cbSize = sizeof(sinfo);
+ sinfo.fMask = SEE_MASK_FLAG_DDEWAIT |
+ SEE_MASK_FLAG_NO_UI;
+ sinfo.hwnd = nullptr;
+ sinfo.lpVerb = (LPWSTR)&cmdVerb;
+ sinfo.nShow = SW_SHOWNORMAL;
+
+ LPITEMIDLIST pidl = nullptr;
+ SFGAOF sfgao;
+
+ // Bug 394974
+ if (SUCCEEDED(SHParseDisplayName(utf16Spec.get(), nullptr,
+ &pidl, 0, &sfgao))) {
+ sinfo.lpIDList = pidl;
+ sinfo.fMask |= SEE_MASK_INVOKEIDLIST;
+ } else {
+ // SHParseDisplayName failed. Bailing out as work around for
+ // Microsoft Security Bulletin MS07-061
+ rv = NS_ERROR_FAILURE;
+ }
+ if (NS_SUCCEEDED(rv)) {
+ BOOL result = ShellExecuteExW(&sinfo);
+ if (!result || ((LONG_PTR)sinfo.hInstApp) < 32)
+ rv = NS_ERROR_FAILURE;
+ }
+ if (pidl)
+ CoTaskMemFree(pidl);
+ }
+
+ return rv;
+}
+
+// Given a path to a local file, return its nsILocalHandlerApp instance.
+bool nsMIMEInfoWin::GetLocalHandlerApp(const nsAString& aCommandHandler,
+ nsCOMPtr<nsILocalHandlerApp>& aApp)
+{
+ nsCOMPtr<nsIFile> locfile;
+ nsresult rv =
+ NS_NewLocalFile(aCommandHandler, true, getter_AddRefs(locfile));
+ if (NS_FAILED(rv))
+ return false;
+
+ aApp = do_CreateInstance("@mozilla.org/uriloader/local-handler-app;1");
+ if (!aApp)
+ return false;
+
+ aApp->SetExecutable(locfile);
+ return true;
+}
+
+// Return the cleaned up file path associated with a command verb
+// located in root/Applications.
+bool nsMIMEInfoWin::GetAppsVerbCommandHandler(const nsAString& appExeName,
+ nsAString& applicationPath,
+ bool edit)
+{
+ nsCOMPtr<nsIWindowsRegKey> appKey =
+ do_CreateInstance("@mozilla.org/windows-registry-key;1");
+ if (!appKey)
+ return false;
+
+ // HKEY_CLASSES_ROOT\Applications\iexplore.exe
+ nsAutoString applicationsPath;
+ applicationsPath.AppendLiteral("Applications\\");
+ applicationsPath.Append(appExeName);
+
+ nsresult rv = appKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT,
+ applicationsPath,
+ nsIWindowsRegKey::ACCESS_QUERY_VALUE);
+ if (NS_FAILED(rv))
+ return false;
+
+ // Check for the NoOpenWith flag, if it exists
+ uint32_t value;
+ if (NS_SUCCEEDED(appKey->ReadIntValue(
+ NS_LITERAL_STRING("NoOpenWith"), &value)) &&
+ value == 1)
+ return false;
+
+ nsAutoString dummy;
+ if (NS_SUCCEEDED(appKey->ReadStringValue(
+ NS_LITERAL_STRING("NoOpenWith"), dummy)))
+ return false;
+
+ appKey->Close();
+
+ // HKEY_CLASSES_ROOT\Applications\iexplore.exe\shell\open\command
+ applicationsPath.AssignLiteral("Applications\\");
+ applicationsPath.Append(appExeName);
+ if (!edit)
+ applicationsPath.AppendLiteral("\\shell\\open\\command");
+ else
+ applicationsPath.AppendLiteral("\\shell\\edit\\command");
+
+
+ rv = appKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT,
+ applicationsPath,
+ nsIWindowsRegKey::ACCESS_QUERY_VALUE);
+ if (NS_FAILED(rv))
+ return false;
+
+ nsAutoString appFilesystemCommand;
+ if (NS_SUCCEEDED(appKey->ReadStringValue(EmptyString(),
+ appFilesystemCommand))) {
+
+ // Expand environment vars, clean up any misc.
+ if (!nsLocalFile::CleanupCmdHandlerPath(appFilesystemCommand))
+ return false;
+
+ applicationPath = appFilesystemCommand;
+ return true;
+ }
+ return false;
+}
+
+// Return a fully populated command string based on
+// passing information. Used in launchWithFile to trace
+// back to the full handler path based on the dll.
+// (dll, targetfile, return args, open/edit)
+bool nsMIMEInfoWin::GetDllLaunchInfo(nsIFile * aDll,
+ nsIFile * aFile,
+ nsAString& args,
+ bool edit)
+{
+ if (!aDll || !aFile)
+ return false;
+
+ nsString appExeName;
+ aDll->GetLeafName(appExeName);
+
+ nsCOMPtr<nsIWindowsRegKey> appKey =
+ do_CreateInstance("@mozilla.org/windows-registry-key;1");
+ if (!appKey)
+ return false;
+
+ // HKEY_CLASSES_ROOT\Applications\iexplore.exe
+ nsAutoString applicationsPath;
+ applicationsPath.AppendLiteral("Applications\\");
+ applicationsPath.Append(appExeName);
+
+ nsresult rv = appKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT,
+ applicationsPath,
+ nsIWindowsRegKey::ACCESS_QUERY_VALUE);
+ if (NS_FAILED(rv))
+ return false;
+
+ // Check for the NoOpenWith flag, if it exists
+ uint32_t value;
+ rv = appKey->ReadIntValue(NS_LITERAL_STRING("NoOpenWith"), &value);
+ if (NS_SUCCEEDED(rv) && value == 1)
+ return false;
+
+ nsAutoString dummy;
+ if (NS_SUCCEEDED(appKey->ReadStringValue(NS_LITERAL_STRING("NoOpenWith"),
+ dummy)))
+ return false;
+
+ appKey->Close();
+
+ // HKEY_CLASSES_ROOT\Applications\iexplore.exe\shell\open\command
+ applicationsPath.AssignLiteral("Applications\\");
+ applicationsPath.Append(appExeName);
+ if (!edit)
+ applicationsPath.AppendLiteral("\\shell\\open\\command");
+ else
+ applicationsPath.AppendLiteral("\\shell\\edit\\command");
+
+ rv = appKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT,
+ applicationsPath,
+ nsIWindowsRegKey::ACCESS_QUERY_VALUE);
+ if (NS_FAILED(rv))
+ return false;
+
+ nsAutoString appFilesystemCommand;
+ if (NS_SUCCEEDED(appKey->ReadStringValue(EmptyString(),
+ appFilesystemCommand))) {
+ // Replace embedded environment variables.
+ uint32_t bufLength =
+ ::ExpandEnvironmentStringsW(appFilesystemCommand.get(),
+ L"", 0);
+ if (bufLength == 0) // Error
+ return false;
+
+ auto destination = mozilla::MakeUniqueFallible<wchar_t[]>(bufLength);
+ if (!destination)
+ return false;
+ if (!::ExpandEnvironmentStringsW(appFilesystemCommand.get(),
+ destination.get(),
+ bufLength))
+ return false;
+
+ appFilesystemCommand.Assign(destination.get());
+
+ // C:\Windows\System32\rundll32.exe "C:\Program Files\Windows
+ // Photo Gallery\PhotoViewer.dll", ImageView_Fullscreen %1
+ nsAutoString params;
+ NS_NAMED_LITERAL_STRING(rundllSegment, "rundll32.exe ");
+ int32_t index = appFilesystemCommand.Find(rundllSegment);
+ if (index > kNotFound) {
+ params.Append(Substring(appFilesystemCommand,
+ index + rundllSegment.Length()));
+ } else {
+ params.Append(appFilesystemCommand);
+ }
+
+ // check to make sure we have a %1 and fill it
+ NS_NAMED_LITERAL_STRING(percentOneParam, "%1");
+ index = params.Find(percentOneParam);
+ if (index == kNotFound) // no parameter
+ return false;
+
+ nsString target;
+ aFile->GetTarget(target);
+ params.Replace(index, 2, target);
+
+ args = params;
+
+ return true;
+ }
+ return false;
+}
+
+// Return the cleaned up file path associated with a progid command
+// verb located in root.
+bool nsMIMEInfoWin::GetProgIDVerbCommandHandler(const nsAString& appProgIDName,
+ nsAString& applicationPath,
+ bool edit)
+{
+ nsCOMPtr<nsIWindowsRegKey> appKey =
+ do_CreateInstance("@mozilla.org/windows-registry-key;1");
+ if (!appKey)
+ return false;
+
+ nsAutoString appProgId(appProgIDName);
+
+ // HKEY_CLASSES_ROOT\Windows.XPSReachViewer\shell\open\command
+ if (!edit)
+ appProgId.AppendLiteral("\\shell\\open\\command");
+ else
+ appProgId.AppendLiteral("\\shell\\edit\\command");
+
+ nsresult rv = appKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT,
+ appProgId,
+ nsIWindowsRegKey::ACCESS_QUERY_VALUE);
+ if (NS_FAILED(rv))
+ return false;
+
+ nsAutoString appFilesystemCommand;
+ if (NS_SUCCEEDED(appKey->ReadStringValue(EmptyString(), appFilesystemCommand))) {
+
+ // Expand environment vars, clean up any misc.
+ if (!nsLocalFile::CleanupCmdHandlerPath(appFilesystemCommand))
+ return false;
+
+ applicationPath = appFilesystemCommand;
+ return true;
+ }
+ return false;
+}
+
+// Helper routine used in tracking app lists. Converts path
+// entries to lower case and stores them in the trackList array.
+void nsMIMEInfoWin::ProcessPath(nsCOMPtr<nsIMutableArray>& appList,
+ nsTArray<nsString>& trackList,
+ const nsAString& appFilesystemCommand)
+{
+ nsAutoString lower(appFilesystemCommand);
+ ToLowerCase(lower);
+
+ // Don't include firefox.exe in the list
+ WCHAR exe[MAX_PATH+1];
+ uint32_t len = GetModuleFileNameW(nullptr, exe, MAX_PATH);
+ if (len < MAX_PATH && len != 0) {
+ uint32_t index = lower.Find(exe);
+ if (index != -1)
+ return;
+ }
+
+ nsCOMPtr<nsILocalHandlerApp> aApp;
+ if (!GetLocalHandlerApp(appFilesystemCommand, aApp))
+ return;
+
+ // Save in our main tracking arrays
+ appList->AppendElement(aApp, false);
+ trackList.AppendElement(lower);
+}
+
+// Helper routine that handles a compare between a path
+// and an array of paths.
+static bool IsPathInList(nsAString& appPath,
+ nsTArray<nsString>& trackList)
+{
+ // trackList data is always lowercase, see ProcessPath
+ // above.
+ nsAutoString tmp(appPath);
+ ToLowerCase(tmp);
+
+ for (uint32_t i = 0; i < trackList.Length(); i++) {
+ if (tmp.Equals(trackList[i]))
+ return true;
+ }
+ return false;
+}
+
+/**
+ * Returns a list of nsILocalHandlerApp objects containing local
+ * handlers associated with this mimeinfo. Implemented per
+ * platform using information in this object to generate the
+ * best list. Typically used for an "open with" style user
+ * option.
+ *
+ * @return nsIArray of nsILocalHandlerApp
+ */
+NS_IMETHODIMP
+nsMIMEInfoWin::GetPossibleLocalHandlers(nsIArray **_retval)
+{
+ nsresult rv;
+
+ *_retval = nullptr;
+
+ nsCOMPtr<nsIMutableArray> appList =
+ do_CreateInstance("@mozilla.org/array;1");
+
+ if (!appList)
+ return NS_ERROR_FAILURE;
+
+ nsTArray<nsString> trackList;
+
+ nsAutoCString fileExt;
+ GetPrimaryExtension(fileExt);
+
+ nsCOMPtr<nsIWindowsRegKey> regKey =
+ do_CreateInstance("@mozilla.org/windows-registry-key;1");
+ if (!regKey)
+ return NS_ERROR_FAILURE;
+ nsCOMPtr<nsIWindowsRegKey> appKey =
+ do_CreateInstance("@mozilla.org/windows-registry-key;1");
+ if (!appKey)
+ return NS_ERROR_FAILURE;
+
+ nsAutoString workingRegistryPath;
+
+ bool extKnown = false;
+ if (fileExt.IsEmpty()) {
+ extKnown = true;
+ // Mime type discovery is possible in some cases, through
+ // HKEY_CLASSES_ROOT\MIME\Database\Content Type, however, a number
+ // of file extensions related to mime type are simply not defined,
+ // (application/rss+xml & application/atom+xml are good examples)
+ // in which case we can only provide a generic list.
+ nsAutoCString mimeType;
+ GetMIMEType(mimeType);
+ if (!mimeType.IsEmpty()) {
+ workingRegistryPath.AppendLiteral("MIME\\Database\\Content Type\\");
+ workingRegistryPath.Append(NS_ConvertASCIItoUTF16(mimeType));
+
+ rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT,
+ workingRegistryPath,
+ nsIWindowsRegKey::ACCESS_QUERY_VALUE);
+ if(NS_SUCCEEDED(rv)) {
+ nsAutoString mimeFileExt;
+ if (NS_SUCCEEDED(regKey->ReadStringValue(EmptyString(), mimeFileExt))) {
+ CopyUTF16toUTF8(mimeFileExt, fileExt);
+ extKnown = false;
+ }
+ }
+ }
+ }
+
+ nsAutoString fileExtToUse;
+ if (fileExt.First() != '.')
+ fileExtToUse = char16_t('.');
+ fileExtToUse.Append(NS_ConvertUTF8toUTF16(fileExt));
+
+ // Note, the order in which these occur has an effect on the
+ // validity of the resulting display list.
+
+ if (!extKnown) {
+ // 1) Get the default handler if it exists
+ workingRegistryPath = fileExtToUse;
+
+ rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT,
+ workingRegistryPath,
+ nsIWindowsRegKey::ACCESS_QUERY_VALUE);
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoString appProgId;
+ if (NS_SUCCEEDED(regKey->ReadStringValue(EmptyString(), appProgId))) {
+ // Bug 358297 - ignore the embedded internet explorer handler
+ if (appProgId != NS_LITERAL_STRING("XPSViewer.Document")) {
+ nsAutoString appFilesystemCommand;
+ if (GetProgIDVerbCommandHandler(appProgId,
+ appFilesystemCommand,
+ false) &&
+ !IsPathInList(appFilesystemCommand, trackList)) {
+ ProcessPath(appList, trackList, appFilesystemCommand);
+ }
+ }
+ }
+ regKey->Close();
+ }
+
+
+ // 2) list HKEY_CLASSES_ROOT\.ext\OpenWithList
+
+ workingRegistryPath = fileExtToUse;
+ workingRegistryPath.AppendLiteral("\\OpenWithList");
+
+ rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT,
+ workingRegistryPath,
+ nsIWindowsRegKey::ACCESS_QUERY_VALUE);
+ if (NS_SUCCEEDED(rv)) {
+ uint32_t count = 0;
+ if (NS_SUCCEEDED(regKey->GetValueCount(&count)) && count > 0) {
+ for (uint32_t index = 0; index < count; index++) {
+ nsAutoString appName;
+ if (NS_FAILED(regKey->GetValueName(index, appName)))
+ continue;
+
+ // HKEY_CLASSES_ROOT\Applications\firefox.exe = "path params"
+ nsAutoString appFilesystemCommand;
+ if (!GetAppsVerbCommandHandler(appName,
+ appFilesystemCommand,
+ false) ||
+ IsPathInList(appFilesystemCommand, trackList))
+ continue;
+ ProcessPath(appList, trackList, appFilesystemCommand);
+ }
+ }
+ regKey->Close();
+ }
+
+
+ // 3) List HKEY_CLASSES_ROOT\.ext\OpenWithProgids, with the
+ // different step of resolving the progids for the command handler.
+
+ workingRegistryPath = fileExtToUse;
+ workingRegistryPath.AppendLiteral("\\OpenWithProgids");
+
+ rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT,
+ workingRegistryPath,
+ nsIWindowsRegKey::ACCESS_QUERY_VALUE);
+ if (NS_SUCCEEDED(rv)) {
+ uint32_t count = 0;
+ if (NS_SUCCEEDED(regKey->GetValueCount(&count)) && count > 0) {
+ for (uint32_t index = 0; index < count; index++) {
+ // HKEY_CLASSES_ROOT\.ext\OpenWithProgids\Windows.XPSReachViewer
+ nsAutoString appProgId;
+ if (NS_FAILED(regKey->GetValueName(index, appProgId)))
+ continue;
+
+ nsAutoString appFilesystemCommand;
+ if (!GetProgIDVerbCommandHandler(appProgId,
+ appFilesystemCommand,
+ false) ||
+ IsPathInList(appFilesystemCommand, trackList))
+ continue;
+ ProcessPath(appList, trackList, appFilesystemCommand);
+ }
+ }
+ regKey->Close();
+ }
+
+
+ // 4) Add any non configured applications located in the MRU list
+
+ // HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion
+ // \Explorer\FileExts\.ext\OpenWithList
+ workingRegistryPath =
+ NS_LITERAL_STRING("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts\\");
+ workingRegistryPath += fileExtToUse;
+ workingRegistryPath.AppendLiteral("\\OpenWithList");
+
+ rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER,
+ workingRegistryPath,
+ nsIWindowsRegKey::ACCESS_QUERY_VALUE);
+ if (NS_SUCCEEDED(rv)) {
+ uint32_t count = 0;
+ if (NS_SUCCEEDED(regKey->GetValueCount(&count)) && count > 0) {
+ for (uint32_t index = 0; index < count; index++) {
+ nsAutoString appName, appValue;
+ if (NS_FAILED(regKey->GetValueName(index, appName)))
+ continue;
+ if (appName.EqualsLiteral("MRUList"))
+ continue;
+ if (NS_FAILED(regKey->ReadStringValue(appName, appValue)))
+ continue;
+
+ // HKEY_CLASSES_ROOT\Applications\firefox.exe = "path params"
+ nsAutoString appFilesystemCommand;
+ if (!GetAppsVerbCommandHandler(appValue,
+ appFilesystemCommand,
+ false) ||
+ IsPathInList(appFilesystemCommand, trackList))
+ continue;
+ ProcessPath(appList, trackList, appFilesystemCommand);
+ }
+ }
+ }
+
+
+ // 5) Add any non configured progids in the MRU list, with the
+ // different step of resolving the progids for the command handler.
+
+ // HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion
+ // \Explorer\FileExts\.ext\OpenWithProgids
+ workingRegistryPath =
+ NS_LITERAL_STRING("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts\\");
+ workingRegistryPath += fileExtToUse;
+ workingRegistryPath.AppendLiteral("\\OpenWithProgids");
+
+ regKey->Open(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER,
+ workingRegistryPath,
+ nsIWindowsRegKey::ACCESS_QUERY_VALUE);
+ if (NS_SUCCEEDED(rv)) {
+ uint32_t count = 0;
+ if (NS_SUCCEEDED(regKey->GetValueCount(&count)) && count > 0) {
+ for (uint32_t index = 0; index < count; index++) {
+ nsAutoString appIndex, appProgId;
+ if (NS_FAILED(regKey->GetValueName(index, appProgId)))
+ continue;
+
+ nsAutoString appFilesystemCommand;
+ if (!GetProgIDVerbCommandHandler(appProgId,
+ appFilesystemCommand,
+ false) ||
+ IsPathInList(appFilesystemCommand, trackList))
+ continue;
+ ProcessPath(appList, trackList, appFilesystemCommand);
+ }
+ }
+ regKey->Close();
+ }
+
+
+ // 6) Check the perceived type value, and use this to lookup the perceivedtype
+ // open with list.
+ // http://msdn2.microsoft.com/en-us/library/aa969373.aspx
+
+ workingRegistryPath = fileExtToUse;
+
+ regKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT,
+ workingRegistryPath,
+ nsIWindowsRegKey::ACCESS_QUERY_VALUE);
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoString perceivedType;
+ rv = regKey->ReadStringValue(NS_LITERAL_STRING("PerceivedType"),
+ perceivedType);
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoString openWithListPath(NS_LITERAL_STRING("SystemFileAssociations\\"));
+ openWithListPath.Append(perceivedType); // no period
+ openWithListPath.AppendLiteral("\\OpenWithList");
+
+ nsresult rv = appKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT,
+ openWithListPath,
+ nsIWindowsRegKey::ACCESS_QUERY_VALUE);
+ if (NS_SUCCEEDED(rv)) {
+ uint32_t count = 0;
+ if (NS_SUCCEEDED(regKey->GetValueCount(&count)) && count > 0) {
+ for (uint32_t index = 0; index < count; index++) {
+ nsAutoString appName;
+ if (NS_FAILED(regKey->GetValueName(index, appName)))
+ continue;
+
+ // HKEY_CLASSES_ROOT\Applications\firefox.exe = "path params"
+ nsAutoString appFilesystemCommand;
+ if (!GetAppsVerbCommandHandler(appName, appFilesystemCommand,
+ false) ||
+ IsPathInList(appFilesystemCommand, trackList))
+ continue;
+ ProcessPath(appList, trackList, appFilesystemCommand);
+ }
+ }
+ }
+ }
+ }
+ } // extKnown == false
+
+
+ // 7) list global HKEY_CLASSES_ROOT\*\OpenWithList
+ // Listing general purpose handlers, not specific to a mime type or file extension
+
+ workingRegistryPath = NS_LITERAL_STRING("*\\OpenWithList");
+
+ rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT,
+ workingRegistryPath,
+ nsIWindowsRegKey::ACCESS_QUERY_VALUE);
+ if (NS_SUCCEEDED(rv)) {
+ uint32_t count = 0;
+ if (NS_SUCCEEDED(regKey->GetValueCount(&count)) && count > 0) {
+ for (uint32_t index = 0; index < count; index++) {
+ nsAutoString appName;
+ if (NS_FAILED(regKey->GetValueName(index, appName)))
+ continue;
+
+ // HKEY_CLASSES_ROOT\Applications\firefox.exe = "path params"
+ nsAutoString appFilesystemCommand;
+ if (!GetAppsVerbCommandHandler(appName, appFilesystemCommand,
+ false) ||
+ IsPathInList(appFilesystemCommand, trackList))
+ continue;
+ ProcessPath(appList, trackList, appFilesystemCommand);
+ }
+ }
+ regKey->Close();
+ }
+
+
+ // 8) General application's list - not file extension specific on windows
+ workingRegistryPath = NS_LITERAL_STRING("Applications");
+
+ rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT,
+ workingRegistryPath,
+ nsIWindowsRegKey::ACCESS_ENUMERATE_SUB_KEYS|
+ nsIWindowsRegKey::ACCESS_QUERY_VALUE);
+ if (NS_SUCCEEDED(rv)) {
+ uint32_t count = 0;
+ if (NS_SUCCEEDED(regKey->GetChildCount(&count)) && count > 0) {
+ for (uint32_t index = 0; index < count; index++) {
+ nsAutoString appName;
+ if (NS_FAILED(regKey->GetChildName(index, appName)))
+ continue;
+
+ // HKEY_CLASSES_ROOT\Applications\firefox.exe = "path params"
+ nsAutoString appFilesystemCommand;
+ if (!GetAppsVerbCommandHandler(appName, appFilesystemCommand,
+ false) ||
+ IsPathInList(appFilesystemCommand, trackList))
+ continue;
+ ProcessPath(appList, trackList, appFilesystemCommand);
+ }
+ }
+ }
+
+ // Return to the caller
+ *_retval = appList;
+ NS_ADDREF(*_retval);
+
+ return NS_OK;
+}
diff --git a/uriloader/exthandler/win/nsMIMEInfoWin.h b/uriloader/exthandler/win/nsMIMEInfoWin.h
new file mode 100644
index 0000000000..0a5b678e8a
--- /dev/null
+++ b/uriloader/exthandler/win/nsMIMEInfoWin.h
@@ -0,0 +1,71 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMIMEInfoWin_h_
+#define nsMIMEInfoWin_h_
+
+#include "nsMIMEInfoImpl.h"
+#include "nsIPropertyBag.h"
+#include "nsIMutableArray.h"
+#include "nsTArray.h"
+
+class nsMIMEInfoWin : public nsMIMEInfoBase, public nsIPropertyBag {
+ virtual ~nsMIMEInfoWin();
+
+ public:
+ nsMIMEInfoWin(const char* aType = "") : nsMIMEInfoBase(aType) {}
+ nsMIMEInfoWin(const nsACString& aMIMEType) : nsMIMEInfoBase(aMIMEType) {}
+ nsMIMEInfoWin(const nsACString& aType, HandlerClass aClass) :
+ nsMIMEInfoBase(aType, aClass) {}
+
+ NS_IMETHOD LaunchWithFile(nsIFile* aFile);
+ NS_IMETHOD GetHasDefaultHandler(bool * _retval);
+ NS_IMETHOD GetPossibleLocalHandlers(nsIArray **_retval);
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIPROPERTYBAG
+
+ void SetDefaultApplicationHandler(nsIFile* aDefaultApplication)
+ {
+ mDefaultApplication = aDefaultApplication;
+ }
+
+ protected:
+ virtual nsresult LoadUriInternal(nsIURI *aURI);
+ virtual nsresult LaunchDefaultWithFile(nsIFile* aFile);
+
+ private:
+ nsCOMPtr<nsIFile> mDefaultApplication;
+
+ // Given a path to a local handler, return its
+ // nsILocalHandlerApp instance.
+ bool GetLocalHandlerApp(const nsAString& aCommandHandler,
+ nsCOMPtr<nsILocalHandlerApp>& aApp);
+
+ // Return the cleaned up file path associated
+ // with a command verb located in root/Applications.
+ bool GetAppsVerbCommandHandler(const nsAString& appExeName,
+ nsAString& applicationPath,
+ bool bEdit);
+
+ // Return the cleaned up file path associated
+ // with a progid command verb located in root.
+ bool GetProgIDVerbCommandHandler(const nsAString& appProgIDName,
+ nsAString& applicationPath,
+ bool bEdit);
+
+ // Lookup a rundll command handler and return
+ // a populated command template for use with rundll32.exe.
+ bool GetDllLaunchInfo(nsIFile * aDll,
+ nsIFile * aFile,
+ nsAString& args, bool bEdit);
+
+ // Helper routine used in tracking app lists
+ void ProcessPath(nsCOMPtr<nsIMutableArray>& appList,
+ nsTArray<nsString>& trackList,
+ const nsAString& appFilesystemCommand);
+
+};
+
+#endif
diff --git a/uriloader/exthandler/win/nsOSHelperAppService.cpp b/uriloader/exthandler/win/nsOSHelperAppService.cpp
new file mode 100644
index 0000000000..f01f3b49b5
--- /dev/null
+++ b/uriloader/exthandler/win/nsOSHelperAppService.cpp
@@ -0,0 +1,626 @@
+/* -*- Mode: C++; tab-width: 3; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim:set ts=2 sts=2 sw=2 et cin:
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsOSHelperAppService.h"
+#include "nsISupports.h"
+#include "nsString.h"
+#include "nsXPIDLString.h"
+#include "nsIURL.h"
+#include "nsIMIMEInfo.h"
+#include "nsMIMEInfoWin.h"
+#include "nsMimeTypes.h"
+#include "nsIProcess.h"
+#include "plstr.h"
+#include "nsAutoPtr.h"
+#include "nsNativeCharsetUtils.h"
+#include "nsLocalFile.h"
+#include "nsIWindowsRegKey.h"
+#include "mozilla/UniquePtrExtensions.h"
+#include "mozilla/WindowsVersion.h"
+
+// shellapi.h is needed to build with WIN32_LEAN_AND_MEAN
+#include <shellapi.h>
+#include <shlwapi.h>
+
+#define LOG(args) MOZ_LOG(mLog, mozilla::LogLevel::Debug, args)
+
+// helper methods: forward declarations...
+static nsresult GetExtensionFrom4xRegistryInfo(const nsACString& aMimeType,
+ nsString& aFileExtension);
+static nsresult GetExtensionFromWindowsMimeDatabase(const nsACString& aMimeType,
+ nsString& aFileExtension);
+
+nsOSHelperAppService::nsOSHelperAppService() :
+ nsExternalHelperAppService()
+ , mAppAssoc(nullptr)
+{
+ CoInitialize(nullptr);
+ CoCreateInstance(CLSID_ApplicationAssociationRegistration, nullptr,
+ CLSCTX_INPROC, IID_IApplicationAssociationRegistration,
+ (void**)&mAppAssoc);
+}
+
+nsOSHelperAppService::~nsOSHelperAppService()
+{
+ if (mAppAssoc)
+ mAppAssoc->Release();
+ mAppAssoc = nullptr;
+ CoUninitialize();
+}
+
+// The windows registry provides a mime database key which lists a set of mime types and corresponding "Extension" values.
+// we can use this to look up our mime type to see if there is a preferred extension for the mime type.
+static nsresult GetExtensionFromWindowsMimeDatabase(const nsACString& aMimeType,
+ nsString& aFileExtension)
+{
+ nsAutoString mimeDatabaseKey;
+ mimeDatabaseKey.AssignLiteral("MIME\\Database\\Content Type\\");
+
+ AppendASCIItoUTF16(aMimeType, mimeDatabaseKey);
+
+ nsCOMPtr<nsIWindowsRegKey> regKey =
+ do_CreateInstance("@mozilla.org/windows-registry-key;1");
+ if (!regKey)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ nsresult rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT,
+ mimeDatabaseKey,
+ nsIWindowsRegKey::ACCESS_QUERY_VALUE);
+
+ if (NS_SUCCEEDED(rv))
+ regKey->ReadStringValue(NS_LITERAL_STRING("Extension"), aFileExtension);
+
+ return NS_OK;
+}
+
+// We have a serious problem!! I have this content type and the windows registry only gives me
+// helper apps based on extension. Right now, we really don't have a good place to go for
+// trying to figure out the extension for a particular mime type....One short term hack is to look
+// this information in 4.x (it's stored in the windows regsitry).
+static nsresult GetExtensionFrom4xRegistryInfo(const nsACString& aMimeType,
+ nsString& aFileExtension)
+{
+ nsCOMPtr<nsIWindowsRegKey> regKey =
+ do_CreateInstance("@mozilla.org/windows-registry-key;1");
+ if (!regKey)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ nsresult rv = regKey->
+ Open(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER,
+ NS_LITERAL_STRING("Software\\Netscape\\Netscape Navigator\\Suffixes"),
+ nsIWindowsRegKey::ACCESS_QUERY_VALUE);
+ if (NS_FAILED(rv))
+ return NS_ERROR_NOT_AVAILABLE;
+
+ rv = regKey->ReadStringValue(NS_ConvertASCIItoUTF16(aMimeType),
+ aFileExtension);
+ if (NS_FAILED(rv))
+ return NS_OK;
+
+ aFileExtension.Insert(char16_t('.'), 0);
+
+ // this may be a comma separated list of extensions...just take the
+ // first one for now...
+
+ int32_t pos = aFileExtension.FindChar(char16_t(','));
+ if (pos > 0) {
+ // we have a comma separated list of types...
+ // truncate everything after the first comma (including the comma)
+ aFileExtension.Truncate(pos);
+ }
+
+ return NS_OK;
+}
+
+nsresult nsOSHelperAppService::OSProtocolHandlerExists(const char * aProtocolScheme, bool * aHandlerExists)
+{
+ // look up the protocol scheme in the windows registry....if we find a match then we have a handler for it...
+ *aHandlerExists = false;
+ if (aProtocolScheme && *aProtocolScheme)
+ {
+ // Vista: use new application association interface
+ if (mAppAssoc) {
+ wchar_t * pResult = nullptr;
+ NS_ConvertASCIItoUTF16 scheme(aProtocolScheme);
+ // We are responsible for freeing returned strings.
+ HRESULT hr = mAppAssoc->QueryCurrentDefault(scheme.get(),
+ AT_URLPROTOCOL, AL_EFFECTIVE,
+ &pResult);
+ if (SUCCEEDED(hr)) {
+ CoTaskMemFree(pResult);
+ *aHandlerExists = true;
+ }
+ return NS_OK;
+ }
+
+ HKEY hKey;
+ LONG err = ::RegOpenKeyExW(HKEY_CLASSES_ROOT,
+ NS_ConvertASCIItoUTF16(aProtocolScheme).get(),
+ 0,
+ KEY_QUERY_VALUE,
+ &hKey);
+ if (err == ERROR_SUCCESS)
+ {
+ err = ::RegQueryValueExW(hKey, L"URL Protocol",
+ nullptr, nullptr, nullptr, nullptr);
+ *aHandlerExists = (err == ERROR_SUCCESS);
+ // close the key
+ ::RegCloseKey(hKey);
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsOSHelperAppService::GetApplicationDescription(const nsACString& aScheme, nsAString& _retval)
+{
+ nsCOMPtr<nsIWindowsRegKey> regKey =
+ do_CreateInstance("@mozilla.org/windows-registry-key;1");
+ if (!regKey)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ NS_ConvertASCIItoUTF16 buf(aScheme);
+
+ if (mozilla::IsWin8OrLater()) {
+ wchar_t result[1024];
+ DWORD resultSize = 1024;
+ HRESULT hr = AssocQueryString(0x1000 /* ASSOCF_IS_PROTOCOL */,
+ ASSOCSTR_FRIENDLYAPPNAME,
+ buf.get(),
+ NULL,
+ result,
+ &resultSize);
+ if (SUCCEEDED(hr)) {
+ _retval = result;
+ return NS_OK;
+ }
+ }
+
+ if (mAppAssoc) {
+ // Vista: use new application association interface
+ wchar_t * pResult = nullptr;
+ // We are responsible for freeing returned strings.
+ HRESULT hr = mAppAssoc->QueryCurrentDefault(buf.get(),
+ AT_URLPROTOCOL, AL_EFFECTIVE,
+ &pResult);
+ if (SUCCEEDED(hr)) {
+ nsCOMPtr<nsIFile> app;
+ nsAutoString appInfo(pResult);
+ CoTaskMemFree(pResult);
+ if (NS_SUCCEEDED(GetDefaultAppInfo(appInfo, _retval, getter_AddRefs(app))))
+ return NS_OK;
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsCOMPtr<nsIFile> app;
+ GetDefaultAppInfo(buf, _retval, getter_AddRefs(app));
+
+ if (!_retval.Equals(buf))
+ return NS_OK;
+
+ // Fall back to full path
+ buf.AppendLiteral("\\shell\\open\\command");
+ nsresult rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT,
+ buf,
+ nsIWindowsRegKey::ACCESS_QUERY_VALUE);
+ if (NS_FAILED(rv))
+ return NS_ERROR_NOT_AVAILABLE;
+
+ rv = regKey->ReadStringValue(EmptyString(), _retval);
+
+ return NS_SUCCEEDED(rv) ? NS_OK : NS_ERROR_NOT_AVAILABLE;
+}
+
+// GetMIMEInfoFromRegistry: This function obtains the values of some of the nsIMIMEInfo
+// attributes for the mimeType/extension associated with the input registry key. The default
+// entry for that key is the name of a registry key under HKEY_CLASSES_ROOT. The default
+// value for *that* key is the descriptive name of the type. The EditFlags value is a binary
+// value; the low order bit of the third byte of which indicates that the user does not need
+// to be prompted.
+//
+// This function sets only the Description attribute of the input nsIMIMEInfo.
+/* static */
+nsresult nsOSHelperAppService::GetMIMEInfoFromRegistry(const nsAFlatString& fileType, nsIMIMEInfo *pInfo)
+{
+ nsresult rv = NS_OK;
+
+ NS_ENSURE_ARG(pInfo);
+ nsCOMPtr<nsIWindowsRegKey> regKey =
+ do_CreateInstance("@mozilla.org/windows-registry-key;1");
+ if (!regKey)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT,
+ fileType, nsIWindowsRegKey::ACCESS_QUERY_VALUE);
+ if (NS_FAILED(rv))
+ return NS_ERROR_FAILURE;
+
+ // OK, the default value here is the description of the type.
+ nsAutoString description;
+ rv = regKey->ReadStringValue(EmptyString(), description);
+ if (NS_SUCCEEDED(rv))
+ pInfo->SetDescription(description);
+
+ return NS_OK;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////////////
+// method overrides used to gather information from the windows registry for
+// various mime types.
+////////////////////////////////////////////////////////////////////////////////////////////////
+
+/// Looks up the type for the extension aExt and compares it to aType
+/* static */ bool
+nsOSHelperAppService::typeFromExtEquals(const char16_t* aExt, const char *aType)
+{
+ if (!aType)
+ return false;
+ nsAutoString fileExtToUse;
+ if (aExt[0] != char16_t('.'))
+ fileExtToUse = char16_t('.');
+
+ fileExtToUse.Append(aExt);
+
+ bool eq = false;
+ nsCOMPtr<nsIWindowsRegKey> regKey =
+ do_CreateInstance("@mozilla.org/windows-registry-key;1");
+ if (!regKey)
+ return eq;
+
+ nsresult rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT,
+ fileExtToUse,
+ nsIWindowsRegKey::ACCESS_QUERY_VALUE);
+ if (NS_FAILED(rv))
+ return eq;
+
+ nsAutoString type;
+ rv = regKey->ReadStringValue(NS_LITERAL_STRING("Content Type"), type);
+ if (NS_SUCCEEDED(rv))
+ eq = type.EqualsASCII(aType);
+
+ return eq;
+}
+
+// The "real" name of a given helper app (as specified by the path to the
+// executable file held in various registry keys) is stored n the VERSIONINFO
+// block in the file's resources. We need to find the path to the executable
+// and then retrieve the "FileDescription" field value from the file.
+nsresult
+nsOSHelperAppService::GetDefaultAppInfo(const nsAString& aAppInfo,
+ nsAString& aDefaultDescription,
+ nsIFile** aDefaultApplication)
+{
+ nsAutoString handlerCommand;
+
+ // If all else fails, use the file type key name, which will be
+ // something like "pngfile" for .pngs, "WMVFile" for .wmvs, etc.
+ aDefaultDescription = aAppInfo;
+ *aDefaultApplication = nullptr;
+
+ if (aAppInfo.IsEmpty())
+ return NS_ERROR_FAILURE;
+
+ // aAppInfo may be a file, file path, program id, or
+ // Applications reference -
+ // c:\dir\app.exe
+ // Applications\appfile.exe/dll (shell\open...)
+ // ProgID.progid (shell\open...)
+
+ nsAutoString handlerKeyName(aAppInfo);
+
+ nsCOMPtr<nsIWindowsRegKey> chkKey =
+ do_CreateInstance("@mozilla.org/windows-registry-key;1");
+ if (!chkKey)
+ return NS_ERROR_FAILURE;
+
+ nsresult rv = chkKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT,
+ handlerKeyName,
+ nsIWindowsRegKey::ACCESS_QUERY_VALUE);
+ if (NS_FAILED(rv)) {
+ // It's a file system path to a handler
+ handlerCommand.Assign(aAppInfo);
+ }
+ else {
+ handlerKeyName.AppendLiteral("\\shell\\open\\command");
+ nsCOMPtr<nsIWindowsRegKey> regKey =
+ do_CreateInstance("@mozilla.org/windows-registry-key;1");
+ if (!regKey)
+ return NS_ERROR_FAILURE;
+
+ nsresult rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT,
+ handlerKeyName,
+ nsIWindowsRegKey::ACCESS_QUERY_VALUE);
+ if (NS_FAILED(rv))
+ return NS_ERROR_FAILURE;
+
+ // OK, the default value here is the description of the type.
+ rv = regKey->ReadStringValue(EmptyString(), handlerCommand);
+ if (NS_FAILED(rv)) {
+
+ // Check if there is a DelegateExecute string
+ nsAutoString delegateExecute;
+ rv = regKey->ReadStringValue(NS_LITERAL_STRING("DelegateExecute"), delegateExecute);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Look for InProcServer32
+ nsAutoString delegateExecuteRegPath;
+ delegateExecuteRegPath.AssignLiteral("CLSID\\");
+ delegateExecuteRegPath.Append(delegateExecute);
+ delegateExecuteRegPath.AppendLiteral("\\InProcServer32");
+ rv = chkKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT,
+ delegateExecuteRegPath,
+ nsIWindowsRegKey::ACCESS_QUERY_VALUE);
+ if (NS_SUCCEEDED(rv)) {
+ rv = chkKey->ReadStringValue(EmptyString(), handlerCommand);
+ }
+
+ if (NS_FAILED(rv)) {
+ // Look for LocalServer32
+ delegateExecuteRegPath.AssignLiteral("CLSID\\");
+ delegateExecuteRegPath.Append(delegateExecute);
+ delegateExecuteRegPath.AppendLiteral("\\LocalServer32");
+ rv = chkKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT,
+ delegateExecuteRegPath,
+ nsIWindowsRegKey::ACCESS_QUERY_VALUE);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = chkKey->ReadStringValue(EmptyString(), handlerCommand);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ }
+
+ // XXX FIXME: If this fails, the UI will display the full command
+ // string.
+ // There are some rare cases this can happen - ["url.dll" -foo]
+ // for example won't resolve correctly to the system dir. The
+ // subsequent launch of the helper app will work though.
+ nsCOMPtr<nsILocalFileWin> lf = new nsLocalFile();
+ rv = lf->InitWithCommandLine(handlerCommand);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // The "FileDescription" field contains the actual name of the application.
+ lf->GetVersionInfoField("FileDescription", aDefaultDescription);
+ lf.forget(aDefaultApplication);
+
+ return NS_OK;
+}
+
+already_AddRefed<nsMIMEInfoWin> nsOSHelperAppService::GetByExtension(const nsAFlatString& aFileExt, const char *aTypeHint)
+{
+ if (aFileExt.IsEmpty())
+ return nullptr;
+
+ // Determine the mime type.
+ nsAutoCString typeToUse;
+ if (aTypeHint && *aTypeHint) {
+ typeToUse.Assign(aTypeHint);
+ } else if (!GetMIMETypeFromOSForExtension(NS_ConvertUTF16toUTF8(aFileExt), typeToUse)) {
+ return nullptr;
+ }
+
+ RefPtr<nsMIMEInfoWin> mimeInfo = new nsMIMEInfoWin(typeToUse);
+
+ // windows registry assumes your file extension is going to include the '.',
+ // but our APIs expect it to not be there, so make sure we normalize that bit.
+ nsAutoString fileExtToUse;
+ if (aFileExt.First() != char16_t('.'))
+ fileExtToUse = char16_t('.');
+
+ fileExtToUse.Append(aFileExt);
+
+ // don't append the '.' for our APIs.
+ mimeInfo->AppendExtension(NS_ConvertUTF16toUTF8(Substring(fileExtToUse, 1)));
+ mimeInfo->SetPreferredAction(nsIMIMEInfo::useSystemDefault);
+
+ nsAutoString appInfo;
+ bool found;
+
+ // Retrieve the default application for this extension
+ if (mAppAssoc) {
+ // Vista: use the new application association COM interfaces
+ // for resolving helpers.
+ nsString assocType(fileExtToUse);
+ wchar_t * pResult = nullptr;
+ HRESULT hr = mAppAssoc->QueryCurrentDefault(assocType.get(),
+ AT_FILEEXTENSION, AL_EFFECTIVE,
+ &pResult);
+ if (SUCCEEDED(hr)) {
+ found = true;
+ appInfo.Assign(pResult);
+ CoTaskMemFree(pResult);
+ }
+ else {
+ found = false;
+ }
+ }
+ else
+ {
+ nsCOMPtr<nsIWindowsRegKey> regKey =
+ do_CreateInstance("@mozilla.org/windows-registry-key;1");
+ if (!regKey)
+ return nullptr;
+ nsresult rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT,
+ fileExtToUse,
+ nsIWindowsRegKey::ACCESS_QUERY_VALUE);
+ if (NS_SUCCEEDED(rv)) {
+ found = NS_SUCCEEDED(regKey->ReadStringValue(EmptyString(),
+ appInfo));
+ }
+ }
+
+ // Bug 358297 - ignore the default handler, force the user to choose app
+ if (appInfo.EqualsLiteral("XPSViewer.Document"))
+ found = false;
+
+ if (!found) {
+ return nullptr;
+ }
+
+ // Get other nsIMIMEInfo fields from registry, if possible.
+ nsAutoString defaultDescription;
+ nsCOMPtr<nsIFile> defaultApplication;
+
+ if (NS_FAILED(GetDefaultAppInfo(appInfo, defaultDescription,
+ getter_AddRefs(defaultApplication)))) {
+ return nullptr;
+ }
+
+ mimeInfo->SetDefaultDescription(defaultDescription);
+ mimeInfo->SetDefaultApplicationHandler(defaultApplication);
+
+ // Grab the general description
+ GetMIMEInfoFromRegistry(appInfo, mimeInfo);
+
+ return mimeInfo.forget();
+}
+
+already_AddRefed<nsIMIMEInfo> nsOSHelperAppService::GetMIMEInfoFromOS(const nsACString& aMIMEType, const nsACString& aFileExt, bool *aFound)
+{
+ *aFound = true;
+
+ const nsCString& flatType = PromiseFlatCString(aMIMEType);
+ const nsCString& flatExt = PromiseFlatCString(aFileExt);
+
+ nsAutoString fileExtension;
+ /* XXX The Equals is a gross hack to wallpaper over the most common Win32
+ * extension issues caused by the fix for bug 116938. See bug
+ * 120327, comment 271 for why this is needed. Not even sure we
+ * want to remove this once we have fixed all this stuff to work
+ * right; any info we get from the OS on this type is pretty much
+ * useless....
+ * We'll do extension-based lookup for this type later in this function.
+ */
+ if (!aMIMEType.LowerCaseEqualsLiteral(APPLICATION_OCTET_STREAM)) {
+ // (1) try to use the windows mime database to see if there is a mapping to a file extension
+ // (2) try to see if we have some left over 4.x registry info we can peek at...
+ GetExtensionFromWindowsMimeDatabase(aMIMEType, fileExtension);
+ LOG(("Windows mime database: extension '%s'\n", fileExtension.get()));
+ if (fileExtension.IsEmpty()) {
+ GetExtensionFrom4xRegistryInfo(aMIMEType, fileExtension);
+ LOG(("4.x Registry: extension '%s'\n", fileExtension.get()));
+ }
+ }
+ // If we found an extension for the type, do the lookup
+ RefPtr<nsMIMEInfoWin> mi;
+ if (!fileExtension.IsEmpty())
+ mi = GetByExtension(fileExtension, flatType.get());
+ LOG(("Extension lookup on '%s' found: 0x%p\n", fileExtension.get(), mi.get()));
+
+ bool hasDefault = false;
+ if (mi) {
+ mi->GetHasDefaultHandler(&hasDefault);
+ // OK. We might have the case that |aFileExt| is a valid extension for the
+ // mimetype we were given. In that case, we do want to append aFileExt
+ // to the mimeinfo that we have. (E.g.: We are asked for video/mpeg and
+ // .mpg, but the primary extension for video/mpeg is .mpeg. But because
+ // .mpg is an extension for video/mpeg content, we want to append it)
+ if (!aFileExt.IsEmpty() && typeFromExtEquals(NS_ConvertUTF8toUTF16(flatExt).get(), flatType.get())) {
+ LOG(("Appending extension '%s' to mimeinfo, because its mimetype is '%s'\n",
+ flatExt.get(), flatType.get()));
+ bool extExist = false;
+ mi->ExtensionExists(aFileExt, &extExist);
+ if (!extExist)
+ mi->AppendExtension(aFileExt);
+ }
+ }
+ if (!mi || !hasDefault) {
+ RefPtr<nsMIMEInfoWin> miByExt =
+ GetByExtension(NS_ConvertUTF8toUTF16(aFileExt), flatType.get());
+ LOG(("Ext. lookup for '%s' found 0x%p\n", flatExt.get(), miByExt.get()));
+ if (!miByExt && mi)
+ return mi.forget();
+ if (miByExt && !mi) {
+ return miByExt.forget();
+ }
+ if (!miByExt && !mi) {
+ *aFound = false;
+ mi = new nsMIMEInfoWin(flatType);
+ if (!aFileExt.IsEmpty()) {
+ mi->AppendExtension(aFileExt);
+ }
+
+ return mi.forget();
+ }
+
+ // if we get here, mi has no default app. copy from extension lookup.
+ nsCOMPtr<nsIFile> defaultApp;
+ nsAutoString desc;
+ miByExt->GetDefaultDescription(desc);
+
+ mi->SetDefaultDescription(desc);
+ }
+ return mi.forget();
+}
+
+NS_IMETHODIMP
+nsOSHelperAppService::GetProtocolHandlerInfoFromOS(const nsACString &aScheme,
+ bool *found,
+ nsIHandlerInfo **_retval)
+{
+ NS_ASSERTION(!aScheme.IsEmpty(), "No scheme was specified!");
+
+ nsresult rv = OSProtocolHandlerExists(nsPromiseFlatCString(aScheme).get(),
+ found);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsMIMEInfoWin *handlerInfo =
+ new nsMIMEInfoWin(aScheme, nsMIMEInfoBase::eProtocolInfo);
+ NS_ENSURE_TRUE(handlerInfo, NS_ERROR_OUT_OF_MEMORY);
+ NS_ADDREF(*_retval = handlerInfo);
+
+ if (!*found) {
+ // Code that calls this requires an object regardless if the OS has
+ // something for us, so we return the empty object.
+ return NS_OK;
+ }
+
+ nsAutoString desc;
+ GetApplicationDescription(aScheme, desc);
+ handlerInfo->SetDefaultDescription(desc);
+
+ return NS_OK;
+}
+
+bool
+nsOSHelperAppService::GetMIMETypeFromOSForExtension(const nsACString& aExtension,
+ nsACString& aMIMEType)
+{
+ if (aExtension.IsEmpty())
+ return false;
+
+ // windows registry assumes your file extension is going to include the '.'.
+ // so make sure it's there...
+ nsAutoString fileExtToUse;
+ if (aExtension.First() != '.')
+ fileExtToUse = char16_t('.');
+
+ AppendUTF8toUTF16(aExtension, fileExtToUse);
+
+ // Try to get an entry from the windows registry.
+ nsCOMPtr<nsIWindowsRegKey> regKey =
+ do_CreateInstance("@mozilla.org/windows-registry-key;1");
+ if (!regKey)
+ return false;
+
+ nsresult rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT,
+ fileExtToUse,
+ nsIWindowsRegKey::ACCESS_QUERY_VALUE);
+ if (NS_FAILED(rv))
+ return false;
+
+ nsAutoString mimeType;
+ if (NS_FAILED(regKey->ReadStringValue(NS_LITERAL_STRING("Content Type"),
+ mimeType)) || mimeType.IsEmpty()) {
+ return false;
+ }
+ // Content-Type is always in ASCII
+ aMIMEType.Truncate();
+ LossyAppendUTF16toASCII(mimeType, aMIMEType);
+ return true;
+}
diff --git a/uriloader/exthandler/win/nsOSHelperAppService.h b/uriloader/exthandler/win/nsOSHelperAppService.h
new file mode 100644
index 0000000000..b00529433c
--- /dev/null
+++ b/uriloader/exthandler/win/nsOSHelperAppService.h
@@ -0,0 +1,65 @@
+/* -*- Mode: C++; tab-width: 3; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsOSHelperAppService_h__
+#define nsOSHelperAppService_h__
+
+// The OS helper app service is a subclass of nsExternalHelperAppService and is implemented on each
+// platform. It contains platform specific code for finding helper applications for a given mime type
+// in addition to launching those applications.
+
+#include "nsExternalHelperAppService.h"
+#include "nsCExternalHandlerService.h"
+#include "nsMIMEInfoImpl.h"
+#include "nsCOMPtr.h"
+#include <windows.h>
+
+#ifdef _WIN32_WINNT
+#undef _WIN32_WINNT
+#endif
+#define _WIN32_WINNT 0x0600
+#include <shlobj.h>
+
+class nsMIMEInfoWin;
+
+class nsOSHelperAppService : public nsExternalHelperAppService
+{
+public:
+ nsOSHelperAppService();
+ virtual ~nsOSHelperAppService();
+
+ // override nsIExternalProtocolService methods
+ nsresult OSProtocolHandlerExists(const char * aProtocolScheme, bool * aHandlerExists);
+ nsresult LoadUriInternal(nsIURI * aURL);
+ NS_IMETHOD GetApplicationDescription(const nsACString& aScheme, nsAString& _retval);
+
+ // method overrides for windows registry look up steps....
+ already_AddRefed<nsIMIMEInfo> GetMIMEInfoFromOS(const nsACString& aMIMEType, const nsACString& aFileExt, bool *aFound);
+ NS_IMETHOD GetProtocolHandlerInfoFromOS(const nsACString &aScheme,
+ bool *found,
+ nsIHandlerInfo **_retval);
+ virtual bool GetMIMETypeFromOSForExtension(const nsACString& aExtension,
+ nsACString& aMIMEType) override;
+
+ /** Get the string value of a registry value and store it in result.
+ * @return true on success, false on failure
+ */
+ static bool GetValueString(HKEY hKey, const char16_t* pValueName, nsAString& result);
+
+protected:
+ nsresult GetDefaultAppInfo(const nsAString& aTypeName, nsAString& aDefaultDescription, nsIFile** aDefaultApplication);
+ // Lookup a mime info by extension, using an optional type hint
+ already_AddRefed<nsMIMEInfoWin> GetByExtension(const nsAFlatString& aFileExt, const char *aTypeHint = nullptr);
+ nsresult FindOSMimeInfoForType(const char * aMimeContentType, nsIURI * aURI, char ** aFileExtension, nsIMIMEInfo ** aMIMEInfo);
+
+ static nsresult GetMIMEInfoFromRegistry(const nsAFlatString& fileType, nsIMIMEInfo *pInfo);
+ /// Looks up the type for the extension aExt and compares it to aType
+ static bool typeFromExtEquals(const char16_t* aExt, const char *aType);
+
+private:
+ IApplicationAssociationRegistration* mAppAssoc;
+};
+
+#endif // nsOSHelperAppService_h__
diff --git a/uriloader/moz.build b/uriloader/moz.build
new file mode 100644
index 0000000000..840c9cc05e
--- /dev/null
+++ b/uriloader/moz.build
@@ -0,0 +1,11 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DIRS += [
+ 'base',
+ 'exthandler',
+ 'prefetch',
+]
diff --git a/uriloader/prefetch/OfflineCacheUpdateChild.cpp b/uriloader/prefetch/OfflineCacheUpdateChild.cpp
new file mode 100644
index 0000000000..555508c373
--- /dev/null
+++ b/uriloader/prefetch/OfflineCacheUpdateChild.cpp
@@ -0,0 +1,528 @@
+/* -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "BackgroundUtils.h"
+#include "OfflineCacheUpdateChild.h"
+#include "nsOfflineCacheUpdate.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/TabChild.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "mozilla/net/NeckoCommon.h"
+
+#include "nsIApplicationCacheContainer.h"
+#include "nsIApplicationCacheChannel.h"
+#include "nsIApplicationCacheService.h"
+#include "nsIDocShell.h"
+#include "nsIDocShellTreeItem.h"
+#include "nsIDocShellTreeOwner.h"
+#include "nsPIDOMWindow.h"
+#include "nsIDOMOfflineResourceList.h"
+#include "nsIDocument.h"
+#include "nsIObserverService.h"
+#include "nsIURL.h"
+#include "nsITabChild.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsServiceManagerUtils.h"
+#include "nsStreamUtils.h"
+#include "nsThreadUtils.h"
+#include "nsProxyRelease.h"
+#include "mozilla/Logging.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+
+using namespace mozilla::ipc;
+using namespace mozilla::net;
+using mozilla::dom::TabChild;
+using mozilla::dom::ContentChild;
+
+//
+// To enable logging (see mozilla/Logging.h for full details):
+//
+// set MOZ_LOG=nsOfflineCacheUpdate:5
+// set MOZ_LOG_FILE=offlineupdate.log
+//
+// this enables LogLevel::Debug level information and places all output in
+// the file offlineupdate.log
+//
+extern mozilla::LazyLogModule gOfflineCacheUpdateLog;
+
+#undef LOG
+#define LOG(args) MOZ_LOG(gOfflineCacheUpdateLog, mozilla::LogLevel::Debug, args)
+
+#undef LOG_ENABLED
+#define LOG_ENABLED() MOZ_LOG_TEST(gOfflineCacheUpdateLog, mozilla::LogLevel::Debug)
+
+namespace mozilla {
+namespace docshell {
+
+//-----------------------------------------------------------------------------
+// OfflineCacheUpdateChild::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_INTERFACE_MAP_BEGIN(OfflineCacheUpdateChild)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+ NS_INTERFACE_MAP_ENTRY(nsIOfflineCacheUpdate)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_ADDREF(OfflineCacheUpdateChild)
+NS_IMPL_RELEASE(OfflineCacheUpdateChild)
+
+//-----------------------------------------------------------------------------
+// OfflineCacheUpdateChild <public>
+//-----------------------------------------------------------------------------
+
+OfflineCacheUpdateChild::OfflineCacheUpdateChild(nsPIDOMWindowInner* aWindow)
+ : mState(STATE_UNINITIALIZED)
+ , mIsUpgrade(false)
+ , mSucceeded(false)
+ , mWindow(aWindow)
+ , mByteProgress(0)
+{
+}
+
+OfflineCacheUpdateChild::~OfflineCacheUpdateChild()
+{
+ LOG(("OfflineCacheUpdateChild::~OfflineCacheUpdateChild [%p]", this));
+}
+
+void
+OfflineCacheUpdateChild::GatherObservers(nsCOMArray<nsIOfflineCacheUpdateObserver> &aObservers)
+{
+ for (int32_t i = 0; i < mWeakObservers.Count(); i++) {
+ nsCOMPtr<nsIOfflineCacheUpdateObserver> observer =
+ do_QueryReferent(mWeakObservers[i]);
+ if (observer)
+ aObservers.AppendObject(observer);
+ else
+ mWeakObservers.RemoveObjectAt(i--);
+ }
+
+ for (int32_t i = 0; i < mObservers.Count(); i++) {
+ aObservers.AppendObject(mObservers[i]);
+ }
+}
+
+void
+OfflineCacheUpdateChild::SetDocument(nsIDOMDocument *aDocument)
+{
+ // The design is one document for one cache update on the content process.
+ NS_ASSERTION(!mDocument, "Setting more then a single document on a child offline cache update");
+
+ LOG(("Document %p added to update child %p", aDocument, this));
+
+ // Add document only if it was not loaded from an offline cache.
+ // If it were loaded from an offline cache then it has already
+ // been associated with it and must not be again cached as
+ // implicit (which are the reasons we collect documents here).
+ nsCOMPtr<nsIDocument> document = do_QueryInterface(aDocument);
+ if (!document)
+ return;
+
+ nsIChannel* channel = document->GetChannel();
+ nsCOMPtr<nsIApplicationCacheChannel> appCacheChannel =
+ do_QueryInterface(channel);
+ if (!appCacheChannel)
+ return;
+
+ bool loadedFromAppCache;
+ appCacheChannel->GetLoadedFromApplicationCache(&loadedFromAppCache);
+ if (loadedFromAppCache)
+ return;
+
+ mDocument = aDocument;
+}
+
+nsresult
+OfflineCacheUpdateChild::AssociateDocument(nsIDOMDocument *aDocument,
+ nsIApplicationCache *aApplicationCache)
+{
+ // Check that the document that requested this update was
+ // previously associated with an application cache. If not, it
+ // should be associated with the new one.
+ nsCOMPtr<nsIApplicationCacheContainer> container =
+ do_QueryInterface(aDocument);
+ if (!container)
+ return NS_OK;
+
+ nsCOMPtr<nsIApplicationCache> existingCache;
+ nsresult rv = container->GetApplicationCache(getter_AddRefs(existingCache));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!existingCache) {
+ if (LOG_ENABLED()) {
+ nsAutoCString clientID;
+ if (aApplicationCache) {
+ aApplicationCache->GetClientID(clientID);
+ }
+ LOG(("Update %p: associating app cache %s to document %p",
+ this, clientID.get(), aDocument));
+ }
+
+ rv = container->SetApplicationCache(aApplicationCache);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// OfflineCacheUpdateChild::nsIOfflineCacheUpdate
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+OfflineCacheUpdateChild::Init(nsIURI *aManifestURI,
+ nsIURI *aDocumentURI,
+ nsIPrincipal *aLoadingPrincipal,
+ nsIDOMDocument *aDocument,
+ nsIFile *aCustomProfileDir)
+{
+ nsresult rv;
+
+ // Make sure the service has been initialized
+ nsOfflineCacheUpdateService* service =
+ nsOfflineCacheUpdateService::EnsureService();
+ if (!service)
+ return NS_ERROR_FAILURE;
+
+ if (aCustomProfileDir) {
+ NS_ERROR("Custom Offline Cache Update not supported on child process");
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ LOG(("OfflineCacheUpdateChild::Init [%p]", this));
+
+ // Only http and https applications are supported.
+ bool match;
+ rv = aManifestURI->SchemeIs("http", &match);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!match) {
+ rv = aManifestURI->SchemeIs("https", &match);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!match)
+ return NS_ERROR_ABORT;
+ }
+
+ mManifestURI = aManifestURI;
+
+ rv = mManifestURI->GetAsciiHost(mUpdateDomain);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mDocumentURI = aDocumentURI;
+ mLoadingPrincipal = aLoadingPrincipal;
+
+ mState = STATE_INITIALIZED;
+
+ if (aDocument)
+ SetDocument(aDocument);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+OfflineCacheUpdateChild::InitPartial(nsIURI *aManifestURI,
+ const nsACString& clientID,
+ nsIURI *aDocumentURI,
+ nsIPrincipal *aLoadingPrincipal)
+{
+ NS_NOTREACHED("Not expected to do partial offline cache updates"
+ " on the child process");
+ // For now leaving this method, we may discover we need it.
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+OfflineCacheUpdateChild::InitForUpdateCheck(nsIURI *aManifestURI,
+ nsIPrincipal* aLoadingPrincipal,
+ nsIObserver *aObserver)
+{
+ NS_NOTREACHED("Not expected to do only update checks"
+ " from the child process");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+OfflineCacheUpdateChild::GetUpdateDomain(nsACString &aUpdateDomain)
+{
+ NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED);
+
+ aUpdateDomain = mUpdateDomain;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+OfflineCacheUpdateChild::GetStatus(uint16_t *aStatus)
+{
+ switch (mState) {
+ case STATE_CHECKING :
+ *aStatus = nsIDOMOfflineResourceList::CHECKING;
+ return NS_OK;
+ case STATE_DOWNLOADING :
+ *aStatus = nsIDOMOfflineResourceList::DOWNLOADING;
+ return NS_OK;
+ default :
+ *aStatus = nsIDOMOfflineResourceList::IDLE;
+ return NS_OK;
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+OfflineCacheUpdateChild::GetPartial(bool *aPartial)
+{
+ *aPartial = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+OfflineCacheUpdateChild::GetManifestURI(nsIURI **aManifestURI)
+{
+ NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED);
+
+ NS_IF_ADDREF(*aManifestURI = mManifestURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+OfflineCacheUpdateChild::GetSucceeded(bool *aSucceeded)
+{
+ NS_ENSURE_TRUE(mState == STATE_FINISHED, NS_ERROR_NOT_AVAILABLE);
+
+ *aSucceeded = mSucceeded;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+OfflineCacheUpdateChild::GetIsUpgrade(bool *aIsUpgrade)
+{
+ NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED);
+
+ *aIsUpgrade = mIsUpgrade;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+OfflineCacheUpdateChild::AddDynamicURI(nsIURI *aURI)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+OfflineCacheUpdateChild::Cancel()
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+OfflineCacheUpdateChild::AddObserver(nsIOfflineCacheUpdateObserver *aObserver,
+ bool aHoldWeak)
+{
+ LOG(("OfflineCacheUpdateChild::AddObserver [%p]", this));
+
+ NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED);
+
+ if (aHoldWeak) {
+ nsCOMPtr<nsIWeakReference> weakRef = do_GetWeakReference(aObserver);
+ mWeakObservers.AppendObject(weakRef);
+ } else {
+ mObservers.AppendObject(aObserver);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+OfflineCacheUpdateChild::RemoveObserver(nsIOfflineCacheUpdateObserver *aObserver)
+{
+ LOG(("OfflineCacheUpdateChild::RemoveObserver [%p]", this));
+
+ NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED);
+
+ for (int32_t i = 0; i < mWeakObservers.Count(); i++) {
+ nsCOMPtr<nsIOfflineCacheUpdateObserver> observer =
+ do_QueryReferent(mWeakObservers[i]);
+ if (observer == aObserver) {
+ mWeakObservers.RemoveObjectAt(i);
+ return NS_OK;
+ }
+ }
+
+ for (int32_t i = 0; i < mObservers.Count(); i++) {
+ if (mObservers[i] == aObserver) {
+ mObservers.RemoveObjectAt(i);
+ return NS_OK;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+OfflineCacheUpdateChild::GetByteProgress(uint64_t * _result)
+{
+ NS_ENSURE_ARG(_result);
+
+ *_result = mByteProgress;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+OfflineCacheUpdateChild::Schedule()
+{
+ LOG(("OfflineCacheUpdateChild::Schedule [%p]", this));
+
+ NS_ASSERTION(mWindow, "Window must be provided to the offline cache update child");
+
+ nsCOMPtr<nsPIDOMWindowInner> window = mWindow.forget();
+ nsCOMPtr<nsIDocShell >docshell = window->GetDocShell();
+ if (!docshell) {
+ NS_WARNING("doc shell tree item is null");
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsITabChild> tabchild = docshell->GetTabChild();
+ // because owner implements nsITabChild, we can assume that it is
+ // the one and only TabChild.
+ TabChild* child = tabchild ? static_cast<TabChild*>(tabchild.get()) : nullptr;
+
+ if (MissingRequiredTabChild(child, "offlinecacheupdate")) {
+ return NS_ERROR_FAILURE;
+ }
+
+ URIParams manifestURI, documentURI;
+ SerializeURI(mManifestURI, manifestURI);
+ SerializeURI(mDocumentURI, documentURI);
+
+ nsresult rv = NS_OK;
+ PrincipalInfo loadingPrincipalInfo;
+ rv = PrincipalToPrincipalInfo(mLoadingPrincipal,
+ &loadingPrincipalInfo);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ LOG(("Calling offline-cache-update-added"));
+ observerService->NotifyObservers(static_cast<nsIOfflineCacheUpdate*>(this),
+ "offline-cache-update-added",
+ nullptr);
+ LOG(("Done offline-cache-update-added"));
+ }
+
+ // mDocument is non-null if both:
+ // 1. this update was initiated by a document that referred a manifest
+ // 2. the document has not already been loaded from the application cache
+ // This tells the update to cache this document even in case the manifest
+ // has not been changed since the last fetch.
+ // See also nsOfflineCacheUpdate::ScheduleImplicit.
+ bool stickDocument = mDocument != nullptr;
+
+ // Need to addref ourself here, because the IPC stack doesn't hold
+ // a reference to us. Will be released in RecvFinish() that identifies
+ // the work has been done.
+ ContentChild::GetSingleton()->SendPOfflineCacheUpdateConstructor(
+ this, manifestURI, documentURI, loadingPrincipalInfo,
+ stickDocument);
+
+ // ContentChild::DeallocPOfflineCacheUpdate will release this.
+ NS_ADDREF_THIS();
+
+ return NS_OK;
+}
+
+bool
+OfflineCacheUpdateChild::RecvAssociateDocuments(const nsCString &cacheGroupId,
+ const nsCString &cacheClientId)
+{
+ LOG(("OfflineCacheUpdateChild::RecvAssociateDocuments [%p, cache=%s]", this, cacheClientId.get()));
+
+ nsresult rv;
+
+ nsCOMPtr<nsIApplicationCache> cache =
+ do_CreateInstance(NS_APPLICATIONCACHE_CONTRACTID, &rv);
+ if (NS_FAILED(rv))
+ return true;
+
+ cache->InitAsHandle(cacheGroupId, cacheClientId);
+
+ if (mDocument) {
+ AssociateDocument(mDocument, cache);
+ }
+
+ nsCOMArray<nsIOfflineCacheUpdateObserver> observers;
+ GatherObservers(observers);
+
+ for (int32_t i = 0; i < observers.Count(); i++)
+ observers[i]->ApplicationCacheAvailable(cache);
+
+ return true;
+}
+
+bool
+OfflineCacheUpdateChild::RecvNotifyStateEvent(const uint32_t &event,
+ const uint64_t &byteProgress)
+{
+ LOG(("OfflineCacheUpdateChild::RecvNotifyStateEvent [%p]", this));
+
+ mByteProgress = byteProgress;
+
+ // Convert the public observer state to our internal state
+ switch (event) {
+ case nsIOfflineCacheUpdateObserver::STATE_CHECKING:
+ mState = STATE_CHECKING;
+ break;
+
+ case nsIOfflineCacheUpdateObserver::STATE_DOWNLOADING:
+ mState = STATE_DOWNLOADING;
+ break;
+
+ default:
+ break;
+ }
+
+ nsCOMArray<nsIOfflineCacheUpdateObserver> observers;
+ GatherObservers(observers);
+
+ for (int32_t i = 0; i < observers.Count(); i++)
+ observers[i]->UpdateStateChanged(this, event);
+
+ return true;
+}
+
+bool
+OfflineCacheUpdateChild::RecvFinish(const bool &succeeded,
+ const bool &isUpgrade)
+{
+ LOG(("OfflineCacheUpdateChild::RecvFinish [%p]", this));
+
+ RefPtr<OfflineCacheUpdateChild> kungFuDeathGrip(this);
+
+ mState = STATE_FINISHED;
+ mSucceeded = succeeded;
+ mIsUpgrade = isUpgrade;
+
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ LOG(("Calling offline-cache-update-completed"));
+ observerService->NotifyObservers(static_cast<nsIOfflineCacheUpdate*>(this),
+ "offline-cache-update-completed",
+ nullptr);
+ LOG(("Done offline-cache-update-completed"));
+ }
+
+ // This is by contract the last notification from the parent, release
+ // us now. This is corresponding to AddRef in Schedule().
+ // TabChild::DeallocPOfflineCacheUpdate will call Release.
+ OfflineCacheUpdateChild::Send__delete__(this);
+
+ return true;
+}
+
+} // namespace docshell
+} // namespace mozilla
diff --git a/uriloader/prefetch/OfflineCacheUpdateChild.h b/uriloader/prefetch/OfflineCacheUpdateChild.h
new file mode 100644
index 0000000000..89d1e6f1f8
--- /dev/null
+++ b/uriloader/prefetch/OfflineCacheUpdateChild.h
@@ -0,0 +1,94 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsOfflineCacheUpdateChild_h
+#define nsOfflineCacheUpdateChild_h
+
+#include "mozilla/docshell/POfflineCacheUpdateChild.h"
+#include "nsIOfflineCacheUpdate.h"
+
+#include "nsCOMArray.h"
+#include "nsCOMPtr.h"
+#include "nsIDOMDocument.h"
+#include "nsIObserver.h"
+#include "nsIObserverService.h"
+#include "nsIURI.h"
+#include "nsString.h"
+#include "nsWeakReference.h"
+
+class nsPIDOMWindowInner;
+
+namespace mozilla {
+namespace docshell {
+
+class OfflineCacheUpdateChild : public nsIOfflineCacheUpdate
+ , public POfflineCacheUpdateChild
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOFFLINECACHEUPDATE
+
+ virtual bool
+ RecvNotifyStateEvent(const uint32_t& stateEvent,
+ const uint64_t& byteProgress) override;
+
+ virtual bool
+ RecvAssociateDocuments(
+ const nsCString& cacheGroupId,
+ const nsCString& cacheClientId) override;
+
+ virtual bool
+ RecvFinish(const bool& succeeded,
+ const bool& isUpgrade) override;
+
+ explicit OfflineCacheUpdateChild(nsPIDOMWindowInner* aWindow);
+
+ void SetDocument(nsIDOMDocument *aDocument);
+
+private:
+ ~OfflineCacheUpdateChild();
+
+ nsresult AssociateDocument(nsIDOMDocument *aDocument,
+ nsIApplicationCache *aApplicationCache);
+ void GatherObservers(nsCOMArray<nsIOfflineCacheUpdateObserver> &aObservers);
+ nsresult Finish();
+
+ enum {
+ STATE_UNINITIALIZED,
+ STATE_INITIALIZED,
+ STATE_CHECKING,
+ STATE_DOWNLOADING,
+ STATE_CANCELLED,
+ STATE_FINISHED
+ } mState;
+
+ bool mIsUpgrade;
+ bool mSucceeded;
+
+ nsCString mUpdateDomain;
+ nsCOMPtr<nsIURI> mManifestURI;
+ nsCOMPtr<nsIURI> mDocumentURI;
+ nsCOMPtr<nsIPrincipal> mLoadingPrincipal;
+
+ nsCOMPtr<nsIObserverService> mObserverService;
+
+ /* Clients watching this update for changes */
+ nsCOMArray<nsIWeakReference> mWeakObservers;
+ nsCOMArray<nsIOfflineCacheUpdateObserver> mObservers;
+
+ /* Document that requested this update */
+ nsCOMPtr<nsIDOMDocument> mDocument;
+
+ /* Keep reference to the window that owns this update to call the
+ parent offline cache update construcor */
+ nsCOMPtr<nsPIDOMWindowInner> mWindow;
+
+ uint64_t mByteProgress;
+};
+
+} // namespace docshell
+} // namespace mozilla
+
+#endif
diff --git a/uriloader/prefetch/OfflineCacheUpdateGlue.cpp b/uriloader/prefetch/OfflineCacheUpdateGlue.cpp
new file mode 100644
index 0000000000..71ca986ffb
--- /dev/null
+++ b/uriloader/prefetch/OfflineCacheUpdateGlue.cpp
@@ -0,0 +1,228 @@
+/* -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "OfflineCacheUpdateGlue.h"
+#include "nsOfflineCacheUpdate.h"
+#include "mozilla/Services.h"
+
+#include "nsIApplicationCache.h"
+#include "nsIApplicationCacheChannel.h"
+#include "nsIApplicationCacheContainer.h"
+#include "nsIChannel.h"
+#include "nsIDocument.h"
+#include "mozilla/Logging.h"
+
+//
+// To enable logging (see mozilla/Logging.h for full details):
+//
+// set MOZ_LOG=nsOfflineCacheUpdate:5
+// set MOZ_LOG_FILE=offlineupdate.log
+//
+// this enables LogLevel::Info level information and places all output in
+// the file offlineupdate.log
+//
+extern mozilla::LazyLogModule gOfflineCacheUpdateLog;
+
+#undef LOG
+#define LOG(args) MOZ_LOG(gOfflineCacheUpdateLog, mozilla::LogLevel::Debug, args)
+
+#undef LOG_ENABLED
+#define LOG_ENABLED() MOZ_LOG_TEST(gOfflineCacheUpdateLog, mozilla::LogLevel::Debug)
+
+namespace mozilla {
+namespace docshell {
+
+//-----------------------------------------------------------------------------
+// OfflineCacheUpdateGlue::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(OfflineCacheUpdateGlue,
+ nsIOfflineCacheUpdate,
+ nsIOfflineCacheUpdateObserver,
+ nsISupportsWeakReference)
+
+//-----------------------------------------------------------------------------
+// OfflineCacheUpdateGlue <public>
+//-----------------------------------------------------------------------------
+
+OfflineCacheUpdateGlue::OfflineCacheUpdateGlue()
+: mCoalesced(false)
+{
+ LOG(("OfflineCacheUpdateGlue::OfflineCacheUpdateGlue [%p]", this));
+}
+
+OfflineCacheUpdateGlue::~OfflineCacheUpdateGlue()
+{
+ LOG(("OfflineCacheUpdateGlue::~OfflineCacheUpdateGlue [%p]", this));
+}
+
+nsIOfflineCacheUpdate*
+OfflineCacheUpdateGlue::EnsureUpdate()
+{
+ if (!mUpdate) {
+ mUpdate = new nsOfflineCacheUpdate();
+ LOG(("OfflineCacheUpdateGlue [%p] is using update [%p]", this, mUpdate.get()));
+ }
+
+ return mUpdate;
+}
+
+NS_IMETHODIMP
+OfflineCacheUpdateGlue::Schedule()
+{
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ LOG(("Calling offline-cache-update-added"));
+ observerService->NotifyObservers(static_cast<nsIOfflineCacheUpdate*>(this),
+ "offline-cache-update-added",
+ nullptr);
+ LOG(("Done offline-cache-update-added"));
+ }
+
+ if (!EnsureUpdate())
+ return NS_ERROR_NULL_POINTER;
+
+ // Do not use weak reference, we must survive!
+ mUpdate->AddObserver(this, false);
+
+ if (mCoalesced) // already scheduled
+ return NS_OK;
+
+ return mUpdate->Schedule();
+}
+
+NS_IMETHODIMP
+OfflineCacheUpdateGlue::Init(nsIURI *aManifestURI,
+ nsIURI *aDocumentURI,
+ nsIPrincipal* aLoadingPrincipal,
+ nsIDOMDocument *aDocument,
+ nsIFile *aCustomProfileDir)
+{
+ nsresult rv;
+
+ nsAutoCString originSuffix;
+ rv = aLoadingPrincipal->GetOriginSuffix(originSuffix);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsOfflineCacheUpdateService* service =
+ nsOfflineCacheUpdateService::EnsureService();
+ if (service) {
+ service->FindUpdate(aManifestURI, originSuffix, aCustomProfileDir,
+ getter_AddRefs(mUpdate));
+ mCoalesced = !!mUpdate;
+ }
+
+ if (!EnsureUpdate())
+ return NS_ERROR_NULL_POINTER;
+
+ mDocumentURI = aDocumentURI;
+ mLoadingPrincipal = aLoadingPrincipal;
+
+ if (aDocument)
+ SetDocument(aDocument);
+
+ if (mCoalesced) { // already initialized
+ LOG(("OfflineCacheUpdateGlue %p coalesced with update %p", this, mUpdate.get()));
+ return NS_OK;
+ }
+
+ return mUpdate->Init(aManifestURI, aDocumentURI, aLoadingPrincipal, nullptr,
+ aCustomProfileDir);
+}
+
+void
+OfflineCacheUpdateGlue::SetDocument(nsIDOMDocument *aDocument)
+{
+ // The design is one document for one cache update on the content process.
+ NS_ASSERTION(!mDocument,
+ "Setting more then a single document on an instance of OfflineCacheUpdateGlue");
+
+ LOG(("Document %p added to update glue %p", aDocument, this));
+
+ // Add document only if it was not loaded from an offline cache.
+ // If it were loaded from an offline cache then it has already
+ // been associated with it and must not be again cached as
+ // implicit (which are the reasons we collect documents here).
+ nsCOMPtr<nsIDocument> document = do_QueryInterface(aDocument);
+ if (!document)
+ return;
+
+ nsIChannel* channel = document->GetChannel();
+ nsCOMPtr<nsIApplicationCacheChannel> appCacheChannel =
+ do_QueryInterface(channel);
+ if (!appCacheChannel)
+ return;
+
+ bool loadedFromAppCache;
+ appCacheChannel->GetLoadedFromApplicationCache(&loadedFromAppCache);
+ if (loadedFromAppCache)
+ return;
+
+ if (EnsureUpdate()) {
+ mUpdate->StickDocument(mDocumentURI);
+ }
+
+ mDocument = aDocument;
+}
+
+NS_IMETHODIMP
+OfflineCacheUpdateGlue::UpdateStateChanged(nsIOfflineCacheUpdate *aUpdate, uint32_t state)
+{
+ if (state == nsIOfflineCacheUpdateObserver::STATE_FINISHED) {
+ LOG(("OfflineCacheUpdateGlue got STATE_FINISHED [%p]", this));
+
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ LOG(("Calling offline-cache-update-completed"));
+ observerService->NotifyObservers(static_cast<nsIOfflineCacheUpdate*>(this),
+ "offline-cache-update-completed",
+ nullptr);
+ LOG(("Done offline-cache-update-completed"));
+ }
+
+ aUpdate->RemoveObserver(this);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+OfflineCacheUpdateGlue::ApplicationCacheAvailable(nsIApplicationCache *aApplicationCache)
+{
+ NS_ENSURE_ARG(aApplicationCache);
+
+ // Check that the document that requested this update was
+ // previously associated with an application cache. If not, it
+ // should be associated with the new one.
+ nsCOMPtr<nsIApplicationCacheContainer> container =
+ do_QueryInterface(mDocument);
+ if (!container)
+ return NS_OK;
+
+ nsCOMPtr<nsIApplicationCache> existingCache;
+ nsresult rv = container->GetApplicationCache(getter_AddRefs(existingCache));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!existingCache) {
+ if (LOG_ENABLED()) {
+ nsAutoCString clientID;
+ if (aApplicationCache) {
+ aApplicationCache->GetClientID(clientID);
+ }
+ LOG(("Update %p: associating app cache %s to document %p",
+ this, clientID.get(), mDocument.get()));
+ }
+
+ rv = container->SetApplicationCache(aApplicationCache);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+} // namespace docshell
+} // namespace mozilla
diff --git a/uriloader/prefetch/OfflineCacheUpdateGlue.h b/uriloader/prefetch/OfflineCacheUpdateGlue.h
new file mode 100644
index 0000000000..92201ec82b
--- /dev/null
+++ b/uriloader/prefetch/OfflineCacheUpdateGlue.h
@@ -0,0 +1,80 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsOfflineCacheUpdateGlue_h
+#define nsOfflineCacheUpdateGlue_h
+
+#include "nsIOfflineCacheUpdate.h"
+
+#include "nsCOMPtr.h"
+#include "nsAutoPtr.h"
+#include "nsString.h"
+#include "nsWeakReference.h"
+#include "mozilla/Attributes.h"
+
+class nsOfflineCacheUpdate;
+
+namespace mozilla {
+namespace docshell {
+
+// Like FORWARD_SAFE except methods:
+// Schedule
+// Init
+#define NS_ADJUSTED_FORWARD_NSIOFFLINECACHEUPDATE(_to) \
+ NS_IMETHOD GetStatus(uint16_t *aStatus) override { return !_to ? NS_ERROR_NULL_POINTER : _to->GetStatus(aStatus); } \
+ NS_IMETHOD GetPartial(bool *aPartial) override { return !_to ? NS_ERROR_NULL_POINTER : _to->GetPartial(aPartial); } \
+ NS_IMETHOD GetIsUpgrade(bool *aIsUpgrade) override { return !_to ? NS_ERROR_NULL_POINTER : _to->GetIsUpgrade(aIsUpgrade); } \
+ NS_IMETHOD GetUpdateDomain(nsACString & aUpdateDomain) override { return !_to ? NS_ERROR_NULL_POINTER : _to->GetUpdateDomain(aUpdateDomain); } \
+ NS_IMETHOD GetManifestURI(nsIURI **aManifestURI) override { return !_to ? NS_ERROR_NULL_POINTER : _to->GetManifestURI(aManifestURI); } \
+ NS_IMETHOD GetSucceeded(bool *aSucceeded) override { return !_to ? NS_ERROR_NULL_POINTER : _to->GetSucceeded(aSucceeded); } \
+ NS_IMETHOD InitPartial(nsIURI *aManifestURI, const nsACString & aClientID, nsIURI *aDocumentURI, nsIPrincipal *aLoadingPrincipal) override { return !_to ? NS_ERROR_NULL_POINTER : _to->InitPartial(aManifestURI, aClientID, aDocumentURI, aLoadingPrincipal); } \
+ NS_IMETHOD InitForUpdateCheck(nsIURI *aManifestURI, nsIPrincipal* aLoadingPrincipal, nsIObserver *aObserver) override { return !_to ? NS_ERROR_NULL_POINTER : _to->InitForUpdateCheck(aManifestURI, aLoadingPrincipal, aObserver); } \
+ NS_IMETHOD AddDynamicURI(nsIURI *aURI) override { return !_to ? NS_ERROR_NULL_POINTER : _to->AddDynamicURI(aURI); } \
+ NS_IMETHOD AddObserver(nsIOfflineCacheUpdateObserver *aObserver, bool aHoldWeak) override { return !_to ? NS_ERROR_NULL_POINTER : _to->AddObserver(aObserver, aHoldWeak); } \
+ NS_IMETHOD RemoveObserver(nsIOfflineCacheUpdateObserver *aObserver) override { return !_to ? NS_ERROR_NULL_POINTER : _to->RemoveObserver(aObserver); } \
+ NS_IMETHOD GetByteProgress(uint64_t * _result) override { return !_to ? NS_ERROR_NULL_POINTER : _to->GetByteProgress(_result); } \
+ NS_IMETHOD Cancel() override { return !_to ? NS_ERROR_NULL_POINTER : _to->Cancel(); }
+
+class OfflineCacheUpdateGlue final : public nsSupportsWeakReference
+ , public nsIOfflineCacheUpdate
+ , public nsIOfflineCacheUpdateObserver
+{
+public:
+ NS_DECL_ISUPPORTS
+
+private:
+ nsIOfflineCacheUpdate* EnsureUpdate();
+
+public:
+ NS_ADJUSTED_FORWARD_NSIOFFLINECACHEUPDATE(EnsureUpdate())
+ NS_IMETHOD Schedule(void) override;
+ NS_IMETHOD Init(nsIURI *aManifestURI,
+ nsIURI *aDocumentURI,
+ nsIPrincipal* aLoadingPrincipal,
+ nsIDOMDocument *aDocument,
+ nsIFile *aCustomProfileDir) override;
+
+ NS_DECL_NSIOFFLINECACHEUPDATEOBSERVER
+
+ OfflineCacheUpdateGlue();
+
+ void SetDocument(nsIDOMDocument *aDocument);
+
+private:
+ ~OfflineCacheUpdateGlue();
+
+ RefPtr<nsOfflineCacheUpdate> mUpdate;
+ bool mCoalesced;
+
+ /* Document that requested this update */
+ nsCOMPtr<nsIDOMDocument> mDocument;
+ nsCOMPtr<nsIURI> mDocumentURI;
+ nsCOMPtr<nsIPrincipal> mLoadingPrincipal;
+};
+
+} // namespace docshell
+} // namespace mozilla
+
+#endif
diff --git a/uriloader/prefetch/OfflineCacheUpdateParent.cpp b/uriloader/prefetch/OfflineCacheUpdateParent.cpp
new file mode 100644
index 0000000000..0381ec3f62
--- /dev/null
+++ b/uriloader/prefetch/OfflineCacheUpdateParent.cpp
@@ -0,0 +1,294 @@
+/* -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "OfflineCacheUpdateParent.h"
+
+#include "BackgroundUtils.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/dom/TabParent.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "mozilla/Unused.h"
+#include "nsContentUtils.h"
+#include "nsOfflineCacheUpdate.h"
+#include "nsIApplicationCache.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsNetUtil.h"
+
+using namespace mozilla::ipc;
+using mozilla::BasePrincipal;
+using mozilla::DocShellOriginAttributes;
+using mozilla::PrincipalOriginAttributes;
+using mozilla::dom::TabParent;
+
+//
+// To enable logging (see mozilla/Logging.h for full details):
+//
+// set MOZ_LOG=nsOfflineCacheUpdate:5
+// set MOZ_LOG_FILE=offlineupdate.log
+//
+// this enables LogLevel::Debug level information and places all output in
+// the file offlineupdate.log
+//
+extern mozilla::LazyLogModule gOfflineCacheUpdateLog;
+
+#undef LOG
+#define LOG(args) MOZ_LOG(gOfflineCacheUpdateLog, mozilla::LogLevel::Debug, args)
+
+#undef LOG_ENABLED
+#define LOG_ENABLED() MOZ_LOG_TEST(gOfflineCacheUpdateLog, mozilla::LogLevel::Debug)
+
+namespace mozilla {
+namespace docshell {
+
+//-----------------------------------------------------------------------------
+// OfflineCacheUpdateParent::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(OfflineCacheUpdateParent,
+ nsIOfflineCacheUpdateObserver,
+ nsILoadContext)
+
+//-----------------------------------------------------------------------------
+// OfflineCacheUpdateParent <public>
+//-----------------------------------------------------------------------------
+
+
+OfflineCacheUpdateParent::OfflineCacheUpdateParent()
+ : mIPCClosed(false)
+{
+ // Make sure the service has been initialized
+ nsOfflineCacheUpdateService::EnsureService();
+
+ LOG(("OfflineCacheUpdateParent::OfflineCacheUpdateParent [%p]", this));
+}
+
+OfflineCacheUpdateParent::~OfflineCacheUpdateParent()
+{
+ LOG(("OfflineCacheUpdateParent::~OfflineCacheUpdateParent [%p]", this));
+}
+
+void
+OfflineCacheUpdateParent::ActorDestroy(ActorDestroyReason why)
+{
+ mIPCClosed = true;
+}
+
+nsresult
+OfflineCacheUpdateParent::Schedule(const URIParams& aManifestURI,
+ const URIParams& aDocumentURI,
+ const PrincipalInfo& aLoadingPrincipalInfo,
+ const bool& stickDocument)
+{
+ LOG(("OfflineCacheUpdateParent::RecvSchedule [%p]", this));
+
+ nsresult rv;
+
+ RefPtr<nsOfflineCacheUpdate> update;
+ nsCOMPtr<nsIURI> manifestURI = DeserializeURI(aManifestURI);
+ if (!manifestURI)
+ return NS_ERROR_FAILURE;
+
+ mLoadingPrincipal = PrincipalInfoToPrincipal(aLoadingPrincipalInfo, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsOfflineCacheUpdateService* service =
+ nsOfflineCacheUpdateService::EnsureService();
+ if (!service)
+ return NS_ERROR_FAILURE;
+
+ bool offlinePermissionAllowed = false;
+
+ rv = service->OfflineAppAllowed(
+ mLoadingPrincipal, nullptr, &offlinePermissionAllowed);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!offlinePermissionAllowed)
+ return NS_ERROR_DOM_SECURITY_ERR;
+
+ nsCOMPtr<nsIURI> documentURI = DeserializeURI(aDocumentURI);
+ if (!documentURI)
+ return NS_ERROR_FAILURE;
+
+ if (!NS_SecurityCompareURIs(manifestURI, documentURI, false))
+ return NS_ERROR_DOM_SECURITY_ERR;
+
+ nsAutoCString originSuffix;
+ rv = mLoadingPrincipal->GetOriginSuffix(originSuffix);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ service->FindUpdate(manifestURI,
+ originSuffix,
+ nullptr,
+ getter_AddRefs(update));
+ if (!update) {
+ update = new nsOfflineCacheUpdate();
+
+ // Leave aDocument argument null. Only glues and children keep
+ // document instances.
+ rv = update->Init(manifestURI, documentURI, mLoadingPrincipal, nullptr, nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Must add before Schedule() call otherwise we would miss
+ // oncheck event notification.
+ update->AddObserver(this, false);
+
+ rv = update->Schedule();
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ update->AddObserver(this, false);
+ }
+
+ if (stickDocument) {
+ nsCOMPtr<nsIURI> stickURI;
+ documentURI->Clone(getter_AddRefs(stickURI));
+ update->StickDocument(stickURI);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+OfflineCacheUpdateParent::UpdateStateChanged(nsIOfflineCacheUpdate *aUpdate, uint32_t state)
+{
+ if (mIPCClosed)
+ return NS_ERROR_UNEXPECTED;
+
+ LOG(("OfflineCacheUpdateParent::StateEvent [%p]", this));
+
+ uint64_t byteProgress;
+ aUpdate->GetByteProgress(&byteProgress);
+ Unused << SendNotifyStateEvent(state, byteProgress);
+
+ if (state == nsIOfflineCacheUpdateObserver::STATE_FINISHED) {
+ // Tell the child the particulars after the update has finished.
+ // Sending the Finish event will release the child side of the protocol
+ // and notify "offline-cache-update-completed" on the child process.
+ bool isUpgrade;
+ aUpdate->GetIsUpgrade(&isUpgrade);
+ bool succeeded;
+ aUpdate->GetSucceeded(&succeeded);
+
+ Unused << SendFinish(succeeded, isUpgrade);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+OfflineCacheUpdateParent::ApplicationCacheAvailable(nsIApplicationCache *aApplicationCache)
+{
+ if (mIPCClosed)
+ return NS_ERROR_UNEXPECTED;
+
+ NS_ENSURE_ARG(aApplicationCache);
+
+ nsCString cacheClientId;
+ aApplicationCache->GetClientID(cacheClientId);
+ nsCString cacheGroupId;
+ aApplicationCache->GetGroupID(cacheGroupId);
+
+ Unused << SendAssociateDocuments(cacheGroupId, cacheClientId);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// OfflineCacheUpdateParent::nsILoadContext
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+OfflineCacheUpdateParent::GetAssociatedWindow(mozIDOMWindowProxy** aAssociatedWindow)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+OfflineCacheUpdateParent::GetTopWindow(mozIDOMWindowProxy** aTopWindow)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+OfflineCacheUpdateParent::GetTopFrameElement(nsIDOMElement** aElement)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+OfflineCacheUpdateParent::GetNestedFrameId(uint64_t* aId)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+OfflineCacheUpdateParent::GetIsContent(bool *aIsContent)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+OfflineCacheUpdateParent::GetUsePrivateBrowsing(bool *aUsePrivateBrowsing)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+NS_IMETHODIMP
+OfflineCacheUpdateParent::SetUsePrivateBrowsing(bool aUsePrivateBrowsing)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+OfflineCacheUpdateParent::SetPrivateBrowsing(bool aUsePrivateBrowsing)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+OfflineCacheUpdateParent::GetUseRemoteTabs(bool *aUseRemoteTabs)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+OfflineCacheUpdateParent::SetRemoteTabs(bool aUseRemoteTabs)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+OfflineCacheUpdateParent::GetIsInIsolatedMozBrowserElement(bool *aIsInIsolatedMozBrowserElement)
+{
+ NS_ENSURE_TRUE(mLoadingPrincipal, NS_ERROR_UNEXPECTED);
+ return mLoadingPrincipal->GetIsInIsolatedMozBrowserElement(aIsInIsolatedMozBrowserElement);
+}
+
+NS_IMETHODIMP
+OfflineCacheUpdateParent::GetAppId(uint32_t *aAppId)
+{
+ NS_ENSURE_TRUE(mLoadingPrincipal, NS_ERROR_UNEXPECTED);
+ return mLoadingPrincipal->GetAppId(aAppId);
+}
+
+NS_IMETHODIMP
+OfflineCacheUpdateParent::GetOriginAttributes(JS::MutableHandleValue aAttrs)
+{
+ NS_ENSURE_TRUE(mLoadingPrincipal, NS_ERROR_UNEXPECTED);
+
+ JSContext* cx = nsContentUtils::GetCurrentJSContext();
+ MOZ_ASSERT(cx);
+
+ nsresult rv = mLoadingPrincipal->GetOriginAttributes(cx, aAttrs);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+OfflineCacheUpdateParent::IsTrackingProtectionOn(bool* aIsTrackingProtectionOn)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+} // namespace docshell
+} // namespace mozilla
diff --git a/uriloader/prefetch/OfflineCacheUpdateParent.h b/uriloader/prefetch/OfflineCacheUpdateParent.h
new file mode 100644
index 0000000000..f6dbc1cb2a
--- /dev/null
+++ b/uriloader/prefetch/OfflineCacheUpdateParent.h
@@ -0,0 +1,65 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsOfflineCacheUpdateParent_h
+#define nsOfflineCacheUpdateParent_h
+
+#include "mozilla/docshell/POfflineCacheUpdateParent.h"
+#include "mozilla/BasePrincipal.h"
+#include "nsIOfflineCacheUpdate.h"
+
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsILoadContext.h"
+
+class nsIPrincipal;
+
+namespace mozilla {
+
+namespace ipc {
+class URIParams;
+} // namespace ipc
+
+namespace docshell {
+
+class OfflineCacheUpdateParent : public POfflineCacheUpdateParent
+ , public nsIOfflineCacheUpdateObserver
+ , public nsILoadContext
+{
+ typedef mozilla::ipc::URIParams URIParams;
+ typedef mozilla::ipc::PrincipalInfo PrincipalInfo;
+
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOFFLINECACHEUPDATEOBSERVER
+ NS_DECL_NSILOADCONTEXT
+
+ nsresult
+ Schedule(const URIParams& manifestURI,
+ const URIParams& documentURI,
+ const PrincipalInfo& loadingPrincipalInfo,
+ const bool& stickDocument);
+
+ void
+ StopSendingMessagesToChild()
+ {
+ mIPCClosed = true;
+ }
+
+ explicit OfflineCacheUpdateParent();
+
+ virtual void ActorDestroy(ActorDestroyReason aWhy) override;
+private:
+ ~OfflineCacheUpdateParent();
+
+ bool mIPCClosed;
+
+ nsCOMPtr<nsIPrincipal> mLoadingPrincipal;
+};
+
+} // namespace docshell
+} // namespace mozilla
+
+#endif
diff --git a/uriloader/prefetch/POfflineCacheUpdate.ipdl b/uriloader/prefetch/POfflineCacheUpdate.ipdl
new file mode 100644
index 0000000000..e624752522
--- /dev/null
+++ b/uriloader/prefetch/POfflineCacheUpdate.ipdl
@@ -0,0 +1,28 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PContent;
+
+namespace mozilla {
+namespace docshell {
+
+//-------------------------------------------------------------------
+protocol POfflineCacheUpdate
+{
+ manager PContent;
+
+parent:
+ async __delete__();
+
+child:
+ async NotifyStateEvent(uint32_t stateEvent, uint64_t byteProgress);
+ async AssociateDocuments(nsCString cacheGroupId, nsCString cacheClientId);
+ async Finish(bool succeeded, bool isUpgrade);
+};
+
+}
+}
diff --git a/uriloader/prefetch/moz.build b/uriloader/prefetch/moz.build
new file mode 100644
index 0000000000..348d57a92c
--- /dev/null
+++ b/uriloader/prefetch/moz.build
@@ -0,0 +1,45 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPIDL_SOURCES += [
+ 'nsIOfflineCacheUpdate.idl',
+ 'nsIPrefetchService.idl',
+]
+
+XPIDL_MODULE = 'prefetch'
+
+EXPORTS += [
+ 'nsCPrefetchService.h',
+]
+
+EXPORTS.mozilla.docshell += [
+ 'OfflineCacheUpdateChild.h',
+ 'OfflineCacheUpdateParent.h',
+]
+
+UNIFIED_SOURCES += [
+ 'nsOfflineCacheUpdate.cpp',
+ 'nsOfflineCacheUpdateService.cpp',
+ 'nsPrefetchService.cpp',
+ 'OfflineCacheUpdateChild.cpp',
+ 'OfflineCacheUpdateGlue.cpp',
+ 'OfflineCacheUpdateParent.cpp',
+]
+
+IPDL_SOURCES += [
+ 'POfflineCacheUpdate.ipdl',
+]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
+
+LOCAL_INCLUDES += [
+ '/dom/base',
+]
+
+if CONFIG['GNU_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow']
diff --git a/uriloader/prefetch/nsCPrefetchService.h b/uriloader/prefetch/nsCPrefetchService.h
new file mode 100644
index 0000000000..d74d89fe7b
--- /dev/null
+++ b/uriloader/prefetch/nsCPrefetchService.h
@@ -0,0 +1,52 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsCPrefetchService_h__
+#define nsCPrefetchService_h__
+
+#include "nsIPrefetchService.h"
+
+/**
+ * nsPrefetchService : nsIPrefetchService
+ */
+#define NS_PREFETCHSERVICE_CONTRACTID \
+ "@mozilla.org/prefetch-service;1"
+#define NS_PREFETCHSERVICE_CID \
+{ /* 6b8bdffc-3394-417d-be83-a81b7c0f63bf */ \
+ 0x6b8bdffc, \
+ 0x3394, \
+ 0x417d, \
+ {0xbe, 0x83, 0xa8, 0x1b, 0x7c, 0x0f, 0x63, 0xbf} \
+}
+
+/**
+ * nsOfflineCacheUpdateService : nsIOfflineCacheUpdateService
+ */
+
+#define NS_OFFLINECACHEUPDATESERVICE_CONTRACTID \
+ "@mozilla.org/offlinecacheupdate-service;1"
+#define NS_OFFLINECACHEUPDATESERVICE_CID \
+{ /* ec06f3fc-70db-4ecd-94e0-a6e91ca44d8a */ \
+ 0xec06f3fc, \
+ 0x70db, \
+ 0x4ecd , \
+ {0x94, 0xe0, 0xa6, 0xe9, 0x1c, 0xa4, 0x4d, 0x8a} \
+}
+
+/**
+ * nsOfflineCacheUpdate : nsIOfflineCacheUpdate
+ */
+
+#define NS_OFFLINECACHEUPDATE_CONTRACTID \
+ "@mozilla.org/offlinecacheupdate;1"
+#define NS_OFFLINECACHEUPDATE_CID \
+{ /* e56f5e01-c7cc-4675-a9d7-b8f1e4127295 */ \
+ 0xe56f5e01, \
+ 0xc7cc, \
+ 0x4675, \
+ {0xa9, 0xd7, 0xb8, 0xf1, 0xe4, 0x12, 0x72, 0x95} \
+}
+
+
+#endif // !nsCPrefetchService_h__
diff --git a/uriloader/prefetch/nsIOfflineCacheUpdate.idl b/uriloader/prefetch/nsIOfflineCacheUpdate.idl
new file mode 100644
index 0000000000..1308a8de27
--- /dev/null
+++ b/uriloader/prefetch/nsIOfflineCacheUpdate.idl
@@ -0,0 +1,292 @@
+/* -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface mozIDOMWindow;
+interface nsIURI;
+interface nsIDOMNode;
+interface nsIDOMDocument;
+interface nsIOfflineCacheUpdate;
+interface nsIPrincipal;
+interface nsIPrefBranch;
+interface nsIApplicationCache;
+interface nsIFile;
+interface nsIObserver;
+
+[scriptable, uuid(47360d57-8ef4-4a5d-8865-1a27a739ad1a)]
+interface nsIOfflineCacheUpdateObserver : nsISupports {
+ const unsigned long STATE_ERROR = 1;
+ const unsigned long STATE_CHECKING = 2;
+ const unsigned long STATE_NOUPDATE = 3;
+ const unsigned long STATE_OBSOLETE = 4;
+ const unsigned long STATE_DOWNLOADING = 5;
+ const unsigned long STATE_ITEMSTARTED = 6;
+ const unsigned long STATE_ITEMCOMPLETED = 7;
+ const unsigned long STATE_ITEMPROGRESS = 8;
+ const unsigned long STATE_FINISHED = 10;
+
+ /**
+ * aUpdate has changed its state.
+ *
+ * @param aUpdate
+ * The nsIOfflineCacheUpdate being processed.
+ * @param event
+ * See enumeration above
+ */
+ void updateStateChanged(in nsIOfflineCacheUpdate aUpdate, in uint32_t state);
+
+ /**
+ * Informs the observer about an application being available to associate.
+ *
+ * @param applicationCache
+ * The application cache instance that has been created or found by the
+ * update to associate with
+ */
+ void applicationCacheAvailable(in nsIApplicationCache applicationCache);
+};
+
+/**
+ * An nsIOfflineCacheUpdate is used to update an application's offline
+ * resources.
+ *
+ * It can be used to perform partial or complete updates.
+ *
+ * One update object will be updating at a time. The active object will
+ * load its items one by one, sending itemCompleted() to any registered
+ * observers.
+ */
+[scriptable, uuid(6e3e26ea-45b2-4db7-9e4a-93b965679298)]
+interface nsIOfflineCacheUpdate : nsISupports {
+ /**
+ * Fetch the status of the running update. This will return a value
+ * defined in nsIDOMOfflineResourceList.
+ */
+ readonly attribute unsigned short status;
+
+ /**
+ * TRUE if the update is being used to add specific resources.
+ * FALSE if the complete cache update process is happening.
+ */
+ readonly attribute boolean partial;
+
+ /**
+ * TRUE if this is an upgrade attempt, FALSE if it is a new cache
+ * attempt.
+ */
+ readonly attribute boolean isUpgrade;
+
+ /**
+ * The domain being updated, and the domain that will own any URIs added
+ * with this update.
+ */
+ readonly attribute ACString updateDomain;
+
+ /**
+ * The manifest for the offline application being updated.
+ */
+ readonly attribute nsIURI manifestURI;
+
+ /**
+ * TRUE if the cache update completed successfully.
+ */
+ readonly attribute boolean succeeded;
+
+ /**
+ * Initialize the update.
+ *
+ * @param aManifestURI
+ * The manifest URI to be checked.
+ * @param aDocumentURI
+ * The page that is requesting the update.
+ * @param aLoadingPrincipal
+ * The principal of the page that is requesting the update.
+ */
+ void init(in nsIURI aManifestURI,
+ in nsIURI aDocumentURI,
+ in nsIPrincipal aLoadingPrincipal,
+ in nsIDOMDocument aDocument,
+ [optional] in nsIFile aCustomProfileDir);
+
+ /**
+ * Initialize the update for partial processing.
+ *
+ * @param aManifestURI
+ * The manifest URI of the related cache.
+ * @param aClientID
+ * Client ID of the cache to store resource to. This ClientID
+ * must be ID of cache in the cache group identified by
+ * the manifest URI passed in the first parameter.
+ * @param aDocumentURI
+ * The page that is requesting the update. May be null
+ * when this information is unknown.
+ */
+ void initPartial(in nsIURI aManifestURI, in ACString aClientID,
+ in nsIURI aDocumentURI, in nsIPrincipal aPrincipal);
+
+ /**
+ * Initialize the update to only check whether there is an update
+ * to the manifest available (if it has actually changed on the server).
+ *
+ * @param aManifestURI
+ * The manifest URI of the related cache.
+ * @param aObserver
+ * nsIObserver implementation that receives the result.
+ * When aTopic == "offline-cache-update-available" there is an update to
+ * to download. Update of the app cache will lead to a new version
+ * download.
+ * When aTopic == "offline-cache-update-unavailable" then there is no
+ * update available (the manifest has not changed on the server).
+ */
+ void initForUpdateCheck(in nsIURI aManifestURI,
+ in nsIPrincipal aLoadingPrincipal,
+ in nsIObserver aObserver);
+
+ /**
+ * Add a dynamic URI to the offline cache as part of the update.
+ *
+ * @param aURI
+ * The URI to add.
+ */
+ void addDynamicURI(in nsIURI aURI);
+
+ /**
+ * Add the update to the offline update queue. An offline-cache-update-added
+ * event will be sent to the observer service.
+ */
+ void schedule();
+
+ /**
+ * Observe loads that are added to the update.
+ *
+ * @param aObserver
+ * object that notifications will be sent to.
+ * @param aHoldWeak
+ * TRUE if you want the update to hold a weak reference to the
+ * observer, FALSE for a strong reference.
+ */
+ void addObserver(in nsIOfflineCacheUpdateObserver aObserver,
+ in boolean aHoldWeak);
+
+ /**
+ * Remove an observer from the update.
+ *
+ * @param aObserver
+ * the observer to remove.
+ */
+ void removeObserver(in nsIOfflineCacheUpdateObserver aObserver);
+
+ /**
+ * Cancel the update when still in progress. This stops all running resource
+ * downloads and discards the downloaded cache version. Throws when update
+ * has already finished and made the new cache version active.
+ */
+ void cancel();
+
+ /**
+ * Return the number of bytes downloaded so far
+ */
+ readonly attribute uint64_t byteProgress;
+};
+
+[scriptable, uuid(44971e74-37e4-4140-8677-a4cf213a3f4b)]
+interface nsIOfflineCacheUpdateService : nsISupports {
+ /**
+ * Constants for the offline-app permission.
+ *
+ * XXX: This isn't a great place for this, but it's really the only
+ * private offline-app-related interface
+ */
+
+ /**
+ * Allow the domain to use offline APIs, and don't warn about excessive
+ * usage.
+ */
+ const unsigned long ALLOW_NO_WARN = 3;
+
+ /**
+ * Access to the list of cache updates that have been scheduled.
+ */
+ readonly attribute unsigned long numUpdates;
+ nsIOfflineCacheUpdate getUpdate(in unsigned long index);
+
+ /**
+ * Schedule a cache update for a given offline manifest. If an
+ * existing update is scheduled or running, that update will be returned.
+ * Otherwise a new update will be scheduled.
+ */
+ nsIOfflineCacheUpdate scheduleUpdate(in nsIURI aManifestURI,
+ in nsIURI aDocumentURI,
+ in nsIPrincipal aLoadingPrincipal,
+ in mozIDOMWindow aWindow);
+
+ /**
+ * Schedule a cache update for a given offline manifest using app cache
+ * bound to the given appID+inIsolatedMozBrowser flag. If an existing update
+ * is scheduled or running, that update will be returned. Otherwise a new
+ * update will be scheduled.
+ */
+ nsIOfflineCacheUpdate scheduleAppUpdate(in nsIURI aManifestURI,
+ in nsIURI aDocumentURI,
+ in nsIPrincipal aLoadingPrincipal,
+ in nsIFile aProfileDir);
+
+ /**
+ * Schedule a cache update for a manifest when the document finishes
+ * loading.
+ */
+ void scheduleOnDocumentStop(in nsIURI aManifestURI,
+ in nsIURI aDocumentURI,
+ in nsIPrincipal aLoadingPrincipal,
+ in nsIDOMDocument aDocument);
+
+ /**
+ * Schedule a check to see if an update is available.
+ *
+ * This will not update or make any changes to the appcache.
+ * It only notifies the observer to indicate whether the manifest has
+ * changed on the server (or not): a changed manifest means that an
+ * update is available.
+ *
+ * For arguments see nsIOfflineCacheUpdate.initForUpdateCheck() method
+ * description.
+ */
+ void checkForUpdate(in nsIURI aManifestURI,
+ in nsIPrincipal aLoadingPrincipal,
+ in nsIObserver aObserver);
+
+ /**
+ * Checks whether a principal should have access to the offline
+ * cache.
+ * @param aPrincipal
+ * The principal to check.
+ * @param aPrefBranch
+ * The pref branch to use to check the
+ * offline-apps.allow_by_default pref. If not specified,
+ * the pref service will be used.
+ */
+ boolean offlineAppAllowed(in nsIPrincipal aPrincipal,
+ in nsIPrefBranch aPrefBranch);
+
+ /**
+ * Checks whether a document at the given URI should have access
+ * to the offline cache.
+ * @param aURI
+ * The URI to check
+ * @param aPrefBranch
+ * The pref branch to use to check the
+ * offline-apps.allow_by_default pref. If not specified,
+ * the pref service will be used.
+ */
+ boolean offlineAppAllowedForURI(in nsIURI aURI,
+ in nsIPrefBranch aPrefBranch);
+
+ /**
+ * Sets the "offline-app" permission for the principal.
+ * In the single process model calls directly on permission manager.
+ * In the multi process model dispatches to the parent process.
+ */
+ void allowOfflineApp(in nsIPrincipal aPrincipal);
+};
diff --git a/uriloader/prefetch/nsIPrefetchService.idl b/uriloader/prefetch/nsIPrefetchService.idl
new file mode 100644
index 0000000000..198320dd2d
--- /dev/null
+++ b/uriloader/prefetch/nsIPrefetchService.idl
@@ -0,0 +1,37 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIURI;
+interface nsIDOMNode;
+interface nsISimpleEnumerator;
+
+[scriptable, uuid(422a1807-4e7f-463d-b8d7-ca2ceb9b5d53)]
+interface nsIPrefetchService : nsISupports
+{
+ /**
+ * Enqueue a request to prefetch the specified URI.
+ *
+ * @param aURI the URI of the document to prefetch
+ * @param aReferrerURI the URI of the referring page
+ * @param aSource the DOM node (such as a <link> tag) that requested this
+ * fetch, or null if the prefetch was not requested by a DOM node.
+ * @param aExplicit the link element has an explicit prefetch link type
+ */
+ void prefetchURI(in nsIURI aURI,
+ in nsIURI aReferrerURI,
+ in nsIDOMNode aSource,
+ in boolean aExplicit);
+
+ /**
+ * Find out if there are any prefetches running or queued
+ */
+ boolean hasMoreElements();
+
+ /**
+ * Cancel prefetch
+ */
+ void cancelPrefetchURI(in nsIURI aURI, in nsIDOMNode aSource);
+};
diff --git a/uriloader/prefetch/nsOfflineCacheUpdate.cpp b/uriloader/prefetch/nsOfflineCacheUpdate.cpp
new file mode 100644
index 0000000000..4b6cd4d0cf
--- /dev/null
+++ b/uriloader/prefetch/nsOfflineCacheUpdate.cpp
@@ -0,0 +1,2471 @@
+/* -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsOfflineCacheUpdate.h"
+
+#include "nsCPrefetchService.h"
+#include "nsCURILoader.h"
+#include "nsIApplicationCacheContainer.h"
+#include "nsIApplicationCacheChannel.h"
+#include "nsIApplicationCacheService.h"
+#include "nsICachingChannel.h"
+#include "nsIContent.h"
+#include "mozilla/dom/Element.h"
+#include "nsIDocumentLoader.h"
+#include "nsIDOMElement.h"
+#include "nsIDOMWindow.h"
+#include "nsIDOMOfflineResourceList.h"
+#include "nsIDocument.h"
+#include "nsIObserverService.h"
+#include "nsIURL.h"
+#include "nsIWebProgress.h"
+#include "nsICryptoHash.h"
+#include "nsICacheEntry.h"
+#include "nsIPermissionManager.h"
+#include "nsIPrincipal.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsServiceManagerUtils.h"
+#include "nsStreamUtils.h"
+#include "nsThreadUtils.h"
+#include "nsProxyRelease.h"
+#include "nsIConsoleService.h"
+#include "mozilla/Logging.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Attributes.h"
+#include "nsContentUtils.h"
+#include "nsIPrincipal.h"
+
+#include "nsXULAppAPI.h"
+
+using namespace mozilla;
+
+static const uint32_t kRescheduleLimit = 3;
+// Max number of retries for every entry of pinned app.
+static const uint32_t kPinnedEntryRetriesLimit = 3;
+// Maximum number of parallel items loads
+static const uint32_t kParallelLoadLimit = 15;
+
+// Quota for offline apps when preloading
+static const int32_t kCustomProfileQuota = 512000;
+
+//
+// To enable logging (see mozilla/Logging.h for full details):
+//
+// set MOZ_LOG=nsOfflineCacheUpdate:5
+// set MOZ_LOG_FILE=offlineupdate.log
+//
+// this enables LogLevel::Debug level information and places all output in
+// the file offlineupdate.log
+//
+extern LazyLogModule gOfflineCacheUpdateLog;
+
+#undef LOG
+#define LOG(args) MOZ_LOG(gOfflineCacheUpdateLog, mozilla::LogLevel::Debug, args)
+
+#undef LOG_ENABLED
+#define LOG_ENABLED() MOZ_LOG_TEST(gOfflineCacheUpdateLog, mozilla::LogLevel::Debug)
+
+class AutoFreeArray {
+public:
+ AutoFreeArray(uint32_t count, char **values)
+ : mCount(count), mValues(values) {};
+ ~AutoFreeArray() { NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(mCount, mValues); }
+private:
+ uint32_t mCount;
+ char **mValues;
+};
+
+namespace {
+
+nsresult
+DropReferenceFromURL(nsIURI * aURI)
+{
+ // XXXdholbert If this SetRef fails, callers of this method probably
+ // want to call aURI->CloneIgnoringRef() and use the result of that.
+ return aURI->SetRef(EmptyCString());
+}
+
+void
+LogToConsole(const char * message, nsOfflineCacheUpdateItem * item = nullptr)
+{
+ nsCOMPtr<nsIConsoleService> consoleService =
+ do_GetService(NS_CONSOLESERVICE_CONTRACTID);
+ if (consoleService)
+ {
+ nsAutoString messageUTF16 = NS_ConvertUTF8toUTF16(message);
+ if (item && item->mURI) {
+ messageUTF16.AppendLiteral(", URL=");
+ messageUTF16.Append(
+ NS_ConvertUTF8toUTF16(item->mURI->GetSpecOrDefault()));
+ }
+ consoleService->LogStringMessage(messageUTF16.get());
+ }
+}
+
+} // namespace
+
+//-----------------------------------------------------------------------------
+// nsManifestCheck
+//-----------------------------------------------------------------------------
+
+class nsManifestCheck final : public nsIStreamListener
+ , public nsIChannelEventSink
+ , public nsIInterfaceRequestor
+{
+public:
+ nsManifestCheck(nsOfflineCacheUpdate *aUpdate,
+ nsIURI *aURI,
+ nsIURI *aReferrerURI,
+ nsIPrincipal* aLoadingPrincipal)
+ : mUpdate(aUpdate)
+ , mURI(aURI)
+ , mReferrerURI(aReferrerURI)
+ , mLoadingPrincipal(aLoadingPrincipal)
+ {}
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSICHANNELEVENTSINK
+ NS_DECL_NSIINTERFACEREQUESTOR
+
+ nsresult Begin();
+
+private:
+
+ ~nsManifestCheck() {}
+
+ static nsresult ReadManifest(nsIInputStream *aInputStream,
+ void *aClosure,
+ const char *aFromSegment,
+ uint32_t aOffset,
+ uint32_t aCount,
+ uint32_t *aBytesConsumed);
+
+ RefPtr<nsOfflineCacheUpdate> mUpdate;
+ nsCOMPtr<nsIURI> mURI;
+ nsCOMPtr<nsIURI> mReferrerURI;
+ nsCOMPtr<nsIPrincipal> mLoadingPrincipal;
+ nsCOMPtr<nsICryptoHash> mManifestHash;
+ nsCOMPtr<nsIChannel> mChannel;
+};
+
+//-----------------------------------------------------------------------------
+// nsManifestCheck::nsISupports
+//-----------------------------------------------------------------------------
+NS_IMPL_ISUPPORTS(nsManifestCheck,
+ nsIRequestObserver,
+ nsIStreamListener,
+ nsIChannelEventSink,
+ nsIInterfaceRequestor)
+
+//-----------------------------------------------------------------------------
+// nsManifestCheck <public>
+//-----------------------------------------------------------------------------
+
+nsresult
+nsManifestCheck::Begin()
+{
+ nsresult rv;
+ mManifestHash = do_CreateInstance("@mozilla.org/security/hash;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mManifestHash->Init(nsICryptoHash::MD5);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = NS_NewChannel(getter_AddRefs(mChannel),
+ mURI,
+ mLoadingPrincipal,
+ nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED,
+ nsIContentPolicy::TYPE_OTHER,
+ nullptr, // loadGroup
+ nullptr, // aCallbacks
+ nsIRequest::LOAD_BYPASS_CACHE);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // configure HTTP specific stuff
+ nsCOMPtr<nsIHttpChannel> httpChannel =
+ do_QueryInterface(mChannel);
+ if (httpChannel) {
+ httpChannel->SetReferrer(mReferrerURI);
+ httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("X-Moz"),
+ NS_LITERAL_CSTRING("offline-resource"),
+ false);
+ }
+
+ return mChannel->AsyncOpen2(this);
+}
+
+//-----------------------------------------------------------------------------
+// nsManifestCheck <public>
+//-----------------------------------------------------------------------------
+
+/* static */ nsresult
+nsManifestCheck::ReadManifest(nsIInputStream *aInputStream,
+ void *aClosure,
+ const char *aFromSegment,
+ uint32_t aOffset,
+ uint32_t aCount,
+ uint32_t *aBytesConsumed)
+{
+ nsManifestCheck *manifestCheck =
+ static_cast<nsManifestCheck*>(aClosure);
+
+ nsresult rv;
+ *aBytesConsumed = aCount;
+
+ rv = manifestCheck->mManifestHash->Update(
+ reinterpret_cast<const uint8_t *>(aFromSegment), aCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsManifestCheck::nsIStreamListener
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsManifestCheck::OnStartRequest(nsIRequest *aRequest,
+ nsISupports *aContext)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsManifestCheck::OnDataAvailable(nsIRequest *aRequest,
+ nsISupports *aContext,
+ nsIInputStream *aStream,
+ uint64_t aOffset,
+ uint32_t aCount)
+{
+ uint32_t bytesRead;
+ aStream->ReadSegments(ReadManifest, this, aCount, &bytesRead);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsManifestCheck::OnStopRequest(nsIRequest *aRequest,
+ nsISupports *aContext,
+ nsresult aStatus)
+{
+ nsAutoCString manifestHash;
+ if (NS_SUCCEEDED(aStatus)) {
+ mManifestHash->Finish(true, manifestHash);
+ }
+
+ mUpdate->ManifestCheckCompleted(aStatus, manifestHash);
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsManifestCheck::nsIInterfaceRequestor
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsManifestCheck::GetInterface(const nsIID &aIID, void **aResult)
+{
+ if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) {
+ NS_ADDREF_THIS();
+ *aResult = static_cast<nsIChannelEventSink *>(this);
+ return NS_OK;
+ }
+
+ return NS_ERROR_NO_INTERFACE;
+}
+
+//-----------------------------------------------------------------------------
+// nsManifestCheck::nsIChannelEventSink
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsManifestCheck::AsyncOnChannelRedirect(nsIChannel *aOldChannel,
+ nsIChannel *aNewChannel,
+ uint32_t aFlags,
+ nsIAsyncVerifyRedirectCallback *callback)
+{
+ // Redirects should cause the load (and therefore the update) to fail.
+ if (aFlags & nsIChannelEventSink::REDIRECT_INTERNAL) {
+ callback->OnRedirectVerifyCallback(NS_OK);
+ return NS_OK;
+ }
+
+ LogToConsole("Manifest check failed because its response is a redirect");
+
+ aOldChannel->Cancel(NS_ERROR_ABORT);
+ return NS_ERROR_ABORT;
+}
+
+//-----------------------------------------------------------------------------
+// nsOfflineCacheUpdateItem::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(nsOfflineCacheUpdateItem,
+ nsIRequestObserver,
+ nsIStreamListener,
+ nsIRunnable,
+ nsIInterfaceRequestor,
+ nsIChannelEventSink)
+
+//-----------------------------------------------------------------------------
+// nsOfflineCacheUpdateItem <public>
+//-----------------------------------------------------------------------------
+
+nsOfflineCacheUpdateItem::nsOfflineCacheUpdateItem(nsIURI *aURI,
+ nsIURI *aReferrerURI,
+ nsIPrincipal* aLoadingPrincipal,
+ nsIApplicationCache *aApplicationCache,
+ nsIApplicationCache *aPreviousApplicationCache,
+ uint32_t type,
+ uint32_t loadFlags)
+ : mURI(aURI)
+ , mReferrerURI(aReferrerURI)
+ , mLoadingPrincipal(aLoadingPrincipal)
+ , mApplicationCache(aApplicationCache)
+ , mPreviousApplicationCache(aPreviousApplicationCache)
+ , mItemType(type)
+ , mLoadFlags(loadFlags)
+ , mChannel(nullptr)
+ , mState(LoadStatus::UNINITIALIZED)
+ , mBytesRead(0)
+{
+}
+
+nsOfflineCacheUpdateItem::~nsOfflineCacheUpdateItem()
+{
+}
+
+nsresult
+nsOfflineCacheUpdateItem::OpenChannel(nsOfflineCacheUpdate *aUpdate)
+{
+ if (LOG_ENABLED()) {
+ LOG(("%p: Opening channel for %s", this,
+ mURI->GetSpecOrDefault().get()));
+ }
+
+ if (mUpdate) {
+ // Holding a reference to the update means this item is already
+ // in progress (has a channel, or is just in between OnStopRequest()
+ // and its Run() call. We must never open channel on this item again.
+ LOG((" %p is already running! ignoring", this));
+ return NS_ERROR_ALREADY_OPENED;
+ }
+
+ nsresult rv = nsOfflineCacheUpdate::GetCacheKey(mURI, mCacheKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t flags = nsIRequest::LOAD_BACKGROUND |
+ nsICachingChannel::LOAD_ONLY_IF_MODIFIED;
+
+ if (mApplicationCache == mPreviousApplicationCache) {
+ // Same app cache to read from and to write to is used during
+ // an only-update-check procedure. Here we protect the existing
+ // cache from being modified.
+ flags |= nsIRequest::INHIBIT_CACHING;
+ }
+
+ flags |= mLoadFlags;
+
+ rv = NS_NewChannel(getter_AddRefs(mChannel),
+ mURI,
+ mLoadingPrincipal,
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER,
+ nullptr, // aLoadGroup
+ this, // aCallbacks
+ flags);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIApplicationCacheChannel> appCacheChannel =
+ do_QueryInterface(mChannel, &rv);
+
+ // Support for nsIApplicationCacheChannel is required.
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Use the existing application cache as the cache to check.
+ rv = appCacheChannel->SetApplicationCache(mPreviousApplicationCache);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Set the new application cache as the target for write.
+ rv = appCacheChannel->SetApplicationCacheForWrite(mApplicationCache);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // configure HTTP specific stuff
+ nsCOMPtr<nsIHttpChannel> httpChannel =
+ do_QueryInterface(mChannel);
+ if (httpChannel) {
+ httpChannel->SetReferrer(mReferrerURI);
+ httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("X-Moz"),
+ NS_LITERAL_CSTRING("offline-resource"),
+ false);
+ }
+
+ rv = mChannel->AsyncOpen2(this);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mUpdate = aUpdate;
+
+ mState = LoadStatus::REQUESTED;
+
+ return NS_OK;
+}
+
+nsresult
+nsOfflineCacheUpdateItem::Cancel()
+{
+ if (mChannel) {
+ mChannel->Cancel(NS_ERROR_ABORT);
+ mChannel = nullptr;
+ }
+
+ mState = LoadStatus::UNINITIALIZED;
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsOfflineCacheUpdateItem::nsIStreamListener
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsOfflineCacheUpdateItem::OnStartRequest(nsIRequest *aRequest,
+ nsISupports *aContext)
+{
+ mState = LoadStatus::RECEIVING;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheUpdateItem::OnDataAvailable(nsIRequest *aRequest,
+ nsISupports *aContext,
+ nsIInputStream *aStream,
+ uint64_t aOffset,
+ uint32_t aCount)
+{
+ uint32_t bytesRead = 0;
+ aStream->ReadSegments(NS_DiscardSegment, nullptr, aCount, &bytesRead);
+ mBytesRead += bytesRead;
+ LOG(("loaded %u bytes into offline cache [offset=%llu]\n",
+ bytesRead, aOffset));
+
+ mUpdate->OnByteProgress(bytesRead);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheUpdateItem::OnStopRequest(nsIRequest *aRequest,
+ nsISupports *aContext,
+ nsresult aStatus)
+{
+ if (LOG_ENABLED()) {
+ LOG(("%p: Done fetching offline item %s [status=%x]\n",
+ this, mURI->GetSpecOrDefault().get(), aStatus));
+ }
+
+ if (mBytesRead == 0 && aStatus == NS_OK) {
+ // we didn't need to read (because LOAD_ONLY_IF_MODIFIED was
+ // specified), but the object should report loadedSize as if it
+ // did.
+ mChannel->GetContentLength(&mBytesRead);
+ mUpdate->OnByteProgress(mBytesRead);
+ }
+
+ if (NS_FAILED(aStatus)) {
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
+ if (httpChannel) {
+ bool isNoStore;
+ if (NS_SUCCEEDED(httpChannel->IsNoStoreResponse(&isNoStore))
+ && isNoStore) {
+ LogToConsole("Offline cache manifest item has Cache-control: no-store header",
+ this);
+ }
+ }
+ }
+
+ // We need to notify the update that the load is complete, but we
+ // want to give the channel a chance to close the cache entries.
+ NS_DispatchToCurrentThread(this);
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsOfflineCacheUpdateItem::nsIRunnable
+//-----------------------------------------------------------------------------
+NS_IMETHODIMP
+nsOfflineCacheUpdateItem::Run()
+{
+ // Set mState to LOADED here rather than in OnStopRequest to prevent
+ // race condition when checking state of all mItems in ProcessNextURI().
+ // If state would have been set in OnStopRequest we could mistakenly
+ // take this item as already finished and finish the update process too
+ // early when ProcessNextURI() would get called between OnStopRequest()
+ // and Run() of this item. Finish() would then have been called twice.
+ mState = LoadStatus::LOADED;
+
+ RefPtr<nsOfflineCacheUpdate> update;
+ update.swap(mUpdate);
+ update->LoadCompleted(this);
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsOfflineCacheUpdateItem::nsIInterfaceRequestor
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsOfflineCacheUpdateItem::GetInterface(const nsIID &aIID, void **aResult)
+{
+ if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) {
+ NS_ADDREF_THIS();
+ *aResult = static_cast<nsIChannelEventSink *>(this);
+ return NS_OK;
+ }
+
+ return NS_ERROR_NO_INTERFACE;
+}
+
+//-----------------------------------------------------------------------------
+// nsOfflineCacheUpdateItem::nsIChannelEventSink
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsOfflineCacheUpdateItem::AsyncOnChannelRedirect(nsIChannel *aOldChannel,
+ nsIChannel *aNewChannel,
+ uint32_t aFlags,
+ nsIAsyncVerifyRedirectCallback *cb)
+{
+ if (!(aFlags & nsIChannelEventSink::REDIRECT_INTERNAL)) {
+ // Don't allow redirect in case of non-internal redirect and cancel
+ // the channel to clean the cache entry.
+ LogToConsole("Offline cache manifest failed because an item redirects", this);
+
+ aOldChannel->Cancel(NS_ERROR_ABORT);
+ return NS_ERROR_ABORT;
+ }
+
+ nsCOMPtr<nsIURI> newURI;
+ nsresult rv = aNewChannel->GetURI(getter_AddRefs(newURI));
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIApplicationCacheChannel> appCacheChannel =
+ do_QueryInterface(aNewChannel);
+ if (appCacheChannel) {
+ rv = appCacheChannel->SetApplicationCacheForWrite(mApplicationCache);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsAutoCString oldScheme;
+ mURI->GetScheme(oldScheme);
+
+ bool match;
+ if (NS_FAILED(newURI->SchemeIs(oldScheme.get(), &match)) || !match) {
+ LOG(("rejected: redirected to a different scheme\n"));
+ return NS_ERROR_ABORT;
+ }
+
+ // HTTP request headers are not automatically forwarded to the new channel.
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aNewChannel);
+ NS_ENSURE_STATE(httpChannel);
+
+ httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("X-Moz"),
+ NS_LITERAL_CSTRING("offline-resource"),
+ false);
+
+ mChannel = aNewChannel;
+
+ cb->OnRedirectVerifyCallback(NS_OK);
+ return NS_OK;
+}
+
+nsresult
+nsOfflineCacheUpdateItem::GetRequestSucceeded(bool * succeeded)
+{
+ *succeeded = false;
+
+ if (!mChannel)
+ return NS_OK;
+
+ nsresult rv;
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool reqSucceeded;
+ rv = httpChannel->GetRequestSucceeded(&reqSucceeded);
+ if (NS_ERROR_NOT_AVAILABLE == rv)
+ return NS_OK;
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!reqSucceeded) {
+ LOG(("Request failed"));
+ return NS_OK;
+ }
+
+ nsresult channelStatus;
+ rv = httpChannel->GetStatus(&channelStatus);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (NS_FAILED(channelStatus)) {
+ LOG(("Channel status=0x%08x", channelStatus));
+ return NS_OK;
+ }
+
+ *succeeded = true;
+ return NS_OK;
+}
+
+bool
+nsOfflineCacheUpdateItem::IsScheduled()
+{
+ return mState == LoadStatus::UNINITIALIZED;
+}
+
+bool
+nsOfflineCacheUpdateItem::IsInProgress()
+{
+ return mState == LoadStatus::REQUESTED ||
+ mState == LoadStatus::RECEIVING;
+}
+
+bool
+nsOfflineCacheUpdateItem::IsCompleted()
+{
+ return mState == LoadStatus::LOADED;
+}
+
+nsresult
+nsOfflineCacheUpdateItem::GetStatus(uint16_t *aStatus)
+{
+ if (!mChannel) {
+ *aStatus = 0;
+ return NS_OK;
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t httpStatus;
+ rv = httpChannel->GetResponseStatus(&httpStatus);
+ if (rv == NS_ERROR_NOT_AVAILABLE) {
+ *aStatus = 0;
+ return NS_OK;
+ }
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ *aStatus = uint16_t(httpStatus);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsOfflineManifestItem
+//-----------------------------------------------------------------------------
+
+//-----------------------------------------------------------------------------
+// nsOfflineManifestItem <public>
+//-----------------------------------------------------------------------------
+
+nsOfflineManifestItem::nsOfflineManifestItem(nsIURI *aURI,
+ nsIURI *aReferrerURI,
+ nsIPrincipal* aLoadingPrincipal,
+ nsIApplicationCache *aApplicationCache,
+ nsIApplicationCache *aPreviousApplicationCache)
+ : nsOfflineCacheUpdateItem(aURI, aReferrerURI, aLoadingPrincipal,
+ aApplicationCache, aPreviousApplicationCache,
+ nsIApplicationCache::ITEM_MANIFEST, 0)
+ , mParserState(PARSE_INIT)
+ , mNeedsUpdate(true)
+ , mStrictFileOriginPolicy(false)
+ , mManifestHashInitialized(false)
+{
+ ReadStrictFileOriginPolicyPref();
+}
+
+nsOfflineManifestItem::~nsOfflineManifestItem()
+{
+}
+
+//-----------------------------------------------------------------------------
+// nsOfflineManifestItem <private>
+//-----------------------------------------------------------------------------
+
+/* static */
+nsresult
+nsOfflineManifestItem::ReadManifest(nsIInputStream *aInputStream,
+ void *aClosure,
+ const char *aFromSegment,
+ uint32_t aOffset,
+ uint32_t aCount,
+ uint32_t *aBytesConsumed)
+{
+ nsOfflineManifestItem *manifest =
+ static_cast<nsOfflineManifestItem*>(aClosure);
+
+ nsresult rv;
+
+ *aBytesConsumed = aCount;
+
+ if (manifest->mParserState == PARSE_ERROR) {
+ // parse already failed, ignore this
+ return NS_OK;
+ }
+
+ if (!manifest->mManifestHashInitialized) {
+ // Avoid re-creation of crypto hash when it fails from some reason the first time
+ manifest->mManifestHashInitialized = true;
+
+ manifest->mManifestHash = do_CreateInstance("@mozilla.org/security/hash;1", &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = manifest->mManifestHash->Init(nsICryptoHash::MD5);
+ if (NS_FAILED(rv)) {
+ manifest->mManifestHash = nullptr;
+ LOG(("Could not initialize manifest hash for byte-to-byte check, rv=%08x", rv));
+ }
+ }
+ }
+
+ if (manifest->mManifestHash) {
+ rv = manifest->mManifestHash->Update(reinterpret_cast<const uint8_t *>(aFromSegment), aCount);
+ if (NS_FAILED(rv)) {
+ manifest->mManifestHash = nullptr;
+ LOG(("Could not update manifest hash, rv=%08x", rv));
+ }
+ }
+
+ manifest->mReadBuf.Append(aFromSegment, aCount);
+
+ nsCString::const_iterator begin, iter, end;
+ manifest->mReadBuf.BeginReading(begin);
+ manifest->mReadBuf.EndReading(end);
+
+ for (iter = begin; iter != end; iter++) {
+ if (*iter == '\r' || *iter == '\n') {
+ rv = manifest->HandleManifestLine(begin, iter);
+
+ if (NS_FAILED(rv)) {
+ LOG(("HandleManifestLine failed with 0x%08x", rv));
+ *aBytesConsumed = 0; // Avoid assertion failure in stream tee
+ return NS_ERROR_ABORT;
+ }
+
+ begin = iter;
+ begin++;
+ }
+ }
+
+ // any leftovers are saved for next time
+ manifest->mReadBuf = Substring(begin, end);
+
+ return NS_OK;
+}
+
+nsresult
+nsOfflineManifestItem::AddNamespace(uint32_t namespaceType,
+ const nsCString &namespaceSpec,
+ const nsCString &data)
+
+{
+ nsresult rv;
+ if (!mNamespaces) {
+ mNamespaces = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsCOMPtr<nsIApplicationCacheNamespace> ns =
+ do_CreateInstance(NS_APPLICATIONCACHENAMESPACE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = ns->Init(namespaceType, namespaceSpec, data);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mNamespaces->AppendElement(ns, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult
+nsOfflineManifestItem::HandleManifestLine(const nsCString::const_iterator &aBegin,
+ const nsCString::const_iterator &aEnd)
+{
+ nsCString::const_iterator begin = aBegin;
+ nsCString::const_iterator end = aEnd;
+
+ // all lines ignore trailing spaces and tabs
+ nsCString::const_iterator last = end;
+ --last;
+ while (end != begin && (*last == ' ' || *last == '\t')) {
+ --end;
+ --last;
+ }
+
+ if (mParserState == PARSE_INIT) {
+ // Allow a UTF-8 BOM
+ if (begin != end && static_cast<unsigned char>(*begin) == 0xef) {
+ if (++begin == end || static_cast<unsigned char>(*begin) != 0xbb ||
+ ++begin == end || static_cast<unsigned char>(*begin) != 0xbf) {
+ mParserState = PARSE_ERROR;
+ LogToConsole("Offline cache manifest BOM error", this);
+ return NS_OK;
+ }
+ ++begin;
+ }
+
+ const nsCSubstring &magic = Substring(begin, end);
+
+ if (!magic.EqualsLiteral("CACHE MANIFEST")) {
+ mParserState = PARSE_ERROR;
+ LogToConsole("Offline cache manifest magic incorrect", this);
+ return NS_OK;
+ }
+
+ mParserState = PARSE_CACHE_ENTRIES;
+ return NS_OK;
+ }
+
+ // lines other than the first ignore leading spaces and tabs
+ while (begin != end && (*begin == ' ' || *begin == '\t'))
+ begin++;
+
+ // ignore blank lines and comments
+ if (begin == end || *begin == '#')
+ return NS_OK;
+
+ const nsCSubstring &line = Substring(begin, end);
+
+ if (line.EqualsLiteral("CACHE:")) {
+ mParserState = PARSE_CACHE_ENTRIES;
+ return NS_OK;
+ }
+
+ if (line.EqualsLiteral("FALLBACK:")) {
+ mParserState = PARSE_FALLBACK_ENTRIES;
+ return NS_OK;
+ }
+
+ if (line.EqualsLiteral("NETWORK:")) {
+ mParserState = PARSE_BYPASS_ENTRIES;
+ return NS_OK;
+ }
+
+ // Every other section type we don't know must be silently ignored.
+ nsCString::const_iterator lastChar = end;
+ if (*(--lastChar) == ':') {
+ mParserState = PARSE_UNKNOWN_SECTION;
+ return NS_OK;
+ }
+
+ nsresult rv;
+
+ switch(mParserState) {
+ case PARSE_INIT:
+ case PARSE_ERROR: {
+ // this should have been dealt with earlier
+ return NS_ERROR_FAILURE;
+ }
+
+ case PARSE_UNKNOWN_SECTION: {
+ // just jump over
+ return NS_OK;
+ }
+
+ case PARSE_CACHE_ENTRIES: {
+ nsCOMPtr<nsIURI> uri;
+ rv = NS_NewURI(getter_AddRefs(uri), line, nullptr, mURI);
+ if (NS_FAILED(rv))
+ break;
+ if (NS_FAILED(DropReferenceFromURL(uri)))
+ break;
+
+ nsAutoCString scheme;
+ uri->GetScheme(scheme);
+
+ // Manifest URIs must have the same scheme as the manifest.
+ bool match;
+ if (NS_FAILED(mURI->SchemeIs(scheme.get(), &match)) || !match)
+ break;
+
+ mExplicitURIs.AppendObject(uri);
+
+ if (!NS_SecurityCompareURIs(mURI, uri,
+ mStrictFileOriginPolicy)) {
+ mAnonymousURIs.AppendObject(uri);
+ }
+
+ break;
+ }
+
+ case PARSE_FALLBACK_ENTRIES: {
+ int32_t separator = line.FindChar(' ');
+ if (separator == kNotFound) {
+ separator = line.FindChar('\t');
+ if (separator == kNotFound)
+ break;
+ }
+
+ nsCString namespaceSpec(Substring(line, 0, separator));
+ nsCString fallbackSpec(Substring(line, separator + 1));
+ namespaceSpec.CompressWhitespace();
+ fallbackSpec.CompressWhitespace();
+
+ nsCOMPtr<nsIURI> namespaceURI;
+ rv = NS_NewURI(getter_AddRefs(namespaceURI), namespaceSpec, nullptr, mURI);
+ if (NS_FAILED(rv))
+ break;
+ if (NS_FAILED(DropReferenceFromURL(namespaceURI)))
+ break;
+ rv = namespaceURI->GetAsciiSpec(namespaceSpec);
+ if (NS_FAILED(rv))
+ break;
+
+ nsCOMPtr<nsIURI> fallbackURI;
+ rv = NS_NewURI(getter_AddRefs(fallbackURI), fallbackSpec, nullptr, mURI);
+ if (NS_FAILED(rv))
+ break;
+ if (NS_FAILED(DropReferenceFromURL(fallbackURI)))
+ break;
+ rv = fallbackURI->GetAsciiSpec(fallbackSpec);
+ if (NS_FAILED(rv))
+ break;
+
+ // Manifest and namespace must be same origin
+ if (!NS_SecurityCompareURIs(mURI, namespaceURI,
+ mStrictFileOriginPolicy))
+ break;
+
+ // Fallback and namespace must be same origin
+ if (!NS_SecurityCompareURIs(namespaceURI, fallbackURI,
+ mStrictFileOriginPolicy))
+ break;
+
+ mFallbackURIs.AppendObject(fallbackURI);
+
+ AddNamespace(nsIApplicationCacheNamespace::NAMESPACE_FALLBACK,
+ namespaceSpec, fallbackSpec);
+ break;
+ }
+
+ case PARSE_BYPASS_ENTRIES: {
+ if (line[0] == '*' && (line.Length() == 1 || line[1] == ' ' || line[1] == '\t'))
+ {
+ // '*' indicates to make the online whitelist wildcard flag open,
+ // i.e. do allow load of resources not present in the offline cache
+ // or not conforming any namespace.
+ // We achive that simply by adding an 'empty' - i.e. universal
+ // namespace of BYPASS type into the cache.
+ AddNamespace(nsIApplicationCacheNamespace::NAMESPACE_BYPASS,
+ EmptyCString(), EmptyCString());
+ break;
+ }
+
+ nsCOMPtr<nsIURI> bypassURI;
+ rv = NS_NewURI(getter_AddRefs(bypassURI), line, nullptr, mURI);
+ if (NS_FAILED(rv))
+ break;
+
+ nsAutoCString scheme;
+ bypassURI->GetScheme(scheme);
+ bool equals;
+ if (NS_FAILED(mURI->SchemeIs(scheme.get(), &equals)) || !equals)
+ break;
+ if (NS_FAILED(DropReferenceFromURL(bypassURI)))
+ break;
+ nsCString spec;
+ if (NS_FAILED(bypassURI->GetAsciiSpec(spec)))
+ break;
+
+ AddNamespace(nsIApplicationCacheNamespace::NAMESPACE_BYPASS,
+ spec, EmptyCString());
+ break;
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsOfflineManifestItem::GetOldManifestContentHash(nsIRequest *aRequest)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsICachingChannel> cachingChannel = do_QueryInterface(aRequest, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // load the main cache token that is actually the old offline cache token and
+ // read previous manifest content hash value
+ nsCOMPtr<nsISupports> cacheToken;
+ cachingChannel->GetCacheToken(getter_AddRefs(cacheToken));
+ if (cacheToken) {
+ nsCOMPtr<nsICacheEntry> cacheDescriptor(do_QueryInterface(cacheToken, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = cacheDescriptor->GetMetaDataElement("offline-manifest-hash", getter_Copies(mOldManifestHashValue));
+ if (NS_FAILED(rv))
+ mOldManifestHashValue.Truncate();
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsOfflineManifestItem::CheckNewManifestContentHash(nsIRequest *aRequest)
+{
+ nsresult rv;
+
+ if (!mManifestHash) {
+ // Nothing to compare against...
+ return NS_OK;
+ }
+
+ nsCString newManifestHashValue;
+ rv = mManifestHash->Finish(true, mManifestHashValue);
+ mManifestHash = nullptr;
+
+ if (NS_FAILED(rv)) {
+ LOG(("Could not finish manifest hash, rv=%08x", rv));
+ // This is not critical error
+ return NS_OK;
+ }
+
+ if (!ParseSucceeded()) {
+ // Parsing failed, the hash is not valid
+ return NS_OK;
+ }
+
+ if (mOldManifestHashValue == mManifestHashValue) {
+ LOG(("Update not needed, downloaded manifest content is byte-for-byte identical"));
+ mNeedsUpdate = false;
+ }
+
+ // Store the manifest content hash value to the new
+ // offline cache token
+ nsCOMPtr<nsICachingChannel> cachingChannel = do_QueryInterface(aRequest, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISupports> cacheToken;
+ cachingChannel->GetOfflineCacheToken(getter_AddRefs(cacheToken));
+ if (cacheToken) {
+ nsCOMPtr<nsICacheEntry> cacheDescriptor(do_QueryInterface(cacheToken, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = cacheDescriptor->SetMetaDataElement("offline-manifest-hash", mManifestHashValue.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+void
+nsOfflineManifestItem::ReadStrictFileOriginPolicyPref()
+{
+ mStrictFileOriginPolicy =
+ Preferences::GetBool("security.fileuri.strict_origin_policy", true);
+}
+
+NS_IMETHODIMP
+nsOfflineManifestItem::OnStartRequest(nsIRequest *aRequest,
+ nsISupports *aContext)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIHttpChannel> channel = do_QueryInterface(aRequest, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool succeeded;
+ rv = channel->GetRequestSucceeded(&succeeded);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!succeeded) {
+ LOG(("HTTP request failed"));
+ LogToConsole("Offline cache manifest HTTP request failed", this);
+ mParserState = PARSE_ERROR;
+ return NS_ERROR_ABORT;
+ }
+
+ rv = GetOldManifestContentHash(aRequest);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return nsOfflineCacheUpdateItem::OnStartRequest(aRequest, aContext);
+}
+
+NS_IMETHODIMP
+nsOfflineManifestItem::OnDataAvailable(nsIRequest *aRequest,
+ nsISupports *aContext,
+ nsIInputStream *aStream,
+ uint64_t aOffset,
+ uint32_t aCount)
+{
+ uint32_t bytesRead = 0;
+ aStream->ReadSegments(ReadManifest, this, aCount, &bytesRead);
+ mBytesRead += bytesRead;
+
+ if (mParserState == PARSE_ERROR) {
+ LOG(("OnDataAvailable is canceling the request due a parse error\n"));
+ return NS_ERROR_ABORT;
+ }
+
+ LOG(("loaded %u bytes into offline cache [offset=%u]\n",
+ bytesRead, aOffset));
+
+ // All the parent method does is read and discard, don't bother
+ // chaining up.
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineManifestItem::OnStopRequest(nsIRequest *aRequest,
+ nsISupports *aContext,
+ nsresult aStatus)
+{
+ if (mBytesRead == 0) {
+ // We didn't need to read (because LOAD_ONLY_IF_MODIFIED was
+ // specified).
+ mNeedsUpdate = false;
+ } else {
+ // Handle any leftover manifest data.
+ nsCString::const_iterator begin, end;
+ mReadBuf.BeginReading(begin);
+ mReadBuf.EndReading(end);
+ nsresult rv = HandleManifestLine(begin, end);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = CheckNewManifestContentHash(aRequest);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return nsOfflineCacheUpdateItem::OnStopRequest(aRequest, aContext, aStatus);
+}
+
+//-----------------------------------------------------------------------------
+// nsOfflineCacheUpdate::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(nsOfflineCacheUpdate,
+ nsIOfflineCacheUpdateObserver,
+ nsIOfflineCacheUpdate,
+ nsIRunnable)
+
+//-----------------------------------------------------------------------------
+// nsOfflineCacheUpdate <public>
+//-----------------------------------------------------------------------------
+
+nsOfflineCacheUpdate::nsOfflineCacheUpdate()
+ : mState(STATE_UNINITIALIZED)
+ , mAddedItems(false)
+ , mPartialUpdate(false)
+ , mOnlyCheckUpdate(false)
+ , mSucceeded(true)
+ , mObsolete(false)
+ , mItemsInProgress(0)
+ , mRescheduleCount(0)
+ , mPinnedEntryRetriesCount(0)
+ , mPinned(false)
+ , mByteProgress(0)
+{
+}
+
+nsOfflineCacheUpdate::~nsOfflineCacheUpdate()
+{
+ LOG(("nsOfflineCacheUpdate::~nsOfflineCacheUpdate [%p]", this));
+}
+
+/* static */
+nsresult
+nsOfflineCacheUpdate::GetCacheKey(nsIURI *aURI, nsACString &aKey)
+{
+ aKey.Truncate();
+
+ nsCOMPtr<nsIURI> newURI;
+ nsresult rv = aURI->CloneIgnoringRef(getter_AddRefs(newURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = newURI->GetAsciiSpec(aKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult
+nsOfflineCacheUpdate::InitInternal(nsIURI *aManifestURI,
+ nsIPrincipal* aLoadingPrincipal)
+{
+ nsresult rv;
+
+ // Only http and https applications are supported.
+ bool match;
+ rv = aManifestURI->SchemeIs("http", &match);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!match) {
+ rv = aManifestURI->SchemeIs("https", &match);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!match)
+ return NS_ERROR_ABORT;
+ }
+
+ mManifestURI = aManifestURI;
+ mLoadingPrincipal = aLoadingPrincipal;
+
+ rv = mManifestURI->GetAsciiHost(mUpdateDomain);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mPartialUpdate = false;
+
+ return NS_OK;
+}
+
+nsresult
+nsOfflineCacheUpdate::Init(nsIURI *aManifestURI,
+ nsIURI *aDocumentURI,
+ nsIPrincipal* aLoadingPrincipal,
+ nsIDOMDocument *aDocument,
+ nsIFile *aCustomProfileDir)
+{
+ nsresult rv;
+
+ // Make sure the service has been initialized
+ nsOfflineCacheUpdateService* service =
+ nsOfflineCacheUpdateService::EnsureService();
+ if (!service)
+ return NS_ERROR_FAILURE;
+
+ LOG(("nsOfflineCacheUpdate::Init [%p]", this));
+
+ rv = InitInternal(aManifestURI, aLoadingPrincipal);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIApplicationCacheService> cacheService =
+ do_GetService(NS_APPLICATIONCACHESERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString originSuffix;
+ rv = aLoadingPrincipal->GetOriginSuffix(originSuffix);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mDocumentURI = aDocumentURI;
+
+ if (aCustomProfileDir) {
+ rv = cacheService->BuildGroupIDForSuffix(aManifestURI, originSuffix, mGroupID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Create only a new offline application cache in the custom profile
+ // This is a preload of a new cache.
+
+ // XXX Custom updates don't support "updating" of an existing cache
+ // in the custom profile at the moment. This support can be, though,
+ // simply added as well when needed.
+ mPreviousApplicationCache = nullptr;
+
+ rv = cacheService->CreateCustomApplicationCache(mGroupID,
+ aCustomProfileDir,
+ kCustomProfileQuota,
+ getter_AddRefs(mApplicationCache));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mCustomProfileDir = aCustomProfileDir;
+ }
+ else {
+ rv = cacheService->BuildGroupIDForSuffix(aManifestURI, originSuffix, mGroupID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = cacheService->GetActiveCache(mGroupID,
+ getter_AddRefs(mPreviousApplicationCache));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = cacheService->CreateApplicationCache(mGroupID,
+ getter_AddRefs(mApplicationCache));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = nsOfflineCacheUpdateService::OfflineAppPinnedForURI(aDocumentURI,
+ nullptr,
+ &mPinned);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mState = STATE_INITIALIZED;
+ return NS_OK;
+}
+
+nsresult
+nsOfflineCacheUpdate::InitForUpdateCheck(nsIURI *aManifestURI,
+ nsIPrincipal* aLoadingPrincipal,
+ nsIObserver *aObserver)
+{
+ nsresult rv;
+
+ // Make sure the service has been initialized
+ nsOfflineCacheUpdateService* service =
+ nsOfflineCacheUpdateService::EnsureService();
+ if (!service)
+ return NS_ERROR_FAILURE;
+
+ LOG(("nsOfflineCacheUpdate::InitForUpdateCheck [%p]", this));
+
+ rv = InitInternal(aManifestURI, aLoadingPrincipal);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIApplicationCacheService> cacheService =
+ do_GetService(NS_APPLICATIONCACHESERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString originSuffix;
+ rv = aLoadingPrincipal->GetOriginSuffix(originSuffix);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = cacheService->BuildGroupIDForSuffix(aManifestURI, originSuffix, mGroupID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = cacheService->GetActiveCache(mGroupID,
+ getter_AddRefs(mPreviousApplicationCache));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // To load the manifest properly using current app cache to satisfy and
+ // also to compare the cached content hash value we have to set 'some'
+ // app cache to write to on the channel. Otherwise the cached version will
+ // be used and no actual network request will be made. We use the same
+ // app cache here. OpenChannel prevents caching in this case using
+ // INHIBIT_CACHING load flag.
+ mApplicationCache = mPreviousApplicationCache;
+
+ rv = nsOfflineCacheUpdateService::OfflineAppPinnedForURI(aManifestURI,
+ nullptr,
+ &mPinned);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mUpdateAvailableObserver = aObserver;
+ mOnlyCheckUpdate = true;
+
+ mState = STATE_INITIALIZED;
+ return NS_OK;
+}
+
+nsresult
+nsOfflineCacheUpdate::InitPartial(nsIURI *aManifestURI,
+ const nsACString& clientID,
+ nsIURI *aDocumentURI,
+ nsIPrincipal *aLoadingPrincipal)
+{
+ nsresult rv;
+
+ // Make sure the service has been initialized
+ nsOfflineCacheUpdateService* service =
+ nsOfflineCacheUpdateService::EnsureService();
+ if (!service)
+ return NS_ERROR_FAILURE;
+
+ LOG(("nsOfflineCacheUpdate::InitPartial [%p]", this));
+
+ mPartialUpdate = true;
+ mDocumentURI = aDocumentURI;
+ mLoadingPrincipal = aLoadingPrincipal;
+
+ mManifestURI = aManifestURI;
+ rv = mManifestURI->GetAsciiHost(mUpdateDomain);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIApplicationCacheService> cacheService =
+ do_GetService(NS_APPLICATIONCACHESERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = cacheService->GetApplicationCache(clientID,
+ getter_AddRefs(mApplicationCache));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!mApplicationCache) {
+ nsAutoCString manifestSpec;
+ rv = GetCacheKey(mManifestURI, manifestSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = cacheService->CreateApplicationCache
+ (manifestSpec, getter_AddRefs(mApplicationCache));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = mApplicationCache->GetManifestURI(getter_AddRefs(mManifestURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString groupID;
+ rv = mApplicationCache->GetGroupID(groupID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = nsOfflineCacheUpdateService::OfflineAppPinnedForURI(aDocumentURI,
+ nullptr,
+ &mPinned);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mState = STATE_INITIALIZED;
+ return NS_OK;
+}
+
+nsresult
+nsOfflineCacheUpdate::HandleManifest(bool *aDoUpdate)
+{
+ // Be pessimistic
+ *aDoUpdate = false;
+
+ bool succeeded;
+ nsresult rv = mManifestItem->GetRequestSucceeded(&succeeded);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!succeeded || !mManifestItem->ParseSucceeded()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!mManifestItem->NeedsUpdate()) {
+ return NS_OK;
+ }
+
+ // Add items requested by the manifest.
+ const nsCOMArray<nsIURI> &manifestURIs = mManifestItem->GetExplicitURIs();
+ for (int32_t i = 0; i < manifestURIs.Count(); i++) {
+ rv = AddURI(manifestURIs[i], nsIApplicationCache::ITEM_EXPLICIT);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ const nsCOMArray<nsIURI> &anonURIs = mManifestItem->GetAnonymousURIs();
+ for (int32_t i = 0; i < anonURIs.Count(); i++) {
+ rv = AddURI(anonURIs[i], nsIApplicationCache::ITEM_EXPLICIT,
+ nsIRequest::LOAD_ANONYMOUS);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ const nsCOMArray<nsIURI> &fallbackURIs = mManifestItem->GetFallbackURIs();
+ for (int32_t i = 0; i < fallbackURIs.Count(); i++) {
+ rv = AddURI(fallbackURIs[i], nsIApplicationCache::ITEM_FALLBACK);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // The document that requested the manifest is implicitly included
+ // as part of that manifest update.
+ rv = AddURI(mDocumentURI, nsIApplicationCache::ITEM_IMPLICIT);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Add items previously cached implicitly
+ rv = AddExistingItems(nsIApplicationCache::ITEM_IMPLICIT);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Add items requested by the script API
+ rv = AddExistingItems(nsIApplicationCache::ITEM_DYNAMIC);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Add opportunistically cached items conforming current opportunistic
+ // namespace list
+ rv = AddExistingItems(nsIApplicationCache::ITEM_OPPORTUNISTIC,
+ &mManifestItem->GetOpportunisticNamespaces());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aDoUpdate = true;
+
+ return NS_OK;
+}
+
+bool
+nsOfflineCacheUpdate::CheckUpdateAvailability()
+{
+ nsresult rv;
+
+ bool succeeded;
+ rv = mManifestItem->GetRequestSucceeded(&succeeded);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ if (!succeeded || !mManifestItem->ParseSucceeded()) {
+ return false;
+ }
+
+ if (!mPinned) {
+ uint16_t status;
+ rv = mManifestItem->GetStatus(&status);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ // Treat these as there would be an update available,
+ // since this is indication of demand to remove this
+ // offline cache.
+ if (status == 404 || status == 410) {
+ return true;
+ }
+ }
+
+ return mManifestItem->NeedsUpdate();
+}
+
+void
+nsOfflineCacheUpdate::LoadCompleted(nsOfflineCacheUpdateItem *aItem)
+{
+ nsresult rv;
+
+ LOG(("nsOfflineCacheUpdate::LoadCompleted [%p]", this));
+
+ if (mState == STATE_FINISHED) {
+ LOG((" after completion, ignoring"));
+ return;
+ }
+
+ // Keep the object alive through a Finish() call.
+ nsCOMPtr<nsIOfflineCacheUpdate> kungFuDeathGrip(this);
+
+ if (mState == STATE_CANCELLED) {
+ NotifyState(nsIOfflineCacheUpdateObserver::STATE_ERROR);
+ Finish();
+ return;
+ }
+
+ if (mState == STATE_CHECKING) {
+ // Manifest load finished.
+
+ if (mOnlyCheckUpdate) {
+ Finish();
+ NotifyUpdateAvailability(CheckUpdateAvailability());
+ return;
+ }
+
+ NS_ASSERTION(mManifestItem,
+ "Must have a manifest item in STATE_CHECKING.");
+ NS_ASSERTION(mManifestItem == aItem,
+ "Unexpected aItem in nsOfflineCacheUpdate::LoadCompleted");
+
+ // A 404 or 410 is interpreted as an intentional removal of
+ // the manifest file, rather than a transient server error.
+ // Obsolete this cache group if one of these is returned.
+ uint16_t status;
+ rv = mManifestItem->GetStatus(&status);
+ if (status == 404 || status == 410) {
+ LogToConsole("Offline cache manifest removed, cache cleared", mManifestItem);
+ mSucceeded = false;
+ if (mPreviousApplicationCache) {
+ if (mPinned) {
+ // Do not obsolete a pinned application.
+ NotifyState(nsIOfflineCacheUpdateObserver::STATE_NOUPDATE);
+ } else {
+ NotifyState(nsIOfflineCacheUpdateObserver::STATE_OBSOLETE);
+ mObsolete = true;
+ }
+ } else {
+ NotifyState(nsIOfflineCacheUpdateObserver::STATE_ERROR);
+ mObsolete = true;
+ }
+ Finish();
+ return;
+ }
+
+ bool doUpdate;
+ if (NS_FAILED(HandleManifest(&doUpdate))) {
+ mSucceeded = false;
+ NotifyState(nsIOfflineCacheUpdateObserver::STATE_ERROR);
+ Finish();
+ return;
+ }
+
+ if (!doUpdate) {
+ LogToConsole("Offline cache doesn't need to update", mManifestItem);
+
+ mSucceeded = false;
+
+ AssociateDocuments(mPreviousApplicationCache);
+
+ ScheduleImplicit();
+
+ // If we didn't need an implicit update, we can
+ // send noupdate and end the update now.
+ if (!mImplicitUpdate) {
+ NotifyState(nsIOfflineCacheUpdateObserver::STATE_NOUPDATE);
+ Finish();
+ }
+ return;
+ }
+
+ rv = mApplicationCache->MarkEntry(mManifestItem->mCacheKey,
+ mManifestItem->mItemType);
+ if (NS_FAILED(rv)) {
+ mSucceeded = false;
+ NotifyState(nsIOfflineCacheUpdateObserver::STATE_ERROR);
+ Finish();
+ return;
+ }
+
+ mState = STATE_DOWNLOADING;
+ NotifyState(nsIOfflineCacheUpdateObserver::STATE_DOWNLOADING);
+
+ // Start fetching resources.
+ ProcessNextURI();
+
+ return;
+ }
+
+ // Normal load finished.
+ if (mItemsInProgress) // Just to be safe here!
+ --mItemsInProgress;
+
+ bool succeeded;
+ rv = aItem->GetRequestSucceeded(&succeeded);
+
+ if (mPinned && NS_SUCCEEDED(rv) && succeeded) {
+ uint32_t dummy_cache_type;
+ rv = mApplicationCache->GetTypes(aItem->mCacheKey, &dummy_cache_type);
+ bool item_doomed = NS_FAILED(rv); // can not find it? -> doomed
+
+ if (item_doomed &&
+ mPinnedEntryRetriesCount < kPinnedEntryRetriesLimit &&
+ (aItem->mItemType & (nsIApplicationCache::ITEM_EXPLICIT |
+ nsIApplicationCache::ITEM_FALLBACK))) {
+ rv = EvictOneNonPinned();
+ if (NS_FAILED(rv)) {
+ mSucceeded = false;
+ NotifyState(nsIOfflineCacheUpdateObserver::STATE_ERROR);
+ Finish();
+ return;
+ }
+
+ // This reverts the item state to UNINITIALIZED that makes it to
+ // be scheduled for download again.
+ rv = aItem->Cancel();
+ if (NS_FAILED(rv)) {
+ mSucceeded = false;
+ NotifyState(nsIOfflineCacheUpdateObserver::STATE_ERROR);
+ Finish();
+ return;
+ }
+
+ mPinnedEntryRetriesCount++;
+
+ LogToConsole("An unpinned offline cache deleted");
+
+ // Retry this item.
+ ProcessNextURI();
+ return;
+ }
+ }
+
+ // According to parallelism this may imply more pinned retries count,
+ // but that is not critical, since at one moment the algoritm will
+ // stop anyway. Also, this code may soon be completely removed
+ // after we have a separate storage for pinned apps.
+ mPinnedEntryRetriesCount = 0;
+
+ // Check for failures. 3XX, 4XX and 5XX errors on items explicitly
+ // listed in the manifest will cause the update to fail.
+ if (NS_FAILED(rv) || !succeeded) {
+ if (aItem->mItemType &
+ (nsIApplicationCache::ITEM_EXPLICIT |
+ nsIApplicationCache::ITEM_FALLBACK)) {
+ LogToConsole("Offline cache manifest item failed to load", aItem);
+ mSucceeded = false;
+ }
+ } else {
+ rv = mApplicationCache->MarkEntry(aItem->mCacheKey, aItem->mItemType);
+ if (NS_FAILED(rv)) {
+ mSucceeded = false;
+ }
+ }
+
+ if (!mSucceeded) {
+ NotifyState(nsIOfflineCacheUpdateObserver::STATE_ERROR);
+ Finish();
+ return;
+ }
+
+ NotifyState(nsIOfflineCacheUpdateObserver::STATE_ITEMCOMPLETED);
+
+ ProcessNextURI();
+}
+
+void
+nsOfflineCacheUpdate::ManifestCheckCompleted(nsresult aStatus,
+ const nsCString &aManifestHash)
+{
+ // Keep the object alive through a Finish() call.
+ nsCOMPtr<nsIOfflineCacheUpdate> kungFuDeathGrip(this);
+
+ if (NS_SUCCEEDED(aStatus)) {
+ nsAutoCString firstManifestHash;
+ mManifestItem->GetManifestHash(firstManifestHash);
+ if (aManifestHash != firstManifestHash) {
+ LOG(("Manifest has changed during cache items download [%p]", this));
+ LogToConsole("Offline cache manifest changed during update", mManifestItem);
+ aStatus = NS_ERROR_FAILURE;
+ }
+ }
+
+ if (NS_FAILED(aStatus)) {
+ mSucceeded = false;
+ NotifyState(nsIOfflineCacheUpdateObserver::STATE_ERROR);
+ }
+
+ if (NS_FAILED(aStatus) && mRescheduleCount < kRescheduleLimit) {
+ // Do the final stuff but prevent notification of STATE_FINISHED.
+ // That would disconnect listeners that are responsible for document
+ // association after a successful update. Forwarding notifications
+ // from a new update through this dead update to them is absolutely
+ // correct.
+ FinishNoNotify();
+
+ RefPtr<nsOfflineCacheUpdate> newUpdate =
+ new nsOfflineCacheUpdate();
+ // Leave aDocument argument null. Only glues and children keep
+ // document instances.
+ newUpdate->Init(mManifestURI, mDocumentURI, mLoadingPrincipal, nullptr,
+ mCustomProfileDir);
+
+ // In a rare case the manifest will not be modified on the next refetch
+ // transfer all master document URIs to the new update to ensure that
+ // all documents refering it will be properly cached.
+ for (int32_t i = 0; i < mDocumentURIs.Count(); i++) {
+ newUpdate->StickDocument(mDocumentURIs[i]);
+ }
+
+ newUpdate->mRescheduleCount = mRescheduleCount + 1;
+ newUpdate->AddObserver(this, false);
+ newUpdate->Schedule();
+ }
+ else {
+ LogToConsole("Offline cache update done", mManifestItem);
+ Finish();
+ }
+}
+
+nsresult
+nsOfflineCacheUpdate::Begin()
+{
+ LOG(("nsOfflineCacheUpdate::Begin [%p]", this));
+
+ // Keep the object alive through a ProcessNextURI()/Finish() call.
+ nsCOMPtr<nsIOfflineCacheUpdate> kungFuDeathGrip(this);
+
+ mItemsInProgress = 0;
+
+ if (mState == STATE_CANCELLED) {
+ nsresult rv = NS_DispatchToMainThread(NewRunnableMethod(this,
+ &nsOfflineCacheUpdate::AsyncFinishWithError));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+ }
+
+ if (mPartialUpdate) {
+ mState = STATE_DOWNLOADING;
+ NotifyState(nsIOfflineCacheUpdateObserver::STATE_DOWNLOADING);
+ ProcessNextURI();
+ return NS_OK;
+ }
+
+ // Start checking the manifest.
+ mManifestItem = new nsOfflineManifestItem(mManifestURI,
+ mDocumentURI,
+ mLoadingPrincipal,
+ mApplicationCache,
+ mPreviousApplicationCache);
+ if (!mManifestItem) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ mState = STATE_CHECKING;
+ mByteProgress = 0;
+ NotifyState(nsIOfflineCacheUpdateObserver::STATE_CHECKING);
+
+ nsresult rv = mManifestItem->OpenChannel(this);
+ if (NS_FAILED(rv)) {
+ LoadCompleted(mManifestItem);
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsOfflineCacheUpdate <private>
+//-----------------------------------------------------------------------------
+
+nsresult
+nsOfflineCacheUpdate::AddExistingItems(uint32_t aType,
+ nsTArray<nsCString>* namespaceFilter)
+{
+ if (!mPreviousApplicationCache) {
+ return NS_OK;
+ }
+
+ if (namespaceFilter && namespaceFilter->Length() == 0) {
+ // Don't bother to walk entries when there are no namespaces
+ // defined.
+ return NS_OK;
+ }
+
+ uint32_t count = 0;
+ char **keys = nullptr;
+ nsresult rv = mPreviousApplicationCache->GatherEntries(aType,
+ &count, &keys);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ AutoFreeArray autoFree(count, keys);
+
+ for (uint32_t i = 0; i < count; i++) {
+ if (namespaceFilter) {
+ bool found = false;
+ for (uint32_t j = 0; j < namespaceFilter->Length() && !found; j++) {
+ found = StringBeginsWith(nsDependentCString(keys[i]),
+ namespaceFilter->ElementAt(j));
+ }
+
+ if (!found)
+ continue;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ if (NS_SUCCEEDED(NS_NewURI(getter_AddRefs(uri), keys[i]))) {
+ rv = AddURI(uri, aType);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsOfflineCacheUpdate::ProcessNextURI()
+{
+ // Keep the object alive through a Finish() call.
+ nsCOMPtr<nsIOfflineCacheUpdate> kungFuDeathGrip(this);
+
+ LOG(("nsOfflineCacheUpdate::ProcessNextURI [%p, inprogress=%d, numItems=%d]",
+ this, mItemsInProgress, mItems.Length()));
+
+ if (mState != STATE_DOWNLOADING) {
+ LOG((" should only be called from the DOWNLOADING state, ignoring"));
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsOfflineCacheUpdateItem * runItem = nullptr;
+ uint32_t completedItems = 0;
+ for (uint32_t i = 0; i < mItems.Length(); ++i) {
+ nsOfflineCacheUpdateItem * item = mItems[i];
+
+ if (item->IsScheduled()) {
+ runItem = item;
+ break;
+ }
+
+ if (item->IsCompleted())
+ ++completedItems;
+ }
+
+ if (completedItems == mItems.Length()) {
+ LOG(("nsOfflineCacheUpdate::ProcessNextURI [%p]: all items loaded", this));
+
+ if (mPartialUpdate) {
+ return Finish();
+ } else {
+ // Verify that the manifest wasn't changed during the
+ // update, to prevent capturing a cache while the server
+ // is being updated. The check will call
+ // ManifestCheckCompleted() when it's done.
+ RefPtr<nsManifestCheck> manifestCheck =
+ new nsManifestCheck(this, mManifestURI, mDocumentURI, mLoadingPrincipal);
+ if (NS_FAILED(manifestCheck->Begin())) {
+ mSucceeded = false;
+ NotifyState(nsIOfflineCacheUpdateObserver::STATE_ERROR);
+ return Finish();
+ }
+
+ return NS_OK;
+ }
+ }
+
+ if (!runItem) {
+ LOG(("nsOfflineCacheUpdate::ProcessNextURI [%p]:"
+ " No more items to include in parallel load", this));
+ return NS_OK;
+ }
+
+ if (LOG_ENABLED()) {
+ LOG(("%p: Opening channel for %s", this,
+ runItem->mURI->GetSpecOrDefault().get()));
+ }
+
+ ++mItemsInProgress;
+ NotifyState(nsIOfflineCacheUpdateObserver::STATE_ITEMSTARTED);
+
+ nsresult rv = runItem->OpenChannel(this);
+ if (NS_FAILED(rv)) {
+ LoadCompleted(runItem);
+ return rv;
+ }
+
+ if (mItemsInProgress >= kParallelLoadLimit) {
+ LOG(("nsOfflineCacheUpdate::ProcessNextURI [%p]:"
+ " At parallel load limit", this));
+ return NS_OK;
+ }
+
+ // This calls this method again via a post triggering
+ // a parallel item load
+ return NS_DispatchToCurrentThread(this);
+}
+
+void
+nsOfflineCacheUpdate::GatherObservers(nsCOMArray<nsIOfflineCacheUpdateObserver> &aObservers)
+{
+ for (int32_t i = 0; i < mWeakObservers.Count(); i++) {
+ nsCOMPtr<nsIOfflineCacheUpdateObserver> observer =
+ do_QueryReferent(mWeakObservers[i]);
+ if (observer)
+ aObservers.AppendObject(observer);
+ else
+ mWeakObservers.RemoveObjectAt(i--);
+ }
+
+ for (int32_t i = 0; i < mObservers.Count(); i++) {
+ aObservers.AppendObject(mObservers[i]);
+ }
+}
+
+void
+nsOfflineCacheUpdate::NotifyState(uint32_t state)
+{
+ LOG(("nsOfflineCacheUpdate::NotifyState [%p, %d]", this, state));
+
+ if (state == STATE_ERROR) {
+ LogToConsole("Offline cache update error", mManifestItem);
+ }
+
+ nsCOMArray<nsIOfflineCacheUpdateObserver> observers;
+ GatherObservers(observers);
+
+ for (int32_t i = 0; i < observers.Count(); i++) {
+ observers[i]->UpdateStateChanged(this, state);
+ }
+}
+
+void
+nsOfflineCacheUpdate::NotifyUpdateAvailability(bool updateAvailable)
+{
+ if (!mUpdateAvailableObserver)
+ return;
+
+ LOG(("nsOfflineCacheUpdate::NotifyUpdateAvailability [this=%p, avail=%d]",
+ this, updateAvailable));
+
+ const char* topic = updateAvailable
+ ? "offline-cache-update-available"
+ : "offline-cache-update-unavailable";
+
+ nsCOMPtr<nsIObserver> observer;
+ observer.swap(mUpdateAvailableObserver);
+ observer->Observe(mManifestURI, topic, nullptr);
+}
+
+void
+nsOfflineCacheUpdate::AssociateDocuments(nsIApplicationCache* cache)
+{
+ if (!cache) {
+ LOG(("nsOfflineCacheUpdate::AssociateDocuments bypassed"
+ ", no cache provided [this=%p]", this));
+ return;
+ }
+
+ nsCOMArray<nsIOfflineCacheUpdateObserver> observers;
+ GatherObservers(observers);
+
+ for (int32_t i = 0; i < observers.Count(); i++) {
+ observers[i]->ApplicationCacheAvailable(cache);
+ }
+}
+
+void
+nsOfflineCacheUpdate::StickDocument(nsIURI *aDocumentURI)
+{
+ if (!aDocumentURI)
+ return;
+
+ mDocumentURIs.AppendObject(aDocumentURI);
+}
+
+void
+nsOfflineCacheUpdate::SetOwner(nsOfflineCacheUpdateOwner *aOwner)
+{
+ NS_ASSERTION(!mOwner, "Tried to set cache update owner twice.");
+ mOwner = aOwner;
+}
+
+bool
+nsOfflineCacheUpdate::IsForGroupID(const nsCSubstring &groupID)
+{
+ return mGroupID == groupID;
+}
+
+bool
+nsOfflineCacheUpdate::IsForProfile(nsIFile* aCustomProfileDir)
+{
+ if (!mCustomProfileDir && !aCustomProfileDir)
+ return true;
+ if (!mCustomProfileDir || !aCustomProfileDir)
+ return false;
+
+ bool equals;
+ nsresult rv = mCustomProfileDir->Equals(aCustomProfileDir, &equals);
+
+ return NS_SUCCEEDED(rv) && equals;
+}
+
+nsresult
+nsOfflineCacheUpdate::UpdateFinished(nsOfflineCacheUpdate *aUpdate)
+{
+ // Keep the object alive through a Finish() call.
+ nsCOMPtr<nsIOfflineCacheUpdate> kungFuDeathGrip(this);
+
+ mImplicitUpdate = nullptr;
+
+ NotifyState(nsIOfflineCacheUpdateObserver::STATE_NOUPDATE);
+ Finish();
+
+ return NS_OK;
+}
+
+void
+nsOfflineCacheUpdate::OnByteProgress(uint64_t byteIncrement)
+{
+ mByteProgress += byteIncrement;
+ NotifyState(nsIOfflineCacheUpdateObserver::STATE_ITEMPROGRESS);
+}
+
+nsresult
+nsOfflineCacheUpdate::ScheduleImplicit()
+{
+ if (mDocumentURIs.Count() == 0)
+ return NS_OK;
+
+ nsresult rv;
+
+ RefPtr<nsOfflineCacheUpdate> update = new nsOfflineCacheUpdate();
+ NS_ENSURE_TRUE(update, NS_ERROR_OUT_OF_MEMORY);
+
+ nsAutoCString clientID;
+ if (mPreviousApplicationCache) {
+ rv = mPreviousApplicationCache->GetClientID(clientID);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else if (mApplicationCache) {
+ rv = mApplicationCache->GetClientID(clientID);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else {
+ NS_ERROR("Offline cache update not having set mApplicationCache?");
+ }
+
+ rv = update->InitPartial(mManifestURI, clientID, mDocumentURI, mLoadingPrincipal);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (int32_t i = 0; i < mDocumentURIs.Count(); i++) {
+ rv = update->AddURI(mDocumentURIs[i],
+ nsIApplicationCache::ITEM_IMPLICIT);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ update->SetOwner(this);
+ rv = update->Begin();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mImplicitUpdate = update;
+
+ return NS_OK;
+}
+
+nsresult
+nsOfflineCacheUpdate::FinishNoNotify()
+{
+ LOG(("nsOfflineCacheUpdate::Finish [%p]", this));
+
+ mState = STATE_FINISHED;
+
+ if (!mPartialUpdate && !mOnlyCheckUpdate) {
+ if (mSucceeded) {
+ nsIArray *namespaces = mManifestItem->GetNamespaces();
+ nsresult rv = mApplicationCache->AddNamespaces(namespaces);
+ if (NS_FAILED(rv)) {
+ NotifyState(nsIOfflineCacheUpdateObserver::STATE_ERROR);
+ mSucceeded = false;
+ }
+
+ rv = mApplicationCache->Activate();
+ if (NS_FAILED(rv)) {
+ NotifyState(nsIOfflineCacheUpdateObserver::STATE_ERROR);
+ mSucceeded = false;
+ }
+
+ AssociateDocuments(mApplicationCache);
+ }
+
+ if (mObsolete) {
+ nsCOMPtr<nsIApplicationCacheService> appCacheService =
+ do_GetService(NS_APPLICATIONCACHESERVICE_CONTRACTID);
+ if (appCacheService) {
+ nsAutoCString groupID;
+ mApplicationCache->GetGroupID(groupID);
+ appCacheService->DeactivateGroup(groupID);
+ }
+ }
+
+ if (!mSucceeded) {
+ // Update was not merged, mark all the loads as failures
+ for (uint32_t i = 0; i < mItems.Length(); i++) {
+ mItems[i]->Cancel();
+ }
+
+ mApplicationCache->Discard();
+ }
+ }
+
+ nsresult rv = NS_OK;
+
+ if (mOwner) {
+ rv = mOwner->UpdateFinished(this);
+ // mozilla::WeakPtr is missing some key features, like setting it to
+ // null explicitly.
+ mOwner = mozilla::WeakPtr<nsOfflineCacheUpdateOwner>();
+ }
+
+ return rv;
+}
+
+nsresult
+nsOfflineCacheUpdate::Finish()
+{
+ nsresult rv = FinishNoNotify();
+
+ NotifyState(nsIOfflineCacheUpdateObserver::STATE_FINISHED);
+
+ return rv;
+}
+
+void
+nsOfflineCacheUpdate::AsyncFinishWithError()
+{
+ NotifyState(nsOfflineCacheUpdate::STATE_ERROR);
+ Finish();
+}
+
+static nsresult
+EvictOneOfCacheGroups(nsIApplicationCacheService *cacheService,
+ uint32_t count, const char * const *groups)
+{
+ nsresult rv;
+ unsigned int i;
+
+ for (i = 0; i < count; i++) {
+ nsCOMPtr<nsIURI> uri;
+ rv = NS_NewURI(getter_AddRefs(uri), groups[i]);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsDependentCString group_name(groups[i]);
+ nsCOMPtr<nsIApplicationCache> cache;
+ rv = cacheService->GetActiveCache(group_name, getter_AddRefs(cache));
+ // Maybe someone in another thread or process have deleted it.
+ if (NS_FAILED(rv) || !cache)
+ continue;
+
+ bool pinned;
+ rv = nsOfflineCacheUpdateService::OfflineAppPinnedForURI(uri,
+ nullptr,
+ &pinned);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!pinned) {
+ rv = cache->Discard();
+ return NS_OK;
+ }
+ }
+
+ return NS_ERROR_FILE_NOT_FOUND;
+}
+
+nsresult
+nsOfflineCacheUpdate::EvictOneNonPinned()
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIApplicationCacheService> cacheService =
+ do_GetService(NS_APPLICATIONCACHESERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t count;
+ char **groups;
+ rv = cacheService->GetGroupsTimeOrdered(&count, &groups);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = EvictOneOfCacheGroups(cacheService, count, groups);
+
+ NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(count, groups);
+ return rv;
+}
+
+//-----------------------------------------------------------------------------
+// nsOfflineCacheUpdate::nsIOfflineCacheUpdate
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsOfflineCacheUpdate::GetUpdateDomain(nsACString &aUpdateDomain)
+{
+ NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED);
+
+ aUpdateDomain = mUpdateDomain;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheUpdate::GetStatus(uint16_t *aStatus)
+{
+ switch (mState) {
+ case STATE_CHECKING :
+ *aStatus = nsIDOMOfflineResourceList::CHECKING;
+ return NS_OK;
+ case STATE_DOWNLOADING :
+ *aStatus = nsIDOMOfflineResourceList::DOWNLOADING;
+ return NS_OK;
+ default :
+ *aStatus = nsIDOMOfflineResourceList::IDLE;
+ return NS_OK;
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheUpdate::GetPartial(bool *aPartial)
+{
+ *aPartial = mPartialUpdate || mOnlyCheckUpdate;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheUpdate::GetManifestURI(nsIURI **aManifestURI)
+{
+ NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED);
+
+ NS_IF_ADDREF(*aManifestURI = mManifestURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheUpdate::GetSucceeded(bool *aSucceeded)
+{
+ NS_ENSURE_TRUE(mState == STATE_FINISHED, NS_ERROR_NOT_AVAILABLE);
+
+ *aSucceeded = mSucceeded;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheUpdate::GetIsUpgrade(bool *aIsUpgrade)
+{
+ NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED);
+
+ *aIsUpgrade = (mPreviousApplicationCache != nullptr);
+
+ return NS_OK;
+}
+
+nsresult
+nsOfflineCacheUpdate::AddURI(nsIURI *aURI, uint32_t aType, uint32_t aLoadFlags)
+{
+ NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED);
+
+ if (mState >= STATE_DOWNLOADING)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ // Resource URIs must have the same scheme as the manifest.
+ nsAutoCString scheme;
+ aURI->GetScheme(scheme);
+
+ bool match;
+ if (NS_FAILED(mManifestURI->SchemeIs(scheme.get(), &match)) || !match)
+ return NS_ERROR_FAILURE;
+
+ // Don't fetch the same URI twice.
+ for (uint32_t i = 0; i < mItems.Length(); i++) {
+ bool equals;
+ if (NS_SUCCEEDED(mItems[i]->mURI->Equals(aURI, &equals)) && equals &&
+ mItems[i]->mLoadFlags == aLoadFlags) {
+ // retain both types.
+ mItems[i]->mItemType |= aType;
+ return NS_OK;
+ }
+ }
+
+ RefPtr<nsOfflineCacheUpdateItem> item =
+ new nsOfflineCacheUpdateItem(aURI,
+ mDocumentURI,
+ mLoadingPrincipal,
+ mApplicationCache,
+ mPreviousApplicationCache,
+ aType,
+ aLoadFlags);
+ if (!item) return NS_ERROR_OUT_OF_MEMORY;
+
+ mItems.AppendElement(item);
+ mAddedItems = true;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheUpdate::AddDynamicURI(nsIURI *aURI)
+{
+ if (GeckoProcessType_Default != XRE_GetProcessType())
+ return NS_ERROR_NOT_IMPLEMENTED;
+
+ // If this is a partial update and the resource is already in the
+ // cache, we should only mark the entry, not fetch it again.
+ if (mPartialUpdate) {
+ nsAutoCString key;
+ GetCacheKey(aURI, key);
+
+ uint32_t types;
+ nsresult rv = mApplicationCache->GetTypes(key, &types);
+ if (NS_SUCCEEDED(rv)) {
+ if (!(types & nsIApplicationCache::ITEM_DYNAMIC)) {
+ mApplicationCache->MarkEntry
+ (key, nsIApplicationCache::ITEM_DYNAMIC);
+ }
+ return NS_OK;
+ }
+ }
+
+ return AddURI(aURI, nsIApplicationCache::ITEM_DYNAMIC);
+}
+
+NS_IMETHODIMP
+nsOfflineCacheUpdate::Cancel()
+{
+ LOG(("nsOfflineCacheUpdate::Cancel [%p]", this));
+
+ if ((mState == STATE_FINISHED) || (mState == STATE_CANCELLED)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ mState = STATE_CANCELLED;
+ mSucceeded = false;
+
+ // Cancel all running downloads
+ for (uint32_t i = 0; i < mItems.Length(); ++i) {
+ nsOfflineCacheUpdateItem * item = mItems[i];
+
+ if (item->IsInProgress())
+ item->Cancel();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheUpdate::AddObserver(nsIOfflineCacheUpdateObserver *aObserver,
+ bool aHoldWeak)
+{
+ LOG(("nsOfflineCacheUpdate::AddObserver [%p] to update [%p]", aObserver, this));
+
+ NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED);
+
+ if (aHoldWeak) {
+ nsCOMPtr<nsIWeakReference> weakRef = do_GetWeakReference(aObserver);
+ mWeakObservers.AppendObject(weakRef);
+ } else {
+ mObservers.AppendObject(aObserver);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheUpdate::RemoveObserver(nsIOfflineCacheUpdateObserver *aObserver)
+{
+ LOG(("nsOfflineCacheUpdate::RemoveObserver [%p] from update [%p]", aObserver, this));
+
+ NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED);
+
+ for (int32_t i = 0; i < mWeakObservers.Count(); i++) {
+ nsCOMPtr<nsIOfflineCacheUpdateObserver> observer =
+ do_QueryReferent(mWeakObservers[i]);
+ if (observer == aObserver) {
+ mWeakObservers.RemoveObjectAt(i);
+ return NS_OK;
+ }
+ }
+
+ for (int32_t i = 0; i < mObservers.Count(); i++) {
+ if (mObservers[i] == aObserver) {
+ mObservers.RemoveObjectAt(i);
+ return NS_OK;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheUpdate::GetByteProgress(uint64_t * _result)
+{
+ NS_ENSURE_ARG(_result);
+
+ *_result = mByteProgress;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheUpdate::Schedule()
+{
+ LOG(("nsOfflineCacheUpdate::Schedule [%p]", this));
+
+ nsOfflineCacheUpdateService* service =
+ nsOfflineCacheUpdateService::EnsureService();
+
+ if (!service) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return service->ScheduleUpdate(this);
+}
+
+NS_IMETHODIMP
+nsOfflineCacheUpdate::UpdateStateChanged(nsIOfflineCacheUpdate *aUpdate,
+ uint32_t aState)
+{
+ if (aState == nsIOfflineCacheUpdateObserver::STATE_FINISHED) {
+ // Take the mSucceeded flag from the underlying update, we will be
+ // queried for it soon. mSucceeded of this update is false (manifest
+ // check failed) but the subsequent re-fetch update might succeed
+ bool succeeded;
+ aUpdate->GetSucceeded(&succeeded);
+ mSucceeded = succeeded;
+ }
+
+ NotifyState(aState);
+ if (aState == nsIOfflineCacheUpdateObserver::STATE_FINISHED)
+ aUpdate->RemoveObserver(this);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheUpdate::ApplicationCacheAvailable(nsIApplicationCache *applicationCache)
+{
+ AssociateDocuments(applicationCache);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsOfflineCacheUpdate::nsIRunable
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsOfflineCacheUpdate::Run()
+{
+ ProcessNextURI();
+ return NS_OK;
+}
diff --git a/uriloader/prefetch/nsOfflineCacheUpdate.h b/uriloader/prefetch/nsOfflineCacheUpdate.h
new file mode 100644
index 0000000000..4ccba41359
--- /dev/null
+++ b/uriloader/prefetch/nsOfflineCacheUpdate.h
@@ -0,0 +1,381 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsOfflineCacheUpdate_h__
+#define nsOfflineCacheUpdate_h__
+
+#include "nsIOfflineCacheUpdate.h"
+
+#include "nsAutoPtr.h"
+#include "nsCOMArray.h"
+#include "nsCOMPtr.h"
+#include "nsIChannelEventSink.h"
+#include "nsIDOMDocument.h"
+#include "nsIDOMNode.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIMutableArray.h"
+#include "nsIObserver.h"
+#include "nsIObserverService.h"
+#include "nsIApplicationCache.h"
+#include "nsIRequestObserver.h"
+#include "nsIRunnable.h"
+#include "nsIStreamListener.h"
+#include "nsIURI.h"
+#include "nsIWebProgressListener.h"
+#include "nsClassHashtable.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsWeakReference.h"
+#include "nsICryptoHash.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/WeakPtr.h"
+#include "nsTHashtable.h"
+#include "nsHashKeys.h"
+
+class nsOfflineCacheUpdate;
+
+class nsOfflineCacheUpdateItem : public nsIStreamListener
+ , public nsIRunnable
+ , public nsIInterfaceRequestor
+ , public nsIChannelEventSink
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIRUNNABLE
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSICHANNELEVENTSINK
+
+ nsOfflineCacheUpdateItem(nsIURI *aURI,
+ nsIURI *aReferrerURI,
+ nsIPrincipal* aLoadingPrincipal,
+ nsIApplicationCache *aApplicationCache,
+ nsIApplicationCache *aPreviousApplicationCache,
+ uint32_t aType,
+ uint32_t aLoadFlags);
+
+ nsCOMPtr<nsIURI> mURI;
+ nsCOMPtr<nsIURI> mReferrerURI;
+ nsCOMPtr<nsIPrincipal> mLoadingPrincipal;
+ nsCOMPtr<nsIApplicationCache> mApplicationCache;
+ nsCOMPtr<nsIApplicationCache> mPreviousApplicationCache;
+ nsCString mCacheKey;
+ uint32_t mItemType;
+ uint32_t mLoadFlags;
+
+ nsresult OpenChannel(nsOfflineCacheUpdate *aUpdate);
+ nsresult Cancel();
+ nsresult GetRequestSucceeded(bool * succeeded);
+
+ bool IsInProgress();
+ bool IsScheduled();
+ bool IsCompleted();
+
+ nsresult GetStatus(uint16_t *aStatus);
+
+private:
+ enum LoadStatus : uint16_t {
+ UNINITIALIZED = 0U,
+ REQUESTED = 1U,
+ RECEIVING = 2U,
+ LOADED = 3U
+ };
+
+ RefPtr<nsOfflineCacheUpdate> mUpdate;
+ nsCOMPtr<nsIChannel> mChannel;
+ uint16_t mState;
+
+protected:
+ virtual ~nsOfflineCacheUpdateItem();
+
+ int64_t mBytesRead;
+};
+
+
+class nsOfflineManifestItem : public nsOfflineCacheUpdateItem
+{
+public:
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIREQUESTOBSERVER
+
+ nsOfflineManifestItem(nsIURI *aURI,
+ nsIURI *aReferrerURI,
+ nsIPrincipal* aLoadingPrincipal,
+ nsIApplicationCache *aApplicationCache,
+ nsIApplicationCache *aPreviousApplicationCache);
+ virtual ~nsOfflineManifestItem();
+
+ nsCOMArray<nsIURI> &GetExplicitURIs() { return mExplicitURIs; }
+ nsCOMArray<nsIURI> &GetAnonymousURIs() { return mAnonymousURIs; }
+ nsCOMArray<nsIURI> &GetFallbackURIs() { return mFallbackURIs; }
+
+ nsTArray<nsCString> &GetOpportunisticNamespaces()
+ { return mOpportunisticNamespaces; }
+ nsIArray *GetNamespaces()
+ { return mNamespaces.get(); }
+
+ bool ParseSucceeded()
+ { return (mParserState != PARSE_INIT && mParserState != PARSE_ERROR); }
+ bool NeedsUpdate() { return mParserState != PARSE_INIT && mNeedsUpdate; }
+
+ void GetManifestHash(nsCString &aManifestHash)
+ { aManifestHash = mManifestHashValue; }
+
+private:
+ static nsresult ReadManifest(nsIInputStream *aInputStream,
+ void *aClosure,
+ const char *aFromSegment,
+ uint32_t aOffset,
+ uint32_t aCount,
+ uint32_t *aBytesConsumed);
+
+ nsresult AddNamespace(uint32_t namespaceType,
+ const nsCString &namespaceSpec,
+ const nsCString &data);
+
+ nsresult HandleManifestLine(const nsCString::const_iterator &aBegin,
+ const nsCString::const_iterator &aEnd);
+
+ /**
+ * Saves "offline-manifest-hash" meta data from the old offline cache
+ * token to mOldManifestHashValue member to be compared on
+ * successfull load.
+ */
+ nsresult GetOldManifestContentHash(nsIRequest *aRequest);
+ /**
+ * This method setups the mNeedsUpdate to false when hash value
+ * of the just downloaded manifest file is the same as stored in cache's
+ * "offline-manifest-hash" meta data. Otherwise stores the new value
+ * to this meta data.
+ */
+ nsresult CheckNewManifestContentHash(nsIRequest *aRequest);
+
+ void ReadStrictFileOriginPolicyPref();
+
+ enum {
+ PARSE_INIT,
+ PARSE_CACHE_ENTRIES,
+ PARSE_FALLBACK_ENTRIES,
+ PARSE_BYPASS_ENTRIES,
+ PARSE_UNKNOWN_SECTION,
+ PARSE_ERROR
+ } mParserState;
+
+ nsCString mReadBuf;
+
+ nsCOMArray<nsIURI> mExplicitURIs;
+ nsCOMArray<nsIURI> mAnonymousURIs;
+ nsCOMArray<nsIURI> mFallbackURIs;
+
+ // All opportunistic caching namespaces. Used to decide whether
+ // to include previously-opportunistically-cached entries.
+ nsTArray<nsCString> mOpportunisticNamespaces;
+
+ // Array of nsIApplicationCacheNamespace objects specified by the
+ // manifest.
+ nsCOMPtr<nsIMutableArray> mNamespaces;
+
+ bool mNeedsUpdate;
+ bool mStrictFileOriginPolicy;
+
+ // manifest hash data
+ nsCOMPtr<nsICryptoHash> mManifestHash;
+ bool mManifestHashInitialized;
+ nsCString mManifestHashValue;
+ nsCString mOldManifestHashValue;
+};
+
+class nsOfflineCacheUpdateOwner
+ : public mozilla::SupportsWeakPtr<nsOfflineCacheUpdateOwner>
+{
+public:
+ MOZ_DECLARE_WEAKREFERENCE_TYPENAME(nsOfflineCacheUpdateOwner)
+ virtual ~nsOfflineCacheUpdateOwner() {}
+ virtual nsresult UpdateFinished(nsOfflineCacheUpdate *aUpdate) = 0;
+};
+
+class nsOfflineCacheUpdate final : public nsIOfflineCacheUpdate
+ , public nsIOfflineCacheUpdateObserver
+ , public nsIRunnable
+ , public nsOfflineCacheUpdateOwner
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOFFLINECACHEUPDATE
+ NS_DECL_NSIOFFLINECACHEUPDATEOBSERVER
+ NS_DECL_NSIRUNNABLE
+
+ nsOfflineCacheUpdate();
+
+ static nsresult GetCacheKey(nsIURI *aURI, nsACString &aKey);
+
+ nsresult Init();
+
+ nsresult Begin();
+
+ void LoadCompleted(nsOfflineCacheUpdateItem *aItem);
+ void ManifestCheckCompleted(nsresult aStatus,
+ const nsCString &aManifestHash);
+ void StickDocument(nsIURI *aDocumentURI);
+
+ void SetOwner(nsOfflineCacheUpdateOwner *aOwner);
+
+ bool IsForGroupID(const nsCSubstring &groupID);
+ bool IsForProfile(nsIFile* aCustomProfileDir);
+
+ virtual nsresult UpdateFinished(nsOfflineCacheUpdate *aUpdate) override;
+
+protected:
+ ~nsOfflineCacheUpdate();
+
+ friend class nsOfflineCacheUpdateItem;
+ void OnByteProgress(uint64_t byteIncrement);
+
+private:
+ nsresult InitInternal(nsIURI *aManifestURI, nsIPrincipal* aPrincipal);
+ nsresult HandleManifest(bool *aDoUpdate);
+ nsresult AddURI(nsIURI *aURI, uint32_t aItemType, uint32_t aLoadFlags = 0);
+
+ nsresult ProcessNextURI();
+
+ // Adds items from the previous cache witha type matching aType.
+ // If namespaceFilter is non-null, only items matching the
+ // specified namespaces will be added.
+ nsresult AddExistingItems(uint32_t aType,
+ nsTArray<nsCString>* namespaceFilter = nullptr);
+ nsresult ScheduleImplicit();
+ void AssociateDocuments(nsIApplicationCache* cache);
+ bool CheckUpdateAvailability();
+ void NotifyUpdateAvailability(bool updateAvailable);
+
+ void GatherObservers(nsCOMArray<nsIOfflineCacheUpdateObserver> &aObservers);
+ void NotifyState(uint32_t state);
+ nsresult Finish();
+ nsresult FinishNoNotify();
+
+ void AsyncFinishWithError();
+
+ // Find one non-pinned cache group and evict it.
+ nsresult EvictOneNonPinned();
+
+ enum {
+ STATE_UNINITIALIZED,
+ STATE_INITIALIZED,
+ STATE_CHECKING,
+ STATE_DOWNLOADING,
+ STATE_CANCELLED,
+ STATE_FINISHED
+ } mState;
+
+ mozilla::WeakPtr<nsOfflineCacheUpdateOwner> mOwner;
+
+ bool mAddedItems;
+ bool mPartialUpdate;
+ bool mOnlyCheckUpdate;
+ bool mSucceeded;
+ bool mObsolete;
+
+ nsCString mUpdateDomain;
+ nsCString mGroupID;
+ nsCOMPtr<nsIURI> mManifestURI;
+ nsCOMPtr<nsIURI> mDocumentURI;
+ nsCOMPtr<nsIPrincipal> mLoadingPrincipal;
+ nsCOMPtr<nsIFile> mCustomProfileDir;
+
+ nsCOMPtr<nsIObserver> mUpdateAvailableObserver;
+
+ nsCOMPtr<nsIApplicationCache> mApplicationCache;
+ nsCOMPtr<nsIApplicationCache> mPreviousApplicationCache;
+
+ nsCOMPtr<nsIObserverService> mObserverService;
+
+ RefPtr<nsOfflineManifestItem> mManifestItem;
+
+ /* Items being updated */
+ uint32_t mItemsInProgress;
+ nsTArray<RefPtr<nsOfflineCacheUpdateItem> > mItems;
+
+ /* Clients watching this update for changes */
+ nsCOMArray<nsIWeakReference> mWeakObservers;
+ nsCOMArray<nsIOfflineCacheUpdateObserver> mObservers;
+
+ /* Documents that requested this update */
+ nsCOMArray<nsIURI> mDocumentURIs;
+
+ /* Reschedule count. When an update is rescheduled due to
+ * mismatched manifests, the reschedule count will be increased. */
+ uint32_t mRescheduleCount;
+
+ /* Whena an entry for a pinned app is retried, retries count is
+ * increaded. */
+ uint32_t mPinnedEntryRetriesCount;
+
+ RefPtr<nsOfflineCacheUpdate> mImplicitUpdate;
+
+ bool mPinned;
+
+ uint64_t mByteProgress;
+};
+
+class nsOfflineCacheUpdateService final : public nsIOfflineCacheUpdateService
+ , public nsIObserver
+ , public nsOfflineCacheUpdateOwner
+ , public nsSupportsWeakReference
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOFFLINECACHEUPDATESERVICE
+ NS_DECL_NSIOBSERVER
+
+ nsOfflineCacheUpdateService();
+
+ nsresult Init();
+
+ nsresult ScheduleUpdate(nsOfflineCacheUpdate *aUpdate);
+ nsresult FindUpdate(nsIURI *aManifestURI,
+ nsACString const &aOriginSuffix,
+ nsIFile *aCustomProfileDir,
+ nsOfflineCacheUpdate **aUpdate);
+
+ nsresult Schedule(nsIURI *aManifestURI,
+ nsIURI *aDocumentURI,
+ nsIPrincipal* aLoadingPrincipal,
+ nsIDOMDocument *aDocument,
+ nsPIDOMWindowInner* aWindow,
+ nsIFile* aCustomProfileDir,
+ nsIOfflineCacheUpdate **aUpdate);
+
+ virtual nsresult UpdateFinished(nsOfflineCacheUpdate *aUpdate) override;
+
+ /**
+ * Returns the singleton nsOfflineCacheUpdateService without an addref, or
+ * nullptr if the service couldn't be created.
+ */
+ static nsOfflineCacheUpdateService *EnsureService();
+
+ /** Addrefs and returns the singleton nsOfflineCacheUpdateService. */
+ static nsOfflineCacheUpdateService *GetInstance();
+
+ static nsresult OfflineAppPinnedForURI(nsIURI *aDocumentURI,
+ nsIPrefBranch *aPrefBranch,
+ bool *aPinned);
+
+ static nsTHashtable<nsCStringHashKey>* AllowedDomains();
+
+private:
+ ~nsOfflineCacheUpdateService();
+
+ nsresult ProcessNextUpdate();
+
+ nsTArray<RefPtr<nsOfflineCacheUpdate> > mUpdates;
+ static nsTHashtable<nsCStringHashKey>* mAllowedDomains;
+
+ bool mDisabled;
+ bool mUpdateRunning;
+ bool mLowFreeSpace;
+};
+
+#endif
diff --git a/uriloader/prefetch/nsOfflineCacheUpdateService.cpp b/uriloader/prefetch/nsOfflineCacheUpdateService.cpp
new file mode 100644
index 0000000000..adb3fd5160
--- /dev/null
+++ b/uriloader/prefetch/nsOfflineCacheUpdateService.cpp
@@ -0,0 +1,736 @@
+/* -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "OfflineCacheUpdateChild.h"
+#include "OfflineCacheUpdateParent.h"
+#include "nsXULAppAPI.h"
+#include "OfflineCacheUpdateGlue.h"
+#include "nsOfflineCacheUpdate.h"
+
+#include "nsCPrefetchService.h"
+#include "nsCURILoader.h"
+#include "nsIApplicationCacheContainer.h"
+#include "nsIApplicationCacheChannel.h"
+#include "nsIApplicationCacheService.h"
+#include "nsICachingChannel.h"
+#include "nsIContent.h"
+#include "nsIDocShell.h"
+#include "nsIDocumentLoader.h"
+#include "nsIDOMElement.h"
+#include "nsIDOMWindow.h"
+#include "nsIDOMOfflineResourceList.h"
+#include "nsIDocument.h"
+#include "nsIObserverService.h"
+#include "nsIURL.h"
+#include "nsIWebProgress.h"
+#include "nsIWebNavigation.h"
+#include "nsICryptoHash.h"
+#include "nsIPermissionManager.h"
+#include "nsIPrincipal.h"
+#include "nsNetCID.h"
+#include "nsServiceManagerUtils.h"
+#include "nsStreamUtils.h"
+#include "nsThreadUtils.h"
+#include "nsProxyRelease.h"
+#include "mozilla/Logging.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Unused.h"
+#include "nsIDiskSpaceWatcher.h"
+#include "nsIDocShell.h"
+#include "nsIDocShellTreeItem.h"
+#include "nsIDocShellTreeOwner.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/PermissionMessageUtils.h"
+#include "nsContentUtils.h"
+#include "mozilla/Unused.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+static nsOfflineCacheUpdateService *gOfflineCacheUpdateService = nullptr;
+static bool sAllowOfflineCache = true;
+
+nsTHashtable<nsCStringHashKey>* nsOfflineCacheUpdateService::mAllowedDomains = nullptr;
+
+nsTHashtable<nsCStringHashKey>* nsOfflineCacheUpdateService::AllowedDomains()
+{
+ if (!mAllowedDomains)
+ mAllowedDomains = new nsTHashtable<nsCStringHashKey>();
+
+ return mAllowedDomains;
+}
+
+
+typedef mozilla::docshell::OfflineCacheUpdateParent OfflineCacheUpdateParent;
+typedef mozilla::docshell::OfflineCacheUpdateChild OfflineCacheUpdateChild;
+typedef mozilla::docshell::OfflineCacheUpdateGlue OfflineCacheUpdateGlue;
+
+//
+// To enable logging (see mozilla/Logging.h for full details):
+//
+// set MOZ_LOG=nsOfflineCacheUpdate:5
+// set MOZ_LOG_FILE=offlineupdate.log
+//
+// this enables LogLevel::Debug level information and places all output in
+// the file offlineupdate.log
+//
+LazyLogModule gOfflineCacheUpdateLog("nsOfflineCacheUpdate");
+
+#undef LOG
+#define LOG(args) MOZ_LOG(gOfflineCacheUpdateLog, mozilla::LogLevel::Debug, args)
+
+#undef LOG_ENABLED
+#define LOG_ENABLED() MOZ_LOG_TEST(gOfflineCacheUpdateLog, mozilla::LogLevel::Debug)
+
+//-----------------------------------------------------------------------------
+// nsOfflineCachePendingUpdate
+//-----------------------------------------------------------------------------
+
+class nsOfflineCachePendingUpdate final : public nsIWebProgressListener
+ , public nsSupportsWeakReference
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIWEBPROGRESSLISTENER
+
+ nsOfflineCachePendingUpdate(nsOfflineCacheUpdateService *aService,
+ nsIURI *aManifestURI,
+ nsIURI *aDocumentURI,
+ nsIPrincipal* aLoadingPrincipal,
+ nsIDOMDocument *aDocument)
+ : mService(aService)
+ , mManifestURI(aManifestURI)
+ , mDocumentURI(aDocumentURI)
+ , mLoadingPrincipal(aLoadingPrincipal)
+ , mDidReleaseThis(false)
+ {
+ mDocument = do_GetWeakReference(aDocument);
+ }
+
+private:
+ ~nsOfflineCachePendingUpdate() {}
+
+ RefPtr<nsOfflineCacheUpdateService> mService;
+ nsCOMPtr<nsIURI> mManifestURI;
+ nsCOMPtr<nsIURI> mDocumentURI;
+ nsCOMPtr<nsIPrincipal> mLoadingPrincipal;
+ nsCOMPtr<nsIWeakReference> mDocument;
+ bool mDidReleaseThis;
+};
+
+NS_IMPL_ISUPPORTS(nsOfflineCachePendingUpdate,
+ nsIWebProgressListener,
+ nsISupportsWeakReference)
+
+//-----------------------------------------------------------------------------
+// nsOfflineCacheUpdateService::nsIWebProgressListener
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsOfflineCachePendingUpdate::OnProgressChange(nsIWebProgress *aProgress,
+ nsIRequest *aRequest,
+ int32_t curSelfProgress,
+ int32_t maxSelfProgress,
+ int32_t curTotalProgress,
+ int32_t maxTotalProgress)
+{
+ NS_NOTREACHED("notification excluded in AddProgressListener(...)");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCachePendingUpdate::OnStateChange(nsIWebProgress* aWebProgress,
+ nsIRequest *aRequest,
+ uint32_t progressStateFlags,
+ nsresult aStatus)
+{
+ if (mDidReleaseThis) {
+ return NS_OK;
+ }
+ nsCOMPtr<nsIDOMDocument> updateDoc = do_QueryReferent(mDocument);
+ if (!updateDoc) {
+ // The document that scheduled this update has gone away,
+ // we don't need to listen anymore.
+ aWebProgress->RemoveProgressListener(this);
+ MOZ_ASSERT(!mDidReleaseThis);
+ mDidReleaseThis = true;
+ NS_RELEASE_THIS();
+ return NS_OK;
+ }
+
+ if (!(progressStateFlags & STATE_STOP)) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<mozIDOMWindowProxy> windowProxy;
+ aWebProgress->GetDOMWindow(getter_AddRefs(windowProxy));
+ if (!windowProxy) return NS_OK;
+
+ auto* outerWindow = nsPIDOMWindowOuter::From(windowProxy);
+ nsPIDOMWindowInner* innerWindow = outerWindow->GetCurrentInnerWindow();
+
+ nsCOMPtr<nsIDocument> progressDoc = outerWindow->GetDoc();
+ if (!progressDoc) return NS_OK;
+
+ if (!SameCOMIdentity(progressDoc, updateDoc)) {
+ return NS_OK;
+ }
+
+ LOG(("nsOfflineCachePendingUpdate::OnStateChange [%p, doc=%p]",
+ this, progressDoc.get()));
+
+ // Only schedule the update if the document loaded successfully
+ if (NS_SUCCEEDED(aStatus)) {
+ nsCOMPtr<nsIOfflineCacheUpdate> update;
+ mService->Schedule(mManifestURI, mDocumentURI, mLoadingPrincipal, updateDoc, innerWindow,
+ nullptr, getter_AddRefs(update));
+ if (mDidReleaseThis) {
+ return NS_OK;
+ }
+ }
+
+ aWebProgress->RemoveProgressListener(this);
+ MOZ_ASSERT(!mDidReleaseThis);
+ mDidReleaseThis = true;
+ NS_RELEASE_THIS();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCachePendingUpdate::OnLocationChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ nsIURI *location,
+ uint32_t aFlags)
+{
+ NS_NOTREACHED("notification excluded in AddProgressListener(...)");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCachePendingUpdate::OnStatusChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ nsresult aStatus,
+ const char16_t* aMessage)
+{
+ NS_NOTREACHED("notification excluded in AddProgressListener(...)");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCachePendingUpdate::OnSecurityChange(nsIWebProgress *aWebProgress,
+ nsIRequest *aRequest,
+ uint32_t state)
+{
+ NS_NOTREACHED("notification excluded in AddProgressListener(...)");
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsOfflineCacheUpdateService::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(nsOfflineCacheUpdateService,
+ nsIOfflineCacheUpdateService,
+ nsIObserver,
+ nsISupportsWeakReference)
+
+//-----------------------------------------------------------------------------
+// nsOfflineCacheUpdateService <public>
+//-----------------------------------------------------------------------------
+
+nsOfflineCacheUpdateService::nsOfflineCacheUpdateService()
+ : mDisabled(false)
+ , mUpdateRunning(false)
+ , mLowFreeSpace(false)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ Preferences::AddBoolVarCache(&sAllowOfflineCache,
+ "browser.cache.offline.enable",
+ true);
+}
+
+nsOfflineCacheUpdateService::~nsOfflineCacheUpdateService()
+{
+ gOfflineCacheUpdateService = nullptr;
+}
+
+nsresult
+nsOfflineCacheUpdateService::Init()
+{
+ // Observe xpcom-shutdown event
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (!observerService)
+ return NS_ERROR_FAILURE;
+
+ nsresult rv = observerService->AddObserver(this,
+ NS_XPCOM_SHUTDOWN_OBSERVER_ID,
+ true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Get the current status of the disk in terms of free space and observe
+ // low device storage notifications.
+ nsCOMPtr<nsIDiskSpaceWatcher> diskSpaceWatcherService =
+ do_GetService("@mozilla.org/toolkit/disk-space-watcher;1");
+ if (diskSpaceWatcherService) {
+ diskSpaceWatcherService->GetIsDiskFull(&mLowFreeSpace);
+ } else {
+ NS_WARNING("Could not get disk status from nsIDiskSpaceWatcher");
+ }
+
+ rv = observerService->AddObserver(this, "disk-space-watcher", false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ gOfflineCacheUpdateService = this;
+
+ return NS_OK;
+}
+
+/* static */
+nsOfflineCacheUpdateService *
+nsOfflineCacheUpdateService::GetInstance()
+{
+ if (!gOfflineCacheUpdateService) {
+ gOfflineCacheUpdateService = new nsOfflineCacheUpdateService();
+ if (!gOfflineCacheUpdateService)
+ return nullptr;
+ NS_ADDREF(gOfflineCacheUpdateService);
+ nsresult rv = gOfflineCacheUpdateService->Init();
+ if (NS_FAILED(rv)) {
+ NS_RELEASE(gOfflineCacheUpdateService);
+ return nullptr;
+ }
+ return gOfflineCacheUpdateService;
+ }
+
+ NS_ADDREF(gOfflineCacheUpdateService);
+
+ return gOfflineCacheUpdateService;
+}
+
+/* static */
+nsOfflineCacheUpdateService *
+nsOfflineCacheUpdateService::EnsureService()
+{
+ if (!gOfflineCacheUpdateService) {
+ // Make the service manager hold a long-lived reference to the service
+ nsCOMPtr<nsIOfflineCacheUpdateService> service =
+ do_GetService(NS_OFFLINECACHEUPDATESERVICE_CONTRACTID);
+ }
+
+ return gOfflineCacheUpdateService;
+}
+
+nsresult
+nsOfflineCacheUpdateService::ScheduleUpdate(nsOfflineCacheUpdate *aUpdate)
+{
+ LOG(("nsOfflineCacheUpdateService::Schedule [%p, update=%p]",
+ this, aUpdate));
+
+ aUpdate->SetOwner(this);
+
+ mUpdates.AppendElement(aUpdate);
+ ProcessNextUpdate();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheUpdateService::ScheduleOnDocumentStop(nsIURI *aManifestURI,
+ nsIURI *aDocumentURI,
+ nsIPrincipal* aLoadingPrincipal,
+ nsIDOMDocument *aDocument)
+{
+ LOG(("nsOfflineCacheUpdateService::ScheduleOnDocumentStop [%p, manifestURI=%p, documentURI=%p doc=%p]",
+ this, aManifestURI, aDocumentURI, aDocument));
+
+ nsCOMPtr<nsIDocument> doc = do_QueryInterface(aDocument);
+ nsCOMPtr<nsIWebProgress> progress = do_QueryInterface(doc->GetContainer());
+ NS_ENSURE_TRUE(progress, NS_ERROR_INVALID_ARG);
+
+ // Proceed with cache update
+ RefPtr<nsOfflineCachePendingUpdate> update =
+ new nsOfflineCachePendingUpdate(this, aManifestURI, aDocumentURI,
+ aLoadingPrincipal, aDocument);
+ NS_ENSURE_TRUE(update, NS_ERROR_OUT_OF_MEMORY);
+
+ nsresult rv = progress->AddProgressListener
+ (update, nsIWebProgress::NOTIFY_STATE_DOCUMENT);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // The update will release when it has scheduled itself.
+ Unused << update.forget();
+
+ return NS_OK;
+}
+
+nsresult
+nsOfflineCacheUpdateService::UpdateFinished(nsOfflineCacheUpdate *aUpdate)
+{
+ LOG(("nsOfflineCacheUpdateService::UpdateFinished [%p, update=%p]",
+ this, aUpdate));
+
+ NS_ASSERTION(mUpdates.Length() > 0 &&
+ mUpdates[0] == aUpdate, "Unknown update completed");
+
+ // keep this item alive until we're done notifying observers
+ RefPtr<nsOfflineCacheUpdate> update = mUpdates[0];
+ Unused << update;
+ mUpdates.RemoveElementAt(0);
+ mUpdateRunning = false;
+
+ ProcessNextUpdate();
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsOfflineCacheUpdateService <private>
+//-----------------------------------------------------------------------------
+
+nsresult
+nsOfflineCacheUpdateService::ProcessNextUpdate()
+{
+ LOG(("nsOfflineCacheUpdateService::ProcessNextUpdate [%p, num=%d]",
+ this, mUpdates.Length()));
+
+ if (mDisabled)
+ return NS_ERROR_ABORT;
+
+ if (mUpdateRunning)
+ return NS_OK;
+
+ if (mUpdates.Length() > 0) {
+ mUpdateRunning = true;
+ // Canceling the update before Begin() call will make the update
+ // asynchronously finish with an error.
+ if (mLowFreeSpace) {
+ mUpdates[0]->Cancel();
+ }
+ return mUpdates[0]->Begin();
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsOfflineCacheUpdateService::nsIOfflineCacheUpdateService
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsOfflineCacheUpdateService::GetNumUpdates(uint32_t *aNumUpdates)
+{
+ LOG(("nsOfflineCacheUpdateService::GetNumUpdates [%p]", this));
+
+ *aNumUpdates = mUpdates.Length();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheUpdateService::GetUpdate(uint32_t aIndex,
+ nsIOfflineCacheUpdate **aUpdate)
+{
+ LOG(("nsOfflineCacheUpdateService::GetUpdate [%p, %d]", this, aIndex));
+
+ if (aIndex < mUpdates.Length()) {
+ NS_ADDREF(*aUpdate = mUpdates[aIndex]);
+ } else {
+ *aUpdate = nullptr;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsOfflineCacheUpdateService::FindUpdate(nsIURI *aManifestURI,
+ nsACString const &aOriginSuffix,
+ nsIFile *aCustomProfileDir,
+ nsOfflineCacheUpdate **aUpdate)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIApplicationCacheService> cacheService =
+ do_GetService(NS_APPLICATIONCACHESERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString groupID;
+ rv = cacheService->BuildGroupIDForSuffix(aManifestURI, aOriginSuffix, groupID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<nsOfflineCacheUpdate> update;
+ for (uint32_t i = 0; i < mUpdates.Length(); i++) {
+ update = mUpdates[i];
+
+ bool partial;
+ rv = update->GetPartial(&partial);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (partial) {
+ // Partial updates aren't considered
+ continue;
+ }
+
+ if (update->IsForGroupID(groupID) && update->IsForProfile(aCustomProfileDir)) {
+ update.swap(*aUpdate);
+ return NS_OK;
+ }
+ }
+
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+nsresult
+nsOfflineCacheUpdateService::Schedule(nsIURI *aManifestURI,
+ nsIURI *aDocumentURI,
+ nsIPrincipal* aLoadingPrincipal,
+ nsIDOMDocument *aDocument,
+ nsPIDOMWindowInner* aWindow,
+ nsIFile* aCustomProfileDir,
+ nsIOfflineCacheUpdate **aUpdate)
+{
+ nsCOMPtr<nsIOfflineCacheUpdate> update;
+ if (GeckoProcessType_Default != XRE_GetProcessType()) {
+ update = new OfflineCacheUpdateChild(aWindow);
+ }
+ else {
+ update = new OfflineCacheUpdateGlue();
+ }
+
+ nsresult rv;
+
+ if (aWindow) {
+ // Ensure there is window.applicationCache object that is
+ // responsible for association of the new applicationCache
+ // with the corresponding document. Just ignore the result.
+ nsCOMPtr<nsIDOMOfflineResourceList> appCacheWindowObject =
+ aWindow->GetApplicationCache();
+ }
+
+ rv = update->Init(aManifestURI, aDocumentURI, aLoadingPrincipal, aDocument,
+ aCustomProfileDir);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = update->Schedule();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ADDREF(*aUpdate = update);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheUpdateService::ScheduleUpdate(nsIURI *aManifestURI,
+ nsIURI *aDocumentURI,
+ nsIPrincipal* aLoadingPrincipal,
+ mozIDOMWindow* aWindow,
+ nsIOfflineCacheUpdate **aUpdate)
+{
+ return Schedule(aManifestURI, aDocumentURI, aLoadingPrincipal, nullptr,
+ nsPIDOMWindowInner::From(aWindow), nullptr, aUpdate);
+}
+
+NS_IMETHODIMP
+nsOfflineCacheUpdateService::ScheduleAppUpdate(nsIURI *aManifestURI,
+ nsIURI *aDocumentURI,
+ nsIPrincipal* aLoadingPrincipal,
+ nsIFile *aProfileDir,
+ nsIOfflineCacheUpdate **aUpdate)
+{
+ return Schedule(aManifestURI, aDocumentURI, aLoadingPrincipal, nullptr, nullptr,
+ aProfileDir, aUpdate);
+}
+
+NS_IMETHODIMP nsOfflineCacheUpdateService::CheckForUpdate(nsIURI *aManifestURI,
+ nsIPrincipal* aLoadingPrincipal,
+ nsIObserver *aObserver)
+{
+ if (GeckoProcessType_Default != XRE_GetProcessType()) {
+ // Not intended to support this on child processes
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ nsCOMPtr<nsIOfflineCacheUpdate> update = new OfflineCacheUpdateGlue();
+
+ nsresult rv;
+
+ rv = update->InitForUpdateCheck(aManifestURI, aLoadingPrincipal, aObserver);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = update->Schedule();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsOfflineCacheUpdateService::nsIObserver
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsOfflineCacheUpdateService::Observe(nsISupports *aSubject,
+ const char *aTopic,
+ const char16_t *aData)
+{
+ if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
+ if (mUpdates.Length() > 0)
+ mUpdates[0]->Cancel();
+ mDisabled = true;
+ }
+
+ if (!strcmp(aTopic, "disk-space-watcher")) {
+ if (NS_LITERAL_STRING("full").Equals(aData)) {
+ mLowFreeSpace = true;
+ for (uint32_t i = 0; i < mUpdates.Length(); i++) {
+ mUpdates[i]->Cancel();
+ }
+ } else if (NS_LITERAL_STRING("free").Equals(aData)) {
+ mLowFreeSpace = false;
+ }
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsOfflineCacheUpdateService::nsIOfflineCacheUpdateService
+//-----------------------------------------------------------------------------
+
+static nsresult
+OfflineAppPermForPrincipal(nsIPrincipal *aPrincipal,
+ nsIPrefBranch *aPrefBranch,
+ bool pinned,
+ bool *aAllowed)
+{
+ *aAllowed = false;
+
+ if (!sAllowOfflineCache) {
+ return NS_OK;
+ }
+
+ if (!aPrincipal)
+ return NS_ERROR_INVALID_ARG;
+
+ nsCOMPtr<nsIURI> uri;
+ aPrincipal->GetURI(getter_AddRefs(uri));
+
+ if (!uri)
+ return NS_OK;
+
+ nsCOMPtr<nsIURI> innerURI = NS_GetInnermostURI(uri);
+ if (!innerURI)
+ return NS_OK;
+
+ // only http and https applications can use offline APIs.
+ bool match;
+ nsresult rv = innerURI->SchemeIs("http", &match);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!match) {
+ rv = innerURI->SchemeIs("https", &match);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!match) {
+ return NS_OK;
+ }
+ }
+
+ nsAutoCString domain;
+ rv = innerURI->GetAsciiHost(domain);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (nsOfflineCacheUpdateService::AllowedDomains()->Contains(domain)) {
+ *aAllowed = true;
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIPermissionManager> permissionManager =
+ services::GetPermissionManager();
+ if (!permissionManager) {
+ return NS_OK;
+ }
+
+ uint32_t perm;
+ const char *permName = pinned ? "pin-app" : "offline-app";
+ permissionManager->TestExactPermissionFromPrincipal(aPrincipal, permName, &perm);
+
+ if (perm == nsIPermissionManager::ALLOW_ACTION ||
+ perm == nsIOfflineCacheUpdateService::ALLOW_NO_WARN) {
+ *aAllowed = true;
+ }
+
+ // offline-apps.allow_by_default is now effective at the cache selection
+ // algorithm code (nsContentSink).
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheUpdateService::OfflineAppAllowed(nsIPrincipal *aPrincipal,
+ nsIPrefBranch *aPrefBranch,
+ bool *aAllowed)
+{
+ return OfflineAppPermForPrincipal(aPrincipal, aPrefBranch, false, aAllowed);
+}
+
+NS_IMETHODIMP
+nsOfflineCacheUpdateService::OfflineAppAllowedForURI(nsIURI *aURI,
+ nsIPrefBranch *aPrefBranch,
+ bool *aAllowed)
+{
+ PrincipalOriginAttributes attrs;
+ nsCOMPtr<nsIPrincipal> principal =
+ BasePrincipal::CreateCodebasePrincipal(aURI, attrs);
+ return OfflineAppPermForPrincipal(principal, aPrefBranch, false, aAllowed);
+}
+
+nsresult
+nsOfflineCacheUpdateService::OfflineAppPinnedForURI(nsIURI *aDocumentURI,
+ nsIPrefBranch *aPrefBranch,
+ bool *aPinned)
+{
+ PrincipalOriginAttributes attrs;
+ nsCOMPtr<nsIPrincipal> principal =
+ BasePrincipal::CreateCodebasePrincipal(aDocumentURI, attrs);
+ return OfflineAppPermForPrincipal(principal, aPrefBranch, true, aPinned);
+}
+
+NS_IMETHODIMP
+nsOfflineCacheUpdateService::AllowOfflineApp(nsIPrincipal *aPrincipal)
+{
+ nsresult rv;
+
+ if (!sAllowOfflineCache) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (GeckoProcessType_Default != XRE_GetProcessType()) {
+ ContentChild* child = ContentChild::GetSingleton();
+
+ if (!child->SendSetOfflinePermission(IPC::Principal(aPrincipal))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsAutoCString domain;
+ rv = aPrincipal->GetBaseDomain(domain);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsOfflineCacheUpdateService::AllowedDomains()->PutEntry(domain);
+ }
+ else {
+ nsCOMPtr<nsIPermissionManager> permissionManager =
+ services::GetPermissionManager();
+ if (!permissionManager)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ rv = permissionManager->AddFromPrincipal(
+ aPrincipal, "offline-app", nsIPermissionManager::ALLOW_ACTION,
+ nsIPermissionManager::EXPIRE_NEVER, 0);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
diff --git a/uriloader/prefetch/nsPrefetchService.cpp b/uriloader/prefetch/nsPrefetchService.cpp
new file mode 100644
index 0000000000..bd2b10d304
--- /dev/null
+++ b/uriloader/prefetch/nsPrefetchService.cpp
@@ -0,0 +1,931 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsPrefetchService.h"
+#include "nsICacheEntry.h"
+#include "nsIServiceManager.h"
+#include "nsICategoryManager.h"
+#include "nsIObserverService.h"
+#include "nsIWebProgress.h"
+#include "nsCURILoader.h"
+#include "nsICacheInfoChannel.h"
+#include "nsIHttpChannel.h"
+#include "nsIURL.h"
+#include "nsISimpleEnumerator.h"
+#include "nsNetUtil.h"
+#include "nsString.h"
+#include "nsXPIDLString.h"
+#include "nsReadableUtils.h"
+#include "nsStreamUtils.h"
+#include "nsAutoPtr.h"
+#include "prtime.h"
+#include "mozilla/Logging.h"
+#include "plstr.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/CORSMode.h"
+#include "mozilla/dom/HTMLLinkElement.h"
+#include "nsIDOMNode.h"
+#include "nsINode.h"
+#include "nsIDocument.h"
+#include "nsContentUtils.h"
+
+using namespace mozilla;
+
+//
+// To enable logging (see mozilla/Logging.h for full details):
+//
+// set MOZ_LOG=nsPrefetch:5
+// set MOZ_LOG_FILE=prefetch.log
+//
+// this enables LogLevel::Debug level information and places all output in
+// the file prefetch.log
+//
+static LazyLogModule gPrefetchLog("nsPrefetch");
+
+#undef LOG
+#define LOG(args) MOZ_LOG(gPrefetchLog, mozilla::LogLevel::Debug, args)
+
+#undef LOG_ENABLED
+#define LOG_ENABLED() MOZ_LOG_TEST(gPrefetchLog, mozilla::LogLevel::Debug)
+
+#define PREFETCH_PREF "network.prefetch-next"
+#define PARALLELISM_PREF "network.prefetch-next.parallelism"
+#define AGGRESSIVE_PREF "network.prefetch-next.aggressive"
+
+//-----------------------------------------------------------------------------
+// helpers
+//-----------------------------------------------------------------------------
+
+static inline uint32_t
+PRTimeToSeconds(PRTime t_usec)
+{
+ PRTime usec_per_sec = PR_USEC_PER_SEC;
+ return uint32_t(t_usec /= usec_per_sec);
+}
+
+#define NowInSeconds() PRTimeToSeconds(PR_Now())
+
+//-----------------------------------------------------------------------------
+// nsPrefetchNode <public>
+//-----------------------------------------------------------------------------
+
+nsPrefetchNode::nsPrefetchNode(nsPrefetchService *aService,
+ nsIURI *aURI,
+ nsIURI *aReferrerURI,
+ nsIDOMNode *aSource)
+ : mURI(aURI)
+ , mReferrerURI(aReferrerURI)
+ , mService(aService)
+ , mChannel(nullptr)
+ , mBytesRead(0)
+ , mShouldFireLoadEvent(false)
+{
+ nsCOMPtr<nsIWeakReference> source = do_GetWeakReference(aSource);
+ mSources.AppendElement(source);
+}
+
+nsresult
+nsPrefetchNode::OpenChannel()
+{
+ if (mSources.IsEmpty()) {
+ // Don't attempt to prefetch if we don't have a source node
+ // (which should never happen).
+ return NS_ERROR_FAILURE;
+ }
+ nsCOMPtr<nsINode> source;
+ while (!mSources.IsEmpty() && !(source = do_QueryReferent(mSources.ElementAt(0)))) {
+ // If source is null remove it.
+ // (which should never happen).
+ mSources.RemoveElementAt(0);
+ }
+
+ if (!source) {
+ // Don't attempt to prefetch if we don't have a source node
+ // (which should never happen).
+
+ return NS_ERROR_FAILURE;
+ }
+ nsCOMPtr<nsILoadGroup> loadGroup = source->OwnerDoc()->GetDocumentLoadGroup();
+ CORSMode corsMode = CORS_NONE;
+ net::ReferrerPolicy referrerPolicy = net::RP_Unset;
+ if (source->IsHTMLElement(nsGkAtoms::link)) {
+ dom::HTMLLinkElement* link = static_cast<dom::HTMLLinkElement*>(source.get());
+ corsMode = link->GetCORSMode();
+ referrerPolicy = link->GetLinkReferrerPolicy();
+ }
+
+ if (referrerPolicy == net::RP_Unset) {
+ referrerPolicy = source->OwnerDoc()->GetReferrerPolicy();
+ }
+
+ uint32_t securityFlags;
+ if (corsMode == CORS_NONE) {
+ securityFlags = nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS;
+ } else {
+ securityFlags = nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS;
+ if (corsMode == CORS_USE_CREDENTIALS) {
+ securityFlags |= nsILoadInfo::SEC_COOKIES_INCLUDE;
+ }
+ }
+ nsresult rv = NS_NewChannelInternal(getter_AddRefs(mChannel),
+ mURI,
+ source,
+ source->NodePrincipal(),
+ nullptr, //aTriggeringPrincipal
+ securityFlags,
+ nsIContentPolicy::TYPE_OTHER,
+ loadGroup, // aLoadGroup
+ this, // aCallbacks
+ nsIRequest::LOAD_BACKGROUND |
+ nsICachingChannel::LOAD_ONLY_IF_MODIFIED);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // configure HTTP specific stuff
+ nsCOMPtr<nsIHttpChannel> httpChannel =
+ do_QueryInterface(mChannel);
+ if (httpChannel) {
+ httpChannel->SetReferrerWithPolicy(mReferrerURI, referrerPolicy);
+ httpChannel->SetRequestHeader(
+ NS_LITERAL_CSTRING("X-Moz"),
+ NS_LITERAL_CSTRING("prefetch"),
+ false);
+ }
+
+ rv = mChannel->AsyncOpen2(this);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ // Drop the ref to the channel, because we don't want to end up with
+ // cycles through it.
+ mChannel = nullptr;
+ }
+ return rv;
+}
+
+nsresult
+nsPrefetchNode::CancelChannel(nsresult error)
+{
+ mChannel->Cancel(error);
+ mChannel = nullptr;
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsPrefetchNode::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(nsPrefetchNode,
+ nsIRequestObserver,
+ nsIStreamListener,
+ nsIInterfaceRequestor,
+ nsIChannelEventSink,
+ nsIRedirectResultListener)
+
+//-----------------------------------------------------------------------------
+// nsPrefetchNode::nsIStreamListener
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsPrefetchNode::OnStartRequest(nsIRequest *aRequest,
+ nsISupports *aContext)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIHttpChannel> httpChannel =
+ do_QueryInterface(aRequest, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ // if the load is cross origin without CORS, or the CORS access is rejected,
+ // always fire load event to avoid leaking site information.
+ nsCOMPtr<nsILoadInfo> loadInfo = httpChannel->GetLoadInfo();
+ mShouldFireLoadEvent = loadInfo->GetTainting() == LoadTainting::Opaque ||
+ (loadInfo->GetTainting() == LoadTainting::CORS &&
+ (NS_FAILED(httpChannel->GetStatus(&rv)) ||
+ NS_FAILED(rv)));
+
+ // no need to prefetch http error page
+ bool requestSucceeded;
+ if (NS_FAILED(httpChannel->GetRequestSucceeded(&requestSucceeded)) ||
+ !requestSucceeded) {
+ return NS_BINDING_ABORTED;
+ }
+
+ nsCOMPtr<nsICacheInfoChannel> cacheInfoChannel =
+ do_QueryInterface(aRequest, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ // no need to prefetch a document that is already in the cache
+ bool fromCache;
+ if (NS_SUCCEEDED(cacheInfoChannel->IsFromCache(&fromCache)) &&
+ fromCache) {
+ LOG(("document is already in the cache; canceling prefetch\n"));
+ // although it's canceled we still want to fire load event
+ mShouldFireLoadEvent = true;
+ return NS_BINDING_ABORTED;
+ }
+
+ //
+ // no need to prefetch a document that must be requested fresh each
+ // and every time.
+ //
+ uint32_t expTime;
+ if (NS_SUCCEEDED(cacheInfoChannel->GetCacheTokenExpirationTime(&expTime))) {
+ if (NowInSeconds() >= expTime) {
+ LOG(("document cannot be reused from cache; "
+ "canceling prefetch\n"));
+ return NS_BINDING_ABORTED;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrefetchNode::OnDataAvailable(nsIRequest *aRequest,
+ nsISupports *aContext,
+ nsIInputStream *aStream,
+ uint64_t aOffset,
+ uint32_t aCount)
+{
+ uint32_t bytesRead = 0;
+ aStream->ReadSegments(NS_DiscardSegment, nullptr, aCount, &bytesRead);
+ mBytesRead += bytesRead;
+ LOG(("prefetched %u bytes [offset=%llu]\n", bytesRead, aOffset));
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsPrefetchNode::OnStopRequest(nsIRequest *aRequest,
+ nsISupports *aContext,
+ nsresult aStatus)
+{
+ LOG(("done prefetching [status=%x]\n", aStatus));
+
+ if (mBytesRead == 0 && aStatus == NS_OK && mChannel) {
+ // we didn't need to read (because LOAD_ONLY_IF_MODIFIED was
+ // specified), but the object should report loadedSize as if it
+ // did.
+ mChannel->GetContentLength(&mBytesRead);
+ }
+
+ mService->NotifyLoadCompleted(this);
+ mService->DispatchEvent(this, mShouldFireLoadEvent || NS_SUCCEEDED(aStatus));
+ mService->ProcessNextURI(this);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsPrefetchNode::nsIInterfaceRequestor
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsPrefetchNode::GetInterface(const nsIID &aIID, void **aResult)
+{
+ if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) {
+ NS_ADDREF_THIS();
+ *aResult = static_cast<nsIChannelEventSink *>(this);
+ return NS_OK;
+ }
+
+ if (aIID.Equals(NS_GET_IID(nsIRedirectResultListener))) {
+ NS_ADDREF_THIS();
+ *aResult = static_cast<nsIRedirectResultListener *>(this);
+ return NS_OK;
+ }
+
+ return NS_ERROR_NO_INTERFACE;
+}
+
+//-----------------------------------------------------------------------------
+// nsPrefetchNode::nsIChannelEventSink
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsPrefetchNode::AsyncOnChannelRedirect(nsIChannel *aOldChannel,
+ nsIChannel *aNewChannel,
+ uint32_t aFlags,
+ nsIAsyncVerifyRedirectCallback *callback)
+{
+ nsCOMPtr<nsIURI> newURI;
+ nsresult rv = aNewChannel->GetURI(getter_AddRefs(newURI));
+ if (NS_FAILED(rv))
+ return rv;
+
+ bool match;
+ rv = newURI->SchemeIs("http", &match);
+ if (NS_FAILED(rv) || !match) {
+ rv = newURI->SchemeIs("https", &match);
+ if (NS_FAILED(rv) || !match) {
+ LOG(("rejected: URL is not of type http/https\n"));
+ return NS_ERROR_ABORT;
+ }
+ }
+
+ // HTTP request headers are not automatically forwarded to the new channel.
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aNewChannel);
+ NS_ENSURE_STATE(httpChannel);
+
+ httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("X-Moz"),
+ NS_LITERAL_CSTRING("prefetch"),
+ false);
+
+ // Assign to mChannel after we get notification about success of the
+ // redirect in OnRedirectResult.
+ mRedirectChannel = aNewChannel;
+
+ callback->OnRedirectVerifyCallback(NS_OK);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsPrefetchNode::nsIRedirectResultListener
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsPrefetchNode::OnRedirectResult(bool proceeding)
+{
+ if (proceeding && mRedirectChannel)
+ mChannel = mRedirectChannel;
+
+ mRedirectChannel = nullptr;
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsPrefetchService <public>
+//-----------------------------------------------------------------------------
+
+nsPrefetchService::nsPrefetchService()
+ : mMaxParallelism(6)
+ , mStopCount(0)
+ , mHaveProcessed(false)
+ , mDisabled(true)
+ , mAggressive(false)
+{
+}
+
+nsPrefetchService::~nsPrefetchService()
+{
+ Preferences::RemoveObserver(this, PREFETCH_PREF);
+ Preferences::RemoveObserver(this, PARALLELISM_PREF);
+ Preferences::RemoveObserver(this, AGGRESSIVE_PREF);
+ // cannot reach destructor if prefetch in progress (listener owns reference
+ // to this service)
+ EmptyQueue();
+}
+
+nsresult
+nsPrefetchService::Init()
+{
+ nsresult rv;
+
+ // read prefs and hook up pref observer
+ mDisabled = !Preferences::GetBool(PREFETCH_PREF, !mDisabled);
+ Preferences::AddWeakObserver(this, PREFETCH_PREF);
+
+ mMaxParallelism = Preferences::GetInt(PARALLELISM_PREF, mMaxParallelism);
+ if (mMaxParallelism < 1) {
+ mMaxParallelism = 1;
+ }
+ Preferences::AddWeakObserver(this, PARALLELISM_PREF);
+
+ mAggressive = Preferences::GetBool(AGGRESSIVE_PREF, false);
+ Preferences::AddWeakObserver(this, AGGRESSIVE_PREF);
+
+ // Observe xpcom-shutdown event
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (!observerService)
+ return NS_ERROR_FAILURE;
+
+ rv = observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!mDisabled)
+ AddProgressListener();
+
+ return NS_OK;
+}
+
+void
+nsPrefetchService::ProcessNextURI(nsPrefetchNode *aFinished)
+{
+ nsresult rv;
+
+ if (aFinished) {
+ mCurrentNodes.RemoveElement(aFinished);
+ }
+
+ if (mCurrentNodes.Length() >= static_cast<uint32_t>(mMaxParallelism)) {
+ // We already have enough prefetches going on, so hold off
+ // for now.
+ return;
+ }
+
+ do {
+ if (mQueue.empty()) {
+ break;
+ }
+ RefPtr<nsPrefetchNode> node = mQueue.front().forget();
+ mQueue.pop_front();
+
+ if (LOG_ENABLED()) {
+ LOG(("ProcessNextURI [%s]\n",
+ node->mURI->GetSpecOrDefault().get())); }
+
+ //
+ // if opening the channel fails (e.g. security check returns an error),
+ // send an error event and then just skip to the next uri
+ //
+ rv = node->OpenChannel();
+ if (NS_SUCCEEDED(rv)) {
+ mCurrentNodes.AppendElement(node);
+ } else {
+ DispatchEvent(node, false);
+ }
+ }
+ while (NS_FAILED(rv));
+}
+
+void
+nsPrefetchService::NotifyLoadRequested(nsPrefetchNode *node)
+{
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (!observerService)
+ return;
+
+ observerService->NotifyObservers(static_cast<nsIStreamListener*>(node),
+ "prefetch-load-requested", nullptr);
+}
+
+void
+nsPrefetchService::NotifyLoadCompleted(nsPrefetchNode *node)
+{
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (!observerService)
+ return;
+
+ observerService->NotifyObservers(static_cast<nsIStreamListener*>(node),
+ "prefetch-load-completed", nullptr);
+}
+
+void
+nsPrefetchService::DispatchEvent(nsPrefetchNode *node, bool aSuccess)
+{
+ for (uint32_t i = 0; i < node->mSources.Length(); i++) {
+ nsCOMPtr<nsINode> domNode = do_QueryReferent(node->mSources.ElementAt(i));
+ if (domNode && domNode->IsInComposedDoc()) {
+ nsContentUtils::DispatchTrustedEvent(domNode->OwnerDoc(),
+ domNode,
+ aSuccess ?
+ NS_LITERAL_STRING("load") :
+ NS_LITERAL_STRING("error"),
+ /* aCanBubble = */ false,
+ /* aCancelable = */ false);
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// nsPrefetchService <private>
+//-----------------------------------------------------------------------------
+
+void
+nsPrefetchService::AddProgressListener()
+{
+ // Register as an observer for the document loader
+ nsCOMPtr<nsIWebProgress> progress =
+ do_GetService(NS_DOCUMENTLOADER_SERVICE_CONTRACTID);
+ if (progress)
+ progress->AddProgressListener(this, nsIWebProgress::NOTIFY_STATE_DOCUMENT);
+}
+
+void
+nsPrefetchService::RemoveProgressListener()
+{
+ // Register as an observer for the document loader
+ nsCOMPtr<nsIWebProgress> progress =
+ do_GetService(NS_DOCUMENTLOADER_SERVICE_CONTRACTID);
+ if (progress)
+ progress->RemoveProgressListener(this);
+}
+
+nsresult
+nsPrefetchService::EnqueueURI(nsIURI *aURI,
+ nsIURI *aReferrerURI,
+ nsIDOMNode *aSource,
+ nsPrefetchNode **aNode)
+{
+ RefPtr<nsPrefetchNode> node = new nsPrefetchNode(this, aURI, aReferrerURI,
+ aSource);
+ mQueue.push_back(node);
+ node.forget(aNode);
+ return NS_OK;
+}
+
+void
+nsPrefetchService::EmptyQueue()
+{
+ while (!mQueue.empty()) {
+ mQueue.pop_back();
+ }
+}
+
+void
+nsPrefetchService::StartPrefetching()
+{
+ //
+ // at initialization time we might miss the first DOCUMENT START
+ // notification, so we have to be careful to avoid letting our
+ // stop count go negative.
+ //
+ if (mStopCount > 0)
+ mStopCount--;
+
+ LOG(("StartPrefetching [stopcount=%d]\n", mStopCount));
+
+ // only start prefetching after we've received enough DOCUMENT
+ // STOP notifications. we do this inorder to defer prefetching
+ // until after all sub-frames have finished loading.
+ if (!mStopCount) {
+ mHaveProcessed = true;
+ while (!mQueue.empty() && mCurrentNodes.Length() < static_cast<uint32_t>(mMaxParallelism)) {
+ ProcessNextURI(nullptr);
+ }
+ }
+}
+
+void
+nsPrefetchService::StopPrefetching()
+{
+ mStopCount++;
+
+ LOG(("StopPrefetching [stopcount=%d]\n", mStopCount));
+
+ // only kill the prefetch queue if we are actively prefetching right now
+ if (mCurrentNodes.IsEmpty()) {
+ return;
+ }
+
+ for (uint32_t i = 0; i < mCurrentNodes.Length(); ++i) {
+ mCurrentNodes[i]->CancelChannel(NS_BINDING_ABORTED);
+ }
+ mCurrentNodes.Clear();
+ EmptyQueue();
+}
+
+//-----------------------------------------------------------------------------
+// nsPrefetchService::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(nsPrefetchService,
+ nsIPrefetchService,
+ nsIWebProgressListener,
+ nsIObserver,
+ nsISupportsWeakReference)
+
+//-----------------------------------------------------------------------------
+// nsPrefetchService::nsIPrefetchService
+//-----------------------------------------------------------------------------
+
+nsresult
+nsPrefetchService::Prefetch(nsIURI *aURI,
+ nsIURI *aReferrerURI,
+ nsIDOMNode *aSource,
+ bool aExplicit)
+{
+ nsresult rv;
+
+ NS_ENSURE_ARG_POINTER(aURI);
+ NS_ENSURE_ARG_POINTER(aReferrerURI);
+
+ if (LOG_ENABLED()) {
+ LOG(("PrefetchURI [%s]\n", aURI->GetSpecOrDefault().get()));
+ }
+
+ if (mDisabled) {
+ LOG(("rejected: prefetch service is disabled\n"));
+ return NS_ERROR_ABORT;
+ }
+
+ //
+ // XXX we should really be asking the protocol handler if it supports
+ // caching, so we can determine if there is any value to prefetching.
+ // for now, we'll only prefetch http links since we know that's the
+ // most common case. ignore https links since https content only goes
+ // into the memory cache.
+ //
+ // XXX we might want to either leverage nsIProtocolHandler::protocolFlags
+ // or possibly nsIRequest::loadFlags to determine if this URI should be
+ // prefetched.
+ //
+ bool match;
+ rv = aURI->SchemeIs("http", &match);
+ if (NS_FAILED(rv) || !match) {
+ rv = aURI->SchemeIs("https", &match);
+ if (NS_FAILED(rv) || !match) {
+ LOG(("rejected: URL is not of type http/https\n"));
+ return NS_ERROR_ABORT;
+ }
+ }
+
+ //
+ // the referrer URI must be http:
+ //
+ rv = aReferrerURI->SchemeIs("http", &match);
+ if (NS_FAILED(rv) || !match) {
+ rv = aReferrerURI->SchemeIs("https", &match);
+ if (NS_FAILED(rv) || !match) {
+ LOG(("rejected: referrer URL is neither http nor https\n"));
+ return NS_ERROR_ABORT;
+ }
+ }
+
+ // skip URLs that contain query strings, except URLs for which prefetching
+ // has been explicitly requested.
+ if (!aExplicit) {
+ nsCOMPtr<nsIURL> url(do_QueryInterface(aURI, &rv));
+ if (NS_FAILED(rv)) return rv;
+ nsAutoCString query;
+ rv = url->GetQuery(query);
+ if (NS_FAILED(rv) || !query.IsEmpty()) {
+ LOG(("rejected: URL has a query string\n"));
+ return NS_ERROR_ABORT;
+ }
+ }
+
+ //
+ // Check whether it is being prefetched.
+ //
+ for (uint32_t i = 0; i < mCurrentNodes.Length(); ++i) {
+ bool equals;
+ if (NS_SUCCEEDED(mCurrentNodes[i]->mURI->Equals(aURI, &equals)) &&
+ equals) {
+ nsCOMPtr<nsIWeakReference> source = do_GetWeakReference(aSource);
+ if (mCurrentNodes[i]->mSources.IndexOf(source) ==
+ mCurrentNodes[i]->mSources.NoIndex) {
+ LOG(("URL is already being prefetched, add a new reference "
+ "document\n"));
+ mCurrentNodes[i]->mSources.AppendElement(source);
+ return NS_OK;
+ } else {
+ LOG(("URL is already being prefetched by this document"));
+ return NS_ERROR_ABORT;
+ }
+ }
+ }
+
+ //
+ // Check whether it is on the prefetch queue.
+ //
+ for (std::deque<RefPtr<nsPrefetchNode>>::iterator nodeIt = mQueue.begin();
+ nodeIt != mQueue.end(); nodeIt++) {
+ bool equals;
+ RefPtr<nsPrefetchNode> node = nodeIt->get();
+ if (NS_SUCCEEDED(node->mURI->Equals(aURI, &equals)) && equals) {
+ nsCOMPtr<nsIWeakReference> source = do_GetWeakReference(aSource);
+ if (node->mSources.IndexOf(source) ==
+ node->mSources.NoIndex) {
+ LOG(("URL is already being prefetched, add a new reference "
+ "document\n"));
+ node->mSources.AppendElement(do_GetWeakReference(aSource));
+ return NS_OK;
+ } else {
+ LOG(("URL is already being prefetched by this document"));
+ return NS_ERROR_ABORT;
+ }
+
+ }
+ }
+
+ RefPtr<nsPrefetchNode> enqueuedNode;
+ rv = EnqueueURI(aURI, aReferrerURI, aSource,
+ getter_AddRefs(enqueuedNode));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NotifyLoadRequested(enqueuedNode);
+
+ // if there are no pages loading, kick off the request immediately
+ if (mStopCount == 0 && mHaveProcessed) {
+ ProcessNextURI(nullptr);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrefetchService::CancelPrefetchURI(nsIURI* aURI,
+ nsIDOMNode* aSource)
+{
+ NS_ENSURE_ARG_POINTER(aURI);
+
+ if (LOG_ENABLED()) {
+ LOG(("CancelPrefetchURI [%s]\n", aURI->GetSpecOrDefault().get()));
+ }
+
+ //
+ // look in current prefetches
+ //
+ for (uint32_t i = 0; i < mCurrentNodes.Length(); ++i) {
+ bool equals;
+ if (NS_SUCCEEDED(mCurrentNodes[i]->mURI->Equals(aURI, &equals)) &&
+ equals) {
+ nsCOMPtr<nsIWeakReference> source = do_GetWeakReference(aSource);
+ if (mCurrentNodes[i]->mSources.IndexOf(source) !=
+ mCurrentNodes[i]->mSources.NoIndex) {
+ mCurrentNodes[i]->mSources.RemoveElement(source);
+ if (mCurrentNodes[i]->mSources.IsEmpty()) {
+ mCurrentNodes[i]->CancelChannel(NS_BINDING_ABORTED);
+ mCurrentNodes.RemoveElementAt(i);
+ }
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ //
+ // look into the prefetch queue
+ //
+ for (std::deque<RefPtr<nsPrefetchNode>>::iterator nodeIt = mQueue.begin();
+ nodeIt != mQueue.end(); nodeIt++) {
+ bool equals;
+ RefPtr<nsPrefetchNode> node = nodeIt->get();
+ if (NS_SUCCEEDED(node->mURI->Equals(aURI, &equals)) && equals) {
+ nsCOMPtr<nsIWeakReference> source = do_GetWeakReference(aSource);
+ if (node->mSources.IndexOf(source) !=
+ node->mSources.NoIndex) {
+
+#ifdef DEBUG
+ int32_t inx = node->mSources.IndexOf(source);
+ nsCOMPtr<nsIDOMNode> domNode =
+ do_QueryReferent(node->mSources.ElementAt(inx));
+ MOZ_ASSERT(domNode);
+#endif
+
+ node->mSources.RemoveElement(source);
+ if (node->mSources.IsEmpty()) {
+ mQueue.erase(nodeIt);
+ }
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ // not found!
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsPrefetchService::PrefetchURI(nsIURI *aURI,
+ nsIURI *aReferrerURI,
+ nsIDOMNode *aSource,
+ bool aExplicit)
+{
+ return Prefetch(aURI, aReferrerURI, aSource, aExplicit);
+}
+
+NS_IMETHODIMP
+nsPrefetchService::HasMoreElements(bool *aHasMore)
+{
+ *aHasMore = (mCurrentNodes.Length() || !mQueue.empty());
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsPrefetchService::nsIWebProgressListener
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsPrefetchService::OnProgressChange(nsIWebProgress *aProgress,
+ nsIRequest *aRequest,
+ int32_t curSelfProgress,
+ int32_t maxSelfProgress,
+ int32_t curTotalProgress,
+ int32_t maxTotalProgress)
+{
+ NS_NOTREACHED("notification excluded in AddProgressListener(...)");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrefetchService::OnStateChange(nsIWebProgress* aWebProgress,
+ nsIRequest *aRequest,
+ uint32_t progressStateFlags,
+ nsresult aStatus)
+{
+ if (mAggressive) {
+ LOG(("Document load state is ignored in aggressive mode"));
+ return NS_OK;
+ }
+
+ if (progressStateFlags & STATE_IS_DOCUMENT) {
+ if (progressStateFlags & STATE_STOP)
+ StartPrefetching();
+ else if (progressStateFlags & STATE_START)
+ StopPrefetching();
+ }
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsPrefetchService::OnLocationChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ nsIURI *location,
+ uint32_t aFlags)
+{
+ NS_NOTREACHED("notification excluded in AddProgressListener(...)");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrefetchService::OnStatusChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ nsresult aStatus,
+ const char16_t* aMessage)
+{
+ NS_NOTREACHED("notification excluded in AddProgressListener(...)");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrefetchService::OnSecurityChange(nsIWebProgress *aWebProgress,
+ nsIRequest *aRequest,
+ uint32_t state)
+{
+ NS_NOTREACHED("notification excluded in AddProgressListener(...)");
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsPrefetchService::nsIObserver
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsPrefetchService::Observe(nsISupports *aSubject,
+ const char *aTopic,
+ const char16_t *aData)
+{
+ LOG(("nsPrefetchService::Observe [topic=%s]\n", aTopic));
+
+ if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
+ StopPrefetching();
+ EmptyQueue();
+ mDisabled = true;
+ }
+ else if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
+ const nsCString converted = NS_ConvertUTF16toUTF8(aData);
+ const char* pref = converted.get();
+ if (!strcmp(pref, PREFETCH_PREF)) {
+ if (Preferences::GetBool(PREFETCH_PREF, false)) {
+ if (mDisabled) {
+ LOG(("enabling prefetching\n"));
+ mDisabled = false;
+ AddProgressListener();
+ }
+ } else {
+ if (!mDisabled) {
+ LOG(("disabling prefetching\n"));
+ StopPrefetching();
+ EmptyQueue();
+ mDisabled = true;
+ RemoveProgressListener();
+ }
+ }
+ } else if (!strcmp(pref, PARALLELISM_PREF)) {
+ mMaxParallelism = Preferences::GetInt(PARALLELISM_PREF, mMaxParallelism);
+ if (mMaxParallelism < 1) {
+ mMaxParallelism = 1;
+ }
+ // If our parallelism has increased, go ahead and kick off enough
+ // prefetches to fill up our allowance. If we're now over our
+ // allowance, we'll just silently let some of them finish to get
+ // back below our limit.
+ while (!mQueue.empty() && mCurrentNodes.Length() < static_cast<uint32_t>(mMaxParallelism)) {
+ ProcessNextURI(nullptr);
+ }
+ } else if (!strcmp(pref, AGGRESSIVE_PREF)) {
+ mAggressive = Preferences::GetBool(AGGRESSIVE_PREF, false);
+ // in aggressive mode, clear stop count and start prefetching immediately
+ if (mAggressive) {
+ mStopCount = 0;
+ StartPrefetching();
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+// vim: ts=4 sw=4 expandtab
diff --git a/uriloader/prefetch/nsPrefetchService.h b/uriloader/prefetch/nsPrefetchService.h
new file mode 100644
index 0000000000..883449e689
--- /dev/null
+++ b/uriloader/prefetch/nsPrefetchService.h
@@ -0,0 +1,121 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsPrefetchService_h__
+#define nsPrefetchService_h__
+
+#include "nsCPrefetchService.h"
+#include "nsIObserver.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIChannelEventSink.h"
+#include "nsIRedirectResultListener.h"
+#include "nsIWebProgressListener.h"
+#include "nsIStreamListener.h"
+#include "nsIChannel.h"
+#include "nsIURI.h"
+#include "nsWeakReference.h"
+#include "nsCOMPtr.h"
+#include "nsAutoPtr.h"
+#include "mozilla/Attributes.h"
+#include <deque>
+
+class nsPrefetchService;
+class nsPrefetchNode;
+
+//-----------------------------------------------------------------------------
+// nsPrefetchService
+//-----------------------------------------------------------------------------
+
+class nsPrefetchService final : public nsIPrefetchService
+ , public nsIWebProgressListener
+ , public nsIObserver
+ , public nsSupportsWeakReference
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPREFETCHSERVICE
+ NS_DECL_NSIWEBPROGRESSLISTENER
+ NS_DECL_NSIOBSERVER
+
+ nsPrefetchService();
+
+ nsresult Init();
+ void ProcessNextURI(nsPrefetchNode *aFinished);
+
+ void NotifyLoadRequested(nsPrefetchNode *node);
+ void NotifyLoadCompleted(nsPrefetchNode *node);
+ void DispatchEvent(nsPrefetchNode *node, bool aSuccess);
+
+private:
+ ~nsPrefetchService();
+
+ nsresult Prefetch(nsIURI *aURI,
+ nsIURI *aReferrerURI,
+ nsIDOMNode *aSource,
+ bool aExplicit);
+
+ void AddProgressListener();
+ void RemoveProgressListener();
+ nsresult EnqueueURI(nsIURI *aURI, nsIURI *aReferrerURI,
+ nsIDOMNode *aSource, nsPrefetchNode **node);
+ void EmptyQueue();
+
+ void StartPrefetching();
+ void StopPrefetching();
+
+ std::deque<RefPtr<nsPrefetchNode>> mQueue;
+ nsTArray<RefPtr<nsPrefetchNode>> mCurrentNodes;
+ int32_t mMaxParallelism;
+ int32_t mStopCount;
+ // true if pending document loads have ever reached zero.
+ int32_t mHaveProcessed;
+ bool mDisabled;
+
+ // In usual case prefetch does not start until all normal loads are done.
+ // Aggressive mode ignores normal loads and just start prefetch ASAP.
+ // It's mainly for testing purpose and discoraged for normal use;
+ // see https://bugzilla.mozilla.org/show_bug.cgi?id=1281415 for details.
+ bool mAggressive;
+};
+
+//-----------------------------------------------------------------------------
+// nsPrefetchNode
+//-----------------------------------------------------------------------------
+
+class nsPrefetchNode final : public nsIStreamListener
+ , public nsIInterfaceRequestor
+ , public nsIChannelEventSink
+ , public nsIRedirectResultListener
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSICHANNELEVENTSINK
+ NS_DECL_NSIREDIRECTRESULTLISTENER
+
+ nsPrefetchNode(nsPrefetchService *aPrefetchService,
+ nsIURI *aURI,
+ nsIURI *aReferrerURI,
+ nsIDOMNode *aSource);
+
+ nsresult OpenChannel();
+ nsresult CancelChannel(nsresult error);
+
+ nsCOMPtr<nsIURI> mURI;
+ nsCOMPtr<nsIURI> mReferrerURI;
+ nsTArray<nsCOMPtr<nsIWeakReference>> mSources;
+
+private:
+ ~nsPrefetchNode() {}
+
+ RefPtr<nsPrefetchService> mService;
+ nsCOMPtr<nsIChannel> mChannel;
+ nsCOMPtr<nsIChannel> mRedirectChannel;
+ int64_t mBytesRead;
+ bool mShouldFireLoadEvent;
+};
+
+#endif // !nsPrefetchService_h__