From c84bf2a64d11dbceb92e2d56c2b9066fca687293 Mon Sep 17 00:00:00 2001 From: Gijs Kruitbosch Date: Tue, 26 Nov 2019 18:51:04 +0000 Subject: [PATCH] Bug 1496380 - stop recursion via the external protocol handler if Firefox is either the default OS handler or a configured external handler, r=mossop This is an initial implementation of this idea that works on mac. I've added a Windows implementation in another commit in this stack. I'll look at a Linux one in a follow-up bug. I do not think we need them in the child process implementation or on Android. Effectively, this makes nsIHandlerInfo::LaunchWithURI() fall back to asking if the handler info points to the OS default and that's us, or if it points to a helper app and that's us. The latter is fairly easy to check, but the former, more common case, is actually annoying - there don't seem to be APIs on the external helper app service or the handler service that provide any information about the app that's currently the default. So despite my belief that these interfaces have too many methods that all do very similar things, and what we need is fewer interfaces with fewer methods, I added another one... For this mac implementation, I'm comparing bundle URLs and added newer API usage for 10.10 and later to avoid deprecation warnings. I've not changed the mac shell service as it uses bundle identifiers to check if we're the default. Another way of fixing these issues would be to complain about things when we receive these URIs from external parties and our own config says that we will just hand them to someone else. I decided not to do so because we end up with at least one of the following problems: - if we implement in BrowserContentHandler, that won't help for PWAs/Thunderbird - if we try to implement in the external protocol handler, we'd need to start passing load flag information through to lots of checks. - it wouldn't stop the recursion until we've already done one round of it for links that are in webpages, which seems suboptimal (ie, if you clicked a mailto: link on a webpage it'd go to the OS with that mailto link and only realize something's awry when we've gone back through the OS to us, rather than straightaway). If we wanted to, we could add a fix like that in belt-and-suspenders fashion. Differential Revision: https://phabricator.services.mozilla.com/D48742 --HG-- extra : moz-landing-system : lando --- .../android/nsOSHelperAppService.cpp | 12 ++ .../exthandler/android/nsOSHelperAppService.h | 4 + .../exthandler/mac/nsOSHelperAppService.h | 3 + .../exthandler/mac/nsOSHelperAppService.mm | 134 ++++++++++++------ .../exthandler/nsExternalHelperAppService.cpp | 14 -- .../exthandler/nsExternalHelperAppService.h | 18 ++- .../exthandler/nsIExternalProtocolService.idl | 7 + uriloader/exthandler/nsMIMEInfoImpl.cpp | 69 +++++++++ .../exthandler/nsOSHelperAppServiceChild.cpp | 6 + .../exthandler/nsOSHelperAppServiceChild.h | 2 + .../exthandler/tests/mochitest/browser.ini | 2 + .../mochitest/browser_protocolhandler_loop.js | 82 +++++++++++ .../exthandler/uikit/nsOSHelperAppService.h | 2 + .../exthandler/uikit/nsOSHelperAppService.mm | 5 + .../exthandler/unix/nsOSHelperAppService.cpp | 6 + .../exthandler/unix/nsOSHelperAppService.h | 2 + .../exthandler/win/nsOSHelperAppService.cpp | 6 + .../exthandler/win/nsOSHelperAppService.h | 3 + 18 files changed, 319 insertions(+), 58 deletions(-) create mode 100644 uriloader/exthandler/tests/mochitest/browser_protocolhandler_loop.js diff --git a/uriloader/exthandler/android/nsOSHelperAppService.cpp b/uriloader/exthandler/android/nsOSHelperAppService.cpp index c18ddabfb435..d2eeb929e2dc 100644 --- a/uriloader/exthandler/android/nsOSHelperAppService.cpp +++ b/uriloader/exthandler/android/nsOSHelperAppService.cpp @@ -39,6 +39,18 @@ nsresult nsOSHelperAppService::OSProtocolHandlerExists(const char* aScheme, return NS_ERROR_NOT_IMPLEMENTED; } +NS_IMETHODIMP +nsOSHelperAppService::GetApplicationDescription(const nsACString& aScheme, + nsAString& _retval) { + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +nsOSHelperAppService::IsCurrentAppOSDefaultForProtocol( + const nsACString& aScheme, bool* _retval) { + return NS_ERROR_NOT_AVAILABLE; +} + nsresult nsOSHelperAppService::GetProtocolHandlerInfoFromOS( const nsACString& aScheme, bool* found, nsIHandlerInfo** info) { // We don't want to get protocol handlers from the OS in GV; the app diff --git a/uriloader/exthandler/android/nsOSHelperAppService.h b/uriloader/exthandler/android/nsOSHelperAppService.h index 4e9ddaf4f0bc..216061910593 100644 --- a/uriloader/exthandler/android/nsOSHelperAppService.h +++ b/uriloader/exthandler/android/nsOSHelperAppService.h @@ -24,6 +24,10 @@ class nsOSHelperAppService : public nsExternalHelperAppService { NS_IMETHOD GetProtocolHandlerInfoFromOS(const nsACString& aScheme, bool* found, nsIHandlerInfo** _retval) override; + NS_IMETHOD GetApplicationDescription(const nsACString& aScheme, + nsAString& _retval) override; + NS_IMETHOD IsCurrentAppOSDefaultForProtocol(const nsACString& aScheme, + bool* _retval) override; static nsIHandlerApp* CreateAndroidHandlerApp( const nsAString& aName, const nsAString& aDescription, diff --git a/uriloader/exthandler/mac/nsOSHelperAppService.h b/uriloader/exthandler/mac/nsOSHelperAppService.h index 9234a3f1cb00..648b286310a3 100644 --- a/uriloader/exthandler/mac/nsOSHelperAppService.h +++ b/uriloader/exthandler/mac/nsOSHelperAppService.h @@ -26,6 +26,9 @@ class nsOSHelperAppService : public nsExternalHelperAppService { NS_IMETHOD GetApplicationDescription(const nsACString& aScheme, nsAString& _retval) override; + NS_IMETHOD IsCurrentAppOSDefaultForProtocol(const nsACString& aScheme, + bool* _retval) override; + nsresult GetMIMEInfoFromOS(const nsACString& aMIMEType, const nsACString& aFileExt, bool* aFound, nsIMIMEInfo** aMIMEInfo) override; diff --git a/uriloader/exthandler/mac/nsOSHelperAppService.mm b/uriloader/exthandler/mac/nsOSHelperAppService.mm index 3bc8fc62ac85..14a300b89fc8 100644 --- a/uriloader/exthandler/mac/nsOSHelperAppService.mm +++ b/uriloader/exthandler/mac/nsOSHelperAppService.mm @@ -31,6 +31,49 @@ #define HELPERAPPLAUNCHER_BUNDLE_URL "chrome://global/locale/helperAppLauncher.properties" #define BRAND_BUNDLE_URL "chrome://branding/locale/brand.properties" +nsresult GetDefaultBundleURL(const nsACString& aScheme, CFURLRef* aBundleURL) { + 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) { + if (@available(macOS 10.10, *)) { + *aBundleURL = ::LSCopyDefaultApplicationURLForURL(lookupCFURL, kLSRolesAll, NULL); + if (*aBundleURL) { + rv = NS_OK; + } + } else { + OSStatus theErr = ::LSGetApplicationForURL(lookupCFURL, kLSRolesAll, NULL, aBundleURL); + if (theErr == noErr && *aBundleURL) { + rv = NS_OK; + } + } + + ::CFRelease(lookupCFURL); + } + + ::CFRelease(lookupCFString); + } + + ::CFRelease(schemeCFString); + } + + return rv; + + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; +} + using mozilla::LogLevel; /* This is an undocumented interface (in the Foundation framework) that has @@ -91,52 +134,57 @@ NS_IMETHODIMP nsOSHelperAppService::GetApplicationDescription(const nsACString& nsresult rv = NS_ERROR_NOT_AVAILABLE; - CFStringRef schemeCFString = ::CFStringCreateWithBytes( - kCFAllocatorDefault, (const UInt8*)PromiseFlatCString(aScheme).get(), aScheme.Length(), - kCFStringEncodingUTF8, false); + CFURLRef handlerBundleURL; + rv = GetDefaultBundleURL(aScheme, &handlerBundleURL); - 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 buffer; - CFIndex bundleNameLength = ::CFStringGetLength(bundleName); - buffer.SetLength(bundleNameLength); - ::CFStringGetCharacters(bundleName, CFRangeMake(0, bundleNameLength), - buffer.Elements()); - _retval.Assign(reinterpret_cast(buffer.Elements()), bundleNameLength); - rv = NS_OK; - } - - ::CFRelease(handlerBundle); - } - - ::CFRelease(appCFURL); - } - - ::CFRelease(lookupCFURL); - } - - ::CFRelease(lookupCFString); + if (NS_SUCCEEDED(rv) && handlerBundleURL) { + CFBundleRef handlerBundle = CFBundleCreate(NULL, handlerBundleURL); + if (!handlerBundle) { + ::CFRelease(handlerBundleURL); + return NS_ERROR_OUT_OF_MEMORY; } - ::CFRelease(schemeCFString); + // Get the human-readable name of the bundle + CFStringRef bundleName = + (CFStringRef)::CFBundleGetValueForInfoDictionaryKey(handlerBundle, kCFBundleNameKey); + + if (bundleName) { + AutoTArray buffer; + CFIndex bundleNameLength = ::CFStringGetLength(bundleName); + buffer.SetLength(bundleNameLength); + ::CFStringGetCharacters(bundleName, CFRangeMake(0, bundleNameLength), buffer.Elements()); + _retval.Assign(reinterpret_cast(buffer.Elements()), bundleNameLength); + rv = NS_OK; + } + ::CFRelease(handlerBundle); + ::CFRelease(handlerBundleURL); + } + + return rv; + + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; +} + +NS_IMETHODIMP nsOSHelperAppService::IsCurrentAppOSDefaultForProtocol(const nsACString& aScheme, + bool* _retval) { + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; + + nsresult rv = NS_ERROR_NOT_AVAILABLE; + + CFURLRef handlerBundleURL; + rv = GetDefaultBundleURL(aScheme, &handlerBundleURL); + if (NS_SUCCEEDED(rv) && handlerBundleURL) { + // Ensure we don't accidentally return success if we can't get an app bundle. + rv = NS_ERROR_NOT_AVAILABLE; + CFBundleRef appBundle = ::CFBundleGetMainBundle(); + if (appBundle) { + CFURLRef selfURL = ::CFBundleCopyBundleURL(appBundle); + *_retval = ::CFEqual(selfURL, handlerBundleURL); + rv = NS_OK; + ::CFRelease(appBundle); + ::CFRelease(selfURL); + } + ::CFRelease(handlerBundleURL); } return rv; diff --git a/uriloader/exthandler/nsExternalHelperAppService.cpp b/uriloader/exthandler/nsExternalHelperAppService.cpp index 4f664d7f826e..98fa00b01818 100644 --- a/uriloader/exthandler/nsExternalHelperAppService.cpp +++ b/uriloader/exthandler/nsExternalHelperAppService.cpp @@ -1008,13 +1008,6 @@ nsExternalHelperAppService::LoadURI(nsIURI* aURI, 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 ////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -1101,13 +1094,6 @@ nsExternalHelperAppService::GetProtocolHandlerInfo( 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) { diff --git a/uriloader/exthandler/nsExternalHelperAppService.h b/uriloader/exthandler/nsExternalHelperAppService.h index 9ee8146556f7..a0676e24ab6d 100644 --- a/uriloader/exthandler/nsExternalHelperAppService.h +++ b/uriloader/exthandler/nsExternalHelperAppService.h @@ -44,6 +44,8 @@ class MaybeCloseWindowHelper; /** * The helper app service. Responsible for handling content that Mozilla * itself can not handle + * Note that this is an abstract class - we depend on appropriate subclassing + * on a per-OS basis to implement some methods. */ class nsExternalHelperAppService : public nsIExternalHelperAppService, public nsPIExternalAppLauncher, @@ -55,7 +57,6 @@ class nsExternalHelperAppService : public nsIExternalHelperAppService, NS_DECL_ISUPPORTS NS_DECL_NSIEXTERNALHELPERAPPSERVICE NS_DECL_NSPIEXTERNALAPPLAUNCHER - NS_DECL_NSIEXTERNALPROTOCOLSERVICE NS_DECL_NSIMIMESERVICE NS_DECL_NSIOBSERVER @@ -67,6 +68,21 @@ class nsExternalHelperAppService : public nsIExternalHelperAppService, */ MOZ_MUST_USE nsresult Init(); + /** + * nsIExternalProtocolService methods that we provide in this class. Other + * methods should be implemented by per-OS subclasses. + */ + NS_IMETHOD ExternalProtocolHandlerExists(const char* aProtocolScheme, + bool* aHandlerExists) override; + NS_IMETHOD IsExposedProtocol(const char* aProtocolScheme, + bool* aResult) override; + NS_IMETHOD GetProtocolHandlerInfo(const nsACString& aScheme, + nsIHandlerInfo** aHandlerInfo) override; + NS_IMETHOD LoadURI(nsIURI* aURI, + nsIInterfaceRequestor* aWindowContext) override; + NS_IMETHOD SetProtocolHandlerDefaults(nsIHandlerInfo* aHandlerInfo, + bool aOSHandlerExists) override; + /** * Given a string identifying an application, create an nsIFile representing * it. This function should look in $PATH for the application. diff --git a/uriloader/exthandler/nsIExternalProtocolService.idl b/uriloader/exthandler/nsIExternalProtocolService.idl index 03e9c6e25dc1..61dfd8c07fdf 100644 --- a/uriloader/exthandler/nsIExternalProtocolService.idl +++ b/uriloader/exthandler/nsIExternalProtocolService.idl @@ -123,4 +123,11 @@ interface nsIExternalProtocolService : nsISupports * possible to get a description for it. */ AString getApplicationDescription(in AUTF8String aScheme); + + /** + * Check if this app is registered as the OS default for a given scheme. + * + * @param aScheme The scheme to look up. For example, "mms". + */ + bool isCurrentAppOSDefaultForProtocol(in AUTF8String aScheme); }; diff --git a/uriloader/exthandler/nsMIMEInfoImpl.cpp b/uriloader/exthandler/nsMIMEInfoImpl.cpp index 510cee40c406..bc977c8d8d86 100644 --- a/uriloader/exthandler/nsMIMEInfoImpl.cpp +++ b/uriloader/exthandler/nsMIMEInfoImpl.cpp @@ -13,6 +13,42 @@ #include "nsEscape.h" #include "nsIURILoader.h" #include "nsCURILoader.h" +#include "nsCExternalHandlerService.h" +#include "nsIExternalProtocolService.h" +#include "mozilla/StaticPtr.h" + +static bool sInitializedOurData = false; +StaticRefPtr sOurAppFile; + +static already_AddRefed GetCanonicalExecutable(nsIFile* aFile) { + nsCOMPtr binary = aFile; +#ifdef XP_MACOSX + nsAutoString leafName; + if (binary) { + binary->GetLeafName(leafName); + } + while (binary && !StringEndsWith(leafName, NS_LITERAL_STRING(".app"))) { + nsCOMPtr parent; + binary->GetParent(getter_AddRefs(parent)); + binary = parent; + if (binary) { + binary->GetLeafName(leafName); + } + } +#endif + return binary.forget(); +} + +static void EnsureAppDetailsAvailable() { + if (sInitializedOurData) { + return; + } + sInitializedOurData = true; + nsCOMPtr binary; + XRE_GetBinaryPath(getter_AddRefs(binary)); + sOurAppFile = GetCanonicalExecutable(binary); + ClearOnShutdown(&sOurAppFile); +} // nsISupports methods NS_IMPL_ADDREF(nsMIMEInfoBase) @@ -287,12 +323,45 @@ nsMIMEInfoBase::LaunchWithURI(nsIURI* aURI, "nsMIMEInfoBase should be a protocol handler"); if (mPreferredAction == useSystemDefault) { + // First, ensure we're not accidentally going to call ourselves. + // That'd lead to an infinite loop (see bug 215554). + nsCOMPtr extProtService = + do_GetService(NS_EXTERNALPROTOCOLSERVICE_CONTRACTID); + if (!extProtService) { + return NS_ERROR_FAILURE; + } + nsAutoCString scheme; + aURI->GetScheme(scheme); + bool isDefault = false; + nsresult rv = + extProtService->IsCurrentAppOSDefaultForProtocol(scheme, &isDefault); + if (NS_SUCCEEDED(rv) && isDefault) { + // Lie. This will trip the handler service into showing a dialog asking + // what the user wants. + return NS_ERROR_FILE_NOT_FOUND; + } return LoadUriInternal(aURI); } if (mPreferredAction == useHelperApp) { if (!mPreferredApplication) return NS_ERROR_FILE_NOT_FOUND; + EnsureAppDetailsAvailable(); + nsCOMPtr localPreferredHandler = + do_QueryInterface(mPreferredApplication); + if (localPreferredHandler) { + nsCOMPtr executable; + localPreferredHandler->GetExecutable(getter_AddRefs(executable)); + executable = GetCanonicalExecutable(executable); + bool isOurExecutable = false; + if (!executable || + NS_FAILED(executable->Equals(sOurAppFile, &isOurExecutable)) || + isOurExecutable) { + // Lie. This will trip the handler service into showing a dialog asking + // what the user wants. + return NS_ERROR_FILE_NOT_FOUND; + } + } return mPreferredApplication->LaunchWithURI(aURI, aWindowContext); } diff --git a/uriloader/exthandler/nsOSHelperAppServiceChild.cpp b/uriloader/exthandler/nsOSHelperAppServiceChild.cpp index 8a0ebcdebe1d..1131eed9a30e 100644 --- a/uriloader/exthandler/nsOSHelperAppServiceChild.cpp +++ b/uriloader/exthandler/nsOSHelperAppServiceChild.cpp @@ -120,6 +120,12 @@ nsOSHelperAppServiceChild::GetProtocolHandlerInfoFromOS( return NS_ERROR_NOT_IMPLEMENTED; } +NS_IMETHODIMP +nsOSHelperAppServiceChild::IsCurrentAppOSDefaultForProtocol( + const nsACString& aScheme, bool* aRetVal) { + return NS_ERROR_NOT_IMPLEMENTED; +} + nsresult nsOSHelperAppServiceChild::GetFileTokenForPath( const char16_t* platformAppPath, nsIFile** aFile) { return NS_ERROR_NOT_IMPLEMENTED; diff --git a/uriloader/exthandler/nsOSHelperAppServiceChild.h b/uriloader/exthandler/nsOSHelperAppServiceChild.h index bf1535f8c37d..f37caf010d12 100644 --- a/uriloader/exthandler/nsOSHelperAppServiceChild.h +++ b/uriloader/exthandler/nsOSHelperAppServiceChild.h @@ -37,6 +37,8 @@ class nsOSHelperAppServiceChild : public nsExternalHelperAppService { NS_IMETHOD GetApplicationDescription(const nsACString& aScheme, nsAString& aRetVal) override; + NS_IMETHOD IsCurrentAppOSDefaultForProtocol(const nsACString& aScheme, + bool* _retval) override; NS_IMETHOD GetMIMEInfoFromOS(const nsACString& aMIMEType, const nsACString& aFileExt, bool* aFound, diff --git a/uriloader/exthandler/tests/mochitest/browser.ini b/uriloader/exthandler/tests/mochitest/browser.ini index b8e9c8937ad7..1828fee560d6 100644 --- a/uriloader/exthandler/tests/mochitest/browser.ini +++ b/uriloader/exthandler/tests/mochitest/browser.ini @@ -11,5 +11,7 @@ support-files = download.sjs [browser_download_always_ask_preferred_app.js] [browser_download_privatebrowsing.js] +[browser_protocolhandler_loop.js] +skip-if = fission # Bug 1597154 [browser_remember_download_option.js] [browser_web_protocol_handlers.js] diff --git a/uriloader/exthandler/tests/mochitest/browser_protocolhandler_loop.js b/uriloader/exthandler/tests/mochitest/browser_protocolhandler_loop.js new file mode 100644 index 000000000000..81ce6adba030 --- /dev/null +++ b/uriloader/exthandler/tests/mochitest/browser_protocolhandler_loop.js @@ -0,0 +1,82 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +add_task(async function test_helperapp() { + // Set up the test infrastructure: + const kProt = "foopydoopydoo"; + const extProtocolSvc = Cc[ + "@mozilla.org/uriloader/external-protocol-service;1" + ].getService(Ci.nsIExternalProtocolService); + const handlerSvc = Cc["@mozilla.org/uriloader/handler-service;1"].getService( + Ci.nsIHandlerService + ); + let handlerInfo = extProtocolSvc.getProtocolHandlerInfo(kProt); + if (handlerSvc.exists(handlerInfo)) { + handlerSvc.fillHandlerInfo(handlerInfo, ""); + } + // Say we want to use a specific app: + handlerInfo.preferredAction = Ci.nsIHandlerInfo.useHelperApp; + handlerInfo.alwaysAskBeforeHandling = false; + + // Say it's us: + let selfFile = Services.dirsvc.get("XREExeF", Ci.nsIFile); + // Make sure it's the .app + if (AppConstants.platform == "macosx") { + while ( + !selfFile.leafName.endsWith(".app") && + !selfFile.leafName.endsWith(".app/") + ) { + selfFile = selfFile.parent; + } + } + let selfHandlerApp = Cc[ + "@mozilla.org/uriloader/local-handler-app;1" + ].createInstance(Ci.nsILocalHandlerApp); + selfHandlerApp.executable = selfFile; + handlerInfo.possibleApplicationHandlers.appendElement(selfHandlerApp); + handlerInfo.preferredApplicationHandler = selfHandlerApp; + handlerSvc.store(handlerInfo); + + await BrowserTestUtils.withNewTab("about:blank", async browser => { + // Now, do some safety stubbing. If we do end up recursing we spawn + // infinite tabs. We definitely don't want that. Avoid it by stubbing + // our external URL handling bits: + let oldAddTab = gBrowser.addTab; + registerCleanupFunction( + () => (gBrowser.addTab = gBrowser.loadOneTab = oldAddTab) + ); + let wrongThingHappenedPromise = new Promise(resolve => { + gBrowser.addTab = gBrowser.loadOneTab = function(aURI) { + ok(false, "Tried to open unexpected URL in a tab: " + aURI); + resolve(null); + // Pass a dummy object to avoid upsetting BrowserContentHandler - + // if it thinks opening the tab failed, it tries to open a window instead, + // which we can't prevent as easily, and at which point we still end up + // with runaway tabs. + return {}; + }; + }); + // We can't use TestUtils.topicObserved because it leaks. + let askedUserPromise = new Promise(r => { + let obs = () => { + r("yes"); + Services.obs.removeObserver(obs, "domwindowopened"); + }; + Services.obs.addObserver(obs, "domwindowopened"); + }); + BrowserTestUtils.loadURI(browser, kProt + ":test"); + let win = await Promise.race([wrongThingHappenedPromise, askedUserPromise]); + ok(win, "Should have gotten a window"); + // This is really annoying. Hanging on to the window from the observer + // leaks for some reason. Just close it now. It has no window type, so use + // the lack of one to distinguish it from the browser and the harness. + for (let openWin of Services.wm.getEnumerator("")) { + if (!openWin.document.documentElement.getAttribute("windowtype")) { + openWin.close(); + } + } + askedUserPromise = null; + }); +}); diff --git a/uriloader/exthandler/uikit/nsOSHelperAppService.h b/uriloader/exthandler/uikit/nsOSHelperAppService.h index 78bea7da7404..8c1b1fb6b3d9 100644 --- a/uriloader/exthandler/uikit/nsOSHelperAppService.h +++ b/uriloader/exthandler/uikit/nsOSHelperAppService.h @@ -24,6 +24,8 @@ class nsOSHelperAppService final : public nsExternalHelperAppService { // override nsIExternalProtocolService methods NS_IMETHOD GetApplicationDescription(const nsACString& aScheme, nsAString& _retval); + NS_IMETHOD IsCurrentAppOSDefaultForProtocol(const nsACString& aScheme, + bool* _retval); // method overrides --> used to hook the mime service into internet config.... NS_IMETHOD GetFromTypeAndExtension(const nsACString& aType, diff --git a/uriloader/exthandler/uikit/nsOSHelperAppService.mm b/uriloader/exthandler/uikit/nsOSHelperAppService.mm index f4336907f406..c0eb14d28d98 100644 --- a/uriloader/exthandler/uikit/nsOSHelperAppService.mm +++ b/uriloader/exthandler/uikit/nsOSHelperAppService.mm @@ -21,6 +21,11 @@ nsOSHelperAppService::GetApplicationDescription(const nsACString& aScheme, nsASt return NS_ERROR_NOT_AVAILABLE; } +NS_IMETHODIMP +nsOSHelperAppService::IsCurrentAppOSDefaultForProtocol(const nsACString& aScheme, bool* _retval) { + return NS_ERROR_NOT_AVAILABLE; +} + nsresult nsOSHelperAppService::GetFileTokenForPath(const char16_t* aPlatformAppPath, nsIFile** aFile) { return NS_ERROR_NOT_IMPLEMENTED; diff --git a/uriloader/exthandler/unix/nsOSHelperAppService.cpp b/uriloader/exthandler/unix/nsOSHelperAppService.cpp index 7e66e1e57dd3..1b2b73d01d28 100644 --- a/uriloader/exthandler/unix/nsOSHelperAppService.cpp +++ b/uriloader/exthandler/unix/nsOSHelperAppService.cpp @@ -1057,6 +1057,12 @@ NS_IMETHODIMP nsOSHelperAppService::GetApplicationDescription( #endif } +NS_IMETHODIMP nsOSHelperAppService::IsCurrentAppOSDefaultForProtocol( + const nsACString& aScheme, bool* _retval) { + *_retval = false; + return NS_OK; +} + nsresult nsOSHelperAppService::GetFileTokenForPath( const char16_t* platformAppPath, nsIFile** aFile) { LOG(("-- nsOSHelperAppService::GetFileTokenForPath: '%s'\n", diff --git a/uriloader/exthandler/unix/nsOSHelperAppService.h b/uriloader/exthandler/unix/nsOSHelperAppService.h index 7b412f701bd2..6ef973568b5b 100644 --- a/uriloader/exthandler/unix/nsOSHelperAppService.h +++ b/uriloader/exthandler/unix/nsOSHelperAppService.h @@ -36,6 +36,8 @@ class nsOSHelperAppService : public nsExternalHelperAppService { bool* aHandlerExists) override; NS_IMETHOD GetApplicationDescription(const nsACString& aScheme, nsAString& _retval) override; + NS_IMETHOD IsCurrentAppOSDefaultForProtocol(const nsACString& aScheme, + bool* _retval) override; // GetFileTokenForPath must be implemented by each platform. // platformAppPath --> a platform specific path to an application that we got diff --git a/uriloader/exthandler/win/nsOSHelperAppService.cpp b/uriloader/exthandler/win/nsOSHelperAppService.cpp index 2c4d0302f70e..52859977be37 100644 --- a/uriloader/exthandler/win/nsOSHelperAppService.cpp +++ b/uriloader/exthandler/win/nsOSHelperAppService.cpp @@ -152,6 +152,12 @@ NS_IMETHODIMP nsOSHelperAppService::GetApplicationDescription( return NS_ERROR_NOT_AVAILABLE; } +NS_IMETHODIMP nsOSHelperAppService::IsCurrentAppOSDefaultForProtocol( + const nsACString& aScheme, bool* _retval) { + *_retval = false; + return NS_OK; +} + // 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 diff --git a/uriloader/exthandler/win/nsOSHelperAppService.h b/uriloader/exthandler/win/nsOSHelperAppService.h index 350fbea21f3a..0b5cbc21e5ec 100644 --- a/uriloader/exthandler/win/nsOSHelperAppService.h +++ b/uriloader/exthandler/win/nsOSHelperAppService.h @@ -37,6 +37,9 @@ class nsOSHelperAppService : public nsExternalHelperAppService { NS_IMETHOD GetApplicationDescription(const nsACString& aScheme, nsAString& _retval) override; + NS_IMETHOD IsCurrentAppOSDefaultForProtocol(const nsACString& aScheme, + bool* _retval) override; + // method overrides for windows registry look up steps.... NS_IMETHOD GetMIMEInfoFromOS(const nsACString& aMIMEType, const nsACString& aFileExt, bool* aFound,