summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorananta@chromium.org <ananta@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-09-17 21:47:16 +0000
committerananta@chromium.org <ananta@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-09-17 21:47:16 +0000
commitd9d8f0c234ea70e6c5121c038c3c6ab33699f903 (patch)
treeb755aee455afacf4bb88950ab74e6876ad85dd39
parent1b8a1b648148a058b2c03d89398a65038992732f (diff)
downloadchromium_src-d9d8f0c234ea70e6c5121c038c3c6ab33699f903.zip
chromium_src-d9d8f0c234ea70e6c5121c038c3c6ab33699f903.tar.gz
chromium_src-d9d8f0c234ea70e6c5121c038c3c6ab33699f903.tar.bz2
Added full support for invoking before unload and unload handlers on ChromeFrame rendered
pages. This allows a webpage to put up a confirmation dialog in its beforeunload handler and potentially cancel the operation. We only support invoking unload handlers on the page for IE full tab mode. To achieve this the active document handles the OLECMDID_ONUNLOAD exec command which is passed by the DOCHOST to the object which allows us to potentially cancel the operation. Thanks to Stoyan for his help in authoring parts of this CL. The AutomationMsg_RunUnloadHandlers message which is used only by ChromeFrame is now a sync message which returns back a bool indicating whether the unload operation can be continued or not. The ExternalTabContainer now implements the BeforeUnloadFired method in the TabContentsDelegate and aborts the unload operation if the user chose to not proceed with the unload. Fixes bug http://code.google.com/p/chromium/issues/detail?id=33200 Bug=33200 Test=Covered by existing unload event test. Will add a test which validates whether a page can cancel the unload operation in a subsequent CL. Review URL: http://codereview.chromium.org/3450014 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@59854 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/browser/automation/automation_provider.cc3
-rw-r--r--chrome/browser/automation/automation_provider.h3
-rw-r--r--chrome/browser/automation/automation_provider_win.cc5
-rw-r--r--chrome/browser/external_tab_container_win.cc68
-rw-r--r--chrome/browser/external_tab_container_win.h31
-rw-r--r--chrome/test/automation/automation_messages_internal.h8
-rw-r--r--chrome_frame/chrome_active_document.cc13
-rw-r--r--chrome_frame/chrome_active_document.h7
-rw-r--r--chrome_frame/chrome_frame_activex_base.h37
-rw-r--r--chrome_frame/chrome_frame_automation.cc32
-rw-r--r--chrome_frame/chrome_frame_automation.h8
-rw-r--r--chrome_frame/custom_sync_call_context.h26
-rw-r--r--chrome_frame/utils.cc34
-rw-r--r--chrome_frame/utils.h4
14 files changed, 175 insertions, 104 deletions
diff --git a/chrome/browser/automation/automation_provider.cc b/chrome/browser/automation/automation_provider.cc
index 664c639..a7a59ad 100644
--- a/chrome/browser/automation/automation_provider.cc
+++ b/chrome/browser/automation/automation_provider.cc
@@ -381,7 +381,8 @@ void AutomationProvider::OnMessageReceived(const IPC::Message& message) {
IPC_MESSAGE_HANDLER(AutomationMsg_HandleMessageFromExternalHost,
OnMessageFromExternalHost)
IPC_MESSAGE_HANDLER(AutomationMsg_BrowserMove, OnBrowserMoved)
- IPC_MESSAGE_HANDLER(AutomationMsg_RunUnloadHandlers, OnRunUnloadHandlers)
+ IPC_MESSAGE_HANDLER_DELAY_REPLY(AutomationMsg_RunUnloadHandlers,
+ OnRunUnloadHandlers)
IPC_MESSAGE_HANDLER(AutomationMsg_SetZoomLevel, OnSetZoomLevel)
#endif // defined(OS_WIN)
#if defined(OS_CHROMEOS)
diff --git a/chrome/browser/automation/automation_provider.h b/chrome/browser/automation/automation_provider.h
index 2ad6dea..233a8c7 100644
--- a/chrome/browser/automation/automation_provider.h
+++ b/chrome/browser/automation/automation_provider.h
@@ -361,8 +361,7 @@ class AutomationProvider : public base::RefCounted<AutomationProvider>,
void OnBrowserMoved(int handle);
- void OnRunUnloadHandlers(int handle, gfx::NativeWindow notification_window,
- int notification_message);
+ void OnRunUnloadHandlers(int handle, IPC::Message* reply_message);
void OnSetZoomLevel(int handle, int zoom_level);
diff --git a/chrome/browser/automation/automation_provider_win.cc b/chrome/browser/automation/automation_provider_win.cc
index 86dfec1..98ee218 100644
--- a/chrome/browser/automation/automation_provider_win.cc
+++ b/chrome/browser/automation/automation_provider_win.cc
@@ -527,11 +527,10 @@ void AutomationProvider::NavigateExternalTabAtIndex(
}
void AutomationProvider::OnRunUnloadHandlers(
- int handle, gfx::NativeWindow notification_window,
- int notification_message) {
+ int handle, IPC::Message* reply_message) {
ExternalTabContainer* external_tab = GetExternalTabForHandle(handle);
if (external_tab) {
- external_tab->RunUnloadHandlers(notification_window, notification_message);
+ external_tab->RunUnloadHandlers(reply_message);
}
}
diff --git a/chrome/browser/external_tab_container_win.cc b/chrome/browser/external_tab_container_win.cc
index 24d7f60..012a966 100644
--- a/chrome/browser/external_tab_container_win.cc
+++ b/chrome/browser/external_tab_container_win.cc
@@ -57,13 +57,11 @@ ExternalTabContainer::ExternalTabContainer(
handle_top_level_requests_(false),
external_method_factory_(this),
enabled_extension_automation_(false),
- waiting_for_unload_event_(false),
pending_(false),
infobars_enabled_(true),
focus_manager_(NULL),
external_tab_view_(NULL),
- notification_window_(NULL),
- notification_message_(NULL) {
+ unload_reply_message_(NULL) {
}
ExternalTabContainer::~ExternalTabContainer() {
@@ -417,15 +415,16 @@ void ExternalTabContainer::LoadingStateChanged(TabContents* source) {
}
void ExternalTabContainer::CloseContents(TabContents* source) {
- static const int kExternalTabCloseContentsDelayMS = 100;
+ if (!automation_)
+ return;
- if (waiting_for_unload_event_) {
- PostMessage(notification_window_, notification_message_, 0, 0);
- waiting_for_unload_event_ = false;
+ if (unload_reply_message_) {
+ AutomationMsg_RunUnloadHandlers::WriteReplyParams(unload_reply_message_,
+ true);
+ automation_->Send(unload_reply_message_);
+ unload_reply_message_ = NULL;
} else {
- if (automation_) {
- automation_->Send(new AutomationMsg_CloseExternalTab(0, tab_handle_));
- }
+ automation_->Send(new AutomationMsg_CloseExternalTab(0, tab_handle_));
}
}
@@ -612,6 +611,27 @@ void ExternalTabContainer::ShowHtmlDialog(HtmlDialogUIDelegate* delegate,
browser_->window()->ShowHTMLDialog(delegate, parent);
}
+void ExternalTabContainer::BeforeUnloadFired(TabContents* tab,
+ bool proceed,
+ bool* proceed_to_fire_unload) {
+ DCHECK(unload_reply_message_);
+ *proceed_to_fire_unload = true;
+
+ if (!automation_) {
+ delete unload_reply_message_;
+ unload_reply_message_ = NULL;
+ return;
+ }
+
+ if (!proceed) {
+ AutomationMsg_RunUnloadHandlers::WriteReplyParams(unload_reply_message_,
+ false);
+ automation_->Send(unload_reply_message_);
+ unload_reply_message_ = NULL;
+ *proceed_to_fire_unload = false;
+ }
+}
+
////////////////////////////////////////////////////////////////////////////////
// ExternalTabContainer, NotificationObserver implementation:
@@ -717,20 +737,24 @@ void ExternalTabContainer::OnFinalMessage(HWND window) {
Release();
}
-void ExternalTabContainer::RunUnloadHandlers(
- gfx::NativeWindow notification_window,
- int notification_message) {
- DCHECK(::IsWindow(notification_window));
- if (tab_contents_) {
- notification_window_ = notification_window;
- notification_message_ = notification_message;
+void ExternalTabContainer::RunUnloadHandlers(IPC::Message* reply_message) {
+ if (!automation_) {
+ delete reply_message;
+ return;
+ }
- if (Browser::RunUnloadEventsHelper(tab_contents_)) {
- waiting_for_unload_event_ = true;
- }
+ // If we have a pending unload message, then just respond back to this
+ // request and continue processing the previous unload message.
+ if (unload_reply_message_) {
+ AutomationMsg_RunUnloadHandlers::WriteReplyParams(reply_message, true);
+ automation_->Send(reply_message);
+ return;
}
- if (!waiting_for_unload_event_) {
- PostMessage(notification_window, notification_message, 0, 0);
+ if (tab_contents_ && Browser::RunUnloadEventsHelper(tab_contents_)) {
+ unload_reply_message_ = reply_message;
+ } else {
+ AutomationMsg_RunUnloadHandlers::WriteReplyParams(reply_message, true);
+ automation_->Send(reply_message);
}
}
diff --git a/chrome/browser/external_tab_container_win.h b/chrome/browser/external_tab_container_win.h
index 591a1ff..597901b 100644
--- a/chrome/browser/external_tab_container_win.h
+++ b/chrome/browser/external_tab_container_win.h
@@ -148,15 +148,6 @@ class ExternalTabContainer : public TabContentsDelegate,
const NavigationEntry::SSLStatus& ssl,
bool show_history);
- // Overriden from TabContentsDelegate::AutomationResourceRoutingDelegate
- virtual void RegisterRenderViewHost(RenderViewHost* render_view_host);
- virtual void UnregisterRenderViewHost(RenderViewHost* render_view_host);
-
- // Overridden from NotificationObserver:
- virtual void Observe(NotificationType type,
- const NotificationSource& source,
- const NotificationDetails& details);
-
// Handles the context menu display operation. This allows external
// hosts to customize the menu.
virtual bool HandleContextMenu(const ContextMenuParams& params);
@@ -172,6 +163,19 @@ class ExternalTabContainer : public TabContentsDelegate,
virtual void ShowHtmlDialog(HtmlDialogUIDelegate* delegate,
gfx::NativeWindow parent_window);
+ virtual void BeforeUnloadFired(TabContents* tab,
+ bool proceed,
+ bool* proceed_to_fire_unload);
+
+ // Overriden from TabContentsDelegate::AutomationResourceRoutingDelegate
+ virtual void RegisterRenderViewHost(RenderViewHost* render_view_host);
+ virtual void UnregisterRenderViewHost(RenderViewHost* render_view_host);
+
+ // Overridden from NotificationObserver:
+ virtual void Observe(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details);
+
// Returns the ExternalTabContainer instance associated with the cookie
// passed in. It also erases the corresponding reference from the map.
// Returns NULL if we fail to find the cookie in the map.
@@ -204,8 +208,7 @@ class ExternalTabContainer : public TabContentsDelegate,
virtual bool infobars_enabled();
- void RunUnloadHandlers(gfx::NativeWindow notification_window,
- int notification_message);
+ void RunUnloadHandlers(IPC::Message* reply_message);
protected:
// Overridden from views::WidgetWin:
@@ -308,9 +311,6 @@ class ExternalTabContainer : public TabContentsDelegate,
// A mapping between accelerators and commands.
std::map<views::Accelerator, int> accelerator_table_;
- // Set to true if the tab is waiting for the unload event to complete.
- bool waiting_for_unload_event_;
-
// Contains the list of URL requests which are pending waiting for an ack
// from the external host.
std::vector<PendingTopLevelNavigation> pending_open_url_requests_;
@@ -326,8 +326,7 @@ class ExternalTabContainer : public TabContentsDelegate,
views::View* external_tab_view_;
- gfx::NativeWindow notification_window_;
- int notification_message_;
+ IPC::Message* unload_reply_message_;
DISALLOW_COPY_AND_ASSIGN(ExternalTabContainer);
};
diff --git a/chrome/test/automation/automation_messages_internal.h b/chrome/test/automation/automation_messages_internal.h
index 2f413d7..2664ee6 100644
--- a/chrome/test/automation/automation_messages_internal.h
+++ b/chrome/test/automation/automation_messages_internal.h
@@ -1413,12 +1413,8 @@ IPC_BEGIN_MESSAGES(Automation)
// runs unload handlers if any on the current page.
// Request:
// -int: Tab handle
- // -gfx::NativeWindow: notification window
- // -int: notification message.
- // Response:
- // None expected
- IPC_MESSAGE_ROUTED3(AutomationMsg_RunUnloadHandlers, int, gfx::NativeWindow,
- int)
+ // -bool: result: true->unload, false->don't unload
+ IPC_SYNC_MESSAGE_ROUTED1_1(AutomationMsg_RunUnloadHandlers, int, bool)
// This message sets the current zoom level on the tab
// Request:
diff --git a/chrome_frame/chrome_active_document.cc b/chrome_frame/chrome_active_document.cc
index 6d350f8..d5c4250 100644
--- a/chrome_frame/chrome_active_document.cc
+++ b/chrome_frame/chrome_active_document.cc
@@ -876,6 +876,19 @@ void ChromeActiveDocument::OnSetZoomRange(const GUID* cmd_group_guid,
}
}
+void ChromeActiveDocument::OnUnload(const GUID* cmd_group_guid,
+ DWORD command_id,
+ DWORD cmd_exec_opt,
+ VARIANT* in_args,
+ VARIANT* out_args) {
+ if (IsValid() && out_args) {
+ bool should_unload = true;
+ automation_client_->OnUnload(&should_unload);
+ out_args->vt = VT_BOOL;
+ out_args->boolVal = should_unload ? VARIANT_TRUE : VARIANT_FALSE;
+ }
+}
+
void ChromeActiveDocument::OnOpenURL(int tab_handle,
const GURL& url_to_open,
const GURL& referrer,
diff --git a/chrome_frame/chrome_active_document.h b/chrome_frame/chrome_active_document.h
index 146f091..370ba32 100644
--- a/chrome_frame/chrome_active_document.h
+++ b/chrome_frame/chrome_active_document.h
@@ -276,6 +276,7 @@ BEGIN_EXEC_COMMAND_MAP(ChromeActiveDocument)
OnDisplayPrivacyInfo)
EXEC_COMMAND_HANDLER(NULL, OLECMDID_OPTICAL_GETZOOMRANGE, OnGetZoomRange)
EXEC_COMMAND_HANDLER(NULL, OLECMDID_OPTICAL_ZOOM, OnSetZoomRange)
+ EXEC_COMMAND_HANDLER(NULL, OLECMDID_ONUNLOAD, OnUnload)
END_EXEC_COMMAND_MAP()
// IPCs from automation server.
@@ -390,6 +391,12 @@ END_EXEC_COMMAND_MAP()
void OnSetZoomRange(const GUID* cmd_group_guid, DWORD command_id,
DWORD cmd_exec_opt, VARIANT* in_args, VARIANT* out_args);
+ // This function handles the OLECMDID_ONUNLOAD command. It enables Chrome to
+ // invoke before unload and unload handlers on the page if any, thereby
+ // enabling a webpage to potentially cancel the operation.
+ void OnUnload(const GUID* cmd_group_guid, DWORD command_id,
+ DWORD cmd_exec_opt, VARIANT* in_args, VARIANT* out_args);
+
// Call exec on our site's command target
HRESULT IEExec(const GUID* cmd_group_guid, DWORD command_id,
DWORD cmd_exec_opt, VARIANT* in_args, VARIANT* out_args);
diff --git a/chrome_frame/chrome_frame_activex_base.h b/chrome_frame/chrome_frame_activex_base.h
index dae53df..5f281f3 100644
--- a/chrome_frame/chrome_frame_activex_base.h
+++ b/chrome_frame/chrome_frame_activex_base.h
@@ -389,43 +389,6 @@ END_MSG_MAP()
return true;
}
- // IOleInPlaceObject overrides.
- STDMETHOD(InPlaceDeactivate)(void) {
- static UINT onload_handlers_done_msg =
- RegisterWindowMessage(L"ChromeFrame_OnloadHandlersDone");
-
- if (m_bInPlaceActive && IsWindow() && IsValid()) {
- static const int kChromeFrameUnloadEventTimerId = 0xdeadbeef;
- static const int kChromeFrameUnloadEventTimeout = 1000;
-
- // To prevent us from indefinitely waiting for an acknowledgement from
- // Chrome indicating that unload handlers have been run, we set a 1
- // second timer and exit the loop when it fires.
- ::SetTimer(m_hWnd, kChromeFrameUnloadEventTimerId,
- kChromeFrameUnloadEventTimeout, NULL);
-
- automation_client_->RunUnloadHandlers(m_hWnd, onload_handlers_done_msg);
-
- MSG msg = {0};
- while (GetMessage(&msg, NULL, 0, 0)) {
- if (msg.message == onload_handlers_done_msg &&
- msg.hwnd == m_hWnd) {
- break;
- }
-
- if (msg.message == WM_TIMER &&
- msg.wParam == kChromeFrameUnloadEventTimerId) {
- break;
- }
- TranslateMessage(&msg);
- DispatchMessage(&msg);
- }
-
- ::KillTimer(m_hWnd, kChromeFrameUnloadEventTimerId);
- }
- return IOleInPlaceObjectWindowlessImpl<T>::InPlaceDeactivate();
- }
-
protected:
virtual void GetProfilePath(const std::wstring& profile_name,
FilePath* profile_path) {
diff --git a/chrome_frame/chrome_frame_automation.cc b/chrome_frame/chrome_frame_automation.cc
index 908df61d..67d9ea8 100644
--- a/chrome_frame/chrome_frame_automation.cc
+++ b/chrome_frame/chrome_frame_automation.cc
@@ -122,6 +122,9 @@ class ChromeFrameAutomationProxyImpl::CFMsgDispatcher
case AutomationMsg_GetEnabledExtensions::ID:
InvokeCallback<GetEnabledExtensionsContext>(msg, context);
break;
+ case AutomationMsg_RunUnloadHandlers::ID:
+ InvokeCallback<UnloadContext>(msg, context);
+ break;
default:
NOTREACHED();
}
@@ -1367,19 +1370,6 @@ void ChromeFrameAutomationClient::RemoveBrowsingData(int remove_mask) {
new AutomationMsg_RemoveBrowsingData(0, remove_mask));
}
-void ChromeFrameAutomationClient::RunUnloadHandlers(HWND notification_window,
- int notification_message) {
- if (automation_server_) {
- automation_server_->Send(
- new AutomationMsg_RunUnloadHandlers(0, tab_handle_,
- notification_window,
- notification_message));
- } else {
- // Post this message to ensure that the caller exits his message loop.
- ::PostMessage(notification_window, notification_message, 0, 0);
- }
-}
-
void ChromeFrameAutomationClient::SetUrlFetcher(
PluginUrlRequestManager* url_fetcher) {
DCHECK(url_fetcher != NULL);
@@ -1395,6 +1385,22 @@ void ChromeFrameAutomationClient::SetZoomLevel(PageZoom::Function zoom_level) {
}
}
+void ChromeFrameAutomationClient::OnUnload(bool* should_unload) {
+ *should_unload = true;
+ if (automation_server_) {
+ const DWORD kUnloadEventTimeout = 20000;
+
+ IPC::SyncMessage* msg = new AutomationMsg_RunUnloadHandlers(0, tab_handle_,
+ should_unload);
+ base::WaitableEvent unload_call_finished(false, false);
+ UnloadContext* unload_context = new UnloadContext(&unload_call_finished,
+ should_unload);
+ automation_server_->SendAsAsync(msg, unload_context, this);
+ HANDLE done = unload_call_finished.handle();
+ WaitWithMessageLoop(&done, 1, kUnloadEventTimeout);
+ }
+}
+
//////////////////////////////////////////////////////////////////////////
// PluginUrlRequestDelegate implementation.
// Forward network related responses to Chrome.
diff --git a/chrome_frame/chrome_frame_automation.h b/chrome_frame/chrome_frame_automation.h
index 7bb572a..0890fb7 100644
--- a/chrome_frame/chrome_frame_automation.h
+++ b/chrome_frame/chrome_frame_automation.h
@@ -415,13 +415,13 @@ class ChromeFrameAutomationClient
// For IDeleteBrowsingHistorySupport
void RemoveBrowsingData(int remove_mask);
- // Sends an IPC message to the external tab container requesting it to run
- // unload handlers on the page.
- void RunUnloadHandlers(HWND notification_window, int notification_message);
-
// Sets the current zoom level on the tab.
void SetZoomLevel(PageZoom::Function zoom_level);
+ // Fires before unload and unload handlers on the page if any. Allows the
+ // the website to put up a confirmation dialog on unload.
+ void OnUnload(bool* should_unload);
+
protected:
// ChromeFrameAutomationProxy::LaunchDelegate implementation.
virtual void LaunchComplete(ChromeFrameAutomationProxy* proxy,
diff --git a/chrome_frame/custom_sync_call_context.h b/chrome_frame/custom_sync_call_context.h
index 28812ef..9cbe3d6 100644
--- a/chrome_frame/custom_sync_call_context.h
+++ b/chrome_frame/custom_sync_call_context.h
@@ -6,6 +6,7 @@
#include <vector>
#include "base/ref_counted.h"
+#include "base/waitable_event.h"
#include "chrome_frame/sync_msg_reply_dispatcher.h"
#include "chrome_frame/chrome_frame_automation.h"
#include "ipc/ipc_sync_message.h"
@@ -122,6 +123,31 @@ class BeginNavigateContext
scoped_refptr<ChromeFrameAutomationClient> client_;
};
+// Class that maintains contextual information for the unload operation, i.e.
+// when the user attempts to navigate away from a page rendered in ChromeFrame.
+class UnloadContext
+ : public SyncMessageReplyDispatcher::SyncMessageCallContext {
+ public:
+ typedef Tuple1<bool> output_type;
+ explicit UnloadContext(base::WaitableEvent* unload_done, bool* should_unload)
+ : should_unload_(should_unload),
+ unload_done_(unload_done) {
+ }
+
+ void Completed(bool should_unload) {
+ *should_unload_ = should_unload;
+ unload_done_->Signal();
+ should_unload_ = NULL;
+ unload_done_ = NULL;
+ // This object will be destroyed after this. Cannot access any members
+ // on returning from this function.
+ }
+
+ private:
+ base::WaitableEvent* unload_done_;
+ bool* should_unload_;
+};
+
#endif // CHROME_FRAME_CUSTOM_SYNC_CALL_CONTEXT_H_
diff --git a/chrome_frame/utils.cc b/chrome_frame/utils.cc
index 35fbc83..80095428 100644
--- a/chrome_frame/utils.cc
+++ b/chrome_frame/utils.cc
@@ -1453,3 +1453,37 @@ void PinModule() {
}
}
}
+
+void WaitWithMessageLoop(HANDLE* handles, int count, DWORD timeout) {
+ base::Time now = base::Time::Now();
+ base::Time wait_until = now + base::TimeDelta::FromMilliseconds(timeout);
+
+ while (wait_until >= now) {
+ base::TimeDelta wait_time = wait_until - now;
+ DWORD wait = MsgWaitForMultipleObjects(
+ count, handles, FALSE, static_cast<DWORD>(wait_time.InMilliseconds()),
+ QS_ALLINPUT);
+ switch (wait) {
+ case WAIT_OBJECT_0:
+ case WAIT_TIMEOUT:
+ return;
+
+ case WAIT_OBJECT_0 + 1: {
+ MSG msg = {0};
+ while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ }
+ break;
+ }
+
+ default: {
+ NOTREACHED() << "Unexpected return from MsgWaitForMultipleObjects :"
+ << wait;
+ return;
+ }
+ }
+ now = base::Time::Now();
+ }
+}
+
diff --git a/chrome_frame/utils.h b/chrome_frame/utils.h
index e0da6ac..eadad72 100644
--- a/chrome_frame/utils.h
+++ b/chrome_frame/utils.h
@@ -550,4 +550,8 @@ bool CanNavigate(const GURL& url, IInternetSecurityManager* security_manager,
// Call if you make irreversible patches.
void PinModule();
+// Helper function to spin a message loop and dispatch messages while waiting
+// for a handle to be signaled.
+void WaitWithMessageLoop(HANDLE* handles, int count, DWORD timeout);
+
#endif // CHROME_FRAME_UTILS_H_