diff options
author | jianli@chromium.org <jianli@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-01-15 18:49:58 +0000 |
---|---|---|
committer | jianli@chromium.org <jianli@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-01-15 18:49:58 +0000 |
commit | 6aa4a1c041ca9bd2c3087c3c059a87193b1a82e1 (patch) | |
tree | 8e833c393312e866250077c15bc1d90464fe99d7 /chrome/browser/views/tab_contents | |
parent | 963dfb5a05c5b0e3fa8ed74d803f01cb10fd455e (diff) | |
download | chromium_src-6aa4a1c041ca9bd2c3087c3c059a87193b1a82e1.zip chromium_src-6aa4a1c041ca9bd2c3087c3c059a87193b1a82e1.tar.gz chromium_src-6aa4a1c041ca9bd2c3087c3c059a87193b1a82e1.tar.bz2 |
Support dragging a virtual file out of the browser.
BUG=none
TEST=none
Review URL: http://codereview.chromium.org/351029
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@36378 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/views/tab_contents')
4 files changed, 439 insertions, 79 deletions
diff --git a/chrome/browser/views/tab_contents/tab_contents_drag_win.cc b/chrome/browser/views/tab_contents/tab_contents_drag_win.cc new file mode 100644 index 0000000..a507c6c --- /dev/null +++ b/chrome/browser/views/tab_contents/tab_contents_drag_win.cc @@ -0,0 +1,328 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/views/tab_contents/tab_contents_drag_win.h" + +#include <windows.h> + +#include "base/file_path.h" +#include "base/message_loop.h" +#include "base/task.h" +#include "base/thread.h" +#include "base/win_util.h" +#include "chrome/browser/bookmarks/bookmark_drag_data.h" +#include "chrome/browser/chrome_thread.h" +#include "chrome/browser/download/drag_download_file_win.h" +#include "chrome/browser/profile.h" +#include "chrome/browser/tab_contents/tab_contents.h" +#include "chrome/browser/tab_contents/web_drag_source_win.h" +#include "chrome/browser/tab_contents/web_drop_target_win.h" +#include "chrome/browser/views/tab_contents/tab_contents_view_win.h" +#include "chrome/common/url_constants.h" +#include "net/base/net_util.h" +#include "webkit/glue/webdropdata.h" + +using WebKit::WebDragOperationsMask; + +namespace { + +HHOOK msg_hook = NULL; +DWORD drag_out_thread_id = 0; +bool mouse_up_received = false; + +LRESULT CALLBACK MsgFilterProc(int code, WPARAM wparam, LPARAM lparam) { + if (code == base::MessagePumpForUI::kMessageFilterCode && + !mouse_up_received) { + MSG* msg = reinterpret_cast<MSG*>(lparam); + // We do not care about WM_SYSKEYDOWN and WM_SYSKEYUP because when ALT key + // is pressed down on drag-and-drop, it means to create a link. + if (msg->message == WM_MOUSEMOVE || msg->message == WM_LBUTTONUP || + msg->message == WM_KEYDOWN || msg->message == WM_KEYUP) { + // Forward the message from the UI thread to the drag-and-drop thread. + PostThreadMessage(drag_out_thread_id, + msg->message, + msg->wParam, + msg->lParam); + + // If the left button is up, we do not need to forward the message any + // more. + if (msg->message == WM_LBUTTONUP || !(GetKeyState(VK_LBUTTON) & 0x8000)) + mouse_up_received = true; + + return TRUE; + } + } + return CallNextHookEx(msg_hook, code, wparam, lparam); +} + +} // namespace + +class DragDropThread : public base::Thread { + public: + explicit DragDropThread(TabContentsDragWin* drag_handler) + : base::Thread("Chrome_DragDropThread"), + drag_handler_(drag_handler) { + } + + virtual ~DragDropThread() { + Thread::Stop(); + } + + protected: + // base::Thread implementations: + virtual void Init() { + int ole_result = OleInitialize(NULL); + DCHECK(ole_result == S_OK); + } + + virtual void CleanUp() { + OleUninitialize(); + } + + private: + // Hold a reference count to TabContentsDragWin to make sure that it is always + // alive in the thread lifetime. + scoped_refptr<TabContentsDragWin> drag_handler_; + + DISALLOW_COPY_AND_ASSIGN(DragDropThread); +}; + +TabContentsDragWin::TabContentsDragWin(TabContentsViewWin* view) + : view_(view), + drag_ended_(false), + old_drop_target_suspended_state_(false) { +#ifndef NDEBUG + drag_drop_thread_id_ = 0; +#endif +} + +TabContentsDragWin::~TabContentsDragWin() { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); + DCHECK(!drag_source_.get()); + DCHECK(!drag_drop_thread_.get()); +} + +void TabContentsDragWin::StartDragging(const WebDropData& drop_data, + WebDragOperationsMask ops) { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); + + drag_source_ = new WebDragSource(view_->GetNativeView(), + view_->tab_contents()); + + const GURL& page_url = view_->tab_contents()->GetURL(); + const std::string& page_encoding = view_->tab_contents()->encoding(); + + // If it is not drag-out, do the drag-and-drop in the current UI thread. + if (!drop_data.download_url.is_valid()) { + DoDragging(drop_data, ops, page_url, page_encoding); + EndDragging(false); + return; + } + + // We do not want to drag and drop the download to itself. + old_drop_target_suspended_state_ = view_->drop_target()->suspended(); + view_->drop_target()->set_suspended(true); + + // Start a background thread to do the drag-and-drop. + DCHECK(!drag_drop_thread_.get()); + drag_drop_thread_.reset(new DragDropThread(this)); + base::Thread::Options options; + options.message_loop_type = MessageLoop::TYPE_UI; + if (drag_drop_thread_->StartWithOptions(options)) { + drag_drop_thread_->message_loop()->PostTask( + FROM_HERE, + NewRunnableMethod(this, + &TabContentsDragWin::StartBackgroundDragging, + drop_data, + ops, + page_url, + page_encoding)); + } + + // Install a hook procedure to monitor the messages so that we can forward + // the appropriate ones to the background thread. + drag_out_thread_id = drag_drop_thread_->thread_id(); + mouse_up_received = false; + DCHECK(!msg_hook); + msg_hook = SetWindowsHookEx(WH_MSGFILTER, + MsgFilterProc, + NULL, + GetCurrentThreadId()); + + // Attach the input state of the background thread to the UI thread so that + // SetCursor can work from the background thread. + AttachThreadInput(drag_out_thread_id, GetCurrentThreadId(), TRUE); +} + +void TabContentsDragWin::StartBackgroundDragging( + const WebDropData& drop_data, + WebDragOperationsMask ops, + const GURL& page_url, + const std::string& page_encoding) { +#ifndef NDEBUG + drag_drop_thread_id_ = PlatformThread::CurrentId(); +#endif + + DoDragging(drop_data, ops, page_url, page_encoding); + ChromeThread::PostTask( + ChromeThread::UI, FROM_HERE, + NewRunnableMethod(this, &TabContentsDragWin::EndDragging, true)); +} + +void TabContentsDragWin::PrepareDragForDownload( + const WebDropData& drop_data, + OSExchangeData* data, + const GURL& page_url, + const std::string& page_encoding) { + // Provide the data as file (CF_HDROP). A temporary download file with the + // Zone.Identifier ADS (Alternate Data Stream) attached will be created. + scoped_refptr<DragDownloadFile> download_file = + new DragDownloadFile(drop_data.download_url, + page_url, + page_encoding, + view_->tab_contents()); + OSExchangeData::DownloadFileInfo* file_download = + new OSExchangeData::DownloadFileInfo(FilePath(), + 0, + download_file.get()); + data->SetDownloadFileInfo(file_download); + + // Enable asynchronous operation. + OSExchangeDataProviderWin::GetIAsyncOperation(*data)->SetAsyncMode(TRUE); +} + +void TabContentsDragWin::PrepareDragForFileContents( + const WebDropData& drop_data, OSExchangeData* data) { + // Images without ALT text will only have a file extension so we need to + // synthesize one from the provided extension and URL. + FilePath file_name(drop_data.file_description_filename); + file_name = file_name.BaseName().RemoveExtension(); + if (file_name.value().empty()) { + // Retrieve the name from the URL. + file_name = net::GetSuggestedFilename(drop_data.url, "", "", FilePath()); + if (file_name.value().size() + drop_data.file_extension.size() + 1 > + MAX_PATH) { + file_name = FilePath(file_name.value().substr( + 0, MAX_PATH - drop_data.file_extension.size() - 2)); + } + } + file_name = file_name.ReplaceExtension(drop_data.file_extension); + data->SetFileContents(file_name.value(), drop_data.file_contents); +} + +void TabContentsDragWin::PrepareDragForUrl(const WebDropData& drop_data, + OSExchangeData* data) { + if (drop_data.url.SchemeIs(chrome::kJavaScriptScheme)) { + // We don't want to allow javascript URLs to be dragged to the desktop, + // but we do want to allow them to be added to the bookmarks bar + // (bookmarklets). So we create a fake bookmark entry (BookmarkDragData + // object) which explorer.exe cannot handle, and write the entry to data. + BookmarkDragData::Element bm_elt; + bm_elt.is_url = true; + bm_elt.url = drop_data.url; + bm_elt.title = drop_data.url_title; + + BookmarkDragData bm_drag_data; + bm_drag_data.elements.push_back(bm_elt); + + // Pass in NULL as the profile so that the bookmark always adds the url + // rather than trying to move an existing url. + bm_drag_data.Write(NULL, data); + } else { + data->SetURL(drop_data.url, drop_data.url_title); + } +} + +void TabContentsDragWin::DoDragging(const WebDropData& drop_data, + WebDragOperationsMask ops, + const GURL& page_url, + const std::string& page_encoding) { + OSExchangeData data; + + // TODO(tc): Generate an appropriate drag image. + + if (drop_data.download_url.is_valid()) { + PrepareDragForDownload(drop_data, &data, page_url, page_encoding); + + // Set the observer. + OSExchangeDataProviderWin::GetDataObjectImpl(data)->set_observer(this); + } else { + // We set the file contents before the URL because the URL also sets file + // contents (to a .URL shortcut). We want to prefer file content data over + // a shortcut so we add it first. + if (!drop_data.file_contents.empty()) + PrepareDragForFileContents(drop_data, &data); + if (!drop_data.text_html.empty()) + data.SetHtml(drop_data.text_html, drop_data.html_base_url); + if (drop_data.url.is_valid()) + PrepareDragForUrl(drop_data, &data); + if (!drop_data.plain_text.empty()) + data.SetString(drop_data.plain_text); + } + + DWORD effects = 0; + + // We need to enable recursive tasks on the message loop so we can get + // updates while in the system DoDragDrop loop. + bool old_state = MessageLoop::current()->NestableTasksAllowed(); + MessageLoop::current()->SetNestableTasksAllowed(true); + DoDragDrop(OSExchangeDataProviderWin::GetIDataObject(data), drag_source_, + DROPEFFECT_COPY | DROPEFFECT_LINK, &effects); + // TODO(snej): Use 'ops' param instead of hardcoding dropeffects + MessageLoop::current()->SetNestableTasksAllowed(old_state); +} + +void TabContentsDragWin::EndDragging(bool restore_suspended_state) { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); + + if (drag_ended_) + return; + drag_ended_ = true; + + if (restore_suspended_state) + view_->drop_target()->set_suspended(old_drop_target_suspended_state_); + + drag_source_ = NULL; + + if (msg_hook) { + AttachThreadInput(drag_out_thread_id, GetCurrentThreadId(), FALSE); + UnhookWindowsHookEx(msg_hook); + msg_hook = NULL; + } + + view_->EndDragging(); +} + +void TabContentsDragWin::CancelDrag() { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); + + drag_source_->CancelDrag(); +} + +void TabContentsDragWin::CloseThread() { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); + + drag_drop_thread_.reset(); +} + +void TabContentsDragWin::OnWaitForData() { + DCHECK(drag_drop_thread_id_ == PlatformThread::CurrentId()); + + // When the left button is release and we start to wait for the data, end + // the dragging before DoDragDrop returns. This makes the page leave the drag + // mode so that it can start to process the normal input events. + ChromeThread::PostTask( + ChromeThread::UI, FROM_HERE, + NewRunnableMethod(this, &TabContentsDragWin::EndDragging, true)); +} + +void TabContentsDragWin::OnDataObjectDisposed() { + DCHECK(drag_drop_thread_id_ == PlatformThread::CurrentId()); + + // The drag-and-drop thread is only closed after OLE is done with + // DataObjectImpl. + ChromeThread::PostTask( + ChromeThread::UI, FROM_HERE, + NewRunnableMethod(this, &TabContentsDragWin::CloseThread)); +} diff --git a/chrome/browser/views/tab_contents/tab_contents_drag_win.h b/chrome/browser/views/tab_contents/tab_contents_drag_win.h new file mode 100644 index 0000000..5fbddd9 --- /dev/null +++ b/chrome/browser/views/tab_contents/tab_contents_drag_win.h @@ -0,0 +1,93 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_VIEWS_TAB_CONTENTS_TAB_CONTENTS_DRAG_WIN_H_ +#define CHROME_BROWSER_VIEWS_TAB_CONTENTS_TAB_CONTENTS_DRAG_WIN_H_ + +#include "app/os_exchange_data_provider_win.h" +#include "base/platform_thread.h" +#include "base/ref_counted.h" +#include "base/scoped_ptr.h" +#include "third_party/WebKit/WebKit/chromium/public/WebDragOperation.h" + +class DragDropThread; +class TabContentsViewWin; +class WebDragSource; +struct WebDropData; + +// Windows-specific drag-and-drop handling in TabContentsView. +// If we are dragging a virtual file out of the browser, we use a background +// thread to do the drag-and-drop because we do not want to run nested +// message loop in the UI thread. For all other cases, the drag-and-drop happens +// in the UI thread. +class TabContentsDragWin + : public DataObjectImpl::Observer, + public base::RefCountedThreadSafe<TabContentsDragWin> { + public: + explicit TabContentsDragWin(TabContentsViewWin* view); + virtual ~TabContentsDragWin(); + + // Called on UI thread. + void StartDragging(const WebDropData& drop_data, + WebKit::WebDragOperationsMask ops); + void CancelDrag(); + + // DataObjectImpl::Observer implementation. + // Called on drag-and-drop thread. + virtual void OnWaitForData(); + virtual void OnDataObjectDisposed(); + + private: + // Called on either UI thread or drag-and-drop thread. + void PrepareDragForDownload(const WebDropData& drop_data, + OSExchangeData* data, + const GURL& page_url, + const std::string& page_encoding); + void PrepareDragForFileContents(const WebDropData& drop_data, + OSExchangeData* data); + void PrepareDragForUrl(const WebDropData& drop_data, OSExchangeData* data); + void DoDragging(const WebDropData& drop_data, + WebKit::WebDragOperationsMask ops, + const GURL& page_url, + const std::string& page_encoding); + + // Called on drag-and-drop thread. + void StartBackgroundDragging(const WebDropData& drop_data, + WebKit::WebDragOperationsMask ops, + const GURL& page_url, + const std::string& page_encoding); + // Called on UI thread. + void EndDragging(bool restore_suspended_state); + void CloseThread(); + + // For debug check only. Access only on drag-and-drop thread. +#ifndef NDEBUG + PlatformThreadId drag_drop_thread_id_; +#endif + + // All the member variables below are accessed on UI thread. + + // Keep track of the TabContentsViewWin it is associated with. + TabContentsViewWin* view_; + + // |drag_source_| is our callback interface passed to the system when we + // want to initiate a drag and drop operation. We use it to tell if a + // drag operation is happening. + scoped_refptr<WebDragSource> drag_source_; + + // The thread used by the drag-out download. This is because we want to avoid + // running nested message loop in main UI thread. + scoped_ptr<DragDropThread> drag_drop_thread_; + + // The flag to guard that EndDragging is not called twice. + bool drag_ended_; + + // Keep track of the old suspended state of the drop target. + bool old_drop_target_suspended_state_; + + DISALLOW_COPY_AND_ASSIGN(TabContentsDragWin); +}; + + +#endif // CHROME_BROWSER_VIEWS_TAB_CONTENTS_TAB_CONTENTS_DRAG_WIN_H_ diff --git a/chrome/browser/views/tab_contents/tab_contents_view_win.cc b/chrome/browser/views/tab_contents/tab_contents_view_win.cc index a6f8b3e..f6bb820 100644 --- a/chrome/browser/views/tab_contents/tab_contents_view_win.cc +++ b/chrome/browser/views/tab_contents/tab_contents_view_win.cc @@ -8,12 +8,10 @@ #include "app/gfx/canvas_paint.h" #include "app/os_exchange_data.h" -#include "app/os_exchange_data_provider_win.h" #include "base/file_path.h" #include "base/keyboard_codes.h" #include "base/time.h" #include "base/win_util.h" -#include "chrome/browser/bookmarks/bookmark_drag_data.h" #include "chrome/browser/browser.h" // TODO(beng): this dependency is awful. #include "chrome/browser/browser_process.h" #include "chrome/browser/download/download_request_manager.h" @@ -24,16 +22,13 @@ #include "chrome/browser/tab_contents/interstitial_page.h" #include "chrome/browser/tab_contents/tab_contents.h" #include "chrome/browser/tab_contents/tab_contents_delegate.h" -#include "chrome/browser/tab_contents/web_drag_source_win.h" #include "chrome/browser/tab_contents/web_drop_target_win.h" #include "chrome/browser/views/sad_tab_view.h" #include "chrome/browser/views/tab_contents/render_view_context_menu_win.h" -#include "chrome/common/url_constants.h" -#include "net/base/net_util.h" +#include "chrome/browser/views/tab_contents/tab_contents_drag_win.h" #include "views/focus/view_storage.h" #include "views/screen.h" #include "views/widget/root_view.h" -#include "webkit/glue/webdropdata.h" using WebKit::WebDragOperation; using WebKit::WebDragOperationNone; @@ -62,8 +57,6 @@ TabContentsViewWin::~TabContentsViewWin() { views::ViewStorage* view_storage = views::ViewStorage::GetSharedInstance(); if (view_storage->RetrieveView(last_focused_view_storage_id_) != NULL) view_storage->RemoveView(last_focused_view_storage_id_); - - DCHECK(!drag_source_.get()); } void TabContentsViewWin::Unparent() { @@ -132,70 +125,11 @@ void TabContentsViewWin::GetContainerBounds(gfx::Rect* out) const { void TabContentsViewWin::StartDragging(const WebDropData& drop_data, WebDragOperationsMask ops) { - OSExchangeData data; - - // TODO(tc): Generate an appropriate drag image. - - // We set the file contents before the URL because the URL also sets file - // contents (to a .URL shortcut). We want to prefer file content data over a - // shortcut so we add it first. - if (!drop_data.file_contents.empty()) { - // Images without ALT text will only have a file extension so we need to - // synthesize one from the provided extension and URL. - FilePath file_name(drop_data.file_description_filename); - file_name = file_name.BaseName().RemoveExtension(); - if (file_name.value().empty()) { - // Retrieve the name from the URL. - file_name = net::GetSuggestedFilename(drop_data.url, "", "", FilePath()); - if (file_name.value().size() + drop_data.file_extension.size() + 1 > - MAX_PATH) { - file_name = FilePath(file_name.value().substr( - 0, MAX_PATH - drop_data.file_extension.size() - 2)); - } - } - file_name = file_name.ReplaceExtension(drop_data.file_extension); - data.SetFileContents(file_name.value(), drop_data.file_contents); - } - if (!drop_data.text_html.empty()) - data.SetHtml(drop_data.text_html, drop_data.html_base_url); - if (drop_data.url.is_valid()) { - if (drop_data.url.SchemeIs(chrome::kJavaScriptScheme)) { - // We don't want to allow javascript URLs to be dragged to the desktop, - // but we do want to allow them to be added to the bookmarks bar - // (bookmarklets). So we create a fake bookmark entry (BookmarkDragData - // object) which explorer.exe cannot handle, and write the entry to data. - BookmarkDragData::Element bm_elt; - bm_elt.is_url = true; - bm_elt.url = drop_data.url; - bm_elt.title = drop_data.url_title; - - BookmarkDragData bm_drag_data; - bm_drag_data.elements.push_back(bm_elt); - - // Pass in NULL as the profile so that the bookmark always adds the url - // rather than trying to move an existing url. - bm_drag_data.Write(NULL, &data); - } else { - data.SetURL(drop_data.url, drop_data.url_title); - } - } - if (!drop_data.plain_text.empty()) - data.SetString(drop_data.plain_text); - - drag_source_ = new WebDragSource(GetNativeView(), tab_contents()); - - DWORD effects; - - // We need to enable recursive tasks on the message loop so we can get - // updates while in the system DoDragDrop loop. - bool old_state = MessageLoop::current()->NestableTasksAllowed(); - MessageLoop::current()->SetNestableTasksAllowed(true); - DoDragDrop(OSExchangeDataProviderWin::GetIDataObject(data), drag_source_, - DROPEFFECT_COPY | DROPEFFECT_LINK, &effects); - // TODO(snej): Use 'ops' param instead of hardcoding dropeffects - MessageLoop::current()->SetNestableTasksAllowed(old_state); + drag_handler_ = new TabContentsDragWin(this); + drag_handler_->StartDragging(drop_data, ops); +} - drag_source_ = NULL; +void TabContentsViewWin::EndDragging() { if (close_tab_after_drag_ends_) { close_tab_timer_.Start(base::TimeDelta::FromMilliseconds(0), this, &TabContentsViewWin::CloseTab); @@ -203,6 +137,8 @@ void TabContentsViewWin::StartDragging(const WebDropData& drop_data, if (tab_contents()->render_view_host()) tab_contents()->render_view_host()->DragSourceSystemDragEnded(); + + drag_handler_ = NULL; } void TabContentsViewWin::OnDestroy() { @@ -328,15 +264,15 @@ void TabContentsViewWin::RestoreFocus() { } bool TabContentsViewWin::IsDoingDrag() const { - return drag_source_.get() != NULL; + return drag_handler_.get() != NULL; } void TabContentsViewWin::CancelDragAndCloseTab() { DCHECK(IsDoingDrag()); // We can't close the tab while we're in the drag and - // |drag_source_->CancelDrag()| is async. Instead, set a flag to cancel + // |drag_handler_->CancelDrag()| is async. Instead, set a flag to cancel // the drag and when the drag nested message loop ends, close the tab. - drag_source_->CancelDrag(); + drag_handler_->CancelDrag(); close_tab_after_drag_ends_ = true; } diff --git a/chrome/browser/views/tab_contents/tab_contents_view_win.h b/chrome/browser/views/tab_contents/tab_contents_view_win.h index 5987baa..dc1e257 100644 --- a/chrome/browser/views/tab_contents/tab_contents_view_win.h +++ b/chrome/browser/views/tab_contents/tab_contents_view_win.h @@ -13,6 +13,7 @@ class RenderViewContextMenuWin; class SadTabView; +class TabContentsDragWin; struct WebDropData; class WebDragSource; class WebDropTarget; @@ -62,6 +63,10 @@ class TabContentsViewWin : public TabContentsView, // WidgetWin overridde. virtual views::FocusManager* GetFocusManager(); + void EndDragging(); + + WebDropTarget* drop_target() const { return drop_target_.get(); } + private: // A helper method for closing the tab. void CloseTab(); @@ -118,11 +123,6 @@ class TabContentsViewWin : public TabContentsView, // accessible when unparented. views::FocusManager* focus_manager_; - // |drag_source_| is our callback interface passed to the system when we - // want to initiate a drag and drop operation. We use it to tell if a - // drag operation is happening. - scoped_refptr<WebDragSource> drag_source_; - // Set to true if we want to close the tab after the system drag operation // has finished. bool close_tab_after_drag_ends_; @@ -130,6 +130,9 @@ class TabContentsViewWin : public TabContentsView, // Used to close the tab after the stack has unwound. base::OneShotTimer<TabContentsViewWin> close_tab_timer_; + // Used to handle the drag-and-drop. + scoped_refptr<TabContentsDragWin> drag_handler_; + DISALLOW_COPY_AND_ASSIGN(TabContentsViewWin); }; |