gecko-dev/toolkit/xre/nsNativeAppSupportWin.cpp
Ehsan Akhgari e5e885ae31 Bug 1521000 - Part 2: Adjust our clang-format rules to include spaces after the hash for nested preprocessor directives r=sylvestre
# ignore-this-changeset

--HG--
extra : amend_source : 7221c8d15a765df71171099468e7c7faa648f37c
extra : histedit_source : a0cce6015636202bff09e35a13f72e03257a7695
2019-01-18 10:16:18 +01:00

1441 lines
47 KiB
C++

/* -*- Mode: C++; tab-width: 4; 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 "nsNativeAppSupportBase.h"
#include "nsNativeAppSupportWin.h"
#include "nsAppRunner.h"
#include "nsContentUtils.h"
#include "nsXULAppAPI.h"
#include "nsString.h"
#include "nsIBrowserDOMWindow.h"
#include "nsCommandLine.h"
#include "nsCOMPtr.h"
#include "nsIComponentManager.h"
#include "nsIServiceManager.h"
#include "nsIDOMChromeWindow.h"
#include "nsXPCOM.h"
#include "nsISupportsPrimitives.h"
#include "nsIWindowWatcher.h"
#include "nsPIDOMWindow.h"
#include "nsGlobalWindow.h"
#include "nsIDocShell.h"
#include "nsIDocShellTreeItem.h"
#include "nsIBaseWindow.h"
#include "nsIWidget.h"
#include "nsIAppShellService.h"
#include "nsIXULWindow.h"
#include "nsIInterfaceRequestor.h"
#include "nsIInterfaceRequestorUtils.h"
#include "nsIPromptService.h"
#include "nsNetCID.h"
#include "nsNetUtil.h"
#include "mozilla/Services.h"
#include "nsIFile.h"
#include "nsIObserver.h"
#include "nsIObserverService.h"
#include "nsIWebNavigation.h"
#include "nsIWindowMediator.h"
#include "nsNativeCharsetUtils.h"
#include "nsIAppStartup.h"
#include "mozilla/Assertions.h"
#include "mozilla/dom/Location.h"
#include <windows.h>
#include <shellapi.h>
#include <ddeml.h>
#include <stdlib.h>
#include <stdio.h>
#include <io.h>
#include <direct.h>
#include <fcntl.h>
using namespace mozilla;
static HWND hwndForDOMWindow(mozIDOMWindowProxy *);
static nsresult GetMostRecentWindow(const char16_t *aType,
mozIDOMWindowProxy **aWindow) {
nsresult rv;
nsCOMPtr<nsIWindowMediator> med(
do_GetService(NS_WINDOWMEDIATOR_CONTRACTID, &rv));
if (NS_FAILED(rv)) return rv;
if (med) return med->GetMostRecentWindow(aType, aWindow);
return NS_ERROR_FAILURE;
}
static void activateWindow(mozIDOMWindowProxy *win) {
// Try to get native window handle.
HWND hwnd = hwndForDOMWindow(win);
if (hwnd) {
// Restore the window if it is minimized.
if (::IsIconic(hwnd)) {
::ShowWindow(hwnd, SW_RESTORE);
}
// Use the OS call, if possible.
::SetForegroundWindow(hwnd);
} else {
// Use internal method.
nsCOMPtr<nsPIDOMWindowOuter> piWin = nsPIDOMWindowOuter::From(win);
piWin->Focus();
}
}
#ifdef DEBUG_law
# undef MOZ_DEBUG_DDE
# define MOZ_DEBUG_DDE 1
#endif
// Simple Win32 mutex wrapper.
struct Win32Mutex {
explicit Win32Mutex(const char16_t *name)
: mName(name), mHandle(0), mState(-1) {
mHandle = CreateMutexW(0, FALSE, mName.get());
#if MOZ_DEBUG_DDE
printf("CreateMutex error = 0x%08X\n", (int)GetLastError());
#endif
}
~Win32Mutex() {
if (mHandle) {
// Make sure we release it if we own it.
Unlock();
BOOL rc MOZ_MAYBE_UNUSED = CloseHandle(mHandle);
#if MOZ_DEBUG_DDE
if (!rc) {
printf("CloseHandle error = 0x%08X\n", (int)GetLastError());
}
#endif
}
}
BOOL Lock(DWORD timeout) {
if (mHandle) {
#if MOZ_DEBUG_DDE
printf("Waiting (%d msec) for DDE mutex...\n", (int)timeout);
#endif
mState = WaitForSingleObject(mHandle, timeout);
#if MOZ_DEBUG_DDE
printf("...wait complete, result = 0x%08X, GetLastError=0x%08X\n",
(int)mState, (int)::GetLastError());
#endif
return mState == WAIT_OBJECT_0 || mState == WAIT_ABANDONED;
} else {
return FALSE;
}
}
void Unlock() {
if (mHandle && mState == WAIT_OBJECT_0) {
#if MOZ_DEBUG_DDE
printf("Releasing DDE mutex\n");
#endif
ReleaseMutex(mHandle);
mState = -1;
}
}
private:
nsString mName;
HANDLE mHandle;
DWORD mState;
};
/* DDE Notes
*
* This section describes the Win32 DDE service implementation for
* Mozilla. DDE is used on Win32 platforms to communicate between
* separate instances of mozilla.exe (or other Mozilla-based
* executables), or, between the Win32 desktop shell and Mozilla.
*
* The first instance of Mozilla will become the "server" and
* subsequent executables (and the shell) will use DDE to send
* requests to that process. The requests are DDE "execute" requests
* that pass the command line arguments.
*
* Mozilla registers the DDE application "Mozilla" and currently
* supports only the "WWW_OpenURL" topic. This should be reasonably
* compatible with applications that interfaced with Netscape
* Communicator (and its predecessors?). Note that even that topic
* may not be supported in a compatible fashion as the command-line
* options for Mozilla are different than for Communiator.
*
* It is imperative that at most one instance of Mozilla execute in
* "server mode" at any one time. The "native app support" in Mozilla
* on Win32 ensures that only the server process performs XPCOM
* initialization (that is not required for subsequent client processes
* to communicate with the server process).
*
* To guarantee that only one server starts up, a Win32 "mutex" is used
* to ensure only one process executes the server-detection code. That
* code consists of initializing DDE and doing a DdeConnect to Mozilla's
* application/topic. If that connection succeeds, then a server process
* must be running already.
*
* Otherwise, no server has started. In that case, the current process
* calls DdeNameService to register that application/topic. Only at that
* point does the mutex get released.
*
* There are a couple of subtleties that one should be aware of:
*
* 1. It is imperative that DdeInitialize be called only after the mutex
* lock has been obtained. The reason is that at shutdown, DDE
* notifications go out to all initialized DDE processes. Thus, if
* the mutex is owned by a terminating intance of Mozilla, then
* calling DdeInitialize and then WaitForSingleObject will cause the
* DdeUninitialize from the terminating process to "hang" until the
* process waiting for the mutex times out (and can then service the
* notification that the DDE server is terminating). So, don't mess
* with the sequence of things in the startup/shutdown logic.
*
* 2. All mutex requests are made with a reasonably long timeout value and
* are designed to "fail safe" (i.e., a timeout is treated as failure).
*
* 3. An attempt has been made to minimize the degree to which the main
* Mozilla application logic needs to be aware of the DDE mechanisms
* implemented herein. As a result, this module surfaces a very
* large-grained interface, consisting of simple start/stop methods.
* As a consequence, details of certain scenarios can be "lost."
* Particularly, incoming DDE requests can arrive after this module
* initiates the DDE server, but before Mozilla is initialized to the
* point where those requests can be serviced (e.g., open a browser
* window to a particular URL). Since the client process sends the
* request early on, it may not be prepared to respond to that error.
* Thus, such situations may fail silently. The design goal is that
* they fail harmlessly. Refinements on this point will be made as
* details emerge (and time permits).
*/
/* Update 2001 March
*
* A significant DDE bug in Windows is causing Mozilla to get wedged at
* startup. This is detailed in Bugzill bug 53952
* (http://bugzilla.mozilla.org/show_bug.cgi?id=53952).
*
* To resolve this, we are using a new strategy:
* o Use a "message window" to detect that Mozilla is already running and
* to pass requests from a second instance back to the first;
* o Run only as a "DDE server" (not as DDE client); this avoids the
* problematic call to DDEConnect().
*
* We still use the mutex semaphore to protect the code that detects
* whether Mozilla is already running.
*/
/* Update 2007 January
*
* A change in behavior was implemented in July 2004 which made the
* application on launch to add and on quit to remove the ddexec registry key.
* See bug 246078.
* Windows Vista has changed the methods used to set an application as default
* and the new methods are incompatible with removing the ddeexec registry key.
* See bug 353089.
*
* OS DDE Sequence:
* 1. OS checks if the dde name is registered.
* 2. If it is registered the OS sends a DDE request with the WWW_OpenURL topic
* and the params as specified in the default value of the ddeexec registry
* key for the verb (e.g. open).
* 3. If it isn't registered the OS launches the executable defined in the
* verb's (e.g. open) command registry key.
* 4. If the ifexec registry key is not present the OS sends a DDE request with
* the WWW_OpenURL topic and the params as specified in the default value of
* the ddeexec registry key for the verb (e.g. open).
* 5. If the ifexec registry key is present the OS sends a DDE request with the
* WWW_OpenURL topic and the params as specified in the ifexec registry key
* for the verb (e.g. open).
*
* Application DDE Sequence:
* 1. If the application is running a DDE request is received with the
* WWW_OpenURL topic and the params as specified in the default value of the
* ddeexec registry key (e.g. "%1",,0,0,,,, where '%1' is the url to open)
* for the verb (e.g. open).
* 2. If the application is not running it is launched with the --requestPending
* and the --url argument.
* 2.1 If the application does not need to restart and the --requestPending
* argument is present the accompanying url will not be used. Instead the
* application will wait for the DDE message to open the url.
* 2.2 If the application needs to restart the --requestPending argument is
* removed from the arguments used to restart the application and the url
* will be handled normally.
*
* Note: Due to a bug in IE the ifexec key should not be used (see bug 355650).
*/
class nsNativeAppSupportWin : public nsNativeAppSupportBase,
public nsIObserver {
public:
NS_DECL_NSIOBSERVER
NS_DECL_ISUPPORTS_INHERITED
// Overrides of base implementation.
NS_IMETHOD Start(bool *aResult) override;
NS_IMETHOD Stop(bool *aResult) override;
NS_IMETHOD Quit() override;
NS_IMETHOD Enable() override;
// The "old" Start method (renamed).
NS_IMETHOD StartDDE();
// Utility function to handle a Win32-specific command line
// option: "--console", which dynamically creates a Windows
// console.
void CheckConsole();
private:
~nsNativeAppSupportWin() {}
static void HandleCommandLine(const char *aCmdLineString,
nsIFile *aWorkingDir, uint32_t aState);
static HDDEDATA CALLBACK HandleDDENotification(UINT uType, UINT uFmt,
HCONV hconv, HSZ hsz1,
HSZ hsz2, HDDEDATA hdata,
ULONG_PTR dwData1,
ULONG_PTR dwData2);
static void ParseDDEArg(HSZ args, int index, nsString &string);
static void ParseDDEArg(const WCHAR *args, int index, nsString &aString);
static HDDEDATA CreateDDEData(DWORD value);
static HDDEDATA CreateDDEData(LPBYTE value, DWORD len);
static bool InitTopicStrings();
static int FindTopic(HSZ topic);
static void ActivateLastWindow();
static nsresult OpenWindow(const char *urlstr, const char *args);
static nsresult OpenBrowserWindow();
static void SetupSysTrayIcon();
static void RemoveSysTrayIcon();
static int mConversations;
enum {
topicOpenURL,
topicActivate,
topicCancelProgress,
topicVersion,
topicRegisterViewer,
topicUnRegisterViewer,
topicGetWindowInfo,
// Note: Insert new values above this line!!!!!
topicCount // Count of the number of real topics
};
static HSZ mApplication, mTopics[topicCount];
static DWORD mInstance;
static bool mCanHandleRequests;
static char16_t mMutexName[];
friend struct MessageWindow;
}; // nsNativeAppSupportWin
NS_INTERFACE_MAP_BEGIN(nsNativeAppSupportWin)
NS_INTERFACE_MAP_ENTRY(nsIObserver)
NS_INTERFACE_MAP_END_INHERITING(nsNativeAppSupportBase)
NS_IMPL_ADDREF_INHERITED(nsNativeAppSupportWin, nsNativeAppSupportBase)
NS_IMPL_RELEASE_INHERITED(nsNativeAppSupportWin, nsNativeAppSupportBase)
void UseParentConsole() {
if (AttachConsole(ATTACH_PARENT_PROCESS)) {
// Redirect the standard streams to the existing console, but
// only if they haven't been redirected to a valid file.
// Visual Studio's _fileno() returns -2 for the standard
// streams if they aren't associated with an output stream.
if (_fileno(stdout) == -2) {
freopen("CONOUT$", "w", stdout);
}
// There is no CONERR$, so use CONOUT$ for stderr as well.
if (_fileno(stderr) == -2) {
freopen("CONOUT$", "w", stderr);
}
if (_fileno(stdin) == -2) {
freopen("CONIN$", "r", stdin);
}
}
}
void nsNativeAppSupportWin::CheckConsole() {
for (int i = 1; i < gArgc; ++i) {
if (strcmp("-console", gArgv[i]) == 0 ||
strcmp("--console", gArgv[i]) == 0 ||
strcmp("/console", gArgv[i]) == 0) {
if (AllocConsole()) {
// Redirect the standard streams to the new console, but
// only if they haven't been redirected to a valid file.
// Visual Studio's _fileno() returns -2 for the standard
// streams if they aren't associated with an output stream.
if (_fileno(stdout) == -2) {
freopen("CONOUT$", "w", stdout);
}
// There is no CONERR$, so use CONOUT$ for stderr as well.
if (_fileno(stderr) == -2) {
freopen("CONOUT$", "w", stderr);
}
if (_fileno(stdin) == -2) {
freopen("CONIN$", "r", stdin);
}
}
} else if (strcmp("-attach-console", gArgv[i]) == 0 ||
strcmp("--attach-console", gArgv[i]) == 0 ||
strcmp("/attach-console", gArgv[i]) == 0) {
UseParentConsole();
}
}
}
// Create and return an instance of class nsNativeAppSupportWin.
nsresult NS_CreateNativeAppSupport(nsINativeAppSupport **aResult) {
nsNativeAppSupportWin *pNative = new nsNativeAppSupportWin;
if (!pNative) return NS_ERROR_OUT_OF_MEMORY;
// Check for dynamic console creation request.
pNative->CheckConsole();
*aResult = pNative;
NS_ADDREF(*aResult);
return NS_OK;
}
// Constants
#define MOZ_DDE_APPLICATION "Mozilla"
#define MOZ_MUTEX_NAMESPACE L"Local\\"
#define MOZ_STARTUP_MUTEX_NAME L"StartupMutex"
#define MOZ_DDE_START_TIMEOUT 30000
#define MOZ_DDE_STOP_TIMEOUT 15000
#define MOZ_DDE_EXEC_TIMEOUT 15000
// The array entries must match the enum ordering!
const char *const topicNames[] = {"WWW_OpenURL", "WWW_Activate",
"WWW_CancelProgress", "WWW_Version",
"WWW_RegisterViewer", "WWW_UnRegisterViewer",
"WWW_GetWindowInfo"};
// Static member definitions.
int nsNativeAppSupportWin::mConversations = 0;
HSZ nsNativeAppSupportWin::mApplication = 0;
HSZ nsNativeAppSupportWin::mTopics[nsNativeAppSupportWin::topicCount] = {0};
DWORD nsNativeAppSupportWin::mInstance = 0;
bool nsNativeAppSupportWin::mCanHandleRequests = false;
char16_t nsNativeAppSupportWin::mMutexName[128] = {0};
// Message window encapsulation.
struct MessageWindow {
// ctor/dtor are simplistic
MessageWindow() {
// Try to find window.
mHandle = ::FindWindowW(className(), 0);
}
// Act like an HWND.
operator HWND() { return mHandle; }
// Class name: appName + "MessageWindow"
static const wchar_t *className() {
static wchar_t classNameBuffer[128];
static wchar_t *mClassName = 0;
if (!mClassName) {
::_snwprintf(classNameBuffer,
128, // size of classNameBuffer in PRUnichars
L"%s%s",
static_cast<const wchar_t *>(
NS_ConvertUTF8toUTF16(gAppData->remotingName).get()),
L"MessageWindow");
mClassName = classNameBuffer;
}
return mClassName;
}
// Create: Register class and create window.
NS_IMETHOD Create() {
WNDCLASSW classStruct = {0, // style
&MessageWindow::WindowProc, // lpfnWndProc
0, // cbClsExtra
0, // cbWndExtra
0, // hInstance
0, // hIcon
0, // hCursor
0, // hbrBackground
0, // lpszMenuName
className()}; // lpszClassName
// Register the window class.
NS_ENSURE_TRUE(::RegisterClassW(&classStruct), NS_ERROR_FAILURE);
// Create the window.
NS_ENSURE_TRUE((mHandle = ::CreateWindowW(className(),
0, // title
WS_CAPTION, // style
0, 0, 0, 0, // x, y, cx, cy
0, // parent
0, // menu
0, // instance
0)), // create struct
NS_ERROR_FAILURE);
#if MOZ_DEBUG_DDE
printf("Message window = 0x%08X\n", (int)mHandle);
#endif
return NS_OK;
}
// Destory: Get rid of window and reset mHandle.
NS_IMETHOD Destroy() {
nsresult retval = NS_OK;
if (mHandle) {
// DestroyWindow can only destroy windows created from
// the same thread.
BOOL desRes = DestroyWindow(mHandle);
if (FALSE != desRes) {
mHandle = nullptr;
} else {
retval = NS_ERROR_FAILURE;
}
}
return retval;
}
// SendRequest: Pass the command line via WM_COPYDATA to message window.
NS_IMETHOD SendRequest() {
WCHAR *cmd = ::GetCommandLineW();
WCHAR cwd[MAX_PATH];
_wgetcwd(cwd, MAX_PATH);
// 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(mHandle);
::SendMessage(mHandle, WM_COPYDATA, 0, (LPARAM)&cds);
return NS_OK;
}
// Window proc.
static LRESULT CALLBACK WindowProc(HWND msgWindow, UINT msg, WPARAM wp,
LPARAM lp) {
if (msg == WM_COPYDATA) {
if (!nsNativeAppSupportWin::mCanHandleRequests) return FALSE;
// This is an incoming request.
COPYDATASTRUCT *cds = (COPYDATASTRUCT *)lp;
#if MOZ_DEBUG_DDE
printf("Incoming request: %s\n", (const char *)cds->lpData);
#endif
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;
#ifdef MOZ_DEBUG_DDE
printf("Working dir: %s\n", wdpath);
#endif
NS_NewLocalFile(NS_ConvertUTF8toUTF16(wdpath), false,
getter_AddRefs(workingDir));
}
(void)nsNativeAppSupportWin::HandleCommandLine(
(char *)cds->lpData, workingDir, nsICommandLine::STATE_REMOTE_AUTO);
// Get current window and return its window handle.
nsCOMPtr<mozIDOMWindowProxy> win;
GetMostRecentWindow(0, getter_AddRefs(win));
return win ? (LRESULT)hwndForDOMWindow(win) : 0;
}
return DefWindowProc(msgWindow, msg, wp, lp);
}
private:
HWND mHandle;
}; // struct MessageWindow
/* Start: Tries to find the "message window" to determine if it
* exists. If so, then Mozilla is already running. In that
* case, we use the handle to the "message" window and send
* a request corresponding to this process's command line
* options.
*
* If not, then this is the first instance of Mozilla. In
* that case, we create and set up the message window.
*
* The checking for existence of the message window must
* be protected by use of a mutex semaphore.
*/
NS_IMETHODIMP
nsNativeAppSupportWin::Start(bool *aResult) {
NS_ENSURE_ARG(aResult);
NS_ENSURE_TRUE(mInstance == 0, NS_ERROR_NOT_INITIALIZED);
NS_ENSURE_STATE(gAppData);
if (getenv("MOZ_NO_REMOTE")) {
*aResult = true;
return NS_OK;
}
nsresult rv = NS_ERROR_FAILURE;
*aResult = false;
// Grab mutex first.
// Build mutex name from app name.
::_snwprintf(
reinterpret_cast<wchar_t *>(mMutexName),
sizeof mMutexName / sizeof(char16_t), L"%s%s%s", MOZ_MUTEX_NAMESPACE,
static_cast<const wchar_t *>(NS_ConvertUTF8toUTF16(gAppData->name).get()),
MOZ_STARTUP_MUTEX_NAME);
Win32Mutex startupLock = Win32Mutex(mMutexName);
NS_ENSURE_TRUE(startupLock.Lock(MOZ_DDE_START_TIMEOUT), NS_ERROR_FAILURE);
// Search for existing message window.
MessageWindow msgWindow;
if ((HWND)msgWindow) {
// We are a client process. Pass request to message window.
rv = msgWindow.SendRequest();
} else {
// We will be server.
rv = msgWindow.Create();
if (NS_SUCCEEDED(rv)) {
// Start up DDE server.
this->StartDDE();
// Tell caller to spin message loop.
*aResult = true;
}
}
startupLock.Unlock();
return rv;
}
bool nsNativeAppSupportWin::InitTopicStrings() {
for (int i = 0; i < topicCount; i++) {
if (!(mTopics[i] = DdeCreateStringHandleA(
mInstance, const_cast<char *>(topicNames[i]), CP_WINANSI))) {
return false;
}
}
return true;
}
int nsNativeAppSupportWin::FindTopic(HSZ topic) {
for (int i = 0; i < topicCount; i++) {
if (DdeCmpStringHandles(topic, mTopics[i]) == 0) {
return i;
}
}
return -1;
}
// Start DDE server.
//
// This used to be the Start() method when we were using DDE as the
// primary IPC mechanism between secondary Mozilla processes and the
// initial "server" process.
//
// Now, it simply initializes the DDE server. The caller must check
// that this process is to be the server, and, must acquire the DDE
// startup mutex semaphore prior to calling this routine. See ::Start(),
// above.
NS_IMETHODIMP
nsNativeAppSupportWin::StartDDE() {
NS_ENSURE_TRUE(mInstance == 0, NS_ERROR_NOT_INITIALIZED);
// Initialize DDE.
NS_ENSURE_TRUE(DMLERR_NO_ERROR ==
DdeInitialize(&mInstance,
nsNativeAppSupportWin::HandleDDENotification,
APPCLASS_STANDARD, 0),
NS_ERROR_FAILURE);
// Allocate DDE strings.
NS_ENSURE_TRUE(
(mApplication = DdeCreateStringHandleA(
mInstance, (char *)(const char *)gAppData->name, CP_WINANSI)) &&
InitTopicStrings(),
NS_ERROR_FAILURE);
// Next step is to register a DDE service.
NS_ENSURE_TRUE(DdeNameService(mInstance, mApplication, 0, DNS_REGISTER),
NS_ERROR_FAILURE);
#if MOZ_DEBUG_DDE
printf("DDE server started\n");
#endif
return NS_OK;
}
// If no DDE conversations are pending, terminate DDE.
NS_IMETHODIMP
nsNativeAppSupportWin::Stop(bool *aResult) {
NS_ENSURE_ARG(aResult);
NS_ENSURE_TRUE(mInstance, NS_ERROR_NOT_INITIALIZED);
nsresult rv = NS_OK;
*aResult = true;
Win32Mutex ddeLock(mMutexName);
if (ddeLock.Lock(MOZ_DDE_STOP_TIMEOUT)) {
if (mConversations == 0) {
this->Quit();
} else {
*aResult = false;
}
ddeLock.Unlock();
} else {
// No DDE application name specified, but that's OK. Just
// forge ahead.
*aResult = true;
}
return rv;
}
NS_IMETHODIMP
nsNativeAppSupportWin::Observe(nsISupports *aSubject, const char *aTopic,
const char16_t *aData) {
if (strcmp(aTopic, "quit-application") == 0) {
Quit();
} else {
NS_ERROR("Unexpected observer topic.");
}
return NS_OK;
}
// Terminate DDE regardless.
NS_IMETHODIMP
nsNativeAppSupportWin::Quit() {
// If another process wants to look for the message window, they need
// to wait to hold the lock, in which case they will not find the
// window as we will destroy ours under our lock.
// When the mutex goes off the stack, it is unlocked via destructor.
Win32Mutex mutexLock(mMutexName);
NS_ENSURE_TRUE(mutexLock.Lock(MOZ_DDE_START_TIMEOUT), NS_ERROR_FAILURE);
// If we've got a message window to receive IPC or new window requests,
// get rid of it as we are shutting down.
// Note: Destroy calls DestroyWindow, which will only work on a window
// created by the same thread.
MessageWindow mw;
mw.Destroy();
if (mInstance) {
// Unregister application name.
DdeNameService(mInstance, mApplication, 0, DNS_UNREGISTER);
// Clean up strings.
if (mApplication) {
DdeFreeStringHandle(mInstance, mApplication);
mApplication = 0;
}
for (int i = 0; i < topicCount; i++) {
if (mTopics[i]) {
DdeFreeStringHandle(mInstance, mTopics[i]);
mTopics[i] = 0;
}
}
DdeUninitialize(mInstance);
mInstance = 0;
#if MOZ_DEBUG_DDE
printf("DDE server stopped\n");
#endif
}
return NS_OK;
}
NS_IMETHODIMP
nsNativeAppSupportWin::Enable() {
mCanHandleRequests = true;
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
if (obs) {
obs->AddObserver(this, "quit-application", false);
} else {
NS_ERROR("No observer service?");
}
return NS_OK;
}
#if MOZ_DEBUG_DDE
// Macro to generate case statement for a given XTYP value.
# define XTYP_CASE(t) \
case t: \
result = #t; \
break
static nsCString uTypeDesc(UINT uType) {
nsCString result;
switch (uType) {
XTYP_CASE(XTYP_ADVSTART);
XTYP_CASE(XTYP_CONNECT);
XTYP_CASE(XTYP_ADVREQ);
XTYP_CASE(XTYP_REQUEST);
XTYP_CASE(XTYP_WILDCONNECT);
XTYP_CASE(XTYP_ADVDATA);
XTYP_CASE(XTYP_EXECUTE);
XTYP_CASE(XTYP_POKE);
XTYP_CASE(XTYP_ADVSTOP);
XTYP_CASE(XTYP_CONNECT_CONFIRM);
XTYP_CASE(XTYP_DISCONNECT);
XTYP_CASE(XTYP_ERROR);
XTYP_CASE(XTYP_MONITOR);
XTYP_CASE(XTYP_REGISTER);
XTYP_CASE(XTYP_XACT_COMPLETE);
XTYP_CASE(XTYP_UNREGISTER);
default:
result = "XTYP_?????";
}
return result;
}
static nsCString hszValue(DWORD instance, HSZ hsz) {
// Extract string from HSZ.
nsCString result("[");
DWORD len = DdeQueryString(instance, hsz, nullptr, nullptr, CP_WINANSI);
if (len) {
char buffer[256];
DdeQueryString(instance, hsz, buffer, sizeof buffer, CP_WINANSI);
result += buffer;
}
result += "]";
return result;
}
#endif
// Utility function to escape double-quotes within a string.
static void escapeQuotes(nsAString &aString) {
int32_t offset = -1;
while (1) {
// Find next '"'.
offset = aString.FindChar('"', ++offset);
if (offset == kNotFound) {
// No more quotes, exit.
break;
} else {
// Insert back-slash ahead of the '"'.
aString.Insert(char16_t('\\'), offset);
// Increment offset because we just inserted a slash
offset++;
}
}
return;
}
HDDEDATA CALLBACK nsNativeAppSupportWin::HandleDDENotification(
UINT uType, // transaction type
UINT uFmt, // clipboard data format
HCONV hconv, // handle to the conversation
HSZ hsz1, // handle to a string
HSZ hsz2, // handle to a string
HDDEDATA hdata, // handle to a global memory object
ULONG_PTR dwData1, // transaction-specific data
ULONG_PTR dwData2) { // transaction-specific data
if (!mCanHandleRequests) return 0;
#if MOZ_DEBUG_DDE
printf("DDE: uType =%s\n", uTypeDesc(uType).get());
printf(" uFmt =%u\n", (unsigned)uFmt);
printf(" hconv =%08x\n", (int)hconv);
printf(" hsz1 =%08x:%s\n", (int)hsz1, hszValue(mInstance, hsz1).get());
printf(" hsz2 =%08x:%s\n", (int)hsz2, hszValue(mInstance, hsz2).get());
printf(" hdata =%08x\n", (int)hdata);
printf(" dwData1=%08x\n", (int)dwData1);
printf(" dwData2=%08x\n", (int)dwData2);
#endif
HDDEDATA result = 0;
if (uType & XCLASS_BOOL) {
switch (uType) {
case XTYP_CONNECT:
// Make sure its for our service/topic.
if (FindTopic(hsz1) != -1) {
// We support this connection.
result = (HDDEDATA)1;
}
break;
case XTYP_CONNECT_CONFIRM:
// We don't care about the conversation handle, at this point.
result = (HDDEDATA)1;
break;
}
} else if (uType & XCLASS_DATA) {
if (uType == XTYP_REQUEST) {
switch (FindTopic(hsz1)) {
case topicOpenURL: {
// Open a given URL...
// Get the URL from the first argument in the command.
nsAutoString url;
ParseDDEArg(hsz2, 0, url);
// Read the 3rd argument in the command to determine if a
// new window is to be used.
nsAutoString windowID;
ParseDDEArg(hsz2, 2, windowID);
// "" means to open the URL in a new window.
if (windowID.IsEmpty()) {
url.InsertLiteral(u"mozilla -new-window ", 0);
} else {
url.InsertLiteral(u"mozilla -url ", 0);
}
#if MOZ_DEBUG_DDE
printf("Handling dde XTYP_REQUEST request: [%s]...\n",
NS_ConvertUTF16toUTF8(url).get());
#endif
// Now handle it.
HandleCommandLine(NS_ConvertUTF16toUTF8(url).get(), nullptr,
nsICommandLine::STATE_REMOTE_EXPLICIT);
// Return pseudo window ID.
result = CreateDDEData(1);
break;
}
case topicGetWindowInfo: {
// This topic has to get the current URL, get the current
// page title and then format the output into the DDE
// return string. The return value is "URL","Page Title",
// "Window ID" however the window ID is not used for this
// command, therefore it is returned as a null string
// This isn't really a loop. We just use "break"
// statements to bypass the remaining steps when
// something goes wrong.
do {
// Get most recently used Nav window.
nsCOMPtr<mozIDOMWindowProxy> navWin;
GetMostRecentWindow(u"navigator:browser", getter_AddRefs(navWin));
nsCOMPtr<nsPIDOMWindowOuter> piNavWin = do_QueryInterface(navWin);
if (!piNavWin) {
// There is not a window open
break;
}
// Get content window.
nsCOMPtr<nsPIDOMWindowOuter> internalContent =
nsGlobalWindowOuter::Cast(piNavWin)->GetContent();
if (!internalContent) {
break;
}
// Get location.
RefPtr<dom::Location> location = internalContent->GetLocation();
if (!location) {
break;
}
// Get href for URL.
nsAutoString url;
if (NS_FAILED(location->GetHref(url))) {
break;
}
// Escape any double-quotes.
escapeQuotes(url);
// Now for the title...
// Get the base window from the doc shell...
nsCOMPtr<nsIBaseWindow> baseWindow =
do_QueryInterface(internalContent->GetDocShell());
if (!baseWindow) {
break;
}
// And from the base window we can get the title.
nsString title;
if (!baseWindow) {
break;
}
baseWindow->GetTitle(title);
// Escape any double-quotes in the title.
escapeQuotes(title);
// Use a string buffer for the output data, first
// save a quote.
nsAutoCString outpt(NS_LITERAL_CSTRING("\""));
// Now copy the URL converting the Unicode string
// to a single-byte ASCII string
nsAutoCString tmpNativeStr;
NS_CopyUnicodeToNative(url, tmpNativeStr);
outpt.Append(tmpNativeStr);
// Add the "," used to separate the URL and the page
// title
outpt.Append(NS_LITERAL_CSTRING("\",\""));
// Now copy the current page title to the return string
NS_CopyUnicodeToNative(title, tmpNativeStr);
outpt.Append(tmpNativeStr);
// Fill out the return string with the remainin ",""
outpt.Append(NS_LITERAL_CSTRING("\",\"\""));
// Create a DDE handle to a char string for the data
// being returned, this copies and creates a "shared"
// copy of the DDE response until the calling APP
// reads it and says it can be freed.
result = CreateDDEData((LPBYTE)(const char *)outpt.get(),
outpt.Length() + 1);
#if MOZ_DEBUG_DDE
printf("WWW_GetWindowInfo->%s\n", outpt.get());
#endif
} while (false);
break;
}
case topicActivate: {
// Activate a Nav window...
nsAutoString windowID;
ParseDDEArg(hsz2, 0, windowID);
// 4294967295 is decimal for 0xFFFFFFFF which is also a
// correct value to do that Activate last window stuff
if (windowID.EqualsLiteral("-1") ||
windowID.EqualsLiteral("4294967295")) {
// We only support activating the most recent window (or a new one).
ActivateLastWindow();
// Return pseudo window ID.
result = CreateDDEData(1);
}
break;
}
case topicVersion: {
// Return version. We're restarting at 1.0!
DWORD version = 1 << 16; // "1.0"
result = CreateDDEData(version);
break;
}
case topicRegisterViewer: {
// Register new viewer (not implemented).
result = CreateDDEData(false);
break;
}
case topicUnRegisterViewer: {
// Unregister new viewer (not implemented).
result = CreateDDEData(false);
break;
}
default:
break;
}
} else if (uType & XTYP_POKE) {
switch (FindTopic(hsz1)) {
case topicCancelProgress: {
// "Handle" progress cancel (actually, pretty much ignored).
result = (HDDEDATA)DDE_FACK;
break;
}
default:
break;
}
}
} else if (uType & XCLASS_FLAGS) {
if (uType == XTYP_EXECUTE) {
// Prove that we received the request.
DWORD bytes;
LPBYTE request = DdeAccessData(hdata, &bytes);
#if MOZ_DEBUG_DDE
printf("Handling dde request: [%s]...\n", (char *)request);
#endif
nsAutoString url;
ParseDDEArg((const WCHAR *)request, 0, url);
// Read the 3rd argument in the command to determine if a
// new window is to be used.
nsAutoString windowID;
ParseDDEArg((const WCHAR *)request, 2, windowID);
// "" means to open the URL in a new window.
if (windowID.IsEmpty()) {
url.InsertLiteral(u"mozilla -new-window ", 0);
} else {
url.InsertLiteral(u"mozilla -url ", 0);
}
#if MOZ_DEBUG_DDE
printf("Handling dde XTYP_REQUEST request: [%s]...\n",
NS_ConvertUTF16toUTF8(url).get());
#endif
// Now handle it.
HandleCommandLine(NS_ConvertUTF16toUTF8(url).get(), nullptr,
nsICommandLine::STATE_REMOTE_EXPLICIT);
// Release the data.
DdeUnaccessData(hdata);
result = (HDDEDATA)DDE_FACK;
} else {
result = (HDDEDATA)DDE_FNOTPROCESSED;
}
} else if (uType & XCLASS_NOTIFICATION) {
}
#if MOZ_DEBUG_DDE
printf("DDE result=%d (0x%08X)\n", (int)result, (int)result);
#endif
return result;
}
// Utility function to advance to end of quoted string.
// p+offset must point to the comma preceding the arg on entry.
// On return, p+result points to the closing '"' (or end of the string
// if the closing '"' is missing) if the arg is quoted. If the arg
// is not quoted, then p+result will point to the first character
// of the arg.
static int32_t advanceToEndOfQuotedArg(const WCHAR *p, int32_t offset,
int32_t len) {
// Check whether the current arg is quoted.
if (p[++offset] == '"') {
// Advance past the closing quote.
while (offset < len && p[++offset] != '"') {
// If the current character is a backslash, then the
// next character can't be a *real* '"', so skip it.
if (p[offset] == '\\') {
offset++;
}
}
}
return offset;
}
void nsNativeAppSupportWin::ParseDDEArg(const WCHAR *args, int index,
nsString &aString) {
if (args) {
nsDependentString temp(args);
// offset points to the comma preceding the desired arg.
int32_t offset = -1;
// Skip commas till we get to the arg we want.
while (index--) {
// If this arg is quoted, then go to closing quote.
offset = advanceToEndOfQuotedArg(args, offset, temp.Length());
// Find next comma.
offset = temp.FindChar(',', offset);
if (offset == kNotFound) {
// No more commas, give up.
aString = args;
return;
}
}
// The desired argument starts just past the preceding comma,
// which offset points to, and extends until the following
// comma (or the end of the string).
//
// Since the argument might be enclosed in quotes, we need to
// deal with that before searching for the terminating comma.
// We advance offset so it ends up pointing to the start of
// the argument we want.
int32_t end = advanceToEndOfQuotedArg(args, offset++, temp.Length());
// Find next comma (or end of string).
end = temp.FindChar(',', end);
if (end == kNotFound) {
// Arg is the rest of the string.
end = temp.Length();
}
// Extract result.
aString.Assign(args + offset, end - offset);
}
return;
}
// Utility to parse out argument from a DDE item string.
void nsNativeAppSupportWin::ParseDDEArg(HSZ args, int index,
nsString &aString) {
DWORD argLen = DdeQueryStringW(mInstance, args, nullptr, 0, CP_WINUNICODE);
// there wasn't any string, so return empty string
if (!argLen) return;
nsAutoString temp;
// Ensure result's buffer is sufficiently big.
temp.SetLength(argLen);
// Now get the string contents.
DdeQueryString(mInstance, args,
reinterpret_cast<wchar_t *>(temp.BeginWriting()),
temp.Length(), CP_WINUNICODE);
// Parse out the given arg.
ParseDDEArg(temp.get(), index, aString);
return;
}
HDDEDATA nsNativeAppSupportWin::CreateDDEData(DWORD value) {
return CreateDDEData((LPBYTE)&value, sizeof value);
}
HDDEDATA nsNativeAppSupportWin::CreateDDEData(LPBYTE value, DWORD len) {
HDDEDATA result =
DdeCreateDataHandle(mInstance, value, len, 0, mApplication, CF_TEXT, 0);
return result;
}
void nsNativeAppSupportWin::ActivateLastWindow() {
nsCOMPtr<mozIDOMWindowProxy> navWin;
GetMostRecentWindow(u"navigator:browser", getter_AddRefs(navWin));
if (navWin) {
// Activate that window.
activateWindow(navWin);
} else {
// Need to create a Navigator window, then.
OpenBrowserWindow();
}
}
void nsNativeAppSupportWin::HandleCommandLine(const char *aCmdLineString,
nsIFile *aWorkingDir,
uint32_t aState) {
nsresult rv;
int justCounting = 1;
char **argv = 0;
// Flags, etc.
int init = 1;
int between, quoted, bSlashCount;
int argc;
const char *p;
nsAutoCString arg;
nsCOMPtr<nsICommandLineRunner> cmdLine(new nsCommandLine());
// Parse command line args according to MS spec
// (see "Parsing C++ Command-Line Arguments" at
// http://msdn.microsoft.com/library/devprods/vs6/visualc/vclang/_pluslang_parsing_c.2b2b_.command.2d.line_arguments.htm).
// We loop if we've not finished the second pass through.
while (1) {
// Initialize if required.
if (init) {
p = aCmdLineString;
between = 1;
argc = quoted = bSlashCount = 0;
init = 0;
}
if (between) {
// We are traversing whitespace between args.
// Check for start of next arg.
if (*p != 0 && !isspace(*p)) {
// Start of another arg.
between = 0;
arg = "";
switch (*p) {
case '\\':
// Count the backslash.
bSlashCount = 1;
break;
case '"':
// Remember we're inside quotes.
quoted = 1;
break;
default:
// Add character to arg.
arg += *p;
break;
}
} else {
// Another space between args, ignore it.
}
} else {
// We are processing the contents of an argument.
// Check for whitespace or end.
if (*p == 0 || (!quoted && isspace(*p))) {
// Process pending backslashes (interpret them
// literally since they're not followed by a ").
while (bSlashCount) {
arg += '\\';
bSlashCount--;
}
// End current arg.
if (!justCounting) {
argv[argc] = new char[arg.Length() + 1];
strcpy(argv[argc], arg.get());
}
argc++;
// We're now between args.
between = 1;
} else {
// Still inside argument, process the character.
switch (*p) {
case '"':
// First, digest preceding backslashes (if any).
while (bSlashCount > 1) {
// Put one backsplash in arg for each pair.
arg += '\\';
bSlashCount -= 2;
}
if (bSlashCount) {
// Quote is literal.
arg += '"';
bSlashCount = 0;
} else {
// Quote starts or ends a quoted section.
if (quoted) {
// Check for special case of consecutive double
// quotes inside a quoted section.
if (*(p + 1) == '"') {
// This implies a literal double-quote. Fake that
// out by causing next double-quote to look as
// if it was preceded by a backslash.
bSlashCount = 1;
} else {
quoted = 0;
}
} else {
quoted = 1;
}
}
break;
case '\\':
// Add to count.
bSlashCount++;
break;
default:
// Accept any preceding backslashes literally.
while (bSlashCount) {
arg += '\\';
bSlashCount--;
}
// Just add next char to the current arg.
arg += *p;
break;
}
}
}
// Check for end of input.
if (*p) {
// Go to next character.
p++;
} else {
// If on first pass, go on to second.
if (justCounting) {
// Allocate argv array.
argv = new char *[argc];
// Start second pass
justCounting = 0;
init = 1;
} else {
// Quit.
break;
}
}
}
rv = cmdLine->Init(argc, argv, aWorkingDir, aState);
// Cleanup.
while (argc) {
delete[] argv[--argc];
}
delete[] argv;
if (NS_FAILED(rv)) {
NS_ERROR("Error initializing command line.");
return;
}
cmdLine->Run();
}
nsresult nsNativeAppSupportWin::OpenWindow(const char *urlstr,
const char *args) {
nsresult rv = NS_ERROR_FAILURE;
nsCOMPtr<nsIWindowWatcher> wwatch(do_GetService(NS_WINDOWWATCHER_CONTRACTID));
nsCOMPtr<nsISupportsCString> sarg(
do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID));
if (sarg) sarg->SetData(nsDependentCString(args));
if (wwatch && sarg) {
nsCOMPtr<mozIDOMWindowProxy> newWindow;
rv = wwatch->OpenWindow(0, urlstr, "_blank", "chrome,dialog=no,all", sarg,
getter_AddRefs(newWindow));
#if MOZ_DEBUG_DDE
} else {
printf("Get WindowWatcher (or create string) failed\n");
#endif
}
return rv;
}
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));
}
nsresult nsNativeAppSupportWin::OpenBrowserWindow() {
nsresult rv = NS_OK;
// Open the argument URL in the most recently used Navigator window.
// If there is no Nav window, open a new one.
// If at all possible, hand the request off to the most recent
// browser window.
nsCOMPtr<mozIDOMWindowProxy> navWin;
GetMostRecentWindow(u"navigator:browser", getter_AddRefs(navWin));
// This isn't really a loop. We just use "break" statements to fall
// out to the OpenWindow call when things go awry.
do {
// If caller requires a new window, then don't use an existing one.
if (!navWin) {
// Have to open a new one.
break;
}
nsCOMPtr<nsIBrowserDOMWindow> bwin;
{ // scope a bunch of temporary cruft used to generate bwin
nsCOMPtr<nsIWebNavigation> navNav(do_GetInterface(navWin));
nsCOMPtr<nsIDocShellTreeItem> navItem(do_QueryInterface(navNav));
if (navItem) {
nsCOMPtr<nsIDocShellTreeItem> rootItem;
navItem->GetRootTreeItem(getter_AddRefs(rootItem));
nsCOMPtr<nsPIDOMWindowOuter> rootWin =
rootItem ? rootItem->GetWindow() : nullptr;
nsCOMPtr<nsIDOMChromeWindow> chromeWin(do_QueryInterface(rootWin));
if (chromeWin) chromeWin->GetBrowserDOMWindow(getter_AddRefs(bwin));
}
}
if (bwin) {
nsCOMPtr<nsIURI> uri;
NS_NewURI(getter_AddRefs(uri), NS_LITERAL_CSTRING("about:blank"), 0, 0);
if (uri) {
nsCOMPtr<mozIDOMWindowProxy> container;
rv = bwin->OpenURI(uri, 0, nsIBrowserDOMWindow::OPEN_DEFAULTWINDOW,
nsIBrowserDOMWindow::OPEN_EXTERNAL,
nsContentUtils::GetSystemPrincipal(),
getter_AddRefs(container));
if (NS_SUCCEEDED(rv)) return NS_OK;
}
}
NS_ERROR("failed to hand off external URL to extant window");
} while (false);
// open a new window if caller requested it or if anything above failed
char *argv[] = {0};
nsCOMPtr<nsICommandLineRunner> cmdLine(new nsCommandLine());
rv = cmdLine->Init(0, argv, nullptr, nsICommandLine::STATE_REMOTE_EXPLICIT);
NS_ENSURE_SUCCESS(rv, rv);
return cmdLine->Run();
}