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_drag_dest_win.h"
      6 
      7 #include <windows.h>
      8 #include <shlobj.h>
      9 
     10 #include "base/win/win_util.h"
     11 #include "content/browser/renderer_host/render_view_host_impl.h"
     12 #include "content/browser/web_contents/web_drag_utils_win.h"
     13 #include "content/public/browser/web_contents.h"
     14 #include "content/public/browser/web_contents_delegate.h"
     15 #include "content/public/browser/web_drag_dest_delegate.h"
     16 #include "content/public/common/drop_data.h"
     17 #include "net/base/net_util.h"
     18 #include "third_party/WebKit/public/web/WebInputEvent.h"
     19 #include "ui/base/clipboard/clipboard_util_win.h"
     20 #include "ui/base/dragdrop/os_exchange_data.h"
     21 #include "ui/base/dragdrop/os_exchange_data_provider_win.h"
     22 #include "ui/base/window_open_disposition.h"
     23 #include "ui/gfx/point.h"
     24 #include "url/gurl.h"
     25 
     26 using WebKit::WebDragOperationNone;
     27 using WebKit::WebDragOperationCopy;
     28 using WebKit::WebDragOperationLink;
     29 using WebKit::WebDragOperationMove;
     30 using WebKit::WebDragOperationGeneric;
     31 
     32 namespace content {
     33 namespace {
     34 
     35 const unsigned short kHighBitMaskShort = 0x8000;
     36 
     37 // A helper method for getting the preferred drop effect.
     38 DWORD GetPreferredDropEffect(DWORD effect) {
     39   if (effect & DROPEFFECT_COPY)
     40     return DROPEFFECT_COPY;
     41   if (effect & DROPEFFECT_LINK)
     42     return DROPEFFECT_LINK;
     43   if (effect & DROPEFFECT_MOVE)
     44     return DROPEFFECT_MOVE;
     45   return DROPEFFECT_NONE;
     46 }
     47 
     48 int GetModifierFlags() {
     49   int modifier_state = 0;
     50   if (base::win::IsShiftPressed())
     51     modifier_state |= WebKit::WebInputEvent::ShiftKey;
     52   if (base::win::IsCtrlPressed())
     53     modifier_state |= WebKit::WebInputEvent::ControlKey;
     54   if (base::win::IsAltPressed())
     55     modifier_state |= WebKit::WebInputEvent::AltKey;
     56   if (::GetKeyState(VK_LWIN) & kHighBitMaskShort)
     57     modifier_state |= WebKit::WebInputEvent::MetaKey;
     58   if (::GetKeyState(VK_RWIN) & kHighBitMaskShort)
     59     modifier_state |= WebKit::WebInputEvent::MetaKey;
     60   return modifier_state;
     61 }
     62 
     63 // Helper method for converting Window's specific IDataObject to a DropData
     64 // object.
     65 void PopulateDropData(IDataObject* data_object, DropData* drop_data) {
     66   base::string16 url_str;
     67   if (ui::ClipboardUtil::GetUrl(
     68           data_object, &url_str, &drop_data->url_title, false)) {
     69     GURL test_url(url_str);
     70     if (test_url.is_valid())
     71       drop_data->url = test_url;
     72   }
     73   std::vector<base::string16> filenames;
     74   ui::ClipboardUtil::GetFilenames(data_object, &filenames);
     75   for (size_t i = 0; i < filenames.size(); ++i)
     76     drop_data->filenames.push_back(
     77         DropData::FileInfo(filenames[i], base::string16()));
     78   base::string16 text;
     79   ui::ClipboardUtil::GetPlainText(data_object, &text);
     80   if (!text.empty()) {
     81     drop_data->text = base::NullableString16(text, false);
     82   }
     83   base::string16 html;
     84   std::string html_base_url;
     85   ui::ClipboardUtil::GetHtml(data_object, &html, &html_base_url);
     86   if (!html.empty()) {
     87     drop_data->html = base::NullableString16(html, false);
     88   }
     89   if (!html_base_url.empty()) {
     90     drop_data->html_base_url = GURL(html_base_url);
     91   }
     92   ui::ClipboardUtil::GetWebCustomData(data_object, &drop_data->custom_data);
     93 }
     94 
     95 }  // namespace
     96 
     97 // InterstitialDropTarget is like a ui::DropTargetWin implementation that
     98 // WebDragDest passes through to if an interstitial is showing.  Rather than
     99 // passing messages on to the renderer, we just check to see if there's a link
    100 // in the drop data and handle links as navigations.
    101 class InterstitialDropTarget {
    102  public:
    103   explicit InterstitialDropTarget(WebContents* web_contents)
    104       : web_contents_(web_contents) {}
    105 
    106   DWORD OnDragEnter(IDataObject* data_object, DWORD effect) {
    107     return ui::ClipboardUtil::HasUrl(data_object) ?
    108         GetPreferredDropEffect(effect) : DROPEFFECT_NONE;
    109   }
    110 
    111   DWORD OnDragOver(IDataObject* data_object, DWORD effect) {
    112     return ui::ClipboardUtil::HasUrl(data_object) ?
    113         GetPreferredDropEffect(effect) : DROPEFFECT_NONE;
    114   }
    115 
    116   void OnDragLeave(IDataObject* data_object) {
    117   }
    118 
    119   DWORD OnDrop(IDataObject* data_object, DWORD effect) {
    120     if (!ui::ClipboardUtil::HasUrl(data_object))
    121       return DROPEFFECT_NONE;
    122 
    123     std::wstring url;
    124     std::wstring title;
    125     ui::ClipboardUtil::GetUrl(data_object, &url, &title, true);
    126     OpenURLParams params(GURL(url), Referrer(), CURRENT_TAB,
    127                          PAGE_TRANSITION_AUTO_BOOKMARK, false);
    128     web_contents_->OpenURL(params);
    129     return GetPreferredDropEffect(effect);
    130   }
    131 
    132  private:
    133   WebContents* web_contents_;
    134 
    135   DISALLOW_COPY_AND_ASSIGN(InterstitialDropTarget);
    136 };
    137 
    138 WebDragDest::WebDragDest(HWND source_hwnd, WebContents* web_contents)
    139     : ui::DropTargetWin(source_hwnd),
    140       web_contents_(web_contents),
    141       current_rvh_(NULL),
    142       drag_cursor_(WebDragOperationNone),
    143       interstitial_drop_target_(new InterstitialDropTarget(web_contents)),
    144       delegate_(NULL),
    145       canceled_(false) {
    146 }
    147 
    148 WebDragDest::~WebDragDest() {
    149 }
    150 
    151 DWORD WebDragDest::OnDragEnter(IDataObject* data_object,
    152                                DWORD key_state,
    153                                POINT cursor_position,
    154                                DWORD effects) {
    155   current_rvh_ = web_contents_->GetRenderViewHost();
    156 
    157   // TODO(tc): PopulateDropData can be slow depending on what is in the
    158   // IDataObject.  Maybe we can do this in a background thread.
    159   scoped_ptr<DropData> drop_data;
    160   drop_data.reset(new DropData());
    161   PopulateDropData(data_object, drop_data.get());
    162 
    163   if (drop_data->url.is_empty())
    164     ui::OSExchangeDataProviderWin::GetPlainTextURL(data_object,
    165                                                    &drop_data->url);
    166 
    167   // Give the delegate an opportunity to cancel the drag.
    168   canceled_ = !web_contents_->GetDelegate()->CanDragEnter(
    169       web_contents_,
    170       *drop_data,
    171       WinDragOpMaskToWebDragOpMask(effects));
    172   if (canceled_)
    173     return DROPEFFECT_NONE;
    174 
    175   if (delegate_)
    176     delegate_->DragInitialize(web_contents_);
    177 
    178   // Don't pass messages to the renderer if an interstitial page is showing
    179   // because we don't want the interstitial page to navigate.  Instead,
    180   // pass the messages on to a separate interstitial DropTarget handler.
    181   if (web_contents_->ShowingInterstitialPage())
    182     return interstitial_drop_target_->OnDragEnter(data_object, effects);
    183 
    184   drop_data_.swap(drop_data);
    185   drag_cursor_ = WebDragOperationNone;
    186 
    187   POINT client_pt = cursor_position;
    188   ScreenToClient(GetHWND(), &client_pt);
    189   web_contents_->GetRenderViewHost()->DragTargetDragEnter(*drop_data_,
    190       gfx::Point(client_pt.x, client_pt.y),
    191       gfx::Point(cursor_position.x, cursor_position.y),
    192       WinDragOpMaskToWebDragOpMask(effects),
    193       GetModifierFlags());
    194 
    195   if (delegate_)
    196       delegate_->OnDragEnter(data_object);
    197 
    198   // We lie here and always return a DROPEFFECT because we don't want to
    199   // wait for the IPC call to return.
    200   return WebDragOpToWinDragOp(drag_cursor_);
    201 }
    202 
    203 DWORD WebDragDest::OnDragOver(IDataObject* data_object,
    204                               DWORD key_state,
    205                               POINT cursor_position,
    206                               DWORD effects) {
    207   DCHECK(current_rvh_);
    208   if (current_rvh_ != web_contents_->GetRenderViewHost())
    209     OnDragEnter(data_object, key_state, cursor_position, effects);
    210 
    211   if (canceled_)
    212     return DROPEFFECT_NONE;
    213 
    214   if (web_contents_->ShowingInterstitialPage())
    215     return interstitial_drop_target_->OnDragOver(data_object, effects);
    216 
    217   POINT client_pt = cursor_position;
    218   ScreenToClient(GetHWND(), &client_pt);
    219   web_contents_->GetRenderViewHost()->DragTargetDragOver(
    220       gfx::Point(client_pt.x, client_pt.y),
    221       gfx::Point(cursor_position.x, cursor_position.y),
    222       WinDragOpMaskToWebDragOpMask(effects),
    223       GetModifierFlags());
    224 
    225   if (delegate_)
    226     delegate_->OnDragOver(data_object);
    227 
    228   return WebDragOpToWinDragOp(drag_cursor_);
    229 }
    230 
    231 void WebDragDest::OnDragLeave(IDataObject* data_object) {
    232   DCHECK(current_rvh_);
    233   if (current_rvh_ != web_contents_->GetRenderViewHost())
    234     return;
    235 
    236   if (canceled_)
    237     return;
    238 
    239   if (web_contents_->ShowingInterstitialPage()) {
    240     interstitial_drop_target_->OnDragLeave(data_object);
    241   } else {
    242     web_contents_->GetRenderViewHost()->DragTargetDragLeave();
    243   }
    244 
    245   if (delegate_)
    246     delegate_->OnDragLeave(data_object);
    247 
    248   drop_data_.reset();
    249 }
    250 
    251 DWORD WebDragDest::OnDrop(IDataObject* data_object,
    252                           DWORD key_state,
    253                           POINT cursor_position,
    254                           DWORD effect) {
    255   DCHECK(current_rvh_);
    256   if (current_rvh_ != web_contents_->GetRenderViewHost())
    257     OnDragEnter(data_object, key_state, cursor_position, effect);
    258 
    259   if (web_contents_->ShowingInterstitialPage())
    260     interstitial_drop_target_->OnDragOver(data_object, effect);
    261 
    262   if (web_contents_->ShowingInterstitialPage())
    263     return interstitial_drop_target_->OnDrop(data_object, effect);
    264 
    265   POINT client_pt = cursor_position;
    266   ScreenToClient(GetHWND(), &client_pt);
    267   web_contents_->GetRenderViewHost()->DragTargetDrop(
    268       gfx::Point(client_pt.x, client_pt.y),
    269       gfx::Point(cursor_position.x, cursor_position.y),
    270       GetModifierFlags());
    271 
    272   if (delegate_)
    273     delegate_->OnDrop(data_object);
    274 
    275   current_rvh_ = NULL;
    276 
    277   // This isn't always correct, but at least it's a close approximation.
    278   // For now, we always map a move to a copy to prevent potential data loss.
    279   DWORD drop_effect = WebDragOpToWinDragOp(drag_cursor_);
    280   DWORD result = drop_effect != DROPEFFECT_MOVE ? drop_effect : DROPEFFECT_COPY;
    281 
    282   drop_data_.reset();
    283   return result;
    284 }
    285 
    286 }  // namespace content
    287