#include "config.h"
#include "WebView.h"
#include "APIPageConfiguration.h"
#include "DrawingAreaProxyImpl.h"
#include "Logging.h"
#include "NativeWebKeyboardEvent.h"
#include "NativeWebMouseEvent.h"
#include "NativeWebWheelEvent.h"
#include "WKAPICast.h"
#include "WebContextMenuProxyWin.h"
#include "WebEditCommandProxy.h"
#include "WebEventFactory.h"
#include "WebPageGroup.h"
#include "WebPageProxy.h"
#include "WebProcessPool.h"
#include <Commctrl.h>
#include <WebCore/BitmapInfo.h>
#include <WebCore/Cursor.h>
#include <WebCore/Editor.h>
#include <WebCore/FileSystem.h>
#include <WebCore/FloatRect.h>
#include <WebCore/HWndDC.h>
#include <WebCore/IntRect.h>
#include <WebCore/NotImplemented.h>
#include <WebCore/Region.h>
#include <WebCore/WebCoreInstanceHandle.h>
#include <WebCore/WindowMessageBroadcaster.h>
#include <WebCore/WindowsTouch.h>
#include <cairo-win32.h>
#include <cairo.h>
#include <wtf/SoftLinking.h>
#include <wtf/text/StringBuffer.h>
#include <wtf/text/StringBuilder.h>
#include <wtf/text/WTFString.h>
using namespace WebCore;
namespace WebKit {
static const LPCWSTR kWebKit2WebViewWindowClassName = L"WebKit2WebViewWindowClass";
static const int kMaxToolTipWidth = 250;
enum {
UpdateActiveStateTimer = 1,
};
LRESULT CALLBACK WebView::WebViewWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
LONG_PTR longPtr = ::GetWindowLongPtr(hWnd, 0);
if (WebView* webView = reinterpret_cast<WebView*>(longPtr))
return webView->wndProc(hWnd, message, wParam, lParam);
if (message == WM_CREATE) {
LPCREATESTRUCT createStruct = reinterpret_cast<LPCREATESTRUCT>(lParam);
::SetWindowLongPtr(hWnd, 0, (LONG_PTR)createStruct->lpCreateParams);
return 0;
}
return ::DefWindowProc(hWnd, message, wParam, lParam);
}
LRESULT WebView::wndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
LRESULT lResult = 0;
bool handled = true;
switch (message) {
case WM_CLOSE:
m_page->tryClose();
break;
case WM_DESTROY:
m_isBeingDestroyed = true;
close();
break;
case WM_ERASEBKGND:
lResult = 1;
break;
case WM_PAINT:
lResult = onPaintEvent(hWnd, message, wParam, lParam, handled);
break;
case WM_PRINTCLIENT:
lResult = onPrintClientEvent(hWnd, message, wParam, lParam, handled);
break;
case WM_MOUSEACTIVATE:
setWasActivatedByMouseEvent(true);
handled = false;
break;
case WM_MOUSEMOVE:
case WM_LBUTTONDOWN:
case WM_MBUTTONDOWN:
case WM_RBUTTONDOWN:
case WM_LBUTTONDBLCLK:
case WM_MBUTTONDBLCLK:
case WM_RBUTTONDBLCLK:
case WM_LBUTTONUP:
case WM_MBUTTONUP:
case WM_RBUTTONUP:
case WM_MOUSELEAVE:
lResult = onMouseEvent(hWnd, message, wParam, lParam, handled);
break;
case WM_MOUSEWHEEL:
lResult = onWheelEvent(hWnd, message, wParam, lParam, handled);
break;
case WM_HSCROLL:
lResult = onHorizontalScroll(hWnd, message, wParam, lParam, handled);
break;
case WM_VSCROLL:
lResult = onVerticalScroll(hWnd, message, wParam, lParam, handled);
break;
case WM_SYSKEYDOWN:
case WM_KEYDOWN:
case WM_SYSCHAR:
case WM_CHAR:
case WM_SYSKEYUP:
case WM_KEYUP:
lResult = onKeyEvent(hWnd, message, wParam, lParam, handled);
break;
case WM_SIZE:
lResult = onSizeEvent(hWnd, message, wParam, lParam, handled);
break;
case WM_WINDOWPOSCHANGED:
lResult = onWindowPositionChangedEvent(hWnd, message, wParam, lParam, handled);
break;
case WM_SETFOCUS:
lResult = onSetFocusEvent(hWnd, message, wParam, lParam, handled);
break;
case WM_KILLFOCUS:
lResult = onKillFocusEvent(hWnd, message, wParam, lParam, handled);
break;
case WM_TIMER:
lResult = onTimerEvent(hWnd, message, wParam, lParam, handled);
break;
case WM_SHOWWINDOW:
lResult = onShowWindowEvent(hWnd, message, wParam, lParam, handled);
break;
case WM_SETCURSOR:
lResult = onSetCursor(hWnd, message, wParam, lParam, handled);
break;
case WM_MENUCOMMAND:
lResult = onMenuCommand(hWnd, message, wParam, lParam, handled);
break;
default:
handled = false;
break;
}
if (!handled)
lResult = ::DefWindowProc(hWnd, message, wParam, lParam);
return lResult;
}
bool WebView::registerWebViewWindowClass()
{
static bool haveRegisteredWindowClass = false;
if (haveRegisteredWindowClass)
return true;
haveRegisteredWindowClass = true;
WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_DBLCLKS;
wcex.lpfnWndProc = WebView::WebViewWndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = sizeof(WebView*);
wcex.hInstance = instanceHandle();
wcex.hIcon = 0;
wcex.hCursor = ::LoadCursor(0, IDC_ARROW);
wcex.hbrBackground = 0;
wcex.lpszMenuName = 0;
wcex.lpszClassName = kWebKit2WebViewWindowClassName;
wcex.hIconSm = 0;
return !!::RegisterClassEx(&wcex);
}
WebView::WebView(RECT rect, const API::PageConfiguration& configuration, HWND parentWindow)
: m_pageClient(std::make_unique<PageClientImpl>(*this))
{
registerWebViewWindowClass();
m_window = ::CreateWindowExW(0, kWebKit2WebViewWindowClassName, 0, WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_VISIBLE,
rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, parentWindow ? parentWindow : HWND_MESSAGE, 0, instanceHandle(), this);
ASSERT(::IsWindow(m_window));
ASSERT(m_isVisible == static_cast<bool>(::GetWindowLong(m_window, GWL_STYLE) & WS_VISIBLE));
auto pageConfiguration = configuration.copy();
auto* preferences = pageConfiguration->preferences();
if (!preferences && pageConfiguration->pageGroup()) {
preferences = &pageConfiguration->pageGroup()->preferences();
pageConfiguration->setPreferences(preferences);
}
if (preferences) {
preferences->setAcceleratedCompositingEnabled(false);
}
WebProcessPool* processPool = pageConfiguration->processPool();
m_page = processPool->createWebPage(*m_pageClient, WTFMove(pageConfiguration));
m_page->initializeWebPage();
if (m_page->drawingArea())
m_page->drawingArea()->setSize(IntSize(rect.right - rect.left, rect.bottom - rect.top));
initializeToolTipWindow();
windowAncestryDidChange();
}
WebView::~WebView()
{
if (::IsWindow(m_toolTipWindow))
::DestroyWindow(m_toolTipWindow);
}
void WebView::initialize()
{
if (shouldInitializeTrackPointHack()) {
::CreateWindow(TEXT("SCROLLBAR"), TEXT("FAKETRACKPOINTHSCROLLBAR"), WS_CHILD | WS_VISIBLE | SBS_HORZ, 0, 0, 0, 0, m_window, 0, instanceHandle(), 0);
::CreateWindow(TEXT("SCROLLBAR"), TEXT("FAKETRACKPOINTVSCROLLBAR"), WS_CHILD | WS_VISIBLE | SBS_VERT, 0, 0, 0, 0, m_window, 0, instanceHandle(), 0);
}
}
void WebView::setParentWindow(HWND parentWindow)
{
if (m_window) {
if (::GetParent(m_window) == parentWindow)
return;
if (parentWindow)
::SetParent(m_window, parentWindow);
else if (!m_isBeingDestroyed) {
::SetParent(m_window, HWND_MESSAGE);
}
}
windowAncestryDidChange();
}
static HWND findTopLevelParentWindow(HWND window)
{
if (!window)
return 0;
HWND current = window;
for (HWND parent = GetParent(current); current; current = parent, parent = GetParent(parent)) {
if (!parent || !(GetWindowLongPtr(current, GWL_STYLE) & (WS_POPUP | WS_CHILD)))
return current;
}
ASSERT_NOT_REACHED();
return 0;
}
void WebView::windowAncestryDidChange()
{
HWND newTopLevelParentWindow;
if (m_window)
newTopLevelParentWindow = findTopLevelParentWindow(m_window);
else {
newTopLevelParentWindow = 0;
}
if (newTopLevelParentWindow == m_topLevelParentWindow)
return;
if (m_topLevelParentWindow)
WindowMessageBroadcaster::removeListener(m_topLevelParentWindow, this);
m_topLevelParentWindow = newTopLevelParentWindow;
if (m_topLevelParentWindow)
WindowMessageBroadcaster::addListener(m_topLevelParentWindow, this);
updateActiveState();
}
LRESULT WebView::onMouseEvent(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, bool& handled)
{
NativeWebMouseEvent mouseEvent = NativeWebMouseEvent(hWnd, message, wParam, lParam, m_wasActivatedByMouseEvent);
setWasActivatedByMouseEvent(false);
switch (message) {
case WM_LBUTTONDOWN:
case WM_MBUTTONDOWN:
case WM_RBUTTONDOWN:
::SetFocus(m_window);
::SetCapture(m_window);
break;
case WM_LBUTTONUP:
case WM_MBUTTONUP:
case WM_RBUTTONUP:
::ReleaseCapture();
break;
case WM_MOUSEMOVE:
startTrackingMouseLeave();
break;
case WM_MOUSELEAVE:
stopTrackingMouseLeave();
break;
case WM_LBUTTONDBLCLK:
case WM_MBUTTONDBLCLK:
case WM_RBUTTONDBLCLK:
break;
default:
ASSERT_NOT_REACHED();
}
m_page->handleMouseEvent(mouseEvent);
handled = true;
return 0;
}
LRESULT WebView::onWheelEvent(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, bool& handled)
{
NativeWebWheelEvent wheelEvent(hWnd, message, wParam, lParam);
if (wheelEvent.controlKey()) {
handled = false;
return 0;
}
m_page->handleWheelEvent(wheelEvent);
handled = true;
return 0;
}
LRESULT WebView::onHorizontalScroll(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, bool& handled)
{
ScrollDirection direction;
ScrollGranularity granularity;
switch (LOWORD(wParam)) {
case SB_LINELEFT:
granularity = ScrollByLine;
direction = ScrollLeft;
break;
case SB_LINERIGHT:
granularity = ScrollByLine;
direction = ScrollRight;
break;
case SB_PAGELEFT:
granularity = ScrollByDocument;
direction = ScrollLeft;
break;
case SB_PAGERIGHT:
granularity = ScrollByDocument;
direction = ScrollRight;
break;
default:
handled = false;
return 0;
}
m_page->scrollBy(direction, granularity);
handled = true;
return 0;
}
LRESULT WebView::onVerticalScroll(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, bool& handled)
{
ScrollDirection direction;
ScrollGranularity granularity;
switch (LOWORD(wParam)) {
case SB_LINEDOWN:
granularity = ScrollByLine;
direction = ScrollDown;
break;
case SB_LINEUP:
granularity = ScrollByLine;
direction = ScrollUp;
break;
case SB_PAGEDOWN:
granularity = ScrollByDocument;
direction = ScrollDown;
break;
case SB_PAGEUP:
granularity = ScrollByDocument;
direction = ScrollUp;
break;
default:
handled = false;
return 0;
}
m_page->scrollBy(direction, granularity);
handled = true;
return 0;
}
LRESULT WebView::onKeyEvent(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, bool& handled)
{
m_page->handleKeyboardEvent(NativeWebKeyboardEvent(hWnd, message, wParam, lParam));
handled = true;
return 0;
}
static void drawPageBackground(HDC dc, const WebPageProxy* page, const RECT& rect)
{
if (!page->drawsBackground())
return;
::FillRect(dc, &rect, reinterpret_cast<HBRUSH>(COLOR_WINDOW + 1));
}
void WebView::paint(HDC hdc, const IntRect& dirtyRect)
{
if (dirtyRect.isEmpty())
return;
m_page->endPrinting();
if (DrawingAreaProxyImpl* drawingArea = static_cast<DrawingAreaProxyImpl*>(m_page->drawingArea())) {
Region unpaintedRegion;
cairo_surface_t* surface = cairo_win32_surface_create(hdc);
cairo_t* context = cairo_create(surface);
drawingArea->paint(context, dirtyRect, unpaintedRegion);
cairo_destroy(context);
cairo_surface_destroy(surface);
Vector<IntRect> unpaintedRects = unpaintedRegion.rects();
for (size_t i = 0; i < unpaintedRects.size(); ++i) {
RECT winRect = unpaintedRects[i];
drawPageBackground(hdc, m_page.get(), unpaintedRects[i]);
}
} else
drawPageBackground(hdc, m_page.get(), dirtyRect);
}
LRESULT WebView::onPaintEvent(HWND hWnd, UINT message, WPARAM, LPARAM, bool& handled)
{
PAINTSTRUCT paintStruct;
HDC hdc = ::BeginPaint(m_window, &paintStruct);
paint(hdc, paintStruct.rcPaint);
::EndPaint(m_window, &paintStruct);
handled = true;
return 0;
}
LRESULT WebView::onPrintClientEvent(HWND hWnd, UINT, WPARAM wParam, LPARAM, bool& handled)
{
HDC hdc = reinterpret_cast<HDC>(wParam);
RECT winRect;
::GetClientRect(hWnd, &winRect);
paint(hdc, winRect);
handled = true;
return 0;
}
LRESULT WebView::onSizeEvent(HWND, UINT, WPARAM, LPARAM lParam, bool& handled)
{
int width = LOWORD(lParam);
int height = HIWORD(lParam);
if (m_page && m_page->drawingArea()) {
m_page->drawingArea()->setSize(IntSize(width, height), m_nextResizeScrollOffset);
m_nextResizeScrollOffset = IntSize();
}
handled = true;
return 0;
}
LRESULT WebView::onWindowPositionChangedEvent(HWND, UINT, WPARAM, LPARAM lParam, bool& handled)
{
if (reinterpret_cast<WINDOWPOS*>(lParam)->flags & SWP_SHOWWINDOW)
updateActiveStateSoon();
handled = false;
return 0;
}
LRESULT WebView::onSetFocusEvent(HWND, UINT, WPARAM, LPARAM lParam, bool& handled)
{
m_page->activityStateDidChange(ActivityState::IsFocused);
handled = true;
return 0;
}
LRESULT WebView::onKillFocusEvent(HWND, UINT, WPARAM, LPARAM lParam, bool& handled)
{
m_page->activityStateDidChange(ActivityState::IsFocused);
handled = true;
return 0;
}
LRESULT WebView::onTimerEvent(HWND hWnd, UINT, WPARAM wParam, LPARAM, bool& handled)
{
switch (wParam) {
case UpdateActiveStateTimer:
::KillTimer(hWnd, UpdateActiveStateTimer);
updateActiveState();
break;
}
handled = true;
return 0;
}
LRESULT WebView::onShowWindowEvent(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, bool& handled)
{
if (!lParam)
setIsVisible(wParam);
handled = false;
return 0;
}
LRESULT WebView::onSetCursor(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, bool& handled)
{
if (!m_lastCursorSet) {
handled = false;
return 0;
}
::SetCursor(m_lastCursorSet);
return 0;
}
LRESULT WebView::onMenuCommand(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, bool& handled)
{
auto hMenu = reinterpret_cast<HMENU>(lParam);
auto index = static_cast<unsigned>(wParam);
MENUITEMINFO menuItemInfo;
menuItemInfo.cbSize = sizeof(menuItemInfo);
menuItemInfo.cch = 0;
menuItemInfo.fMask = MIIM_STRING;
::GetMenuItemInfo(hMenu, index, TRUE, &menuItemInfo);
menuItemInfo.cch++;
Vector<WCHAR> buffer(menuItemInfo.cch);
menuItemInfo.dwTypeData = buffer.data();
menuItemInfo.fMask |= MIIM_ID;
::GetMenuItemInfo(hMenu, index, TRUE, &menuItemInfo);
String title(buffer.data(), menuItemInfo.cch);
ContextMenuAction action = static_cast<ContextMenuAction>(menuItemInfo.wID);
bool enabled = !(menuItemInfo.fState & MFS_DISABLED);
bool checked = menuItemInfo.fState & MFS_CHECKED;
WebContextMenuItemData item(ContextMenuItemType::ActionType, action, title, enabled, checked);
m_page->contextMenuItemSelected(item);
handled = true;
return 0;
}
void WebView::updateActiveState()
{
m_page->activityStateDidChange(ActivityState::WindowIsActive);
}
void WebView::updateActiveStateSoon()
{
::SetTimer(m_window, UpdateActiveStateTimer, 0, 0);
}
static bool initCommonControls()
{
static bool haveInitialized = false;
if (haveInitialized)
return true;
INITCOMMONCONTROLSEX init;
init.dwSize = sizeof(init);
init.dwICC = ICC_TREEVIEW_CLASSES;
haveInitialized = !!::InitCommonControlsEx(&init);
return haveInitialized;
}
void WebView::initializeToolTipWindow()
{
if (!initCommonControls())
return;
m_toolTipWindow = ::CreateWindowEx(WS_EX_TRANSPARENT, TOOLTIPS_CLASS, 0, WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
m_window, 0, 0, 0);
if (!m_toolTipWindow)
return;
TOOLINFO info = { 0 };
info.cbSize = sizeof(info);
info.uFlags = TTF_IDISHWND | TTF_SUBCLASS;
info.uId = reinterpret_cast<UINT_PTR>(m_window);
::SendMessage(m_toolTipWindow, TTM_ADDTOOL, 0, reinterpret_cast<LPARAM>(&info));
::SendMessage(m_toolTipWindow, TTM_SETMAXTIPWIDTH, 0, kMaxToolTipWidth);
::SetWindowPos(m_toolTipWindow, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
}
void WebView::startTrackingMouseLeave()
{
if (m_trackingMouseLeave)
return;
m_trackingMouseLeave = true;
TRACKMOUSEEVENT trackMouseEvent;
trackMouseEvent.cbSize = sizeof(TRACKMOUSEEVENT);
trackMouseEvent.dwFlags = TME_LEAVE;
trackMouseEvent.hwndTrack = m_window;
::TrackMouseEvent(&trackMouseEvent);
}
void WebView::stopTrackingMouseLeave()
{
if (!m_trackingMouseLeave)
return;
m_trackingMouseLeave = false;
TRACKMOUSEEVENT trackMouseEvent;
trackMouseEvent.cbSize = sizeof(TRACKMOUSEEVENT);
trackMouseEvent.dwFlags = TME_LEAVE | TME_CANCEL;
trackMouseEvent.hwndTrack = m_window;
::TrackMouseEvent(&trackMouseEvent);
}
bool WebView::shouldInitializeTrackPointHack()
{
static bool shouldCreateScrollbars;
static bool hasRunTrackPointCheck;
if (hasRunTrackPointCheck)
return shouldCreateScrollbars;
hasRunTrackPointCheck = true;
const wchar_t* trackPointKeys[] = {
L"Software\\Lenovo\\TrackPoint",
L"Software\\Lenovo\\UltraNav",
L"Software\\Alps\\Apoint\\TrackPoint",
L"Software\\Synaptics\\SynTPEnh\\UltraNavUSB",
L"Software\\Synaptics\\SynTPEnh\\UltraNavPS2"
};
for (size_t i = 0; i < WTF_ARRAY_LENGTH(trackPointKeys); ++i) {
HKEY trackPointKey;
int readKeyResult = ::RegOpenKeyExW(HKEY_CURRENT_USER, trackPointKeys[i], 0, KEY_READ, &trackPointKey);
::RegCloseKey(trackPointKey);
if (readKeyResult == ERROR_SUCCESS) {
shouldCreateScrollbars = true;
return shouldCreateScrollbars;
}
}
return shouldCreateScrollbars;
}
void WebView::close()
{
if (m_window) {
if (!m_isBeingDestroyed)
DestroyWindow(m_window);
m_window = 0;
}
setParentWindow(0);
m_page->close();
}
HCURSOR WebView::cursorToShow() const
{
if (!m_page->isValid())
return 0;
static HCURSOR arrowCursor = ::LoadCursor(0, IDC_ARROW);
if (m_overrideCursor && m_webCoreCursor == arrowCursor)
return m_overrideCursor;
return m_webCoreCursor;
}
void WebView::updateNativeCursor()
{
m_lastCursorSet = cursorToShow();
if (!m_lastCursorSet)
return;
::SetCursor(m_lastCursorSet);
}
void WebView::setOverrideCursor(HCURSOR overrideCursor)
{
m_overrideCursor = overrideCursor;
updateNativeCursor();
}
void WebView::setIsInWindow(bool isInWindow)
{
m_isInWindow = isInWindow;
if (m_page)
m_page->activityStateDidChange(ActivityState::IsInWindow);
}
void WebView::setIsVisible(bool isVisible)
{
m_isVisible = isVisible;
if (m_page)
m_page->activityStateDidChange(ActivityState::IsVisible);
}
bool WebView::isWindowActive()
{
HWND activeWindow = ::GetActiveWindow();
return (activeWindow && m_topLevelParentWindow == findTopLevelParentWindow(activeWindow));
}
bool WebView::isFocused()
{
return ::GetFocus() == m_window;
}
bool WebView::isVisible()
{
return m_isVisible;
}
bool WebView::isInWindow()
{
return m_isInWindow;
}
void WebView::setScrollOffsetOnNextResize(const IntSize& scrollOffset)
{
m_nextResizeScrollOffset = scrollOffset;
}
void WebView::setViewNeedsDisplay(const WebCore::Region& region)
{
const RECT r = region.bounds();
::InvalidateRect(m_window, &r, true);
}
#if ENABLE(INPUT_TYPE_COLOR)
PassRefPtr<WebColorChooserProxy> WebView::createColorChooserProxy(WebPageProxy*, const WebCore::Color&, const WebCore::IntRect&)
{
notImplemented();
return 0;
}
#endif
void WebView::didCommitLoadForMainFrame(bool useCustomRepresentation)
{
}
double WebView::customRepresentationZoomFactor()
{
return 1;
}
void WebView::setCustomRepresentationZoomFactor(double)
{
}
void WebView::findStringInCustomRepresentation(const String&, FindOptions, unsigned)
{
}
void WebView::countStringMatchesInCustomRepresentation(const String&, FindOptions, unsigned)
{
}
HWND WebView::nativeWindow()
{
return m_window;
}
void WebView::windowReceivedMessage(HWND, UINT message, WPARAM wParam, LPARAM)
{
switch (message) {
case WM_NCACTIVATE:
updateActiveStateSoon();
break;
case WM_SETTINGCHANGE:
break;
}
}
}