Bug 1565597 - Mozilla Remoting implementation for macOS, r=mossop

Differential Revision: https://phabricator.services.mozilla.com/D56997

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Yuri 2019-12-13 09:20:24 +00:00
Родитель 35f21af04d
Коммит 9da62d84ad
15 изменённых файлов: 288 добавлений и 205 удалений

Просмотреть файл

@ -3,11 +3,20 @@
* 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 RemoteUtils_h__
#define RemoteUtils_h__
#ifndef TOOLKIT_COMPONENTS_REMOTE_REMOTEUTILS_H_
#define TOOLKIT_COMPONENTS_REMOTE_REMOTEUTILS_H_
#include "nsString.h"
#if defined XP_WIN || defined XP_MACOSX
static void BuildClassName(const char* aProgram, const char* aProfile,
nsString& aClassName) {
aClassName.AppendPrintf("Mozilla_%s_%s_RemoteWindow", aProgram, aProfile);
}
#endif
char* ConstructCommandLine(int32_t argc, char** argv,
const char* aDesktopStartupID,
int* aCommandLineLength);
#endif // RemoteUtils_h__
#endif // TOOLKIT_COMPONENTS_REMOTE_REMOTEUTILS_H_

Просмотреть файл

@ -32,8 +32,14 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
'nsWinRemoteClient.cpp',
'nsWinRemoteServer.cpp',
]
if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
SOURCES += [
'nsMacRemoteClient.mm',
'nsMacRemoteServer.mm',
]
LOCAL_INCLUDES += [
'../../profile',
'../../xre',
]
FINAL_LIBRARY = 'xul'

Просмотреть файл

@ -0,0 +1,28 @@
/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:expandtab:shiftwidth=4:tabstop=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 TOOLKIT_COMPONENTS_REMOTE_NSMACREMOTECLIENT_H_
#define TOOLKIT_COMPONENTS_REMOTE_NSMACREMOTECLIENT_H_
#import <CoreFoundation/CoreFoundation.h>
#include "nscore.h"
#include "nsRemoteClient.h"
class nsMacRemoteClient : public nsRemoteClient {
public:
virtual ~nsMacRemoteClient() = default;
nsresult Init() override;
nsresult SendCommandLine(const char* aProgram, const char* aProfile,
int32_t argc, char** argv,
const char* aDesktopStartupID, char** aResponse,
bool* aSucceeded) override;
};
#endif // TOOLKIT_COMPONENTS_REMOTE_NSMACREMOTECLIENT_H_

Просмотреть файл

@ -0,0 +1,62 @@
/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:expandtab:shiftwidth=4:tabstop=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/. */
#import <CoreFoundation/CoreFoundation.h>
#import <Foundation/Foundation.h>
#include <sys/param.h>
#include "MacAutoreleasePool.h"
#include "nsMacRemoteClient.h"
#include "RemoteUtils.h"
using namespace mozilla;
nsresult nsMacRemoteClient::Init() { return NS_OK; }
nsresult nsMacRemoteClient::SendCommandLine(const char* aProgram, const char* aProfile,
int32_t argc, char** argv,
const char* aDesktopStartupID, char** aResponse,
bool* aSucceeded) {
mozilla::MacAutoreleasePool pool;
*aSucceeded = false;
nsString className;
BuildClassName(aProgram, aProfile, className);
NSString* serverNameString =
[NSString stringWithCharacters:reinterpret_cast<const unichar*>(className.get())
length:className.Length()];
CFMessagePortRef messageServer = CFMessagePortCreateRemote(0, (CFStringRef)serverNameString);
if (messageServer) {
// Getting current process directory
char cwdPtr[MAXPATHLEN + 1];
getcwd(cwdPtr, MAXPATHLEN + 1);
NSMutableArray* argumentsArray = [NSMutableArray array];
for (int i = 0; i < argc; i++) {
NSString* argument = [NSString stringWithUTF8String:argv[i]];
[argumentsArray addObject:argument];
}
NSDictionary* dict = @{@"args" : argumentsArray};
NSData* data = [NSKeyedArchiver archivedDataWithRootObject:dict];
CFMessagePortSendRequest(messageServer, 0, (CFDataRef)data, 10.0, 0.0, NULL, NULL);
CFMessagePortInvalidate(messageServer);
CFRelease(messageServer);
*aSucceeded = true;
} else {
// Remote Server not found. Doing nothing.
}
return NS_OK;
}

