1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
15 * The Original Code is Mozilla Communicator client code.
17 * The Initial Developer of the Original Code is
18 * Netscape Communications Corporation.
19 * Portions created by the Initial Developer are Copyright (C) 1998
20 * the Initial Developer. All Rights Reserved.
23 * Bill Law law@netscape.com
24 * Robert Strong robert.bugzilla@gmail.com
26 * Alternatively, the contents of this file may be used under the terms of
27 * either the GNU General Public License Version 2 or later (the "GPL"), or
28 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 * in which case the provisions of the GPL or the LGPL are applicable instead
30 * of those above. If you wish to allow use of your version of this file only
31 * under the terms of either the GPL or the LGPL, and not to allow others to
32 * use your version of this file under the terms of the MPL, indicate your
33 * decision by deleting the provisions above and replace them with the notice
34 * and other provisions required by the GPL or the LGPL. If you do not delete
35 * the provisions above, a recipient may use your version of this file under
36 * the terms of any one of the MPL, the GPL or the LGPL.
38 * ***** END LICENSE BLOCK ***** */
40 #include "nsNativeAppSupportBase.h"
41 #include "nsNativeAppSupportWin.h"
42 #include "nsAppRunner.h"
43 #include "nsXULAppAPI.h"
45 #include "nsIBrowserDOMWindow.h"
46 #include "nsICommandLineRunner.h"
48 #include "nsXPIDLString.h"
49 #include "nsIComponentManager.h"
50 #include "nsIServiceManager.h"
51 #include "nsIDOMChromeWindow.h"
53 #include "nsISupportsPrimitives.h"
54 #include "nsISupportsArray.h"
55 #include "nsIWindowWatcher.h"
56 #include "nsPIDOMWindow.h"
57 #include "nsIDocShell.h"
58 #include "nsIDocShellTreeItem.h"
59 #include "nsIBaseWindow.h"
60 #include "nsIWidget.h"
61 #include "nsIAppShellService.h"
62 #include "nsIXULWindow.h"
63 #include "nsIInterfaceRequestor.h"
64 #include "nsIInterfaceRequestorUtils.h"
65 #include "nsIPromptService.h"
67 #include "nsNetUtil.h"
68 #include "nsIObserver.h"
69 #include "nsIObserverService.h"
70 #include "nsIDOMLocation.h"
71 #include "nsIJSContextStack.h"
72 #include "nsIWebNavigation.h"
73 #include "nsIWindowMediator.h"
74 #include "nsNativeCharsetUtils.h"
75 #include "nsIAppStartup.h"
86 static HWND
hwndForDOMWindow( nsISupports
* );
90 GetMostRecentWindow(const PRUnichar
* aType
, nsIDOMWindowInternal
** aWindow
) {
92 nsCOMPtr
<nsIWindowMediator
> med( do_GetService( NS_WINDOWMEDIATOR_CONTRACTID
, &rv
) );
93 if ( NS_FAILED( rv
) )
97 return med
->GetMostRecentWindow( aType
, aWindow
);
99 return NS_ERROR_FAILURE
;
104 activateWindow( nsIDOMWindowInternal
*win
) {
105 // Try to get native window handle.
106 HWND hwnd
= hwndForDOMWindow( win
);
108 // Restore the window if it is minimized.
109 if ( ::IsIconic( hwnd
) ) {
110 ::ShowWindow( hwnd
, SW_RESTORE
);
112 // Use the OS call, if possible.
113 ::SetForegroundWindow( hwnd
);
115 // Use internal method.
123 #define MOZ_DEBUG_DDE 1
126 // Simple Win32 mutex wrapper.
128 Mutex( const char *name
)
132 mHandle
= CreateMutexA( 0, FALSE
, mName
.get() );
134 printf( "CreateMutex error = 0x%08X\n", (int)GetLastError() );
139 // Make sure we release it if we own it.
142 BOOL rc
= CloseHandle( mHandle
);
145 printf( "CloseHandle error = 0x%08X\n", (int)GetLastError() );
150 BOOL
Lock( DWORD timeout
) {
153 printf( "Waiting (%d msec) for DDE mutex...\n", (int)timeout
);
155 mState
= WaitForSingleObject( mHandle
, timeout
);
157 printf( "...wait complete, result = 0x%08X, GetLastError=0x%08X\n", (int)mState
, (int)::GetLastError() );
159 return mState
== WAIT_OBJECT_0
|| mState
== WAIT_ABANDONED
;
165 if ( mHandle
&& mState
== WAIT_OBJECT_0
) {
167 printf( "Releasing DDE mutex\n" );
169 ReleaseMutex( mHandle
);
181 * This section describes the Win32 DDE service implementation for
182 * Mozilla. DDE is used on Win32 platforms to communicate between
183 * separate instances of mozilla.exe (or other Mozilla-based
184 * executables), or, between the Win32 desktop shell and Mozilla.
186 * The first instance of Mozilla will become the "server" and
187 * subsequent executables (and the shell) will use DDE to send
188 * requests to that process. The requests are DDE "execute" requests
189 * that pass the command line arguments.
191 * Mozilla registers the DDE application "Mozilla" and currently
192 * supports only the "WWW_OpenURL" topic. This should be reasonably
193 * compatible with applications that interfaced with Netscape
194 * Communicator (and its predecessors?). Note that even that topic
195 * may not be supported in a compatible fashion as the command-line
196 * options for Mozilla are different than for Communiator.
198 * It is imperative that at most one instance of Mozilla execute in
199 * "server mode" at any one time. The "native app support" in Mozilla
200 * on Win32 ensures that only the server process performs XPCOM
201 * initialization (that is not required for subsequent client processes
202 * to communicate with the server process).
204 * To guarantee that only one server starts up, a Win32 "mutex" is used
205 * to ensure only one process executes the server-detection code. That
206 * code consists of initializing DDE and doing a DdeConnect to Mozilla's
207 * application/topic. If that connection succeeds, then a server process
208 * must be running already.
210 * Otherwise, no server has started. In that case, the current process
211 * calls DdeNameService to register that application/topic. Only at that
212 * point does the mutex get released.
214 * There are a couple of subtleties that one should be aware of:
216 * 1. It is imperative that DdeInitialize be called only after the mutex
217 * lock has been obtained. The reason is that at shutdown, DDE
218 * notifications go out to all initialized DDE processes. Thus, if
219 * the mutex is owned by a terminating intance of Mozilla, then
220 * calling DdeInitialize and then WaitForSingleObject will cause the
221 * DdeUninitialize from the terminating process to "hang" until the
222 * process waiting for the mutex times out (and can then service the
223 * notification that the DDE server is terminating). So, don't mess
224 * with the sequence of things in the startup/shutdown logic.
226 * 2. All mutex requests are made with a reasonably long timeout value and
227 * are designed to "fail safe" (i.e., a timeout is treated as failure).
229 * 3. An attempt has been made to minimize the degree to which the main
230 * Mozilla application logic needs to be aware of the DDE mechanisms
231 * implemented herein. As a result, this module surfaces a very
232 * large-grained interface, consisting of simple start/stop methods.
233 * As a consequence, details of certain scenarios can be "lost."
234 * Particularly, incoming DDE requests can arrive after this module
235 * initiates the DDE server, but before Mozilla is initialized to the
236 * point where those requests can be serviced (e.g., open a browser
237 * window to a particular URL). Since the client process sends the
238 * request early on, it may not be prepared to respond to that error.
239 * Thus, such situations may fail silently. The design goal is that
240 * they fail harmlessly. Refinements on this point will be made as
241 * details emerge (and time permits).
246 * A significant DDE bug in Windows is causing Mozilla to get wedged at
247 * startup. This is detailed in Bugzill bug 53952
248 * (http://bugzilla.mozilla.org/show_bug.cgi?id=53952).
250 * To resolve this, we are using a new strategy:
251 * o Use a "message window" to detect that Mozilla is already running and
252 * to pass requests from a second instance back to the first;
253 * o Run only as a "DDE server" (not as DDE client); this avoids the
254 * problematic call to DDEConnect().
256 * We still use the mutex semaphore to protect the code that detects
257 * whether Mozilla is already running.
260 /* Update 2007 January
262 * A change in behavior was implemented in July 2004 which made the
263 * application on launch to add and on quit to remove the ddexec registry key.
265 * Windows Vista has changed the methods used to set an application as default
266 * and the new methods are incompatible with removing the ddeexec registry key.
270 * 1. OS checks if the dde name is registered.
271 * 2. If it is registered the OS sends a DDE request with the WWW_OpenURL topic
272 * and the params as specified in the default value of the ddeexec registry
273 * key for the verb (e.g. open).
274 * 3. If it isn't registered the OS launches the executable defined in the
275 * verb's (e.g. open) command registry key.
276 * 4. If the ifexec registry key is not present the OS sends a DDE request with
277 * the WWW_OpenURL topic and the params as specified in the default value of
278 * the ddeexec registry key for the verb (e.g. open).
279 * 5. If the ifexec registry key is present the OS sends a DDE request with the
280 * WWW_OpenURL topic and the params as specified in the ifexec registry key
281 * for the verb (e.g. open).
283 * Application DDE Sequence:
284 * 1. If the application is running a DDE request is received with the
285 * WWW_OpenURL topic and the params as specified in the default value of the
286 * ddeexec registry key (e.g. "%1",,0,0,,,, where '%1' is the url to open)
287 * for the verb (e.g. open).
288 * 2. If the application is not running it is launched with the -requestPending
289 * and the -url argument.
290 * 2.1 If the application does not need to restart and the -requestPending
291 * argument is present the accompanying url will not be used. Instead the
292 * application will wait for the DDE message to open the url.
293 * 2.2 If the application needs to restart the -requestPending argument is
294 * removed from the arguments used to restart the application and the url
295 * will be handled normally.
297 * Note: Due to a bug in IE the ifexec key should not be used (see bug 355650).
300 class nsNativeAppSupportWin
: public nsNativeAppSupportBase
,
305 NS_DECL_ISUPPORTS_INHERITED
307 // Overrides of base implementation.
308 NS_IMETHOD
Start( PRBool
*aResult
);
309 NS_IMETHOD
Stop( PRBool
*aResult
);
313 // The "old" Start method (renamed).
314 NS_IMETHOD
StartDDE();
316 // Utility function to handle a Win32-specific command line
317 // option: "-console", which dynamically creates a Windows
322 static HDDEDATA CALLBACK
HandleDDENotification( UINT uType
,
330 static void HandleCommandLine(const char* aCmdLineString
, nsIFile
* aWorkingDir
, PRUint32 aState
);
331 static void ParseDDEArg( HSZ args
, int index
, nsString
& string
);
332 static void ParseDDEArg( const WCHAR
* args
, int index
, nsString
& aString
);
333 static void ActivateLastWindow();
334 static HDDEDATA
CreateDDEData( DWORD value
);
335 static HDDEDATA
CreateDDEData( LPBYTE value
, DWORD len
);
336 static PRBool
InitTopicStrings();
337 static int FindTopic( HSZ topic
);
338 static nsresult
OpenWindow( const char *urlstr
, const char *args
);
339 static nsresult
OpenBrowserWindow();
340 static nsresult
ReParent( nsISupports
*window
, HWND newParent
);
341 static void SetupSysTrayIcon();
342 static void RemoveSysTrayIcon();
344 static int mConversations
;
351 topicUnRegisterViewer
,
353 // Note: Insert new values above this line!!!!!
354 topicCount
// Count of the number of real topics
357 static HSZ mApplication
, mTopics
[ topicCount
];
358 static DWORD mInstance
;
359 static PRBool mCanHandleRequests
;
360 static char mMutexName
[];
361 friend struct MessageWindow
;
362 }; // nsNativeAppSupportWin
364 NS_INTERFACE_MAP_BEGIN(nsNativeAppSupportWin
)
365 NS_INTERFACE_MAP_ENTRY(nsIObserver
)
366 NS_INTERFACE_MAP_END_INHERITING(nsNativeAppSupportBase
)
368 NS_IMPL_ADDREF_INHERITED(nsNativeAppSupportWin
, nsNativeAppSupportBase
)
369 NS_IMPL_RELEASE_INHERITED(nsNativeAppSupportWin
, nsNativeAppSupportBase
)
372 nsNativeAppSupportWin::CheckConsole() {
373 for ( int i
= 1; i
< gArgc
; i
++ ) {
374 if ( strcmp( "-console", gArgv
[i
] ) == 0
376 strcmp( "/console", gArgv
[i
] ) == 0 ) {
377 // Users wants to make sure we have a console.
378 // Try to allocate one.
379 BOOL rc
= ::AllocConsole();
381 // Console allocated. Fix it up so that output works in
382 // all cases. See http://support.microsoft.com/support/kb/articles/q105/3/05.asp.
385 int hCrt
= ::_open_osfhandle( (long)GetStdHandle( STD_OUTPUT_HANDLE
),
388 FILE *hf
= ::_fdopen( hCrt
, "w" );
392 ::fprintf( stdout
, "stdout directed to dynamic console\n" );
398 hCrt
= ::_open_osfhandle( (long)::GetStdHandle( STD_ERROR_HANDLE
),
401 FILE *hf
= ::_fdopen( hCrt
, "w" );
405 ::fprintf( stderr
, "stderr directed to dynamic console\n" );
411 /* Don't bother for now.
412 hCrt = ::_open_osfhandle( (long)::GetStdHandle( STD_INPUT_HANDLE ),
415 FILE *hf = ::_fdopen( hCrt, "r" );
422 // Failed. Probably because there already is one.
423 // There's little we can do, in any case.
426 // Remove the console argument from the command line.
428 gArgv
[i
] = gArgv
[i
+ 1];
434 // Don't bother doing this more than once.
443 // Create and return an instance of class nsNativeAppSupportWin.
445 NS_CreateNativeAppSupport( nsINativeAppSupport
**aResult
) {
446 nsNativeAppSupportWin
*pNative
= new nsNativeAppSupportWin
;
447 if (!pNative
) return NS_ERROR_OUT_OF_MEMORY
;
449 // Check for dynamic console creation request.
450 pNative
->CheckConsole();
453 NS_ADDREF( *aResult
);
459 #define MOZ_DDE_APPLICATION "Mozilla"
460 #define MOZ_MUTEX_NAMESPACE "Local\\"
461 #define MOZ_STARTUP_MUTEX_NAME "StartupMutex"
462 #define MOZ_DDE_START_TIMEOUT 30000
463 #define MOZ_DDE_STOP_TIMEOUT 15000
464 #define MOZ_DDE_EXEC_TIMEOUT 15000
466 // The array entries must match the enum ordering!
467 const char * const topicNames
[] = { "WWW_OpenURL",
469 "WWW_CancelProgress",
471 "WWW_RegisterViewer",
472 "WWW_UnRegisterViewer",
473 "WWW_GetWindowInfo" };
475 // Static member definitions.
476 int nsNativeAppSupportWin::mConversations
= 0;
477 HSZ
nsNativeAppSupportWin::mApplication
= 0;
478 HSZ
nsNativeAppSupportWin::mTopics
[nsNativeAppSupportWin::topicCount
] = { 0 };
479 DWORD
nsNativeAppSupportWin::mInstance
= 0;
480 PRBool
nsNativeAppSupportWin::mCanHandleRequests
= PR_FALSE
;
482 char nsNativeAppSupportWin::mMutexName
[ 128 ] = { 0 };
485 // Message window encapsulation.
486 struct MessageWindow
{
487 // ctor/dtor are simplistic
489 // Try to find window.
490 mHandle
= ::FindWindowA( className(), 0 );
498 // Class name: appName + "MessageWindow"
499 static const char *className() {
500 static char classNameBuffer
[128];
501 static char *mClassName
= 0;
503 ::_snprintf( classNameBuffer
,
504 sizeof classNameBuffer
,
508 mClassName
= classNameBuffer
;
513 // Create: Register class and create window.
514 NS_IMETHOD
Create() {
515 WNDCLASSA classStruct
= { 0, // style
516 &MessageWindow::WindowProc
, // lpfnWndProc
524 className() }; // lpszClassName
526 // Register the window class.
527 NS_ENSURE_TRUE( ::RegisterClassA( &classStruct
), NS_ERROR_FAILURE
);
529 // Create the window.
530 NS_ENSURE_TRUE( ( mHandle
= ::CreateWindowA(className(),
533 0,0,0,0, // x, y, cx, cy
537 0 ) ), // create struct
541 printf( "Message window = 0x%08X\n", (int)mHandle
);
547 // Destory: Get rid of window and reset mHandle.
548 NS_IMETHOD
Destroy() {
549 nsresult retval
= NS_OK
;
552 // DestroyWindow can only destroy windows created from
554 BOOL desRes
= DestroyWindow( mHandle
);
555 if ( FALSE
!= desRes
) {
559 retval
= NS_ERROR_FAILURE
;
566 // SendRequest: Pass the command line via WM_COPYDATA to message window.
567 NS_IMETHOD
SendRequest() {
568 WCHAR
*cmd
= ::GetCommandLineW();
570 _wgetcwd(cwd
, MAX_PATH
);
572 // Construct a narrow UTF8 buffer <commandline>\0<workingdir>\0
573 NS_ConvertUTF16toUTF8
utf8buffer(cmd
);
574 utf8buffer
.Append('\0');
575 AppendUTF16toUTF8(cwd
, utf8buffer
);
576 utf8buffer
.Append('\0');
578 // We used to set dwData to zero, when we didn't send the working dir.
579 // Now we're using it as a version number.
580 COPYDATASTRUCT cds
= {
583 (void*) utf8buffer
.get()
585 // Bring the already running Mozilla process to the foreground.
586 // nsWindow will restore the window (if minimized) and raise it.
587 ::SetForegroundWindow( mHandle
);
588 ::SendMessage( mHandle
, WM_COPYDATA
, 0, (LPARAM
)&cds
);
593 static long CALLBACK
WindowProc( HWND msgWindow
, UINT msg
, WPARAM wp
, LPARAM lp
) {
594 if ( msg
== WM_COPYDATA
) {
595 if (!nsNativeAppSupportWin::mCanHandleRequests
)
598 // This is an incoming request.
599 COPYDATASTRUCT
*cds
= (COPYDATASTRUCT
*)lp
;
601 printf( "Incoming request: %s\n", (const char*)cds
->lpData
);
603 nsCOMPtr
<nsILocalFile
> workingDir
;
605 if (1 >= cds
->dwData
) {
606 char* wdpath
= (char*) cds
->lpData
;
607 // skip the command line, and get the working dir of the
608 // other process, which is after the first null char
615 printf( "Working dir: %s\n", wdpath
);
618 NS_NewLocalFile(NS_ConvertUTF8toUTF16(wdpath
),
620 getter_AddRefs(workingDir
));
622 (void)nsNativeAppSupportWin::HandleCommandLine((char*)cds
->lpData
, workingDir
, nsICommandLine::STATE_REMOTE_AUTO
);
624 // Get current window and return its window handle.
625 nsCOMPtr
<nsIDOMWindowInternal
> win
;
626 GetMostRecentWindow( 0, getter_AddRefs( win
) );
627 return win
? (long)hwndForDOMWindow( win
) : 0;
629 return DefWindowProc( msgWindow
, msg
, wp
, lp
);
634 }; // struct MessageWindow
636 /* Start: Tries to find the "message window" to determine if it
637 * exists. If so, then Mozilla is already running. In that
638 * case, we use the handle to the "message" window and send
639 * a request corresponding to this process's command line
642 * If not, then this is the first instance of Mozilla. In
643 * that case, we create and set up the message window.
645 * The checking for existance of the message window must
646 * be protected by use of a mutex semaphore.
649 nsNativeAppSupportWin::Start( PRBool
*aResult
) {
650 NS_ENSURE_ARG( aResult
);
651 NS_ENSURE_TRUE( mInstance
== 0, NS_ERROR_NOT_INITIALIZED
);
652 NS_ENSURE_STATE( gAppData
);
654 if (getenv("MOZ_NO_REMOTE"))
660 nsresult rv
= NS_ERROR_FAILURE
;
665 // Build mutex name from app name.
666 ::_snprintf( mMutexName
, sizeof mMutexName
, "%s%s%s", MOZ_MUTEX_NAMESPACE
,
667 gAppData
->name
, MOZ_STARTUP_MUTEX_NAME
);
668 Mutex startupLock
= Mutex( mMutexName
);
670 NS_ENSURE_TRUE( startupLock
.Lock( MOZ_DDE_START_TIMEOUT
), NS_ERROR_FAILURE
);
672 // Search for existing message window.
673 MessageWindow msgWindow
;
674 if ( (HWND
)msgWindow
) {
675 // We are a client process. Pass request to message window.
676 rv
= msgWindow
.SendRequest();
678 // We will be server.
679 rv
= msgWindow
.Create();
680 if ( NS_SUCCEEDED( rv
) ) {
681 // Start up DDE server.
683 // Tell caller to spin message loop.
688 startupLock
.Unlock();
694 nsNativeAppSupportWin::InitTopicStrings() {
695 for ( int i
= 0; i
< topicCount
; i
++ ) {
696 if ( !( mTopics
[ i
] = DdeCreateStringHandleA( mInstance
, const_cast<char *>(topicNames
[ i
]), CP_WINANSI
) ) ) {
704 nsNativeAppSupportWin::FindTopic( HSZ topic
) {
705 for ( int i
= 0; i
< topicCount
; i
++ ) {
706 if ( DdeCmpStringHandles( topic
, mTopics
[i
] ) == 0 ) {
716 // This used to be the Start() method when we were using DDE as the
717 // primary IPC mechanism between secondary Mozilla processes and the
718 // initial "server" process.
720 // Now, it simply initializes the DDE server. The caller must check
721 // that this process is to be the server, and, must acquire the DDE
722 // startup mutex semaphore prior to calling this routine. See ::Start(),
725 nsNativeAppSupportWin::StartDDE() {
726 NS_ENSURE_TRUE( mInstance
== 0, NS_ERROR_NOT_INITIALIZED
);
729 NS_ENSURE_TRUE( DMLERR_NO_ERROR
== DdeInitialize( &mInstance
,
730 nsNativeAppSupportWin::HandleDDENotification
,
735 // Allocate DDE strings.
736 NS_ENSURE_TRUE( ( mApplication
= DdeCreateStringHandleA( mInstance
, (char*) gAppData
->name
, CP_WINANSI
) ) && InitTopicStrings(),
739 // Next step is to register a DDE service.
740 NS_ENSURE_TRUE( DdeNameService( mInstance
, mApplication
, 0, DNS_REGISTER
), NS_ERROR_FAILURE
);
743 printf( "DDE server started\n" );
749 // If no DDE conversations are pending, terminate DDE.
751 nsNativeAppSupportWin::Stop( PRBool
*aResult
) {
752 NS_ENSURE_ARG( aResult
);
753 NS_ENSURE_TRUE( mInstance
, NS_ERROR_NOT_INITIALIZED
);
758 Mutex
ddeLock( mMutexName
);
760 if ( ddeLock
.Lock( MOZ_DDE_STOP_TIMEOUT
) ) {
761 if ( mConversations
== 0 ) {
770 // No DDE application name specified, but that's OK. Just
779 nsNativeAppSupportWin::Observe(nsISupports
* aSubject
, const char* aTopic
,
780 const PRUnichar
* aData
)
782 if (strcmp(aTopic
, "quit-application") == 0) {
785 NS_ERROR("Unexpected observer topic.");
791 // Terminate DDE regardless.
793 nsNativeAppSupportWin::Quit() {
794 // If another process wants to look for the message window, they need
795 // to wait to hold the lock, in which case they will not find the
796 // window as we will destroy ours under our lock.
797 // When the mutex goes off the stack, it is unlocked via destructor.
798 Mutex
mutexLock(mMutexName
);
799 NS_ENSURE_TRUE(mutexLock
.Lock(MOZ_DDE_START_TIMEOUT
), NS_ERROR_FAILURE
);
801 // If we've got a message window to receive IPC or new window requests,
802 // get rid of it as we are shutting down.
803 // Note: Destroy calls DestroyWindow, which will only work on a window
804 // created by the same thread.
809 // Unregister application name.
810 DdeNameService( mInstance
, mApplication
, 0, DNS_UNREGISTER
);
812 if ( mApplication
) {
813 DdeFreeStringHandle( mInstance
, mApplication
);
816 for ( int i
= 0; i
< topicCount
; i
++ ) {
818 DdeFreeStringHandle( mInstance
, mTopics
[i
] );
822 DdeUninitialize( mInstance
);
825 printf( "DDE server stopped\n" );
833 nsNativeAppSupportWin::Enable()
835 mCanHandleRequests
= PR_TRUE
;
837 nsCOMPtr
<nsIObserverService
> obs
838 (do_GetService("@mozilla.org/observer-service;1"));
840 obs
->AddObserver(this, "quit-application", PR_FALSE
);
842 NS_ERROR("No observer service?");
849 // Macro to generate case statement for a given XTYP value.
850 #define XTYP_CASE(t) \
851 case t: result = #t; break
853 static nsCString
uTypeDesc( UINT uType
) {
856 XTYP_CASE(XTYP_ADVSTART
);
857 XTYP_CASE(XTYP_CONNECT
);
858 XTYP_CASE(XTYP_ADVREQ
);
859 XTYP_CASE(XTYP_REQUEST
);
860 XTYP_CASE(XTYP_WILDCONNECT
);
861 XTYP_CASE(XTYP_ADVDATA
);
862 XTYP_CASE(XTYP_EXECUTE
);
863 XTYP_CASE(XTYP_POKE
);
864 XTYP_CASE(XTYP_ADVSTOP
);
865 XTYP_CASE(XTYP_CONNECT_CONFIRM
);
866 XTYP_CASE(XTYP_DISCONNECT
);
867 XTYP_CASE(XTYP_ERROR
);
868 XTYP_CASE(XTYP_MONITOR
);
869 XTYP_CASE(XTYP_REGISTER
);
870 XTYP_CASE(XTYP_XACT_COMPLETE
);
871 XTYP_CASE(XTYP_UNREGISTER
);
872 default: result
= "XTYP_?????";
877 static nsCString
hszValue( DWORD instance
, HSZ hsz
) {
878 // Extract string from HSZ.
879 nsCString
result("[");
880 DWORD len
= DdeQueryString( instance
, hsz
, NULL
, NULL
, CP_WINANSI
);
883 DdeQueryString( instance
, hsz
, buffer
, sizeof buffer
, CP_WINANSI
);
890 // These are purely a safety measure to avoid the infamous "won't
891 // build non-debug" type Tinderbox flames.
892 static nsCString
uTypeDesc( UINT
) {
893 return nsCString( "?" );
895 static nsCString
hszValue( DWORD
, HSZ
) {
896 return nsCString( "?" );
901 // Utility function to escape double-quotes within a string.
902 static void escapeQuotes( nsAString
&aString
) {
906 offset
= aString
.FindChar( '"', ++offset
);
907 if ( offset
== kNotFound
) {
908 // No more quotes, exit.
911 // Insert back-slash ahead of the '"'.
912 aString
.Insert( PRUnichar('\\'), offset
);
913 // Increment offset because we just inserted a slash
921 nsNativeAppSupportWin::HandleDDENotification( UINT uType
, // transaction type
922 UINT uFmt
, // clipboard data format
923 HCONV hconv
, // handle to the conversation
924 HSZ hsz1
, // handle to a string
925 HSZ hsz2
, // handle to a string
926 HDDEDATA hdata
, // handle to a global memory object
927 ULONG dwData1
, // transaction-specific data
928 ULONG dwData2
) { // transaction-specific data
930 if (!mCanHandleRequests
)
935 printf( "DDE: uType =%s\n", uTypeDesc( uType
).get() );
936 printf( " uFmt =%u\n", (unsigned)uFmt
);
937 printf( " hconv =%08x\n", (int)hconv
);
938 printf( " hsz1 =%08x:%s\n", (int)hsz1
, hszValue( mInstance
, hsz1
).get() );
939 printf( " hsz2 =%08x:%s\n", (int)hsz2
, hszValue( mInstance
, hsz2
).get() );
940 printf( " hdata =%08x\n", (int)hdata
);
941 printf( " dwData1=%08x\n", (int)dwData1
);
942 printf( " dwData2=%08x\n", (int)dwData2
);
946 if ( uType
& XCLASS_BOOL
) {
949 // Make sure its for our service/topic.
950 if ( FindTopic( hsz1
) != -1 ) {
951 // We support this connection.
952 result
= (HDDEDATA
)1;
955 case XTYP_CONNECT_CONFIRM
:
956 // We don't care about the conversation handle, at this point.
957 result
= (HDDEDATA
)1;
960 } else if ( uType
& XCLASS_DATA
) {
961 if ( uType
== XTYP_REQUEST
) {
962 switch ( FindTopic( hsz1
) ) {
964 // Open a given URL...
966 // Get the URL from the first argument in the command.
968 ParseDDEArg(hsz2
, 0, url
);
970 // Read the 3rd argument in the command to determine if a
971 // new window is to be used.
972 nsAutoString windowID
;
973 ParseDDEArg(hsz2
, 2, windowID
);
974 // "" means to open the URL in a new window.
975 if ( windowID
.IsEmpty() ) {
976 url
.Insert(NS_LITERAL_STRING("mozilla -new-window "), 0);
979 url
.Insert(NS_LITERAL_STRING("mozilla -url "), 0);
983 printf( "Handling dde XTYP_REQUEST request: [%s]...\n", NS_ConvertUTF16toUTF8(url
).get() );
986 HandleCommandLine(NS_ConvertUTF16toUTF8(url
).get(), nsnull
, nsICommandLine::STATE_REMOTE_EXPLICIT
);
988 // Return pseudo window ID.
989 result
= CreateDDEData( 1 );
992 case topicGetWindowInfo
: {
993 // This topic has to get the current URL, get the current
994 // page title and then format the output into the DDE
995 // return string. The return value is "URL","Page Title",
996 // "Window ID" however the window ID is not used for this
997 // command, therefore it is returned as a null string
999 // This isn't really a loop. We just use "break"
1000 // statements to bypass the remaining steps when
1001 // something goes wrong.
1003 // Get most recently used Nav window.
1004 nsCOMPtr
<nsIDOMWindowInternal
> navWin
;
1005 GetMostRecentWindow( NS_LITERAL_STRING( "navigator:browser" ).get(),
1006 getter_AddRefs( navWin
) );
1008 // There is not a window open
1011 // Get content window.
1012 nsCOMPtr
<nsIDOMWindow
> content
;
1013 navWin
->GetContent( getter_AddRefs( content
) );
1017 // Convert that to internal interface.
1018 nsCOMPtr
<nsPIDOMWindow
> internalContent( do_QueryInterface( content
) );
1019 if ( !internalContent
) {
1023 nsCOMPtr
<nsIDOMLocation
> location
;
1024 internalContent
->GetLocation( getter_AddRefs( location
) );
1028 // Get href for URL.
1030 if ( NS_FAILED( location
->GetHref( url
) ) ) {
1033 // Escape any double-quotes.
1034 escapeQuotes( url
);
1036 // Now for the title...
1038 // Get the base window from the doc shell...
1039 nsCOMPtr
<nsIBaseWindow
> baseWindow
=
1040 do_QueryInterface( internalContent
->GetDocShell() );
1041 if ( !baseWindow
) {
1044 // And from the base window we can get the title.
1045 nsXPIDLString title
;
1049 baseWindow
->GetTitle(getter_Copies(title
));
1050 // Escape any double-quotes in the title.
1051 escapeQuotes( title
);
1053 // Use a string buffer for the output data, first
1055 nsCAutoString
outpt( NS_LITERAL_CSTRING("\"") );
1056 // Now copy the URL converting the Unicode string
1057 // to a single-byte ASCII string
1058 nsCAutoString tmpNativeStr
;
1059 NS_CopyUnicodeToNative( url
, tmpNativeStr
);
1060 outpt
.Append( tmpNativeStr
);
1061 // Add the "," used to separate the URL and the page
1063 outpt
.Append( NS_LITERAL_CSTRING("\",\"") );
1064 // Now copy the current page title to the return string
1065 NS_CopyUnicodeToNative( title
, tmpNativeStr
);
1066 outpt
.Append( tmpNativeStr
);
1067 // Fill out the return string with the remainin ",""
1068 outpt
.Append( NS_LITERAL_CSTRING( "\",\"\"" ));
1070 // Create a DDE handle to a char string for the data
1071 // being returned, this copies and creates a "shared"
1072 // copy of the DDE response until the calling APP
1073 // reads it and says it can be freed.
1074 result
= CreateDDEData( (LPBYTE
)(const char*)outpt
.get(),
1075 outpt
.Length() + 1 );
1077 printf( "WWW_GetWindowInfo->%s\n", outpt
.get() );
1079 } while ( PR_FALSE
);
1082 case topicActivate
: {
1083 // Activate a Nav window...
1084 nsAutoString windowID
;
1085 ParseDDEArg(hsz2
, 0, windowID
);
1086 // 4294967295 is decimal for 0xFFFFFFFF which is also a
1087 // correct value to do that Activate last window stuff
1088 if ( windowID
.EqualsLiteral( "-1" ) ||
1089 windowID
.EqualsLiteral( "4294967295" ) ) {
1090 // We only support activating the most recent window (or a new one).
1091 ActivateLastWindow();
1092 // Return pseudo window ID.
1093 result
= CreateDDEData( 1 );
1097 case topicVersion
: {
1098 // Return version. We're restarting at 1.0!
1099 DWORD version
= 1 << 16; // "1.0"
1100 result
= CreateDDEData( version
);
1103 case topicRegisterViewer
: {
1104 // Register new viewer (not implemented).
1105 result
= CreateDDEData( PR_FALSE
);
1108 case topicUnRegisterViewer
: {
1109 // Unregister new viewer (not implemented).
1110 result
= CreateDDEData( PR_FALSE
);
1116 } else if ( uType
& XTYP_POKE
) {
1117 switch ( FindTopic( hsz1
) ) {
1118 case topicCancelProgress
: {
1119 // "Handle" progress cancel (actually, pretty much ignored).
1120 result
= (HDDEDATA
)DDE_FACK
;
1127 } else if ( uType
& XCLASS_FLAGS
) {
1128 if ( uType
== XTYP_EXECUTE
) {
1129 // Prove that we received the request.
1131 LPBYTE request
= DdeAccessData( hdata
, &bytes
);
1133 printf( "Handling dde request: [%s]...\n", (char*)request
);
1135 // Default is to open in current window.
1136 PRBool new_window
= PR_FALSE
;
1139 ParseDDEArg((const WCHAR
*) request
, 0, url
);
1141 // Read the 3rd argument in the command to determine if a
1142 // new window is to be used.
1143 nsAutoString windowID
;
1144 ParseDDEArg((const WCHAR
*) request
, 2, windowID
);
1146 // "" means to open the URL in a new window.
1147 if ( windowID
.IsEmpty() ) {
1148 url
.Insert(NS_LITERAL_STRING("mozilla -new-window "), 0);
1151 url
.Insert(NS_LITERAL_STRING("mozilla -url "), 0);
1154 printf( "Handling dde XTYP_REQUEST request: [%s]...\n", NS_ConvertUTF16toUTF8(url
).get() );
1157 HandleCommandLine(NS_ConvertUTF16toUTF8(url
).get(), nsnull
, nsICommandLine::STATE_REMOTE_EXPLICIT
);
1159 // Release the data.
1160 DdeUnaccessData( hdata
);
1161 result
= (HDDEDATA
)DDE_FACK
;
1163 result
= (HDDEDATA
)DDE_FNOTPROCESSED
;
1165 } else if ( uType
& XCLASS_NOTIFICATION
) {
1168 printf( "DDE result=%d (0x%08X)\n", (int)result
, (int)result
);
1173 // Utility function to advance to end of quoted string.
1174 // p+offset must point to the comma preceding the arg on entry.
1175 // On return, p+result points to the closing '"' (or end of the string
1176 // if the closing '"' is missing) if the arg is quoted. If the arg
1177 // is not quoted, then p+result will point to the first character
1179 static PRInt32
advanceToEndOfQuotedArg( const WCHAR
*p
, PRInt32 offset
, PRInt32 len
) {
1180 // Check whether the current arg is quoted.
1181 if ( p
[++offset
] == '"' ) {
1182 // Advance past the closing quote.
1183 while ( offset
< len
&& p
[++offset
] != '"' ) {
1184 // If the current character is a backslash, then the
1185 // next character can't be a *real* '"', so skip it.
1186 if ( p
[offset
] == '\\' ) {
1194 void nsNativeAppSupportWin::ParseDDEArg( const WCHAR
* args
, int index
, nsString
& aString
) {
1196 nsDependentString
temp(args
);
1198 // offset points to the comma preceding the desired arg.
1199 PRInt32 offset
= -1;
1200 // Skip commas till we get to the arg we want.
1202 // If this arg is quoted, then go to closing quote.
1203 offset
= advanceToEndOfQuotedArg( args
, offset
, temp
.Length());
1205 offset
= temp
.FindChar( ',', offset
);
1206 if ( offset
== kNotFound
) {
1207 // No more commas, give up.
1212 // The desired argument starts just past the preceding comma,
1213 // which offset points to, and extends until the following
1214 // comma (or the end of the string).
1216 // Since the argument might be enclosed in quotes, we need to
1217 // deal with that before searching for the terminating comma.
1218 // We advance offset so it ends up pointing to the start of
1219 // the argument we want.
1220 PRInt32 end
= advanceToEndOfQuotedArg( args
, offset
++, temp
.Length() );
1221 // Find next comma (or end of string).
1222 end
= temp
.FindChar( ',', end
);
1223 if ( end
== kNotFound
) {
1224 // Arg is the rest of the string.
1225 end
= temp
.Length();
1228 aString
.Assign( args
+ offset
, end
- offset
);
1233 // Utility to parse out argument from a DDE item string.
1234 void nsNativeAppSupportWin::ParseDDEArg( HSZ args
, int index
, nsString
& aString
) {
1235 DWORD argLen
= DdeQueryStringW( mInstance
, args
, NULL
, NULL
, CP_WINUNICODE
);
1236 // there wasn't any string, so return empty string
1237 if ( !argLen
) return;
1239 // Ensure result's buffer is sufficiently big.
1240 temp
.SetLength( argLen
);
1241 // Now get the string contents.
1242 DdeQueryString( mInstance
, args
, temp
.BeginWriting(), temp
.Length(), CP_WINUNICODE
);
1243 // Parse out the given arg.
1244 ParseDDEArg(temp
.get(), index
, aString
);
1248 void nsNativeAppSupportWin::ActivateLastWindow() {
1249 nsCOMPtr
<nsIDOMWindowInternal
> navWin
;
1250 GetMostRecentWindow( NS_LITERAL_STRING("navigator:browser").get(), getter_AddRefs( navWin
) );
1252 // Activate that window.
1253 activateWindow( navWin
);
1255 // Need to create a Navigator window, then.
1256 OpenBrowserWindow();
1260 HDDEDATA
nsNativeAppSupportWin::CreateDDEData( DWORD value
) {
1261 return CreateDDEData( (LPBYTE
)&value
, sizeof value
);
1264 HDDEDATA
nsNativeAppSupportWin::CreateDDEData( LPBYTE value
, DWORD len
) {
1265 HDDEDATA result
= DdeCreateDataHandle( mInstance
,
1276 nsNativeAppSupportWin::HandleCommandLine(const char* aCmdLineString
,
1277 nsIFile
* aWorkingDir
,
1282 int justCounting
= 1;
1286 int between
, quoted
, bSlashCount
;
1291 nsCOMPtr
<nsICommandLineRunner
> cmdLine
1292 (do_CreateInstance("@mozilla.org/toolkit/command-line;1"));
1294 NS_ERROR("Couldn't create command line!");
1298 // Parse command line args according to MS spec
1299 // (see "Parsing C++ Command-Line Arguments" at
1300 // http://msdn.microsoft.com/library/devprods/vs6/visualc/vclang/_pluslang_parsing_c.2b2b_.command.2d.line_arguments.htm).
1301 // We loop if we've not finished the second pass through.
1303 // Initialize if required.
1307 argc
= quoted
= bSlashCount
= 0;
1312 // We are traversing whitespace between args.
1313 // Check for start of next arg.
1314 if ( *p
!= 0 && !isspace( *p
) ) {
1315 // Start of another arg.
1320 // Count the backslash.
1324 // Remember we're inside quotes.
1328 // Add character to arg.
1333 // Another space between args, ignore it.
1336 // We are processing the contents of an argument.
1337 // Check for whitespace or end.
1338 if ( *p
== 0 || ( !quoted
&& isspace( *p
) ) ) {
1339 // Process pending backslashes (interpret them
1340 // literally since they're not followed by a ").
1341 while( bSlashCount
) {
1346 if ( !justCounting
) {
1347 argv
[argc
] = new char[ arg
.Length() + 1 ];
1348 strcpy( argv
[argc
], arg
.get() );
1351 // We're now between args.
1354 // Still inside argument, process the character.
1357 // First, digest preceding backslashes (if any).
1358 while ( bSlashCount
> 1 ) {
1359 // Put one backsplash in arg for each pair.
1363 if ( bSlashCount
) {
1364 // Quote is literal.
1368 // Quote starts or ends a quoted section.
1370 // Check for special case of consecutive double
1371 // quotes inside a quoted section.
1372 if ( *(p
+1) == '"' ) {
1373 // This implies a literal double-quote. Fake that
1374 // out by causing next double-quote to look as
1375 // if it was preceded by a backslash.
1390 // Accept any preceding backslashes literally.
1391 while ( bSlashCount
) {
1395 // Just add next char to the current arg.
1401 // Check for end of input.
1403 // Go to next character.
1406 // If on first pass, go on to second.
1407 if ( justCounting
) {
1408 // Allocate argv array.
1409 argv
= new char*[ argc
];
1411 // Start second pass
1421 rv
= cmdLine
->Init(argc
, argv
, aWorkingDir
, aState
);
1425 delete [] argv
[ --argc
];
1429 if (NS_FAILED(rv
)) {
1430 NS_ERROR("Error initializing command line.");
1438 nsNativeAppSupportWin::OpenWindow( const char*urlstr
, const char *args
) {
1440 nsresult rv
= NS_ERROR_FAILURE
;
1442 nsCOMPtr
<nsIWindowWatcher
> wwatch(do_GetService(NS_WINDOWWATCHER_CONTRACTID
));
1443 nsCOMPtr
<nsISupportsCString
> sarg(do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID
));
1445 sarg
->SetData(nsDependentCString(args
));
1447 if (wwatch
&& sarg
) {
1448 nsCOMPtr
<nsIDOMWindow
> newWindow
;
1449 rv
= wwatch
->OpenWindow(0, urlstr
, "_blank", "chrome,dialog=no,all",
1450 sarg
, getter_AddRefs(newWindow
));
1453 printf("Get WindowWatcher (or create string) failed\n");
1459 HWND
hwndForDOMWindow( nsISupports
*window
) {
1460 nsCOMPtr
<nsPIDOMWindow
> pidomwindow( do_QueryInterface(window
) );
1461 if ( !pidomwindow
) {
1465 nsCOMPtr
<nsIBaseWindow
> ppBaseWindow
=
1466 do_QueryInterface( pidomwindow
->GetDocShell() );
1467 if ( !ppBaseWindow
) {
1471 nsCOMPtr
<nsIWidget
> ppWidget
;
1472 ppBaseWindow
->GetMainWidget( getter_AddRefs( ppWidget
) );
1474 return (HWND
)( ppWidget
->GetNativeData( NS_NATIVE_WIDGET
) );
1477 static const char sJSStackContractID
[] = "@mozilla.org/js/xpc/ContextStack;1";
1479 class SafeJSContext
{
1485 JSContext
*get() { return mContext
; }
1488 nsCOMPtr
<nsIThreadJSContextStack
> mService
;
1489 JSContext
*mContext
;
1492 SafeJSContext::SafeJSContext() : mContext(nsnull
) {
1495 SafeJSContext::~SafeJSContext() {
1500 rv
= mService
->Pop(&cx
);
1501 NS_ASSERTION(NS_SUCCEEDED(rv
) && cx
== mContext
, "JSContext push/pop mismatch");
1505 nsresult
SafeJSContext::Push() {
1506 if (mContext
) // only once
1507 return NS_ERROR_FAILURE
;
1509 mService
= do_GetService(sJSStackContractID
);
1512 if (NS_SUCCEEDED(mService
->GetSafeJSContext(&cx
)) &&
1514 NS_SUCCEEDED(mService
->Push(cx
))) {
1515 // Save cx in mContext to indicate need to pop.
1519 return mContext
? NS_OK
: NS_ERROR_FAILURE
;
1524 nsNativeAppSupportWin::OpenBrowserWindow()
1526 nsresult rv
= NS_OK
;
1528 // Open the argument URL in the most recently used Navigator window.
1529 // If there is no Nav window, open a new one.
1531 // If at all possible, hand the request off to the most recent
1534 nsCOMPtr
<nsIDOMWindowInternal
> navWin
;
1535 GetMostRecentWindow( NS_LITERAL_STRING( "navigator:browser" ).get(), getter_AddRefs( navWin
) );
1537 // This isn't really a loop. We just use "break" statements to fall
1538 // out to the OpenWindow call when things go awry.
1540 // If caller requires a new window, then don't use an existing one.
1542 // Have to open a new one.
1546 nsCOMPtr
<nsIBrowserDOMWindow
> bwin
;
1547 { // scope a bunch of temporary cruft used to generate bwin
1548 nsCOMPtr
<nsIWebNavigation
> navNav( do_GetInterface( navWin
) );
1549 nsCOMPtr
<nsIDocShellTreeItem
> navItem( do_QueryInterface( navNav
) );
1551 nsCOMPtr
<nsIDocShellTreeItem
> rootItem
;
1552 navItem
->GetRootTreeItem( getter_AddRefs( rootItem
) );
1553 nsCOMPtr
<nsIDOMWindow
> rootWin( do_GetInterface( rootItem
) );
1554 nsCOMPtr
<nsIDOMChromeWindow
> chromeWin(do_QueryInterface(rootWin
));
1556 chromeWin
->GetBrowserDOMWindow( getter_AddRefs ( bwin
) );
1560 nsCOMPtr
<nsIURI
> uri
;
1561 NS_NewURI( getter_AddRefs( uri
), NS_LITERAL_CSTRING("about:blank"), 0, 0 );
1563 nsCOMPtr
<nsIDOMWindow
> container
;
1564 rv
= bwin
->OpenURI( uri
, 0,
1565 nsIBrowserDOMWindow::OPEN_DEFAULTWINDOW
,
1566 nsIBrowserDOMWindow::OPEN_EXTERNAL
,
1567 getter_AddRefs( container
) );
1568 if ( NS_SUCCEEDED( rv
) )
1573 NS_ERROR("failed to hand off external URL to extant window\n");
1574 } while ( PR_FALSE
);
1576 // open a new window if caller requested it or if anything above failed
1578 char* argv
[] = { 0 };
1579 nsCOMPtr
<nsICommandLineRunner
> cmdLine
1580 (do_CreateInstance("@mozilla.org/toolkit/command-line;1"));
1581 NS_ENSURE_TRUE(cmdLine
, NS_ERROR_FAILURE
);
1583 rv
= cmdLine
->Init(0, argv
, nsnull
, nsICommandLine::STATE_REMOTE_EXPLICIT
);
1584 NS_ENSURE_SUCCESS(rv
, rv
);
1586 return cmdLine
->Run();