541 lines
18 KiB
Plaintext
541 lines
18 KiB
Plaintext
// Copyright (c) 2008-2009 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 <Cocoa/Cocoa.h>
|
|
|
|
#include "base/compiler_specific.h"
|
|
#include "third_party/WebKit/Source/WebCore/config.h"
|
|
MSVC_PUSH_WARNING_LEVEL(0);
|
|
#include "KeyEventCocoa.h" // NOLINT(build/include)
|
|
MSVC_POP_WARNING();
|
|
|
|
#undef LOG
|
|
#import "libcef/webwidget_host.h"
|
|
#include "libcef/cef_thread.h"
|
|
|
|
#include "base/bind.h"
|
|
#import "base/logging.h"
|
|
#import "skia/ext/platform_canvas.h"
|
|
#import "third_party/WebKit/Source/WebKit/chromium/public/mac/WebInputEventFactory.h"
|
|
#import "third_party/WebKit/Source/WebKit/chromium/public/mac/WebScreenInfoFactory.h"
|
|
#import "third_party/WebKit/Source/WebKit/chromium/public/WebInputEvent.h"
|
|
#import "third_party/WebKit/Source/WebKit/chromium/public/WebPopupMenu.h"
|
|
#import "third_party/WebKit/Source/WebKit/chromium/public/WebScreenInfo.h"
|
|
#import "third_party/WebKit/Source/WebKit/chromium/public/platform/WebSize.h"
|
|
#import "third_party/skia/include/core/SkRegion.h"
|
|
#import "ui/gfx/rect.h"
|
|
#import "ui/gfx/size.h"
|
|
|
|
using webkit::npapi::WebPluginGeometry;
|
|
using WebKit::WebInputEvent;
|
|
using WebKit::WebInputEventFactory;
|
|
using WebKit::WebKeyboardEvent;
|
|
using WebKit::WebMouseEvent;
|
|
using WebKit::WebMouseWheelEvent;
|
|
using WebKit::WebPopupMenu;
|
|
using WebKit::WebScreenInfo;
|
|
using WebKit::WebScreenInfoFactory;
|
|
using WebKit::WebSize;
|
|
using WebKit::WebWidgetClient;
|
|
|
|
namespace {
|
|
|
|
inline SkIRect convertToSkiaRect(const gfx::Rect& r) {
|
|
return SkIRect::MakeLTRB(r.x(), r.y(), r.right(), r.bottom());
|
|
}
|
|
|
|
inline gfx::Rect convertFromSkiaRect(const SkIRect& r) {
|
|
return gfx::Rect(r.x(), r.y(), r.width(), r.height());
|
|
}
|
|
|
|
} // namespace
|
|
|
|
/*static*/
|
|
WebWidgetHost* WebWidgetHost::Create(NSView* parent_view,
|
|
WebWidgetClient* client,
|
|
PaintDelegate* paint_delegate) {
|
|
WebWidgetHost* host = new WebWidgetHost();
|
|
|
|
if (!paint_delegate) {
|
|
const NSRect bounds = [parent_view bounds];
|
|
host->view_ = [[NSView alloc] initWithFrame:bounds];
|
|
[parent_view addSubview:host->view_];
|
|
|
|
host->webwidget_ = WebPopupMenu::create(client);
|
|
host->webwidget_->resize(WebSize(NSWidth(bounds), NSHeight(bounds)));
|
|
} else {
|
|
host->paint_delegate_ = paint_delegate;
|
|
host->view_ = nil;
|
|
host->webwidget_ = WebPopupMenu::create(client);
|
|
}
|
|
|
|
return host;
|
|
}
|
|
|
|
WebWidgetHost::WebWidgetHost()
|
|
: view_(NULL),
|
|
paint_delegate_(NULL),
|
|
webwidget_(NULL),
|
|
canvas_w_(0),
|
|
canvas_h_(0),
|
|
popup_(false),
|
|
timer_executing_(false),
|
|
timer_wanted_(false),
|
|
frame_delay_(1000 / kDefaultFrameRate),
|
|
mouse_modifiers_(0),
|
|
painting_(false),
|
|
layouting_(false) {
|
|
set_painting(false);
|
|
}
|
|
|
|
WebWidgetHost::~WebWidgetHost() {
|
|
}
|
|
|
|
void WebWidgetHost::ScrollRect(int dx, int dy, const gfx::Rect& clip_rect) {
|
|
DCHECK(dx || dy);
|
|
|
|
int width, height;
|
|
GetSize(width, height);
|
|
const gfx::Rect client_rect(width, height);
|
|
gfx::Rect rect = clip_rect.Intersect(client_rect);
|
|
|
|
const int x = rect.x();
|
|
const int y = rect.y();
|
|
const int r = rect.right();
|
|
const int b = rect.bottom();
|
|
const int w = rect.width();
|
|
const int h = rect.height();
|
|
const int Dx = ABS(dx);
|
|
const int Dy = ABS(dy);
|
|
|
|
// If we're in a state right now where we cannot draw into the view then just
|
|
// mark the scrolling rect as dirty and it will be completely redrawn instead.
|
|
// The Paint() method can end up calling this method indirectly if the view
|
|
// needs to be laid out; calling scrollRect:by: in this situation leads to
|
|
// unwanted behavior. Finally, scrolling the rectangle by more than the size
|
|
// of the view means we can just invalidate the entire scroll rect.
|
|
if (!view_ || [view_ canDraw] || painting_ || layouting_ || Dx >= w ||
|
|
Dy >= h) {
|
|
InvalidateRect(clip_rect);
|
|
return;
|
|
}
|
|
|
|
// The scrolling rect must not scroll outside the clip rect because that will
|
|
// clobber the scrollbars. As a result we shorten the rectangle a bit from the
|
|
// leading side of the scroll (could be either horizontally or vertically).
|
|
rect = gfx::Rect(dx>=0? x: x + Dx, dy>=0? y: y + Dy, w - Dx, h - Dy);
|
|
|
|
// Convert the scroll rectangle to the view's coordinate system and perform
|
|
// the scroll directly without invalidating the view. In theory this could
|
|
// cause some kind of performance issue since we're not coalescing redraw
|
|
// events. In practice, however, we get much smoother scrolling of big views
|
|
// since just copying the pixels within the window is much faster than
|
|
// redrawing them.
|
|
NSRect cocoa_rect = NSRectFromCGRect(rect.ToCGRect());
|
|
cocoa_rect.origin.y = client_rect.height() - NSMaxY(cocoa_rect);
|
|
[view_ scrollRect:cocoa_rect by:NSMakeSize(dx, -dy)];
|
|
|
|
// Repaint the rectangle that was revealed when scrolling the given rectangle.
|
|
// The invalidated area will be painted before the view contents are flushed
|
|
// to the screen buffer.
|
|
rect = gfx::Rect(dx>=0? x: r - Dx, dy>=0? y: b - Dy,
|
|
dx>0? Dx: w, dy>0? Dy: h);
|
|
InvalidateRect(rect);
|
|
|
|
// If any part of the scrolled rect was marked as dirty make sure to redraw
|
|
// it in the new scrolled-to location. Otherwise we can end up with artifacts
|
|
// for overlapping elements. If there are multiple dirty regions in the scroll
|
|
// rect the rectangle union of those regions will be redrawn.
|
|
SkRegion moved_paint_rgn(paint_rgn_);
|
|
moved_paint_rgn.translate(dx, dy);
|
|
moved_paint_rgn.op(convertToSkiaRect(client_rect), SkRegion::kIntersect_Op);
|
|
InvalidateRect(convertFromSkiaRect(moved_paint_rgn.getBounds()));
|
|
}
|
|
|
|
void WebWidgetHost::Paint(SkRegion& update_rgn) {
|
|
int width, height;
|
|
GetSize(width, height);
|
|
gfx::Rect client_rect(width, height);
|
|
|
|
SkRegion damaged_rgn;
|
|
|
|
if (view_) {
|
|
// Union the rectangle that WebKit think needs repainting with the rectangle
|
|
// of the view that must be painted now. In most situations this will not
|
|
// affect the painted rectangle. In some situations we could end up re-
|
|
// painting areas that are already in the canvas and only dirty in the view
|
|
// itself. However, if we don't do this we can get artifacts when scrolling
|
|
// because contents of the canvas are no longer correct after scrolling only
|
|
// in the view.
|
|
paint_rgn_.op(update_rgn, SkRegion::kUnion_Op);
|
|
|
|
// When we are not using accelerated compositing the canvas area is allowed
|
|
// to differ in size from the client by a certain number of pixels (128 in
|
|
// this case). When accelerated compositing is in effect the size must match
|
|
// exactly.
|
|
const int extra_w = (webwidget_->isAcceleratedCompositingActive()? 0: 128);
|
|
const int extra_h = (webwidget_->isAcceleratedCompositingActive()? 0: 128);
|
|
const int min_w = client_rect.width();
|
|
const int min_h = client_rect.height();
|
|
const int max_w = client_rect.width() + extra_w * 2;
|
|
const int max_h = client_rect.height() + extra_h * 2;
|
|
|
|
const bool too_small = (canvas_w_ < min_w || canvas_h_ < min_h);
|
|
const bool too_large = (canvas_w_ > max_w || canvas_h_ > max_h);
|
|
|
|
if (!canvas_.get() || too_small || too_large) {
|
|
canvas_w_ = client_rect.width() + extra_w;
|
|
canvas_h_ = client_rect.height() + extra_h;
|
|
canvas_.reset(new skia::PlatformCanvas(canvas_w_, canvas_h_, true));
|
|
paint_rgn_.setRect(convertToSkiaRect(client_rect));
|
|
}
|
|
} else if (!canvas_.get() || canvas_w_ != client_rect.width() ||
|
|
canvas_h_ != client_rect.height()) {
|
|
paint_rgn_.setRect(convertToSkiaRect(client_rect));
|
|
|
|
// The canvas must be the exact size of the client area.
|
|
canvas_w_ = client_rect.width();
|
|
canvas_h_ = client_rect.height();
|
|
canvas_.reset(new skia::PlatformCanvas(canvas_w_, canvas_h_, true));
|
|
}
|
|
|
|
webwidget_->animate(0.0);
|
|
|
|
// Layout any views that have not been laid out yet. The layout may result in
|
|
// more invalidation. Keep track of the fact that we are laying out views,
|
|
// because this will sometimes cause ScrollRect to be called and we don't want
|
|
// to try to scrollRect:by: then.
|
|
layouting_ = true;
|
|
webwidget_->layout();
|
|
layouting_ = false;
|
|
|
|
// Draw into the graphics context of the canvas instead of the view's context.
|
|
// The view's context is pushed onto the context stack to be restored below.
|
|
CGContextRef bitmap = skia::GetBitmapContext(skia::GetTopDevice(*canvas_));
|
|
NSGraphicsContext* paint_context =
|
|
[NSGraphicsContext graphicsContextWithGraphicsPort:bitmap flipped:YES];
|
|
[NSGraphicsContext saveGraphicsState];
|
|
[NSGraphicsContext setCurrentContext:paint_context];
|
|
|
|
// Paint the canvas if necessary. The painting operation can cause additional
|
|
// regions to be invalidated because some elements are laid out the first time
|
|
// they are painted.
|
|
while (!paint_rgn_.isEmpty()) {
|
|
SkRegion draw_rgn;
|
|
draw_rgn.swap(paint_rgn_);
|
|
|
|
SkRegion::Cliperator iterator(draw_rgn, convertToSkiaRect(client_rect));
|
|
for (; !iterator.done(); iterator.next()) {
|
|
const SkIRect& r = iterator.rect();
|
|
PaintRect(convertFromSkiaRect(r));
|
|
|
|
if (!view_)
|
|
damaged_rgn.op(r, SkRegion::kUnion_Op);
|
|
}
|
|
|
|
if (view_) {
|
|
// If any more rectangles were made dirty during the paint operation, make
|
|
// sure they are copied to the window buffer, by including the paint
|
|
// region. If nothing needs additional painting, this is a no-op.
|
|
update_rgn.op(paint_rgn_, SkRegion::kUnion_Op);
|
|
}
|
|
}
|
|
|
|
if (!view_ && plugin_map_.size() > 0) {
|
|
// Flash seems to stop calling NPN_InvalidateRect, which means we stop
|
|
// painting. If we've got a plugin make sure we paint its rect each time.
|
|
|
|
PluginMap::const_iterator it = plugin_map_.begin();
|
|
for (; it != plugin_map_.end(); ++it) {
|
|
if (it->second.visible &&
|
|
client_rect.Intersects(it->second.window_rect)) {
|
|
const WebPluginGeometry* geom = &it->second;
|
|
damaged_rgn.op(convertToSkiaRect(geom->window_rect),
|
|
SkRegion::kUnion_Op);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (view_) {
|
|
// Set the context back to our view and copy the bitmap that we just painted
|
|
// into to the view. Only the regions that were updated are copied.
|
|
[NSGraphicsContext restoreGraphicsState];
|
|
NSGraphicsContext* view_context = [NSGraphicsContext currentContext];
|
|
CGContextRef context =
|
|
static_cast<CGContextRef>([view_context graphicsPort]);
|
|
|
|
SkRegion::Cliperator iterator(update_rgn, convertToSkiaRect(client_rect));
|
|
for (; !iterator.done(); iterator.next()) {
|
|
const SkIRect& r = iterator.rect();
|
|
CGRect copy_rect = { { r.x(), r.y() }, { r.width(), r.height() } };
|
|
const float x = r.x();
|
|
const float y = client_rect.height() - r.bottom();
|
|
skia::DrawToNativeContext(canvas_.get(), context, x, y, ©_rect);
|
|
}
|
|
} else {
|
|
if (damaged_rgn.isEmpty())
|
|
return;
|
|
|
|
// Paint to the delegate.
|
|
DCHECK(paint_delegate_);
|
|
const SkBitmap& bitmap = canvas_->getDevice()->accessBitmap(false);
|
|
DCHECK(bitmap.config() == SkBitmap::kARGB_8888_Config);
|
|
const void* pixels = bitmap.getPixels();
|
|
|
|
std::vector<CefRect> damaged_rects;
|
|
SkRegion::Cliperator iterator(damaged_rgn, convertToSkiaRect(client_rect));
|
|
for (; !iterator.done(); iterator.next()) {
|
|
const SkIRect& r = iterator.rect();
|
|
damaged_rects.push_back(
|
|
CefRect(r.left(), r.top(), r.width(), r.height()));
|
|
}
|
|
|
|
paint_delegate_->Paint(popup_, damaged_rects, pixels);
|
|
}
|
|
}
|
|
|
|
void WebWidgetHost::SetTooltipText(const CefString& tooltip_text) {
|
|
// TODO(port): Implement this method as part of tooltip support.
|
|
}
|
|
|
|
WebScreenInfo WebWidgetHost::GetScreenInfo() {
|
|
return WebScreenInfoFactory::screenInfo(view_);
|
|
}
|
|
|
|
void WebWidgetHost::MouseEvent(NSEvent *event) {
|
|
const WebMouseEvent& web_event = WebInputEventFactory::mouseEvent(
|
|
event, view_);
|
|
webwidget_->handleInputEvent(web_event);
|
|
}
|
|
|
|
void WebWidgetHost::WheelEvent(NSEvent *event) {
|
|
webwidget_->handleInputEvent(
|
|
WebInputEventFactory::mouseWheelEvent(event, view_));
|
|
}
|
|
|
|
void WebWidgetHost::KeyEvent(NSEvent *event) {
|
|
WebKeyboardEvent keyboard_event(WebInputEventFactory::keyboardEvent(event));
|
|
last_key_event_ = keyboard_event;
|
|
webwidget_->handleInputEvent(keyboard_event);
|
|
}
|
|
|
|
void WebWidgetHost::SetFocus(bool enable) {
|
|
webwidget_->setFocus(enable);
|
|
}
|
|
|
|
void WebWidgetHost::SendKeyEvent(cef_key_type_t type,
|
|
const cef_key_info_t& keyInfo,
|
|
int modifiers) {
|
|
WebKeyboardEvent event;
|
|
|
|
switch (type) {
|
|
case KT_KEYUP:
|
|
default:
|
|
event.type = WebInputEvent::KeyUp;
|
|
break;
|
|
case KT_KEYDOWN:
|
|
event.type = WebInputEvent::RawKeyDown;
|
|
break;
|
|
case KT_CHAR:
|
|
event.type = WebInputEvent::Char;
|
|
break;
|
|
}
|
|
event.timeStampSeconds = TickCount();
|
|
|
|
if (modifiers & KEY_SHIFT)
|
|
event.modifiers |= WebInputEvent::ShiftKey;
|
|
if (modifiers & KEY_ALT)
|
|
event.modifiers |= WebInputEvent::AltKey;
|
|
if (modifiers & KEY_CTRL)
|
|
event.modifiers |= WebInputEvent::ControlKey;
|
|
if (modifiers & KEY_META)
|
|
event.modifiers |= WebInputEvent::MetaKey;
|
|
if (modifiers & KEY_KEYPAD)
|
|
event.modifiers |= WebInputEvent::IsKeyPad;
|
|
|
|
// There are several kinds of characters for which we produce key code from
|
|
// char code:
|
|
// 1. Roman letters. Windows keyboard layouts affect both virtual key codes
|
|
// and character codes for these, so e.g. 'A' gets the same keyCode on
|
|
// QWERTY, AZERTY or Dvorak layouts.
|
|
// 2. Keys for which there is no known Mac virtual key codes, like
|
|
// PrintScreen.
|
|
// 3. Certain punctuation keys. On Windows, these are also remapped depending
|
|
// on current keyboard layout, but see comment in
|
|
// windowsKeyCodeForCharCode().
|
|
|
|
if (type == KT_KEYUP || type == KT_KEYDOWN) {
|
|
if (keyInfo.character != 0) {
|
|
// Cmd switches Roman letters for Dvorak-QWERTY layout, so try modified
|
|
// characters first.
|
|
event.windowsKeyCode =
|
|
WebCore::windowsKeyCodeForCharCode(keyInfo.character);
|
|
}
|
|
if (event.windowsKeyCode == 0 && keyInfo.characterNoModifiers != 0) {
|
|
// Ctrl+A on an AZERTY keyboard would get VK_Q keyCode if we relied on
|
|
// keyInfo.keyCode below.
|
|
event.windowsKeyCode =
|
|
WebCore::windowsKeyCodeForCharCode(keyInfo.characterNoModifiers);
|
|
}
|
|
}
|
|
|
|
if (event.windowsKeyCode == 0) {
|
|
// Map Mac virtual key code directly to Windows one for any keys not handled
|
|
// above. E.g. the key next to Caps Lock has the same Event.keyCode on U.S.
|
|
// keyboard ('A') and on Russian keyboard (CYRILLIC LETTER EF).
|
|
event.windowsKeyCode = WebCore::windowsKeyCodeForKeyCode(keyInfo.keyCode);
|
|
}
|
|
|
|
event.nativeKeyCode = keyInfo.keyCode;
|
|
|
|
int textChar = keyInfo.character;
|
|
int unmodifiedChar = keyInfo.characterNoModifiers;
|
|
|
|
// Always use 13 for Enter/Return -- we don't want to use AppKit's
|
|
// different character for Enter.
|
|
if (event.windowsKeyCode == '\r') {
|
|
textChar = '\r';
|
|
unmodifiedChar = '\r';
|
|
}
|
|
|
|
// The adjustments below are only needed in backward compatibility mode,
|
|
// but we cannot tell what mode we are in from here.
|
|
|
|
// Turn 0x7F into 8, because backspace needs to always be 8.
|
|
if (textChar == '\x7F')
|
|
textChar = '\x8';
|
|
if (unmodifiedChar == '\x7F')
|
|
unmodifiedChar = '\x8';
|
|
// Always use 9 for tab -- we don't want to use AppKit's different character
|
|
// for shift-tab.
|
|
if (event.windowsKeyCode == 9) {
|
|
textChar = '\x9';
|
|
unmodifiedChar = '\x9';
|
|
}
|
|
|
|
event.text[0] = textChar;
|
|
event.unmodifiedText[0] = unmodifiedChar;
|
|
|
|
event.setKeyIdentifierFromWindowsKeyCode();
|
|
|
|
event.isSystemKey = !!(modifiers & KEY_META);
|
|
|
|
last_key_event_ = event;
|
|
|
|
webwidget_->handleInputEvent(event);
|
|
}
|
|
|
|
void WebWidgetHost::SendMouseClickEvent(int x, int y,
|
|
cef_mouse_button_type_t type,
|
|
bool mouseUp, int clickCount) {
|
|
WebMouseEvent event;
|
|
|
|
switch(type) {
|
|
case MBT_LEFT:
|
|
event.button = WebMouseEvent::ButtonLeft;
|
|
event.modifiers |= WebInputEvent::LeftButtonDown;
|
|
break;
|
|
case MBT_MIDDLE:
|
|
event.button = WebMouseEvent::ButtonMiddle;
|
|
event.modifiers |= WebInputEvent::MiddleButtonDown;
|
|
break;
|
|
case MBT_RIGHT:
|
|
event.button = WebMouseEvent::ButtonRight;
|
|
event.modifiers |= WebInputEvent::RightButtonDown;
|
|
break;
|
|
}
|
|
|
|
if (mouseUp)
|
|
event.type = WebInputEvent::MouseUp;
|
|
else
|
|
event.type = WebInputEvent::MouseDown;
|
|
|
|
event.clickCount = clickCount;
|
|
event.timeStampSeconds = TickCount();
|
|
event.x = x;
|
|
event.y = y;
|
|
event.windowX = event.x;
|
|
event.windowY = event.y;
|
|
|
|
if (mouseUp) {
|
|
mouse_button_down_ = WebMouseEvent::ButtonNone;
|
|
mouse_modifiers_ &= ~event.modifiers;
|
|
} else {
|
|
mouse_modifiers_ |= event.modifiers;
|
|
mouse_button_down_ = event.button;
|
|
}
|
|
event.modifiers = mouse_modifiers_;
|
|
|
|
webwidget_->handleInputEvent(event);
|
|
}
|
|
|
|
void WebWidgetHost::SendMouseMoveEvent(int x, int y, bool mouseLeave) {
|
|
WebMouseEvent event;
|
|
|
|
event.type = WebInputEvent::MouseMove;
|
|
event.timeStampSeconds = TickCount();
|
|
event.button = mouse_button_down_;
|
|
event.x = x;
|
|
event.y = y;
|
|
event.windowX = event.x;
|
|
event.windowY = event.y;
|
|
event.modifiers = mouse_modifiers_;
|
|
|
|
webwidget_->handleInputEvent(event);
|
|
}
|
|
|
|
void WebWidgetHost::SendMouseWheelEvent(int x, int y, int deltaX, int deltaY) {
|
|
WebMouseWheelEvent event;
|
|
|
|
// Conversion between wheel delta amounts and number of pixels to scroll.
|
|
static const double scrollbarPixelsPerCocoaTick = 40.0;
|
|
|
|
event.type = WebInputEvent::MouseWheel;
|
|
event.timeStampSeconds = TickCount();
|
|
event.button = WebMouseEvent::ButtonNone;
|
|
event.deltaX = static_cast<float>(deltaX);
|
|
event.deltaY = static_cast<float>(deltaY);
|
|
event.wheelTicksX = static_cast<float>(deltaX/scrollbarPixelsPerCocoaTick);
|
|
event.wheelTicksY = static_cast<float>(deltaY/scrollbarPixelsPerCocoaTick);
|
|
event.hasPreciseScrollingDeltas = true;
|
|
event.x = x;
|
|
event.y = y;
|
|
event.windowX = event.x;
|
|
event.windowY = event.y;
|
|
|
|
webwidget_->handleInputEvent(event);
|
|
}
|
|
|
|
void WebWidgetHost::SendFocusEvent(bool setFocus) {
|
|
SetFocus(setFocus);
|
|
}
|
|
|
|
void WebWidgetHost::SendCaptureLostEvent() {
|
|
}
|
|
|
|
void WebWidgetHost::InvalidateWindow() {
|
|
DCHECK(view_);
|
|
[view_ setNeedsDisplay:YES];
|
|
}
|
|
|
|
void WebWidgetHost::InvalidateWindowRect(const gfx::Rect& rect) {
|
|
DCHECK(view_);
|
|
|
|
int width, height;
|
|
GetSize(width, height);
|
|
const gfx::Rect client_rect(width, height);
|
|
|
|
NSRect cocoa_rect = NSRectFromCGRect(rect.ToCGRect());
|
|
cocoa_rect.origin.y = client_rect.height() - NSMaxY(cocoa_rect);
|
|
[view_ setNeedsDisplayInRect:cocoa_rect];
|
|
}
|
|
|
|
void WebWidgetHost::EnsureTooltip() {
|
|
// TODO(port): Implement this method as part of tooltip support.
|
|
}
|
|
|
|
void WebWidgetHost::ResetTooltip() {
|
|
// TODO(port): Implement this method as part of tooltip support.
|
|
}
|