Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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 "nsDirectoryServiceDefs.h"
#include "nsIImageLoadingContent.h"
#include "mozilla/dom/Document.h"
#include "nsComponentManagerUtils.h"
#include "nsIContent.h"
#include "nsICookieJarSettings.h"
#include "nsILocalFileMac.h"
#include "nsIObserverService.h"
#include "nsIPrefService.h"
#include "nsIServiceManager.h"
#include "nsIStringBundle.h"
#include "nsIURL.h"
#include "nsIWebBrowserPersist.h"
#include "nsMacShellService.h"
#include "nsIProperties.h"
#include "nsServiceManagerUtils.h"
#include "nsShellService.h"
#include "nsString.h"
#include "nsIDocShell.h"
#include "nsILoadContext.h"
#include "nsIPrefService.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/ReferrerInfo.h"
#include <ApplicationServices/ApplicationServices.h>
#define SAFARI_BUNDLE_IDENTIFIER "com.apple.Safari"
using mozilla::dom::Element;
NS_IMPL_ISUPPORTS(nsMacShellService, nsIShellService, nsIWebProgressListener)
NS_IMETHODIMP
nsMacShellService::IsDefaultClient(bool aStartupCheck, uint16_t aApps, bool *aIsDefaultClient)
{
*aIsDefaultClient = false;
if (aApps & nsIShellService::BROWSER)
if(!isDefaultHandlerForProtocol(CFSTR("http")))
return NS_OK;
if (aApps & nsIShellService::MAIL)
if(!isDefaultHandlerForProtocol(CFSTR("mailto")))
return NS_OK;
if (aApps & nsIShellService::NEWS)
if(!isDefaultHandlerForProtocol(CFSTR("news")))
return NS_OK;
if (aApps & nsIShellService::RSS)
if(!isDefaultHandlerForProtocol(CFSTR("feed")))
return NS_OK;
*aIsDefaultClient = true;
return NS_OK;
}
NS_IMETHODIMP
nsMacShellService::SetDefaultClient(bool aForAllUsers,
bool aClaimAllTypes, uint16_t aApps)
{
// Note: We don't support aForAllUsers on macOS.
CFStringRef suiteID = ::CFBundleGetIdentifier(::CFBundleGetMainBundle());
if (!suiteID)
return NS_ERROR_FAILURE;
if (aApps & nsIShellService::BROWSER)
{
if (::LSSetDefaultHandlerForURLScheme(CFSTR("http"), suiteID) != noErr)
return NS_ERROR_FAILURE;
if (::LSSetDefaultHandlerForURLScheme(CFSTR("https"), suiteID) != noErr)
return NS_ERROR_FAILURE;
if (::LSSetDefaultRoleHandlerForContentType(kUTTypeHTML, kLSRolesAll, suiteID) != noErr)
return NS_ERROR_FAILURE;
if (::LSSetDefaultRoleHandlerForContentType(CFSTR("public.xhtml"), kLSRolesAll, suiteID) != noErr)
return NS_ERROR_FAILURE;
}
if (aApps & nsIShellService::MAIL)
if (::LSSetDefaultHandlerForURLScheme(CFSTR("mailto"), suiteID) != noErr)
return NS_ERROR_FAILURE;
if (aApps & nsIShellService::NEWS)
if (::LSSetDefaultHandlerForURLScheme(CFSTR("news"), suiteID) != noErr)
return NS_ERROR_FAILURE;
if (aApps & nsIShellService::RSS)
if (::LSSetDefaultHandlerForURLScheme(CFSTR("feed"), suiteID) != noErr)
return NS_ERROR_FAILURE;
return NS_OK;
}
bool
nsMacShellService::isDefaultHandlerForProtocol(CFStringRef aScheme)
{
bool isDefault = false;
CFStringRef suiteID = ::CFBundleGetIdentifier(::CFBundleGetMainBundle());
if (!suiteID)
{
// CFBundleGetIdentifier is expected to return nullptr only if the specified
// bundle doesn't have a bundle identifier in its dictionary. In this case,
// that means a failure, since our bundle does have an identifier.
return isDefault;
}
// Get the default handler's bundle ID for the scheme.
CFStringRef defaultHandlerID = ::LSCopyDefaultHandlerForURLScheme(aScheme);
if (defaultHandlerID)
{
// The handler ID in LaunchServices is in all lower case, but the bundle
// identifier could have upper case characters. So we're using
// CFStringCompare with the kCFCompareCaseInsensitive option here.
isDefault = ::CFStringCompare(suiteID, defaultHandlerID,
kCFCompareCaseInsensitive) == kCFCompareEqualTo;
::CFRelease(defaultHandlerID);
}
return isDefault;
}
NS_IMETHODIMP
nsMacShellService::SetDesktopBackground(Element* aElement,
int32_t aPosition,
const nsACString& aImageName)
{
// Note: We don't support aPosition on OS X.
// Get the image URI:
nsresult rv;
nsCOMPtr<nsIImageLoadingContent> imageContent = do_QueryInterface(aElement, &rv);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIURI> imageURI;
rv = imageContent->GetCurrentURI(getter_AddRefs(imageURI));
NS_ENSURE_SUCCESS(rv, rv);
nsIURI *docURI = aElement->OwnerDoc()->GetDocumentURI();
if (!docURI)
return NS_ERROR_FAILURE;
nsCOMPtr<nsIProperties> fileLocator
(do_GetService("@mozilla.org/file/directory_service;1", &rv));
NS_ENSURE_SUCCESS(rv, rv);
// Get the current user's "Pictures" folder (That's ~/Pictures):
fileLocator->Get(NS_OSX_PICTURE_DOCUMENTS_DIR, NS_GET_IID(nsIFile),
getter_AddRefs(mBackgroundFile));
if (!mBackgroundFile)
return NS_ERROR_OUT_OF_MEMORY;
nsAutoString fileNameUnicode;
CopyUTF8toUTF16(aImageName, fileNameUnicode);
// and add the image file name itself:
mBackgroundFile->Append(fileNameUnicode);
// Download the image; the desktop background will be set in OnStateChange():
nsCOMPtr<nsIWebBrowserPersist> wbp
(do_CreateInstance("@mozilla.org/embedding/browser/nsWebBrowserPersist;1", &rv));
NS_ENSURE_SUCCESS(rv, rv);
uint32_t flags = nsIWebBrowserPersist::PERSIST_FLAGS_NO_CONVERSION |
nsIWebBrowserPersist::PERSIST_FLAGS_REPLACE_EXISTING_FILES |
nsIWebBrowserPersist::PERSIST_FLAGS_FROM_CACHE;
wbp->SetPersistFlags(flags);
wbp->SetProgressListener(this);
nsCOMPtr<nsILoadContext> loadContext;
nsCOMPtr<nsISupports> container = aElement->OwnerDoc()->GetContainer();
nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(container);
if (docShell)
{
loadContext = do_QueryInterface(docShell);
}
auto referrerInfo = mozilla::MakeRefPtr<mozilla::dom::ReferrerInfo>(*aElement);
nsCOMPtr<nsICookieJarSettings> cookieJarSettings =
aElement->OwnerDoc()->CookieJarSettings();
return wbp->SaveURI(imageURI, aElement->NodePrincipal(), 0, referrerInfo,
cookieJarSettings, nullptr, nullptr, mBackgroundFile,
nsIContentPolicy::TYPE_IMAGE, loadContext);
}
NS_IMETHODIMP
nsMacShellService::OnProgressChange(nsIWebProgress* aWebProgress,
nsIRequest* aRequest,
int32_t aCurSelfProgress,
int32_t aMaxSelfProgress,
int32_t aCurTotalProgress,
int32_t aMaxTotalProgress)
{
return NS_OK;
}
NS_IMETHODIMP
nsMacShellService::OnLocationChange(nsIWebProgress* aWebProgress,
nsIRequest* aRequest,
nsIURI* aLocation,
uint32_t aFlags)
{
return NS_OK;
}
NS_IMETHODIMP
nsMacShellService::OnStatusChange(nsIWebProgress* aWebProgress,
nsIRequest* aRequest,
nsresult aStatus,
const char16_t* aMessage)
{
return NS_OK;
}
NS_IMETHODIMP
nsMacShellService::OnSecurityChange(nsIWebProgress* aWebProgress,
nsIRequest* aRequest,
uint32_t aState)
{
return NS_OK;
}
NS_IMETHODIMP
nsMacShellService::OnContentBlockingEvent(nsIWebProgress* aWebProgress,
nsIRequest* aRequest,
uint32_t aEvent) {
return NS_OK;
}
NS_IMETHODIMP
nsMacShellService::OnStateChange(nsIWebProgress* aWebProgress,
nsIRequest* aRequest,
uint32_t aStateFlags,
nsresult aStatus)
{
if (aStateFlags & STATE_STOP)
{
bool exists = false;
mBackgroundFile->Exists(&exists);
if (!exists)
return NS_OK;
nsAutoCString nativePath;
mBackgroundFile->GetNativePath(nativePath);
AEDesc tAEDesc = { typeNull, nil };
OSErr err = noErr;
AliasHandle aliasHandle = nil;
FSRef pictureRef;
OSStatus status;
// Convert the path into a FSRef:
status = ::FSPathMakeRef((const UInt8*)nativePath.get(), &pictureRef,
nullptr);
if (status == noErr)
{
err = ::FSNewAlias(nil, &pictureRef, &aliasHandle);
if (err == noErr && aliasHandle == nil)
err = paramErr;
if (err == noErr)
{
// We need the descriptor (based on the picture file reference)
// for the 'Set Desktop Picture' apple event.
char handleState = ::HGetState((Handle)aliasHandle);
::HLock((Handle)aliasHandle);
err = ::AECreateDesc(typeAlias, *aliasHandle,
GetHandleSize((Handle)aliasHandle), &tAEDesc);
// Unlock the alias handler:
::HSetState((Handle)aliasHandle, handleState);
::DisposeHandle((Handle)aliasHandle);
}
if (err == noErr)
{
AppleEvent tAppleEvent;
OSType sig = 'MACS';
AEBuildError tAEBuildError;
// Create a 'Set Desktop Picture' Apple Event:
err = ::AEBuildAppleEvent(kAECoreSuite, kAESetData, typeApplSignature,
&sig, sizeof(OSType), kAutoGenerateReturnID,
kAnyTransactionID, &tAppleEvent, &tAEBuildError,
"'----':'obj '{want:type (prop),form:prop" \
",seld:type('dpic'),from:'null'()},data:(@)",
&tAEDesc);
if (err == noErr)
{
AppleEvent reply = { typeNull, nil };
// Send the event we built, the reply event isn't necessary:
err = ::AESend(&tAppleEvent, &reply, kAENoReply, kAENormalPriority,
kNoTimeOut, nil, nil);
::AEDisposeDesc(&tAppleEvent);
}
}
}
}
return NS_OK;
}
NS_IMETHODIMP
nsMacShellService::GetDesktopBackgroundColor(uint32_t *aColor)
{
// This method and |SetDesktopBackgroundColor| has no meaning on macOS.
// The mac desktop preferences UI uses pictures for the few solid colors it
// supports.
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
nsMacShellService::SetDesktopBackgroundColor(uint32_t aColor)
{
// This method and |GetDesktopBackgroundColor| has no meaning on macOS.
// The mac desktop preferences UI uses pictures for the few solid colors it
// supports.
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
nsMacShellService::OpenApplicationWithURI(nsIFile* aApplication, const nsACString& aURI)
{
nsCOMPtr<nsILocalFileMac> lfm(do_QueryInterface(aApplication));
CFURLRef appURL;
nsresult rv = lfm->GetCFURL(&appURL);
if (NS_FAILED(rv))
return rv;
const nsCString& spec = PromiseFlatCString(aURI);
const UInt8* uriString = (const UInt8*)spec.get();
CFURLRef uri = ::CFURLCreateWithBytes(nullptr, uriString, aURI.Length(),
kCFStringEncodingUTF8, nullptr);
if (!uri)
return NS_ERROR_OUT_OF_MEMORY;
CFArrayRef uris = ::CFArrayCreate(nullptr, (const void**)&uri, 1, nullptr);
if (!uris)
{
::CFRelease(uri);
return NS_ERROR_OUT_OF_MEMORY;
}
LSLaunchURLSpec launchSpec;
launchSpec.appURL = appURL;
launchSpec.itemURLs = uris;
launchSpec.passThruParams = nullptr;
launchSpec.launchFlags = kLSLaunchDefaults;
launchSpec.asyncRefCon = nullptr;
OSErr err = ::LSOpenFromURLSpec(&launchSpec, nullptr);
::CFRelease(uris);
::CFRelease(uri);
return err != noErr ? NS_ERROR_FAILURE : NS_OK;
}
NS_IMETHODIMP
nsMacShellService::GetDefaultFeedReader(nsIFile** _retval)
{
nsresult rv = NS_ERROR_FAILURE;
*_retval = nullptr;
CFStringRef defaultHandlerID = ::LSCopyDefaultHandlerForURLScheme(CFSTR("feed"));
if (!defaultHandlerID)
{
defaultHandlerID = ::CFStringCreateWithCString(kCFAllocatorDefault,
SAFARI_BUNDLE_IDENTIFIER,
kCFStringEncodingASCII);
}
CFURLRef defaultHandlerURL = nullptr;
OSStatus status = ::LSFindApplicationForInfo(kLSUnknownCreator,
defaultHandlerID,
nullptr, // inName
nullptr, // outAppRef
&defaultHandlerURL);
if (status == noErr && defaultHandlerURL)
{
nsCOMPtr<nsILocalFileMac> defaultReader =
do_CreateInstance("@mozilla.org/file/local;1", &rv);
if (NS_SUCCEEDED(rv))
{
rv = defaultReader->InitWithCFURL(defaultHandlerURL);
if (NS_SUCCEEDED(rv))
{
NS_ADDREF(*_retval = defaultReader);
}
}
::CFRelease(defaultHandlerURL);
}
::CFRelease(defaultHandlerID);
return rv;
}