// Copyright (c) 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. #include "libcef/browser_webview_delegate.h" #import "libcef/browser_webview_mac.h" #include "libcef/browser_impl.h" #include "libcef/drag_data_impl.h" #import "include/cef_application_mac.h" #import #include "base/file_util.h" #include "base/mac/mac_util.h" #include "base/sys_string_conversions.h" #include "base/utf_string_conversions.h" #include "base/threading/thread_restrictions.h" #include "skia/ext/skia_utils_mac.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/WebDocument.h" #include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebDragData.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h" #include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebImage.h" #include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebPoint.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebPopupMenu.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebView.h" #include "ui/base/layout.h" #include "ui/gfx/image/image_skia.h" #include "ui/gfx/image/image_skia_util_mac.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::npapi::WebPluginDelegateImpl; using WebKit::WebContextMenuData; 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; namespace { void AddMenuItem(CefRefPtr browser, CefRefPtr handler, NSMenu* menu, id target, cef_menu_id_t menuId, const std::string& label, bool enabled) { std::string disp_str; if (handler.get()) { // Let the handler change the label if desired. CefString actual_label(label); handler->GetMenuLabel(browser, menuId, actual_label); disp_str = actual_label; } else { disp_str = label; } NSString* str = base::SysUTF8ToNSString(disp_str); NSMenuItem* item = [[[NSMenuItem alloc] initWithTitle:str action:enabled?@selector(menuItemSelected:):nil keyEquivalent:@""] autorelease]; [item setTarget:target]; [item setTag:menuId]; [menu addItem:item]; } void AddMenuSeparator(NSMenu* menu) { NSMenuItem* item = [NSMenuItem separatorItem]; [menu addItem:item]; } NSString* GetDialogLabel(WebKit::WebFrame* webframe, const std::string& label) { const GURL& url = webframe->document().url(); std::string urlStr; if (!url.is_empty()) urlStr = url.host(); string16 labelStr = ASCIIToUTF16(label); if (!urlStr.empty()) labelStr += ASCIIToUTF16(" - " + urlStr); return base::SysUTF16ToNSString(labelStr); } } // namespace @interface BrowserMenuDelegate : NSObject { @private CefRefPtr browser_; } - (id)initWithBrowser:(CefBrowserImpl*)browser; - (void)menuItemSelected:(id)sender; @end @implementation BrowserMenuDelegate - (id)initWithBrowser:(CefBrowserImpl*)browser { self = [super init]; if (self) browser_ = browser; return self; } // Called when a context menu item is selected by the user. - (void)menuItemSelected:(id)sender { cef_menu_id_t menuId = static_cast([sender tag]); bool handled = false; CefRefPtr client = browser_->GetClient(); if (client.get()) { CefRefPtr handler = client->GetMenuHandler(); if (handler.get()) { // Ask the handler if it wants to handle the action. handled = handler->OnMenuAction(browser_.get(), menuId); } } if(!handled) { // Execute the action. browser_->UIT_HandleAction(menuId, browser_->GetFocusedFrame()); } } @end // WebViewClient -------------------------------------------------------------- WebExternalPopupMenu* BrowserWebViewDelegate::createExternalPopupMenu( const WebPopupMenuInfo& info, WebExternalPopupMenuClient* client) { DCHECK(!external_popup_menu_.get()); external_popup_menu_.reset(new ExternalPopupMenu(this, info, client)); return external_popup_menu_.get(); } void BrowserWebViewDelegate::ClosePopupMenu() { if (external_popup_menu_ == NULL) { NOTREACHED(); return; } external_popup_menu_.reset(); } void BrowserWebViewDelegate::showContextMenu( WebKit::WebFrame* frame, const WebKit::WebContextMenuData& data) { WebWidgetHost* host = GetWidgetHost(); if (!host) return; NSView *view = browser_->UIT_GetMainWndHandle(); if (!view) return; NSWindow* window = [view window]; int screenX = -1; int screenY = -1; NSPoint mouse_pt = {data.mousePosition.x, data.mousePosition.y}; if (!browser_->IsWindowRenderingDisabled()) { mouse_pt = [window mouseLocationOutsideOfEventStream]; NSPoint screen_pt = [window convertBaseToScreen:mouse_pt]; screenX = screen_pt.x; screenY = screen_pt.y; } int edit_flags = 0; int type_flags = 0; NSMenu* menu = nil; // Make sure events can be pumped while the menu is up. MessageLoop::ScopedNestableTaskAllower allow(MessageLoop::current()); // Give the client a chance to handle the menu. if (OnBeforeMenu(data, mouse_pt.x, mouse_pt.y, edit_flags, type_flags)) return; CefRefPtr client = browser_->GetClient(); CefRefPtr handler; if (client.get()) handler = client->GetMenuHandler(); if (client.get() && browser_->IsWindowRenderingDisabled()) { // Retrieve the screen coordinates. CefRefPtr render_handler = client->GetRenderHandler(); if (!render_handler.get() || !render_handler->GetScreenPoint(browser_, mouse_pt.x, mouse_pt.y, screenX, screenY)) { return; } } BrowserMenuDelegate* delegate = [[[BrowserMenuDelegate alloc] initWithBrowser:browser_] autorelease]; // Build the correct default context menu if (type_flags & MENUTYPE_EDITABLE) { menu = [[[NSMenu alloc] initWithTitle:@""] autorelease]; AddMenuItem(browser_, handler, menu, delegate, MENU_ID_UNDO, "Undo", !!(edit_flags & MENU_CAN_UNDO)); AddMenuItem(browser_, handler, menu, delegate, MENU_ID_REDO, "Redo", !!(edit_flags & MENU_CAN_REDO)); AddMenuSeparator(menu); AddMenuItem(browser_, handler, menu, delegate, MENU_ID_CUT, "Cut", !!(edit_flags & MENU_CAN_CUT)); AddMenuItem(browser_, handler, menu, delegate, MENU_ID_COPY, "Copy", !!(edit_flags & MENU_CAN_COPY)); AddMenuItem(browser_, handler, menu, delegate, MENU_ID_PASTE, "Paste", !!(edit_flags & MENU_CAN_PASTE)); AddMenuItem(browser_, handler, menu, delegate, MENU_ID_DELETE, "Delete", !!(edit_flags & MENU_CAN_DELETE)); AddMenuSeparator(menu); AddMenuItem(browser_, handler, menu, delegate, MENU_ID_SELECTALL, "Select All", !!(edit_flags & MENU_CAN_SELECT_ALL)); } else if(type_flags & MENUTYPE_SELECTION) { menu = [[[NSMenu alloc] initWithTitle:@""] autorelease]; AddMenuItem(browser_, handler, menu, delegate, MENU_ID_COPY, "Copy", !!(edit_flags & MENU_CAN_COPY)); } else if(type_flags & (MENUTYPE_PAGE | MENUTYPE_FRAME)) { menu = [[[NSMenu alloc] initWithTitle:@""] autorelease]; AddMenuItem(browser_, handler, menu, delegate, MENU_ID_NAV_BACK, "Back", !!(edit_flags & MENU_CAN_GO_BACK)); AddMenuItem(browser_, handler, menu, delegate, MENU_ID_NAV_FORWARD, "Forward", !!(edit_flags & MENU_CAN_GO_FORWARD)); // TODO(port): Enable the below menu items when supported. //AddMenuSeparator(menu); //AddMenuItem(browser_, handler, menu, delegate, MENU_ID_PRINT, "Print", // true); //AddMenuItem(browser_, handler, menu, delegate, MENU_ID_VIEWSOURCE, // "View Source", true); } if (!menu) return; // Synthesize an event for the click, as there is no certainty that // [NSApp currentEvent] will return a valid event. NSPoint screen_pt = {screenX, screenY}; NSPoint window_pt = [window convertScreenToBase:screen_pt]; NSEvent* currentEvent = [NSApp currentEvent]; NSTimeInterval eventTime = [currentEvent timestamp]; NSEvent* clickEvent = [NSEvent mouseEventWithType:NSRightMouseDown location:window_pt modifierFlags:NSRightMouseDownMask timestamp:eventTime windowNumber:[window windowNumber] context:nil eventNumber:0 clickCount:1 pressure:1.0]; // Menu selection events go to the BrowserMenuDelegate. [menu setDelegate:delegate]; // Show the menu. [NSMenu popUpContextMenu:menu withEvent:clickEvent forView:view]; } // WebWidgetClient ------------------------------------------------------------ void BrowserWebViewDelegate::show(WebNavigationPolicy policy) { DCHECK(this != browser_->UIT_GetPopupDelegate()); } void BrowserWebViewDelegate::didChangeCursor(const WebCursorInfo& cursor_info) { NSCursor* ns_cursor = WebCursor(cursor_info).GetNativeCursor(); if (!browser_->IsWindowRenderingDisabled()) { [ns_cursor set]; } else { // Notify the handler of cursor change. CefRefPtr client = browser_->GetClient(); if (client.get()) { CefRefPtr handler = client->GetRenderHandler(); if (handler.get()) handler->OnCursorChange(browser_, ns_cursor); } } } WebRect BrowserWebViewDelegate::windowRect() { if (WebWidgetHost* host = GetWidgetHost()) { if (!browser_->IsWindowRenderingDisabled()) { NSView *view = host->view_handle(); NSRect rect = [view frame]; return gfx::Rect(NSRectToCGRect(rect)); } else { // Retrieve the view rectangle from the handler. CefRefPtr client = browser_->GetClient(); if (client.get()) { CefRefPtr handler = client->GetRenderHandler(); if (handler.get()) { CefRect rect(0, 0, 0, 0); if (handler->GetViewRect(browser_, rect)) return WebRect(rect.x, rect.y, rect.width, rect.height); } } } } return WebRect(); } void BrowserWebViewDelegate::setWindowRect(const WebRect& rect) { if (this == browser_->UIT_GetWebViewDelegate()) { // TODO(port): Set the window rectangle. } else if (this == browser_->UIT_GetPopupDelegate()) { NOTREACHED(); } } WebRect BrowserWebViewDelegate::rootWindowRect() { if (WebWidgetHost* host = GetWidgetHost()) { NSView *view = host->view_handle(); NSRect rect = [[[view window] contentView] frame]; return gfx::Rect(NSRectToCGRect(rect)); } return WebRect(); } @interface NSWindow(OSInternals) - (NSRect)_growBoxRect; @end WebRect BrowserWebViewDelegate::windowResizerRect() { NSRect resize_rect = NSMakeRect(0, 0, 0, 0); WebWidgetHost* host = GetWidgetHost(); if (host) { NSView *view = host->view_handle(); NSWindow* window = [view window]; if (window == nil) return gfx::Rect(); resize_rect = [window _growBoxRect]; // The scrollbar assumes that the resizer goes all the way down to the // bottom corner, so we ignore any y offset to the rect itself and use the // entire bottom corner. resize_rect.origin.y = 0; // Convert to view coordinates from window coordinates. resize_rect = [view convertRect:resize_rect fromView:nil]; // Flip the rect in view coordinates resize_rect.origin.y = [view frame].size.height - resize_rect.origin.y - resize_rect.size.height; } return gfx::Rect(NSRectToCGRect(resize_rect)); } void BrowserWebViewDelegate::startDragging( WebKit::WebFrame* frame, const WebDragData& data, WebDragOperationsMask mask, const WebImage& image, const WebPoint& image_offset) { if (browser_->settings().drag_drop_disabled || browser_->IsWindowRenderingDisabled()) { browser_->UIT_GetWebView()->dragSourceSystemDragEnded(); return; } WebWidgetHost* host = GetWidgetHost(); if (!host) return; BrowserWebView *view = static_cast(host->view_handle()); if (!view) return; WebDropData drop_data(data); CefRefPtr client = browser_->GetClient(); if (client.get()) { CefRefPtr handler = client->GetDragHandler(); if (handler.get()) { CefRefPtr data(new CefDragDataImpl(drop_data)); if (handler->OnDragStart(browser_, data, static_cast(mask))) { browser_->UIT_GetWebView()->dragSourceSystemDragEnded(); 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()); NSImage* ns_image = nil; if (!image.isNull()) { ui::ScaleFactor scale_factor = ui::GetScaleFactorForNativeView(host->view_handle()); gfx::ImageSkia image_skia( gfx::ImageSkiaRep(image.getSkBitmap(), scale_factor)); ns_image = gfx::NSImageFromImageSkia(image_skia); } NSPoint offset = NSPointFromCGPoint(gfx::Point(image_offset).ToCGPoint()); // Keep a reference to the NSView so that it won't be destroyed until after // the drag operation has completed. [view retain]; [view startDragWithDropData:drop_data dragOperationMask:static_cast(mask) image:ns_image offset:offset]; [view release]; } void BrowserWebViewDelegate::runModal() { NOTIMPLEMENTED(); } // WebPluginPageDelegate ------------------------------------------------------ webkit::npapi::WebPluginDelegate* BrowserWebViewDelegate::CreatePluginDelegate( const FilePath& path, const std::string& mime_type) { WebWidgetHost *host = GetWidgetHost(); if (!host) return NULL; WebPluginDelegateImpl* delegate = WebPluginDelegateImpl::Create(path, mime_type); if (delegate) delegate->SetNoBufferContext(); return delegate; } void BrowserWebViewDelegate::CreatedPluginWindow( gfx::PluginWindowHandle handle) { if (browser_->IsWindowRenderingDisabled()) { WebViewHost* host = browser_->UIT_GetWebViewHost(); if (host) host->AddWindowedPlugin(handle); } } void BrowserWebViewDelegate::WillDestroyPluginWindow( gfx::PluginWindowHandle handle) { if (browser_->IsWindowRenderingDisabled()) { WebViewHost* host = browser_->UIT_GetWebViewHost(); if (host) host->RemoveWindowedPlugin(handle); } } void BrowserWebViewDelegate::DidMovePlugin( const webkit::npapi::WebPluginGeometry& move) { if (browser_->IsWindowRenderingDisabled()) { WebViewHost* host = browser_->UIT_GetWebViewHost(); if (host) { host->MoveWindowedPlugin(move); } } } // Protected methods ---------------------------------------------------------- void BrowserWebViewDelegate::ShowJavaScriptAlert( WebKit::WebFrame* webframe, const CefString& message) { NSString* label = GetDialogLabel(webframe, "JavaScript Alert"); std::string messageStr(message); NSString* text = [NSString stringWithUTF8String:messageStr.c_str()]; NSAlert* alert = [NSAlert alertWithMessageText:label defaultButton:@"OK" alternateButton:nil otherButton:nil informativeTextWithFormat:text]; [alert runModal]; } bool BrowserWebViewDelegate::ShowJavaScriptConfirm( WebKit::WebFrame* webframe, const CefString& message) { NSString* label = GetDialogLabel(webframe, "JavaScript Confirm"); std::string messageStr(message); NSString* text = [NSString stringWithUTF8String:messageStr.c_str()]; NSAlert *alert = [NSAlert alertWithMessageText:label defaultButton:@"OK" alternateButton:@"Cancel" otherButton:nil informativeTextWithFormat:text]; NSInteger r = [alert runModal]; return (r == NSAlertDefaultReturn); } bool BrowserWebViewDelegate::ShowJavaScriptPrompt( WebKit::WebFrame* webframe, const CefString& message, const CefString& default_value, CefString* result) { NSString* label = GetDialogLabel(webframe, "JavaScript Prompt"); NSAlert *alert = [NSAlert alertWithMessageText:label defaultButton:@"OK" alternateButton:@"Cancel" otherButton:nil informativeTextWithFormat:@""]; NSTextField *input = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, 300, 22)]; [[input cell] setLineBreakMode:NSLineBreakByTruncatingTail]; std::string default_valueStr(default_value); [input setStringValue: [NSString stringWithUTF8String:default_valueStr.c_str()]]; [alert setAccessoryView:input]; [input release]; std::string messageStr(message); [alert setInformativeText:[NSString stringWithUTF8String:messageStr.c_str()]]; NSInteger r = [alert runModal]; if (r == NSAlertDefaultReturn) { [input validateEditing]; *result = base::SysNSStringToUTF8([input stringValue]); } return (r == NSAlertDefaultReturn); } // Called to show the file chooser dialog. bool BrowserWebViewDelegate::ShowFileChooser( std::vector& file_names, bool multi_select, const WebKit::WebString& title, const FilePath& default_file, const std::vector& accept_mime_types) { NSOpenPanel* dialog = [NSOpenPanel openPanel]; if (!title.isNull()) [dialog setTitle:base::SysUTF16ToNSString(title)]; NSString* default_dir = nil; NSString* default_filename = nil; if (!default_file.empty()) { // The file dialog is going to do a ton of stats anyway. Not much // point in eliminating this one. base::ThreadRestrictions::ScopedAllowIO allow_io; if (file_util::DirectoryExists(default_file)) { default_dir = base::SysUTF8ToNSString(default_file.value()); } else { default_dir = base::SysUTF8ToNSString(default_file.DirName().value()); default_filename = base::SysUTF8ToNSString(default_file.BaseName().value()); } } [dialog setAllowsOtherFileTypes:YES]; [dialog setAllowsMultipleSelection:multi_select]; [dialog setCanChooseFiles:YES]; [dialog setCanChooseDirectories:NO]; // [NSOpenPanel runModalForDirectory:file:] was deprecated on OS-X 10.7. NSInteger result; if ([NSOpenPanel respondsToSelector:@selector(runModalForDirectory:file:)]) { result = (NSInteger) [dialog performSelector:@selector(runModalForDirectory:file:) withObject:default_dir withObject:default_filename]; } else { if (default_dir) [dialog setDirectoryURL:[NSURL URLWithString:default_dir]]; if (default_filename) [dialog setNameFieldStringValue:default_filename]; result = [dialog runModal]; } if (result == NSFileHandlingPanelCancelButton) return false; NSArray *urls = [dialog URLs]; int i, count = [urls count]; for (i=0; i