summaryrefslogtreecommitdiffstats
path: root/toolkit/components/remote
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /toolkit/components/remote
parentInitial commit. (diff)
downloadthunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz
thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/components/remote')
-rw-r--r--toolkit/components/remote/RemoteUtils.cpp102
-rw-r--r--toolkit/components/remote/RemoteUtils.h31
-rw-r--r--toolkit/components/remote/WinRemoteMessage.cpp124
-rw-r--r--toolkit/components/remote/WinRemoteMessage.h69
-rw-r--r--toolkit/components/remote/moz.build53
-rw-r--r--toolkit/components/remote/nsDBusRemoteClient.cpp188
-rw-r--r--toolkit/components/remote/nsDBusRemoteClient.h34
-rw-r--r--toolkit/components/remote/nsDBusRemoteServer.cpp214
-rw-r--r--toolkit/components/remote/nsDBusRemoteServer.h38
-rw-r--r--toolkit/components/remote/nsGTKRemoteServer.cpp69
-rw-r--r--toolkit/components/remote/nsGTKRemoteServer.h37
-rw-r--r--toolkit/components/remote/nsMacRemoteClient.h27
-rw-r--r--toolkit/components/remote/nsMacRemoteClient.mm62
-rw-r--r--toolkit/components/remote/nsMacRemoteServer.h30
-rw-r--r--toolkit/components/remote/nsMacRemoteServer.mm134
-rw-r--r--toolkit/components/remote/nsRemoteClient.h64
-rw-r--r--toolkit/components/remote/nsRemoteServer.h21
-rw-r--r--toolkit/components/remote/nsRemoteService.cpp192
-rw-r--r--toolkit/components/remote/nsRemoteService.h49
-rw-r--r--toolkit/components/remote/nsUnixRemoteServer.cpp113
-rw-r--r--toolkit/components/remote/nsUnixRemoteServer.h28
-rw-r--r--toolkit/components/remote/nsWinRemoteClient.cpp44
-rw-r--r--toolkit/components/remote/nsWinRemoteClient.h25
-rw-r--r--toolkit/components/remote/nsWinRemoteServer.cpp104
-rw-r--r--toolkit/components/remote/nsWinRemoteServer.h27
-rw-r--r--toolkit/components/remote/nsXRemoteClient.cpp653
-rw-r--r--toolkit/components/remote/nsXRemoteClient.h49
-rw-r--r--toolkit/components/remote/nsXRemoteServer.cpp161
-rw-r--r--toolkit/components/remote/nsXRemoteServer.h44
29 files changed, 2786 insertions, 0 deletions
diff --git a/toolkit/components/remote/RemoteUtils.cpp b/toolkit/components/remote/RemoteUtils.cpp
new file mode 100644
index 0000000000..f7677f92b9
--- /dev/null
+++ b/toolkit/components/remote/RemoteUtils.cpp
@@ -0,0 +1,102 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=8:
+ */
+/* vim:set ts=8 sw=2 et cindent: */
+/* 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 <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <limits.h>
+
+#include "RemoteUtils.h"
+
+#ifdef IS_BIG_ENDIAN
+# define TO_LITTLE_ENDIAN32(x) \
+ ((((x)&0xff000000) >> 24) | (((x)&0x00ff0000) >> 8) | \
+ (((x)&0x0000ff00) << 8) | (((x)&0x000000ff) << 24))
+#else
+# define TO_LITTLE_ENDIAN32(x) (x)
+#endif
+
+#ifndef MAX_PATH
+# ifdef PATH_MAX
+# define MAX_PATH PATH_MAX
+# else
+# define MAX_PATH 1024
+# endif
+#endif
+
+/* like strcpy, but return the char after the final null */
+static char* estrcpy(const char* s, char* d) {
+ while (*s) *d++ = *s++;
+
+ *d++ = '\0';
+ return d;
+}
+
+/* Construct a command line from given args and desktop startup ID.
+ * Returned buffer must be released by free().
+ */
+char* ConstructCommandLine(int32_t argc, char** argv, const char* aStartupToken,
+ int* aCommandLineLength) {
+ char cwdbuf[MAX_PATH];
+ if (!getcwd(cwdbuf, MAX_PATH)) return nullptr;
+
+ // the commandline property is constructed as an array of int32_t
+ // followed by a series of null-terminated strings:
+ //
+ // [argc][offsetargv0][offsetargv1...]<workingdir>\0<argv[0]>\0argv[1]...\0
+ // (offset is from the beginning of the buffer)
+
+ static char startupTokenPrefix[] = " STARTUP_TOKEN=";
+
+ int32_t argvlen = strlen(cwdbuf);
+ for (int i = 0; i < argc; ++i) {
+ int32_t len = strlen(argv[i]);
+ if (i == 0 && aStartupToken) {
+ len += sizeof(startupTokenPrefix) - 1 + strlen(aStartupToken);
+ }
+ argvlen += len;
+ }
+
+ auto* buffer =
+ (int32_t*)malloc(argvlen + argc + 1 + sizeof(int32_t) * (argc + 1));
+ if (!buffer) return nullptr;
+
+ buffer[0] = TO_LITTLE_ENDIAN32(argc);
+
+ auto* bufend = (char*)(buffer + argc + 1);
+
+ bufend = estrcpy(cwdbuf, bufend);
+
+ for (int i = 0; i < argc; ++i) {
+ buffer[i + 1] = TO_LITTLE_ENDIAN32(bufend - ((char*)buffer));
+ bufend = estrcpy(argv[i], bufend);
+ if (i == 0 && aStartupToken) {
+ bufend = estrcpy(startupTokenPrefix, bufend - 1);
+ bufend = estrcpy(aStartupToken, bufend - 1);
+ }
+ }
+
+#ifdef DEBUG_command_line
+ int32_t debug_argc = TO_LITTLE_ENDIAN32(*buffer);
+ char* debug_workingdir = (char*)(buffer + argc + 1);
+
+ printf(
+ "Sending command line:\n"
+ " working dir: %s\n"
+ " argc:\t%i",
+ debug_workingdir, debug_argc);
+
+ int32_t* debug_offset = buffer + 1;
+ for (int debug_i = 0; debug_i < debug_argc; ++debug_i)
+ printf(" argv[%i]:\t%s\n", debug_i,
+ ((char*)buffer) + TO_LITTLE_ENDIAN32(debug_offset[debug_i]));
+#endif
+
+ *aCommandLineLength = bufend - reinterpret_cast<char*>(buffer);
+ return reinterpret_cast<char*>(buffer);
+}
diff --git a/toolkit/components/remote/RemoteUtils.h b/toolkit/components/remote/RemoteUtils.h
new file mode 100644
index 0000000000..2d21293eb5
--- /dev/null
+++ b/toolkit/components/remote/RemoteUtils.h
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 8; 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 TOOLKIT_COMPONENTS_REMOTE_REMOTEUTILS_H_
+#define TOOLKIT_COMPONENTS_REMOTE_REMOTEUTILS_H_
+
+#include "nsString.h"
+#if defined XP_WIN
+# include "WinUtils.h"
+#endif
+
+#if defined XP_WIN || defined XP_MACOSX
+static void BuildClassName(const char* aProgram, const char* aProfile,
+ nsString& aClassName) {
+ aClassName.AppendPrintf("Mozilla_%s", aProgram);
+# if defined XP_WIN
+ nsString pfn = mozilla::widget::WinUtils::GetPackageFamilyName();
+ if (!pfn.IsEmpty()) {
+ aClassName.AppendPrintf("_%S", static_cast<const wchar_t*>(pfn.get()));
+ }
+# endif
+ aClassName.AppendPrintf("_%s_RemoteWindow", aProfile);
+}
+#endif
+
+char* ConstructCommandLine(int32_t argc, char** argv, const char* aStartupToken,
+ int* aCommandLineLength);
+
+#endif // TOOLKIT_COMPONENTS_REMOTE_REMOTEUTILS_H_
diff --git a/toolkit/components/remote/WinRemoteMessage.cpp b/toolkit/components/remote/WinRemoteMessage.cpp
new file mode 100644
index 0000000000..98f425ba1a
--- /dev/null
+++ b/toolkit/components/remote/WinRemoteMessage.cpp
@@ -0,0 +1,124 @@
+/* -*- 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 "nsCommandLine.h"
+#include "mozilla/CmdLineAndEnvUtils.h"
+#include "WinRemoteMessage.h"
+
+using namespace mozilla;
+
+WinRemoteMessageSender::WinRemoteMessageSender(const char* aCommandLine)
+ : mData({static_cast<DWORD>(WinRemoteMessageVersion::CommandLineOnly)}),
+ mUtf8Buffer(aCommandLine) {
+ mUtf8Buffer.Append('\0');
+
+ char* mutableBuffer;
+ mData.cbData = mUtf8Buffer.GetMutableData(&mutableBuffer);
+ mData.lpData = mutableBuffer;
+}
+
+WinRemoteMessageSender::WinRemoteMessageSender(const char* aCommandLine,
+ const char* aWorkingDir)
+ : mData({static_cast<DWORD>(
+ WinRemoteMessageVersion::CommandLineAndWorkingDir)}),
+ mUtf8Buffer(aCommandLine) {
+ mUtf8Buffer.Append('\0');
+ mUtf8Buffer.Append(aWorkingDir);
+ mUtf8Buffer.Append('\0');
+
+ char* mutableBuffer;
+ mData.cbData = mUtf8Buffer.GetMutableData(&mutableBuffer);
+ mData.lpData = mutableBuffer;
+}
+
+WinRemoteMessageSender::WinRemoteMessageSender(const wchar_t* aCommandLine,
+ const wchar_t* aWorkingDir)
+ : mData({static_cast<DWORD>(
+ WinRemoteMessageVersion::CommandLineAndWorkingDirInUtf16)}),
+ mUtf16Buffer(aCommandLine) {
+ mUtf16Buffer.Append(u'\0');
+ mUtf16Buffer.Append(aWorkingDir);
+ mUtf16Buffer.Append(u'\0');
+
+ char16_t* mutableBuffer;
+ mData.cbData = mUtf16Buffer.GetMutableData(&mutableBuffer) * sizeof(char16_t);
+ mData.lpData = mutableBuffer;
+}
+
+COPYDATASTRUCT* WinRemoteMessageSender::CopyData() { return &mData; }
+
+nsresult WinRemoteMessageReceiver::ParseV0(const nsACString& aBuffer) {
+ CommandLineParserWin<char> parser;
+ parser.HandleCommandLine(aBuffer);
+
+ mCommandLine = new nsCommandLine();
+ return mCommandLine->Init(parser.Argc(), parser.Argv(), nullptr,
+ nsICommandLine::STATE_REMOTE_AUTO);
+}
+
+nsresult WinRemoteMessageReceiver::ParseV1(const nsACString& aBuffer) {
+ CommandLineParserWin<char> parser;
+ size_t cch = parser.HandleCommandLine(aBuffer);
+ ++cch; // skip a null char
+
+ nsCOMPtr<nsIFile> workingDir;
+ if (cch < aBuffer.Length()) {
+ NS_NewLocalFile(NS_ConvertUTF8toUTF16(Substring(aBuffer, cch)), false,
+ getter_AddRefs(workingDir));
+ }
+
+ mCommandLine = new nsCommandLine();
+ return mCommandLine->Init(parser.Argc(), parser.Argv(), workingDir,
+ nsICommandLine::STATE_REMOTE_AUTO);
+}
+
+nsresult WinRemoteMessageReceiver::ParseV2(const nsAString& aBuffer) {
+ CommandLineParserWin<char16_t> parser;
+ size_t cch = parser.HandleCommandLine(aBuffer);
+ ++cch; // skip a null char
+
+ nsCOMPtr<nsIFile> workingDir;
+ if (cch < aBuffer.Length()) {
+ NS_NewLocalFile(Substring(aBuffer, cch), false, getter_AddRefs(workingDir));
+ }
+
+ int argc = parser.Argc();
+ Vector<nsAutoCString> utf8args;
+ if (!utf8args.reserve(argc)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ UniquePtr<const char*[]> argv(new const char*[argc]);
+ for (int i = 0; i < argc; ++i) {
+ utf8args.infallibleAppend(NS_ConvertUTF16toUTF8(parser.Argv()[i]));
+ argv[i] = utf8args[i].get();
+ }
+
+ mCommandLine = new nsCommandLine();
+ return mCommandLine->Init(argc, argv.get(), workingDir,
+ nsICommandLine::STATE_REMOTE_AUTO);
+}
+
+nsresult WinRemoteMessageReceiver::Parse(const COPYDATASTRUCT* aMessageData) {
+ switch (static_cast<WinRemoteMessageVersion>(aMessageData->dwData)) {
+ case WinRemoteMessageVersion::CommandLineOnly:
+ return ParseV0(nsDependentCSubstring(
+ reinterpret_cast<char*>(aMessageData->lpData), aMessageData->cbData));
+ case WinRemoteMessageVersion::CommandLineAndWorkingDir:
+ return ParseV1(nsDependentCSubstring(
+ reinterpret_cast<char*>(aMessageData->lpData), aMessageData->cbData));
+ case WinRemoteMessageVersion::CommandLineAndWorkingDirInUtf16:
+ return ParseV2(nsDependentSubstring(
+ reinterpret_cast<char16_t*>(aMessageData->lpData),
+ aMessageData->cbData / sizeof(char16_t)));
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unsupported message version");
+ return NS_ERROR_FAILURE;
+ }
+}
+
+nsICommandLineRunner* WinRemoteMessageReceiver::CommandLineRunner() {
+ return mCommandLine;
+}
diff --git a/toolkit/components/remote/WinRemoteMessage.h b/toolkit/components/remote/WinRemoteMessage.h
new file mode 100644
index 0000000000..948c6db310
--- /dev/null
+++ b/toolkit/components/remote/WinRemoteMessage.h
@@ -0,0 +1,69 @@
+/* -*- 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 __WinRemoteMessage_h__
+#define __WinRemoteMessage_h__
+
+#include <windows.h>
+
+#include "nsICommandLineRunner.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+
+// This version defines the format of COPYDATASTRUCT::lpData in a message of
+// WM_COPYDATA.
+// Always use the latest version for production use because the older versions
+// have a bug that a non-ascii character in a utf-8 message cannot be parsed
+// correctly (bug 1650637). We keep the older versions for backward
+// compatibility and the testing purpose only.
+enum class WinRemoteMessageVersion : uint32_t {
+ // "<CommandLine>\0" in utf8
+ CommandLineOnly = 0,
+ // "<CommandLine>\0<WorkingDir>\0" in utf8
+ CommandLineAndWorkingDir,
+ // L"<CommandLine>\0<WorkingDir>\0" in utf16
+ CommandLineAndWorkingDirInUtf16,
+};
+
+class WinRemoteMessageSender final {
+ COPYDATASTRUCT mData;
+ nsAutoString mUtf16Buffer;
+ nsAutoCString mUtf8Buffer;
+
+ public:
+ WinRemoteMessageSender(const wchar_t* aCommandLine,
+ const wchar_t* aWorkingDir);
+
+ WinRemoteMessageSender(const WinRemoteMessageSender&) = delete;
+ WinRemoteMessageSender(WinRemoteMessageSender&&) = delete;
+ WinRemoteMessageSender& operator=(const WinRemoteMessageSender&) = delete;
+ WinRemoteMessageSender& operator=(WinRemoteMessageSender&&) = delete;
+
+ COPYDATASTRUCT* CopyData();
+
+ // Constructors for the old formats. Testing purpose only.
+ explicit WinRemoteMessageSender(const char* aCommandLine);
+ WinRemoteMessageSender(const char* aCommandLine, const char* aWorkingDir);
+};
+
+class WinRemoteMessageReceiver final {
+ nsCOMPtr<nsICommandLineRunner> mCommandLine;
+
+ nsresult ParseV0(const nsACString& aBuffer);
+ nsresult ParseV1(const nsACString& aBuffer);
+ nsresult ParseV2(const nsAString& aBuffer);
+
+ public:
+ WinRemoteMessageReceiver() = default;
+ WinRemoteMessageReceiver(const WinRemoteMessageReceiver&) = delete;
+ WinRemoteMessageReceiver(WinRemoteMessageReceiver&&) = delete;
+ WinRemoteMessageReceiver& operator=(const WinRemoteMessageReceiver&) = delete;
+ WinRemoteMessageReceiver& operator=(WinRemoteMessageReceiver&&) = delete;
+
+ nsresult Parse(const COPYDATASTRUCT* aMessageData);
+ nsICommandLineRunner* CommandLineRunner();
+};
+
+#endif // __WinRemoteMessage_h__
diff --git a/toolkit/components/remote/moz.build b/toolkit/components/remote/moz.build
new file mode 100644
index 0000000000..d3bab6cf9a
--- /dev/null
+++ b/toolkit/components/remote/moz.build
@@ -0,0 +1,53 @@
+# -*- 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/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Toolkit", "Startup and Profile System")
+
+SOURCES += [
+ "nsRemoteService.cpp",
+]
+
+if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk":
+ SOURCES += [
+ "nsUnixRemoteServer.cpp",
+ "RemoteUtils.cpp",
+ ]
+ if CONFIG["MOZ_ENABLE_DBUS"]:
+ SOURCES += [
+ "nsDBusRemoteClient.cpp",
+ "nsDBusRemoteServer.cpp",
+ ]
+ CXXFLAGS += CONFIG["MOZ_DBUS_GLIB_CFLAGS"]
+ EXPORTS += [
+ "nsUnixRemoteServer.h",
+ "RemoteUtils.h",
+ ]
+ else:
+ SOURCES += [
+ "nsGTKRemoteServer.cpp",
+ "nsXRemoteClient.cpp",
+ "nsXRemoteServer.cpp",
+ ]
+ CXXFLAGS += CONFIG["MOZ_GTK3_CFLAGS"]
+
+if CONFIG["MOZ_WIDGET_TOOLKIT"] == "windows":
+ SOURCES += [
+ "nsWinRemoteClient.cpp",
+ "nsWinRemoteServer.cpp",
+ "WinRemoteMessage.cpp",
+ ]
+if CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa":
+ SOURCES += [
+ "nsMacRemoteClient.mm",
+ "nsMacRemoteServer.mm",
+ ]
+
+LOCAL_INCLUDES += [
+ "../../profile",
+ "../../xre",
+]
+FINAL_LIBRARY = "xul"
diff --git a/toolkit/components/remote/nsDBusRemoteClient.cpp b/toolkit/components/remote/nsDBusRemoteClient.cpp
new file mode 100644
index 0000000000..69e8d07c72
--- /dev/null
+++ b/toolkit/components/remote/nsDBusRemoteClient.cpp
@@ -0,0 +1,188 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=8:
+ */
+/* vim:set ts=8 sw=2 et cindent: */
+/* 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 "nsDBusRemoteClient.h"
+#include "RemoteUtils.h"
+#include "mozilla/XREAppData.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Base64.h"
+#include "nsPrintfCString.h"
+
+#include <dlfcn.h>
+#include <dbus/dbus-glib-lowlevel.h>
+
+#undef LOG
+#ifdef MOZ_LOGGING
+static mozilla::LazyLogModule sRemoteLm("nsDBusRemoteClient");
+# define LOG(str, ...) \
+ MOZ_LOG(sRemoteLm, mozilla::LogLevel::Debug, (str, ##__VA_ARGS__))
+#else
+# define LOG(...)
+#endif
+
+nsDBusRemoteClient::nsDBusRemoteClient() {
+ mConnection = nullptr;
+ LOG("nsDBusRemoteClient::nsDBusRemoteClient");
+}
+
+nsDBusRemoteClient::~nsDBusRemoteClient() {
+ LOG("nsDBusRemoteClient::~nsDBusRemoteClient");
+ Shutdown();
+}
+
+nsresult nsDBusRemoteClient::Init() {
+ LOG("nsDBusRemoteClient::Init");
+
+ if (mConnection) return NS_OK;
+
+ mConnection =
+ already_AddRefed<DBusConnection>(dbus_bus_get(DBUS_BUS_SESSION, nullptr));
+ if (!mConnection) {
+ LOG(" failed to get DBus session");
+ return NS_ERROR_FAILURE;
+ }
+
+ dbus_connection_set_exit_on_disconnect(mConnection, false);
+ dbus_connection_setup_with_g_main(mConnection, nullptr);
+
+ return NS_OK;
+}
+
+void nsDBusRemoteClient::Shutdown(void) {
+ LOG("nsDBusRemoteClient::Shutdown");
+ // This connection is owned by libdbus and we don't need to close it
+ mConnection = nullptr;
+}
+
+nsresult nsDBusRemoteClient::SendCommandLine(
+ const char* aProgram, const char* aProfile, int32_t argc, char** argv,
+ const char* aStartupToken, char** aResponse, bool* aWindowFound) {
+ NS_ENSURE_TRUE(aProgram, NS_ERROR_INVALID_ARG);
+
+ LOG("nsDBusRemoteClient::SendCommandLine");
+
+ int commandLineLength;
+ char* commandLine =
+ ConstructCommandLine(argc, argv, aStartupToken, &commandLineLength);
+ if (!commandLine) {
+ LOG(" failed to create command line");
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv =
+ DoSendDBusCommandLine(aProgram, aProfile, commandLine, commandLineLength);
+ free(commandLine);
+
+ *aWindowFound = NS_SUCCEEDED(rv);
+
+ LOG("DoSendDBusCommandLine %s", NS_SUCCEEDED(rv) ? "OK" : "FAILED");
+ return rv;
+}
+
+bool nsDBusRemoteClient::GetRemoteDestinationName(const char* aProgram,
+ const char* aProfile,
+ nsCString& aDestinationName) {
+ // We have a profile name - just create the destination.
+ // D-Bus names can contain only [a-z][A-Z][0-9]_
+ // characters so adjust the profile string properly.
+ nsAutoCString profileName;
+ nsresult rv = mozilla::Base64Encode(nsAutoCString(aProfile), profileName);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ mozilla::XREAppData::SanitizeNameForDBus(profileName);
+
+ aDestinationName =
+ nsPrintfCString("org.mozilla.%s.%s", aProgram, profileName.get());
+ if (aDestinationName.Length() > DBUS_MAXIMUM_NAME_LENGTH)
+ aDestinationName.Truncate(DBUS_MAXIMUM_NAME_LENGTH);
+
+ static auto sDBusValidateBusName = (bool (*)(const char*, DBusError*))dlsym(
+ RTLD_DEFAULT, "dbus_validate_bus_name");
+ if (!sDBusValidateBusName) {
+ LOG(" failed to get dbus_validate_bus_name()");
+ return false;
+ }
+
+ if (!sDBusValidateBusName(aDestinationName.get(), nullptr)) {
+ // We don't have a valid busName yet - try to create a default one.
+ aDestinationName =
+ nsPrintfCString("org.mozilla.%s.%s", aProgram, "default");
+ if (!sDBusValidateBusName(aDestinationName.get(), nullptr)) {
+ // We failed completelly to get a valid bus name - just quit
+ // to prevent crash at dbus_bus_request_name().
+ LOG(" failed to validate profile DBus name");
+ return false;
+ }
+ }
+
+ return true;
+}
+
+nsresult nsDBusRemoteClient::DoSendDBusCommandLine(const char* aProgram,
+ const char* aProfile,
+ const char* aBuffer,
+ int aLength) {
+ LOG("nsDBusRemoteClient::DoSendDBusCommandLine()");
+
+ nsAutoCString appName(aProgram);
+ mozilla::XREAppData::SanitizeNameForDBus(appName);
+
+ nsAutoCString destinationName;
+ if (!GetRemoteDestinationName(appName.get(), aProfile, destinationName)) {
+ LOG(" failed to get remote destination name");
+ return NS_ERROR_FAILURE;
+ }
+
+ nsAutoCString pathName;
+ pathName = nsPrintfCString("/org/mozilla/%s/Remote", appName.get());
+
+ static auto sDBusValidatePathName = (bool (*)(const char*, DBusError*))dlsym(
+ RTLD_DEFAULT, "dbus_validate_path");
+ if (!sDBusValidatePathName ||
+ !sDBusValidatePathName(pathName.get(), nullptr)) {
+ LOG(" failed to validate path name");
+ return NS_ERROR_FAILURE;
+ }
+
+ nsAutoCString remoteInterfaceName;
+ remoteInterfaceName = nsPrintfCString("org.mozilla.%s", appName.get());
+
+ LOG(" DBus destination: %s\n", destinationName.get());
+ LOG(" DBus path: %s\n", pathName.get());
+ LOG(" DBus interface: %s\n", remoteInterfaceName.get());
+
+ RefPtr<DBusMessage> msg =
+ already_AddRefed<DBusMessage>(dbus_message_new_method_call(
+ destinationName.get(),
+ pathName.get(), // object to call on
+ remoteInterfaceName.get(), // interface to call on
+ "OpenURL")); // method name
+ if (!msg) {
+ LOG(" failed to create DBus message");
+ return NS_ERROR_FAILURE;
+ }
+
+ // append arguments
+ if (!dbus_message_append_args(msg, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &aBuffer,
+ aLength, DBUS_TYPE_INVALID)) {
+ LOG(" failed to create DBus message");
+ return NS_ERROR_FAILURE;
+ }
+
+ // send message and get a handle for a reply
+ RefPtr<DBusMessage> reply = already_AddRefed<DBusMessage>(
+ dbus_connection_send_with_reply_and_block(mConnection, msg, -1, nullptr));
+
+#ifdef MOZ_LOGGING
+ if (!reply) {
+ LOG(" failed to get DBus reply");
+ }
+#endif
+
+ return reply ? NS_OK : NS_ERROR_FAILURE;
+}
diff --git a/toolkit/components/remote/nsDBusRemoteClient.h b/toolkit/components/remote/nsDBusRemoteClient.h
new file mode 100644
index 0000000000..fdb4dbc665
--- /dev/null
+++ b/toolkit/components/remote/nsDBusRemoteClient.h
@@ -0,0 +1,34 @@
+/* -*- Mode: C++; tab-width: 8; 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 DBusRemoteClient_h__
+#define DBusRemoteClient_h__
+
+#include "nsRemoteClient.h"
+#include "mozilla/DBusHelpers.h"
+#include "mozilla/RefPtr.h"
+#include "nsStringFwd.h"
+#include "nscore.h"
+
+class nsDBusRemoteClient : public nsRemoteClient {
+ public:
+ nsDBusRemoteClient();
+ ~nsDBusRemoteClient();
+
+ nsresult Init() override;
+ nsresult SendCommandLine(const char* aProgram, const char* aProfile,
+ int32_t argc, char** argv, const char* aStartupToken,
+ char** aResponse, bool* aSucceeded) override;
+ void Shutdown();
+
+ private:
+ bool GetRemoteDestinationName(const char* aProgram, const char* aProfile,
+ nsCString& aDestinationName);
+ nsresult DoSendDBusCommandLine(const char* aProgram, const char* aProfile,
+ const char* aBuffer, int aLength);
+ RefPtr<DBusConnection> mConnection;
+};
+
+#endif // DBusRemoteClient_h__
diff --git a/toolkit/components/remote/nsDBusRemoteServer.cpp b/toolkit/components/remote/nsDBusRemoteServer.cpp
new file mode 100644
index 0000000000..34167177d9
--- /dev/null
+++ b/toolkit/components/remote/nsDBusRemoteServer.cpp
@@ -0,0 +1,214 @@
+/* -*- 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/. */
+
+#include "nsDBusRemoteServer.h"
+
+#include "nsCOMPtr.h"
+#include "mozilla/XREAppData.h"
+#include "mozilla/Base64.h"
+#include "mozilla/ScopeExit.h"
+#include "nsPrintfCString.h"
+
+#include "nsGTKToolkit.h"
+
+#include <dbus/dbus.h>
+#include <dbus/dbus-glib-lowlevel.h>
+
+#include <dlfcn.h>
+
+static const char* introspect_template =
+ "<!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object Introspection "
+ "1.0//EN\"\n"
+ "\"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\">\n"
+ "<node>\n"
+ " <interface name=\"org.freedesktop.DBus.Introspectable\">\n"
+ " <method name=\"Introspect\">\n"
+ " <arg name=\"data\" direction=\"out\" type=\"s\"/>\n"
+ " </method>\n"
+ " </interface>\n"
+ " <interface name=\"org.mozilla.%s\">\n"
+ " <method name=\"OpenURL\">\n"
+ " <arg name=\"url\" direction=\"in\" type=\"ay\"/>\n"
+ " </method>\n"
+ " </interface>\n"
+ "</node>\n";
+
+DBusHandlerResult nsDBusRemoteServer::Introspect(DBusMessage* msg) {
+ DBusMessage* reply = dbus_message_new_method_return(msg);
+ if (!reply) return DBUS_HANDLER_RESULT_NEED_MEMORY;
+
+ nsAutoCString introspect_xml;
+ introspect_xml = nsPrintfCString(introspect_template, mAppName.get());
+
+ const char* message = introspect_xml.get();
+ dbus_message_append_args(reply, DBUS_TYPE_STRING, &message,
+ DBUS_TYPE_INVALID);
+
+ dbus_connection_send(mConnection, reply, nullptr);
+ dbus_message_unref(reply);
+
+ return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+DBusHandlerResult nsDBusRemoteServer::OpenURL(DBusMessage* msg) {
+ DBusMessage* reply = nullptr;
+ const char* commandLine;
+ int length;
+
+ if (!dbus_message_get_args(msg, nullptr, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE,
+ &commandLine, &length, DBUS_TYPE_INVALID) ||
+ length == 0) {
+ nsAutoCString errorMsg;
+ errorMsg = nsPrintfCString("org.mozilla.%s.Error", mAppName.get());
+ reply = dbus_message_new_error(msg, errorMsg.get(), "Wrong argument");
+ } else {
+ guint32 timestamp = gtk_get_current_event_time();
+ if (timestamp == GDK_CURRENT_TIME) {
+ timestamp = guint32(g_get_monotonic_time() / 1000);
+ }
+ HandleCommandLine(commandLine, timestamp);
+ reply = dbus_message_new_method_return(msg);
+ }
+
+ dbus_connection_send(mConnection, reply, nullptr);
+ dbus_message_unref(reply);
+
+ return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+DBusHandlerResult nsDBusRemoteServer::HandleDBusMessage(
+ DBusConnection* aConnection, DBusMessage* msg) {
+ NS_ASSERTION(mConnection == aConnection, "Wrong D-Bus connection.");
+
+ const char* method = dbus_message_get_member(msg);
+ const char* iface = dbus_message_get_interface(msg);
+
+ if ((strcmp("Introspect", method) == 0) &&
+ (strcmp("org.freedesktop.DBus.Introspectable", iface) == 0)) {
+ return Introspect(msg);
+ }
+
+ nsAutoCString ourInterfaceName;
+ ourInterfaceName = nsPrintfCString("org.mozilla.%s", mAppName.get());
+
+ if ((strcmp("OpenURL", method) == 0) &&
+ (strcmp(ourInterfaceName.get(), iface) == 0)) {
+ return OpenURL(msg);
+ }
+
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+void nsDBusRemoteServer::UnregisterDBusInterface(DBusConnection* aConnection) {
+ NS_ASSERTION(mConnection == aConnection, "Wrong D-Bus connection.");
+ // Not implemented
+}
+
+static DBusHandlerResult message_handler(DBusConnection* conn, DBusMessage* msg,
+ void* user_data) {
+ auto interface = static_cast<nsDBusRemoteServer*>(user_data);
+ return interface->HandleDBusMessage(conn, msg);
+}
+
+static void unregister(DBusConnection* conn, void* user_data) {
+ auto interface = static_cast<nsDBusRemoteServer*>(user_data);
+ interface->UnregisterDBusInterface(conn);
+}
+
+static DBusObjectPathVTable remoteHandlersTable = {
+ .unregister_function = unregister,
+ .message_function = message_handler,
+};
+
+nsresult nsDBusRemoteServer::Startup(const char* aAppName,
+ const char* aProfileName) {
+ if (mConnection && dbus_connection_get_is_connected(mConnection)) {
+ // We're already connected so we don't need to reconnect
+ return NS_ERROR_ALREADY_INITIALIZED;
+ }
+
+ // Don't even try to start without any application/profile name
+ if (!aAppName || aAppName[0] == '\0' || !aProfileName ||
+ aProfileName[0] == '\0')
+ return NS_ERROR_INVALID_ARG;
+
+ mConnection = dont_AddRef(dbus_bus_get(DBUS_BUS_SESSION, nullptr));
+ if (!mConnection) {
+ return NS_ERROR_FAILURE;
+ }
+ auto releaseDBusConnection =
+ mozilla::MakeScopeExit([&] { mConnection = nullptr; });
+ dbus_connection_set_exit_on_disconnect(mConnection, false);
+ dbus_connection_setup_with_g_main(mConnection, nullptr);
+
+ mAppName = aAppName;
+ mozilla::XREAppData::SanitizeNameForDBus(mAppName);
+
+ nsAutoCString profileName;
+ MOZ_TRY(
+ mozilla::Base64Encode(aProfileName, strlen(aProfileName), profileName));
+
+ mozilla::XREAppData::SanitizeNameForDBus(profileName);
+
+ nsPrintfCString busName("org.mozilla.%s.%s", mAppName.get(),
+ profileName.get());
+ if (busName.Length() > DBUS_MAXIMUM_NAME_LENGTH)
+ busName.Truncate(DBUS_MAXIMUM_NAME_LENGTH);
+
+ static auto sDBusValidateBusName = (bool (*)(const char*, DBusError*))dlsym(
+ RTLD_DEFAULT, "dbus_validate_bus_name");
+ if (!sDBusValidateBusName) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // We don't have a valid busName yet - try to create a default one.
+ if (!sDBusValidateBusName(busName.get(), nullptr)) {
+ busName = nsPrintfCString("org.mozilla.%s.%s", mAppName.get(), "default");
+ if (!sDBusValidateBusName(busName.get(), nullptr)) {
+ // We failed completelly to get a valid bus name - just quit
+ // to prevent crash at dbus_bus_request_name().
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ DBusError err;
+ dbus_error_init(&err);
+ dbus_bus_request_name(mConnection, busName.get(), DBUS_NAME_FLAG_DO_NOT_QUEUE,
+ &err);
+ // The interface is already owned - there is another application/profile
+ // instance already running.
+ if (dbus_error_is_set(&err)) {
+ dbus_error_free(&err);
+ return NS_ERROR_FAILURE;
+ }
+
+ mPathName = nsPrintfCString("/org/mozilla/%s/Remote", mAppName.get());
+ static auto sDBusValidatePathName = (bool (*)(const char*, DBusError*))dlsym(
+ RTLD_DEFAULT, "dbus_validate_path");
+ if (!sDBusValidatePathName ||
+ !sDBusValidatePathName(mPathName.get(), nullptr)) {
+ return NS_ERROR_FAILURE;
+ }
+ if (!dbus_connection_register_object_path(mConnection, mPathName.get(),
+ &remoteHandlersTable, this)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ releaseDBusConnection.release();
+ return NS_OK;
+}
+
+void nsDBusRemoteServer::Shutdown() {
+ if (!mConnection) {
+ return;
+ }
+
+ dbus_connection_unregister_object_path(mConnection, mPathName.get());
+
+ // dbus_connection_unref() will be called by RefPtr here.
+ mConnection = nullptr;
+}
diff --git a/toolkit/components/remote/nsDBusRemoteServer.h b/toolkit/components/remote/nsDBusRemoteServer.h
new file mode 100644
index 0000000000..ce23a28c36
--- /dev/null
+++ b/toolkit/components/remote/nsDBusRemoteServer.h
@@ -0,0 +1,38 @@
+/* -*- 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 __nsDBusRemoteServer_h__
+#define __nsDBusRemoteServer_h__
+
+#include "nsRemoteServer.h"
+#include "nsUnixRemoteServer.h"
+#include "mozilla/DBusHelpers.h"
+
+class nsDBusRemoteServer final : public nsRemoteServer,
+ public nsUnixRemoteServer {
+ public:
+ nsDBusRemoteServer() : mConnection(nullptr), mAppName(nullptr) {}
+ ~nsDBusRemoteServer() override { Shutdown(); }
+
+ nsresult Startup(const char* aAppName, const char* aProfileName) override;
+ void Shutdown() override;
+
+ DBusHandlerResult HandleDBusMessage(DBusConnection* aConnection,
+ DBusMessage* msg);
+ void UnregisterDBusInterface(DBusConnection* aConnection);
+
+ private:
+ DBusHandlerResult OpenURL(DBusMessage* msg);
+ DBusHandlerResult Introspect(DBusMessage* msg);
+
+ // The connection is owned by DBus library
+ RefPtr<DBusConnection> mConnection;
+ nsCString mAppName;
+ nsCString mPathName;
+};
+
+#endif // __nsDBusRemoteServer_h__
diff --git a/toolkit/components/remote/nsGTKRemoteServer.cpp b/toolkit/components/remote/nsGTKRemoteServer.cpp
new file mode 100644
index 0000000000..5b6eda3f89
--- /dev/null
+++ b/toolkit/components/remote/nsGTKRemoteServer.cpp
@@ -0,0 +1,69 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=8:
+ */
+/* 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 "nsGTKRemoteServer.h"
+
+#include <gtk/gtk.h>
+#include <gdk/gdk.h>
+#include <gdk/gdkx.h>
+
+#include "mozilla/ModuleUtils.h"
+#include "nsAppShellCID.h"
+
+#include "nsCOMPtr.h"
+
+#include "nsGTKToolkit.h"
+
+nsresult nsGTKRemoteServer::Startup(const char* aAppName,
+ const char* aProfileName) {
+ NS_ASSERTION(aAppName, "Don't pass a null appname!");
+
+ if (mServerWindow) {
+ return NS_ERROR_ALREADY_INITIALIZED;
+ }
+
+ XRemoteBaseStartup(aAppName, aProfileName);
+
+ mServerWindow = gtk_invisible_new();
+ gtk_widget_realize(mServerWindow);
+ HandleCommandsFor(mServerWindow);
+
+ return NS_OK;
+}
+
+void nsGTKRemoteServer::Shutdown() {
+ if (!mServerWindow) {
+ return;
+ }
+
+ gtk_widget_destroy(mServerWindow);
+ mServerWindow = nullptr;
+}
+
+void nsGTKRemoteServer::HandleCommandsFor(GtkWidget* widget) {
+ g_signal_connect(G_OBJECT(widget), "property_notify_event",
+ G_CALLBACK(HandlePropertyChange), this);
+
+ gtk_widget_add_events(widget, GDK_PROPERTY_CHANGE_MASK);
+
+ Window window = gdk_x11_window_get_xid(gtk_widget_get_window(widget));
+ nsXRemoteServer::HandleCommandsFor(window);
+}
+
+gboolean nsGTKRemoteServer::HandlePropertyChange(GtkWidget* aWidget,
+ GdkEventProperty* pevent,
+ nsGTKRemoteServer* aThis) {
+ if (pevent->state == GDK_PROPERTY_NEW_VALUE) {
+ Atom changedAtom = gdk_x11_atom_to_xatom(pevent->atom);
+
+ XID window = gdk_x11_window_get_xid(gtk_widget_get_window(aWidget));
+ return aThis->HandleNewProperty(
+ window, GDK_DISPLAY_XDISPLAY(gdk_display_get_default()), pevent->time,
+ changedAtom);
+ }
+ return FALSE;
+}
diff --git a/toolkit/components/remote/nsGTKRemoteServer.h b/toolkit/components/remote/nsGTKRemoteServer.h
new file mode 100644
index 0000000000..767f2f54ad
--- /dev/null
+++ b/toolkit/components/remote/nsGTKRemoteServer.h
@@ -0,0 +1,37 @@
+/* -*- 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 __nsGTKRemoteServer_h__
+#define __nsGTKRemoteServer_h__
+
+#include <gdk/gdk.h>
+#include <gdk/gdkx.h>
+#include <gtk/gtk.h>
+
+#include "nsRemoteServer.h"
+#include "nsXRemoteServer.h"
+#include "mozilla/Attributes.h"
+
+class nsGTKRemoteServer final : public nsXRemoteServer {
+ public:
+ nsGTKRemoteServer() : mServerWindow(nullptr) {}
+ ~nsGTKRemoteServer() override { Shutdown(); }
+
+ nsresult Startup(const char* aAppName, const char* aProfileName) override;
+ void Shutdown() override;
+
+ static gboolean HandlePropertyChange(GtkWidget* widget,
+ GdkEventProperty* event,
+ nsGTKRemoteServer* aData);
+
+ private:
+ void HandleCommandsFor(GtkWidget* aWidget);
+
+ GtkWidget* mServerWindow;
+};
+
+#endif // __nsGTKRemoteService_h__
diff --git a/toolkit/components/remote/nsMacRemoteClient.h b/toolkit/components/remote/nsMacRemoteClient.h
new file mode 100644
index 0000000000..96ad4e2a7f
--- /dev/null
+++ b/toolkit/components/remote/nsMacRemoteClient.h
@@ -0,0 +1,27 @@
+/* -*- 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* aStartupToken,
+ char** aResponse, bool* aSucceeded) override;
+};
+
+#endif // TOOLKIT_COMPONENTS_REMOTE_NSMACREMOTECLIENT_H_
diff --git a/toolkit/components/remote/nsMacRemoteClient.mm b/toolkit/components/remote/nsMacRemoteClient.mm
new file mode 100644
index 0000000000..314eebc030
--- /dev/null
+++ b/toolkit/components/remote/nsMacRemoteClient.mm
@@ -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;
+}
diff --git a/toolkit/components/remote/nsMacRemoteServer.h b/toolkit/components/remote/nsMacRemoteServer.h
new file mode 100644
index 0000000000..0e2bda8a32
--- /dev/null
+++ b/toolkit/components/remote/nsMacRemoteServer.h
@@ -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_
diff --git a/toolkit/components/remote/nsMacRemoteServer.mm b/toolkit/components/remote/nsMacRemoteServer.mm
new file mode 100644
index 0000000000..0936d4d2fd
--- /dev/null
+++ b/toolkit/components/remote/nsMacRemoteServer.mm
@@ -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;
+ }
+}
diff --git a/toolkit/components/remote/nsRemoteClient.h b/toolkit/components/remote/nsRemoteClient.h
new file mode 100644
index 0000000000..f36c33287b
--- /dev/null
+++ b/toolkit/components/remote/nsRemoteClient.h
@@ -0,0 +1,64 @@
+/* -*- 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_NSREMOTECLIENT_H_
+#define TOOLKIT_COMPONENTS_REMOTE_NSREMOTECLIENT_H_
+
+#include "nscore.h"
+
+/**
+ * Pure-virtual common base class for remoting implementations.
+ */
+
+class nsRemoteClient {
+ public:
+ virtual ~nsRemoteClient() = default;
+
+ /**
+ * Initializes the client
+ */
+ virtual nsresult Init() = 0;
+
+ /**
+ * Send a complete command line to a running instance.
+ *
+ * @param aProgram This is the preferred program that we want to use
+ * for this particular command.
+ *
+ * @param aUsername This allows someone to only talk to an instance
+ * of the server that's running under a particular username. If
+ * this isn't specified here it's pulled from the LOGNAME
+ * environmental variable if it's set.
+ *
+ * @param aProfile This allows you to specify a particular server
+ * running under a named profile. If it is not specified the
+ * profile is not checked.
+ *
+ * @param argc The number of command-line arguments.
+ *
+ * @param argv The command-line arguments.
+ *
+ * @param aStartupToken the contents of the DESKTOP_STARTUP_ID environment
+ * variable defined by the Startup Notification specification, or the
+ * XDG_ACTIVATION_TOKEN defined by Wayland.
+ * http://standards.freedesktop.org/startup-notification-spec/startup-notification-0.1.txt
+ * https://wayland.app/protocols/xdg-activation-v1
+ *
+ * @param aResponse If there is a response, it will be here. This
+ * includes error messages. The string is allocated using stdlib
+ * string functions, so free it with free().
+ *
+ * @return true if succeeded, false if no running instance was found.
+ *
+ */
+ virtual nsresult SendCommandLine(const char* aProgram, const char* aProfile,
+ int32_t argc, char** argv,
+ const char* aStartupToken, char** aResponse,
+ bool* aSucceeded) = 0;
+};
+
+#endif // TOOLKIT_COMPONENTS_REMOTE_NSREMOTECLIENT_H_
diff --git a/toolkit/components/remote/nsRemoteServer.h b/toolkit/components/remote/nsRemoteServer.h
new file mode 100644
index 0000000000..7c24bdce71
--- /dev/null
+++ b/toolkit/components/remote/nsRemoteServer.h
@@ -0,0 +1,21 @@
+/* -*- 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 __nsRemoteServer_h__
+#define __nsRemoteServer_h__
+
+#include "nsString.h"
+
+class nsRemoteServer {
+ public:
+ virtual ~nsRemoteServer() = default;
+
+ virtual nsresult Startup(const char* aAppName, const char* aProfileName) = 0;
+ virtual void Shutdown() = 0;
+};
+
+#endif // __nsRemoteServer_h__
diff --git a/toolkit/components/remote/nsRemoteService.cpp b/toolkit/components/remote/nsRemoteService.cpp
new file mode 100644
index 0000000000..3e8e4b5cb8
--- /dev/null
+++ b/toolkit/components/remote/nsRemoteService.cpp
@@ -0,0 +1,192 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=8:
+ */
+/* 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/. */
+
+#ifdef MOZ_WIDGET_GTK
+# ifdef MOZ_ENABLE_DBUS
+# include "nsDBusRemoteServer.h"
+# include "nsDBusRemoteClient.h"
+# else
+# include "nsGTKRemoteServer.h"
+# include "nsXRemoteClient.h"
+# endif
+#elif defined(XP_WIN)
+# include "nsWinRemoteServer.h"
+# include "nsWinRemoteClient.h"
+#elif defined(XP_DARWIN)
+# include "nsMacRemoteServer.h"
+# include "nsMacRemoteClient.h"
+#endif
+#include "nsRemoteService.h"
+
+#include "nsIObserverService.h"
+#include "nsString.h"
+#include "nsServiceManagerUtils.h"
+#include "SpecialSystemDirectory.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/UniquePtr.h"
+
+// Time to wait for the remoting service to start
+#define START_TIMEOUT_SEC 5
+#define START_SLEEP_MSEC 100
+
+using namespace mozilla;
+
+extern int gArgc;
+extern char** gArgv;
+
+using namespace mozilla;
+
+NS_IMPL_ISUPPORTS(nsRemoteService, nsIObserver)
+
+nsRemoteService::nsRemoteService(const char* aProgram) : mProgram(aProgram) {
+ ToLowerCase(mProgram);
+}
+
+void nsRemoteService::SetProfile(nsACString& aProfile) { mProfile = aProfile; }
+
+void nsRemoteService::LockStartup() {
+ nsCOMPtr<nsIFile> mutexDir;
+ nsresult rv = GetSpecialSystemDirectory(OS_TemporaryDirectory,
+ getter_AddRefs(mutexDir));
+ NS_ENSURE_SUCCESS_VOID(rv);
+ rv = mutexDir->AppendNative(mProgram);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ const mozilla::TimeStamp epoch = mozilla::TimeStamp::Now();
+ do {
+ // If we have been waiting for another instance to release the lock it will
+ // have deleted the lock directory when doing so we have to make sure it
+ // exists every time we poll for the lock.
+ rv = mutexDir->Create(nsIFile::DIRECTORY_TYPE, 0700);
+ if (NS_SUCCEEDED(rv) || rv == NS_ERROR_FILE_ALREADY_EXISTS) {
+ mRemoteLockDir = mutexDir;
+ } else {
+ NS_WARNING("Unable to create startup lock directory.");
+ return;
+ }
+
+ rv = mRemoteLock.Lock(mRemoteLockDir, nullptr);
+ if (NS_SUCCEEDED(rv)) {
+ return;
+ }
+
+ mRemoteLockDir = nullptr;
+ PR_Sleep(START_SLEEP_MSEC);
+ } while ((mozilla::TimeStamp::Now() - epoch) <
+ mozilla::TimeDuration::FromSeconds(START_TIMEOUT_SEC));
+
+ NS_WARNING("Failed to lock for startup, continuing anyway.");
+}
+
+void nsRemoteService::UnlockStartup() {
+ if (mRemoteLockDir) {
+ mRemoteLock.Unlock();
+ mRemoteLock.Cleanup();
+
+ mRemoteLockDir->Remove(false);
+ mRemoteLockDir = nullptr;
+ }
+}
+
+RemoteResult nsRemoteService::StartClient(const char* aStartupToken) {
+ if (mProfile.IsEmpty()) {
+ return REMOTE_NOT_FOUND;
+ }
+
+ UniquePtr<nsRemoteClient> client;
+#ifdef MOZ_WIDGET_GTK
+# if defined(MOZ_ENABLE_DBUS)
+ client = MakeUnique<nsDBusRemoteClient>();
+# else
+ client = MakeUnique<nsXRemoteClient>();
+# endif
+#elif defined(XP_WIN)
+ client = MakeUnique<nsWinRemoteClient>();
+#elif defined(XP_DARWIN)
+ client = MakeUnique<nsMacRemoteClient>();
+#else
+ return REMOTE_NOT_FOUND;
+#endif
+
+ nsresult rv = client ? client->Init() : NS_ERROR_FAILURE;
+ if (NS_FAILED(rv)) return REMOTE_NOT_FOUND;
+
+ nsCString response;
+ bool success = false;
+ rv =
+ client->SendCommandLine(mProgram.get(), mProfile.get(), gArgc, gArgv,
+ aStartupToken, getter_Copies(response), &success);
+ // did the command fail?
+ if (!success) return REMOTE_NOT_FOUND;
+
+ // The "command not parseable" error is returned when the
+ // nsICommandLineHandler throws a NS_ERROR_ABORT.
+ if (response.EqualsLiteral("500 command not parseable"))
+ return REMOTE_ARG_BAD;
+
+ if (NS_FAILED(rv)) return REMOTE_NOT_FOUND;
+
+ return REMOTE_FOUND;
+}
+
+void nsRemoteService::StartupServer() {
+ if (mRemoteServer) {
+ return;
+ }
+
+ if (mProfile.IsEmpty()) {
+ return;
+ }
+
+#ifdef MOZ_WIDGET_GTK
+# if defined(MOZ_ENABLE_DBUS)
+ mRemoteServer = MakeUnique<nsDBusRemoteServer>();
+# else
+ mRemoteServer = MakeUnique<nsGTKRemoteServer>();
+# endif
+#elif defined(XP_WIN)
+ mRemoteServer = MakeUnique<nsWinRemoteServer>();
+#elif defined(XP_DARWIN)
+ mRemoteServer = MakeUnique<nsMacRemoteServer>();
+#else
+ return;
+#endif
+
+ if (!mRemoteServer) {
+ return;
+ }
+
+ nsresult rv = mRemoteServer->Startup(mProgram.get(), mProfile.get());
+
+ if (NS_FAILED(rv)) {
+ mRemoteServer = nullptr;
+ return;
+ }
+
+ nsCOMPtr<nsIObserverService> obs(
+ do_GetService("@mozilla.org/observer-service;1"));
+ if (obs) {
+ obs->AddObserver(this, "xpcom-shutdown", false);
+ obs->AddObserver(this, "quit-application", false);
+ }
+}
+
+void nsRemoteService::ShutdownServer() { mRemoteServer = nullptr; }
+
+nsRemoteService::~nsRemoteService() {
+ UnlockStartup();
+ ShutdownServer();
+}
+
+NS_IMETHODIMP
+nsRemoteService::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ // This can be xpcom-shutdown or quit-application, but it's the same either
+ // way.
+ ShutdownServer();
+ return NS_OK;
+}
diff --git a/toolkit/components/remote/nsRemoteService.h b/toolkit/components/remote/nsRemoteService.h
new file mode 100644
index 0000000000..3105913a7f
--- /dev/null
+++ b/toolkit/components/remote/nsRemoteService.h
@@ -0,0 +1,49 @@
+/* -*- 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_NSREMOTESERVER_H_
+#define TOOLKIT_COMPONENTS_REMOTE_NSREMOTESERVER_H_
+
+#include "nsRemoteServer.h"
+#include "nsIObserver.h"
+#include "mozilla/UniquePtr.h"
+#include "nsIFile.h"
+#include "nsProfileLock.h"
+
+enum RemoteResult {
+ REMOTE_NOT_FOUND = 0,
+ REMOTE_FOUND = 1,
+ REMOTE_ARG_BAD = 2
+};
+
+class nsRemoteService final : public nsIObserver {
+ public:
+ // We will be a static singleton, so don't use the ordinary methods.
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ explicit nsRemoteService(const char* aProgram);
+ void SetProfile(nsACString& aProfile);
+
+ void LockStartup();
+ void UnlockStartup();
+
+ RemoteResult StartClient(const char* aStartupToken);
+ void StartupServer();
+ void ShutdownServer();
+
+ private:
+ ~nsRemoteService();
+
+ mozilla::UniquePtr<nsRemoteServer> mRemoteServer;
+ nsProfileLock mRemoteLock;
+ nsCOMPtr<nsIFile> mRemoteLockDir;
+ nsCString mProgram;
+ nsCString mProfile;
+};
+
+#endif // TOOLKIT_COMPONENTS_REMOTE_NSREMOTESERVER_H_
diff --git a/toolkit/components/remote/nsUnixRemoteServer.cpp b/toolkit/components/remote/nsUnixRemoteServer.cpp
new file mode 100644
index 0000000000..f26c90af4a
--- /dev/null
+++ b/toolkit/components/remote/nsUnixRemoteServer.cpp
@@ -0,0 +1,113 @@
+/* -*- 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/. */
+
+#include "nsUnixRemoteServer.h"
+#include "nsGTKToolkit.h"
+#include "nsCOMPtr.h"
+#include "nsICommandLineRunner.h"
+#include "nsCommandLine.h"
+#include "nsIFile.h"
+
+// Set desktop startup ID to the passed ID, if there is one, so that any created
+// windows get created with the right window manager metadata, and any windows
+// that get new tabs and are activated also get the right WM metadata.
+// The timestamp will be used if there is no desktop startup ID, or if we're
+// raising an existing window rather than showing a new window for the first
+// time.
+void nsUnixRemoteServer::SetStartupTokenOrTimestamp(
+ const nsACString& aStartupToken, uint32_t aTimestamp) {
+ nsGTKToolkit* toolkit = nsGTKToolkit::GetToolkit();
+ if (!toolkit) {
+ return;
+ }
+
+ if (!aStartupToken.IsEmpty()) {
+ toolkit->SetStartupToken(aStartupToken);
+ }
+
+ toolkit->SetFocusTimestamp(aTimestamp);
+}
+
+static bool FindExtensionParameterInCommand(const char* aParameterName,
+ const nsACString& aCommand,
+ char aSeparator,
+ nsACString* aValue) {
+ nsAutoCString searchFor;
+ searchFor.Append(aSeparator);
+ searchFor.Append(aParameterName);
+ searchFor.Append('=');
+
+ nsACString::const_iterator start, end;
+ aCommand.BeginReading(start);
+ aCommand.EndReading(end);
+ if (!FindInReadable(searchFor, start, end)) return false;
+
+ nsACString::const_iterator charStart, charEnd;
+ charStart = end;
+ aCommand.EndReading(charEnd);
+ nsACString::const_iterator idStart = charStart, idEnd;
+ if (FindCharInReadable(aSeparator, charStart, charEnd)) {
+ idEnd = charStart;
+ } else {
+ idEnd = charEnd;
+ }
+ *aValue = nsDependentCSubstring(idStart, idEnd);
+ return true;
+}
+
+const char* nsUnixRemoteServer::HandleCommandLine(const char* aBuffer,
+ uint32_t aTimestamp) {
+ nsCOMPtr<nsICommandLineRunner> cmdline(new nsCommandLine());
+
+ // the commandline property is constructed as an array of int32_t
+ // followed by a series of null-terminated strings:
+ //
+ // [argc][offsetargv0][offsetargv1...]<workingdir>\0<argv[0]>\0argv[1]...\0
+ // (offset is from the beginning of the buffer)
+
+ int32_t argc = TO_LITTLE_ENDIAN32(*reinterpret_cast<const int32_t*>(aBuffer));
+ const char* wd = aBuffer + ((argc + 1) * sizeof(int32_t));
+
+ nsCOMPtr<nsIFile> lf;
+ nsresult rv =
+ NS_NewNativeLocalFile(nsDependentCString(wd), true, getter_AddRefs(lf));
+ if (NS_FAILED(rv)) return "509 internal error";
+
+ nsAutoCString desktopStartupID;
+
+ const char** argv = (const char**)malloc(sizeof(char*) * argc);
+ if (!argv) return "509 internal error";
+
+ const int32_t* offset = reinterpret_cast<const int32_t*>(aBuffer) + 1;
+
+ for (int i = 0; i < argc; ++i) {
+ argv[i] = aBuffer + TO_LITTLE_ENDIAN32(offset[i]);
+
+ if (i == 0) {
+ nsDependentCString cmd(argv[0]);
+ FindExtensionParameterInCommand("STARTUP_TOKEN", cmd, ' ',
+ &desktopStartupID);
+ }
+ }
+
+ rv = cmdline->Init(argc, argv, lf, nsICommandLine::STATE_REMOTE_AUTO);
+
+ free(argv);
+ if (NS_FAILED(rv)) {
+ return "509 internal error";
+ }
+
+ SetStartupTokenOrTimestamp(desktopStartupID, aTimestamp);
+
+ rv = cmdline->Run();
+
+ if (NS_ERROR_ABORT == rv) return "500 command not parseable";
+
+ if (NS_FAILED(rv)) return "509 internal error";
+
+ return "200 executed command";
+}
diff --git a/toolkit/components/remote/nsUnixRemoteServer.h b/toolkit/components/remote/nsUnixRemoteServer.h
new file mode 100644
index 0000000000..003f2efbf8
--- /dev/null
+++ b/toolkit/components/remote/nsUnixRemoteServer.h
@@ -0,0 +1,28 @@
+/* -*- 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 __nsUnixRemoteServer_h__
+#define __nsUnixRemoteServer_h__
+
+#include "nsStringFwd.h"
+
+#ifdef IS_BIG_ENDIAN
+# define TO_LITTLE_ENDIAN32(x) \
+ ((((x)&0xff000000) >> 24) | (((x)&0x00ff0000) >> 8) | \
+ (((x)&0x0000ff00) << 8) | (((x)&0x000000ff) << 24))
+#else
+# define TO_LITTLE_ENDIAN32(x) (x)
+#endif
+
+class nsUnixRemoteServer {
+ protected:
+ void SetStartupTokenOrTimestamp(const nsACString& aStartupToken,
+ uint32_t aTimestamp);
+ const char* HandleCommandLine(const char* aBuffer, uint32_t aTimestamp);
+};
+
+#endif // __nsGTKRemoteService_h__
diff --git a/toolkit/components/remote/nsWinRemoteClient.cpp b/toolkit/components/remote/nsWinRemoteClient.cpp
new file mode 100644
index 0000000000..4428070bfa
--- /dev/null
+++ b/toolkit/components/remote/nsWinRemoteClient.cpp
@@ -0,0 +1,44 @@
+/* -*- 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/. */
+
+#include "nsWinRemoteClient.h"
+#include <windows.h>
+#include "RemoteUtils.h"
+#include "WinRemoteMessage.h"
+
+using namespace mozilla;
+
+nsresult nsWinRemoteClient::Init() { return NS_OK; }
+
+nsresult nsWinRemoteClient::SendCommandLine(
+ const char* aProgram, const char* aProfile, int32_t argc, char** argv,
+ const char* aStartupToken, char** aResponse, bool* aSucceeded) {
+ *aSucceeded = false;
+
+ nsString className;
+ BuildClassName(aProgram, aProfile, className);
+
+ HWND handle = ::FindWindowW(className.get(), 0);
+
+ if (!handle) {
+ return NS_OK;
+ }
+
+ WCHAR cwd[MAX_PATH];
+ _wgetcwd(cwd, MAX_PATH);
+ WinRemoteMessageSender sender(::GetCommandLineW(), cwd);
+
+ // Bring the already running Mozilla process to the foreground.
+ // nsWindow will restore the window (if minimized) and raise it.
+ ::SetForegroundWindow(handle);
+ ::SendMessageW(handle, WM_COPYDATA, 0,
+ reinterpret_cast<LPARAM>(sender.CopyData()));
+
+ *aSucceeded = true;
+
+ return NS_OK;
+}
diff --git a/toolkit/components/remote/nsWinRemoteClient.h b/toolkit/components/remote/nsWinRemoteClient.h
new file mode 100644
index 0000000000..64859b69a1
--- /dev/null
+++ b/toolkit/components/remote/nsWinRemoteClient.h
@@ -0,0 +1,25 @@
+/* -*- 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 nsWinRemoteClient_h__
+#define nsWinRemoteClient_h__
+
+#include "nscore.h"
+#include "nsRemoteClient.h"
+
+class nsWinRemoteClient : public nsRemoteClient {
+ public:
+ virtual ~nsWinRemoteClient() = default;
+
+ nsresult Init() override;
+
+ nsresult SendCommandLine(const char* aProgram, const char* aProfile,
+ int32_t argc, char** argv, const char* aStartupToken,
+ char** aResponse, bool* aSucceeded) override;
+};
+
+#endif // nsWinRemoteClient_h__
diff --git a/toolkit/components/remote/nsWinRemoteServer.cpp b/toolkit/components/remote/nsWinRemoteServer.cpp
new file mode 100644
index 0000000000..0fa4f5facc
--- /dev/null
+++ b/toolkit/components/remote/nsWinRemoteServer.cpp
@@ -0,0 +1,104 @@
+/* -*- 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/. */
+
+#include "CmdLineAndEnvUtils.h"
+#include "nsWinRemoteServer.h"
+#include "RemoteUtils.h"
+#include "nsCOMPtr.h"
+#include "nsXPCOM.h"
+#include "nsPIDOMWindow.h"
+#include "nsIWindowMediator.h"
+#include "nsIBaseWindow.h"
+#include "nsIWidget.h"
+#include "nsICommandLineRunner.h"
+#include "nsICommandLine.h"
+#include "nsCommandLine.h"
+#include "nsIDocShell.h"
+#include "WinRemoteMessage.h"
+
+HWND hwndForDOMWindow(mozIDOMWindowProxy* window) {
+ if (!window) {
+ return 0;
+ }
+ nsCOMPtr<nsPIDOMWindowOuter> pidomwindow = nsPIDOMWindowOuter::From(window);
+
+ nsCOMPtr<nsIBaseWindow> ppBaseWindow =
+ do_QueryInterface(pidomwindow->GetDocShell());
+ if (!ppBaseWindow) {
+ return 0;
+ }
+
+ nsCOMPtr<nsIWidget> ppWidget;
+ ppBaseWindow->GetMainWidget(getter_AddRefs(ppWidget));
+
+ return (HWND)(ppWidget->GetNativeData(NS_NATIVE_WIDGET));
+}
+
+static nsresult GetMostRecentWindow(mozIDOMWindowProxy** aWindow) {
+ nsresult rv;
+ nsCOMPtr<nsIWindowMediator> med(
+ do_GetService(NS_WINDOWMEDIATOR_CONTRACTID, &rv));
+ if (NS_FAILED(rv)) return rv;
+
+ if (med) return med->GetMostRecentWindow(nullptr, aWindow);
+
+ return NS_ERROR_FAILURE;
+}
+
+LRESULT CALLBACK WindowProc(HWND msgWindow, UINT msg, WPARAM wp, LPARAM lp) {
+ if (msg == WM_COPYDATA) {
+ WinRemoteMessageReceiver receiver;
+ if (NS_SUCCEEDED(receiver.Parse(reinterpret_cast<COPYDATASTRUCT*>(lp)))) {
+ receiver.CommandLineRunner()->Run();
+ } else {
+ NS_ERROR("Error initializing command line.");
+ }
+
+ // Get current window and return its window handle.
+ nsCOMPtr<mozIDOMWindowProxy> win;
+ GetMostRecentWindow(getter_AddRefs(win));
+ return win ? (LRESULT)hwndForDOMWindow(win) : 0;
+ }
+ return DefWindowProcW(msgWindow, msg, wp, lp);
+}
+
+nsresult nsWinRemoteServer::Startup(const char* aAppName,
+ const char* aProfileName) {
+ nsString className;
+ BuildClassName(aAppName, aProfileName, className);
+
+ WNDCLASSW classStruct = {0, // style
+ &WindowProc, // lpfnWndProc
+ 0, // cbClsExtra
+ 0, // cbWndExtra
+ 0, // hInstance
+ 0, // hIcon
+ 0, // hCursor
+ 0, // hbrBackground
+ 0, // lpszMenuName
+ className.get()}; // lpszClassName
+
+ // Register the window class.
+ NS_ENSURE_TRUE(::RegisterClassW(&classStruct), NS_ERROR_FAILURE);
+
+ // Create the window.
+ mHandle = ::CreateWindowW(className.get(),
+ 0, // title
+ WS_CAPTION, // style
+ 0, 0, 0, 0, // x, y, cx, cy
+ 0, // parent
+ 0, // menu
+ 0, // instance
+ 0); // create struct
+
+ return mHandle ? NS_OK : NS_ERROR_FAILURE;
+}
+
+void nsWinRemoteServer::Shutdown() {
+ DestroyWindow(mHandle);
+ mHandle = nullptr;
+}
diff --git a/toolkit/components/remote/nsWinRemoteServer.h b/toolkit/components/remote/nsWinRemoteServer.h
new file mode 100644
index 0000000000..1687e2d44b
--- /dev/null
+++ b/toolkit/components/remote/nsWinRemoteServer.h
@@ -0,0 +1,27 @@
+/* -*- 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 __nsWinRemoteServer_h__
+#define __nsWinRemoteServer_h__
+
+#include "nsRemoteServer.h"
+
+#include <windows.h>
+
+class nsWinRemoteServer final : public nsRemoteServer {
+ public:
+ nsWinRemoteServer() = default;
+ ~nsWinRemoteServer() override { Shutdown(); }
+
+ nsresult Startup(const char* aAppName, const char* aProfileName) override;
+ void Shutdown() override;
+
+ private:
+ HWND mHandle;
+};
+
+#endif // __nsWinRemoteService_h__
diff --git a/toolkit/components/remote/nsXRemoteClient.cpp b/toolkit/components/remote/nsXRemoteClient.cpp
new file mode 100644
index 0000000000..198f7f70c2
--- /dev/null
+++ b/toolkit/components/remote/nsXRemoteClient.cpp
@@ -0,0 +1,653 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=8:
+ */
+/* vim:set ts=8 sw=2 et cindent: */
+/* 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 "nsDebug.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/IntegerPrintfMacros.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/Unused.h"
+#include "nsXRemoteClient.h"
+#include "RemoteUtils.h"
+#include "prsystem.h"
+#include "mozilla/Logging.h"
+#include "prenv.h"
+#include "prdtoa.h"
+#include <X11/Xatom.h>
+#include <limits.h>
+#include <poll.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#define MOZILLA_VERSION_PROP "_MOZILLA_VERSION"
+#define MOZILLA_LOCK_PROP "_MOZILLA_LOCK"
+#define MOZILLA_COMMANDLINE_PROP "_MOZILLA_COMMANDLINE"
+#define MOZILLA_RESPONSE_PROP "_MOZILLA_RESPONSE"
+#define MOZILLA_USER_PROP "_MOZILLA_USER"
+#define MOZILLA_PROFILE_PROP "_MOZILLA_PROFILE"
+#define MOZILLA_PROGRAM_PROP "_MOZILLA_PROGRAM"
+
+#ifdef IS_BIG_ENDIAN
+# define TO_LITTLE_ENDIAN32(x) \
+ ((((x)&0xff000000) >> 24) | (((x)&0x00ff0000) >> 8) | \
+ (((x)&0x0000ff00) << 8) | (((x)&0x000000ff) << 24))
+#else
+# define TO_LITTLE_ENDIAN32(x) (x)
+#endif
+
+#ifndef MAX_PATH
+# ifdef PATH_MAX
+# define MAX_PATH PATH_MAX
+# else
+# define MAX_PATH 1024
+# endif
+#endif
+
+using mozilla::LogLevel;
+using mozilla::Unused;
+
+static mozilla::LazyLogModule sRemoteLm("nsXRemoteClient");
+
+static int (*sOldHandler)(Display*, XErrorEvent*);
+static bool sGotBadWindow;
+
+nsXRemoteClient::nsXRemoteClient() {
+ mDisplay = 0;
+ mInitialized = false;
+ mMozVersionAtom = 0;
+ mMozLockAtom = 0;
+ mMozCommandLineAtom = 0;
+ mMozResponseAtom = 0;
+ mMozWMStateAtom = 0;
+ mMozUserAtom = 0;
+ mMozProfileAtom = 0;
+ mMozProgramAtom = 0;
+ mLockData = 0;
+ MOZ_LOG(sRemoteLm, LogLevel::Debug, ("nsXRemoteClient::nsXRemoteClient"));
+}
+
+nsXRemoteClient::~nsXRemoteClient() {
+ MOZ_LOG(sRemoteLm, LogLevel::Debug, ("nsXRemoteClient::~nsXRemoteClient"));
+ if (mInitialized) Shutdown();
+}
+
+// Minimize the roundtrips to the X-server
+static const char* XAtomNames[] = {
+ MOZILLA_VERSION_PROP, MOZILLA_LOCK_PROP, MOZILLA_RESPONSE_PROP,
+ "WM_STATE", MOZILLA_USER_PROP, MOZILLA_PROFILE_PROP,
+ MOZILLA_PROGRAM_PROP, MOZILLA_COMMANDLINE_PROP};
+static Atom XAtoms[MOZ_ARRAY_LENGTH(XAtomNames)];
+
+nsresult nsXRemoteClient::Init() {
+ MOZ_LOG(sRemoteLm, LogLevel::Debug, ("nsXRemoteClient::Init"));
+
+ if (mInitialized) return NS_OK;
+
+ // try to open the display
+ mDisplay = XOpenDisplay(0);
+ if (!mDisplay) return NS_ERROR_FAILURE;
+
+ // get our atoms
+ XInternAtoms(mDisplay, const_cast<char**>(XAtomNames),
+ MOZ_ARRAY_LENGTH(XAtomNames), False, XAtoms);
+
+ int i = 0;
+ mMozVersionAtom = XAtoms[i++];
+ mMozLockAtom = XAtoms[i++];
+ mMozResponseAtom = XAtoms[i++];
+ mMozWMStateAtom = XAtoms[i++];
+ mMozUserAtom = XAtoms[i++];
+ mMozProfileAtom = XAtoms[i++];
+ mMozProgramAtom = XAtoms[i++];
+ mMozCommandLineAtom = XAtoms[i];
+
+ mInitialized = true;
+
+ return NS_OK;
+}
+
+void nsXRemoteClient::Shutdown(void) {
+ MOZ_LOG(sRemoteLm, LogLevel::Debug, ("nsXRemoteClient::Shutdown"));
+
+ if (!mInitialized) return;
+
+ // shut everything down
+ XCloseDisplay(mDisplay);
+ mDisplay = 0;
+ mInitialized = false;
+ if (mLockData) {
+ free(mLockData);
+ mLockData = 0;
+ }
+}
+
+static int HandleBadWindow(Display* display, XErrorEvent* event) {
+ if (event->error_code == BadWindow) {
+ sGotBadWindow = true;
+ return 0; // ignored
+ }
+
+ return (*sOldHandler)(display, event);
+}
+
+nsresult nsXRemoteClient::SendCommandLine(
+ const char* aProgram, const char* aProfile, int32_t argc, char** argv,
+ const char* aStartupToken, char** aResponse, bool* aWindowFound) {
+ NS_ENSURE_TRUE(aProgram, NS_ERROR_INVALID_ARG);
+
+ MOZ_LOG(sRemoteLm, LogLevel::Debug, ("nsXRemoteClient::SendCommandLine"));
+
+ *aWindowFound = false;
+
+ // FindBestWindow() iterates down the window hierarchy, so catch X errors
+ // when windows get destroyed before being accessed.
+ sOldHandler = XSetErrorHandler(HandleBadWindow);
+
+ Window w = FindBestWindow(aProgram, aProfile);
+
+ nsresult rv = NS_OK;
+
+ if (w) {
+ // ok, let the caller know that we at least found a window.
+ *aWindowFound = true;
+
+ // Ignore BadWindow errors up to this point. The last request from
+ // FindBestWindow() was a synchronous XGetWindowProperty(), so no need to
+ // Sync. Leave the error handler installed to detect if w gets destroyed.
+ sGotBadWindow = false;
+
+ // make sure we get the right events on that window
+ XSelectInput(mDisplay, w, (PropertyChangeMask | StructureNotifyMask));
+
+ bool destroyed = false;
+
+ // get the lock on the window
+ rv = GetLock(w, &destroyed);
+
+ if (NS_SUCCEEDED(rv)) {
+ // send our command
+ rv = DoSendCommandLine(w, argc, argv, aStartupToken, aResponse,
+ &destroyed);
+
+ // if the window was destroyed, don't bother trying to free the
+ // lock.
+ if (!destroyed) FreeLock(w); // doesn't really matter what this returns
+ }
+ }
+
+ XSetErrorHandler(sOldHandler);
+
+ MOZ_LOG(sRemoteLm, LogLevel::Debug,
+ ("SendCommandInternal returning 0x%" PRIx32 "\n",
+ static_cast<uint32_t>(rv)));
+
+ return rv;
+}
+
+Window nsXRemoteClient::CheckWindow(Window aWindow) {
+ Atom type = None;
+ int format;
+ unsigned long nitems, bytesafter;
+ unsigned char* data;
+ Window innerWindow;
+
+ XGetWindowProperty(mDisplay, aWindow, mMozWMStateAtom, 0, 0, False,
+ AnyPropertyType, &type, &format, &nitems, &bytesafter,
+ &data);
+
+ if (type) {
+ XFree(data);
+ return aWindow;
+ }
+
+ // didn't find it here so check the children of this window
+ innerWindow = CheckChildren(aWindow);
+
+ if (innerWindow) return innerWindow;
+
+ return aWindow;
+}
+
+Window nsXRemoteClient::CheckChildren(Window aWindow) {
+ Window root, parent;
+ Window* children;
+ unsigned int nchildren;
+ unsigned int i;
+ Atom type = None;
+ int format;
+ unsigned long nitems, after;
+ unsigned char* data;
+ Window retval = None;
+
+ if (!XQueryTree(mDisplay, aWindow, &root, &parent, &children, &nchildren))
+ return None;
+
+ // scan the list first before recursing into the list of windows
+ // which can get quite deep.
+ for (i = 0; !retval && (i < nchildren); i++) {
+ XGetWindowProperty(mDisplay, children[i], mMozWMStateAtom, 0, 0, False,
+ AnyPropertyType, &type, &format, &nitems, &after, &data);
+ if (type) {
+ XFree(data);
+ retval = children[i];
+ }
+ }
+
+ // otherwise recurse into the list
+ for (i = 0; !retval && (i < nchildren); i++) {
+ retval = CheckChildren(children[i]);
+ }
+
+ if (children) XFree((char*)children);
+
+ return retval;
+}
+
+nsresult nsXRemoteClient::GetLock(Window aWindow, bool* aDestroyed) {
+ bool locked = false;
+ bool waited = false;
+ *aDestroyed = false;
+
+ nsresult rv = NS_OK;
+
+ if (!mLockData) {
+ char pidstr[32];
+ char sysinfobuf[SYS_INFO_BUFFER_LENGTH];
+ SprintfLiteral(pidstr, "pid%d@", getpid());
+ PRStatus status;
+ status =
+ PR_GetSystemInfo(PR_SI_HOSTNAME, sysinfobuf, SYS_INFO_BUFFER_LENGTH);
+ if (status != PR_SUCCESS) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // allocate enough space for the string plus the terminating
+ // char
+ mLockData = (char*)malloc(strlen(pidstr) + strlen(sysinfobuf) + 1);
+ if (!mLockData) return NS_ERROR_OUT_OF_MEMORY;
+
+ strcpy(mLockData, pidstr);
+ if (!strcat(mLockData, sysinfobuf)) return NS_ERROR_FAILURE;
+ }
+
+ do {
+ int result;
+ Atom actual_type;
+ int actual_format;
+ unsigned long nitems, bytes_after;
+ unsigned char* data = 0;
+
+ XGrabServer(mDisplay);
+
+ result = XGetWindowProperty(
+ mDisplay, aWindow, mMozLockAtom, 0, (65536 / sizeof(long)),
+ False, /* don't delete */
+ XA_STRING, &actual_type, &actual_format, &nitems, &bytes_after, &data);
+
+ // aWindow may have been destroyed before XSelectInput was processed, in
+ // which case there may not be any DestroyNotify event in the queue to
+ // tell us. XGetWindowProperty() was synchronous so error responses have
+ // now been processed, setting sGotBadWindow.
+ if (sGotBadWindow) {
+ *aDestroyed = true;
+ rv = NS_ERROR_FAILURE;
+ } else if (result != Success || actual_type == None) {
+ /* It's not now locked - lock it. */
+ XChangeProperty(mDisplay, aWindow, mMozLockAtom, XA_STRING, 8,
+ PropModeReplace, (unsigned char*)mLockData,
+ strlen(mLockData));
+ locked = True;
+ }
+
+ XUngrabServer(mDisplay);
+ XFlush(mDisplay); // ungrab now!
+
+ if (!locked && !NS_FAILED(rv)) {
+ /* We tried to grab the lock this time, and failed because someone
+ else is holding it already. So, wait for a PropertyDelete event
+ to come in, and try again. */
+ MOZ_LOG(sRemoteLm, LogLevel::Debug,
+ ("window 0x%x is locked by %s; waiting...\n",
+ (unsigned int)aWindow, data));
+ waited = True;
+ while (true) {
+ XEvent event;
+ int poll_retval;
+ struct pollfd pfd;
+
+ pfd.fd = ConnectionNumber(mDisplay);
+ pfd.events = POLLIN;
+
+ poll_retval = poll(&pfd, 1, 10 * 1000);
+ // did we time out?
+ if (poll_retval == 0) {
+ MOZ_LOG(sRemoteLm, LogLevel::Debug,
+ ("timed out waiting for window\n"));
+ rv = NS_ERROR_FAILURE;
+ break;
+ }
+ MOZ_LOG(sRemoteLm, LogLevel::Debug, ("xevent...\n"));
+ // FIXME check the return value from this?
+ XNextEvent(mDisplay, &event);
+ if (event.xany.type == DestroyNotify &&
+ event.xdestroywindow.window == aWindow) {
+ *aDestroyed = true;
+ rv = NS_ERROR_FAILURE;
+ break;
+ }
+ if (event.xany.type == PropertyNotify &&
+ event.xproperty.state == PropertyDelete &&
+ event.xproperty.window == aWindow &&
+ event.xproperty.atom == mMozLockAtom) {
+ /* Ok! Someone deleted their lock, so now we can try
+ again. */
+ MOZ_LOG(
+ sRemoteLm, LogLevel::Debug,
+ ("(0x%x unlocked, trying again...)\n", (unsigned int)aWindow));
+ break;
+ }
+ }
+ }
+ if (data) XFree(data);
+ } while (!locked && !NS_FAILED(rv));
+
+ if (waited && locked) {
+ MOZ_LOG(sRemoteLm, LogLevel::Debug, ("obtained lock.\n"));
+ } else if (*aDestroyed) {
+ MOZ_LOG(sRemoteLm, LogLevel::Debug,
+ ("window 0x%x unexpectedly destroyed.\n", (unsigned int)aWindow));
+ }
+
+ return rv;
+}
+
+Window nsXRemoteClient::FindBestWindow(const char* aProgram,
+ const char* aProfile) {
+ Window root = RootWindowOfScreen(DefaultScreenOfDisplay(mDisplay));
+ Window bestWindow = 0;
+ Window root2, parent, *kids;
+ unsigned int nkids;
+
+ // Get a list of the children of the root window, walk the list
+ // looking for the best window that fits the criteria.
+ if (!XQueryTree(mDisplay, root, &root2, &parent, &kids, &nkids)) {
+ MOZ_LOG(sRemoteLm, LogLevel::Debug,
+ ("XQueryTree failed in nsXRemoteClient::FindBestWindow"));
+ return 0;
+ }
+
+ if (!(kids && nkids)) {
+ MOZ_LOG(sRemoteLm, LogLevel::Debug, ("root window has no children"));
+ return 0;
+ }
+
+ // We'll walk the list of windows looking for a window that best
+ // fits the criteria here.
+
+ for (unsigned int i = 0; i < nkids; i++) {
+ Atom type;
+ int format;
+ unsigned long nitems, bytesafter;
+ unsigned char* data_return = 0;
+ Window w;
+ w = kids[i];
+ // find the inner window with WM_STATE on it
+ w = CheckWindow(w);
+
+ int status = XGetWindowProperty(
+ mDisplay, w, mMozVersionAtom, 0, (65536 / sizeof(long)), False,
+ XA_STRING, &type, &format, &nitems, &bytesafter, &data_return);
+
+ if (!data_return) continue;
+
+ double version = PR_strtod((char*)data_return, nullptr);
+ XFree(data_return);
+
+ if (!(version >= 5.1 && version < 6)) continue;
+
+ data_return = 0;
+
+ if (status != Success || type == None) continue;
+
+ // Check that this window is from the right program.
+ Unused << XGetWindowProperty(
+ mDisplay, w, mMozProgramAtom, 0, (65536 / sizeof(long)), False,
+ XA_STRING, &type, &format, &nitems, &bytesafter, &data_return);
+
+ // If the return name is not the same as this program name, we don't want
+ // this window.
+ if (data_return) {
+ if (strcmp(aProgram, (const char*)data_return)) {
+ XFree(data_return);
+ continue;
+ }
+
+ // This is actually the success condition.
+ XFree(data_return);
+ } else {
+ // Doesn't support the protocol, even though the user
+ // requested it. So we're not going to use this window.
+ continue;
+ }
+
+ // Check to see if it has the user atom on that window. If there
+ // is then we need to make sure that it matches what we have.
+ const char* username = PR_GetEnv("LOGNAME");
+
+ if (username) {
+ Unused << XGetWindowProperty(
+ mDisplay, w, mMozUserAtom, 0, (65536 / sizeof(long)), False,
+ XA_STRING, &type, &format, &nitems, &bytesafter, &data_return);
+
+ // if there's a username compare it with what we have
+ if (data_return) {
+ // If the IDs aren't equal, we don't want this window.
+ if (strcmp(username, (const char*)data_return)) {
+ XFree(data_return);
+ continue;
+ }
+
+ XFree(data_return);
+ }
+ }
+
+ // Check to see if there's a profile name on this window. If
+ // there is, then we need to make sure it matches what someone
+ // passed in.
+ Unused << XGetWindowProperty(
+ mDisplay, w, mMozProfileAtom, 0, (65536 / sizeof(long)), False,
+ XA_STRING, &type, &format, &nitems, &bytesafter, &data_return);
+
+ // If there's a profile compare it with what we have
+ if (data_return) {
+ // If the profiles aren't equal, we don't want this window.
+ if (strcmp(aProfile, (const char*)data_return)) {
+ XFree(data_return);
+ continue;
+ }
+
+ XFree(data_return);
+ } else {
+ // This isn't the window for this profile.
+ continue;
+ }
+
+ // Check to see if the window supports the new command-line passing
+ // protocol, if that is requested.
+
+ // If we got this far, this is the best window. It passed
+ // all the tests.
+ bestWindow = w;
+ break;
+ }
+
+ if (kids) XFree((char*)kids);
+
+ return bestWindow;
+}
+
+nsresult nsXRemoteClient::FreeLock(Window aWindow) {
+ int result;
+ Atom actual_type;
+ int actual_format;
+ unsigned long nitems, bytes_after;
+ unsigned char* data = 0;
+
+ result = XGetWindowProperty(
+ mDisplay, aWindow, mMozLockAtom, 0, (65536 / sizeof(long)),
+ True, /* atomic delete after */
+ XA_STRING, &actual_type, &actual_format, &nitems, &bytes_after, &data);
+ if (result != Success) {
+ MOZ_LOG(sRemoteLm, LogLevel::Debug,
+ ("unable to read and delete " MOZILLA_LOCK_PROP " property\n"));
+ return NS_ERROR_FAILURE;
+ }
+ if (!data || !*data) {
+ MOZ_LOG(sRemoteLm, LogLevel::Debug,
+ ("invalid data on " MOZILLA_LOCK_PROP " of window 0x%x.\n",
+ (unsigned int)aWindow));
+ return NS_ERROR_FAILURE;
+ } else if (strcmp((char*)data, mLockData)) {
+ MOZ_LOG(sRemoteLm, LogLevel::Debug,
+ (MOZILLA_LOCK_PROP " was stolen! Expected \"%s\", saw \"%s\"!\n",
+ mLockData, data));
+ return NS_ERROR_FAILURE;
+ }
+
+ if (data) XFree(data);
+ return NS_OK;
+}
+
+nsresult nsXRemoteClient::DoSendCommandLine(Window aWindow, int32_t argc,
+ char** argv,
+ const char* aStartupToken,
+ char** aResponse,
+ bool* aDestroyed) {
+ *aDestroyed = false;
+
+ int commandLineLength;
+ char* commandLine =
+ ConstructCommandLine(argc, argv, aStartupToken, &commandLineLength);
+ XChangeProperty(mDisplay, aWindow, mMozCommandLineAtom, XA_STRING, 8,
+ PropModeReplace, (unsigned char*)commandLine,
+ commandLineLength);
+ free(commandLine);
+
+ if (!WaitForResponse(aWindow, aResponse, aDestroyed, mMozCommandLineAtom))
+ return NS_ERROR_FAILURE;
+
+ return NS_OK;
+}
+
+bool nsXRemoteClient::WaitForResponse(Window aWindow, char** aResponse,
+ bool* aDestroyed, Atom aCommandAtom) {
+ bool done = false;
+ bool accepted = false;
+
+ while (!done) {
+ XEvent event;
+ XNextEvent(mDisplay, &event);
+ if (event.xany.type == DestroyNotify &&
+ event.xdestroywindow.window == aWindow) {
+ /* Print to warn user...*/
+ MOZ_LOG(sRemoteLm, LogLevel::Debug,
+ ("window 0x%x was destroyed.\n", (unsigned int)aWindow));
+ *aResponse = strdup("Window was destroyed while reading response.");
+ *aDestroyed = true;
+ return false;
+ }
+ if (event.xany.type == PropertyNotify &&
+ event.xproperty.state == PropertyNewValue &&
+ event.xproperty.window == aWindow &&
+ event.xproperty.atom == mMozResponseAtom) {
+ Atom actual_type;
+ int actual_format;
+ unsigned long nitems, bytes_after;
+ unsigned char* data = 0;
+ Bool result;
+ result = XGetWindowProperty(mDisplay, aWindow, mMozResponseAtom, 0,
+ (65536 / sizeof(long)),
+ True, /* atomic delete after */
+ XA_STRING, &actual_type, &actual_format,
+ &nitems, &bytes_after, &data);
+ if (result != Success) {
+ MOZ_LOG(
+ sRemoteLm, LogLevel::Debug,
+ ("failed reading " MOZILLA_RESPONSE_PROP " from window 0x%0x.\n",
+ (unsigned int)aWindow));
+ *aResponse = strdup("Internal error reading response from window.");
+ done = true;
+ } else if (!data || strlen((char*)data) < 5) {
+ MOZ_LOG(sRemoteLm, LogLevel::Debug,
+ ("invalid data on " MOZILLA_RESPONSE_PROP
+ " property of window 0x%0x.\n",
+ (unsigned int)aWindow));
+ *aResponse = strdup("Server returned invalid data in response.");
+ done = true;
+ } else if (*data == '1') { /* positive preliminary reply */
+ MOZ_LOG(sRemoteLm, LogLevel::Debug, ("%s\n", data + 4));
+ /* keep going */
+ done = false;
+ }
+
+ else if (!strncmp((char*)data, "200", 3)) { /* positive completion */
+ *aResponse = strdup((char*)data);
+ accepted = true;
+ done = true;
+ }
+
+ else if (*data == '2') { /* positive completion */
+ MOZ_LOG(sRemoteLm, LogLevel::Debug, ("%s\n", data + 4));
+ *aResponse = strdup((char*)data);
+ accepted = true;
+ done = true;
+ }
+
+ else if (*data == '3') { /* positive intermediate reply */
+ MOZ_LOG(sRemoteLm, LogLevel::Debug,
+ ("internal error: "
+ "server wants more information? (%s)\n",
+ data));
+ *aResponse = strdup((char*)data);
+ done = true;
+ }
+
+ else if (*data == '4' || /* transient negative completion */
+ *data == '5') { /* permanent negative completion */
+ MOZ_LOG(sRemoteLm, LogLevel::Debug, ("%s\n", data + 4));
+ *aResponse = strdup((char*)data);
+ done = true;
+ }
+
+ else {
+ MOZ_LOG(
+ sRemoteLm, LogLevel::Debug,
+ ("unrecognised " MOZILLA_RESPONSE_PROP " from window 0x%x: %s\n",
+ (unsigned int)aWindow, data));
+ *aResponse = strdup((char*)data);
+ done = true;
+ }
+
+ if (data) XFree(data);
+ }
+
+ else if (event.xany.type == PropertyNotify &&
+ event.xproperty.window == aWindow &&
+ event.xproperty.state == PropertyDelete &&
+ event.xproperty.atom == aCommandAtom) {
+ MOZ_LOG(sRemoteLm, LogLevel::Debug,
+ ("(server 0x%x has accepted " MOZILLA_COMMANDLINE_PROP ".)\n",
+ (unsigned int)aWindow));
+ }
+ }
+
+ return accepted;
+}
diff --git a/toolkit/components/remote/nsXRemoteClient.h b/toolkit/components/remote/nsXRemoteClient.h
new file mode 100644
index 0000000000..ad8e7d3787
--- /dev/null
+++ b/toolkit/components/remote/nsXRemoteClient.h
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; tab-width: 8; 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 <X11/X.h>
+#include <X11/Xlib.h>
+
+#include "nsRemoteClient.h"
+
+class nsXRemoteClient : public nsRemoteClient {
+ public:
+ nsXRemoteClient();
+ ~nsXRemoteClient();
+
+ virtual nsresult Init() override;
+ virtual nsresult SendCommandLine(const char* aProgram, const char* aProfile,
+ int32_t argc, char** argv,
+ const char* aStartupToken, char** aResponse,
+ bool* aSucceeded) override;
+ void Shutdown();
+
+ private:
+ Window CheckWindow(Window aWindow);
+ Window CheckChildren(Window aWindow);
+ nsresult GetLock(Window aWindow, bool* aDestroyed);
+ nsresult FreeLock(Window aWindow);
+ Window FindBestWindow(const char* aProgram, const char* aProfile);
+ nsresult DoSendCommandLine(Window aWindow, int32_t argc, char** argv,
+ const char* aStartupToken, char** aResponse,
+ bool* aDestroyed);
+ bool WaitForResponse(Window aWindow, char** aResponse, bool* aDestroyed,
+ Atom aCommandAtom);
+
+ Display* mDisplay;
+
+ Atom mMozVersionAtom;
+ Atom mMozLockAtom;
+ Atom mMozCommandLineAtom;
+ Atom mMozResponseAtom;
+ Atom mMozWMStateAtom;
+ Atom mMozUserAtom;
+ Atom mMozProfileAtom;
+ Atom mMozProgramAtom;
+
+ char* mLockData;
+
+ bool mInitialized;
+};
diff --git a/toolkit/components/remote/nsXRemoteServer.cpp b/toolkit/components/remote/nsXRemoteServer.cpp
new file mode 100644
index 0000000000..1f97b18e20
--- /dev/null
+++ b/toolkit/components/remote/nsXRemoteServer.cpp
@@ -0,0 +1,161 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=8:
+ */
+/* 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/ArrayUtils.h"
+
+#include "nsXRemoteServer.h"
+#include "nsCOMPtr.h"
+#include "nsICommandLine.h"
+
+#include "nsIWidget.h"
+#include "nsAppShellCID.h"
+#include "nsPIDOMWindow.h"
+#include "mozilla/X11Util.h"
+
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "prenv.h"
+#include "nsCRT.h"
+
+#include "nsXULAppAPI.h"
+
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+
+using namespace mozilla;
+
+#define MOZILLA_VERSION_PROP "_MOZILLA_VERSION"
+#define MOZILLA_LOCK_PROP "_MOZILLA_LOCK"
+#define MOZILLA_RESPONSE_PROP "_MOZILLA_RESPONSE"
+#define MOZILLA_USER_PROP "_MOZILLA_USER"
+#define MOZILLA_PROFILE_PROP "_MOZILLA_PROFILE"
+#define MOZILLA_PROGRAM_PROP "_MOZILLA_PROGRAM"
+#define MOZILLA_COMMANDLINE_PROP "_MOZILLA_COMMANDLINE"
+
+const unsigned char kRemoteVersion[] = "5.1";
+
+// Minimize the roundtrips to the X server by getting all the atoms at once
+static const char* XAtomNames[] = {
+ MOZILLA_VERSION_PROP, MOZILLA_LOCK_PROP, MOZILLA_RESPONSE_PROP,
+ MOZILLA_USER_PROP, MOZILLA_PROFILE_PROP, MOZILLA_PROGRAM_PROP,
+ MOZILLA_COMMANDLINE_PROP};
+static Atom XAtoms[MOZ_ARRAY_LENGTH(XAtomNames)];
+
+Atom nsXRemoteServer::sMozVersionAtom;
+Atom nsXRemoteServer::sMozLockAtom;
+Atom nsXRemoteServer::sMozResponseAtom;
+Atom nsXRemoteServer::sMozUserAtom;
+Atom nsXRemoteServer::sMozProfileAtom;
+Atom nsXRemoteServer::sMozProgramAtom;
+Atom nsXRemoteServer::sMozCommandLineAtom;
+
+nsXRemoteServer::nsXRemoteServer() = default;
+
+void nsXRemoteServer::XRemoteBaseStartup(const char* aAppName,
+ const char* aProfileName) {
+ EnsureAtoms();
+
+ mAppName = aAppName;
+ ToLowerCase(mAppName);
+
+ mProfileName = aProfileName;
+}
+
+void nsXRemoteServer::HandleCommandsFor(Window aWindowId) {
+ // set our version
+ XChangeProperty(mozilla::DefaultXDisplay(), aWindowId, sMozVersionAtom,
+ XA_STRING, 8, PropModeReplace, kRemoteVersion,
+ sizeof(kRemoteVersion) - 1);
+
+ // get our username
+ unsigned char* logname;
+ logname = (unsigned char*)PR_GetEnv("LOGNAME");
+ if (logname) {
+ // set the property on the window if it's available
+ XChangeProperty(mozilla::DefaultXDisplay(), aWindowId, sMozUserAtom,
+ XA_STRING, 8, PropModeReplace, logname,
+ strlen((char*)logname));
+ }
+
+ XChangeProperty(mozilla::DefaultXDisplay(), aWindowId, sMozProgramAtom,
+ XA_STRING, 8, PropModeReplace, (unsigned char*)mAppName.get(),
+ mAppName.Length());
+
+ XChangeProperty(mozilla::DefaultXDisplay(), aWindowId, sMozProfileAtom,
+ XA_STRING, 8, PropModeReplace,
+ (unsigned char*)mProfileName.get(), mProfileName.Length());
+}
+
+bool nsXRemoteServer::HandleNewProperty(XID aWindowId, Display* aDisplay,
+ Time aEventTime, Atom aChangedAtom) {
+ if (aChangedAtom == sMozCommandLineAtom) {
+ // We got a new command atom.
+ int result;
+ Atom actual_type;
+ int actual_format;
+ unsigned long nitems, bytes_after;
+ char* data = 0;
+
+ result = XGetWindowProperty(aDisplay, aWindowId, aChangedAtom,
+ 0, /* long_offset */
+ (65536 / sizeof(long)), /* long_length */
+ X11True, /* atomic delete after */
+ XA_STRING, /* req_type */
+ &actual_type, /* actual_type return */
+ &actual_format, /* actual_format_return */
+ &nitems, /* nitems_return */
+ &bytes_after, /* bytes_after_return */
+ (unsigned char**)&data); /* prop_return
+ (we only care
+ about the first ) */
+
+ // Failed to get property off the window?
+ if (result != Success) return false;
+
+ // Failed to get the data off the window or it was the wrong type?
+ if (!data || !TO_LITTLE_ENDIAN32(*reinterpret_cast<int32_t*>(data)))
+ return false;
+
+ // cool, we got the property data.
+ const char* response = HandleCommandLine(data, aEventTime);
+
+ // put the property onto the window as the response
+ XChangeProperty(aDisplay, aWindowId, sMozResponseAtom, XA_STRING, 8,
+ PropModeReplace, (const unsigned char*)response,
+ strlen(response));
+ XFree(data);
+ return true;
+ }
+
+ if (aChangedAtom == sMozResponseAtom) {
+ // client accepted the response. party on wayne.
+ return true;
+ }
+
+ else if (aChangedAtom == sMozLockAtom) {
+ // someone locked the window
+ return true;
+ }
+
+ return false;
+}
+
+void nsXRemoteServer::EnsureAtoms(void) {
+ if (sMozVersionAtom) return;
+
+ XInternAtoms(mozilla::DefaultXDisplay(), const_cast<char**>(XAtomNames),
+ ArrayLength(XAtomNames), X11False, XAtoms);
+
+ int i = 0;
+ sMozVersionAtom = XAtoms[i++];
+ sMozLockAtom = XAtoms[i++];
+ sMozResponseAtom = XAtoms[i++];
+ sMozUserAtom = XAtoms[i++];
+ sMozProfileAtom = XAtoms[i++];
+ sMozProgramAtom = XAtoms[i++];
+ sMozCommandLineAtom = XAtoms[i];
+}
diff --git a/toolkit/components/remote/nsXRemoteServer.h b/toolkit/components/remote/nsXRemoteServer.h
new file mode 100644
index 0000000000..405bafe789
--- /dev/null
+++ b/toolkit/components/remote/nsXRemoteServer.h
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=8:
+ */
+/* 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 NSXREMOTESERVER_H
+#define NSXREMOTESERVER_H
+
+#include "nsString.h"
+#include "nsRemoteServer.h"
+#include "nsUnixRemoteServer.h"
+
+#include <X11/Xlib.h>
+#include <X11/X.h>
+
+/**
+ Base class for GTK/Qt remote service
+*/
+class nsXRemoteServer : public nsRemoteServer, public nsUnixRemoteServer {
+ protected:
+ nsXRemoteServer();
+ bool HandleNewProperty(Window aWindowId, Display* aDisplay, Time aEventTime,
+ Atom aChangedAtom);
+ void XRemoteBaseStartup(const char* aAppName, const char* aProfileName);
+ void HandleCommandsFor(Window aWindowId);
+
+ private:
+ void EnsureAtoms();
+
+ nsCString mAppName;
+ nsCString mProfileName;
+
+ static Atom sMozVersionAtom;
+ static Atom sMozLockAtom;
+ static Atom sMozResponseAtom;
+ static Atom sMozUserAtom;
+ static Atom sMozProfileAtom;
+ static Atom sMozProgramAtom;
+ static Atom sMozCommandLineAtom;
+};
+
+#endif // NSXREMOTESERVER_H