Просмотреть файл

@ -0,0 +1,30 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:expandtab:shiftwidth=2:tabstop=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 TOOLKIT_COMPONENTS_REMOTE_NSMACREMOTESERVER_H_
#define TOOLKIT_COMPONENTS_REMOTE_NSMACREMOTESERVER_H_
#import <CoreFoundation/CoreFoundation.h>
#include "nsRemoteServer.h"
class nsMacRemoteServer final : public nsRemoteServer {
public:
nsMacRemoteServer() = default;
~nsMacRemoteServer() override { Shutdown(); }
nsresult Startup(const char* aAppName, const char* aProfileName) override;
void Shutdown() override;
void HandleCommandLine(CFDataRef aData);
private:
CFRunLoopSourceRef mRunLoopSource;
CFMessagePortRef mMessageServer;
};
#endif // TOOLKIT_COMPONENTS_REMOTE_NSMACREMOTESERVER_H_

Просмотреть файл

@ -0,0 +1,134 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:expandtab:shiftwidth=2:tabstop=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 <Cocoa/Cocoa.h>
#import <CoreServices/CoreServices.h>
#include "MacAutoreleasePool.h"
#include "nsCOMPtr.h"
#include "nsIComponentManager.h"
#include "nsIServiceManager.h"
#include "nsIWindowMediator.h"
#include "nsIWidget.h"
#include "nsICommandLineRunner.h"
#include "nsICommandLine.h"
#include "nsCommandLine.h"
#include "nsIDocShell.h"
#include "nsMacRemoteServer.h"
#include "nsXPCOM.h"
#include "RemoteUtils.h"
CFDataRef messageServerCallback(CFMessagePortRef aLocal, int32_t aMsgid, CFDataRef aData,
void* aInfo) {
// One of the clients submitted a structure.
static_cast<nsMacRemoteServer*>(aInfo)->HandleCommandLine(aData);
return NULL;
}
// aData contains serialized Dictionary, which in turn contains command line arguments
void nsMacRemoteServer::HandleCommandLine(CFDataRef aData) {
mozilla::MacAutoreleasePool pool;
if (aData) {
NSDictionary* dict = [NSKeyedUnarchiver unarchiveObjectWithData:(NSData*)aData];
if (dict && [dict isKindOfClass:[NSDictionary class]]) {
NSArray* args = dict[@"args"];
if (!args) {
NS_ERROR("Wrong parameters passed to the Remote Server");
return;
}
nsCOMPtr<nsICommandLineRunner> cmdLine(new nsCommandLine());
// Converting Objective-C array into a C array,
// which nsICommandLineRunner understands.
int argc = [args count];
const char** argv = new const char*[argc];
for (int i = 0; i < argc; i++) {
const char* arg = [[args objectAtIndex:i] UTF8String];
argv[i] = arg;
}
nsresult rv = cmdLine->Init(argc, argv, nullptr, nsICommandLine::STATE_REMOTE_AUTO);
// Cleaning up C array.
delete[] argv;
if (NS_FAILED(rv)) {
NS_ERROR("Error initializing command line.");
return;
}
// Processing the command line, passed from a remote instance
// in the current instance.
cmdLine->Run();
// And bring the app's window to front.
[[NSRunningApplication currentApplication]
activateWithOptions:NSApplicationActivateIgnoringOtherApps];
}
}
}
nsresult nsMacRemoteServer::Startup(const char* aAppName, const char* aProfileName) {
// This is the first instance ever.
// Let's register a notification listener here,
// In case future instances would want to notify us about command line arguments
// passed to them. Note, that if mozilla process is restarting, we still need to
// register for notifications.
mozilla::MacAutoreleasePool pool;
nsString className;
BuildClassName(aAppName, aProfileName, className);
NSString* serverNameString =
[NSString stringWithCharacters:reinterpret_cast<const unichar*>(className.get())
length:className.Length()];
CFMessagePortContext context;
context.copyDescription = NULL;
context.info = this;
context.release = NULL;
context.retain = NULL;
context.version = NULL;
mMessageServer = CFMessagePortCreateLocal(NULL, (CFStringRef)serverNameString,
messageServerCallback, &context, NULL);
if (!mMessageServer) {
return NS_ERROR_FAILURE;
}
mRunLoopSource = CFMessagePortCreateRunLoopSource(NULL, mMessageServer, 0);
if (!mRunLoopSource) {
CFRelease(mMessageServer);
mMessageServer = NULL;
return NS_ERROR_FAILURE;
}
CFRunLoopRef runLoop = CFRunLoopGetMain();
CFRunLoopAddSource(runLoop, mRunLoopSource, kCFRunLoopDefaultMode);
return NS_OK;
}
void nsMacRemoteServer::Shutdown() {
// 1) Invalidate server connection
if (mMessageServer) {
CFMessagePortInvalidate(mMessageServer);
}
// 2) Release run loop source
if (mRunLoopSource) {
CFRelease(mRunLoopSource);
mRunLoopSource = NULL;
}
// 3) Release server connection
if (mMessageServer) {
CFRelease(mMessageServer);
mMessageServer = NULL;
}
}

