Bug 1966586 - Reuse other browser windows when opening _blank links in Taskbar Tabs windows. r=nrishel
This doesn't affect other tab additions, nor does it stop the tab bar
from appearing altogether. The idea is that _if_ another tab is somehow
made, the user should see it; but we should not create new tabs if we
can avoid it.
This also adds tests for opening URIs in popups and taskbar tabs to make
it less likely that this breaks in future.
Differential Revision: https://phabricator.services.mozilla.com/D253726
/* 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"DownloadPlatform.h"#include"nsNetUtil.h"#include"nsString.h"#include"nsINestedURI.h"#include"nsIProtocolHandler.h"#include"nsIURI.h"#include"nsIFile.h"#include"xpcpublic.h"#include"mozilla/dom/Promise.h"#include"mozilla/Preferences.h"#define PREF_BDM_ADDTORECENTDOCS "browser.download.manager.addToRecentDocs"#ifdef XP_WIN# include <shlobj.h># include <urlmon.h># include "nsILocalFileWin.h"# include "WinTaskbar.h"#endif#ifdef XP_MACOSX# include <CoreFoundation/CoreFoundation.h># include "../../../xpcom/io/CocoaFileUtils.h"#endif#ifdef MOZ_WIDGET_GTK# include <gtk/gtk.h>#endifusingnamespacemozilla;usingdom::Promise;DownloadPlatform*DownloadPlatform::gDownloadPlatformService=nullptr;NS_IMPL_ISUPPORTS(DownloadPlatform,mozIDownloadPlatform);DownloadPlatform*DownloadPlatform::GetDownloadPlatform(){if(!gDownloadPlatformService){gDownloadPlatformService=newDownloadPlatform();}NS_ADDREF(gDownloadPlatformService);returngDownloadPlatformService;}#ifdef MOZ_WIDGET_GTKstaticvoidgio_set_metadata_done(GObject*source_obj,GAsyncResult*res,gpointeruser_data){GError*err=nullptr;g_file_set_attributes_finish(G_FILE(source_obj),res,nullptr,&err);if(err){# ifdef DEBUGNS_DebugBreak(NS_DEBUG_WARNING,"Set file metadata failed: ",err->message,__FILE__,__LINE__);# endifg_error_free(err);}}#endif#ifdef XP_MACOSX// Caller is responsible for freeing any result (CF Create Rule)CFURLRefCreateCFURLFromNSIURI(nsIURI*aURI){nsAutoCStringspec;if(aURI){aURI->GetSpec(spec);}CFStringRefurlStr=::CFStringCreateWithCString(kCFAllocatorDefault,spec.get(),kCFStringEncodingUTF8);if(!urlStr){returnNULL;}CFURLRefurl=::CFURLCreateWithString(kCFAllocatorDefault,urlStr,NULL);::CFRelease(urlStr);returnurl;}#endif#ifdef XP_WINstaticvoidAddToRecentDocs(nsIFile*aTarget,nsAutoString&aPath){nsStringmodelId;if(mozilla::widget::WinTaskbar::GetAppUserModelID(modelId)){nsCOMPtr<nsIURI>uri;if(NS_SUCCEEDED(NS_NewFileURI(getter_AddRefs(uri),aTarget))&&uri){nsCStringspec;if(NS_SUCCEEDED(uri->GetSpec(spec))){IShellItem2*psi=nullptr;if(SUCCEEDED(SHCreateItemFromParsingName(NS_ConvertASCIItoUTF16(spec).get(),nullptr,IID_PPV_ARGS(&psi)))){SHARDAPPIDINFOinfo={psi,modelId.get()};::SHAddToRecentDocs(SHARD_APPIDINFO,&info);psi->Release();return;}}}}::SHAddToRecentDocs(SHARD_PATHW,aPath.get());}#endifnsresultDownloadPlatform::DownloadDone(nsIURI*aSource,nsIURI*aReferrer,nsIFile*aTarget,constnsACString&aContentType,boolaIsPrivate,JSContext*aCx,Promise**aPromise){nsIGlobalObject*globalObject=xpc::NativeGlobal(JS::CurrentGlobalOrNull(aCx));if(NS_WARN_IF(!globalObject)){returnNS_ERROR_FAILURE;}ErrorResultresult;RefPtr<Promise>promise=Promise::Create(globalObject,result);if(NS_WARN_IF(result.Failed())){returnresult.StealNSResult();}nsresultrv=NS_OK;boolpendingAsyncOperations=false;#if defined(XP_WIN) || defined(XP_MACOSX) || defined(MOZ_WIDGET_ANDROID) || \ defined(MOZ_WIDGET_GTK)nsAutoStringpath;if(aTarget&&NS_SUCCEEDED(aTarget->GetPath(path))){# if defined(XP_WIN) || defined(MOZ_WIDGET_GTK) || defined(MOZ_WIDGET_ANDROID)// On Windows and Gtk, add the download to the system's "recent documents"// list, with a pref to disable.{# ifndef MOZ_WIDGET_ANDROIDbooladdToRecentDocs=Preferences::GetBool(PREF_BDM_ADDTORECENTDOCS);if(addToRecentDocs&&!aIsPrivate){# ifdef XP_WINAddToRecentDocs(aTarget,path);# elif defined(MOZ_WIDGET_GTK)GtkRecentManager*manager=gtk_recent_manager_get_default();gchar*uri=g_filename_to_uri(NS_ConvertUTF16toUTF8(path).get(),nullptr,nullptr);if(uri){gtk_recent_manager_add_item(manager,uri);g_free(uri);}# endif}# endif# ifdef MOZ_WIDGET_GTK// Private window should not leak URI to the system (Bug 1535950)if(!aIsPrivate){// Use GIO to store the source URI for later display in the file// manager.GFile*gio_file=g_file_new_for_path(NS_ConvertUTF16toUTF8(path).get());nsCStringsource_uri;nsresultrv=aSource->GetSpec(source_uri);NS_ENSURE_SUCCESS(rv,rv);GFileInfo*file_info=g_file_info_new();g_file_info_set_attribute_string(file_info,"metadata::download-uri",source_uri.get());g_file_set_attributes_async(gio_file,file_info,G_FILE_QUERY_INFO_NONE,G_PRIORITY_DEFAULT,nullptr,gio_set_metadata_done,nullptr);g_object_unref(file_info);g_object_unref(gio_file);}# endif}# endif# ifdef XP_MACOSX// On OS X, make the downloads stack bounce.CFStringRefobservedObject=::CFStringCreateWithCString(kCFAllocatorDefault,NS_ConvertUTF16toUTF8(path).get(),kCFStringEncodingUTF8);CFNotificationCenterRefcenter=::CFNotificationCenterGetDistributedCenter();::CFNotificationCenterPostNotification(center,CFSTR("com.apple.DownloadFileFinished"),observedObject,nullptr,TRUE);::CFRelease(observedObject);// Add OS X origin and referrer file metadataCFStringRefpathCFStr=NULL;if(!path.IsEmpty()){pathCFStr=::CFStringCreateWithCharacters(kCFAllocatorDefault,(constUniChar*)path.get(),path.Length());}if(pathCFStr&&!aIsPrivate){boolisFromWeb=IsURLPossiblyFromWeb(aSource);nsCOMPtr<nsIURI>source(aSource);nsCOMPtr<nsIURI>referrer(aReferrer);rv=NS_DispatchBackgroundTask(NS_NewRunnableFunction("DownloadPlatform::DownloadDone",[pathCFStr,isFromWeb,source,referrer,promise]()mutable{CFURLRefsourceCFURL=CreateCFURLFromNSIURI(source);CFURLRefreferrerCFURL=CreateCFURLFromNSIURI(referrer);CocoaFileUtils::AddOriginMetadataToFile(pathCFStr,sourceCFURL,referrerCFURL);CocoaFileUtils::AddQuarantineMetadataToFile(pathCFStr,sourceCFURL,referrerCFURL,isFromWeb);::CFRelease(pathCFStr);if(sourceCFURL){::CFRelease(sourceCFURL);}if(referrerCFURL){::CFRelease(referrerCFURL);}DebugOnly<nsresult>rv=NS_DispatchToMainThread(NS_NewRunnableFunction("DownloadPlatform::DownloadDoneResolve",[promise=std::move(promise)](){promise->MaybeResolveWithUndefined();}));MOZ_ASSERT(NS_SUCCEEDED(rv));// In non-debug builds, if we've for some reason failed to// dispatch a runnable to the main thread to resolve the// Promise, then it's unlikely we can reject it either. At that// point, the Promise is going to remain in pending limbo until// its global goes away.}),NS_DISPATCH_EVENT_MAY_BLOCK);if(NS_SUCCEEDED(rv)){pendingAsyncOperations=true;}}# endif}#endifif(!pendingAsyncOperations){promise->MaybeResolveWithUndefined();}promise.forget(aPromise);returnrv;}nsresultDownloadPlatform::MapUrlToZone(constnsAString&aURL,uint32_t*aZone){#ifdef XP_WINRefPtr<IInternetSecurityManager>inetSecMgr;if(FAILED(CoCreateInstance(CLSID_InternetSecurityManager,NULL,CLSCTX_ALL,IID_IInternetSecurityManager,getter_AddRefs(inetSecMgr)))){returnNS_ERROR_UNEXPECTED;}DWORDzone;if(inetSecMgr->MapUrlToZone(PromiseFlatString(aURL).get(),&zone,0)!=S_OK){returnNS_ERROR_UNEXPECTED;}else{*aZone=zone;}returnNS_OK;#elsereturnNS_ERROR_NOT_IMPLEMENTED;#endif}// Check if a URI is likely to be web-based, by checking its URI flags.// If in doubt (e.g. if anything fails during the check) claims things// are from the web.boolDownloadPlatform::IsURLPossiblyFromWeb(nsIURI*aURI){nsCOMPtr<nsIIOService>ios=do_GetIOService();nsCOMPtr<nsIURI>uri=aURI;if(!ios){returntrue;}while(uri){// We're not using NS_URIChainHasFlags because we're checking for *any* of 3// flags to be present on *all* of the nested URIs, which it can't do.uint32_tflags;nsresultrv=ios->GetDynamicProtocolFlags(uri,&flags);if(NS_FAILED(rv)){returntrue;}// If not dangerous to load, not a UI resource and not a local file,// assume this is from the web:if(!(flags&nsIProtocolHandler::URI_DANGEROUS_TO_LOAD)&&!(flags&nsIProtocolHandler::URI_IS_UI_RESOURCE)&&!(flags&nsIProtocolHandler::URI_IS_LOCAL_FILE)){returntrue;}// Otherwise, check if the URI is nested, and if so go through// the loop again:nsCOMPtr<nsINestedURI>nestedURI=do_QueryInterface(uri);uri=nullptr;if(nestedURI){rv=nestedURI->GetInnerURI(getter_AddRefs(uri));if(NS_FAILED(rv)){returntrue;}}}returnfalse;}