Home | History | Annotate | Download | only in web_contents
      1 // Copyright (c) 2012 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 "content/browser/web_contents/web_contents_drag_win.h"
      6 
      7 #include <windows.h>
      8 
      9 #include <string>
     10 
     11 #include "base/bind.h"
     12 #include "base/file_util.h"
     13 #include "base/files/file_path.h"
     14 #include "base/message_loop/message_loop.h"
     15 #include "base/pickle.h"
     16 #include "base/strings/utf_string_conversions.h"
     17 #include "base/threading/platform_thread.h"
     18 #include "base/threading/thread.h"
     19 #include "content/browser/download/drag_download_file.h"
     20 #include "content/browser/download/drag_download_util.h"
     21 #include "content/browser/web_contents/web_drag_dest_win.h"
     22 #include "content/browser/web_contents/web_drag_source_win.h"
     23 #include "content/browser/web_contents/web_drag_utils_win.h"
     24 #include "content/public/browser/browser_thread.h"
     25 #include "content/public/browser/content_browser_client.h"
     26 #include "content/public/browser/web_contents.h"
     27 #include "content/public/browser/web_contents_view.h"
     28 #include "content/public/browser/web_drag_dest_delegate.h"
     29 #include "content/public/common/drop_data.h"
     30 #include "net/base/net_util.h"
     31 #include "third_party/skia/include/core/SkBitmap.h"
     32 #include "ui/base/clipboard/clipboard.h"
     33 #include "ui/base/clipboard/custom_data_helper.h"
     34 #include "ui/base/dragdrop/drag_utils.h"
     35 #include "ui/base/layout.h"
     36 #include "ui/base/win/scoped_ole_initializer.h"
     37 #include "ui/gfx/image/image_skia.h"
     38 #include "ui/gfx/screen.h"
     39 #include "ui/gfx/size.h"
     40 
     41 using WebKit::WebDragOperationsMask;
     42 using WebKit::WebDragOperationCopy;
     43 using WebKit::WebDragOperationLink;
     44 using WebKit::WebDragOperationMove;
     45 
     46 namespace content {
     47 namespace {
     48 
     49 bool run_do_drag_drop = true;
     50 
     51 HHOOK msg_hook = NULL;
     52 DWORD drag_out_thread_id = 0;
     53 bool mouse_up_received = false;
     54 
     55 LRESULT CALLBACK MsgFilterProc(int code, WPARAM wparam, LPARAM lparam) {
     56   if (code == base::MessagePumpForUI::kMessageFilterCode &&
     57       !mouse_up_received) {
     58     MSG* msg = reinterpret_cast<MSG*>(lparam);
     59     // We do not care about WM_SYSKEYDOWN and WM_SYSKEYUP because when ALT key
     60     // is pressed down on drag-and-drop, it means to create a link.
     61     if (msg->message == WM_MOUSEMOVE || msg->message == WM_LBUTTONUP ||
     62         msg->message == WM_KEYDOWN || msg->message == WM_KEYUP) {
     63       // Forward the message from the UI thread to the drag-and-drop thread.
     64       PostThreadMessage(drag_out_thread_id,
     65                         msg->message,
     66                         msg->wParam,
     67                         msg->lParam);
     68 
     69       // If the left button is up, we do not need to forward the message any
     70       // more.
     71       if (msg->message == WM_LBUTTONUP || !(GetKeyState(VK_LBUTTON) & 0x8000))
     72         mouse_up_received = true;
     73 
     74       return TRUE;
     75     }
     76   }
     77   return CallNextHookEx(msg_hook, code, wparam, lparam);
     78 }
     79 
     80 void EnableBackgroundDraggingSupport(DWORD thread_id) {
     81   // Install a hook procedure to monitor the messages so that we can forward
     82   // the appropriate ones to the background thread.
     83   drag_out_thread_id = thread_id;
     84   mouse_up_received = false;
     85   DCHECK(!msg_hook);
     86   msg_hook = SetWindowsHookEx(WH_MSGFILTER,
     87                               MsgFilterProc,
     88                               NULL,
     89                               GetCurrentThreadId());
     90 
     91   // Attach the input state of the background thread to the UI thread so that
     92   // SetCursor can work from the background thread.
     93   AttachThreadInput(drag_out_thread_id, GetCurrentThreadId(), TRUE);
     94 }
     95 
     96 void DisableBackgroundDraggingSupport() {
     97   DCHECK(msg_hook);
     98   AttachThreadInput(drag_out_thread_id, GetCurrentThreadId(), FALSE);
     99   UnhookWindowsHookEx(msg_hook);
    100   msg_hook = NULL;
    101 }
    102 
    103 bool IsBackgroundDraggingSupportEnabled() {
    104   return msg_hook != NULL;
    105 }
    106 
    107 }  // namespace
    108 
    109 class DragDropThread : public base::Thread {
    110  public:
    111   explicit DragDropThread(WebContentsDragWin* drag_handler)
    112        : Thread("Chrome_DragDropThread"),
    113          drag_handler_(drag_handler) {
    114   }
    115 
    116   virtual ~DragDropThread() {
    117     Stop();
    118   }
    119 
    120  protected:
    121   // base::Thread implementations:
    122   virtual void Init() {
    123     ole_initializer_.reset(new ui::ScopedOleInitializer());
    124   }
    125 
    126   virtual void CleanUp() {
    127     ole_initializer_.reset();
    128   }
    129 
    130  private:
    131   scoped_ptr<ui::ScopedOleInitializer> ole_initializer_;
    132 
    133   // Hold a reference count to WebContentsDragWin to make sure that it is always
    134   // alive in the thread lifetime.
    135   scoped_refptr<WebContentsDragWin> drag_handler_;
    136 
    137   DISALLOW_COPY_AND_ASSIGN(DragDropThread);
    138 };
    139 
    140 WebContentsDragWin::WebContentsDragWin(
    141     gfx::NativeWindow source_window,
    142     WebContents* web_contents,
    143     WebDragDest* drag_dest,
    144     const base::Callback<void()>& drag_end_callback)
    145     : drag_drop_thread_id_(0),
    146       source_window_(source_window),
    147       web_contents_(web_contents),
    148       drag_dest_(drag_dest),
    149       drag_ended_(false),
    150       drag_end_callback_(drag_end_callback) {
    151 }
    152 
    153 WebContentsDragWin::~WebContentsDragWin() {
    154   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    155   DCHECK(!drag_drop_thread_.get());
    156 }
    157 
    158 void WebContentsDragWin::StartDragging(const DropData& drop_data,
    159                                        WebDragOperationsMask ops,
    160                                        const gfx::ImageSkia& image,
    161                                        const gfx::Vector2d& image_offset) {
    162   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    163 
    164   drag_source_ = new WebDragSource(source_window_, web_contents_);
    165 
    166   const GURL& page_url = web_contents_->GetURL();
    167   const std::string& page_encoding = web_contents_->GetEncoding();
    168 
    169   // If it is not drag-out, do the drag-and-drop in the current UI thread.
    170   if (drop_data.download_metadata.empty()) {
    171     if (DoDragging(drop_data, ops, page_url, page_encoding,
    172                    image, image_offset))
    173       EndDragging();
    174     return;
    175   }
    176 
    177   // Start a background thread to do the drag-and-drop.
    178   DCHECK(!drag_drop_thread_.get());
    179   drag_drop_thread_.reset(new DragDropThread(this));
    180   base::Thread::Options options;
    181   options.message_loop_type = base::MessageLoop::TYPE_UI;
    182   if (drag_drop_thread_->StartWithOptions(options)) {
    183     drag_drop_thread_->message_loop()->PostTask(
    184         FROM_HERE,
    185         base::Bind(&WebContentsDragWin::StartBackgroundDragging, this,
    186                    drop_data, ops, page_url, page_encoding,
    187                    image, image_offset));
    188   }
    189 
    190   EnableBackgroundDraggingSupport(drag_drop_thread_->thread_id());
    191 }
    192 
    193 void WebContentsDragWin::StartBackgroundDragging(
    194     const DropData& drop_data,
    195     WebDragOperationsMask ops,
    196     const GURL& page_url,
    197     const std::string& page_encoding,
    198     const gfx::ImageSkia& image,
    199     const gfx::Vector2d& image_offset) {
    200   drag_drop_thread_id_ = base::PlatformThread::CurrentId();
    201 
    202   if (DoDragging(drop_data, ops, page_url, page_encoding,
    203                  image, image_offset)) {
    204     BrowserThread::PostTask(
    205         BrowserThread::UI,
    206         FROM_HERE,
    207         base::Bind(&WebContentsDragWin::EndDragging, this));
    208   } else {
    209     // When DoDragging returns false, the contents view window is gone and thus
    210     // WebContentsViewWin instance becomes invalid though WebContentsDragWin
    211     // instance is still alive because the task holds a reference count to it.
    212     // We should not do more than the following cleanup items:
    213     // 1) Remove the background dragging support. This is safe since it does not
    214     //    access the instance at all.
    215     // 2) Stop the background thread. This is done in OnDataObjectDisposed.
    216     //    Only drag_drop_thread_ member is accessed.
    217     BrowserThread::PostTask(
    218         BrowserThread::UI,
    219         FROM_HERE,
    220         base::Bind(&DisableBackgroundDraggingSupport));
    221   }
    222 }
    223 
    224 void WebContentsDragWin::PrepareDragForDownload(
    225     const DropData& drop_data,
    226     ui::OSExchangeData* data,
    227     const GURL& page_url,
    228     const std::string& page_encoding) {
    229   // Parse the download metadata.
    230   string16 mime_type;
    231   base::FilePath file_name;
    232   GURL download_url;
    233   if (!ParseDownloadMetadata(drop_data.download_metadata,
    234                              &mime_type,
    235                              &file_name,
    236                              &download_url))
    237     return;
    238 
    239   // Generate the file name based on both mime type and proposed file name.
    240   std::string default_name =
    241       GetContentClient()->browser()->GetDefaultDownloadName();
    242   base::FilePath generated_download_file_name =
    243       net::GenerateFileName(download_url,
    244                             std::string(),
    245                             std::string(),
    246                             UTF16ToUTF8(file_name.value()),
    247                             UTF16ToUTF8(mime_type),
    248                             default_name);
    249   base::FilePath temp_dir_path;
    250   if (!file_util::CreateNewTempDirectory(
    251           FILE_PATH_LITERAL("chrome_drag"), &temp_dir_path))
    252     return;
    253   base::FilePath download_path =
    254       temp_dir_path.Append(generated_download_file_name);
    255 
    256   // We cannot know when the target application will be done using the temporary
    257   // file, so schedule it to be deleted after rebooting.
    258   base::DeleteFileAfterReboot(download_path);
    259   base::DeleteFileAfterReboot(temp_dir_path);
    260 
    261   // Provide the data as file (CF_HDROP). A temporary download file with the
    262   // Zone.Identifier ADS (Alternate Data Stream) attached will be created.
    263   scoped_refptr<DragDownloadFile> download_file =
    264       new DragDownloadFile(
    265           download_path,
    266           scoped_ptr<net::FileStream>(),
    267           download_url,
    268           Referrer(page_url, drop_data.referrer_policy),
    269           page_encoding,
    270           web_contents_);
    271   ui::OSExchangeData::DownloadFileInfo file_download(base::FilePath(),
    272                                                      download_file.get());
    273   data->SetDownloadFileInfo(file_download);
    274 
    275   // Enable asynchronous operation.
    276   ui::OSExchangeDataProviderWin::GetIAsyncOperation(*data)->SetAsyncMode(TRUE);
    277 }
    278 
    279 void WebContentsDragWin::PrepareDragForFileContents(
    280     const DropData& drop_data, ui::OSExchangeData* data) {
    281   static const int kMaxFilenameLength = 255;  // FAT and NTFS
    282   base::FilePath file_name(drop_data.file_description_filename);
    283 
    284   // Images without ALT text will only have a file extension so we need to
    285   // synthesize one from the provided extension and URL.
    286   if (file_name.BaseName().RemoveExtension().empty()) {
    287     const string16 extension = file_name.Extension();
    288     // Retrieve the name from the URL.
    289     file_name = base::FilePath(
    290         net::GetSuggestedFilename(drop_data.url, "", "", "", "", ""));
    291     if (file_name.value().size() + extension.size() > kMaxFilenameLength) {
    292       file_name = base::FilePath(file_name.value().substr(
    293           0, kMaxFilenameLength - extension.size()));
    294     }
    295     file_name = file_name.ReplaceExtension(extension);
    296   }
    297   data->SetFileContents(file_name, drop_data.file_contents);
    298 }
    299 
    300 void WebContentsDragWin::PrepareDragForUrl(const DropData& drop_data,
    301                                            ui::OSExchangeData* data) {
    302   if (drag_dest_->delegate() &&
    303       drag_dest_->delegate()->AddDragData(drop_data, data)) {
    304     return;
    305   }
    306 
    307   data->SetURL(drop_data.url, drop_data.url_title);
    308 }
    309 
    310 bool WebContentsDragWin::DoDragging(const DropData& drop_data,
    311                                     WebDragOperationsMask ops,
    312                                     const GURL& page_url,
    313                                     const std::string& page_encoding,
    314                                     const gfx::ImageSkia& image,
    315                                     const gfx::Vector2d& image_offset) {
    316   ui::OSExchangeData data;
    317 
    318   if (!drop_data.download_metadata.empty()) {
    319     PrepareDragForDownload(drop_data, &data, page_url, page_encoding);
    320 
    321     // Set the observer.
    322     ui::OSExchangeDataProviderWin::GetDataObjectImpl(data)->set_observer(this);
    323   }
    324 
    325   // We set the file contents before the URL because the URL also sets file
    326   // contents (to a .URL shortcut).  We want to prefer file content data over
    327   // a shortcut so we add it first.
    328   if (!drop_data.file_contents.empty())
    329     PrepareDragForFileContents(drop_data, &data);
    330   if (!drop_data.html.string().empty())
    331     data.SetHtml(drop_data.html.string(), drop_data.html_base_url);
    332   // We set the text contents before the URL because the URL also sets text
    333   // content.
    334   if (!drop_data.text.string().empty())
    335     data.SetString(drop_data.text.string());
    336   if (drop_data.url.is_valid())
    337     PrepareDragForUrl(drop_data, &data);
    338   if (!drop_data.custom_data.empty()) {
    339     Pickle pickle;
    340     ui::WriteCustomDataToPickle(drop_data.custom_data, &pickle);
    341     data.SetPickledData(ui::Clipboard::GetWebCustomDataFormatType(), pickle);
    342   }
    343 
    344   // Set drag image.
    345   if (!image.isNull()) {
    346     drag_utils::SetDragImageOnDataObject(image,
    347         gfx::Size(image.width(), image.height()), image_offset, &data);
    348   }
    349 
    350   // Use a local variable to keep track of the contents view window handle.
    351   // It might not be safe to access the instance after DoDragDrop returns
    352   // because the window could be disposed in the nested message loop.
    353   HWND native_window = web_contents_->GetView()->GetNativeView();
    354 
    355   // We need to enable recursive tasks on the message loop so we can get
    356   // updates while in the system DoDragDrop loop.
    357   DWORD effect = DROPEFFECT_NONE;
    358   if (run_do_drag_drop) {
    359     // Keep a reference count such that |drag_source_| will not get deleted
    360     // if the contents view window is gone in the nested message loop invoked
    361     // from DoDragDrop.
    362     scoped_refptr<WebDragSource> retain_source(drag_source_);
    363     retain_source->set_data(&data);
    364     data.SetInDragLoop(true);
    365 
    366     base::MessageLoop::ScopedNestableTaskAllower allow(
    367         base::MessageLoop::current());
    368     DoDragDrop(ui::OSExchangeDataProviderWin::GetIDataObject(data),
    369                drag_source_,
    370                WebDragOpMaskToWinDragOpMask(ops),
    371                &effect);
    372     retain_source->set_data(NULL);
    373   }
    374 
    375   // Bail out immediately if the contents view window is gone.
    376   if (!IsWindow(native_window))
    377     return false;
    378 
    379   // Normally, the drop and dragend events get dispatched in the system
    380   // DoDragDrop message loop so it'd be too late to set the effect to send back
    381   // to the renderer here. However, we use PostTask to delay the execution of
    382   // WebDragSource::OnDragSourceDrop, which means that the delayed dragend
    383   // callback to the renderer doesn't run until this has been set to the correct
    384   // value.
    385   drag_source_->set_effect(effect);
    386 
    387   return true;
    388 }
    389 
    390 void WebContentsDragWin::EndDragging() {
    391   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    392 
    393   if (drag_ended_)
    394     return;
    395   drag_ended_ = true;
    396 
    397   if (IsBackgroundDraggingSupportEnabled())
    398     DisableBackgroundDraggingSupport();
    399 
    400   drag_end_callback_.Run();
    401 }
    402 
    403 void WebContentsDragWin::CancelDrag() {
    404   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    405 
    406   drag_source_->CancelDrag();
    407 }
    408 
    409 void WebContentsDragWin::CloseThread() {
    410   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    411 
    412   drag_drop_thread_.reset();
    413 }
    414 
    415 void WebContentsDragWin::OnWaitForData() {
    416   DCHECK(drag_drop_thread_id_ == base::PlatformThread::CurrentId());
    417 
    418   // When the left button is release and we start to wait for the data, end
    419   // the dragging before DoDragDrop returns. This makes the page leave the drag
    420   // mode so that it can start to process the normal input events.
    421   BrowserThread::PostTask(
    422       BrowserThread::UI,
    423       FROM_HERE,
    424       base::Bind(&WebContentsDragWin::EndDragging, this));
    425 }
    426 
    427 void WebContentsDragWin::OnDataObjectDisposed() {
    428   DCHECK(drag_drop_thread_id_ == base::PlatformThread::CurrentId());
    429 
    430   // The drag-and-drop thread is only closed after OLE is done with
    431   // DataObjectImpl.
    432   BrowserThread::PostTask(
    433       BrowserThread::UI,
    434       FROM_HERE,
    435       base::Bind(&WebContentsDragWin::CloseThread, this));
    436 }
    437 
    438 // static
    439 void WebContentsDragWin::DisableDragDropForTesting() {
    440   run_do_drag_drop = false;
    441 }
    442 
    443 }  // namespace content
    444