Просмотреть файл

@ -5,8 +5,8 @@
* 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 nsRemoteClient_h__
#define nsRemoteClient_h__
#ifndef TOOLKIT_COMPONENTS_REMOTE_NSREMOTECLIENT_H_
#define TOOLKIT_COMPONENTS_REMOTE_NSREMOTECLIENT_H_
#include "nscore.h"
@ -59,4 +59,4 @@ class nsRemoteClient {
char** aResponse, bool* aSucceeded) = 0;
};
#endif // nsRemoteClient_h__
#endif // TOOLKIT_COMPONENTS_REMOTE_NSREMOTECLIENT_H_

Просмотреть файл

@ -20,6 +20,9 @@
#elif defined(XP_WIN)
# include "nsWinRemoteServer.h"
# include "nsWinRemoteClient.h"
#elif defined(XP_DARWIN)
# include "nsMacRemoteServer.h"
# include "nsMacRemoteClient.h"
#endif
#include "nsRemoteService.h"
@ -107,6 +110,8 @@ RemoteResult nsRemoteService::StartClient(const char* aDesktopStartupID) {
}
#elif defined(XP_WIN)
client = new nsWinRemoteClient();
#elif defined(XP_DARWIN)
client = new nsMacRemoteClient();
#else
return REMOTE_NOT_FOUND;
#endif
@ -154,6 +159,8 @@ void nsRemoteService::StartupServer() {
}
#elif defined(XP_WIN)
mRemoteServer = MakeUnique<nsWinRemoteServer>();
#elif defined(XP_DARWIN)
mRemoteServer = MakeUnique<nsMacRemoteServer>();
#else
return;
#endif

Просмотреть файл

@ -5,8 +5,8 @@
* 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 __nsRemoteService_h__
#define __nsRemoteService_h__
#ifndef TOOLKIT_COMPONENTS_REMOTE_NSREMOTESERVER_H_
#define TOOLKIT_COMPONENTS_REMOTE_NSREMOTESERVER_H_
#include "nsRemoteServer.h"
#include "nsIObserver.h"
@ -46,4 +46,4 @@ class nsRemoteService final : public nsIObserver {
nsCString mProfile;
};
#endif // __nsRemoteService_h__
#endif // TOOLKIT_COMPONENTS_REMOTE_NSREMOTESERVER_H_

Просмотреть файл

@ -6,9 +6,8 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsWinRemoteClient.h"
#include "nsWinRemoteUtils.h"
#include <windows.h>
#include "RemoteUtils.h"
using namespace mozilla;

Просмотреть файл

