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"
88 static HWND
hwndForDOMWindow( nsISupports
* );
92 GetMostRecentWindow(const PRUnichar
* aType
, nsIDOMWindowInternal
** aWindow
) {
94 nsCOMPtr
<nsIWindowMediator
> med( do_GetService( NS_WINDOWMEDIATOR_CONTRACTID
, &rv
) );
95 if ( NS_FAILED( rv
) )
99 return med
->GetMostRecentWindow( aType
, aWindow
);
101 return NS_ERROR_FAILURE
;
106 activateWindow( nsIDOMWindowInternal
*win
) {
107 // Try to get native window handle.
108 HWND hwnd
= hwndForDOMWindow( win
);
110 #ifndef WINCE // Can't iconify windows on windows ce
111 // Restore the window if it is minimized.
112 if ( ::IsIconic( hwnd
) ) {
113 ::ShowWindow( hwnd
, SW_RESTORE
);
116 // Use the OS call, if possible.
117 ::SetForegroundWindow( hwnd
);
119 // Use internal method.
127 #define MOZ_DEBUG_DDE 1
130 // Simple Win32 mutex wrapper.
132 Mutex( const PRUnichar
*name
)
136 mHandle
= CreateMutexW( 0, FALSE
, mName
.get() );
138 printf( "CreateMutex error = 0x%08X\n", (int)GetLastError() );
143 // Make sure we release it if we own it.
146 BOOL rc
= CloseHandle( mHandle
);
149 printf( "CloseHandle error = 0x%08X\n", (int)GetLastError() );
154 BOOL
Lock( DWORD timeout
) {
157 printf( "Waiting (%d msec) for DDE mutex...\n", (int)timeout
);
159 mState
= WaitForSingleObject( mHandle
, timeout
);
161 printf( "...wait complete, result = 0x%08X, GetLastError=0x%08X\n", (int)mState
, (int)::GetLastError() );
163 return mState
== WAIT_OBJECT_0
|| mState
== WAIT_ABANDONED
;
169 if ( mHandle
&& mState
== WAIT_OBJECT_0
) {
171 printf( "Releasing DDE mutex\n" );
173 ReleaseMutex( mHandle
);
185 * This section describes the Win32 DDE service implementation for
186 * Mozilla. DDE is used on Win32 platforms to communicate between
187 * separate instances of mozilla.exe (or other Mozilla-based
188 * executables), or, between the Win32 desktop shell and Mozilla.
190 * The first instance of Mozilla will become the "server" and
191 * subsequent executables (and the shell) will use DDE to send
192 * requests to that process. The requests are DDE "execute" requests
193 * that pass the command line arguments.
195 * Mozilla registers the DDE application "Mozilla" and currently
196 * supports only the "WWW_OpenURL" topic. This should be reasonably
197 * compatible with applications that interfaced with Netscape
198 * Communicator (and its predecessors?). Note that even that topic
199 * may not be supported in a compatible fashion as the command-line
200 * options for Mozilla are different than for Communiator.
202 * It is imperative that at most one instance of Mozilla execute in
203 * "server mode" at any one time. The "native app support" in Mozilla
204 * on Win32 ensures that only the server process performs XPCOM
205 * initialization (that is not required for subsequent client processes
206 * to communicate with the server process).
208 * To guarantee that only one server starts up, a Win32 "mutex" is used
209 * to ensure only one process executes the server-detection code. That
210 * code consists of initializing DDE and doing a DdeConnect to Mozilla's
211 * application/topic. If that connection succeeds, then a server process
212 * must be running already.
214 * Otherwise, no server has started. In that case, the current process
215 * calls DdeNameService to register that application/topic. Only at that
216 * point does the mutex get released.
218 * There are a couple of subtleties that one should be aware of:
220 * 1. It is imperative that DdeInitialize be called only after the mutex
221 * lock has been obtained. The reason is that at shutdown, DDE
222 * notifications go out to all initialized DDE processes. Thus, if
223 * the mutex is owned by a terminating intance of Mozilla, then
224 * calling DdeInitialize and then WaitForSingleObject will cause the
225 * DdeUninitialize from the terminating process to "hang" until the
226 * process waiting for the mutex times out (and can then service the
227 * notification that the DDE server is terminating). So, don't mess
228 * with the sequence of things in the startup/shutdown logic.
230 * 2. All mutex requests are made with a reasonably long timeout value and
231 * are designed to "fail safe" (i.e., a timeout is treated as failure).
233 * 3. An attempt has been made to minimize the degree to which the main
234 * Mozilla application logic needs to be aware of the DDE mechanisms
235 * implemented herein. As a result, this module surfaces a very
236 * large-grained interface, consisting of simple start/stop methods.
237 * As a consequence, details of certain scenarios can be "lost."
238 * Particularly, incoming DDE requests can arrive after this module
239 * initiates the DDE server, but before Mozilla is initialized to the
240 * point where those requests can be serviced (e.g., open a browser
241 * window to a particular URL). Since the client process sends the
242 * request early on, it may not be prepared to respond to that error.
243 * Thus, such situations may fail silently. The design goal is that
244 * they fail harmlessly. Refinements on this point will be made as
245 * details emerge (and time permits).
250 * A significant DDE bug in Windows is causing Mozilla to get wedged at
251 * startup. This is detailed in Bugzill bug 53952
252 * (http://bugzilla.mozilla.org/show_bug.cgi?id=53952).
254 * To resolve this, we are using a new strategy:
255 * o Use a "message window" to detect that Mozilla is already running and
256 * to pass requests from a second instance back to the first;
257 * o Run only as a "DDE server" (not as DDE client); this avoids the
258 * problematic call to DDEConnect().
260 * We still use the mutex semaphore to protect the code that detects
261 * whether Mozilla is already running.
264 /* Update 2007 January
266 * A change in behavior was implemented in July 2004 which made the
267 * application on launch to add and on quit to remove the ddexec registry key.
269 * Windows Vista has changed the methods used to set an application as default
270 * and the new methods are incompatible with removing the ddeexec registry key.
274 * 1. OS checks if the dde name is registered.
275 * 2. If it is registered the OS sends a DDE request with the WWW_OpenURL topic
276 * and the params as specified in the default value of the ddeexec registry
277 * key for the verb (e.g. open).
278 * 3. If it isn't registered the OS launches the executable defined in the
279 * verb's (e.g. open) command registry key.
280 * 4. If the ifexec registry key is not present the OS sends a DDE request with
281 * the WWW_OpenURL topic and the params as specified in the default value of
282 * the ddeexec registry key for the verb (e.g. open).
283 * 5. If the ifexec registry key is present the OS sends a DDE request with the
284 * WWW_OpenURL topic and the params as specified in the ifexec registry key
285 * for the verb (e.g. open).
287 * Application DDE Sequence:
288 * 1. If the application is running a DDE request is received with the
289 * WWW_OpenURL topic and the params as specified in the default value of the
290 * ddeexec registry key (e.g. "%1",,0,0,,,, where '%1' is the url to open)
291 * for the verb (e.g. open).
292 * 2. If the application is not running it is launched with the -requestPending
293 * and the -url argument.
294 * 2.1 If the application does not need to restart and the -requestPending
295 * argument is present the accompanying url will not be used. Instead the
296 * application will wait for the DDE message to open the url.
297 * 2.2 If the application needs to restart the -requestPending argument is
298 * removed from the arguments used to restart the application and the url
299 * will be handled normally.
301 * Note: Due to a bug in IE the ifexec key should not be used (see bug 355650).
304 class nsNativeAppSupportWin
: public nsNativeAppSupportBase
,
309 NS_DECL_ISUPPORTS_INHERITED
311 // Overrides of base implementation.
312 NS_IMETHOD
Start( PRBool
*aResult
);
313 NS_IMETHOD
Stop( PRBool
*aResult
);
317 // The "old" Start method (renamed).
318 NS_IMETHOD
StartDDE();
320 // Utility function to handle a Win32-specific command line
321 // option: "-console", which dynamically creates a Windows
326 static void HandleCommandLine(const char* aCmdLineString
, nsIFile
* aWorkingDir
, PRUint32 aState
);
328 static HDDEDATA CALLBACK
HandleDDENotification( UINT uType
,
336 static void ParseDDEArg( HSZ args
, int index
, nsString
& string
);
337 static void ParseDDEArg( const WCHAR
* args
, int index
, nsString
& aString
);
338 static HDDEDATA
CreateDDEData( DWORD value
);
339 static HDDEDATA
CreateDDEData( LPBYTE value
, DWORD len
);
340 static PRBool
InitTopicStrings();
341 static int FindTopic( HSZ topic
);
343 static void ActivateLastWindow();
344 static nsresult
OpenWindow( const char *urlstr
, const char *args
);
345 static nsresult
OpenBrowserWindow();
346 static void SetupSysTrayIcon();
347 static void RemoveSysTrayIcon();
349 static int mConversations
;
356 topicUnRegisterViewer
,
358 // Note: Insert new values above this line!!!!!
359 topicCount
// Count of the number of real topics
362 static HSZ mApplication
, mTopics
[ topicCount
];
364 static DWORD mInstance
;
365 static PRBool mCanHandleRequests
;
366 static PRUnichar mMutexName
[];
367 friend struct MessageWindow
;
368 }; // nsNativeAppSupportWin
370 NS_INTERFACE_MAP_BEGIN(nsNativeAppSupportWin
)
371 NS_INTERFACE_MAP_ENTRY(nsIObserver
)
372 NS_INTERFACE_MAP_END_INHERITING(nsNativeAppSupportBase
)
374 NS_IMPL_ADDREF_INHERITED(nsNativeAppSupportWin
, nsNativeAppSupportBase
)
375 NS_IMPL_RELEASE_INHERITED(nsNativeAppSupportWin
, nsNativeAppSupportBase
)
378 nsNativeAppSupportWin::CheckConsole() {
379 for ( int i
= 1; i
< gArgc
; i
++ ) {
380 if ( strcmp( "-console", gArgv
[i
] ) == 0
382 strcmp( "/console", gArgv
[i
] ) == 0 ) {
383 // Users wants to make sure we have a console.
384 // Try to allocate one.
386 BOOL rc
= ::AllocConsole();
388 // Console allocated. Fix it up so that output works in
389 // all cases. See http://support.microsoft.com/support/kb/articles/q105/3/05.asp.
392 int hCrt
= ::_open_osfhandle( (intptr_t)GetStdHandle( STD_OUTPUT_HANDLE
),
395 FILE *hf
= ::_fdopen( hCrt
, "w" );
399 ::fprintf( stdout
, "stdout directed to dynamic console\n" );
405 hCrt
= ::_open_osfhandle( (intptr_t)::GetStdHandle( STD_ERROR_HANDLE
),
408 FILE *hf
= ::_fdopen( hCrt
, "w" );
412 ::fprintf( stderr
, "stderr directed to dynamic console\n" );
418 /* Don't bother for now.
419 hCrt = ::_open_osfhandle( (long)::GetStdHandle( STD_INPUT_HANDLE ),
422 FILE *hf = ::_fdopen( hCrt, "r" );
429 // Failed. Probably because there already is one.
430 // There's little we can do, in any case.
433 // Remove the console argument from the command line.
435 gArgv
[i
] = gArgv
[i
+ 1];
441 // Don't bother doing this more than once.
450 // Create and return an instance of class nsNativeAppSupportWin.
452 NS_CreateNativeAppSupport( nsINativeAppSupport
**aResult
) {
453 nsNativeAppSupportWin
*pNative
= new nsNativeAppSupportWin
;
454 if (!pNative
) return NS_ERROR_OUT_OF_MEMORY
;
456 // Check for dynamic console creation request.
457 pNative
->CheckConsole();
460 NS_ADDREF( *aResult
);
466 #define MOZ_DDE_APPLICATION "Mozilla"
467 #define MOZ_MUTEX_NAMESPACE L"Local\\"
468 #define MOZ_STARTUP_MUTEX_NAME L"StartupMutex"
469 #define MOZ_DDE_START_TIMEOUT 30000
470 #define MOZ_DDE_STOP_TIMEOUT 15000
471 #define MOZ_DDE_EXEC_TIMEOUT 15000
473 // The array entries must match the enum ordering!
474 const char * const topicNames
[] = { "WWW_OpenURL",
476 "WWW_CancelProgress",
478 "WWW_RegisterViewer",
479 "WWW_UnRegisterViewer",
480 "WWW_GetWindowInfo" };
482 // Static member definitions.
483 int nsNativeAppSupportWin::mConversations
= 0;
485 HSZ
nsNativeAppSupportWin::mApplication
= 0;
486 HSZ
nsNativeAppSupportWin::mTopics
[nsNativeAppSupportWin::topicCount
] = { 0 };
488 DWORD
nsNativeAppSupportWin::mInstance
= 0;
489 PRBool
nsNativeAppSupportWin::mCanHandleRequests
= PR_FALSE
;
491 PRUnichar
nsNativeAppSupportWin::mMutexName
[ 128 ] = { 0 };
494 // Message window encapsulation.
495 struct MessageWindow
{
496 // ctor/dtor are simplistic
498 // Try to find window.
499 mHandle
= ::FindWindowW( className(), 0 );
507 // Class name: appName + "MessageWindow"
508 static const PRUnichar
*className() {
509 static PRUnichar classNameBuffer
[128];
510 static PRUnichar
*mClassName
= 0;
512 ::_snwprintf(classNameBuffer
,
513 128, // size of classNameBuffer in PRUnichars
515 NS_ConvertUTF8toUTF16(gAppData
->name
).get(),
517 mClassName
= classNameBuffer
;
522 // Create: Register class and create window.
523 NS_IMETHOD
Create() {
524 WNDCLASSW classStruct
= { 0, // style
525 &MessageWindow::WindowProc
, // lpfnWndProc
533 className() }; // lpszClassName
535 // Register the window class.
536 NS_ENSURE_TRUE( ::RegisterClassW( &classStruct
), NS_ERROR_FAILURE
);
538 // Create the window.
539 NS_ENSURE_TRUE( ( mHandle
= ::CreateWindowW(className(),
542 0,0,0,0, // x, y, cx, cy
546 0 ) ), // create struct
550 printf( "Message window = 0x%08X\n", (int)mHandle
);
556 // Destory: Get rid of window and reset mHandle.
557 NS_IMETHOD
Destroy() {
558 nsresult retval
= NS_OK
;
561 // DestroyWindow can only destroy windows created from
563 BOOL desRes
= DestroyWindow( mHandle
);
564 if ( FALSE
!= desRes
) {
568 retval
= NS_ERROR_FAILURE
;
575 // SendRequest: Pass the command line via WM_COPYDATA to message window.
576 NS_IMETHOD
SendRequest() {
577 WCHAR
*cmd
= ::GetCommandLineW();
579 _wgetcwd(cwd
, MAX_PATH
);
581 // Construct a narrow UTF8 buffer <commandline>\0<workingdir>\0
583 // For WinCE, we're stuck with providing our own argv[0] for the remote
585 NS_ConvertUTF16toUTF8
utf8buffer(L
"dummy ");
586 AppendUTF16toUTF8(cmd
, utf8buffer
);
588 NS_ConvertUTF16toUTF8
utf8buffer(cmd
);
590 utf8buffer
.Append('\0');
591 AppendUTF16toUTF8(cwd
, utf8buffer
);
592 utf8buffer
.Append('\0');
594 // We used to set dwData to zero, when we didn't send the working dir.
595 // Now we're using it as a version number.
596 COPYDATASTRUCT cds
= {
599 (void*) utf8buffer
.get()
601 // Bring the already running Mozilla process to the foreground.
602 // nsWindow will restore the window (if minimized) and raise it.
604 // for activating the existing window on wince we need "| 0x01"
605 // see http://msdn.microsoft.com/en-us/library/ms940024.aspx for details
606 ::SetForegroundWindow( (HWND
)(((ULONG
) mHandle
) | 0x01) );
608 ::SetForegroundWindow( mHandle
);
610 ::SendMessage( mHandle
, WM_COPYDATA
, 0, (LPARAM
)&cds
);
615 static LRESULT CALLBACK
WindowProc( HWND msgWindow
, UINT msg
, WPARAM wp
, LPARAM lp
) {
616 if ( msg
== WM_COPYDATA
) {
617 if (!nsNativeAppSupportWin::mCanHandleRequests
)
620 // This is an incoming request.
621 COPYDATASTRUCT
*cds
= (COPYDATASTRUCT
*)lp
;
623 printf( "Incoming request: %s\n", (const char*)cds
->lpData
);
625 nsCOMPtr
<nsILocalFile
> workingDir
;
627 if (1 >= cds
->dwData
) {
628 char* wdpath
= (char*) cds
->lpData
;
629 // skip the command line, and get the working dir of the
630 // other process, which is after the first null char
637 printf( "Working dir: %s\n", wdpath
);
640 NS_NewLocalFile(NS_ConvertUTF8toUTF16(wdpath
),
642 getter_AddRefs(workingDir
));
644 (void)nsNativeAppSupportWin::HandleCommandLine((char*)cds
->lpData
, workingDir
, nsICommandLine::STATE_REMOTE_AUTO
);
646 // Get current window and return its window handle.
647 nsCOMPtr
<nsIDOMWindowInternal
> win
;
648 GetMostRecentWindow( 0, getter_AddRefs( win
) );
649 return win
? (LRESULT
)hwndForDOMWindow( win
) : 0;
651 return DefWindowProc( msgWindow
, msg
, wp
, lp
);
656 }; // struct MessageWindow
658 /* Start: Tries to find the "message window" to determine if it
659 * exists. If so, then Mozilla is already running. In that
660 * case, we use the handle to the "message" window and send
661 * a request corresponding to this process's command line
664 * If not, then this is the first instance of Mozilla. In
665 * that case, we create and set up the message window.
667 * The checking for existence of the message window must
668 * be protected by use of a mutex semaphore.
671 nsNativeAppSupportWin::Start( PRBool
*aResult
) {
672 NS_ENSURE_ARG( aResult
);
673 NS_ENSURE_TRUE( mInstance
== 0, NS_ERROR_NOT_INITIALIZED
);
674 NS_ENSURE_STATE( gAppData
);
676 if (getenv("MOZ_NO_REMOTE"))
682 nsresult rv
= NS_ERROR_FAILURE
;
687 // Build mutex name from app name.
688 ::_snwprintf(mMutexName
, sizeof mMutexName
/ sizeof(PRUnichar
), L
"%s%s%s",
690 NS_ConvertUTF8toUTF16(gAppData
->name
).get(),
691 MOZ_STARTUP_MUTEX_NAME
);
692 Mutex startupLock
= Mutex( mMutexName
);
694 NS_ENSURE_TRUE( startupLock
.Lock( MOZ_DDE_START_TIMEOUT
), NS_ERROR_FAILURE
);
696 // Search for existing message window.
697 MessageWindow msgWindow
;
698 if ( (HWND
)msgWindow
) {
699 // We are a client process. Pass request to message window.
700 rv
= msgWindow
.SendRequest();
702 // We will be server.
703 rv
= msgWindow
.Create();
704 if ( NS_SUCCEEDED( rv
) ) {
706 // Start up DDE server.
709 // Tell caller to spin message loop.
714 startupLock
.Unlock();
720 nsNativeAppSupportWin::InitTopicStrings() {
721 for ( int i
= 0; i
< topicCount
; i
++ ) {
722 if ( !( mTopics
[ i
] = DdeCreateStringHandleA( mInstance
, const_cast<char *>(topicNames
[ i
]), CP_WINANSI
) ) ) {
730 nsNativeAppSupportWin::FindTopic( HSZ topic
) {
731 for ( int i
= 0; i
< topicCount
; i
++ ) {
732 if ( DdeCmpStringHandles( topic
, mTopics
[i
] ) == 0 ) {
742 // This used to be the Start() method when we were using DDE as the
743 // primary IPC mechanism between secondary Mozilla processes and the
744 // initial "server" process.
746 // Now, it simply initializes the DDE server. The caller must check
747 // that this process is to be the server, and, must acquire the DDE
748 // startup mutex semaphore prior to calling this routine. See ::Start(),
751 nsNativeAppSupportWin::StartDDE() {
752 NS_ENSURE_TRUE( mInstance
== 0, NS_ERROR_NOT_INITIALIZED
);
755 NS_ENSURE_TRUE( DMLERR_NO_ERROR
== DdeInitialize( &mInstance
,
756 nsNativeAppSupportWin::HandleDDENotification
,
761 // Allocate DDE strings.
762 NS_ENSURE_TRUE( ( mApplication
= DdeCreateStringHandleA( mInstance
, (char*) gAppData
->name
, CP_WINANSI
) ) && InitTopicStrings(),
765 // Next step is to register a DDE service.
766 NS_ENSURE_TRUE( DdeNameService( mInstance
, mApplication
, 0, DNS_REGISTER
), NS_ERROR_FAILURE
);
769 printf( "DDE server started\n" );
775 // If no DDE conversations are pending, terminate DDE.
777 nsNativeAppSupportWin::Stop( PRBool
*aResult
) {
778 NS_ENSURE_ARG( aResult
);
779 NS_ENSURE_TRUE( mInstance
, NS_ERROR_NOT_INITIALIZED
);
784 Mutex
ddeLock( mMutexName
);
786 if ( ddeLock
.Lock( MOZ_DDE_STOP_TIMEOUT
) ) {
787 if ( mConversations
== 0 ) {
796 // No DDE application name specified, but that's OK. Just
805 nsNativeAppSupportWin::Observe(nsISupports
* aSubject
, const char* aTopic
,
806 const PRUnichar
* aData
)
808 if (strcmp(aTopic
, "quit-application") == 0) {
811 NS_ERROR("Unexpected observer topic.");
817 // Terminate DDE regardless.
819 nsNativeAppSupportWin::Quit() {
820 // If another process wants to look for the message window, they need
821 // to wait to hold the lock, in which case they will not find the
822 // window as we will destroy ours under our lock.
823 // When the mutex goes off the stack, it is unlocked via destructor.
824 Mutex
mutexLock(mMutexName
);
825 NS_ENSURE_TRUE(mutexLock
.Lock(MOZ_DDE_START_TIMEOUT
), NS_ERROR_FAILURE
);
827 // If we've got a message window to receive IPC or new window requests,
828 // get rid of it as we are shutting down.
829 // Note: Destroy calls DestroyWindow, which will only work on a window
830 // created by the same thread.
835 // Unregister application name.
837 DdeNameService( mInstance
, mApplication
, 0, DNS_UNREGISTER
);
839 if ( mApplication
) {
840 DdeFreeStringHandle( mInstance
, mApplication
);
843 for ( int i
= 0; i
< topicCount
; i
++ ) {
845 DdeFreeStringHandle( mInstance
, mTopics
[i
] );
849 DdeUninitialize( mInstance
);
853 printf( "DDE server stopped\n" );
861 nsNativeAppSupportWin::Enable()
863 mCanHandleRequests
= PR_TRUE
;
865 nsCOMPtr
<nsIObserverService
> obs
= mozilla::services::GetObserverService();
867 obs
->AddObserver(this, "quit-application", PR_FALSE
);
869 NS_ERROR("No observer service?");
876 // Macro to generate case statement for a given XTYP value.
877 #define XTYP_CASE(t) \
878 case t: result = #t; break
880 static nsCString
uTypeDesc( UINT uType
) {
883 XTYP_CASE(XTYP_ADVSTART
);
884 XTYP_CASE(XTYP_CONNECT
);
885 XTYP_CASE(XTYP_ADVREQ
);
886 XTYP_CASE(XTYP_REQUEST
);
887 XTYP_CASE(XTYP_WILDCONNECT
);
888 XTYP_CASE(XTYP_ADVDATA
);
889 XTYP_CASE(XTYP_EXECUTE
);
890 XTYP_CASE(XTYP_POKE
);
891 XTYP_CASE(XTYP_ADVSTOP
);
892 XTYP_CASE(XTYP_CONNECT_CONFIRM
);
893 XTYP_CASE(XTYP_DISCONNECT
);
894 XTYP_CASE(XTYP_ERROR
);
895 XTYP_CASE(XTYP_MONITOR
);
896 XTYP_CASE(XTYP_REGISTER
);
897 XTYP_CASE(XTYP_XACT_COMPLETE
);
898 XTYP_CASE(XTYP_UNREGISTER
);
899 default: result
= "XTYP_?????";
904 static nsCString
hszValue( DWORD instance
, HSZ hsz
) {
905 // Extract string from HSZ.
906 nsCString
result("[");
907 DWORD len
= DdeQueryString( instance
, hsz
, NULL
, NULL
, CP_WINANSI
);
910 DdeQueryString( instance
, hsz
, buffer
, sizeof buffer
, CP_WINANSI
);
916 #elif !defined(WINCE)
917 // These are purely a safety measure to avoid the infamous "won't
918 // build non-debug" type Tinderbox flames.
919 static nsCString
uTypeDesc( UINT
) {
920 return nsCString( "?" );
922 static nsCString
hszValue( DWORD
, HSZ
) {
923 return nsCString( "?" );
928 // Utility function to escape double-quotes within a string.
929 static void escapeQuotes( nsAString
&aString
) {
933 offset
= aString
.FindChar( '"', ++offset
);
934 if ( offset
== kNotFound
) {
935 // No more quotes, exit.
938 // Insert back-slash ahead of the '"'.
939 aString
.Insert( PRUnichar('\\'), offset
);
940 // Increment offset because we just inserted a slash
949 nsNativeAppSupportWin::HandleDDENotification( UINT uType
, // transaction type
950 UINT uFmt
, // clipboard data format
951 HCONV hconv
, // handle to the conversation
952 HSZ hsz1
, // handle to a string
953 HSZ hsz2
, // handle to a string
954 HDDEDATA hdata
, // handle to a global memory object
955 ULONG_PTR dwData1
, // transaction-specific data
956 ULONG_PTR dwData2
) { // transaction-specific data
958 if (!mCanHandleRequests
)
963 printf( "DDE: uType =%s\n", uTypeDesc( uType
).get() );
964 printf( " uFmt =%u\n", (unsigned)uFmt
);
965 printf( " hconv =%08x\n", (int)hconv
);
966 printf( " hsz1 =%08x:%s\n", (int)hsz1
, hszValue( mInstance
, hsz1
).get() );
967 printf( " hsz2 =%08x:%s\n", (int)hsz2
, hszValue( mInstance
, hsz2
).get() );
968 printf( " hdata =%08x\n", (int)hdata
);
969 printf( " dwData1=%08x\n", (int)dwData1
);
970 printf( " dwData2=%08x\n", (int)dwData2
);
974 if ( uType
& XCLASS_BOOL
) {
977 // Make sure its for our service/topic.
978 if ( FindTopic( hsz1
) != -1 ) {
979 // We support this connection.
980 result
= (HDDEDATA
)1;
983 case XTYP_CONNECT_CONFIRM
:
984 // We don't care about the conversation handle, at this point.
985 result
= (HDDEDATA
)1;
988 } else if ( uType
& XCLASS_DATA
) {
989 if ( uType
== XTYP_REQUEST
) {
990 switch ( FindTopic( hsz1
) ) {
992 // Open a given URL...
994 // Get the URL from the first argument in the command.
996 ParseDDEArg(hsz2
, 0, url
);
998 // Read the 3rd argument in the command to determine if a
999 // new window is to be used.
1000 nsAutoString windowID
;
1001 ParseDDEArg(hsz2
, 2, windowID
);
1002 // "" means to open the URL in a new window.
1003 if ( windowID
.IsEmpty() ) {
1004 url
.Insert(NS_LITERAL_STRING("mozilla -new-window "), 0);
1007 url
.Insert(NS_LITERAL_STRING("mozilla -url "), 0);
1011 printf( "Handling dde XTYP_REQUEST request: [%s]...\n", NS_ConvertUTF16toUTF8(url
).get() );
1014 HandleCommandLine(NS_ConvertUTF16toUTF8(url
).get(), nsnull
, nsICommandLine::STATE_REMOTE_EXPLICIT
);
1016 // Return pseudo window ID.
1017 result
= CreateDDEData( 1 );
1020 case topicGetWindowInfo
: {
1021 // This topic has to get the current URL, get the current
1022 // page title and then format the output into the DDE
1023 // return string. The return value is "URL","Page Title",
1024 // "Window ID" however the window ID is not used for this
1025 // command, therefore it is returned as a null string
1027 // This isn't really a loop. We just use "break"
1028 // statements to bypass the remaining steps when
1029 // something goes wrong.
1031 // Get most recently used Nav window.
1032 nsCOMPtr
<nsIDOMWindowInternal
> navWin
;
1033 GetMostRecentWindow( NS_LITERAL_STRING( "navigator:browser" ).get(),
1034 getter_AddRefs( navWin
) );
1036 // There is not a window open
1039 // Get content window.
1040 nsCOMPtr
<nsIDOMWindow
> content
;
1041 navWin
->GetContent( getter_AddRefs( content
) );
1045 // Convert that to internal interface.
1046 nsCOMPtr
<nsPIDOMWindow
> internalContent( do_QueryInterface( content
) );
1047 if ( !internalContent
) {
1051 nsCOMPtr
<nsIDOMLocation
> location
;
1052 internalContent
->GetLocation( getter_AddRefs( location
) );
1056 // Get href for URL.
1058 if ( NS_FAILED( location
->GetHref( url
) ) ) {
1061 // Escape any double-quotes.
1062 escapeQuotes( url
);
1064 // Now for the title...
1066 // Get the base window from the doc shell...
1067 nsCOMPtr
<nsIBaseWindow
> baseWindow
=
1068 do_QueryInterface( internalContent
->GetDocShell() );
1069 if ( !baseWindow
) {
1072 // And from the base window we can get the title.
1073 nsXPIDLString title
;
1077 baseWindow
->GetTitle(getter_Copies(title
));
1078 // Escape any double-quotes in the title.
1079 escapeQuotes( title
);
1081 // Use a string buffer for the output data, first
1083 nsCAutoString
outpt( NS_LITERAL_CSTRING("\"") );
1084 // Now copy the URL converting the Unicode string
1085 // to a single-byte ASCII string
1086 nsCAutoString tmpNativeStr
;
1087 NS_CopyUnicodeToNative( url
, tmpNativeStr
);
1088 outpt
.Append( tmpNativeStr
);
1089 // Add the "," used to separate the URL and the page
1091 outpt
.Append( NS_LITERAL_CSTRING("\",\"") );
1092 // Now copy the current page title to the return string
1093 NS_CopyUnicodeToNative( title
, tmpNativeStr
);
1094 outpt
.Append( tmpNativeStr
);
1095 // Fill out the return string with the remainin ",""
1096 outpt
.Append( NS_LITERAL_CSTRING( "\",\"\"" ));
1098 // Create a DDE handle to a char string for the data
1099 // being returned, this copies and creates a "shared"
1100 // copy of the DDE response until the calling APP
1101 // reads it and says it can be freed.
1102 result
= CreateDDEData( (LPBYTE
)(const char*)outpt
.get(),
1103 outpt
.Length() + 1 );
1105 printf( "WWW_GetWindowInfo->%s\n", outpt
.get() );
1107 } while ( PR_FALSE
);
1110 case topicActivate
: {
1111 // Activate a Nav window...
1112 nsAutoString windowID
;
1113 ParseDDEArg(hsz2
, 0, windowID
);
1114 // 4294967295 is decimal for 0xFFFFFFFF which is also a
1115 // correct value to do that Activate last window stuff
1116 if ( windowID
.EqualsLiteral( "-1" ) ||
1117 windowID
.EqualsLiteral( "4294967295" ) ) {
1118 // We only support activating the most recent window (or a new one).
1119 ActivateLastWindow();
1120 // Return pseudo window ID.
1121 result
= CreateDDEData( 1 );
1125 case topicVersion
: {
1126 // Return version. We're restarting at 1.0!
1127 DWORD version
= 1 << 16; // "1.0"
1128 result
= CreateDDEData( version
);
1131 case topicRegisterViewer
: {
1132 // Register new viewer (not implemented).
1133 result
= CreateDDEData( PR_FALSE
);
1136 case topicUnRegisterViewer
: {
1137 // Unregister new viewer (not implemented).
1138 result
= CreateDDEData( PR_FALSE
);
1144 } else if ( uType
& XTYP_POKE
) {
1145 switch ( FindTopic( hsz1
) ) {
1146 case topicCancelProgress
: {
1147 // "Handle" progress cancel (actually, pretty much ignored).
1148 result
= (HDDEDATA
)DDE_FACK
;
1155 } else if ( uType
& XCLASS_FLAGS
) {
1156 if ( uType
== XTYP_EXECUTE
) {
1157 // Prove that we received the request.
1159 LPBYTE request
= DdeAccessData( hdata
, &bytes
);
1161 printf( "Handling dde request: [%s]...\n", (char*)request
);
1165 ParseDDEArg((const WCHAR
*) request
, 0, url
);
1167 // Read the 3rd argument in the command to determine if a
1168 // new window is to be used.
1169 nsAutoString windowID
;
1170 ParseDDEArg((const WCHAR
*) request
, 2, windowID
);
1172 // "" means to open the URL in a new window.
1173 if ( windowID
.IsEmpty() ) {
1174 url
.Insert(NS_LITERAL_STRING("mozilla -new-window "), 0);
1177 url
.Insert(NS_LITERAL_STRING("mozilla -url "), 0);
1180 printf( "Handling dde XTYP_REQUEST request: [%s]...\n", NS_ConvertUTF16toUTF8(url
).get() );
1183 HandleCommandLine(NS_ConvertUTF16toUTF8(url
).get(), nsnull
, nsICommandLine::STATE_REMOTE_EXPLICIT
);
1185 // Release the data.
1186 DdeUnaccessData( hdata
);
1187 result
= (HDDEDATA
)DDE_FACK
;
1189 result
= (HDDEDATA
)DDE_FNOTPROCESSED
;
1191 } else if ( uType
& XCLASS_NOTIFICATION
) {
1194 printf( "DDE result=%d (0x%08X)\n", (int)result
, (int)result
);
1199 // Utility function to advance to end of quoted string.
1200 // p+offset must point to the comma preceding the arg on entry.
1201 // On return, p+result points to the closing '"' (or end of the string
1202 // if the closing '"' is missing) if the arg is quoted. If the arg
1203 // is not quoted, then p+result will point to the first character
1205 static PRInt32
advanceToEndOfQuotedArg( const WCHAR
*p
, PRInt32 offset
, PRInt32 len
) {
1206 // Check whether the current arg is quoted.
1207 if ( p
[++offset
] == '"' ) {
1208 // Advance past the closing quote.
1209 while ( offset
< len
&& p
[++offset
] != '"' ) {
1210 // If the current character is a backslash, then the
1211 // next character can't be a *real* '"', so skip it.
1212 if ( p
[offset
] == '\\' ) {
1221 void nsNativeAppSupportWin::ParseDDEArg( const WCHAR
* args
, int index
, nsString
& aString
) {
1223 nsDependentString
temp(args
);
1225 // offset points to the comma preceding the desired arg.
1226 PRInt32 offset
= -1;
1227 // Skip commas till we get to the arg we want.
1229 // If this arg is quoted, then go to closing quote.
1230 offset
= advanceToEndOfQuotedArg( args
, offset
, temp
.Length());
1232 offset
= temp
.FindChar( ',', offset
);
1233 if ( offset
== kNotFound
) {
1234 // No more commas, give up.
1239 // The desired argument starts just past the preceding comma,
1240 // which offset points to, and extends until the following
1241 // comma (or the end of the string).
1243 // Since the argument might be enclosed in quotes, we need to
1244 // deal with that before searching for the terminating comma.
1245 // We advance offset so it ends up pointing to the start of
1246 // the argument we want.
1247 PRInt32 end
= advanceToEndOfQuotedArg( args
, offset
++, temp
.Length() );
1248 // Find next comma (or end of string).
1249 end
= temp
.FindChar( ',', end
);
1250 if ( end
== kNotFound
) {
1251 // Arg is the rest of the string.
1252 end
= temp
.Length();
1255 aString
.Assign( args
+ offset
, end
- offset
);
1260 // Utility to parse out argument from a DDE item string.
1261 void nsNativeAppSupportWin::ParseDDEArg( HSZ args
, int index
, nsString
& aString
) {
1262 DWORD argLen
= DdeQueryStringW( mInstance
, args
, NULL
, 0, CP_WINUNICODE
);
1263 // there wasn't any string, so return empty string
1264 if ( !argLen
) return;
1266 // Ensure result's buffer is sufficiently big.
1267 temp
.SetLength( argLen
);
1268 // Now get the string contents.
1269 DdeQueryString( mInstance
, args
, temp
.BeginWriting(), temp
.Length(), CP_WINUNICODE
);
1270 // Parse out the given arg.
1271 ParseDDEArg(temp
.get(), index
, aString
);
1275 HDDEDATA
nsNativeAppSupportWin::CreateDDEData( DWORD value
) {
1276 return CreateDDEData( (LPBYTE
)&value
, sizeof value
);
1279 HDDEDATA
nsNativeAppSupportWin::CreateDDEData( LPBYTE value
, DWORD len
) {
1280 HDDEDATA result
= DdeCreateDataHandle( mInstance
,
1291 void nsNativeAppSupportWin::ActivateLastWindow() {
1292 nsCOMPtr
<nsIDOMWindowInternal
> navWin
;
1293 GetMostRecentWindow( NS_LITERAL_STRING("navigator:browser").get(), getter_AddRefs( navWin
) );
1295 // Activate that window.
1296 activateWindow( navWin
);
1298 // Need to create a Navigator window, then.
1299 OpenBrowserWindow();
1304 nsNativeAppSupportWin::HandleCommandLine(const char* aCmdLineString
,
1305 nsIFile
* aWorkingDir
,
1310 int justCounting
= 1;
1314 int between
, quoted
, bSlashCount
;
1319 nsCOMPtr
<nsICommandLineRunner
> cmdLine
1320 (do_CreateInstance("@mozilla.org/toolkit/command-line;1"));
1322 NS_ERROR("Couldn't create command line!");
1326 // Parse command line args according to MS spec
1327 // (see "Parsing C++ Command-Line Arguments" at
1328 // http://msdn.microsoft.com/library/devprods/vs6/visualc/vclang/_pluslang_parsing_c.2b2b_.command.2d.line_arguments.htm).
1329 // We loop if we've not finished the second pass through.
1331 // Initialize if required.
1335 argc
= quoted
= bSlashCount
= 0;
1340 // We are traversing whitespace between args.
1341 // Check for start of next arg.
1342 if ( *p
!= 0 && !isspace( *p
) ) {
1343 // Start of another arg.
1348 // Count the backslash.
1352 // Remember we're inside quotes.
1356 // Add character to arg.
1361 // Another space between args, ignore it.
1364 // We are processing the contents of an argument.
1365 // Check for whitespace or end.
1366 if ( *p
== 0 || ( !quoted
&& isspace( *p
) ) ) {
1367 // Process pending backslashes (interpret them
1368 // literally since they're not followed by a ").
1369 while( bSlashCount
) {
1374 if ( !justCounting
) {
1375 argv
[argc
] = new char[ arg
.Length() + 1 ];
1376 strcpy( argv
[argc
], arg
.get() );
1379 // We're now between args.
1382 // Still inside argument, process the character.
1385 // First, digest preceding backslashes (if any).
1386 while ( bSlashCount
> 1 ) {
1387 // Put one backsplash in arg for each pair.
1391 if ( bSlashCount
) {
1392 // Quote is literal.
1396 // Quote starts or ends a quoted section.
1398 // Check for special case of consecutive double
1399 // quotes inside a quoted section.
1400 if ( *(p
+1) == '"' ) {
1401 // This implies a literal double-quote. Fake that
1402 // out by causing next double-quote to look as
1403 // if it was preceded by a backslash.
1418 // Accept any preceding backslashes literally.
1419 while ( bSlashCount
) {
1423 // Just add next char to the current arg.
1429 // Check for end of input.
1431 // Go to next character.
1434 // If on first pass, go on to second.
1435 if ( justCounting
) {
1436 // Allocate argv array.
1437 argv
= new char*[ argc
];
1439 // Start second pass
1449 rv
= cmdLine
->Init(argc
, argv
, aWorkingDir
, aState
);
1453 delete [] argv
[ --argc
];
1457 if (NS_FAILED(rv
)) {
1458 NS_ERROR("Error initializing command line.");
1466 nsNativeAppSupportWin::OpenWindow( const char*urlstr
, const char *args
) {
1468 nsresult rv
= NS_ERROR_FAILURE
;
1470 nsCOMPtr
<nsIWindowWatcher
> wwatch(do_GetService(NS_WINDOWWATCHER_CONTRACTID
));
1471 nsCOMPtr
<nsISupportsCString
> sarg(do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID
));
1473 sarg
->SetData(nsDependentCString(args
));
1475 if (wwatch
&& sarg
) {
1476 nsCOMPtr
<nsIDOMWindow
> newWindow
;
1477 rv
= wwatch
->OpenWindow(0, urlstr
, "_blank", "chrome,dialog=no,all",
1478 sarg
, getter_AddRefs(newWindow
));
1481 printf("Get WindowWatcher (or create string) failed\n");
1487 HWND
hwndForDOMWindow( nsISupports
*window
) {
1488 nsCOMPtr
<nsPIDOMWindow
> pidomwindow( do_QueryInterface(window
) );
1489 if ( !pidomwindow
) {
1493 nsCOMPtr
<nsIBaseWindow
> ppBaseWindow
=
1494 do_QueryInterface( pidomwindow
->GetDocShell() );
1495 if ( !ppBaseWindow
) {
1499 nsCOMPtr
<nsIWidget
> ppWidget
;
1500 ppBaseWindow
->GetMainWidget( getter_AddRefs( ppWidget
) );
1502 return (HWND
)( ppWidget
->GetNativeData( NS_NATIVE_WIDGET
) );
1505 static const char sJSStackContractID
[] = "@mozilla.org/js/xpc/ContextStack;1";
1507 class SafeJSContext
{
1513 JSContext
*get() { return mContext
; }
1516 nsCOMPtr
<nsIThreadJSContextStack
> mService
;
1517 JSContext
*mContext
;
1520 SafeJSContext::SafeJSContext() : mContext(nsnull
) {
1523 SafeJSContext::~SafeJSContext() {
1528 rv
= mService
->Pop(&cx
);
1529 NS_ASSERTION(NS_SUCCEEDED(rv
) && cx
== mContext
, "JSContext push/pop mismatch");
1533 nsresult
SafeJSContext::Push() {
1534 if (mContext
) // only once
1535 return NS_ERROR_FAILURE
;
1537 mService
= do_GetService(sJSStackContractID
);
1540 if (NS_SUCCEEDED(mService
->GetSafeJSContext(&cx
)) &&
1542 NS_SUCCEEDED(mService
->Push(cx
))) {
1543 // Save cx in mContext to indicate need to pop.
1547 return mContext
? NS_OK
: NS_ERROR_FAILURE
;
1552 nsNativeAppSupportWin::OpenBrowserWindow()
1554 nsresult rv
= NS_OK
;
1556 // Open the argument URL in the most recently used Navigator window.
1557 // If there is no Nav window, open a new one.
1559 // If at all possible, hand the request off to the most recent
1562 nsCOMPtr
<nsIDOMWindowInternal
> navWin
;
1563 GetMostRecentWindow( NS_LITERAL_STRING( "navigator:browser" ).get(), getter_AddRefs( navWin
) );
1565 // This isn't really a loop. We just use "break" statements to fall
1566 // out to the OpenWindow call when things go awry.
1568 // If caller requires a new window, then don't use an existing one.
1570 // Have to open a new one.
1574 nsCOMPtr
<nsIBrowserDOMWindow
> bwin
;
1575 { // scope a bunch of temporary cruft used to generate bwin
1576 nsCOMPtr
<nsIWebNavigation
> navNav( do_GetInterface( navWin
) );
1577 nsCOMPtr
<nsIDocShellTreeItem
> navItem( do_QueryInterface( navNav
) );
1579 nsCOMPtr
<nsIDocShellTreeItem
> rootItem
;
1580 navItem
->GetRootTreeItem( getter_AddRefs( rootItem
) );
1581 nsCOMPtr
<nsIDOMWindow
> rootWin( do_GetInterface( rootItem
) );
1582 nsCOMPtr
<nsIDOMChromeWindow
> chromeWin(do_QueryInterface(rootWin
));
1584 chromeWin
->GetBrowserDOMWindow( getter_AddRefs ( bwin
) );
1588 nsCOMPtr
<nsIURI
> uri
;
1589 NS_NewURI( getter_AddRefs( uri
), NS_LITERAL_CSTRING("about:blank"), 0, 0 );
1591 nsCOMPtr
<nsIDOMWindow
> container
;
1592 rv
= bwin
->OpenURI( uri
, 0,
1593 nsIBrowserDOMWindow::OPEN_DEFAULTWINDOW
,
1594 nsIBrowserDOMWindow::OPEN_EXTERNAL
,
1595 getter_AddRefs( container
) );
1596 if ( NS_SUCCEEDED( rv
) )
1601 NS_ERROR("failed to hand off external URL to extant window");
1602 } while ( PR_FALSE
);
1604 // open a new window if caller requested it or if anything above failed
1606 char* argv
[] = { 0 };
1607 nsCOMPtr
<nsICommandLineRunner
> cmdLine
1608 (do_CreateInstance("@mozilla.org/toolkit/command-line;1"));
1609 NS_ENSURE_TRUE(cmdLine
, NS_ERROR_FAILURE
);
1611 rv
= cmdLine
->Init(0, argv
, nsnull
, nsICommandLine::STATE_REMOTE_EXPLICIT
);
1612 NS_ENSURE_SUCCESS(rv
, rv
);
1614 return cmdLine
->Run();