Bug 1650637 - Part3: Introduce WinRemoteMessageSender/Receiver. r=mossop

This patch introduces two classes WinRemoteMessageSender/Receiver to
generate/parse a payload of WM_COPYDATA message which is sent from
nsWinRemoteClient to nsWinRemoteServer.

And, we change the encoding of the payload from utf-8 to utf-16,
incrementing the message version from 1 to 2, because our parsing
logic on nsWinRemoteServer does not support a variable-width character
encoding like utf-8.

Differential Revision: https://phabricator.services.mozilla.com/D84228
This commit is contained in:
Toshihito Kikuchi 2020-07-30 00:26:40 +00:00
Родитель e08d50175e
Коммит 30b7f20b67
7 изменённых файлов: 280 добавлений и 36 удалений

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

@ -0,0 +1,129 @@
/* -*- 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(char* 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(char* aBuffer) {
CommandLineParserWin<char> parser;
parser.HandleCommandLine(aBuffer);
// Moving |wdpath| to the working dir followed by the first null char.
char* wdpath = aBuffer;
while (*wdpath) {
++wdpath;
}
++wdpath;
nsCOMPtr<nsIFile> workingDir;
NS_NewLocalFile(NS_ConvertUTF8toUTF16(wdpath), false,
getter_AddRefs(workingDir));
mCommandLine = new nsCommandLine();
return mCommandLine->Init(parser.Argc(), parser.Argv(), workingDir,
nsICommandLine::STATE_REMOTE_AUTO);
}
nsresult WinRemoteMessageReceiver::ParseV2(char16_t* aBuffer) {
CommandLineParserWin<char16_t> parser;
parser.HandleCommandLine(aBuffer);
// Moving |wdpath| to the working dir followed by the first null char.
char16_t* wdpath = aBuffer;
while (*wdpath) {
++wdpath;
}
++wdpath;
nsCOMPtr<nsIFile> workingDir;
NS_NewLocalFile(nsDependentString(wdpath), 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(COPYDATASTRUCT* aMessageData) {
switch (static_cast<WinRemoteMessageVersion>(aMessageData->dwData)) {
case WinRemoteMessageVersion::CommandLineOnly:
return ParseV0(reinterpret_cast<char*>(aMessageData->lpData));
case WinRemoteMessageVersion::CommandLineAndWorkingDir:
return ParseV1(reinterpret_cast<char*>(aMessageData->lpData));
case WinRemoteMessageVersion::CommandLineAndWorkingDirInUtf16:
return ParseV2(reinterpret_cast<char16_t*>(aMessageData->lpData));
default:
MOZ_ASSERT_UNREACHABLE("Unsupported message version");
return NS_ERROR_FAILURE;
}
}
nsICommandLineRunner* WinRemoteMessageReceiver::CommandLineRunner() {
return mCommandLine;
}

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

@ -0,0 +1,68 @@
/* -*- 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"
// 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(char* aBuffer);
nsresult ParseV1(char* aBuffer);
nsresult ParseV2(char16_t* aBuffer);
public:
WinRemoteMessageReceiver() = default;
WinRemoteMessageReceiver(const WinRemoteMessageReceiver&) = delete;
WinRemoteMessageReceiver(WinRemoteMessageReceiver&&) = delete;
WinRemoteMessageReceiver& operator=(const WinRemoteMessageReceiver&) = delete;
WinRemoteMessageReceiver& operator=(WinRemoteMessageReceiver&&) = delete;
nsresult Parse(COPYDATASTRUCT* aMessageData);
nsICommandLineRunner* CommandLineRunner();
};
#endif // __WinRemoteMessage_h__

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

@ -35,6 +35,7 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
SOURCES += [
'nsWinRemoteClient.cpp',
'nsWinRemoteServer.cpp',
'WinRemoteMessage.cpp',
]
if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
SOURCES += [

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

@ -8,6 +8,7 @@
#include "nsWinRemoteClient.h"
#include <windows.h>
#include "RemoteUtils.h"
#include "WinRemoteMessage.h"
using namespace mozilla;
@ -27,25 +28,15 @@ nsresult nsWinRemoteClient::SendCommandLine(
return NS_OK;
}
WCHAR* cmd = ::GetCommandLineW();
WCHAR cwd[MAX_PATH];
_wgetcwd(cwd, MAX_PATH);
WinRemoteMessageSender sender(::GetCommandLineW(), cwd);
// Construct a narrow UTF8 buffer <commandline>\0<workingdir>\0
NS_ConvertUTF16toUTF8 utf8buffer(cmd);
utf8buffer.Append('\0');
WCHAR* cwdPtr = cwd;
AppendUTF16toUTF8(MakeStringSpan(reinterpret_cast<char16_t*>(cwdPtr)),
utf8buffer);
utf8buffer.Append('\0');
// We used to set dwData to zero, when we didn't send the working dir.
// Now we're using it as a version number.
COPYDATASTRUCT cds = {1, utf8buffer.Length(), (void*)utf8buffer.get()};
// Bring the already running Mozilla process to the foreground.
// nsWindow will restore the window (if minimized) and raise it.
::SetForegroundWindow(handle);
::SendMessage(handle, WM_COPYDATA, 0, (LPARAM)&cds);
::SendMessage(handle, WM_COPYDATA, 0,
reinterpret_cast<LPARAM>(sender.CopyData()));
*aSucceeded = true;

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

@ -18,6 +18,7 @@
#include "nsICommandLine.h"
#include "nsCommandLine.h"
#include "nsIDocShell.h"
#include "WinRemoteMessage.h"
HWND hwndForDOMWindow(mozIDOMWindowProxy* window) {
if (!window) {
@ -50,29 +51,9 @@ static nsresult GetMostRecentWindow(mozIDOMWindowProxy** aWindow) {
LRESULT CALLBACK WindowProc(HWND msgWindow, UINT msg, WPARAM wp, LPARAM lp) {
if (msg == WM_COPYDATA) {
// This is an incoming request.
COPYDATASTRUCT* cds = (COPYDATASTRUCT*)lp;
nsCOMPtr<nsIFile> workingDir;
if (1 >= cds->dwData) {
char* wdpath = (char*)cds->lpData;
// skip the command line, and get the working dir of the
// other process, which is after the first null char
while (*wdpath) ++wdpath;
++wdpath;
NS_NewLocalFile(NS_ConvertUTF8toUTF16(wdpath), false,
getter_AddRefs(workingDir));
}
mozilla::CommandLineParserWin<char> parser;
parser.HandleCommandLine(reinterpret_cast<char*>(cds->lpData));
nsCOMPtr<nsICommandLineRunner> cmdLine(new nsCommandLine());
if (NS_SUCCEEDED(cmdLine->Init(parser.Argc(), parser.Argv(), workingDir,
nsICommandLine::STATE_REMOTE_AUTO))) {
cmdLine->Run();
WinRemoteMessageReceiver receiver;
if (NS_SUCCEEDED(receiver.Parse(reinterpret_cast<COPYDATASTRUCT*>(lp)))) {
receiver.CommandLineRunner()->Run();
} else {
NS_ERROR("Error initializing command line.");
}

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

@ -8,6 +8,7 @@
#include "mozilla/AssembleCmdLine.h"
#include "mozilla/CmdLineAndEnvUtils.h"
#include "mozilla/UniquePtrExtensions.h"
#include "WinRemoteMessage.h"
using namespace mozilla;
@ -21,6 +22,10 @@ struct TestCase {
#define OMEGA_IN_UTF8 "\xe3\x82\xaa\xe3\x83\xa1\xe3\x82\xac"
#define ALPHA_IN_UTF16 L"\u30A2\u30EB\u30D5\u30A1"
#define OMEGA_IN_UTF16 L"\u30AA\u30E1\u30AC"
#define UPPER_CYRILLIC_P_IN_UTF8 "\xd0\xa0"
#define LOWER_CYRILLIC_P_IN_UTF8 "\xd1\x80"
#define UPPER_CYRILLIC_P_IN_UTF16 L"\u0420"
#define LOWER_CYRILLIC_P_IN_UTF16 L"\u0440"
TestCase<char> testCases[] = {
// Copied from TestXREMakeCommandLineWin.ini
@ -101,3 +106,68 @@ TEST(CommandLineParserWin, HandleCommandLine)
EXPECT_EQ(testCase.mArgs[parser.Argc()], nullptr);
}
}
TEST(WinRemoteMessage, SendReceive)
{
const char kCommandline[] =
"dummy.exe /arg1 --arg2 \"3rd arg\" "
"4th=\"" UPPER_CYRILLIC_P_IN_UTF8 " " LOWER_CYRILLIC_P_IN_UTF8 "\"";
const wchar_t kCommandlineW[] =
L"dummy.exe /arg1 --arg2 \"3rd arg\" "
L"4th=\"" UPPER_CYRILLIC_P_IN_UTF16 L" " LOWER_CYRILLIC_P_IN_UTF16 L"\"";
const wchar_t* kExpectedArgsW[] = {
L"-arg1", L"-arg2", L"3rd arg",
L"4th=" UPPER_CYRILLIC_P_IN_UTF16 L" " LOWER_CYRILLIC_P_IN_UTF16};
char workingDirA[MAX_PATH];
wchar_t workingDirW[MAX_PATH];
EXPECT_NE(getcwd(workingDirA, MAX_PATH), nullptr);
EXPECT_NE(_wgetcwd(workingDirW, MAX_PATH), nullptr);
WinRemoteMessageSender v0(kCommandline);
WinRemoteMessageSender v1(kCommandline, workingDirA);
WinRemoteMessageSender v2(kCommandlineW, workingDirW);
WinRemoteMessageReceiver receiver;
int32_t len;
nsAutoString arg;
nsCOMPtr<nsIFile> workingDir;
receiver.Parse(v0.CopyData());
EXPECT_TRUE(NS_SUCCEEDED(receiver.CommandLineRunner()->GetLength(&len)));
EXPECT_EQ(len, ArrayLength(kExpectedArgsW));
for (int i = 0; i < ArrayLength(kExpectedArgsW); ++i) {
EXPECT_TRUE(
NS_SUCCEEDED(receiver.CommandLineRunner()->GetArgument(i, arg)));
EXPECT_STREQ(arg.get(), kExpectedArgsW[i]);
}
EXPECT_EQ(receiver.CommandLineRunner()->GetWorkingDirectory(
getter_AddRefs(workingDir)),
NS_ERROR_NOT_INITIALIZED);
receiver.Parse(v1.CopyData());
EXPECT_TRUE(NS_SUCCEEDED(receiver.CommandLineRunner()->GetLength(&len)));
EXPECT_EQ(len, ArrayLength(kExpectedArgsW));
for (int i = 0; i < ArrayLength(kExpectedArgsW); ++i) {
EXPECT_TRUE(
NS_SUCCEEDED(receiver.CommandLineRunner()->GetArgument(i, arg)));
EXPECT_STREQ(arg.get(), kExpectedArgsW[i]);
}
EXPECT_TRUE(NS_SUCCEEDED(receiver.CommandLineRunner()->GetWorkingDirectory(
getter_AddRefs(workingDir))));
EXPECT_TRUE(NS_SUCCEEDED(workingDir->GetPath(arg)));
EXPECT_STREQ(arg.get(), workingDirW);
receiver.Parse(v2.CopyData());
EXPECT_TRUE(NS_SUCCEEDED(receiver.CommandLineRunner()->GetLength(&len)));
EXPECT_EQ(len, ArrayLength(kExpectedArgsW));
for (int i = 0; i < ArrayLength(kExpectedArgsW); ++i) {
EXPECT_TRUE(
NS_SUCCEEDED(receiver.CommandLineRunner()->GetArgument(i, arg)));
EXPECT_STREQ(arg.get(), kExpectedArgsW[i]);
}
EXPECT_TRUE(NS_SUCCEEDED(receiver.CommandLineRunner()->GetWorkingDirectory(
getter_AddRefs(workingDir))));
EXPECT_TRUE(NS_SUCCEEDED(workingDir->GetPath(arg)));
EXPECT_STREQ(arg.get(), workingDirW);
}

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

@ -10,6 +10,10 @@ UNIFIED_SOURCES = [
'TestCompatVersionCompare.cpp',
]
LOCAL_INCLUDES += [
'/toolkit/components/remote',
]
if CONFIG['OS_TARGET'] == 'WINNT':
UNIFIED_SOURCES += [
'TestAssembleCommandLineWin.cpp',