@ -11,10 +11,6 @@
#include "nscore.h"
#include "nsRemoteClient.h"
/**
* Pure-virtual common base class for remoting implementations.
*/
class nsWinRemoteClient : public nsRemoteClient {
public:
virtual ~nsWinRemoteClient() = default;

Просмотреть файл

@ -6,7 +6,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsWinRemoteServer.h"
#include "nsWinRemoteUtils.h"
#include "RemoteUtils.h"
#include "nsCOMPtr.h"
#include "nsXPCOM.h"
#include "nsPIDOMWindow.h"

Просмотреть файл

@ -1,18 +0,0 @@
/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:expandtab:shiftwidth=4:tabstop=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 nsWinRemoteUtils_h__
#define nsWinRemoteUtils_h__
#include "nsString.h"
static void BuildClassName(const char* aProgram, const char* aProfile,
nsString& aClassName) {
aClassName.AppendPrintf("Mozilla_%s_%s_RemoteWindow", aProgram, aProfile);
}
#endif // nsWinRemoteUtils_h__

Просмотреть файл

@ -1763,7 +1763,7 @@ with only_when(compile_environment & target_is_windows):
@depends(toolkit)
def has_remote(toolkit):
if toolkit in ('gtk', 'windows'):
if toolkit in ('gtk', 'windows', 'cocoa'):
return True
set_config('MOZ_HAS_REMOTE', has_remote)

Просмотреть файл

@ -50,61 +50,6 @@ nsresult GetNativeWindowPointerFromDOMWindow(mozIDOMWindowProxy* a_window,
return NS_OK;
}
// Essentially this notification handler implements the
// "Mozilla remote" functionality on Mac, handing command line arguments (passed
// to a newly launched process) to another copy of the current process that was
// already running (which had registered the handler for this notification). All
// other new copies just broadcast this notification and quit (unless -no-remote
// was specified in either of these processes), making the original process handle
// the arguments passed to this handler.
void remoteClientNotificationCallback(CFNotificationCenterRef aCenter, void* aObserver,
CFStringRef aName, const void* aObject,
CFDictionaryRef aUserInfo) {
// Autorelease pool to prevent memory leaks, in case there is no outer pool.
mozilla::MacAutoreleasePool pool;
NSDictionary* userInfoDict = (__bridge NSDictionary*)aUserInfo;
if (userInfoDict && [userInfoDict objectForKey:@"commandLineArgs"] &&
[userInfoDict objectForKey:@"senderPath"]) {
NSString* senderPath = [userInfoDict objectForKey:@"senderPath"];
if (![senderPath isEqual:[[NSBundle mainBundle] bundlePath]]) {
// The caller is not the process at the same path as we are at. Skipping.
return;
}
NSArray* args = [userInfoDict objectForKey:@"commandLineArgs"];
nsCOMPtr<nsICommandLineRunner> cmdLine(new nsCommandLine());
// Converting Objective-C array into a C array,
// which nsICommandLineRunner understands.
int argc = [args count];
const char** argv = new const char*[argc];
for (int i = 0; i < argc; i++) {
const char* arg = [[args objectAtIndex:i] UTF8String];
argv[i] = arg;
}
// We're not currently passing the working dir as third argument because it
// does not appear to be required.
nsresult rv = cmdLine->Init(argc, argv, nullptr, nsICommandLine::STATE_REMOTE_AUTO);
// Cleaning up C array.
delete[] argv;
if (NS_FAILED(rv)) {
NS_ERROR("Error initializing command line.");
return;
}
// Processing the command line, passed from a remote instance
// in the current instance.
cmdLine->Run();
// And bring the app's window to front.
[[NSRunningApplication currentApplication]
activateWithOptions:NSApplicationActivateIgnoringOtherApps];
}
}
class nsNativeAppSupportCocoa : public nsNativeAppSupportBase {
public:
nsNativeAppSupportCocoa() : mCanShowUI(false) {}
@ -141,121 +86,6 @@ NS_IMETHODIMP nsNativeAppSupportCocoa::Start(bool* _retval) {
*_retval = true;
// Here are the "special" CLI arguments that we can expect to be passed that
// should alter the default "hand args list to remote process and quit" algorithm:
// -headless : was already handled on macOS (allowing running multiple instances
// of the app), meaning this patch shouldn't break it.
// -no-remote : should always proceed, creating a second instance (which will
// fail on macOS, showing a MessageBox "Only one instance can be run at a time",
// unless a different profile dir path is specified).
// The rest of the arguments should be either passed on to
// the original running process (exiting the current process), or be processed by
// the current process (if -no-remote is specified).
mozilla::MacAutoreleasePool pool;
NSArray* arguments = [[NSProcessInfo processInfo] arguments];
BOOL shallProceedLikeNoRemote = NO;
for (NSString* arg in arguments) {
if ([arg isEqualToString:@"-no-remote"] || [arg isEqualToString:@"--no-remote"] ||
[arg isEqualToString:@"-headless"] || [arg isEqualToString:@"--headless"] ||
[arg isEqualToString:@"-createProfile"] || [arg isEqualToString:@"--createProfile"]) {
shallProceedLikeNoRemote = YES;
break;
}
}
BOOL mozillaRestarting = NO;
if ([[[[NSProcessInfo processInfo] environment] objectForKey:@"MOZ_APP_RESTART"]
isEqualToString:@"1"]) {
// Update process completed or restarting the app for another reason.
// Triggered by an old instance that just quit.
mozillaRestarting = YES;
}
// Apart from -no-remote, the user can specify an env variable
// MOZ_NO_REMOTE=1, which makes it behave the same way.
// Also, to make sure the tests do not break,
// if env var MOZ_TEST_PROCESS_UPDATES is present, it means the test is running.
// We should proceed as if -no-remote had been specified.
if (shallProceedLikeNoRemote == NO) {
NSDictionary* environmentVariables = [[NSProcessInfo processInfo] environment];
for (NSString* key in [environmentVariables allKeys]) {
if ([key isEqualToString:@"MOZ_NO_REMOTE"] &&
[environmentVariables[key] isEqualToString:@"1"]) {
shallProceedLikeNoRemote = YES;
break;
}
}
}
// Now that we have handled no-remote-like arguments, at this point:
// 1) Either only the first instance of the process has been launched in any way
// (.app double click, "open", "open -n", invoking executable in Terminal, etc.
// 2) Or the process has been launched with a "macos single instance" mechanism
// override (using "open -n" OR directly by invoking the executable in Terminal
// instead of clicking the .app bundle's icon, etc.).
// So, let's check if this is the first instance ever of the process for the
// current user.
NSString* notificationName = [[[NSBundle mainBundle] bundleIdentifier]
stringByAppendingString:@".distributedNotification.commandLineArgs"];
BOOL runningInstanceFound = NO;
if (!shallProceedLikeNoRemote) {
// We check for other running instances only if -no-remote was not specified.
// The check is needed so the marAppApplyUpdateSuccess.js test doesn't fail on next call.
NSArray* appsWithMatchingId = [NSRunningApplication
runningApplicationsWithBundleIdentifier:[[NSBundle mainBundle] bundleIdentifier]];
NSString* currentAppBundlePath = [[NSBundle mainBundle] bundlePath];
NSRunningApplication* currentApp = [NSRunningApplication currentApplication];
for (NSRunningApplication* app in appsWithMatchingId) {
if ([currentAppBundlePath isEqual:[[app bundleURL] path]] && ![currentApp isEqual:app]) {
runningInstanceFound = YES;
break;
}
}
}
if (!shallProceedLikeNoRemote && !mozillaRestarting && runningInstanceFound) {
// There is another instance of this app already running!
NSArray* arguments = [[NSProcessInfo processInfo] arguments];
NSString* senderPath = [[NSBundle mainBundle] bundlePath];
CFDictionaryRef userInfoDict =
(__bridge CFDictionaryRef) @{@"commandLineArgs" : arguments, @"senderPath" : senderPath};
// This code is shared between Firefox, Thunderbird and other Mozilla products.
// So we need a notification name that is unique to the product, so we
// do not send a notification to Firefox from Thunderbird and so on. I am using
// bundle Id (assuming all Mozilla products come wrapped in .app bundles) -
// it should be unique
// (e.g., org.mozilla.firefox.distributedNotification.commandLineArgs for Firefox).
// We also need to make sure the notifications are "local" to the current user,
// so we do not pass it on to perhaps another running Thunderbird by another
// logged in user. Distributed notifications is the best candidate
// (while darwin notifications ignore the user context).
CFNotificationCenterPostNotification(CFNotificationCenterGetDistributedCenter(),
(__bridge CFStringRef)notificationName, NULL, userInfoDict,
true);
// Do not continue start up sequence for this process - just self-terminate,
// we already passed the arguments on to the original instance of the process.
*_retval = false;
} else {
// This is the first instance ever (or launched as -no-remote)!
// Let's register a notification listener here,
// In case future instances would want to notify us about command line arguments
// passed to them. Note, that if mozilla process is restarting, we still need to
// register for notifications.
CFNotificationCenterAddObserver(CFNotificationCenterGetDistributedCenter(), NULL,
remoteClientNotificationCallback,
(__bridge CFStringRef)notificationName, NULL,
CFNotificationSuspensionBehaviorDeliverImmediately);
// Continue the start up sequence of this process.
*_retval = true;
}
return NS_OK;
NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;