Home | History | Annotate | Download | only in tab_contents
      1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "chrome/browser/ui/views/tab_contents/tab_contents_drag_win.h"
      6 
      7 #include <windows.h>
      8 
      9 #include <string>
     10 
     11 #include "base/file_path.h"
     12 #include "base/message_loop.h"
     13 #include "base/task.h"
     14 #include "base/threading/platform_thread.h"
     15 #include "base/threading/thread.h"
     16 #include "base/utf_string_conversions.h"
     17 #include "chrome/browser/bookmarks/bookmark_node_data.h"
     18 #include "chrome/browser/download/download_util.h"
     19 #include "chrome/browser/download/drag_download_file.h"
     20 #include "chrome/browser/download/drag_download_util.h"
     21 #include "chrome/browser/tab_contents/web_drag_source_win.h"
     22 #include "chrome/browser/tab_contents/web_drag_utils_win.h"
     23 #include "chrome/browser/tab_contents/web_drop_target_win.h"
     24 #include "chrome/browser/ui/views/tab_contents/native_tab_contents_view_win.h"
     25 #include "chrome/common/url_constants.h"
     26 #include "content/browser/browser_thread.h"
     27 #include "content/browser/tab_contents/tab_contents.h"
     28 #include "net/base/net_util.h"
     29 #include "views/drag_utils.h"
     30 #include "webkit/glue/webdropdata.h"
     31 
     32 using WebKit::WebDragOperationsMask;
     33 using WebKit::WebDragOperationCopy;
     34 using WebKit::WebDragOperationLink;
     35 using WebKit::WebDragOperationMove;
     36 
     37 namespace {
     38 
     39 HHOOK msg_hook = NULL;
     40 DWORD drag_out_thread_id = 0;
     41 bool mouse_up_received = false;
     42 
     43 LRESULT CALLBACK MsgFilterProc(int code, WPARAM wparam, LPARAM lparam) {
     44   if (code == base::MessagePumpForUI::kMessageFilterCode &&
     45       !mouse_up_received) {
     46     MSG* msg = reinterpret_cast<MSG*>(lparam);
     47     // We do not care about WM_SYSKEYDOWN and WM_SYSKEYUP because when ALT key
     48     // is pressed down on drag-and-drop, it means to create a link.
     49     if (msg->message == WM_MOUSEMOVE || msg->message == WM_LBUTTONUP ||
     50         msg->message == WM_KEYDOWN || msg->message == WM_KEYUP) {
     51       // Forward the message from the UI thread to the drag-and-drop thread.
     52       PostThreadMessage(drag_out_thread_id,
     53                         msg->message,
     54                         msg->wParam,
     55                         msg->lParam);
     56 
     57       // If the left button is up, we do not need to forward the message any
     58       // more.
     59       if (msg->message == WM_LBUTTONUP || !(GetKeyState(VK_LBUTTON) & 0x8000))
     60         mouse_up_received = true;
     61 
     62       return TRUE;
     63     }
     64   }
     65   return CallNextHookEx(msg_hook, code, wparam, lparam);
     66 }
     67 
     68 }  // namespace
     69 
     70 class DragDropThread : public base::Thread {
     71  public:
     72   explicit DragDropThread(TabContentsDragWin* drag_handler)
     73        : base::Thread("Chrome_DragDropThread"),
     74          drag_handler_(drag_handler) {
     75   }
     76 
     77   virtual ~DragDropThread() {
     78     Thread::Stop();
     79   }
     80 
     81  protected:
     82   // base::Thread implementations:
     83   virtual void Init() {
     84     int ole_result = OleInitialize(NULL);
     85     DCHECK(ole_result == S_OK);
     86   }
     87 
     88   virtual void CleanUp() {
     89     OleUninitialize();
     90   }
     91 
     92  private:
     93   // Hold a reference count to TabContentsDragWin to make sure that it is always
     94   // alive in the thread lifetime.
     95   scoped_refptr<TabContentsDragWin> drag_handler_;
     96 
     97   DISALLOW_COPY_AND_ASSIGN(DragDropThread);
     98 };
     99 
    100 TabContentsDragWin::TabContentsDragWin(NativeTabContentsViewWin* view)
    101     : drag_drop_thread_id_(0),
    102       view_(view),
    103       drag_ended_(false),
    104       old_drop_target_suspended_state_(false) {
    105 }
    106 
    107 TabContentsDragWin::~TabContentsDragWin() {
    108   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    109   DCHECK(!drag_drop_thread_.get());
    110 }
    111 
    112 void TabContentsDragWin::StartDragging(const WebDropData& drop_data,
    113                                        WebDragOperationsMask ops,
    114                                        const SkBitmap& image,
    115                                        const gfx::Point& image_offset) {
    116   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    117 
    118   drag_source_ = new WebDragSource(view_->GetNativeView(),
    119                                    view_->GetTabContents());
    120 
    121   const GURL& page_url = view_->GetTabContents()->GetURL();
    122   const std::string& page_encoding = view_->GetTabContents()->encoding();
    123 
    124   // If it is not drag-out, do the drag-and-drop in the current UI thread.
    125   if (drop_data.download_metadata.empty()) {
    126     DoDragging(drop_data, ops, page_url, page_encoding, image, image_offset);
    127     EndDragging(false);
    128     return;
    129   }
    130 
    131   // We do not want to drag and drop the download to itself.
    132   old_drop_target_suspended_state_ = view_->drop_target()->suspended();
    133   view_->drop_target()->set_suspended(true);
    134 
    135   // Start a background thread to do the drag-and-drop.
    136   DCHECK(!drag_drop_thread_.get());
    137   drag_drop_thread_.reset(new DragDropThread(this));
    138   base::Thread::Options options;
    139   options.message_loop_type = MessageLoop::TYPE_UI;
    140   if (drag_drop_thread_->StartWithOptions(options)) {
    141     drag_drop_thread_->message_loop()->PostTask(
    142         FROM_HERE,
    143         NewRunnableMethod(this,
    144                           &TabContentsDragWin::StartBackgroundDragging,
    145                           drop_data,
    146                           ops,
    147                           page_url,
    148                           page_encoding,
    149                           image,
    150                           image_offset));
    151   }
    152 
    153   // Install a hook procedure to monitor the messages so that we can forward
    154   // the appropriate ones to the background thread.
    155   drag_out_thread_id = drag_drop_thread_->thread_id();
    156   mouse_up_received = false;
    157   DCHECK(!msg_hook);
    158   msg_hook = SetWindowsHookEx(WH_MSGFILTER,
    159                               MsgFilterProc,
    160                               NULL,
    161                               GetCurrentThreadId());
    162 
    163   // Attach the input state of the background thread to the UI thread so that
    164   // SetCursor can work from the background thread.
    165   AttachThreadInput(drag_out_thread_id, GetCurrentThreadId(), TRUE);
    166 }
    167 
    168 void TabContentsDragWin::StartBackgroundDragging(
    169     const WebDropData& drop_data,
    170     WebDragOperationsMask ops,
    171     const GURL& page_url,
    172     const std::string& page_encoding,
    173     const SkBitmap& image,
    174     const gfx::Point& image_offset) {
    175   drag_drop_thread_id_ = base::PlatformThread::CurrentId();
    176 
    177   DoDragging(drop_data, ops, page_url, page_encoding, image, image_offset);
    178   BrowserThread::PostTask(
    179       BrowserThread::UI, FROM_HERE,
    180       NewRunnableMethod(this, &TabContentsDragWin::EndDragging, true));
    181 }
    182 
    183 void TabContentsDragWin::PrepareDragForDownload(
    184     const WebDropData& drop_data,
    185     ui::OSExchangeData* data,
    186     const GURL& page_url,
    187     const std::string& page_encoding) {
    188   // Parse the download metadata.
    189   string16 mime_type;
    190   FilePath file_name;
    191   GURL download_url;
    192   if (!drag_download_util::ParseDownloadMetadata(drop_data.download_metadata,
    193                                                  &mime_type,
    194                                                  &file_name,
    195                                                  &download_url))
    196     return;
    197 
    198   // Generate the download filename.
    199   std::string content_disposition =
    200       "attachment; filename=" + UTF16ToUTF8(file_name.value());
    201   FilePath generated_file_name;
    202   download_util::GenerateFileName(download_url,
    203                                   content_disposition,
    204                                   std::string(),
    205                                   UTF16ToUTF8(mime_type),
    206                                   &generated_file_name);
    207 
    208   // Provide the data as file (CF_HDROP). A temporary download file with the
    209   // Zone.Identifier ADS (Alternate Data Stream) attached will be created.
    210   linked_ptr<net::FileStream> empty_file_stream;
    211   scoped_refptr<DragDownloadFile> download_file =
    212       new DragDownloadFile(generated_file_name,
    213                            empty_file_stream,
    214                            download_url,
    215                            page_url,
    216                            page_encoding,
    217                            view_->GetTabContents());
    218   ui::OSExchangeData::DownloadFileInfo file_download(FilePath(),
    219                                                      download_file.get());
    220   data->SetDownloadFileInfo(file_download);
    221 
    222   // Enable asynchronous operation.
    223   ui::OSExchangeDataProviderWin::GetIAsyncOperation(*data)->SetAsyncMode(TRUE);
    224 }
    225 
    226 void TabContentsDragWin::PrepareDragForFileContents(
    227     const WebDropData& drop_data, ui::OSExchangeData* data) {
    228   // Images without ALT text will only have a file extension so we need to
    229   // synthesize one from the provided extension and URL.
    230   FilePath file_name(drop_data.file_description_filename);
    231   file_name = file_name.BaseName().RemoveExtension();
    232   if (file_name.value().empty()) {
    233     // Retrieve the name from the URL.
    234     file_name = FilePath(
    235         net::GetSuggestedFilename(drop_data.url, "", "", string16()));
    236     if (file_name.value().size() + drop_data.file_extension.size() + 1 >
    237         MAX_PATH) {
    238       file_name = FilePath(file_name.value().substr(
    239           0, MAX_PATH - drop_data.file_extension.size() - 2));
    240     }
    241   }
    242   file_name = file_name.ReplaceExtension(drop_data.file_extension);
    243   data->SetFileContents(file_name, drop_data.file_contents);
    244 }
    245 
    246 void TabContentsDragWin::PrepareDragForUrl(const WebDropData& drop_data,
    247                                            ui::OSExchangeData* data) {
    248   if (drop_data.url.SchemeIs(chrome::kJavaScriptScheme)) {
    249     // We don't want to allow javascript URLs to be dragged to the desktop,
    250     // but we do want to allow them to be added to the bookmarks bar
    251     // (bookmarklets). So we create a fake bookmark entry (BookmarkNodeData
    252     // object) which explorer.exe cannot handle, and write the entry to data.
    253     BookmarkNodeData::Element bm_elt;
    254     bm_elt.is_url = true;
    255     bm_elt.url = drop_data.url;
    256     bm_elt.title = drop_data.url_title;
    257 
    258     BookmarkNodeData bm_drag_data;
    259     bm_drag_data.elements.push_back(bm_elt);
    260 
    261     // Pass in NULL as the profile so that the bookmark always adds the url
    262     // rather than trying to move an existing url.
    263     bm_drag_data.Write(NULL, data);
    264   } else {
    265     data->SetURL(drop_data.url, drop_data.url_title);
    266   }
    267 }
    268 
    269 void TabContentsDragWin::DoDragging(const WebDropData& drop_data,
    270                                     WebDragOperationsMask ops,
    271                                     const GURL& page_url,
    272                                     const std::string& page_encoding,
    273                                     const SkBitmap& image,
    274                                     const gfx::Point& image_offset) {
    275   ui::OSExchangeData data;
    276 
    277   if (!drop_data.download_metadata.empty()) {
    278     PrepareDragForDownload(drop_data, &data, page_url, page_encoding);
    279 
    280     // Set the observer.
    281     ui::OSExchangeDataProviderWin::GetDataObjectImpl(data)->set_observer(this);
    282   } else {
    283     // We set the file contents before the URL because the URL also sets file
    284     // contents (to a .URL shortcut).  We want to prefer file content data over
    285     // a shortcut so we add it first.
    286     if (!drop_data.file_contents.empty())
    287       PrepareDragForFileContents(drop_data, &data);
    288     if (!drop_data.text_html.empty())
    289       data.SetHtml(drop_data.text_html, drop_data.html_base_url);
    290     // We set the text contents before the URL because the URL also sets text
    291     // content.
    292     if (!drop_data.plain_text.empty())
    293       data.SetString(drop_data.plain_text);
    294     if (drop_data.url.is_valid())
    295       PrepareDragForUrl(drop_data, &data);
    296   }
    297 
    298   // Set drag image.
    299   if (!image.isNull()) {
    300     drag_utils::SetDragImageOnDataObject(
    301         image, gfx::Size(image.width(), image.height()), image_offset, &data);
    302   }
    303 
    304   // We need to enable recursive tasks on the message loop so we can get
    305   // updates while in the system DoDragDrop loop.
    306   bool old_state = MessageLoop::current()->NestableTasksAllowed();
    307   MessageLoop::current()->SetNestableTasksAllowed(true);
    308   DWORD effect;
    309   DoDragDrop(ui::OSExchangeDataProviderWin::GetIDataObject(data), drag_source_,
    310              web_drag_utils_win::WebDragOpMaskToWinDragOpMask(ops), &effect);
    311   MessageLoop::current()->SetNestableTasksAllowed(old_state);
    312 
    313   // This works because WebDragSource::OnDragSourceDrop uses PostTask to
    314   // dispatch the actual event.
    315   drag_source_->set_effect(effect);
    316 }
    317 
    318 void TabContentsDragWin::EndDragging(bool restore_suspended_state) {
    319   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    320 
    321   if (drag_ended_)
    322     return;
    323   drag_ended_ = true;
    324 
    325   if (restore_suspended_state)
    326     view_->drop_target()->set_suspended(old_drop_target_suspended_state_);
    327 
    328   if (msg_hook) {
    329     AttachThreadInput(drag_out_thread_id, GetCurrentThreadId(), FALSE);
    330     UnhookWindowsHookEx(msg_hook);
    331     msg_hook = NULL;
    332   }
    333 
    334   view_->EndDragging();
    335 }
    336 
    337 void TabContentsDragWin::CancelDrag() {
    338   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    339 
    340   drag_source_->CancelDrag();
    341 }
    342 
    343 void TabContentsDragWin::CloseThread() {
    344   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    345 
    346   drag_drop_thread_.reset();
    347 }
    348 
    349 void TabContentsDragWin::OnWaitForData() {
    350   DCHECK(drag_drop_thread_id_ == base::PlatformThread::CurrentId());
    351 
    352   // When the left button is release and we start to wait for the data, end
    353   // the dragging before DoDragDrop returns. This makes the page leave the drag
    354   // mode so that it can start to process the normal input events.
    355   BrowserThread::PostTask(
    356       BrowserThread::UI, FROM_HERE,
    357       NewRunnableMethod(this, &TabContentsDragWin::EndDragging, true));
    358 }
    359 
    360 void TabContentsDragWin::OnDataObjectDisposed() {
    361   DCHECK(drag_drop_thread_id_ == base::PlatformThread::CurrentId());
    362 
    363   // The drag-and-drop thread is only closed after OLE is done with
    364   // DataObjectImpl.
    365   BrowserThread::PostTask(
    366       BrowserThread::UI, FROM_HERE,
    367       NewRunnableMethod(this, &TabContentsDragWin::CloseThread));
    368 }
    369