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