diff --git a/widget/src/windows/nsFilePicker.cpp b/widget/src/windows/nsFilePicker.cpp index 17e00c48185b..10da8f343890 100644 --- a/widget/src/windows/nsFilePicker.cpp +++ b/widget/src/windows/nsFilePicker.cpp @@ -63,6 +63,10 @@ PRUnichar *nsFilePicker::mLastUsedUnicodeDirectory; char nsFilePicker::mLastUsedDirectory[MAX_PATH+1] = { 0 }; +static const PRUnichar kDialogPtrProp[] = L"DialogPtrProperty"; +static const DWORD kDialogTimerID = 9999; +static const unsigned long kDialogTimerTimeout = 300; + #define MAX_EXTENSION_LENGTH 10 /////////////////////////////////////////////////////////////////////////////// @@ -158,11 +162,44 @@ private: nsRefPtr mWindow; }; +// Manages a simple callback timer +class AutoTimerCallbackCancel +{ +public: + AutoTimerCallbackCancel(nsFilePicker* aTarget, + nsTimerCallbackFunc aCallbackFunc) { + Init(aTarget, aCallbackFunc); + } + + ~AutoTimerCallbackCancel() { + if (mPickerCallbackTimer) { + mPickerCallbackTimer->Cancel(); + } + } + +private: + void Init(nsFilePicker* aTarget, + nsTimerCallbackFunc aCallbackFunc) { + mPickerCallbackTimer = do_CreateInstance("@mozilla.org/timer;1"); + if (!mPickerCallbackTimer) { + NS_WARNING("do_CreateInstance for timer failed??"); + return; + } + mPickerCallbackTimer->InitWithFuncCallback(aCallbackFunc, + aTarget, + kDialogTimerTimeout, + nsITimer::TYPE_REPEATING_SLACK); + } + nsCOMPtr mPickerCallbackTimer; + +}; + /////////////////////////////////////////////////////////////////////////////// // nsIFilePicker nsFilePicker::nsFilePicker() : mSelectedType(1) + , mDlgWnd(NULL) #if MOZ_WINSDK_TARGETVER >= MOZ_NTDDI_LONGHORN , mFDECookie(0) #endif @@ -181,6 +218,7 @@ nsFilePicker::~nsFilePicker() NS_IMPL_ISUPPORTS1(nsFilePicker, nsIFilePicker) + #if MOZ_WINSDK_TARGETVER >= MOZ_NTDDI_LONGHORN STDMETHODIMP nsFilePicker::QueryInterface(REFIID refiid, void** ppvResult) { @@ -238,23 +276,54 @@ EnsureWindowVisible(HWND hwnd) // Callback hook which will ensure that the window is visible. Currently // only in use on os <= XP. -static UINT_PTR CALLBACK -FilePickerHook(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +UINT_PTR CALLBACK +nsFilePicker::FilePickerHook(HWND hwnd, + UINT msg, + WPARAM wParam, + LPARAM lParam) { - if (msg == WM_NOTIFY) { - LPOFNOTIFYW lpofn = (LPOFNOTIFYW) lParam; - if (!lpofn || !lpofn->lpOFN) { - return 0; - } - - if (CDN_INITDONE == lpofn->hdr.code) { - // The Window will be automatically moved to the last position after - // CDN_INITDONE. We post a message to ensure the window will be visible - // so it will be done after the automatic last position window move. - PostMessage(hwnd, MOZ_WM_ENSUREVISIBLE, 0, 0); - } - } else if (msg == MOZ_WM_ENSUREVISIBLE) { - EnsureWindowVisible(GetParent(hwnd)); + switch(msg) { + case WM_NOTIFY: + { + LPOFNOTIFYW lpofn = (LPOFNOTIFYW) lParam; + if (!lpofn || !lpofn->lpOFN) { + return 0; + } + + if (CDN_INITDONE == lpofn->hdr.code) { + // The Window will be automatically moved to the last position after + // CDN_INITDONE. We post a message to ensure the window will be visible + // so it will be done after the automatic last position window move. + PostMessage(hwnd, MOZ_WM_ENSUREVISIBLE, 0, 0); + } + } + break; + case MOZ_WM_ENSUREVISIBLE: + EnsureWindowVisible(GetParent(hwnd)); + break; + case WM_INITDIALOG: + { + OPENFILENAMEW* pofn = reinterpret_cast(lParam); + SetProp(hwnd, kDialogPtrProp, (HANDLE)pofn->lCustData); + nsFilePicker* picker = reinterpret_cast(pofn->lCustData); + if (picker) { + picker->SetDialogHandle(hwnd); + SetTimer(hwnd, kDialogTimerID, kDialogTimerTimeout, NULL); + } + } + break; + case WM_TIMER: + { + // Check to see if our parent has been torn down, if so, we close too. + if (wParam == kDialogTimerID) { + nsFilePicker* picker = + reinterpret_cast(GetProp(hwnd, kDialogPtrProp)); + if (picker && picker->ClosePickerIfNeeded(true)) { + KillTimer(hwnd, kDialogTimerID); + } + } + } + break; } return 0; } @@ -262,8 +331,11 @@ FilePickerHook(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) // Callback hook which will dynamically allocate a buffer large enough // for the file picker dialog. Currently only in use on os <= XP. -static UINT_PTR CALLBACK -MultiFilePickerHook(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +UINT_PTR CALLBACK +nsFilePicker::MultiFilePickerHook(HWND hwnd, + UINT msg, + WPARAM wParam, + LPARAM lParam) { switch (msg) { case WM_INITDIALOG: @@ -275,6 +347,15 @@ MultiFilePickerHook(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) L"ComboBoxEx32", NULL ); if(comboBox) SendMessage(comboBox, CB_LIMITTEXT, 0, 0); + // Store our nsFilePicker ptr for future use + OPENFILENAMEW* pofn = reinterpret_cast(lParam); + SetProp(hwnd, kDialogPtrProp, (HANDLE)pofn->lCustData); + nsFilePicker* picker = + reinterpret_cast(pofn->lCustData); + if (picker) { + picker->SetDialogHandle(hwnd); + SetTimer(hwnd, kDialogTimerID, kDialogTimerTimeout, NULL); + } } break; case WM_NOTIFY: @@ -327,6 +408,18 @@ MultiFilePickerHook(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) } } break; + case WM_TIMER: + { + // Check to see if our parent has been torn down, if so, we close too. + if (wParam == kDialogTimerID) { + nsFilePicker* picker = + reinterpret_cast(GetProp(hwnd, kDialogPtrProp)); + if (picker && picker->ClosePickerIfNeeded(true)) { + KillTimer(hwnd, kDialogTimerID); + } + } + } + break; } return FilePickerHook(hwnd, msg, wParam, lParam); @@ -374,6 +467,21 @@ nsFilePicker::OnShareViolation(IFileDialog *pfd, HRESULT nsFilePicker::OnTypeChange(IFileDialog *pfd) { + // Failures here result in errors due to security concerns. + nsRefPtr win; + pfd->QueryInterface(IID_IOleWindow, getter_AddRefs(win)); + if (!win) { + NS_ERROR("Could not retrieve the IOleWindow interface for IFileDialog."); + return S_OK; + } + HWND hwnd = NULL; + win->GetWindow(&hwnd); + if (!hwnd) { + NS_ERROR("Could not retrieve the HWND for IFileDialog."); + return S_OK; + } + + SetDialogHandle(hwnd); return S_OK; } @@ -387,6 +495,54 @@ nsFilePicker::OnOverwrite(IFileDialog *pfd, #endif // MOZ_NTDDI_LONGHORN +/* + * Close on parent close logic + */ + +bool +nsFilePicker::ClosePickerIfNeeded(bool aIsXPDialog) +{ + if (!mParentWidget || !mDlgWnd) + return false; + + nsWindow *win = static_cast(mParentWidget.get()); + // Note, the xp callbacks hand us an inner window, so we have to step up + // one to get the actual dialog. + HWND dlgWnd; + if (aIsXPDialog) + dlgWnd = GetParent(mDlgWnd); + else + dlgWnd = mDlgWnd; + if (IsWindow(dlgWnd) && IsWindowVisible(dlgWnd) && win->DestroyCalled()) { + PRUnichar className[64]; + // Make sure we have the right window + if (GetClassNameW(dlgWnd, className, mozilla::ArrayLength(className)) && + !wcscmp(className, L"#32770") && + DestroyWindow(dlgWnd)) { + mDlgWnd = NULL; + return true; + } + } + return false; +} + +void +nsFilePicker::PickerCallbackTimerFunc(nsITimer *aTimer, void *aCtx) +{ + nsFilePicker* picker = (nsFilePicker*)aCtx; + if (picker->ClosePickerIfNeeded(false)) { + aTimer->Cancel(); + } +} + +void +nsFilePicker::SetDialogHandle(HWND aWnd) +{ + if (!aWnd || mDlgWnd) + return; + mDlgWnd = aWnd; +} + /* * Folder picker invocation */ @@ -412,6 +568,7 @@ nsFilePicker::ShowXPFolderPicker(const nsString& aInitialDir) browserInfo.ulFlags = BIF_USENEWUI | BIF_RETURNONLYFSDIRS; browserInfo.hwndOwner = adtw.get(); browserInfo.iImage = nsnull; + browserInfo.lParam = reinterpret_cast(this); if (!aInitialDir.IsEmpty()) { // the dialog is modal so that |initialDir.get()| will be valid in @@ -536,6 +693,7 @@ nsFilePicker::ShowXPFilePicker(const nsString& aInitialDir) ofn.lpstrFile = fileBuffer; ofn.nMaxFile = FILE_BUFFER_SIZE; ofn.hwndOwner = adtw.get(); + ofn.lCustData = reinterpret_cast(this); ofn.Flags = OFN_SHAREAWARE | OFN_LONGNAMES | OFN_OVERWRITEPROMPT | OFN_HIDEREADONLY | OFN_PATHMUSTEXIST | OFN_ENABLESIZING | OFN_EXPLORER; @@ -797,15 +955,18 @@ nsFilePicker::ShowFilePicker(const nsString& aInitialDir) // display - AutoDestroyTmpWindow adtw((HWND)(mParentWidget.get() ? - mParentWidget->GetNativeData(NS_NATIVE_TMP_WINDOW) : NULL)); + { + AutoDestroyTmpWindow adtw((HWND)(mParentWidget.get() ? + mParentWidget->GetNativeData(NS_NATIVE_TMP_WINDOW) : NULL)); + AutoTimerCallbackCancel atcc(this, PickerCallbackTimerFunc); + AutoWidgetPickerState awps(mParentWidget); - AutoWidgetPickerState awps(mParentWidget); - if (FAILED(dialog->Show(adtw.get()))) { + if (FAILED(dialog->Show(adtw.get()))) { + dialog->Unadvise(mFDECookie); + return false; + } dialog->Unadvise(mFDECookie); - return false; } - dialog->Unadvise(mFDECookie); // results diff --git a/widget/src/windows/nsFilePicker.h b/widget/src/windows/nsFilePicker.h index 744a51fc19cf..fe4715299b4d 100644 --- a/widget/src/windows/nsFilePicker.h +++ b/widget/src/windows/nsFilePicker.h @@ -57,6 +57,7 @@ #endif #include "nsILocalFile.h" +#include "nsITimer.h" #include "nsISimpleEnumerator.h" #include "nsCOMArray.h" #include "nsAutoPtr.h" @@ -134,6 +135,11 @@ protected: bool IsPrivacyModeEnabled(); bool IsDefaultPathLink(); bool IsDefaultPathHtml(); + void SetDialogHandle(HWND aWnd); + bool ClosePickerIfNeeded(bool aIsXPDialog); + static void PickerCallbackTimerFunc(nsITimer *aTimer, void *aPicker); + static UINT_PTR CALLBACK MultiFilePickerHook(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); + static UINT_PTR CALLBACK FilePickerHook(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); nsCOMPtr mParentWidget; nsString mTitle; @@ -148,7 +154,7 @@ protected: static char mLastUsedDirectory[]; nsString mUnicodeFile; static PRUnichar *mLastUsedUnicodeDirectory; - DWORD mFDECookie; + HWND mDlgWnd; #if MOZ_WINSDK_TARGETVER >= MOZ_NTDDI_LONGHORN class ComDlgFilterSpec @@ -161,11 +167,11 @@ protected: free(mSpecList); } - PRUint32 Length() { + const PRUint32 Length() { return mLength; } - bool IsEmpty() { + const bool IsEmpty() { return (mLength == 0); } @@ -180,7 +186,8 @@ protected: PRUint32 mLength; }; - ComDlgFilterSpec mComFilterList; + ComDlgFilterSpec mComFilterList; + DWORD mFDECookie; #endif }; diff --git a/widget/src/windows/nsWindow.h b/widget/src/windows/nsWindow.h index 4f68faaedcc7..04ad3d58b5c8 100644 --- a/widget/src/windows/nsWindow.h +++ b/widget/src/windows/nsWindow.h @@ -310,6 +310,7 @@ public: void PickerOpen(); void PickerClosed(); + bool const DestroyCalled() { return mDestroyCalled; } protected: // A magic number to identify the FAKETRACKPOINTSCROLLABLE window created