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