// 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. // This file contains the implementation of BrowserWebViewDelegate, which serves // as the WebViewDelegate for the BrowserWebHost. The host is expected to // have initialized a MessageLoop before these methods are called. #include "libcef/browser_webview_delegate.h" #include #include #include #include "libcef/browser_drag_delegate_win.h" #include "libcef/browser_navigation_controller.h" #include "libcef/browser_impl.h" #include "libcef/cef_context.h" #include "libcef/drag_data_impl.h" #include "libcef/web_drop_target_win.h" #include "base/message_loop.h" #include "base/string_util.h" #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/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/platform/WebRect.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebView.h" #include "ui/gfx/gdi_util.h" #include "ui/gfx/native_widget_types.h" #include "ui/gfx/point.h" #include "webkit/glue/webdropdata.h" #include "webkit/glue/webpreferences.h" #include "webkit/glue/webkit_glue.h" #include "webkit/glue/window_open_disposition.h" #include "webkit/plugins/npapi/plugin_list.h" #include "webkit/plugins/npapi/webplugin.h" #include "webkit/plugins/npapi/webplugin_delegate_impl.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::WebFrame; using WebKit::WebImage; using WebKit::WebNavigationPolicy; using WebKit::WebPoint; using WebKit::WebPopupMenuInfo; using WebKit::WebRect; using WebKit::WebWidget; namespace { static const wchar_t kPluginWindowClassName[] = L"WebPluginHost"; void AddMenuItem(CefRefPtr browser, CefRefPtr handler, HMENU menu, cef_menu_id_t menuId, const wchar_t* label, bool enabled, std::list& label_list) { CefString actual_label(label); if (handler.get()) { // Let the handler change the label if desired, handler->GetMenuLabel(browser, menuId, actual_label); } // Store the label in a list to simplify memory management. label_list.push_back(actual_label); MENUITEMINFO mii; mii.cbSize = sizeof(mii); mii.fMask = MIIM_FTYPE | MIIM_ID | MIIM_STRING; mii.fType = MFT_STRING; if (!enabled) { mii.fMask |= MIIM_STATE; mii.fState = MFS_GRAYED; } mii.wID = menuId; mii.dwTypeData = const_cast(label_list.back().c_str()); InsertMenuItem(menu, -1, TRUE, &mii); } void AddMenuSeparator(HMENU menu) { MENUITEMINFO mii; mii.cbSize = sizeof(mii); mii.fMask = MIIM_FTYPE; mii.fType = MFT_SEPARATOR; InsertMenuItem(menu, -1, TRUE, &mii); } } // namespace // WebViewClient -------------------------------------------------------------- WebExternalPopupMenu* BrowserWebViewDelegate::createExternalPopupMenu( const WebPopupMenuInfo& info, WebExternalPopupMenuClient* client) { NOTREACHED(); return NULL; } // WebWidgetClient ------------------------------------------------------------ void BrowserWebViewDelegate::show(WebNavigationPolicy) { if (this == browser_->UIT_GetWebViewDelegate()) { if (!browser_->IsWindowRenderingDisabled()) { // Restore the window and bring it to the top if the window is currently // visible. HWND root = GetAncestor(browser_->UIT_GetMainWndHandle(), GA_ROOT); if (IsWindowVisible(root)) { ShowWindow(root, SW_SHOWNORMAL); SetWindowPos(root, HWND_TOP, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE); } } } else if (this == browser_->UIT_GetPopupDelegate()) { if (!browser_->IsWindowRenderingDisabled()) { // Show popup widgets without activation. ShowWindow(browser_->UIT_GetPopupWndHandle(), SW_SHOWNA); } else { // Notify the handler of popup visibility change. CefRefPtr client = browser_->GetClient(); if (client.get()) { CefRefPtr handler = client->GetRenderHandler(); if (handler.get()) handler->OnPopupShow(browser_, true); } } } } void BrowserWebViewDelegate::didChangeCursor(const WebCursorInfo& cursor_info) { if (WebWidgetHost* host = GetWidgetHost()) { current_cursor_.InitFromCursorInfo(cursor_info); HMODULE hModule = ::GetModuleHandle(L"libcef.dll"); if (!hModule) hModule = ::GetModuleHandle(NULL); HCURSOR hCursor = current_cursor_.GetCursor(hModule); if (!browser_->IsWindowRenderingDisabled()) { host->SetCursor(hCursor); } else { // Notify the handler of cursor change. CefRefPtr client = browser_->GetClient(); if (client.get()) { CefRefPtr handler = client->GetRenderHandler(); if (handler.get()) handler->OnCursorChange(browser_, hCursor); } } } } WebRect BrowserWebViewDelegate::windowRect() { if (WebWidgetHost* host = GetWidgetHost()) { if (!browser_->IsWindowRenderingDisabled()) { RECT rect; ::GetWindowRect(host->view_handle(), &rect); return gfx::Rect(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()) { // ignored } else if (this == browser_->UIT_GetPopupDelegate()) { if (!browser_->IsWindowRenderingDisabled()) { MoveWindow(browser_->UIT_GetPopupWndHandle(), rect.x, rect.y, rect.width, rect.height, FALSE); } else { browser_->set_popup_rect(rect); browser_->UIT_GetPopupHost()->SetSize(rect.width, rect.height); // Notify the handler of popup size change. CefRefPtr client = browser_->GetClient(); if (client.get()) { CefRefPtr handler = client->GetRenderHandler(); if (handler.get()) { handler->OnPopupSize(browser_, CefRect(rect.x, rect.y, rect.width, rect.height)); } } } } } WebRect BrowserWebViewDelegate::rootWindowRect() { if (WebWidgetHost* host = GetWidgetHost()) { RECT rect; HWND root_window = ::GetAncestor(host->view_handle(), GA_ROOT); ::GetWindowRect(root_window, &rect); return gfx::Rect(rect); } return WebRect(); } WebRect BrowserWebViewDelegate::windowResizerRect() { // Not necessary on Windows. return WebRect(); } void BrowserWebViewDelegate::startDragging( WebFrame* frame, const WebDragData& data, WebDragOperationsMask mask, const WebImage& image, const WebPoint& image_offset) { // Dragging is not supported when window rendering is disabled. if (browser_->settings().drag_drop_disabled || browser_->IsWindowRenderingDisabled()) { EndDragging(); 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))) { EndDragging(); return; } } } drag_delegate_ = new BrowserDragDelegate(this); drag_delegate_->StartDragging(drop_data, mask, image.getSkBitmap(), gfx::Point(image_offset).OffsetFromOrigin()); } void BrowserWebViewDelegate::runModal() { WebWidgetHost* host = GetWidgetHost(); if (!host) return; show(WebKit::WebNavigationPolicyNewWindow); browser_->UIT_SetIsModal(true); CefRefPtr client = browser_->GetClient(); CefRefPtr handler; if (client.get()) handler = client->GetLifeSpanHandler(); bool handled(false); if (handler.get()) { // Let the client override the modal message loop. handled = handler->RunModal(browser_); } if (!handled) { HWND child = ::GetAncestor(browser_->UIT_GetMainWndHandle(), GA_ROOT); HWND owner = ::GetAncestor(browser_->opener_window(), GA_ROOT); if (child && owner) { // Set the owner so that Windows keeps this window above the owner. ::SetWindowLong(child, GWL_HWNDPARENT, (LONG)owner); // Disable the owner if it is enabled so that you can't interact with it. // while this child window is open. if (::IsWindowEnabled(owner)) { ::EnableWindow(owner, FALSE); browser_->set_opener_was_disabled_by_modal_loop(true); } DWORD dwStyle = ::GetWindowLong(child, GWL_STYLE); DWORD dwNewStyle = dwStyle | WS_POPUP; if (dwStyle != dwNewStyle) ::SetWindowLong(child, GWL_STYLE, dwNewStyle); } // Tell the browser to exit this message loop when this window closes. browser_->set_internal_modal_message_loop_is_active(true); // Start a new message loop here and return when this window closes. MessageLoop* message_loop = MessageLoop::current(); bool old_state = message_loop->NestableTasksAllowed(); message_loop->SetNestableTasksAllowed(true); message_loop->Run(); message_loop->SetNestableTasksAllowed(old_state); } } // WebPluginPageDelegate ------------------------------------------------------ webkit::npapi::WebPluginDelegate* BrowserWebViewDelegate::CreatePluginDelegate( const FilePath& file_path, const std::string& mime_type) { WebViewHost* host = browser_->UIT_GetWebViewHost(); if (!host) return NULL; return WebPluginDelegateImpl::Create(file_path, mime_type); } void BrowserWebViewDelegate::CreatedPluginWindow( gfx::PluginWindowHandle handle) { if (!browser_->IsWindowRenderingDisabled()) { // Parent the plugin container to the existing browser window. HWND parent = browser_->UIT_GetWebViewHost()->view_handle(); DCHECK(parent != NULL); SetParent(handle, parent); } else { static bool registered_class = false; if (!registered_class) { WNDCLASSEX wcex = {0}; wcex.cbSize = sizeof(wcex); wcex.style = CS_DBLCLKS; wcex.lpfnWndProc = DefWindowProc; wcex.hInstance = GetModuleHandle(NULL); wcex.hCursor = LoadCursor(NULL, IDC_ARROW); wcex.lpszClassName = kPluginWindowClassName; RegisterClassEx(&wcex); registered_class = true; } // Parent windowed plugin containers to a hidden window. HWND parent = CreateWindow(kPluginWindowClassName, NULL, WS_OVERLAPPED|WS_CLIPCHILDREN|WS_CLIPSIBLINGS, 0, 0, 0, 0, NULL, NULL, GetModuleHandle(NULL), NULL); DCHECK(parent != NULL); SetParent(handle, parent); 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); // Destroy the hidden parent window. DestroyWindow(GetParent(handle)); } } void BrowserWebViewDelegate::DidMovePlugin( const webkit::npapi::WebPluginGeometry& move) { UINT flags = 0; if (move.rects_valid) { HRGN hrgn = ::CreateRectRgn(move.clip_rect.x(), move.clip_rect.y(), move.clip_rect.right(), move.clip_rect.bottom()); gfx::SubtractRectanglesFromRegion(hrgn, move.cutout_rects); // Note: System will own the hrgn after we call SetWindowRgn, // so we don't need to call DeleteObject(hrgn) ::SetWindowRgn(move.window, hrgn, FALSE); } else { flags |= (SWP_NOSIZE | SWP_NOMOVE); } if (move.visible) flags |= SWP_SHOWWINDOW; else flags |= SWP_HIDEWINDOW; ::SetWindowPos(move.window, NULL, move.window_rect.x(), move.window_rect.y(), move.window_rect.width(), move.window_rect.height(), flags); if (browser_->IsWindowRenderingDisabled()) { WebViewHost* host = browser_->UIT_GetWebViewHost(); if (host) { host->MoveWindowedPlugin(move); } } } void BrowserWebViewDelegate::showContextMenu( WebFrame* frame, const WebContextMenuData& data) { int screenX = -1, screenY = -1; POINT mouse_pt = {data.mousePosition.x, data.mousePosition.y}; if (!browser_->IsWindowRenderingDisabled()) { // Perform the conversion to screen coordinates only if window rendering is // enabled. MapWindowPoints(browser_->UIT_GetWebViewWndHandle(), HWND_DESKTOP, &mouse_pt, 1); screenX = mouse_pt.x; screenY = mouse_pt.y; } int edit_flags = 0; int type_flags = 0; HMENU menu = NULL; std::list label_list; // 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; } } // Build the correct default context menu if (type_flags & MENUTYPE_EDITABLE) { menu = CreatePopupMenu(); AddMenuItem(browser_, handler, menu, MENU_ID_UNDO, L"Undo", !!(edit_flags & MENU_CAN_UNDO), label_list); AddMenuItem(browser_, handler, menu, MENU_ID_REDO, L"Redo", !!(edit_flags & MENU_CAN_REDO), label_list); AddMenuSeparator(menu); AddMenuItem(browser_, handler, menu, MENU_ID_CUT, L"Cut", !!(edit_flags & MENU_CAN_CUT), label_list); AddMenuItem(browser_, handler, menu, MENU_ID_COPY, L"Copy", !!(edit_flags & MENU_CAN_COPY), label_list); AddMenuItem(browser_, handler, menu, MENU_ID_PASTE, L"Paste", !!(edit_flags & MENU_CAN_PASTE), label_list); AddMenuItem(browser_, handler, menu, MENU_ID_DELETE, L"Delete", !!(edit_flags & MENU_CAN_DELETE), label_list); AddMenuSeparator(menu); AddMenuItem(browser_, handler, menu, MENU_ID_SELECTALL, L"Select All", !!(edit_flags & MENU_CAN_SELECT_ALL), label_list); } else if (type_flags & MENUTYPE_SELECTION) { menu = CreatePopupMenu(); AddMenuItem(browser_, handler, menu, MENU_ID_COPY, L"Copy", !!(edit_flags & MENU_CAN_COPY), label_list); } else if (type_flags & (MENUTYPE_PAGE | MENUTYPE_FRAME)) { menu = CreatePopupMenu(); AddMenuItem(browser_, handler, menu, MENU_ID_NAV_BACK, L"Back", !!(edit_flags & MENU_CAN_GO_BACK), label_list); AddMenuItem(browser_, handler, menu, MENU_ID_NAV_FORWARD, L"Forward", !!(edit_flags & MENU_CAN_GO_FORWARD), label_list); AddMenuSeparator(menu); AddMenuItem(browser_, handler, menu, MENU_ID_PRINT, L"Print", true, label_list); AddMenuItem(browser_, handler, menu, MENU_ID_VIEWSOURCE, L"View Source", true, label_list); } if (!menu) return; MessageLoop::current()->set_os_modal_loop(true); // Show the context menu int selected_id = TrackPopupMenu(menu, TPM_LEFTALIGN | TPM_RIGHTBUTTON | TPM_RETURNCMD | TPM_RECURSE, screenX, screenY, 0, browser_->UIT_GetMainWndHandle(), NULL); MessageLoop::current()->set_os_modal_loop(false); if (selected_id != 0) { // An action was chosen cef_menu_id_t menuId = static_cast(selected_id); bool handled = false; if (handler.get()) { // Ask the handler if it wants to handle the action handled = handler->OnMenuAction(browser_, menuId); } if (!handled) { // Execute the action browser_->UIT_HandleAction(menuId, browser_->GetFocusedFrame()); } } DestroyMenu(menu); } // Private methods ------------------------------------------------------------ void BrowserWebViewDelegate::RegisterDragDrop() { DCHECK(!drop_target_); drop_target_ = new WebDropTarget(browser_); } void BrowserWebViewDelegate::RevokeDragDrop() { if (drop_target_.get()) ::RevokeDragDrop(browser_->UIT_GetWebViewWndHandle()); } void BrowserWebViewDelegate::EndDragging() { if (browser_->UIT_GetWebView()) browser_->UIT_GetWebView()->dragSourceSystemDragEnded(); drag_delegate_ = NULL; if (destroy_on_drag_end_) browser_->UIT_DestroyBrowser(); } void BrowserWebViewDelegate::ShowJavaScriptAlert(WebFrame* webframe, const CefString& message) { // TODO(cef): Think about what we should be showing as the prompt caption std::wstring messageStr = message; std::wstring titleStr = browser_->UIT_GetTitle(); MessageBox(browser_->UIT_GetMainWndHandle(), messageStr.c_str(), titleStr.c_str(), MB_OK | MB_ICONWARNING); } bool BrowserWebViewDelegate::ShowJavaScriptConfirm(WebFrame* webframe, const CefString& message) { // TODO(cef): Think about what we should be showing as the prompt caption std::wstring messageStr = message; std::wstring titleStr = browser_->UIT_GetTitle(); int rv = MessageBox(browser_->UIT_GetMainWndHandle(), messageStr.c_str(), titleStr.c_str(), MB_YESNO | MB_ICONQUESTION); return (rv == IDYES); } bool BrowserWebViewDelegate::ShowJavaScriptPrompt(WebFrame* webframe, const CefString& message, const CefString& default_value, CefString* result) { // TODO(cef): Implement a default prompt dialog return false; } namespace { // from chrome/browser/views/shell_dialogs_win.cc bool RunOpenFileDialog(const std::wstring& filter, HWND owner, FilePath* path) { OPENFILENAME ofn; // We must do this otherwise the ofn's FlagsEx may be initialized to random // junk in release builds which can cause the Places Bar not to show up! ZeroMemory(&ofn, sizeof(ofn)); ofn.lStructSize = sizeof(ofn); ofn.hwndOwner = owner; wchar_t filename[MAX_PATH]; base::wcslcpy(filename, path->value().c_str(), arraysize(filename)); ofn.lpstrFile = filename; ofn.nMaxFile = MAX_PATH; // We use OFN_NOCHANGEDIR so that the user can rename or delete the directory // without having to close Chrome first. ofn.Flags = OFN_FILEMUSTEXIST | OFN_NOCHANGEDIR; if (!filter.empty()) { ofn.lpstrFilter = filter.c_str(); } bool success = !!GetOpenFileName(&ofn); if (success) *path = FilePath(filename); return success; } bool RunOpenMultiFileDialog(const std::wstring& filter, HWND owner, std::vector* paths) { OPENFILENAME ofn; // We must do this otherwise the ofn's FlagsEx may be initialized to random // junk in release builds which can cause the Places Bar not to show up! ZeroMemory(&ofn, sizeof(ofn)); ofn.lStructSize = sizeof(ofn); ofn.hwndOwner = owner; scoped_array filename(new wchar_t[UNICODE_STRING_MAX_CHARS]); filename[0] = 0; ofn.lpstrFile = filename.get(); ofn.nMaxFile = UNICODE_STRING_MAX_CHARS; // We use OFN_NOCHANGEDIR so that the user can rename or delete the directory // without having to close Chrome first. ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_EXPLORER | OFN_HIDEREADONLY | OFN_ALLOWMULTISELECT; if (!filter.empty()) { ofn.lpstrFilter = filter.c_str(); } bool success = !!GetOpenFileName(&ofn); if (success) { std::vector files; const wchar_t* selection = ofn.lpstrFile; while (*selection) { // Empty string indicates end of list. files.push_back(FilePath(selection)); // Skip over filename and null-terminator. selection += files.back().value().length() + 1; } if (files.empty()) { success = false; } else if (files.size() == 1) { // When there is one file, it contains the path and filename. paths->swap(files); } else { // Otherwise, the first string is the path, and the remainder are // filenames. std::vector::iterator path = files.begin(); for (std::vector::iterator file = path + 1; file != files.end(); ++file) { paths->push_back(path->Append(*file)); } } } return success; } } // namespace bool BrowserWebViewDelegate::ShowFileChooser(std::vector& file_names, const bool multi_select, const WebKit::WebString& title, const FilePath& default_file) { bool result = false; if (multi_select) { result = RunOpenMultiFileDialog(L"", browser_->UIT_GetMainWndHandle(), &file_names); } else { FilePath file_name; result = RunOpenFileDialog(L"", browser_->UIT_GetMainWndHandle(), &file_name); if (result) file_names.push_back(file_name); } return result; }