From 7d006a8dd6df80d2e9f8c57668091d1b9e1192ff Mon Sep 17 00:00:00 2001 From: Marshall Greenblatt Date: Tue, 2 Aug 2011 16:50:06 +0000 Subject: [PATCH] Mac: - Add HTML5 drag&drop support (issue #140). - Client application must now provide NSApplication implementing CefAppProtocol and call CefRunMessageLoop() (issue #248). git-svn-id: https://chromiumembedded.googlecode.com/svn/trunk@269 5089003a-bbd8-11dd-ad1f-f1f9622dbc98 --- cef.gyp | 24 ++ include/cef_application_mac.h | 82 +++++ libcef/browser_drag_delegate_win.cc | 237 +------------ libcef/browser_impl.cc | 4 +- libcef/browser_impl_gtk.cc | 1 - libcef/browser_impl_mac.mm | 1 - libcef/browser_webview_delegate.cc | 51 --- libcef/browser_webview_delegate.h | 16 +- libcef/browser_webview_delegate_gtk.cc | 9 + libcef/browser_webview_delegate_mac.mm | 45 +++ libcef/browser_webview_delegate_win.cc | 45 ++- libcef/browser_webview_mac.h | 13 + libcef/browser_webview_mac.mm | 108 +++++- libcef/cef_process_ui_thread_mac.mm | 59 +--- libcef/download_util.cc | 188 ++++++++++ libcef/download_util.h | 27 ++ libcef/drag_download_util.cc | 113 ++++++ libcef/drag_download_util.h | 63 ++++ libcef/web_drag_source_mac.h | 82 +++++ libcef/web_drag_source_mac.mm | 456 +++++++++++++++++++++++++ libcef/web_drag_utils_mac.h | 37 ++ libcef/web_drag_utils_mac.mm | 101 ++++++ libcef/web_drop_target_mac.h | 69 ++++ libcef/web_drop_target_mac.mm | 234 +++++++++++++ libcef/webview_host.h | 2 + libcef/webview_host_mac.mm | 15 + libcef/webwidget_host.h | 4 +- tests/cefclient/cefclient_mac.mm | 48 ++- 28 files changed, 1778 insertions(+), 356 deletions(-) create mode 100644 include/cef_application_mac.h create mode 100644 libcef/download_util.cc create mode 100644 libcef/download_util.h create mode 100644 libcef/drag_download_util.cc create mode 100644 libcef/drag_download_util.h create mode 100644 libcef/web_drag_source_mac.h create mode 100644 libcef/web_drag_source_mac.mm create mode 100644 libcef/web_drag_utils_mac.h create mode 100644 libcef/web_drag_utils_mac.mm create mode 100644 libcef/web_drop_target_mac.h create mode 100644 libcef/web_drop_target_mac.mm diff --git a/cef.gyp b/cef.gyp index 3e0999fca..68a935e30 100644 --- a/cef.gyp +++ b/cef.gyp @@ -165,6 +165,7 @@ ], }, 'sources': [ + 'include/cef_application_mac.h', 'tests/cefclient/cefclient_mac.mm', 'tests/cefclient/client_handler_mac.mm', 'tests/cefclient/resource_util_mac.mm', @@ -413,6 +414,11 @@ ], 'xcode_settings': { 'INSTALL_PATH': '@executable_path', + # The libcef_static target contains ObjC categories. Passing the -ObjC flag + # is necessary to properly load them and avoid a "selector not recognized" + # runtime error. See http://developer.apple.com/library/mac/#qa/qa1490/_index.html + # for more information. + 'OTHER_LDFLAGS': ['-Wl,-ObjC'], }, 'conditions': [ ['OS=="win"', { @@ -680,6 +686,8 @@ 'libcef/cef_time_util.h', 'libcef/drag_download_file.cc', 'libcef/drag_download_file.h', + 'libcef/drag_download_util.cc', + 'libcef/drag_download_util.h', 'libcef/dom_storage_area.cc', 'libcef/dom_storage_area.h', 'libcef/dom_storage_common.h', @@ -693,6 +701,8 @@ 'libcef/dom_event_impl.h', 'libcef/dom_node_impl.cc', 'libcef/dom_node_impl.h', + 'libcef/download_util.cc', + 'libcef/download_util.h', 'libcef/external_protocol_handler.h', 'libcef/http_header_utils.cc', 'libcef/http_header_utils.h', @@ -753,6 +763,7 @@ }], [ 'OS=="mac"', { 'sources': [ + 'include/cef_application_mac.h', 'include/internal/cef_types_mac.h', 'include/internal/cef_mac.h', 'libcef/browser_impl_mac.mm', @@ -766,6 +777,19 @@ 'libcef/external_protocol_handler_mac.mm', 'libcef/webview_host_mac.mm', 'libcef/webwidget_host_mac.mm', + 'libcef/web_drag_source_mac.h', + 'libcef/web_drag_source_mac.mm', + 'libcef/web_drag_utils_mac.h', + 'libcef/web_drag_utils_mac.mm', + 'libcef/web_drop_target_mac.h', + 'libcef/web_drop_target_mac.mm', + # Build necessary Mozilla sources + '../third_party/mozilla/NSPasteboard+Utils.h', + '../third_party/mozilla/NSPasteboard+Utils.mm', + '../third_party/mozilla/NSString+Utils.h', + '../third_party/mozilla/NSString+Utils.mm', + '../third_party/mozilla/NSURL+Utils.h', + '../third_party/mozilla/NSURL+Utils.m', ], }], [ 'OS=="linux" or OS=="freebsd" or OS=="openbsd"', { diff --git a/include/cef_application_mac.h b/include/cef_application_mac.h new file mode 100644 index 000000000..3c0b59b43 --- /dev/null +++ b/include/cef_application_mac.h @@ -0,0 +1,82 @@ +// Copyright (c) 2011 Marshall A. Greenblatt. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the name Chromium Embedded +// Framework nor the names of its contributors may be used to endorse +// or promote products derived from this software without specific prior +// written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef _CEF_APPLICATION_MAC_H +#define _CEF_APPLICATION_MAC_H +#pragma once + +#include "cef.h" + +#if defined(OS_MACOSX) && defined(__OBJC__) + +#ifdef BUILDING_CEF_SHARED + +// Use the existing CrAppProtocol definition. +#include "base/message_pump_mac.h" + +#else // BUILDING_CEF_SHARED + +#import + +// Copy of CrAppProtocol definition from base/message_pump_mac.h. +@protocol CrAppProtocol +// Must return true if -[NSApplication sendEvent:] is currently on the stack. +- (BOOL)isHandlingSendEvent; +@end + +#endif // BUILDING_CEF_SHARED + +// All CEF client applications must subclass NSApplication and implement this +// protocol. +@protocol CefAppProtocol +- (void)setHandlingSendEvent:(BOOL)handlingSendEvent; +@end + +// Controls the state of |isHandlingSendEvent| in the event loop so that it is +// reset properly. +class CefScopedSendingEvent { +public: + CefScopedSendingEvent() + : app_(static_cast*>( + [NSApplication sharedApplication])), + handling_([app_ isHandlingSendEvent]) { + [app_ setHandlingSendEvent:YES]; + } + ~CefScopedSendingEvent() { + [app_ setHandlingSendEvent:handling_]; + } + +private: + NSApplication* app_; + BOOL handling_; +}; + +#endif // defined(OS_MACOSX) && defined(__OBJC__) + +#endif // _CEF_APPLICATION_MAC_H diff --git a/libcef/browser_drag_delegate_win.cc b/libcef/browser_drag_delegate_win.cc index b544e4f36..64233e6be 100644 --- a/libcef/browser_drag_delegate_win.cc +++ b/libcef/browser_drag_delegate_win.cc @@ -9,6 +9,8 @@ #include "browser_webview_delegate.h" #include "cef_thread.h" #include "drag_download_file.h" +#include "drag_download_util.h" +#include "download_util.h" #include "web_drag_source_win.h" #include "web_drag_utils_win.h" #include "web_drop_target_win.h" @@ -17,16 +19,8 @@ #include -#include "base/file_path.h" -#include "base/message_loop.h" -#include "base/string_util.h" -#include "base/task.h" -#include "base/threading/platform_thread.h" -#include "base/threading/thread.h" -#include "base/threading/thread_restrictions.h" #include "base/utf_string_conversions.h" #include "net/base/file_stream.h" -#include "net/base/mime_util.h" #include "net/base/net_util.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebDocument.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h" @@ -71,214 +65,6 @@ LRESULT CALLBACK MsgFilterProc(int code, WPARAM wparam, LPARAM lparam) { return CallNextHookEx(msg_hook, code, wparam, lparam); } -// Parse the download metadata set in DataTransfer.setData. The metadata -// consists of a set of the following values separated by ":" -// * MIME type -// * File name -// * URL -// If the file name contains special characters, they need to be escaped -// appropriately. -// For example, we can have -// text/plain:example.txt:http://example.com/example.txt -// From chrome/browser/download/drag_download_util.cc -bool ParseDownloadMetadata(const string16& metadata, - string16* mime_type, - FilePath* file_name, - GURL* url) { - const char16 separator = L':'; - - size_t mime_type_end_pos = metadata.find(separator); - if (mime_type_end_pos == string16::npos) - return false; - - size_t file_name_end_pos = metadata.find(separator, mime_type_end_pos + 1); - if (file_name_end_pos == string16::npos) - return false; - - GURL parsed_url = GURL(metadata.substr(file_name_end_pos + 1)); - if (!parsed_url.is_valid()) - return false; - - if (mime_type) - *mime_type = metadata.substr(0, mime_type_end_pos); - if (file_name) { - string16 file_name_str = metadata.substr( - mime_type_end_pos + 1, file_name_end_pos - mime_type_end_pos - 1); -#if defined(OS_WIN) - *file_name = FilePath(file_name_str); -#else - *file_name = FilePath(UTF16ToUTF8(file_name_str)); -#endif - } - if (url) - *url = parsed_url; - - return true; -} - -#if defined(OS_WIN) -// Returns whether the specified extension is automatically integrated into the -// windows shell. -// From chrome/browser/download/download_util.cc -bool IsShellIntegratedExtension(const string16& extension) { - string16 extension_lower = StringToLowerASCII(extension); - - static const wchar_t* const integrated_extensions[] = { - // See . - L"local", - // Right-clicking on shortcuts can be magical. - L"lnk", - }; - - for (int i = 0; i < arraysize(integrated_extensions); ++i) { - if (extension_lower == integrated_extensions[i]) - return true; - } - - // See . - // That vulnerability report is not exactly on point, but files become magical - // if their end in a CLSID. Here we block extensions that look like CLSIDs. - if (extension_lower.size() > 0 && extension_lower.at(0) == L'{' && - extension_lower.at(extension_lower.length() - 1) == L'}') - return true; - - return false; -} - -// Returns whether the specified file name is a reserved name on windows. -// This includes names like "com2.zip" (which correspond to devices) and -// desktop.ini and thumbs.db which have special meaning to the windows shell. -// From chrome/browser/download/download_util.cc -bool IsReservedName(const string16& filename) { - // This list is taken from the MSDN article "Naming a file" - // http://msdn2.microsoft.com/en-us/library/aa365247(VS.85).aspx - // I also added clock$ because GetSaveFileName seems to consider it as a - // reserved name too. - static const wchar_t* const known_devices[] = { - L"con", L"prn", L"aux", L"nul", L"com1", L"com2", L"com3", L"com4", L"com5", - L"com6", L"com7", L"com8", L"com9", L"lpt1", L"lpt2", L"lpt3", L"lpt4", - L"lpt5", L"lpt6", L"lpt7", L"lpt8", L"lpt9", L"clock$" - }; - string16 filename_lower = StringToLowerASCII(filename); - - for (int i = 0; i < arraysize(known_devices); ++i) { - // Exact match. - if (filename_lower == known_devices[i]) - return true; - // Starts with "DEVICE.". - if (filename_lower.find(string16(known_devices[i]) + L".") == 0) - return true; - } - - static const wchar_t* const magic_names[] = { - // These file names are used by the "Customize folder" feature of the shell. - L"desktop.ini", - L"thumbs.db", - }; - - for (int i = 0; i < arraysize(magic_names); ++i) { - if (filename_lower == magic_names[i]) - return true; - } - - return false; -} -#endif // OS_WIN - -// Create an extension based on the file name and mime type. -// From chrome/browser/download/download_util.cc -void GenerateExtension(const FilePath& file_name, - const std::string& mime_type, - FilePath::StringType* generated_extension) { - // We're worried about two things here: - // - // 1) Usability. If the site fails to provide a file extension, we want to - // guess a reasonable file extension based on the content type. - // - // 2) Shell integration. Some file extensions automatically integrate with - // the shell. We block these extensions to prevent a malicious web site - // from integrating with the user's shell. - - // See if our file name already contains an extension. - FilePath::StringType extension = file_name.Extension(); - if (!extension.empty()) - extension.erase(extension.begin()); // Erase preceding '.'. - -#if defined(OS_WIN) - static const FilePath::CharType default_extension[] = - FILE_PATH_LITERAL("download"); - - // Rename shell-integrated extensions. - if (IsShellIntegratedExtension(extension)) - extension.assign(default_extension); -#endif - - if (extension.empty()) { - // The GetPreferredExtensionForMimeType call will end up going to disk. Do - // this on another thread to avoid slowing the IO thread. - // http://crbug.com/61827 - base::ThreadRestrictions::ScopedAllowIO allow_io; - net::GetPreferredExtensionForMimeType(mime_type, &extension); - } - - generated_extension->swap(extension); -} - -// Used to make sure we have a safe file extension and filename for a -// download. |file_name| can either be just the file name or it can be a -// full path to a file. -// From chrome/browser/download/download_util.cc -void GenerateSafeFileName(const std::string& mime_type, FilePath* file_name) { - // Make sure we get the right file extension - FilePath::StringType extension; - GenerateExtension(*file_name, mime_type, &extension); - *file_name = file_name->ReplaceExtension(extension); - -#if defined(OS_WIN) - // Prepend "_" to the file name if it's a reserved name - FilePath::StringType leaf_name = file_name->BaseName().value(); - DCHECK(!leaf_name.empty()); - if (IsReservedName(leaf_name)) { - leaf_name = FilePath::StringType(FILE_PATH_LITERAL("_")) + leaf_name; - *file_name = file_name->DirName(); - if (file_name->value() == FilePath::kCurrentDirectory) { - *file_name = FilePath(leaf_name); - } else { - *file_name = file_name->Append(leaf_name); - } - } -#endif -} - -// Create a file name based on the response from the server. -// From chrome/browser/download/download_util.cc -void GenerateFileName(const GURL& url, - const std::string& content_disposition, - const std::string& referrer_charset, - const std::string& mime_type, - FilePath* generated_name) { - string16 new_name = net::GetSuggestedFilename(GURL(url), - content_disposition, - referrer_charset, - "", - string16(L"download")); - - // TODO(evan): this code is totally wrong -- we should just generate - // Unicode filenames and do all this encoding switching at the end. - // However, I'm just shuffling wrong code around, at least not adding - // to it. -#if defined(OS_WIN) - *generated_name = FilePath(new_name); -#else - *generated_name = FilePath( - base::SysWideToNativeMB(UTF16ToWide(new_name))); -#endif - - DCHECK(!generated_name->empty()); - - GenerateSafeFileName(mime_type, generated_name); -} - } // namespace class DragDropThread : public base::Thread { @@ -406,21 +192,22 @@ void BrowserDragDelegate::PrepareDragForDownload( string16 mime_type; FilePath file_name; GURL download_url; - if (!ParseDownloadMetadata(drop_data.download_metadata, - &mime_type, - &file_name, - &download_url)) + if (!drag_download_util::ParseDownloadMetadata(drop_data.download_metadata, + &mime_type, + &file_name, + &download_url)) return; // Generate the download filename. std::string content_disposition = "attachment; filename=" + UTF16ToUTF8(file_name.value()); FilePath generated_file_name; - GenerateFileName(download_url, - content_disposition, - std::string(), - UTF16ToUTF8(mime_type), - &generated_file_name); + download_util::GenerateFileName(download_url, + content_disposition, + std::string(), + UTF16ToUTF8(mime_type), + std::string(), + &generated_file_name); // Provide the data as file (CF_HDROP). A temporary download file with the // Zone.Identifier ADS (Alternate Data Stream) attached will be created. diff --git a/libcef/browser_impl.cc b/libcef/browser_impl.cc index 810b895e5..fb0958060 100644 --- a/libcef/browser_impl.cc +++ b/libcef/browser_impl.cc @@ -700,6 +700,8 @@ void CefBrowserImpl::UIT_DestroyBrowser() } } } + + UIT_GetWebViewDelegate()->RevokeDragDrop(); #else // Call OnBeforeClose() here for platforms that don't support modal dialogs. if (client_.get()) { @@ -711,8 +713,6 @@ void CefBrowserImpl::UIT_DestroyBrowser() } #endif - UIT_GetWebViewDelegate()->RevokeDragDrop(); - // If the current browser window is a dev tools client then disconnect from // the agent and destroy the client before destroying the window. UIT_DestroyDevToolsClient(); diff --git a/libcef/browser_impl_gtk.cc b/libcef/browser_impl_gtk.cc index 3a9ba5339..5a8b91761 100644 --- a/libcef/browser_impl_gtk.cc +++ b/libcef/browser_impl_gtk.cc @@ -59,7 +59,6 @@ void CefBrowserImpl::UIT_CreateBrowser(const CefString& url) WebViewHost::Create(window_info_.m_ParentWidget, gfx::Rect(), delegate_.get(), NULL, dev_tools_agent_.get(), prefs)); - delegate_->RegisterDragDrop(); if (!settings_.developer_tools_disabled) dev_tools_agent_->SetWebView(webviewhost_->webview()); diff --git a/libcef/browser_impl_mac.mm b/libcef/browser_impl_mac.mm index 08449e311..757bf8091 100644 --- a/libcef/browser_impl_mac.mm +++ b/libcef/browser_impl_mac.mm @@ -86,7 +86,6 @@ void CefBrowserImpl::UIT_CreateBrowser(const CefString& url) webviewhost_.reset( WebViewHost::Create(parentView, contentRect, delegate_.get(), NULL, dev_tools_agent_.get(), prefs)); - delegate_->RegisterDragDrop(); if (!settings_.developer_tools_disabled) dev_tools_agent_->SetWebView(webviewhost_->webview()); diff --git a/libcef/browser_webview_delegate.cc b/libcef/browser_webview_delegate.cc index d1c0e8ed8..5c54f5a5f 100644 --- a/libcef/browser_webview_delegate.cc +++ b/libcef/browser_webview_delegate.cc @@ -34,12 +34,10 @@ #include "third_party/WebKit/Source/WebKit/chromium/public/WebData.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebDataSource.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebDocument.h" -#include "third_party/WebKit/Source/WebKit/chromium/public/WebDragData.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebFileError.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebFileSystemCallbacks.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebHistoryItem.h" -#include "third_party/WebKit/Source/WebKit/chromium/public/WebImage.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebKit.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebKitClient.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebNode.h" @@ -61,7 +59,6 @@ #include "webkit/appcache/web_application_cache_host_impl.h" #include "webkit/glue/glue_serialize.h" #include "webkit/glue/media/video_renderer_impl.h" -#include "webkit/glue/webdropdata.h" #include "webkit/glue/webpreferences.h" #include "webkit/glue/webkit_glue.h" #include "webkit/glue/webmediaplayer_impl.h" @@ -71,7 +68,6 @@ #include "webkit/plugins/npapi/webplugin_impl.h" #if defined(OS_WIN) -// TODO(port): make these files work everywhere. #include "browser_drag_delegate_win.h" #include "web_drop_target_win.h" #endif @@ -84,8 +80,6 @@ using WebKit::WebContextMenuData; using WebKit::WebCookieJar; using WebKit::WebData; using WebKit::WebDataSource; -using WebKit::WebDragData; -using WebKit::WebDragOperationsMask; using WebKit::WebEditingAction; using WebKit::WebFileChooserParams; using WebKit::WebFileSystem; @@ -93,7 +87,6 @@ using WebKit::WebFileSystemCallbacks; using WebKit::WebFormElement; using WebKit::WebFrame; using WebKit::WebHistoryItem; -using WebKit::WebImage; using WebKit::WebMediaPlayer; using WebKit::WebMediaPlayerClient; using WebKit::WebNavigationType; @@ -446,27 +439,6 @@ void BrowserWebViewDelegate::setToolTipText( GetWidgetHost()->SetTooltipText(tooltipStr); } -void BrowserWebViewDelegate::startDragging( - const WebDragData& data, - WebDragOperationsMask mask, - const WebImage& image, - const WebPoint& image_offset) { -#if defined(OS_WIN) - // Dragging is not supported when window rendering is disabled. - if (browser_->IsWindowRenderingDisabled()) { - EndDragging(); - return; - } - - drag_delegate_ = new BrowserDragDelegate(this); - drag_delegate_->StartDragging(WebDropData(data), mask, image.getSkBitmap(), - image_offset); -#else - // TODO(port): Support drag and drop. - EndDragging(); -#endif -} - void BrowserWebViewDelegate::focusNext() { CefRefPtr client = browser_->GetClient(); if (client.get()) { @@ -986,22 +958,6 @@ void BrowserWebViewDelegate::SetSelectTrailingWhitespaceEnabled(bool enabled) { // allows both. } -void BrowserWebViewDelegate::RegisterDragDrop() { -#if defined(OS_WIN) - // TODO(port): add me once drag and drop works. - DCHECK(!drop_target_); - drop_target_ = new WebDropTarget(browser_->UIT_GetWebViewWndHandle(), - browser_->UIT_GetWebView()); -#endif -} - -void BrowserWebViewDelegate::RevokeDragDrop() { -#if defined(OS_WIN) - if (drop_target_.get()) - ::RevokeDragDrop(browser_->UIT_GetWebViewWndHandle()); -#endif -} - void BrowserWebViewDelegate::SetCustomPolicyDelegate(bool is_custom, bool is_permissive) { policy_delegate_enabled_ = is_custom; @@ -1062,13 +1018,6 @@ WebWidgetHost* BrowserWebViewDelegate::GetWidgetHost() { return NULL; } -void BrowserWebViewDelegate::EndDragging() { - browser_->UIT_GetWebView()->dragSourceSystemDragEnded(); -#if defined(OS_WIN) - drag_delegate_ = NULL; -#endif -} - void BrowserWebViewDelegate::UpdateForCommittedLoad(WebFrame* frame, bool is_new_navigation) { // Code duplicated from RenderView::DidCommitLoadForFrame. diff --git a/libcef/browser_webview_delegate.h b/libcef/browser_webview_delegate.h index 825e0a727..fb4a83244 100644 --- a/libcef/browser_webview_delegate.h +++ b/libcef/browser_webview_delegate.h @@ -214,6 +214,13 @@ class BrowserWebViewDelegate : public WebKit::WebViewClient, // Additional accessors #if defined(OS_WIN) + // Sets the webview as a drop target. + void RegisterDragDrop(); + void RevokeDragDrop(); + + // Called after dragging has finished. + void EndDragging(); + BrowserDragDelegate* drag_delegate() { return drag_delegate_.get(); } WebDropTarget* drop_target() { return drop_target_.get(); } #endif @@ -222,12 +229,6 @@ class BrowserWebViewDelegate : public WebKit::WebViewClient, pending_extra_data_.reset(extra_data); } - // Sets the webview as a drop target. - void RegisterDragDrop(); - void RevokeDragDrop(); - - void ResetDragDrop(); - void SetCustomPolicyDelegate(bool is_custom, bool is_permissive); void WaitForPolicyDelegate(); @@ -255,9 +256,6 @@ class BrowserWebViewDelegate : public WebKit::WebViewClient, void ClosePopupMenu(); #endif - // Called after dragging has finished. - void EndDragging(); - protected: // Default handling of JavaScript messages. void ShowJavaScriptAlert(WebKit::WebFrame* webframe, diff --git a/libcef/browser_webview_delegate_gtk.cc b/libcef/browser_webview_delegate_gtk.cc index bdc58ffbd..008a157e3 100644 --- a/libcef/browser_webview_delegate_gtk.cc +++ b/libcef/browser_webview_delegate_gtk.cc @@ -177,6 +177,15 @@ WebRect BrowserWebViewDelegate::windowResizerRect() { return WebRect(); } +void BrowserWebViewDelegate::startDragging( + const WebDragData& data, + WebDragOperationsMask mask, + const WebImage& image, + const WebPoint& image_offset) { + // TODO(port): Support drag and drop. + browser_->UIT_GetWebView()->dragSourceSystemDragEnded(); +} + void BrowserWebViewDelegate::runModal() { NOTIMPLEMENTED(); } diff --git a/libcef/browser_webview_delegate_mac.mm b/libcef/browser_webview_delegate_mac.mm index 3e52f42c5..fcc6fd32c 100644 --- a/libcef/browser_webview_delegate_mac.mm +++ b/libcef/browser_webview_delegate_mac.mm @@ -3,23 +3,35 @@ // found in the LICENSE file. #include "browser_webview_delegate.h" +#import "browser_webview_mac.h" #include "browser_impl.h" +#import "include/cef_application_mac.h" #import +#include "base/mac/mac_util.h" #include "base/sys_string_conversions.h" +#include "skia/ext/skia_utils_mac.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebCursorInfo.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebDragData.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebImage.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebPoint.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebPopupMenu.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebView.h" #include "webkit/glue/webcursor.h" +#include "webkit/glue/webdropdata.h" #include "webkit/plugins/npapi/plugin_list.h" #include "webkit/plugins/npapi/webplugin_delegate_impl.h" #include "webkit/glue/webmenurunner_mac.h" using WebKit::WebCursorInfo; +using WebKit::WebDragData; +using WebKit::WebDragOperationsMask; using WebKit::WebExternalPopupMenu; using WebKit::WebExternalPopupMenuClient; +using WebKit::WebImage; using WebKit::WebNavigationPolicy; +using WebKit::WebPoint; using WebKit::WebPopupMenuInfo; using WebKit::WebRect; using WebKit::WebWidget; @@ -109,6 +121,39 @@ WebRect BrowserWebViewDelegate::windowResizerRect() { return gfx::Rect(NSRectToCGRect(resize_rect)); } +void BrowserWebViewDelegate::startDragging(const WebDragData& data, + WebDragOperationsMask mask, + const WebImage& image, + const WebPoint& image_offset) { + WebWidgetHost* host = GetWidgetHost(); + if (!host) + return; + + BrowserWebView *view = static_cast(host->view_handle()); + if (!view) + return; + + // By allowing nested tasks, the code below also allows Close(), + // which would deallocate |this|. The same problem can occur while + // processing -sendEvent:, so Close() is deferred in that case. + // Drags from web content do not come via -sendEvent:, this sets the + // same flag -sendEvent: would. + CefScopedSendingEvent sendingEventScoper; + + // The drag invokes a nested event loop, arrange to continue + // processing events. + MessageLoop::ScopedNestableTaskAllower allow(MessageLoop::current()); + NSDragOperation op_mask = static_cast(mask); + const SkBitmap& bitmap = gfx::CGImageToSkBitmap(image.getCGImageRef()); + CGColorSpaceRef color_space = base::mac::GetSystemColorSpace(); + NSImage* ns_image = gfx::SkBitmapToNSImageWithColorSpace(bitmap, color_space); + NSPoint offset = NSPointFromCGPoint(gfx::Point(image_offset).ToCGPoint()); + [view startDragWithDropData:WebDropData(data) + dragOperationMask:op_mask + image:ns_image + offset:offset]; +} + void BrowserWebViewDelegate::runModal() { NOTIMPLEMENTED(); } diff --git a/libcef/browser_webview_delegate_win.cc b/libcef/browser_webview_delegate_win.cc index aed199752..c67ac2e58 100644 --- a/libcef/browser_webview_delegate_win.cc +++ b/libcef/browser_webview_delegate_win.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2008-2009 The Chromium Embedded Framework Authors. +// Copyright (c) 2011 The Chromium Embedded Framework Authors. // Portions copyright (c) 2006-2008 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. @@ -7,10 +7,12 @@ // as the WebViewDelegate for the BrowserWebHost. The host is expected to // have initialized a MessageLoop before these methods are called. -#include "browser_webview_delegate.h" +#include "browser_drag_delegate_win.h" #include "browser_navigation_controller.h" #include "browser_impl.h" +#include "browser_webview_delegate.h" #include "cef_context.h" +#include "web_drop_target_win.h" #include #include @@ -21,7 +23,10 @@ #include "net/base/net_errors.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebContextMenuData.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebCursorInfo.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebDragData.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebImage.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebPoint.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebRect.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebView.h" #include "ui/gfx/gdi_util.h" @@ -38,10 +43,14 @@ using webkit::npapi::WebPluginDelegateImpl; using WebKit::WebContextMenuData; using WebKit::WebCursorInfo; +using WebKit::WebDragData; +using WebKit::WebDragOperationsMask; using WebKit::WebExternalPopupMenu; using WebKit::WebExternalPopupMenuClient; using WebKit::WebFrame; +using WebKit::WebImage; using WebKit::WebNavigationPolicy; +using WebKit::WebPoint; using WebKit::WebPopupMenuInfo; using WebKit::WebRect; using WebKit::WebWidget; @@ -169,6 +178,22 @@ WebRect BrowserWebViewDelegate::windowResizerRect() { return WebRect(); } +void BrowserWebViewDelegate::startDragging( + const WebDragData& data, + WebDragOperationsMask mask, + const WebImage& image, + const WebPoint& image_offset) { + // Dragging is not supported when window rendering is disabled. + if (browser_->IsWindowRenderingDisabled()) { + EndDragging(); + return; + } + + drag_delegate_ = new BrowserDragDelegate(this); + drag_delegate_->StartDragging(WebDropData(data), mask, image.getSkBitmap(), + image_offset); +} + void BrowserWebViewDelegate::runModal() { WebWidgetHost* host = GetWidgetHost(); if (!host) @@ -535,6 +560,22 @@ end: // Private methods ------------------------------------------------------------ +void BrowserWebViewDelegate::RegisterDragDrop() { + DCHECK(!drop_target_); + drop_target_ = new WebDropTarget(browser_->UIT_GetWebViewWndHandle(), + browser_->UIT_GetWebView()); +} + +void BrowserWebViewDelegate::RevokeDragDrop() { + if (drop_target_.get()) + ::RevokeDragDrop(browser_->UIT_GetWebViewWndHandle()); +} + +void BrowserWebViewDelegate::EndDragging() { + browser_->UIT_GetWebView()->dragSourceSystemDragEnded(); + drag_delegate_ = NULL; +} + void BrowserWebViewDelegate::ShowJavaScriptAlert(WebFrame* webframe, const CefString& message) { diff --git a/libcef/browser_webview_mac.h b/libcef/browser_webview_mac.h index b61012ab5..3c65df29f 100644 --- a/libcef/browser_webview_mac.h +++ b/libcef/browser_webview_mac.h @@ -3,8 +3,12 @@ // found in the LICENSE file. #import +#include "base/memory/scoped_nsobject.h" class CefBrowserImpl; +@class WebDragSource; +@class WebDropTarget; +struct WebDropData; // A view to wrap the WebCore view and help it live in a Cocoa world. The // (rough) equivalent of Apple's WebView. @@ -13,6 +17,9 @@ class CefBrowserImpl; @private CefBrowserImpl *browser_; // weak NSTrackingArea *trackingArea_; + + scoped_nsobject dragSource_; + scoped_nsobject dropTarget_; } - (void)mouseDown:(NSEvent *)theEvent; @@ -33,6 +40,12 @@ class CefBrowserImpl; - (BOOL)isOpaque; - (void)setFrame:(NSRect)frameRect; +// Called from BrowserWebViewDelegate::startDragging() to initiate dragging. +- (void)startDragWithDropData:(const WebDropData&)dropData + dragOperationMask:(NSDragOperation)operationMask + image:(NSImage*)image + offset:(NSPoint)offset; + @property (nonatomic, assign) CefBrowserImpl *browser; @end diff --git a/libcef/browser_webview_mac.mm b/libcef/browser_webview_mac.mm index 4ef425245..b480d0b12 100644 --- a/libcef/browser_webview_mac.mm +++ b/libcef/browser_webview_mac.mm @@ -8,9 +8,12 @@ #include "browser_impl.h" #include "cef_context.h" +#import "web_drag_source_mac.h" +#import "web_drop_target_mac.h" #include "webwidget_host.h" #include "base/memory/scoped_ptr.h" +#import "third_party/mozilla/NSPasteboard+Utils.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebView.h" #include "ui/gfx/rect.h" @@ -22,13 +25,20 @@ - (id)initWithFrame:(NSRect)frame { self = [super initWithFrame:frame]; if (self) { + dropTarget_.reset([[WebDropTarget alloc] initWithWebView:self]); + + // Register the view to handle the appropriate drag types. + NSArray* types = [NSArray arrayWithObjects:NSStringPboardType, + NSHTMLPboardType, NSURLPboardType, nil]; + [self registerForDraggedTypes:types]; + trackingArea_ = - [[NSTrackingArea alloc] initWithRect:frame - options:NSTrackingMouseMoved | - NSTrackingActiveInActiveApp | - NSTrackingInVisibleRect - owner:self - userInfo:nil]; + [[NSTrackingArea alloc] initWithRect:frame + options:NSTrackingMouseMoved | + NSTrackingActiveInActiveApp | + NSTrackingInVisibleRect + owner:self + userInfo:nil]; [self addTrackingArea:trackingArea_]; } return self; @@ -170,9 +180,91 @@ - (void)setFrame:(NSRect)frameRect { [super setFrame:frameRect]; - if (browser_ && browser_->UIT_GetWebView()) - browser_->UIT_GetWebViewHost()->Resize(gfx::Rect(NSRectToCGRect(frameRect))); + if (browser_ && browser_->UIT_GetWebView()) { + browser_->UIT_GetWebViewHost()->Resize( + gfx::Rect(NSRectToCGRect(frameRect))); + } [self setNeedsDisplay:YES]; } +- (void)startDragWithDropData:(const WebDropData&)dropData + dragOperationMask:(NSDragOperation)operationMask + image:(NSImage*)image + offset:(NSPoint)offset { + dragSource_.reset([[WebDragSource alloc] + initWithWebView:self + dropData:&dropData + image:image + offset:offset + pasteboard:[NSPasteboard pasteboardWithName:NSDragPboard] + dragOperationMask:operationMask]); + [dragSource_ startDrag]; +} + +// NSPasteboardOwner methods + +- (void)pasteboard:(NSPasteboard*)sender provideDataForType:(NSString*)type { + [dragSource_ lazyWriteToPasteboard:sender + forType:type]; +} + +// NSDraggingSource methods + +// Returns what kind of drag operations are available. This is a required +// method for NSDraggingSource. +- (NSDragOperation)draggingSourceOperationMaskForLocal:(BOOL)isLocal { + if (dragSource_.get()) + return [dragSource_ draggingSourceOperationMaskForLocal:isLocal]; + // No web drag source - this is the case for dragging a file from the + // downloads manager. Default to copy operation. Note: It is desirable to + // allow the user to either move or copy, but this requires additional + // plumbing to update the download item's path once its moved. + return NSDragOperationCopy; +} + +// Called when a drag initiated in our view ends. +- (void)draggedImage:(NSImage*)anImage + endedAt:(NSPoint)screenPoint + operation:(NSDragOperation)operation { + [dragSource_ endDragAt:screenPoint operation:operation]; + + // Might as well throw out this object now. + dragSource_.reset(); +} + +// Called when a drag initiated in our view moves. +- (void)draggedImage:(NSImage*)draggedImage movedTo:(NSPoint)screenPoint { + [dragSource_ moveDragTo:screenPoint]; +} + +// Called when we're informed where a file should be dropped. +- (NSArray*)namesOfPromisedFilesDroppedAtDestination:(NSURL*)dropDest { + if (![dropDest isFileURL]) + return nil; + + NSString* file_name = [dragSource_ dragPromisedFileTo:[dropDest path]]; + if (!file_name) + return nil; + + return [NSArray arrayWithObject:file_name]; +} + +// NSDraggingDestination methods + +- (NSDragOperation)draggingEntered:(id)sender { + return [dropTarget_ draggingEntered:sender view:self]; +} + +- (void)draggingExited:(id)sender { + [dropTarget_ draggingExited:sender]; +} + +- (NSDragOperation)draggingUpdated:(id)sender { + return [dropTarget_ draggingUpdated:sender view:self]; +} + +- (BOOL)performDragOperation:(id)sender { + return [dropTarget_ performDragOperation:sender view:self]; +} + @end diff --git a/libcef/cef_process_ui_thread_mac.mm b/libcef/cef_process_ui_thread_mac.mm index 831e44b7c..929b66516 100644 --- a/libcef/cef_process_ui_thread_mac.mm +++ b/libcef/cef_process_ui_thread_mac.mm @@ -3,67 +3,22 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#import + #include "include/cef.h" +#import "include/cef_application_mac.h" #include "cef_process_ui_thread.h" #include "browser_webkit_glue.h" -#include "base/message_pump_mac.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebView.h" #include "third_party/WebKit/Source/WebKit/mac/WebCoreSupport/WebSystemInterface.h" -// CrAppProtocol implementation. -@interface CrApplication : NSApplication { - @private - BOOL handlingSendEvent_; -} -- (BOOL)isHandlingSendEvent; -@end - -@implementation CrApplication -- (BOOL)isHandlingSendEvent { - return handlingSendEvent_; -} - -- (void)sendEvent:(NSEvent*)event { - BOOL wasHandlingSendEvent = handlingSendEvent_; - handlingSendEvent_ = YES; - [super sendEvent:event]; - handlingSendEvent_ = wasHandlingSendEvent; -} -@end - -namespace { - -// Memory autorelease pool. -static NSAutoreleasePool* g_autopool = nil; - -void RunLoopObserver(CFRunLoopObserverRef observer, CFRunLoopActivity activity, - void* info) -{ - CefDoMessageLoopWork(); -} - -} // namespace - void CefProcessUIThread::PlatformInit() { - // Initialize the CrApplication instance. - [CrApplication sharedApplication]; - g_autopool = [[NSAutoreleasePool alloc] init]; + // The NSApplication instance must implement the CefAppProtocol protocol. + DCHECK([[NSApplication sharedApplication] + conformsToProtocol:@protocol(CefAppProtocol)]); InitWebCoreSystemInterface(); - // Register the run loop observer. - CFRunLoopObserverRef observer = - CFRunLoopObserverCreate(NULL, - kCFRunLoopBeforeWaiting, - YES, /* repeat */ - 0, - &RunLoopObserver, - NULL); - if (observer) { - CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, - kCFRunLoopCommonModes); - } - webkit_glue::InitializeDataPak(); // On Mac, the select popup menus are rendered by the browser. @@ -71,6 +26,4 @@ void CefProcessUIThread::PlatformInit() { } void CefProcessUIThread::PlatformCleanUp() { - [g_autopool release]; } - diff --git a/libcef/download_util.cc b/libcef/download_util.cc new file mode 100644 index 000000000..3b5a6821b --- /dev/null +++ b/libcef/download_util.cc @@ -0,0 +1,188 @@ +// Copyright (c) 2011 The Chromium Embedded Framework Authors. +// Portions copyright (c) 2011 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 "download_util.h" + +#include "base/file_path.h" +#include "base/string16.h" +#include "base/string_util.h" +#include "base/sys_string_conversions.h" +#include "base/utf_string_conversions.h" +#include "base/threading/thread.h" +#include "base/threading/thread_restrictions.h" +#include "googleurl/src/gurl.h" +#include "net/base/mime_util.h" +#include "net/base/net_util.h" + +namespace { + +#if defined(OS_WIN) +// Returns whether the specified extension is automatically integrated into the +// windows shell. +// From chrome/browser/download/download_util.cc +bool IsShellIntegratedExtension(const string16& extension) { + string16 extension_lower = StringToLowerASCII(extension); + + static const wchar_t* const integrated_extensions[] = { + // See . + L"local", + // Right-clicking on shortcuts can be magical. + L"lnk", + }; + + for (int i = 0; i < arraysize(integrated_extensions); ++i) { + if (extension_lower == integrated_extensions[i]) + return true; + } + + // See . + // That vulnerability report is not exactly on point, but files become magical + // if their end in a CLSID. Here we block extensions that look like CLSIDs. + if (extension_lower.size() > 0 && extension_lower.at(0) == L'{' && + extension_lower.at(extension_lower.length() - 1) == L'}') + return true; + + return false; +} + +// Returns whether the specified file name is a reserved name on windows. +// This includes names like "com2.zip" (which correspond to devices) and +// desktop.ini and thumbs.db which have special meaning to the windows shell. +// From chrome/browser/download/download_util.cc +bool IsReservedName(const string16& filename) { + // This list is taken from the MSDN article "Naming a file" + // http://msdn2.microsoft.com/en-us/library/aa365247(VS.85).aspx + // I also added clock$ because GetSaveFileName seems to consider it as a + // reserved name too. + static const wchar_t* const known_devices[] = { + L"con", L"prn", L"aux", L"nul", L"com1", L"com2", L"com3", L"com4", L"com5", + L"com6", L"com7", L"com8", L"com9", L"lpt1", L"lpt2", L"lpt3", L"lpt4", + L"lpt5", L"lpt6", L"lpt7", L"lpt8", L"lpt9", L"clock$" + }; + string16 filename_lower = StringToLowerASCII(filename); + + for (int i = 0; i < arraysize(known_devices); ++i) { + // Exact match. + if (filename_lower == known_devices[i]) + return true; + // Starts with "DEVICE.". + if (filename_lower.find(string16(known_devices[i]) + L".") == 0) + return true; + } + + static const wchar_t* const magic_names[] = { + // These file names are used by the "Customize folder" feature of the shell. + L"desktop.ini", + L"thumbs.db", + }; + + for (int i = 0; i < arraysize(magic_names); ++i) { + if (filename_lower == magic_names[i]) + return true; + } + + return false; +} +#endif // OS_WIN + +// Create an extension based on the file name and mime type. +// From chrome/browser/download/download_util.cc +void GenerateExtension(const FilePath& file_name, + const std::string& mime_type, + FilePath::StringType* generated_extension) { + // We're worried about two things here: + // + // 1) Usability. If the site fails to provide a file extension, we want to + // guess a reasonable file extension based on the content type. + // + // 2) Shell integration. Some file extensions automatically integrate with + // the shell. We block these extensions to prevent a malicious web site + // from integrating with the user's shell. + + // See if our file name already contains an extension. + FilePath::StringType extension = file_name.Extension(); + if (!extension.empty()) + extension.erase(extension.begin()); // Erase preceding '.'. + +#if defined(OS_WIN) + static const FilePath::CharType default_extension[] = + FILE_PATH_LITERAL("download"); + + // Rename shell-integrated extensions. + if (IsShellIntegratedExtension(extension)) + extension.assign(default_extension); +#endif + + if (extension.empty()) { + // The GetPreferredExtensionForMimeType call will end up going to disk. Do + // this on another thread to avoid slowing the IO thread. + // http://crbug.com/61827 + base::ThreadRestrictions::ScopedAllowIO allow_io; + net::GetPreferredExtensionForMimeType(mime_type, &extension); + } + + generated_extension->swap(extension); +} + +// Used to make sure we have a safe file extension and filename for a +// download. |file_name| can either be just the file name or it can be a +// full path to a file. +// From chrome/browser/download/download_util.cc +void GenerateSafeFileName(const std::string& mime_type, FilePath* file_name) { + // Make sure we get the right file extension + FilePath::StringType extension; + GenerateExtension(*file_name, mime_type, &extension); + *file_name = file_name->ReplaceExtension(extension); + +#if defined(OS_WIN) + // Prepend "_" to the file name if it's a reserved name + FilePath::StringType leaf_name = file_name->BaseName().value(); + DCHECK(!leaf_name.empty()); + if (IsReservedName(leaf_name)) { + leaf_name = FilePath::StringType(FILE_PATH_LITERAL("_")) + leaf_name; + *file_name = file_name->DirName(); + if (file_name->value() == FilePath::kCurrentDirectory) { + *file_name = FilePath(leaf_name); + } else { + *file_name = file_name->Append(leaf_name); + } + } +#endif +} + +} // namespace + +namespace download_util { + +// Create a file name based on the response from the server. +// From chrome/browser/download/download_util.cc +void GenerateFileName(const GURL& url, + const std::string& content_disposition, + const std::string& referrer_charset, + const std::string& mime_type, + const std::string& suggested_name, + FilePath* generated_name) { + string16 new_name = net::GetSuggestedFilename(GURL(url), + content_disposition, + referrer_charset, + suggested_name, + ASCIIToUTF16("download")); + + // TODO(evan): this code is totally wrong -- we should just generate + // Unicode filenames and do all this encoding switching at the end. + // However, I'm just shuffling wrong code around, at least not adding + // to it. +#if defined(OS_WIN) + *generated_name = FilePath(new_name); +#else + *generated_name = FilePath(base::SysWideToNativeMB(UTF16ToWide(new_name))); +#endif + + DCHECK(!generated_name->empty()); + + GenerateSafeFileName(mime_type, generated_name); +} + +} // namespace download_util diff --git a/libcef/download_util.h b/libcef/download_util.h new file mode 100644 index 000000000..a9701eaa2 --- /dev/null +++ b/libcef/download_util.h @@ -0,0 +1,27 @@ +// Copyright (c) 2011 The Chromium Embedded Framework Authors. +// Portions copyright (c) 2011 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 _DOWNLOAD_UTIL_H +#define _DOWNLOAD_UTIL_H +#pragma once + +#include + +class GURL; +class FilePath; + +namespace download_util { + +// Create a file name based on the response from the server. +void GenerateFileName(const GURL& url, + const std::string& content_disposition, + const std::string& referrer_charset, + const std::string& mime_type, + const std::string& suggested_name, + FilePath* generated_name); + +} // namespace download_util + +#endif // _DOWNLOAD_UTIL_H diff --git a/libcef/drag_download_util.cc b/libcef/drag_download_util.cc new file mode 100644 index 000000000..ae7168611 --- /dev/null +++ b/libcef/drag_download_util.cc @@ -0,0 +1,113 @@ +// Copyright (c) 2011 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 "cef_thread.h" +#include "drag_download_util.h" + +#include "base/string_util.h" +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/memory/scoped_ptr.h" +#include "base/task.h" +#include "base/string_number_conversions.h" +#include "base/utf_string_conversions.h" +#include "googleurl/src/gurl.h" +#include "net/base/file_stream.h" +#include "net/base/net_errors.h" + +using net::FileStream; + +namespace drag_download_util { + +bool ParseDownloadMetadata(const string16& metadata, + string16* mime_type, + FilePath* file_name, + GURL* url) { + const char16 separator = L':'; + + size_t mime_type_end_pos = metadata.find(separator); + if (mime_type_end_pos == string16::npos) + return false; + + size_t file_name_end_pos = metadata.find(separator, mime_type_end_pos + 1); + if (file_name_end_pos == string16::npos) + return false; + + GURL parsed_url = GURL(metadata.substr(file_name_end_pos + 1)); + if (!parsed_url.is_valid()) + return false; + + if (mime_type) + *mime_type = metadata.substr(0, mime_type_end_pos); + if (file_name) { + string16 file_name_str = metadata.substr( + mime_type_end_pos + 1, file_name_end_pos - mime_type_end_pos - 1); +#if defined(OS_WIN) + *file_name = FilePath(file_name_str); +#else + *file_name = FilePath(UTF16ToUTF8(file_name_str)); +#endif + } + if (url) + *url = parsed_url; + + return true; +} + +FileStream* CreateFileStreamForDrop(FilePath* file_path) { + DCHECK(file_path && !file_path->empty()); + + scoped_ptr file_stream(new FileStream); + const int kMaxSeq = 99; + for (int seq = 0; seq <= kMaxSeq; seq++) { + FilePath new_file_path; + if (seq == 0) { + new_file_path = *file_path; + } else { + #if defined(OS_WIN) + string16 suffix = ASCIIToUTF16("-") + base::IntToString16(seq); + #else + std::string suffix = std::string("-") + base::IntToString(seq); + #endif + new_file_path = file_path->InsertBeforeExtension(suffix); + } + + // Explicitly (and redundantly check) for file -- despite the fact that our + // open won't overwrite -- just to avoid log spew. + if (!file_util::PathExists(new_file_path) && + file_stream->Open(new_file_path, base::PLATFORM_FILE_CREATE | + base::PLATFORM_FILE_WRITE) == net::OK) { + *file_path = new_file_path; + return file_stream.release(); + } + } + + return NULL; +} + +PromiseFileFinalizer::PromiseFileFinalizer( + DragDownloadFile* drag_file_downloader) + : drag_file_downloader_(drag_file_downloader) { +} + +PromiseFileFinalizer::~PromiseFileFinalizer() {} + +void PromiseFileFinalizer::Cleanup() { + if (drag_file_downloader_.get()) + drag_file_downloader_ = NULL; +} + +void PromiseFileFinalizer::OnDownloadCompleted(const FilePath& file_path) { + CefThread::PostTask( + CefThread::UI, FROM_HERE, + NewRunnableMethod(this, &PromiseFileFinalizer::Cleanup)); +} + +void PromiseFileFinalizer::OnDownloadAborted() { + CefThread::PostTask( + CefThread::UI, FROM_HERE, + NewRunnableMethod(this, &PromiseFileFinalizer::Cleanup)); +} + +} // namespace drag_download_util diff --git a/libcef/drag_download_util.h b/libcef/drag_download_util.h new file mode 100644 index 000000000..86450e575 --- /dev/null +++ b/libcef/drag_download_util.h @@ -0,0 +1,63 @@ +// Copyright (c) 2011 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 _DRAG_DOWNLOAD_UTIL_H +#define _DRAG_DOWNLOAD_UTIL_H +#pragma once + +#include "drag_download_file.h" + +#include "base/basictypes.h" +#include "base/memory/ref_counted.h" +#include "base/string16.h" +#include "ui/base/dragdrop/download_file_interface.h" + +class FilePath; +class GURL; +namespace net { +class FileStream; +} + +namespace drag_download_util { + +// Parse the download metadata set in DataTransfer.setData. The metadata +// consists of a set of the following values separated by ":" +// * MIME type +// * File name +// * URL +// If the file name contains special characters, they need to be escaped +// appropriately. +// For example, we can have +// text/plain:example.txt:http://example.com/example.txt +bool ParseDownloadMetadata(const string16& metadata, + string16* mime_type, + FilePath* file_name, + GURL* url); + +// Create a new file at the specified path. If the file already exists, try to +// insert the sequential unifier to produce a new file, like foo-01.txt. +// Return a FileStream if successful. +net::FileStream* CreateFileStreamForDrop(FilePath* file_path); + +// Implementation of DownloadFileObserver to finalize the download process. +class PromiseFileFinalizer : public ui::DownloadFileObserver { + public: + explicit PromiseFileFinalizer(DragDownloadFile* drag_file_downloader); + virtual ~PromiseFileFinalizer(); + + // DownloadFileObserver methods. + virtual void OnDownloadCompleted(const FilePath& file_path); + virtual void OnDownloadAborted(); + + private: + void Cleanup(); + + scoped_refptr drag_file_downloader_; + + DISALLOW_COPY_AND_ASSIGN(PromiseFileFinalizer); +}; + +} // namespace drag_download_util + +#endif // _DRAG_DOWNLOAD_UTIL_H diff --git a/libcef/web_drag_source_mac.h b/libcef/web_drag_source_mac.h new file mode 100644 index 000000000..3db96b990 --- /dev/null +++ b/libcef/web_drag_source_mac.h @@ -0,0 +1,82 @@ +// Copyright (c) 2011 The Chromium Embedded Framework Authors. +// Portions copyright (c) 2011 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. + +#import + +#include "base/file_path.h" +#include "base/memory/scoped_nsobject.h" +#include "base/memory/scoped_ptr.h" +#include "googleurl/src/gurl.h" +#include "ui/gfx/native_widget_types.h" + +struct WebDropData; +@class BrowserWebView; + +// A class that handles tracking and event processing for a drag and drop +// originating from the content area. +@interface WebDragSource : NSObject { + @private + // Our web view. Weak reference (owns or co-owns us). + BrowserWebView* view_; + + // Our drop data. Should only be initialized once. + scoped_ptr dropData_; + + // The image to show as drag image. Can be nil. + scoped_nsobject dragImage_; + + // The offset to draw |dragImage_| at. + NSPoint imageOffset_; + + // Our pasteboard. + scoped_nsobject pasteboard_; + + // A mask of the allowed drag operations. + NSDragOperation dragOperationMask_; + + // The file name to be saved to for a drag-out download. + FilePath downloadFileName_; + + // The URL to download from for a drag-out download. + GURL downloadURL_; +} + +// Initialize a WebDragSource object for a drag (originating on the given +// BrowserWebView and with the given dropData and pboard). Fill the pasteboard +// with data types appropriate for dropData. +- (id)initWithWebView:(BrowserWebView*)view + dropData:(const WebDropData*)dropData + image:(NSImage*)image + offset:(NSPoint)offset + pasteboard:(NSPasteboard*)pboard + dragOperationMask:(NSDragOperation)dragOperationMask; + +// Returns a mask of the allowed drag operations. +- (NSDragOperation)draggingSourceOperationMaskForLocal:(BOOL)isLocal; + +// Call when asked to do a lazy write to the pasteboard; hook up to +// -pasteboard:provideDataForType: (on the BrowserWebView). +- (void)lazyWriteToPasteboard:(NSPasteboard*)pboard + forType:(NSString*)type; + +// Start the drag (on the originally provided BrowserWebView); can do this right +// after -initWithContentsView:.... +- (void)startDrag; + +// End the drag and clear the pasteboard; hook up to +// -draggedImage:endedAt:operation:. +- (void)endDragAt:(NSPoint)screenPoint + operation:(NSDragOperation)operation; + +// Drag moved; hook up to -draggedImage:movedTo:. +- (void)moveDragTo:(NSPoint)screenPoint; + +// Call to drag a promised file to the given path (should be called before +// -endDragAt:...); hook up to -namesOfPromisedFilesDroppedAtDestination:. +// Returns the file name (not including path) of the file deposited (or which +// will be deposited). +- (NSString*)dragPromisedFileTo:(NSString*)path; + +@end diff --git a/libcef/web_drag_source_mac.mm b/libcef/web_drag_source_mac.mm new file mode 100644 index 000000000..913fc1de8 --- /dev/null +++ b/libcef/web_drag_source_mac.mm @@ -0,0 +1,456 @@ +// Copyright (c) 2011 The Chromium Embedded Framework Authors. +// Portions copyright (c) 2011 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 "browser_impl.h" +#import "browser_webview_mac.h" +#include "drag_download_util.h" +#include "download_util.h" +#import "web_drag_source_mac.h" + +#include + +#include "base/file_path.h" +#include "base/string_util.h" +#include "base/sys_string_conversions.h" +#include "base/task.h" +#include "base/threading/thread.h" +#include "base/threading/thread_restrictions.h" +#include "base/utf_string_conversions.h" +#include "net/base/file_stream.h" +#include "net/base/net_util.h" +#import "third_party/mozilla/NSPasteboard+Utils.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebDocument.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebPoint.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebView.h" +#include "ui/gfx/mac/nsimage_cache.h" +#include "webkit/glue/webdropdata.h" + +using base::SysNSStringToUTF8; +using base::SysUTF8ToNSString; +using base::SysUTF16ToNSString; +using net::FileStream; +using WebKit::WebDragOperationNone; +using WebKit::WebView; + +namespace { + +// An unofficial standard pasteboard title type to be provided alongside the +// |NSURLPboardType|. +NSString* const kNSURLTitlePboardType = @"public.url-name"; + +// Converts a string16 into a FilePath. Use this method instead of +// -[NSString fileSystemRepresentation] to prevent exceptions from being thrown. +// See http://crbug.com/78782 for more info. +FilePath FilePathFromFilename(const string16& filename) { + NSString* str = SysUTF16ToNSString(filename); + char buf[MAXPATHLEN]; + if (![str getFileSystemRepresentation:buf maxLength:sizeof(buf)]) + return FilePath(); + return FilePath(buf); +} + +// Returns a filename appropriate for the drop data +// TODO(viettrungluu): Refactor to make it common across platforms, +// and move it somewhere sensible. +FilePath GetFileNameFromDragData(const WebDropData& drop_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(FilePathFromFilename(drop_data.file_description_filename)); + file_name = file_name.BaseName().RemoveExtension(); + + if (file_name.empty()) { + // Retrieve the name from the URL. + string16 suggested_filename = + net::GetSuggestedFilename(drop_data.url, "", "", "", string16()); + file_name = FilePathFromFilename(suggested_filename); + } + + file_name = file_name.ReplaceExtension(UTF16ToUTF8(drop_data.file_extension)); + + return file_name; +} + +// This class's sole task is to write out data for a promised file; the caller +// is responsible for opening the file. +class PromiseWriterTask : public Task { + public: + // Assumes ownership of file_stream. + PromiseWriterTask(const WebDropData& drop_data, + FileStream* file_stream); + virtual ~PromiseWriterTask(); + virtual void Run(); + + private: + WebDropData drop_data_; + + // This class takes ownership of file_stream_ and will close and delete it. + scoped_ptr file_stream_; +}; + +// Takes the drop data and an open file stream (which it takes ownership of and +// will close and delete). +PromiseWriterTask::PromiseWriterTask(const WebDropData& drop_data, + FileStream* file_stream) : + drop_data_(drop_data) { + file_stream_.reset(file_stream); + DCHECK(file_stream_.get()); +} + +PromiseWriterTask::~PromiseWriterTask() { + DCHECK(file_stream_.get()); + if (file_stream_.get()) + file_stream_->Close(); +} + +void PromiseWriterTask::Run() { + CHECK(file_stream_.get()); + file_stream_->Write(drop_data_.file_contents.data(), + drop_data_.file_contents.length(), + NULL); + + // Let our destructor take care of business. +} + +} // namespace + + +@interface WebDragSource(Private) + +- (void)fillPasteboard; +- (NSImage*)dragImage; + +@end // @interface WebDragSource(Private) + + +@implementation WebDragSource + +- (id)initWithWebView:(BrowserWebView*)view + dropData:(const WebDropData*)dropData + image:(NSImage*)image + offset:(NSPoint)offset + pasteboard:(NSPasteboard*)pboard + dragOperationMask:(NSDragOperation)dragOperationMask { + if ((self = [super init])) { + view_ = view; + DCHECK(view_); + + dropData_.reset(new WebDropData(*dropData)); + DCHECK(dropData_.get()); + + dragImage_.reset([image retain]); + imageOffset_ = offset; + + pasteboard_.reset([pboard retain]); + DCHECK(pasteboard_.get()); + + dragOperationMask_ = dragOperationMask; + + [self fillPasteboard]; + } + + return self; +} + +- (NSDragOperation)draggingSourceOperationMaskForLocal:(BOOL)isLocal { + return dragOperationMask_; +} + +- (void)lazyWriteToPasteboard:(NSPasteboard*)pboard forType:(NSString*)type { + // NSHTMLPboardType requires the character set to be declared. Otherwise, it + // assumes US-ASCII. Awesome. + static const string16 kHtmlHeader = + ASCIIToUTF16(""); + + // Be extra paranoid; avoid crashing. + if (!dropData_.get()) { + NOTREACHED() << "No drag-and-drop data available for lazy write."; + return; + } + + // HTML. + if ([type isEqualToString:NSHTMLPboardType]) { + DCHECK(!dropData_->text_html.empty()); + // See comment on |kHtmlHeader| above. + [pboard setString:SysUTF16ToNSString(kHtmlHeader + dropData_->text_html) + forType:NSHTMLPboardType]; + + // URL. + } else if ([type isEqualToString:NSURLPboardType]) { + DCHECK(dropData_->url.is_valid()); + NSString* urlStr = SysUTF8ToNSString(dropData_->url.spec()); + NSURL* url = [NSURL URLWithString:urlStr]; + // If NSURL creation failed, check for a badly-escaped javascript URL. + if (!url && urlStr && dropData_->url.SchemeIs("javascript")) { + NSString *escapedStr = + [urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; + url = [NSURL URLWithString:escapedStr]; + } + [url writeToPasteboard:pboard]; + // URL title. + } else if ([type isEqualToString:kNSURLTitlePboardType]) { + [pboard setString:SysUTF16ToNSString(dropData_->url_title) + forType:kNSURLTitlePboardType]; + + // File contents. + } else if ([type isEqualToString:NSFileContentsPboardType] || + [type isEqualToString:NSCreateFileContentsPboardType( + SysUTF16ToNSString(dropData_->file_extension))]) { + // TODO(viettrungluu: find something which is known to accept + // NSFileContentsPboardType to check that this actually works! + scoped_nsobject file_wrapper( + [[NSFileWrapper alloc] initRegularFileWithContents:[NSData + dataWithBytes:dropData_->file_contents.data() + length:dropData_->file_contents.length()]]); + [file_wrapper setPreferredFilename:SysUTF8ToNSString( + GetFileNameFromDragData(*dropData_).value())]; + [pboard writeFileWrapper:file_wrapper]; + + // TIFF. + } else if ([type isEqualToString:NSTIFFPboardType]) { + // TODO(viettrungluu): This is a bit odd since we rely on Cocoa to render + // our image into a TIFF. This is also suboptimal since this is all done + // synchronously. I'm not sure there's much we can easily do about it. + scoped_nsobject image( + [[NSImage alloc] initWithData:[NSData + dataWithBytes:dropData_->file_contents.data() + length:dropData_->file_contents.length()]]); + [pboard setData:[image TIFFRepresentation] forType:NSTIFFPboardType]; + + // Plain text. + } else if ([type isEqualToString:NSStringPboardType]) { + DCHECK(!dropData_->plain_text.empty()); + [pboard setString:SysUTF16ToNSString(dropData_->plain_text) + forType:NSStringPboardType]; + + // Oops! + } else { + NOTREACHED() << "Asked for a drag pasteboard type we didn't offer."; + } +} + +- (NSPoint)convertScreenPoint:(NSPoint)screenPoint { + NSPoint basePoint = [[view_ window] convertScreenToBase:screenPoint]; + return [view_ convertPoint:basePoint fromView:nil]; +} + +- (void)startDrag { + NSEvent* currentEvent = [NSApp currentEvent]; + + // Synthesize an event for dragging, since we can't be sure that + // [NSApp currentEvent] will return a valid dragging event. + NSWindow* window = [view_ window]; + NSPoint position = [window mouseLocationOutsideOfEventStream]; + NSTimeInterval eventTime = [currentEvent timestamp]; + NSEvent* dragEvent = [NSEvent mouseEventWithType:NSLeftMouseDragged + location:position + modifierFlags:NSLeftMouseDraggedMask + timestamp:eventTime + windowNumber:[window windowNumber] + context:nil + eventNumber:0 + clickCount:1 + pressure:1.0]; + + if (dragImage_) { + position.x -= imageOffset_.x; + // Deal with Cocoa's flipped coordinate system. + position.y -= [dragImage_.get() size].height - imageOffset_.y; + } + // Per kwebster, offset arg is ignored, see -_web_DragImageForElement: in + // third_party/WebKit/Source/WebKit/mac/Misc/WebNSViewExtras.m. + [window dragImage:[self dragImage] + at:position + offset:NSZeroSize + event:dragEvent + pasteboard:pasteboard_ + source:view_ + slideBack:YES]; +} + +- (void)endDragAt:(NSPoint)screenPoint + operation:(NSDragOperation)operation { + // Convert |screenPoint| to view coordinates and flip it. + NSWindow* window = [view_ window]; + NSPoint localPoint = [self convertScreenPoint:screenPoint]; + NSRect viewFrame = [window frame]; + localPoint.y = viewFrame.size.height - localPoint.y; + // Flip |screenPoint|. + NSRect screenFrame = [[window screen] frame]; + screenPoint.y = screenFrame.size.height - screenPoint.y; + + // If AppKit returns a copy and move operation, mask off the move bit + // because WebCore does not understand what it means to do both, which + // results in an assertion failure/renderer crash. + if (operation == (NSDragOperationMove | NSDragOperationCopy)) + operation &= ~NSDragOperationMove; + + // TODO: Figure out why |operation| is always NSDragOperationNone. + if (operation == NSDragOperationNone) + operation = NSDragOperationCopy; + + WebView* webview = view_.browser->UIT_GetWebView(); + + gfx::Point client(localPoint.x, localPoint.y); + gfx::Point screen(screenPoint.x, screenPoint.y); + webview->dragSourceEndedAt(client, screen, + static_cast(operation)); + + // Make sure the pasteboard owner isn't us. + [pasteboard_ declareTypes:[NSArray array] owner:nil]; + + webview->dragSourceSystemDragEnded(); +} + +- (void)moveDragTo:(NSPoint)screenPoint { + // Convert |screenPoint| to view coordinates and flip it. + NSWindow* window = [view_ window]; + NSPoint localPoint = [self convertScreenPoint:screenPoint]; + NSRect viewFrame = [window frame]; + localPoint.y = viewFrame.size.height - localPoint.y; + // Flip |screenPoint|. + NSRect screenFrame = [[window screen] frame]; + screenPoint.y = screenFrame.size.height - screenPoint.y; + + WebView* webview = view_.browser->UIT_GetWebView(); + + gfx::Point client(localPoint.x, localPoint.y); + gfx::Point screen(screenPoint.x, screenPoint.y); + webview->dragSourceMovedTo(client, screen, WebDragOperationNone); +} + +- (NSString*)dragPromisedFileTo:(NSString*)path { + // Be extra paranoid; avoid crashing. + if (!dropData_.get()) { + NOTREACHED() << "No drag-and-drop data available for promised file."; + return nil; + } + + FilePath fileName = downloadFileName_.empty() ? + GetFileNameFromDragData(*dropData_) : downloadFileName_; + FilePath filePath(SysNSStringToUTF8(path)); + filePath = filePath.Append(fileName); + + // CreateFileStreamForDrop() will call file_util::PathExists(), + // which is blocking. Since this operation is already blocking the + // UI thread on OSX, it should be reasonable to let it happen. + base::ThreadRestrictions::ScopedAllowIO allowIO; + FileStream* fileStream = + drag_download_util::CreateFileStreamForDrop(&filePath); + if (!fileStream) + return nil; + + if (downloadURL_.is_valid()) { + WebView* webview = view_.browser->UIT_GetWebView(); + const GURL& page_url = webview->mainFrame()->document().url(); + const std::string& page_encoding = + webview->mainFrame()->document().encoding().utf8(); + + scoped_refptr dragFileDownloader(new DragDownloadFile( + filePath, + linked_ptr(fileStream), + downloadURL_, + page_url, + page_encoding, + view_.browser->UIT_GetWebViewDelegate())); + + // The finalizer will take care of closing and deletion. + dragFileDownloader->Start( + new drag_download_util::PromiseFileFinalizer(dragFileDownloader)); + } else { + // The writer will take care of closing and deletion. + CefThread::PostTask(CefThread::FILE, FROM_HERE, + new PromiseWriterTask(*dropData_, fileStream)); + } + + // Once we've created the file, we should return the file name. + return SysUTF8ToNSString(filePath.BaseName().value()); + + return nil; +} + +@end // @implementation WebDragSource + + +@implementation WebDragSource (Private) + +- (void)fillPasteboard { + DCHECK(pasteboard_.get()); + + [pasteboard_ declareTypes:[NSArray array] owner:view_]; + + // HTML. + if (!dropData_->text_html.empty()) + [pasteboard_ addTypes:[NSArray arrayWithObject:NSHTMLPboardType] + owner:view_]; + + // URL (and title). + if (dropData_->url.is_valid()) + [pasteboard_ addTypes:[NSArray arrayWithObjects:NSURLPboardType, + kNSURLTitlePboardType, nil] + owner:view_]; + + // File. + if (!dropData_->file_contents.empty() || + !dropData_->download_metadata.empty()) { + NSString* fileExtension = 0; + + if (dropData_->download_metadata.empty()) { + // |dropData_->file_extension| comes with the '.', which we must strip. + fileExtension = (dropData_->file_extension.length() > 0) ? + SysUTF16ToNSString(dropData_->file_extension.substr(1)) : @""; + } else { + string16 mimeType; + FilePath fileName; + if (drag_download_util::ParseDownloadMetadata( + dropData_->download_metadata, + &mimeType, + &fileName, + &downloadURL_)) { + download_util::GenerateFileName( + downloadURL_, + std::string(), + std::string(), + UTF16ToUTF8(mimeType), + fileName.value(), + &downloadFileName_); + fileExtension = SysUTF8ToNSString(downloadFileName_.Extension()); + } + } + + if (fileExtension) { + // File contents (with and without specific type), file (HFS) promise, + // TIFF. + // TODO(viettrungluu): others? + [pasteboard_ addTypes:[NSArray arrayWithObjects: + NSFileContentsPboardType, + NSCreateFileContentsPboardType(fileExtension), + NSFilesPromisePboardType, + NSTIFFPboardType, + nil] + owner:view_]; + + // For the file promise, we need to specify the extension. + [pasteboard_ setPropertyList:[NSArray arrayWithObject:fileExtension] + forType:NSFilesPromisePboardType]; + } + } + + // Plain text. + if (!dropData_->plain_text.empty()) + [pasteboard_ addTypes:[NSArray arrayWithObject:NSStringPboardType] + owner:view_]; +} + +- (NSImage*)dragImage { + if (dragImage_) + return dragImage_; + + // Default to returning a generic image. + return gfx::GetCachedImageWithName(@"nav.pdf"); +} + +@end // @implementation WebDragSource (Private) diff --git a/libcef/web_drag_utils_mac.h b/libcef/web_drag_utils_mac.h new file mode 100644 index 000000000..2e7bbfea4 --- /dev/null +++ b/libcef/web_drag_utils_mac.h @@ -0,0 +1,37 @@ +// Copyright (c) 2011 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 _WEB_DRAG_UTILS_MAC +#define _WEB_DRAG_UTILS_MAC +#pragma once + +#import + +#include "base/basictypes.h" +#include "googleurl/src/gurl.h" + +namespace drag_util { + +// Populates the |url| and |title| with URL data in |pboard|. There may be more +// than one, but we only handle dropping the first. |url| must not be |NULL|; +// |title| is an optional parameter. Returns |YES| if URL data was obtained from +// the pasteboard, |NO| otherwise. If |convert_filenames| is |YES|, the function +// will also attempt to convert filenames in |pboard| to file URLs. +BOOL PopulateURLAndTitleFromPasteBoard(GURL* url, + string16* title, + NSPasteboard* pboard, + BOOL convert_filenames); + +// Returns the first file URL from |info|, if there is one. If |info| doesn't +// contain any file URLs, an empty |GURL| is returned. +GURL GetFileURLFromDropData(id info); + +// Determines whether the given drag and drop operation contains content that +// is supported by the web view. In particular, if the content is a local file +// URL, this checks if it is of a type that can be shown in the tab contents. +BOOL IsUnsupportedDropData(id info); + +} // namespace drag_util + +#endif // _WEB_DRAG_UTILS_MAC diff --git a/libcef/web_drag_utils_mac.mm b/libcef/web_drag_utils_mac.mm new file mode 100644 index 000000000..3565f624f --- /dev/null +++ b/libcef/web_drag_utils_mac.mm @@ -0,0 +1,101 @@ +// Copyright (c) 2011 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. + +#import "web_drag_utils_mac.h" + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/sys_string_conversions.h" +#include "googleurl/src/gurl.h" +#include "net/base/mime_util.h" +#include "net/base/net_util.h" +#import "third_party/mozilla/NSPasteboard+Utils.h" +#include "webkit/plugins/npapi/plugin_list.h" + +namespace drag_util { + +BOOL PopulateURLAndTitleFromPasteBoard(GURL* url, + string16* title, + NSPasteboard* pboard, + BOOL convert_filenames) { + CHECK(url); + + // Bail out early if there's no URL data. + if (![pboard containsURLData]) + return NO; + + // -getURLs:andTitles:convertingFilenames: will already validate URIs so we + // don't need to again. The arrays returned are both of NSStrings. + NSArray* url_array = nil; + NSArray* title_array = nil; + [pboard getURLs:&url_array andTitles:&title_array + convertingFilenames:convert_filenames]; + DCHECK_EQ([url_array count], [title_array count]); + // It's possible that no URLs were actually provided! + if (![url_array count]) + return NO; + NSString* url_string = [url_array objectAtIndex:0]; + if ([url_string length]) { + // Check again just to make sure to not assign NULL into a std::string, + // which throws an exception. + const char* utf8_url = [url_string UTF8String]; + if (utf8_url) { + *url = GURL(utf8_url); + // Extra paranoia check. + if (title && [title_array count]) + *title = base::SysNSStringToUTF16([title_array objectAtIndex:0]); + } + } + return YES; +} + +GURL GetFileURLFromDropData(id info) { + if ([[info draggingPasteboard] containsURLData]) { + GURL url; + PopulateURLAndTitleFromPasteBoard(&url, + NULL, + [info draggingPasteboard], + YES); + + if (url.SchemeIs("file")) + return url; + } + return GURL(); +} + +static BOOL IsSupportedFileURL(const GURL& url) { + FilePath full_path; + net::FileURLToFilePath(url, &full_path); + + std::string mime_type; + net::GetMimeTypeFromFile(full_path, &mime_type); + + // This logic mirrors |BufferedResourceHandler::ShouldDownload()|. + // TODO(asvitkine): Refactor this out to a common location instead of + // duplicating code. + if (net::IsSupportedMimeType(mime_type)) + return YES; + + // Check whether there is a plugin that supports the mime type. (e.g. PDF) + webkit::npapi::PluginList* list = webkit::npapi::PluginList::Singleton(); + webkit::npapi::WebPluginInfo info; + if (list->PluginsLoaded() && + list->GetPluginInfo(GURL(), mime_type, false, &info, NULL)) { + return webkit::npapi::IsPluginEnabled(info); + } + + return NO; +} + +BOOL IsUnsupportedDropData(id info) { + GURL url = GetFileURLFromDropData(info); + if (!url.is_empty()) { + // If dragging a file, only allow dropping supported file types (that the + // web view can display). + return !IsSupportedFileURL(url); + } + return NO; +} + +} // namespace drag_util diff --git a/libcef/web_drop_target_mac.h b/libcef/web_drop_target_mac.h new file mode 100644 index 000000000..17708182e --- /dev/null +++ b/libcef/web_drop_target_mac.h @@ -0,0 +1,69 @@ +// Copyright (c) 2011 The Chromium Embedded Framework Authors. +// Portions copyright (c) 2011 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. + +#import + +#include "base/string16.h" + +class GURL; +struct WebDropData; +@class BrowserWebView; +class WebViewHost; + +// A class that handles tracking and event processing for a drag and drop +// over the content area. Assumes something else initiates the drag, this is +// only for processing during a drag. + +@interface WebDropTarget : NSObject { + @private + // Our associated WebView. Weak reference. + BrowserWebView* view_; + + // Updated asynchronously during a drag to tell us whether or not we should + // allow the drop. + NSDragOperation current_operation_; + + // Keep track of the WebViewHost we're dragging over. If it changes during a + // drag, we need to re-send the DragEnter message. + WebViewHost* current_wvh_; +} + +// |view| is the WebView representing this browser window, used to communicate +// drag&drop messages to WebCore and handle navigation on a successful drop +// (if necessary). +- (id)initWithWebView:(BrowserWebView*)view; + +// Sets the current operation negotiated by the source and destination, +// which determines whether or not we should allow the drop. Takes effect the +// next time |-draggingUpdated:| is called. +- (void)setCurrentOperation: (NSDragOperation)operation; + +// Messages to send during the tracking of a drag, ususally upon receiving +// calls from the view system. Communicates the drag messages to WebCore. +- (NSDragOperation)draggingEntered:(id)info + view:(NSView*)view; +- (void)draggingExited:(id)info; +- (NSDragOperation)draggingUpdated:(id)info + view:(NSView*)view; +- (BOOL)performDragOperation:(id)info + view:(NSView*)view; + +@end + +// Public use only for unit tests. +@interface WebDropTarget(Testing) +// Given |data|, which should not be nil, fill it in using the contents of the +// given pasteboard. +- (void)populateWebDropData:(WebDropData*)data + fromPasteboard:(NSPasteboard*)pboard; +// Given a point in window coordinates and a view in that window, return a +// flipped point in the coordinate system of |view|. +- (NSPoint)flipWindowPointToView:(const NSPoint&)windowPoint + view:(NSView*)view; +// Given a point in window coordinates and a view in that window, return a +// flipped point in screen coordinates. +- (NSPoint)flipWindowPointToScreen:(const NSPoint&)windowPoint + view:(NSView*)view; +@end diff --git a/libcef/web_drop_target_mac.mm b/libcef/web_drop_target_mac.mm new file mode 100644 index 000000000..69048206f --- /dev/null +++ b/libcef/web_drop_target_mac.mm @@ -0,0 +1,234 @@ +// Copyright (c) 2011 The Chromium Embedded Framework Authors. +// Portions copyright (c) 2011 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 "browser_impl.h" +#import "browser_webview_mac.h" +#include "cef_context.h" +#import "web_drop_target_mac.h" +#import "web_drag_utils_mac.h" + +#include "base/logging.h" +#include "base/sys_string_conversions.h" +#import "third_party/mozilla/NSPasteboard+Utils.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebDragData.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebDragOperation.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebPoint.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebView.h" +#include "webkit/glue/webdropdata.h" +#include "webkit/glue/window_open_disposition.h" + +using WebKit::WebDragOperationsMask; +using WebKit::WebPoint; +using WebKit::WebView; + +@implementation WebDropTarget + +// |view| is the WebView representing this browser window, used to communicate +// drag&drop messages to WebCore and handle navigation on a successful drop +// (if necessary). +- (id)initWithWebView:(BrowserWebView*)view { + if ((self = [super init])) + view_ = view; + return self; +} + +// Call to set whether or not we should allow the drop. Takes effect the +// next time |-draggingUpdated:| is called. +- (void)setCurrentOperation: (NSDragOperation)operation { + current_operation_ = operation; +} + +// Given a point in window coordinates and a view in that window, return a +// flipped point in the coordinate system of |view|. +- (NSPoint)flipWindowPointToView:(const NSPoint&)windowPoint + view:(NSView*)view { + DCHECK(view); + NSPoint viewPoint = [view convertPoint:windowPoint fromView:nil]; + NSRect viewFrame = [view frame]; + viewPoint.y = viewFrame.size.height - viewPoint.y; + return viewPoint; +} + +// Given a point in window coordinates and a view in that window, return a +// flipped point in screen coordinates. +- (NSPoint)flipWindowPointToScreen:(const NSPoint&)windowPoint + view:(NSView*)view { + DCHECK(view); + NSPoint screenPoint = [[view window] convertBaseToScreen:windowPoint]; + NSScreen* screen = [[view window] screen]; + NSRect screenFrame = [screen frame]; + screenPoint.y = screenFrame.size.height - screenPoint.y; + return screenPoint; +} + +// Return YES if the drop site only allows drops that would navigate. If this +// is the case, we don't want to pass messages to the renderer because there's +// really no point (i.e., there's nothing that cares about the mouse position or +// entering and exiting). One example is an interstitial page (e.g., safe +// browsing warning). +- (BOOL)onlyAllowsNavigation { + return false; +} + +// Messages to send during the tracking of a drag, usually upon receiving +// calls from the view system. Communicates the drag messages to WebCore. + +- (NSDragOperation)draggingEntered:(id)info + view:(NSView*)view { + // Save off the current WebViewHost so we can tell if it changes during a + // drag. If it does, we need to send a new enter message in draggingUpdated:. + current_wvh_ = _Context->current_webviewhost(); + DCHECK(current_wvh_); + + if ([self onlyAllowsNavigation]) { + if ([[info draggingPasteboard] containsURLData]) + return NSDragOperationCopy; + return NSDragOperationNone; + } + + WebView* webview = view_.browser->UIT_GetWebView(); + + // Fill out a WebDropData from pasteboard. + WebDropData data; + [self populateWebDropData:&data fromPasteboard:[info draggingPasteboard]]; + + // Create the appropriate mouse locations for WebCore. The draggingLocation + // is in window coordinates. Both need to be flipped. + NSPoint windowPoint = [info draggingLocation]; + NSPoint viewPoint = [self flipWindowPointToView:windowPoint view:view]; + NSPoint screenPoint = [self flipWindowPointToScreen:windowPoint view:view]; + NSDragOperation mask = [info draggingSourceOperationMask]; + webview->dragTargetDragEnter(data.ToDragData(), + WebPoint(viewPoint.x, viewPoint.y), + WebPoint(screenPoint.x, screenPoint.y), + static_cast(mask)); + + // We won't know the true operation (whether the drag is allowed) until we + // hear back from the renderer. For now, be optimistic: + current_operation_ = NSDragOperationCopy; + return current_operation_; +} + +- (void)draggingExited:(id)info { + DCHECK(current_wvh_); + if (current_wvh_ != _Context->current_webviewhost()) + return; + + WebView* webview = view_.browser->UIT_GetWebView(); + + // Nothing to do in the interstitial case. + + webview->dragTargetDragLeave(); +} + +- (NSDragOperation)draggingUpdated:(id)info + view:(NSView*)view { + DCHECK(current_wvh_); + if (current_wvh_ != _Context->current_webviewhost()) + [self draggingEntered:info view:view]; + + if ([self onlyAllowsNavigation]) { + if ([[info draggingPasteboard] containsURLData]) + return NSDragOperationCopy; + return NSDragOperationNone; + } + + WebView* webview = view_.browser->UIT_GetWebView(); + + // Create the appropriate mouse locations for WebCore. The draggingLocation + // is in window coordinates. + NSPoint windowPoint = [info draggingLocation]; + NSPoint viewPoint = [self flipWindowPointToView:windowPoint view:view]; + NSPoint screenPoint = [self flipWindowPointToScreen:windowPoint view:view]; + NSDragOperation mask = [info draggingSourceOperationMask]; + webview->dragTargetDragOver(WebPoint(viewPoint.x, viewPoint.y), + WebPoint(screenPoint.x, screenPoint.y), + static_cast(mask)); + + return current_operation_; +} + +- (BOOL)performDragOperation:(id)info + view:(NSView*)view { + DCHECK(current_wvh_); + if (current_wvh_ != _Context->current_webviewhost()) + [self draggingEntered:info view:view]; + + // Check if we only allow navigation and navigate to a url on the pasteboard. + if ([self onlyAllowsNavigation]) { + NSPasteboard* pboard = [info draggingPasteboard]; + if ([pboard containsURLData]) { + GURL url; + drag_util::PopulateURLAndTitleFromPasteBoard(&url, NULL, pboard, YES); + view_.browser->GetMainFrame()->LoadURL(url.spec()); + return YES; + } + return NO; + } + + current_wvh_ = NULL; + + WebView* webview = view_.browser->UIT_GetWebView(); + + // Create the appropriate mouse locations for WebCore. The draggingLocation + // is in window coordinates. Both need to be flipped. + NSPoint windowPoint = [info draggingLocation]; + NSPoint viewPoint = [self flipWindowPointToView:windowPoint view:view]; + NSPoint screenPoint = [self flipWindowPointToScreen:windowPoint view:view]; + webview->dragTargetDrop(gfx::Point(viewPoint.x, viewPoint.y), + gfx::Point(screenPoint.x, screenPoint.y)); + + return YES; +} + +// Given |data|, which should not be nil, fill it in using the contents of the +// given pasteboard. +- (void)populateWebDropData:(WebDropData*)data + fromPasteboard:(NSPasteboard*)pboard { + DCHECK(data); + DCHECK(pboard); + NSArray* types = [pboard types]; + + // Get URL if possible. To avoid exposing file system paths to web content, + // filenames in the drag are not converted to file URLs. + drag_util::PopulateURLAndTitleFromPasteBoard(&data->url, + &data->url_title, + pboard, + NO); + + // Get plain text. + if ([types containsObject:NSStringPboardType]) { + data->plain_text = + base::SysNSStringToUTF16([pboard stringForType:NSStringPboardType]); + } + + // Get HTML. If there's no HTML, try RTF. + if ([types containsObject:NSHTMLPboardType]) { + data->text_html = + base::SysNSStringToUTF16([pboard stringForType:NSHTMLPboardType]); + } else if ([types containsObject:NSRTFPboardType]) { + NSString* html = [pboard htmlFromRtf]; + data->text_html = base::SysNSStringToUTF16(html); + } + + // Get files. + if ([types containsObject:NSFilenamesPboardType]) { + NSArray* files = [pboard propertyListForType:NSFilenamesPboardType]; + if ([files isKindOfClass:[NSArray class]] && [files count]) { + for (NSUInteger i = 0; i < [files count]; i++) { + NSString* filename = [files objectAtIndex:i]; + BOOL isDir = NO; + BOOL exists = [[NSFileManager defaultManager] fileExistsAtPath:filename + isDirectory:&isDir]; + if (exists && !isDir) + data->filenames.push_back(base::SysNSStringToUTF16(filename)); + } + } + } + + // TODO(pinkerton): Get file contents. http://crbug.com/34661 +} + +@end diff --git a/libcef/webview_host.h b/libcef/webview_host.h index 37b10fe2a..0f914e8fb 100644 --- a/libcef/webview_host.h +++ b/libcef/webview_host.h @@ -50,6 +50,8 @@ class WebViewHost : public WebWidgetHost { } #elif defined(OS_MACOSX) void SetIsActive(bool active); + virtual void MouseEvent(NSEvent *); + virtual void SetFocus(bool enable); #endif protected: diff --git a/libcef/webview_host_mac.mm b/libcef/webview_host_mac.mm index 3aa898bd0..b1804d954 100644 --- a/libcef/webview_host_mac.mm +++ b/libcef/webview_host_mac.mm @@ -7,6 +7,7 @@ #include "webview_host.h" #include "browser_webview_delegate.h" #include "browser_webview_mac.h" +#include "cef_context.h" #include "skia/ext/platform_canvas.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebSize.h" @@ -56,3 +57,17 @@ WebView* WebViewHost::webview() const { void WebViewHost::SetIsActive(bool active) { webview()->setIsActive(active); } + +void WebViewHost::MouseEvent(NSEvent* event) { + _Context->set_current_webviewhost(this); + WebWidgetHost::MouseEvent(event); +} + +void WebViewHost::SetFocus(bool enable) { + if (enable) { + // Set the current WebViewHost in case a drag action is started before mouse + // events are detected for the window. + _Context->set_current_webviewhost(this); + } + WebWidgetHost::SetFocus(enable); +} diff --git a/libcef/webwidget_host.h b/libcef/webwidget_host.h index cb52b4259..a01a4543f 100644 --- a/libcef/webwidget_host.h +++ b/libcef/webwidget_host.h @@ -151,10 +151,10 @@ class WebWidgetHost { // These need to be called from a non-subclass, so they need to be public. public: void Resize(const gfx::Rect& rect); - void MouseEvent(NSEvent *); + virtual void MouseEvent(NSEvent *); void WheelEvent(NSEvent *); void KeyEvent(NSEvent *); - void SetFocus(bool enable); + virtual void SetFocus(bool enable); protected: #elif defined(TOOLKIT_USES_GTK) public: diff --git a/tests/cefclient/cefclient_mac.mm b/tests/cefclient/cefclient_mac.mm index b889971a4..72d0841ef 100644 --- a/tests/cefclient/cefclient_mac.mm +++ b/tests/cefclient/cefclient_mac.mm @@ -4,6 +4,7 @@ // found in the LICENSE file. #include "include/cef.h" +#import "include/cef_application_mac.h" #include "cefclient.h" #include "binding_test.h" #include "client_handler.h" @@ -29,6 +30,31 @@ char szWorkingDir[512]; // The current working directory const int kWindowWidth = 800; const int kWindowHeight = 600; +// Memory AutoRelease pool. +static NSAutoreleasePool* g_autopool = nil; + +// Provide the CefAppProtocol implementation required by CEF. +@interface ClientApplication : NSApplication { +@private + BOOL handlingSendEvent_; +} +@end + +@implementation ClientApplication +- (BOOL)isHandlingSendEvent { + return handlingSendEvent_; +} + +- (void)setHandlingSendEvent:(BOOL)handlingSendEvent { + handlingSendEvent_ = handlingSendEvent; +} + +- (void)sendEvent:(NSEvent*)event { + CefScopedSendingEvent sendingEventScoper; + [super sendEvent:event]; +} +@end + // Receives notifications from controls and the browser window. Will delete // itself when done. @@ -174,6 +200,7 @@ NSButton* MakeButton(NSRect* rect, NSString* title, NSView* parent) { - (IBAction)testAcceleratedLayers:(id)sender; - (IBAction)testWebGL:(id)sender; - (IBAction)testHTML5Video:(id)sender; +- (IBAction)testDragDrop:(id)sender; - (IBAction)testZoomIn:(id)sender; - (IBAction)testZoomOut:(id)sender; - (IBAction)testZoomReset:(id)sender; @@ -243,6 +270,9 @@ NSButton* MakeButton(NSRect* rect, NSString* title, NSView* parent) { [testMenu addItemWithTitle:@"HTML5 Video" action:@selector(testHTML5Video:) keyEquivalent:@""]; + [testMenu addItemWithTitle:@"Drag & Drop" + action:@selector(testDragDrop:) + keyEquivalent:@""]; [testMenu addItemWithTitle:@"Zoom In" action:@selector(testZoomIn:) keyEquivalent:@""]; @@ -419,6 +449,11 @@ NSButton* MakeButton(NSRect* rect, NSString* title, NSView* parent) { RunHTML5VideoTest(g_handler->GetBrowser()); } +- (IBAction)testDragDrop:(id)sender { + if(g_handler.get() && g_handler->GetBrowserHwnd()) + RunDragDropTest(g_handler->GetBrowser()); +} + - (IBAction)testZoomIn:(id)sender { if(g_handler.get() && g_handler->GetBrowserHwnd()) { CefRefPtr browser = g_handler->GetBrowser(); @@ -448,6 +483,9 @@ NSButton* MakeButton(NSRect* rect, NSString* title, NSView* parent) { CefShutdown(); [self release]; + + // Release the AutoRelease pool. + [g_autopool release]; } @end @@ -458,7 +496,13 @@ int main(int argc, char* argv[]) // Retrieve the current working directory. getcwd(szWorkingDir, sizeof(szWorkingDir)); - // Initialize CEF. This will also create the NSApplication instance. + // Initialize the AutoRelease pool. + g_autopool = [[NSAutoreleasePool alloc] init]; + + // Initialize the ClientApplication instance. + [ClientApplication sharedApplication]; + + // Initialize CEF. CefSettings settings; CefInitialize(settings); @@ -472,7 +516,7 @@ int main(int argc, char* argv[]) waitUntilDone:NO]; // Run the application message loop. - [NSApp run]; + CefRunMessageLoop(); // Don't put anything below this line because it won't be executed. return 0;