| // Copyright (c) 2012 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 "content/browser/renderer_host/render_widget_host_view_mac.h" |
| |
| #import <Carbon/Carbon.h> |
| #import <objc/runtime.h> |
| #include <OpenGL/gl.h> |
| #include <QuartzCore/QuartzCore.h> |
| #include <stdint.h> |
| |
| #include <limits> |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/callback_helpers.h" |
| #include "base/command_line.h" |
| #include "base/debug/crash_logging.h" |
| #include "base/logging.h" |
| #include "base/mac/scoped_cftyperef.h" |
| #import "base/mac/scoped_nsobject.h" |
| #include "base/mac/sdk_forward_declarations.h" |
| #include "base/macros.h" |
| #include "base/memory/ref_counted.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/numerics/safe_conversions.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/strings/sys_string_conversions.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/sys_info.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "base/trace_event/trace_event.h" |
| #import "content/browser/accessibility/browser_accessibility_cocoa.h" |
| #import "content/browser/accessibility/browser_accessibility_mac.h" |
| #include "content/browser/accessibility/browser_accessibility_manager_mac.h" |
| #import "content/browser/cocoa/system_hotkey_helper_mac.h" |
| #import "content/browser/cocoa/system_hotkey_map.h" |
| #include "content/browser/frame_host/frame_tree.h" |
| #include "content/browser/frame_host/frame_tree_node.h" |
| #include "content/browser/frame_host/render_frame_host_impl.h" |
| #include "content/browser/gpu/compositor_util.h" |
| #import "content/browser/renderer_host/input/synthetic_gesture_target_mac.h" |
| #include "content/browser/renderer_host/input/web_input_event_builders_mac.h" |
| #include "content/browser/renderer_host/render_view_host_impl.h" |
| #include "content/browser/renderer_host/render_widget_helper.h" |
| #include "content/browser/renderer_host/render_widget_host_delegate.h" |
| #include "content/browser/renderer_host/render_widget_host_input_event_router.h" |
| #include "content/browser/renderer_host/render_widget_host_view_frame_subscriber.h" |
| #import "content/browser/renderer_host/render_widget_host_view_mac_dictionary_helper.h" |
| #import "content/browser/renderer_host/render_widget_host_view_mac_editcommand_helper.h" |
| #import "content/browser/renderer_host/text_input_client_mac.h" |
| #include "content/common/accessibility_messages.h" |
| #include "content/common/edit_command.h" |
| #include "content/common/input_messages.h" |
| #include "content/common/site_isolation_policy.h" |
| #include "content/common/text_input_state.h" |
| #include "content/common/view_messages.h" |
| #include "content/public/browser/browser_context.h" |
| #include "content/public/browser/browser_plugin_guest_manager.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/native_web_keyboard_event.h" |
| #include "content/public/browser/render_widget_host.h" |
| #import "content/public/browser/render_widget_host_view_mac_delegate.h" |
| #include "content/public/browser/web_contents.h" |
| #include "gpu/ipc/common/gpu_messages.h" |
| #include "media/base/video_frame.h" |
| #include "skia/ext/platform_canvas.h" |
| #include "skia/ext/skia_utils_mac.h" |
| #include "third_party/WebKit/public/platform/WebInputEvent.h" |
| #import "ui/base/clipboard/clipboard_util_mac.h" |
| #include "ui/base/cocoa/animation_utils.h" |
| #import "ui/base/cocoa/appkit_utils.h" |
| #include "ui/base/cocoa/cocoa_base_utils.h" |
| #import "ui/base/cocoa/fullscreen_window_manager.h" |
| #import "ui/base/cocoa/underlay_opengl_hosting_window.h" |
| #include "ui/compositor/compositor.h" |
| #include "ui/compositor/layer.h" |
| #include "ui/display/display.h" |
| #include "ui/display/screen.h" |
| #include "ui/events/base_event_utils.h" |
| #include "ui/events/event_utils.h" |
| #include "ui/events/keycodes/keyboard_codes.h" |
| #include "ui/gfx/geometry/dip_util.h" |
| #include "ui/gfx/geometry/point.h" |
| #include "ui/gfx/geometry/rect_conversions.h" |
| #include "ui/gfx/geometry/size_conversions.h" |
| #include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h" |
| #include "ui/gl/gl_switches.h" |
| |
| using content::BrowserAccessibility; |
| using content::BrowserAccessibilityManager; |
| using content::EditCommand; |
| using content::FrameTreeNode; |
| using content::NativeWebKeyboardEvent; |
| using content::RenderFrameHost; |
| using content::RenderViewHost; |
| using content::RenderViewHostImpl; |
| using content::RenderWidgetHostImpl; |
| using content::RenderWidgetHostView; |
| using content::RenderWidgetHostViewMac; |
| using content::RenderWidgetHostViewMacEditCommandHelper; |
| using content::TextInputClientMac; |
| using content::WebContents; |
| using content::WebGestureEventBuilder; |
| using content::WebMouseEventBuilder; |
| using content::WebMouseWheelEventBuilder; |
| using blink::WebInputEvent; |
| using blink::WebMouseEvent; |
| using blink::WebMouseWheelEvent; |
| using blink::WebGestureEvent; |
| |
| namespace { |
| |
| // Whether a keyboard event has been reserved by OSX. |
| BOOL EventIsReservedBySystem(NSEvent* event) { |
| content::SystemHotkeyHelperMac* helper = |
| content::SystemHotkeyHelperMac::GetInstance(); |
| return helper->map()->IsEventReserved(event); |
| } |
| |
| RenderWidgetHostView* GetRenderWidgetHostViewToUse( |
| RenderWidgetHostViewMac* render_widget_host_view) { |
| WebContents* web_contents = render_widget_host_view->GetWebContents(); |
| if (!web_contents) |
| return render_widget_host_view; |
| content::BrowserPluginGuestManager* guest_manager = |
| web_contents->GetBrowserContext()->GetGuestManager(); |
| if (!guest_manager) |
| return render_widget_host_view; |
| content::WebContents* guest = |
| guest_manager->GetFullPageGuest(web_contents); |
| if (!guest) |
| return render_widget_host_view; |
| return guest->GetRenderWidgetHostView(); |
| } |
| |
| } // namespace |
| |
| // These are not documented, so use only after checking -respondsToSelector:. |
| @interface NSApplication (UndocumentedSpeechMethods) |
| - (void)speakString:(NSString*)string; |
| - (void)stopSpeaking:(id)sender; |
| - (BOOL)isSpeaking; |
| @end |
| |
| // Private methods: |
| @interface RenderWidgetHostViewCocoa () |
| @property(nonatomic, assign) NSRange selectedRange; |
| @property(nonatomic, assign) NSRange markedRange; |
| |
| - (BOOL)shouldAutohideCursorForEvent:(NSEvent*)event; |
| - (id)initWithRenderWidgetHostViewMac:(RenderWidgetHostViewMac*)r; |
| - (void)processedWheelEvent:(const blink::WebMouseWheelEvent&)event |
| consumed:(BOOL)consumed; |
| - (void)processedGestureScrollEvent:(const blink::WebGestureEvent&)event |
| consumed:(BOOL)consumed; |
| |
| - (void)keyEvent:(NSEvent*)theEvent wasKeyEquivalent:(BOOL)equiv; |
| - (void)windowDidChangeBackingProperties:(NSNotification*)notification; |
| - (void)windowChangedGlobalFrame:(NSNotification*)notification; |
| - (void)windowDidBecomeKey:(NSNotification*)notification; |
| - (void)windowDidResignKey:(NSNotification*)notification; |
| - (void)updateScreenProperties; |
| - (void)setResponderDelegate: |
| (NSObject<RenderWidgetHostViewMacDelegate>*)delegate; |
| - (void)showLookUpDictionaryOverlayInternal:(NSAttributedString*) string |
| baselinePoint:(NSPoint) baselinePoint |
| targetView:(NSView*) view; |
| @end |
| |
| // A window subclass that allows the fullscreen window to become main and gain |
| // keyboard focus. This is only used for pepper flash. Normal fullscreen is |
| // handled by the browser. |
| @interface PepperFlashFullscreenWindow : UnderlayOpenGLHostingWindow |
| @end |
| |
| @implementation PepperFlashFullscreenWindow |
| |
| - (BOOL)canBecomeKeyWindow { |
| return YES; |
| } |
| |
| - (BOOL)canBecomeMainWindow { |
| return YES; |
| } |
| |
| @end |
| |
| @interface RenderWidgetPopupWindow : NSWindow { |
| // The event tap that allows monitoring of all events, to properly close with |
| // a click outside the bounds of the window. |
| id clickEventTap_; |
| } |
| @end |
| |
| @implementation RenderWidgetPopupWindow |
| |
| - (id)initWithContentRect:(NSRect)contentRect |
| styleMask:(NSUInteger)windowStyle |
| backing:(NSBackingStoreType)bufferingType |
| defer:(BOOL)deferCreation { |
| if (self = [super initWithContentRect:contentRect |
| styleMask:windowStyle |
| backing:bufferingType |
| defer:deferCreation]) { |
| [self setOpaque:NO]; |
| [self setBackgroundColor:[NSColor clearColor]]; |
| [self startObservingClicks]; |
| } |
| return self; |
| } |
| |
| - (void)close { |
| [self stopObservingClicks]; |
| [super close]; |
| } |
| |
| // Gets called when the menubar is clicked. |
| // Needed because the local event monitor doesn't see the click on the menubar. |
| - (void)beganTracking:(NSNotification*)notification { |
| [self close]; |
| } |
| |
| // Install the callback. |
| - (void)startObservingClicks { |
| clickEventTap_ = [NSEvent addLocalMonitorForEventsMatchingMask:NSAnyEventMask |
| handler:^NSEvent* (NSEvent* event) { |
| if ([event window] == self) |
| return event; |
| NSEventType eventType = [event type]; |
| if (eventType == NSLeftMouseDown || eventType == NSRightMouseDown) |
| [self close]; |
| return event; |
| }]; |
| |
| NSNotificationCenter* notificationCenter = |
| [NSNotificationCenter defaultCenter]; |
| [notificationCenter addObserver:self |
| selector:@selector(beganTracking:) |
| name:NSMenuDidBeginTrackingNotification |
| object:[NSApp mainMenu]]; |
| } |
| |
| // Remove the callback. |
| - (void)stopObservingClicks { |
| if (!clickEventTap_) |
| return; |
| |
| [NSEvent removeMonitor:clickEventTap_]; |
| clickEventTap_ = nil; |
| |
| NSNotificationCenter* notificationCenter = |
| [NSNotificationCenter defaultCenter]; |
| [notificationCenter removeObserver:self |
| name:NSMenuDidBeginTrackingNotification |
| object:[NSApp mainMenu]]; |
| } |
| |
| @end |
| |
| namespace { |
| |
| // Maximum number of characters we allow in a tooltip. |
| const size_t kMaxTooltipLength = 1024; |
| |
| // TODO(suzhe): Upstream this function. |
| blink::WebColor WebColorFromNSColor(NSColor *color) { |
| CGFloat r, g, b, a; |
| [color getRed:&r green:&g blue:&b alpha:&a]; |
| |
| return |
| std::max(0, std::min(static_cast<int>(lroundf(255.0f * a)), 255)) << 24 | |
| std::max(0, std::min(static_cast<int>(lroundf(255.0f * r)), 255)) << 16 | |
| std::max(0, std::min(static_cast<int>(lroundf(255.0f * g)), 255)) << 8 | |
| std::max(0, std::min(static_cast<int>(lroundf(255.0f * b)), 255)); |
| } |
| |
| // Extract underline information from an attributed string. Mostly copied from |
| // third_party/WebKit/Source/WebKit/mac/WebView/WebHTMLView.mm |
| void ExtractUnderlines( |
| NSAttributedString* string, |
| std::vector<blink::WebCompositionUnderline>* underlines) { |
| int length = [[string string] length]; |
| int i = 0; |
| while (i < length) { |
| NSRange range; |
| NSDictionary* attrs = [string attributesAtIndex:i |
| longestEffectiveRange:&range |
| inRange:NSMakeRange(i, length - i)]; |
| if (NSNumber *style = [attrs objectForKey:NSUnderlineStyleAttributeName]) { |
| blink::WebColor color = SK_ColorBLACK; |
| if (NSColor *colorAttr = |
| [attrs objectForKey:NSUnderlineColorAttributeName]) { |
| color = WebColorFromNSColor( |
| [colorAttr colorUsingColorSpaceName:NSDeviceRGBColorSpace]); |
| } |
| underlines->push_back( |
| blink::WebCompositionUnderline(range.location, |
| NSMaxRange(range), |
| color, |
| [style intValue] > 1, |
| SK_ColorTRANSPARENT)); |
| } |
| i = range.location + range.length; |
| } |
| } |
| |
| // EnablePasswordInput() and DisablePasswordInput() are copied from |
| // enableSecureTextInput() and disableSecureTextInput() functions in |
| // third_party/WebKit/WebCore/platform/SecureTextInput.cpp |
| // But we don't call EnableSecureEventInput() and DisableSecureEventInput() |
| // here, because they are already called in webkit and they are system wide |
| // functions. |
| void EnablePasswordInput() { |
| CFArrayRef inputSources = TISCreateASCIICapableInputSourceList(); |
| TSMSetDocumentProperty(0, kTSMDocumentEnabledInputSourcesPropertyTag, |
| sizeof(CFArrayRef), &inputSources); |
| CFRelease(inputSources); |
| } |
| |
| void DisablePasswordInput() { |
| TSMRemoveDocumentProperty(0, kTSMDocumentEnabledInputSourcesPropertyTag); |
| } |
| |
| // Calls to [NSScreen screens], required by FlipYFromRectToScreen and |
| // FlipNSRectToRectScreen, can take several milliseconds. Only re-compute this |
| // value when screen info changes. |
| // TODO(ccameron): An observer on every RWHVCocoa will set this to false |
| // on NSApplicationDidChangeScreenParametersNotification. Only one observer |
| // is necessary. |
| bool g_screen_info_up_to_date = false; |
| |
| float FlipYFromRectToScreen(float y, float rect_height) { |
| TRACE_EVENT0("browser", "FlipYFromRectToScreen"); |
| static CGFloat screen_zero_height = 0; |
| if (!g_screen_info_up_to_date) { |
| if ([[NSScreen screens] count] > 0) { |
| screen_zero_height = |
| [[[NSScreen screens] firstObject] frame].size.height; |
| g_screen_info_up_to_date = true; |
| } else { |
| return y; |
| } |
| } |
| return screen_zero_height - y - rect_height; |
| } |
| |
| // Adjusts an NSRect in Cocoa screen coordinates to have an origin in the upper |
| // left of the primary screen (Carbon coordinates), and stuffs it into a |
| // gfx::Rect. |
| gfx::Rect FlipNSRectToRectScreen(const NSRect& rect) { |
| gfx::Rect new_rect(NSRectToCGRect(rect)); |
| new_rect.set_y(FlipYFromRectToScreen(new_rect.y(), new_rect.height())); |
| return new_rect; |
| } |
| |
| // Returns the window that visually contains the given view. This is different |
| // from [view window] in the case of tab dragging, where the view's owning |
| // window is a floating panel attached to the actual browser window that the tab |
| // is visually part of. |
| NSWindow* ApparentWindowForView(NSView* view) { |
| // TODO(shess): In case of !window, the view has been removed from |
| // the view hierarchy because the tab isn't main. Could retrieve |
| // the information from the main tab for our window. |
| NSWindow* enclosing_window = [view window]; |
| |
| // See if this is a tab drag window. The width check is to distinguish that |
| // case from extension popup windows. |
| NSWindow* ancestor_window = [enclosing_window parentWindow]; |
| if (ancestor_window && (NSWidth([enclosing_window frame]) == |
| NSWidth([ancestor_window frame]))) { |
| enclosing_window = ancestor_window; |
| } |
| |
| return enclosing_window; |
| } |
| |
| } // namespace |
| |
| namespace content { |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // BrowserCompositorMacClient, public: |
| |
| NSView* RenderWidgetHostViewMac::BrowserCompositorMacGetNSView() const { |
| return cocoa_view_; |
| } |
| |
| SkColor RenderWidgetHostViewMac::BrowserCompositorMacGetGutterColor( |
| SkColor color) const { |
| // When making an element on the page fullscreen the element's background |
| // may not match the page's, so use black as the gutter color to avoid |
| // flashes of brighter colors during the transition. |
| if (render_widget_host_->delegate() && |
| render_widget_host_->delegate()->IsFullscreenForCurrentTab()) { |
| return SK_ColorBLACK; |
| } |
| return color; |
| } |
| |
| void RenderWidgetHostViewMac::BrowserCompositorMacOnBeginFrame() { |
| UpdateNeedsBeginFramesInternal(); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // AcceleratedWidgetMacNSView, public: |
| |
| NSView* RenderWidgetHostViewMac::AcceleratedWidgetGetNSView() const { |
| return cocoa_view_; |
| } |
| |
| void RenderWidgetHostViewMac::AcceleratedWidgetGetVSyncParameters( |
| base::TimeTicks* timebase, base::TimeDelta* interval) const { |
| if (display_link_ && |
| display_link_->GetVSyncParameters(timebase, interval)) |
| return; |
| *timebase = base::TimeTicks(); |
| *interval = base::TimeDelta(); |
| } |
| |
| void RenderWidgetHostViewMac::AcceleratedWidgetSwapCompleted() { |
| // Set the background color for the root layer from the frame that just |
| // swapped. See RenderWidgetHostViewAura for more details. Note that this is |
| // done only after the swap has completed, so that the background is not set |
| // before the frame is up. |
| UpdateBackgroundColorFromRenderer(last_frame_root_background_color_); |
| |
| if (display_link_) |
| display_link_->NotifyCurrentTime(base::TimeTicks::Now()); |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // RenderWidgetHostViewMac, public: |
| |
| RenderWidgetHostViewMac::RenderWidgetHostViewMac(RenderWidgetHost* widget, |
| bool is_guest_view_hack) |
| : render_widget_host_(RenderWidgetHostImpl::From(widget)), |
| page_at_minimum_scale_(true), |
| mouse_wheel_phase_handler_(RenderWidgetHostImpl::From(widget), this), |
| is_loading_(false), |
| allow_pause_for_resize_or_repaint_(true), |
| is_guest_view_hack_(is_guest_view_hack), |
| fullscreen_parent_host_view_(nullptr), |
| weak_factory_(this) { |
| // |cocoa_view_| owns us and we will be deleted when |cocoa_view_| |
| // goes away. Since we autorelease it, our caller must put |
| // |GetNativeView()| into the view hierarchy right after calling us. |
| cocoa_view_ = [[[RenderWidgetHostViewCocoa alloc] |
| initWithRenderWidgetHostViewMac:this] autorelease]; |
| |
| background_layer_.reset([[CALayer alloc] init]); |
| [cocoa_view_ setLayer:background_layer_]; |
| [cocoa_view_ setWantsLayer:YES]; |
| |
| cc::FrameSinkId frame_sink_id = |
| render_widget_host_->AllocateFrameSinkId(is_guest_view_hack_); |
| browser_compositor_.reset( |
| new BrowserCompositorMac(this, this, render_widget_host_->is_hidden(), |
| [cocoa_view_ window], frame_sink_id)); |
| |
| display::Screen::GetScreen()->AddObserver(this); |
| |
| if (!is_guest_view_hack_) |
| render_widget_host_->SetView(this); |
| |
| // Let the page-level input event router know about our surface ID |
| // namespace for surface-based hit testing. |
| if (render_widget_host_->delegate() && |
| render_widget_host_->delegate()->GetInputEventRouter()) { |
| render_widget_host_->delegate()->GetInputEventRouter()->AddFrameSinkIdOwner( |
| GetFrameSinkId(), this); |
| } |
| |
| RenderViewHost* rvh = RenderViewHost::From(render_widget_host_); |
| bool needs_begin_frames = true; |
| |
| if (rvh) { |
| // TODO(mostynb): actually use prefs. Landing this as a separate CL |
| // first to rebaseline some unreliable layout tests. |
| ignore_result(rvh->GetWebkitPreferences()); |
| needs_begin_frames = !rvh->GetDelegate()->IsNeverVisible(); |
| } |
| |
| if (GetTextInputManager()) |
| GetTextInputManager()->AddObserver(this); |
| |
| // Because of the way Mac pumps messages during resize, (see the code |
| // in RenderMessageFilter::OnMessageReceived), SetNeedsBeginFrame |
| // messages are not delayed on Mac. This leads to creation-time |
| // raciness where renderer sends a SetNeedsBeginFrame(true) before |
| // the renderer host is created to recieve it. |
| // |
| // Any renderer that will produce frames needs to have begin frames sent to |
| // it. So unless it is never visible, start this value at true here to avoid |
| // startup raciness and decrease latency. |
| needs_begin_frames_ = needs_begin_frames; |
| UpdateNeedsBeginFramesInternal(); |
| } |
| |
| RenderWidgetHostViewMac::~RenderWidgetHostViewMac() { |
| display::Screen::GetScreen()->RemoveObserver(this); |
| |
| // This is being called from |cocoa_view_|'s destructor, so invalidate the |
| // pointer. |
| cocoa_view_ = nil; |
| |
| UnlockMouse(); |
| |
| browser_compositor_.reset(); |
| |
| // We are owned by RenderWidgetHostViewCocoa, so if we go away before the |
| // RenderWidgetHost does we need to tell it not to hold a stale pointer to |
| // us. |
| if (render_widget_host_) { |
| // If this is a RenderWidgetHostViewGuest's platform_view_, we're not the |
| // RWH's view, the RenderWidgetHostViewGuest is. So don't reset the RWH's |
| // view, the RenderWidgetHostViewGuest will do it. |
| if (!is_guest_view_hack_) |
| render_widget_host_->SetView(NULL); |
| } |
| |
| // In case the view is deleted (by cocoa view) before calling destroy, we need |
| // to remove this view from the observer list of TextInputManager. |
| if (text_input_manager_ && text_input_manager_->HasObserver(this)) |
| text_input_manager_->RemoveObserver(this); |
| } |
| |
| void RenderWidgetHostViewMac::SetDelegate( |
| NSObject<RenderWidgetHostViewMacDelegate>* delegate) { |
| [cocoa_view_ setResponderDelegate:delegate]; |
| } |
| |
| void RenderWidgetHostViewMac::SetAllowPauseForResizeOrRepaint(bool allow) { |
| allow_pause_for_resize_or_repaint_ = allow; |
| } |
| |
| cc::SurfaceId RenderWidgetHostViewMac::SurfaceIdForTesting() const { |
| return browser_compositor_->GetDelegatedFrameHost()->SurfaceIdForTesting(); |
| } |
| |
| ui::TextInputType RenderWidgetHostViewMac::GetTextInputType() { |
| if (!GetActiveWidget()) |
| return ui::TEXT_INPUT_TYPE_NONE; |
| return text_input_manager_->GetTextInputState()->type; |
| } |
| |
| RenderWidgetHostImpl* RenderWidgetHostViewMac::GetActiveWidget() { |
| return text_input_manager_ ? text_input_manager_->GetActiveWidget() : nullptr; |
| } |
| |
| const TextInputManager::CompositionRangeInfo* |
| RenderWidgetHostViewMac::GetCompositionRangeInfo() { |
| return text_input_manager_ ? text_input_manager_->GetCompositionRangeInfo() |
| : nullptr; |
| } |
| |
| const TextInputManager::TextSelection* |
| RenderWidgetHostViewMac::GetTextSelection() { |
| return text_input_manager_ ? text_input_manager_->GetTextSelection( |
| GetFocusedViewForTextSelection()) |
| : nullptr; |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // RenderWidgetHostViewMac, RenderWidgetHostView implementation: |
| |
| bool RenderWidgetHostViewMac::OnMessageReceived(const IPC::Message& message) { |
| bool handled = true; |
| IPC_BEGIN_MESSAGE_MAP(RenderWidgetHostViewMac, message) |
| IPC_MESSAGE_HANDLER(ViewMsg_GetRenderedTextCompleted, |
| OnGetRenderedTextCompleted) |
| IPC_MESSAGE_UNHANDLED(handled = false) |
| IPC_END_MESSAGE_MAP() |
| return handled; |
| } |
| |
| void RenderWidgetHostViewMac::InitAsChild( |
| gfx::NativeView parent_view) { |
| } |
| |
| void RenderWidgetHostViewMac::InitAsPopup( |
| RenderWidgetHostView* parent_host_view, |
| const gfx::Rect& pos) { |
| bool activatable = popup_type_ == blink::kWebPopupTypeNone; |
| [cocoa_view_ setCloseOnDeactivate:YES]; |
| [cocoa_view_ setCanBeKeyView:activatable ? YES : NO]; |
| |
| NSPoint origin_global = NSPointFromCGPoint(pos.origin().ToCGPoint()); |
| origin_global.y = FlipYFromRectToScreen(origin_global.y, pos.height()); |
| |
| popup_window_.reset([[RenderWidgetPopupWindow alloc] |
| initWithContentRect:NSMakeRect(origin_global.x, origin_global.y, |
| pos.width(), pos.height()) |
| styleMask:NSBorderlessWindowMask |
| backing:NSBackingStoreBuffered |
| defer:NO]); |
| [popup_window_ setLevel:NSPopUpMenuWindowLevel]; |
| [popup_window_ setReleasedWhenClosed:NO]; |
| [popup_window_ makeKeyAndOrderFront:nil]; |
| [[popup_window_ contentView] addSubview:cocoa_view_]; |
| [cocoa_view_ setFrame:[[popup_window_ contentView] bounds]]; |
| [cocoa_view_ setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; |
| [[NSNotificationCenter defaultCenter] |
| addObserver:cocoa_view_ |
| selector:@selector(popupWindowWillClose:) |
| name:NSWindowWillCloseNotification |
| object:popup_window_]; |
| } |
| |
| // This function creates the fullscreen window and hides the dock and menubar if |
| // necessary. Note, this codepath is only used for pepper flash when |
| // pp::FlashFullScreen::SetFullscreen() is called. If |
| // pp::FullScreen::SetFullscreen() is called then the entire browser window |
| // will enter fullscreen instead. |
| void RenderWidgetHostViewMac::InitAsFullscreen( |
| RenderWidgetHostView* reference_host_view) { |
| fullscreen_parent_host_view_ = |
| static_cast<RenderWidgetHostViewMac*>(reference_host_view); |
| NSWindow* parent_window = nil; |
| if (reference_host_view) |
| parent_window = [reference_host_view->GetNativeView() window]; |
| NSScreen* screen = [parent_window screen]; |
| if (!screen) |
| screen = [NSScreen mainScreen]; |
| |
| pepper_fullscreen_window_.reset([[PepperFlashFullscreenWindow alloc] |
| initWithContentRect:[screen frame] |
| styleMask:NSBorderlessWindowMask |
| backing:NSBackingStoreBuffered |
| defer:NO]); |
| [pepper_fullscreen_window_ setLevel:NSFloatingWindowLevel]; |
| [pepper_fullscreen_window_ setReleasedWhenClosed:NO]; |
| [cocoa_view_ setCanBeKeyView:YES]; |
| [cocoa_view_ setFrame:[[pepper_fullscreen_window_ contentView] bounds]]; |
| [cocoa_view_ setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; |
| // If the pepper fullscreen window isn't opaque then there are performance |
| // issues when it's on the discrete GPU and the Chrome window is being drawn |
| // to. http://crbug.com/171911 |
| [pepper_fullscreen_window_ setOpaque:YES]; |
| |
| // Note that this forms a reference cycle between the fullscreen window and |
| // the rwhvmac: The PepperFlashFullscreenWindow retains cocoa_view_, |
| // but cocoa_view_ keeps pepper_fullscreen_window_ in an instance variable. |
| // This cycle is normally broken when -keyEvent: receives an <esc> key, which |
| // explicitly calls Shutdown on the render_widget_host_, which calls |
| // Destroy() on RWHVMac, which drops the reference to |
| // pepper_fullscreen_window_. |
| [[pepper_fullscreen_window_ contentView] addSubview:cocoa_view_]; |
| |
| // Note that this keeps another reference to pepper_fullscreen_window_. |
| fullscreen_window_manager_.reset([[FullscreenWindowManager alloc] |
| initWithWindow:pepper_fullscreen_window_.get() |
| desiredScreen:screen]); |
| [fullscreen_window_manager_ enterFullscreenMode]; |
| [pepper_fullscreen_window_ makeKeyAndOrderFront:nil]; |
| } |
| |
| void RenderWidgetHostViewMac::release_pepper_fullscreen_window_for_testing() { |
| // See comment in InitAsFullscreen(): There is a reference cycle between |
| // rwhvmac and fullscreen window, which is usually broken by hitting <esc>. |
| // Tests that test pepper fullscreen mode without sending an <esc> event |
| // need to call this method to break the reference cycle. |
| [fullscreen_window_manager_ exitFullscreenMode]; |
| fullscreen_window_manager_.reset(); |
| [pepper_fullscreen_window_ close]; |
| pepper_fullscreen_window_.reset(); |
| } |
| |
| int RenderWidgetHostViewMac::window_number() const { |
| NSWindow* window = [cocoa_view_ window]; |
| if (!window) |
| return -1; |
| return [window windowNumber]; |
| } |
| |
| void RenderWidgetHostViewMac::UpdateDisplayLink() { |
| static bool is_vsync_disabled = |
| base::CommandLine::ForCurrentProcess()->HasSwitch( |
| switches::kDisableGpuVsync); |
| if (is_vsync_disabled) |
| return; |
| |
| NSScreen* screen = [[cocoa_view_ window] screen]; |
| NSDictionary* screen_description = [screen deviceDescription]; |
| NSNumber* screen_number = [screen_description objectForKey:@"NSScreenNumber"]; |
| CGDirectDisplayID display_id = [screen_number unsignedIntValue]; |
| |
| display_link_ = ui::DisplayLinkMac::GetForDisplay(display_id); |
| if (!display_link_.get()) { |
| // Note that on some headless systems, the display link will fail to be |
| // created, so this should not be a fatal error. |
| LOG(ERROR) << "Failed to create display link."; |
| } |
| } |
| |
| void RenderWidgetHostViewMac::UpdateDisplayVSyncParameters() { |
| if (!render_widget_host_ || !display_link_.get()) |
| return; |
| |
| if (!display_link_->GetVSyncParameters(&vsync_timebase_, &vsync_interval_)) { |
| vsync_timebase_ = base::TimeTicks(); |
| vsync_interval_ = base::TimeDelta(); |
| return; |
| } |
| |
| browser_compositor_->UpdateVSyncParameters(vsync_timebase_, vsync_interval_); |
| } |
| |
| void RenderWidgetHostViewMac::SpeakText(const std::string& text) { |
| [NSApp speakString:base::SysUTF8ToNSString(text)]; |
| } |
| |
| RenderWidgetHostViewBase* |
| RenderWidgetHostViewMac::GetFocusedViewForTextSelection() { |
| // We obtain the TextSelection from focused RWH which is obtained from the |
| // frame tree. BrowserPlugin-based guests' RWH is not part of the frame tree |
| // and the focused RWH will be that of the embedder which is incorrect. In |
| // this case we should use TextSelection for |this| since RWHV for guest |
| // forwards text selection information to its platform view. |
| return is_guest_view_hack_ ? this : GetFocusedWidget() |
| ? GetFocusedWidget()->GetView() |
| : nullptr; |
| } |
| |
| RenderWidgetHostDelegate* |
| RenderWidgetHostViewMac::GetFocusedRenderWidgetHostDelegate() { |
| if (auto* focused_widget = GetFocusedWidget()) |
| return focused_widget->delegate(); |
| return render_widget_host_->delegate(); |
| } |
| |
| void RenderWidgetHostViewMac::UpdateBackingStoreProperties() { |
| UpdateScreenInfo(cocoa_view_); |
| // Update the ui::Compositor's color space here. The other display properties |
| // of the ui::Compositor are updated by frame metadata. |
| if (browser_compositor_) |
| browser_compositor_->SetDisplayColorSpace(current_display_color_space_); |
| } |
| |
| RenderWidgetHost* RenderWidgetHostViewMac::GetRenderWidgetHost() const { |
| return render_widget_host_; |
| } |
| |
| void RenderWidgetHostViewMac::Show() { |
| ScopedCAActionDisabler disabler; |
| [cocoa_view_ setHidden:NO]; |
| |
| browser_compositor_->SetRenderWidgetHostIsHidden(false); |
| |
| ui::LatencyInfo renderer_latency_info; |
| renderer_latency_info.AddLatencyNumber( |
| ui::TAB_SHOW_COMPONENT, render_widget_host_->GetLatencyComponentId(), 0); |
| render_widget_host_->WasShown(renderer_latency_info); |
| |
| // If there is not a frame being currently drawn, kick one, so that the below |
| // pause will have a frame to wait on. |
| render_widget_host_->ScheduleComposite(); |
| PauseForPendingResizeOrRepaintsAndDraw(); |
| } |
| |
| void RenderWidgetHostViewMac::Hide() { |
| ScopedCAActionDisabler disabler; |
| [cocoa_view_ setHidden:YES]; |
| |
| render_widget_host_->WasHidden(); |
| browser_compositor_->SetRenderWidgetHostIsHidden(true); |
| } |
| |
| void RenderWidgetHostViewMac::WasUnOccluded() { |
| browser_compositor_->SetRenderWidgetHostIsHidden(false); |
| render_widget_host_->WasShown(ui::LatencyInfo()); |
| } |
| |
| void RenderWidgetHostViewMac::WasOccluded() { |
| // Ignore occlusion when in fullscreen low power mode, because the occlusion |
| // is likely coming from the fullscreen low power window. |
| ui::AcceleratedWidgetMac* accelerated_widget_mac = |
| browser_compositor_->GetAcceleratedWidgetMac(); |
| if (accelerated_widget_mac && |
| accelerated_widget_mac->MightBeInFullscreenLowPowerMode()) { |
| return; |
| } |
| |
| render_widget_host_->WasHidden(); |
| browser_compositor_->SetRenderWidgetHostIsHidden(true); |
| } |
| |
| void RenderWidgetHostViewMac::SetSize(const gfx::Size& size) { |
| gfx::Rect rect = GetViewBounds(); |
| rect.set_size(size); |
| SetBounds(rect); |
| } |
| |
| void RenderWidgetHostViewMac::SetBounds(const gfx::Rect& rect) { |
| // |rect.size()| is view coordinates, |rect.origin| is screen coordinates, |
| // TODO(thakis): fix, http://crbug.com/73362 |
| if (render_widget_host_->is_hidden()) |
| return; |
| |
| // During the initial creation of the RenderWidgetHostView in |
| // WebContentsImpl::CreateRenderViewForRenderManager, SetSize is called with |
| // an empty size. In the Windows code flow, it is not ignored because |
| // subsequent sizing calls from the OS flow through TCVW::WasSized which calls |
| // SetSize() again. On Cocoa, we rely on the Cocoa view struture and resizer |
| // flags to keep things sized properly. On the other hand, if the size is not |
| // empty then this is a valid request for a pop-up. |
| if (rect.size().IsEmpty()) |
| return; |
| |
| // Ignore the position of |rect| for non-popup rwhvs. This is because |
| // background tabs do not have a window, but the window is required for the |
| // coordinate conversions. Popups are always for a visible tab. |
| // |
| // Note: If |cocoa_view_| has been removed from the view hierarchy, it's still |
| // valid for resizing to be requested (e.g., during tab capture, to size the |
| // view to screen-capture resolution). In this case, simply treat the view as |
| // relative to the screen. |
| BOOL isRelativeToScreen = IsPopup() || |
| ![[cocoa_view_ superview] isKindOfClass:[BaseView class]]; |
| if (isRelativeToScreen) { |
| // The position of |rect| is screen coordinate system and we have to |
| // consider Cocoa coordinate system is upside-down and also multi-screen. |
| NSPoint origin_global = NSPointFromCGPoint(rect.origin().ToCGPoint()); |
| NSSize size = NSMakeSize(rect.width(), rect.height()); |
| size = [cocoa_view_ convertSize:size toView:nil]; |
| origin_global.y = FlipYFromRectToScreen(origin_global.y, size.height); |
| NSRect frame = NSMakeRect(origin_global.x, origin_global.y, |
| size.width, size.height); |
| if (IsPopup()) |
| [popup_window_ setFrame:frame display:YES]; |
| else |
| [cocoa_view_ setFrame:frame]; |
| } else { |
| BaseView* superview = static_cast<BaseView*>([cocoa_view_ superview]); |
| gfx::Rect rect2 = [superview flipNSRectToRect:[cocoa_view_ frame]]; |
| rect2.set_width(rect.width()); |
| rect2.set_height(rect.height()); |
| [cocoa_view_ setFrame:[superview flipRectToNSRect:rect2]]; |
| } |
| } |
| |
| gfx::Vector2dF RenderWidgetHostViewMac::GetLastScrollOffset() const { |
| return last_scroll_offset_; |
| } |
| |
| gfx::NativeView RenderWidgetHostViewMac::GetNativeView() const { |
| return cocoa_view_; |
| } |
| |
| gfx::NativeViewAccessible RenderWidgetHostViewMac::GetNativeViewAccessible() { |
| return cocoa_view_; |
| } |
| |
| void RenderWidgetHostViewMac::Focus() { |
| [[cocoa_view_ window] makeFirstResponder:cocoa_view_]; |
| } |
| |
| bool RenderWidgetHostViewMac::HasFocus() const { |
| return [[cocoa_view_ window] firstResponder] == cocoa_view_; |
| } |
| |
| bool RenderWidgetHostViewMac::IsSurfaceAvailableForCopy() const { |
| return browser_compositor_->GetDelegatedFrameHost() |
| ->CanCopyFromCompositingSurface(); |
| } |
| |
| bool RenderWidgetHostViewMac::IsShowing() { |
| return ![cocoa_view_ isHidden]; |
| } |
| |
| gfx::Rect RenderWidgetHostViewMac::GetViewBounds() const { |
| NSRect bounds = [cocoa_view_ bounds]; |
| // TODO(shess): In case of !window, the view has been removed from |
| // the view hierarchy because the tab isn't main. Could retrieve |
| // the information from the main tab for our window. |
| NSWindow* enclosing_window = ApparentWindowForView(cocoa_view_); |
| if (!enclosing_window) |
| return gfx::Rect(gfx::Size(NSWidth(bounds), NSHeight(bounds))); |
| |
| bounds = [cocoa_view_ convertRect:bounds toView:nil]; |
| bounds = [enclosing_window convertRectToScreen:bounds]; |
| return FlipNSRectToRectScreen(bounds); |
| } |
| |
| void RenderWidgetHostViewMac::UpdateCursor(const WebCursor& cursor) { |
| WebCursor web_cursor = cursor; |
| [cocoa_view_ updateCursor:web_cursor.GetNativeCursor()]; |
| } |
| |
| void RenderWidgetHostViewMac::SetIsLoading(bool is_loading) { |
| is_loading_ = is_loading; |
| // If we ever decide to show the waiting cursor while the page is loading |
| // like Chrome does on Windows, call |UpdateCursor()| here. |
| } |
| |
| void RenderWidgetHostViewMac::OnUpdateTextInputStateCalled( |
| TextInputManager* text_input_manager, |
| RenderWidgetHostViewBase* updated_view, |
| bool did_update_state) { |
| if (!did_update_state) |
| return; |
| |
| // |updated_view| is the last view to change its TextInputState which can be |
| // used to start/stop monitoring composition info when it has a focused |
| // editable text input field. |
| RenderWidgetHostImpl* widgetHost = |
| RenderWidgetHostImpl::From(updated_view->GetRenderWidgetHost()); |
| |
| // We might end up here when |updated_view| has had active TextInputState and |
| // then got destroyed. In that case, |updated_view->GetRenderWidgetHost()| |
| // returns nullptr. |
| if (!widgetHost) |
| return; |
| |
| // Set the monitor state based on the text input focus state. |
| const bool has_focus = HasFocus(); |
| const TextInputState* state = text_input_manager->GetTextInputState(); |
| bool need_monitor_composition = |
| has_focus && state && state->type != ui::TEXT_INPUT_TYPE_NONE; |
| |
| widgetHost->RequestCompositionUpdates(false /* immediate_request */, |
| need_monitor_composition); |
| |
| if (has_focus) { |
| SetTextInputActive(true); |
| |
| // Let AppKit cache the new input context to make IMEs happy. |
| // See http://crbug.com/73039. |
| [NSApp updateWindows]; |
| |
| #ifndef __LP64__ |
| bool can_compose_inline = |
| !!GetTextInputManager()->GetActiveWidget() |
| ? GetTextInputManager()->GetTextInputState()->can_compose_inline |
| : true; |
| UseInputWindow(TSMGetActiveDocument(), !can_compose_inline); |
| #endif |
| } |
| } |
| |
| void RenderWidgetHostViewMac::OnImeCancelComposition( |
| TextInputManager* text_input_manager, |
| RenderWidgetHostViewBase* updated_view) { |
| [cocoa_view_ cancelComposition]; |
| } |
| |
| void RenderWidgetHostViewMac::OnImeCompositionRangeChanged( |
| TextInputManager* text_input_manager, |
| RenderWidgetHostViewBase* updated_view) { |
| const TextInputManager::CompositionRangeInfo* info = |
| GetCompositionRangeInfo(); |
| if (!info) |
| return; |
| // The RangeChanged message is only sent with valid values. The current |
| // caret position (start == end) will be sent if there is no IME range. |
| [cocoa_view_ setMarkedRange:info->range.ToNSRange()]; |
| } |
| |
| void RenderWidgetHostViewMac::OnSelectionBoundsChanged( |
| TextInputManager* text_input_manager, |
| RenderWidgetHostViewBase* updated_view) { |
| DCHECK_EQ(GetTextInputManager(), text_input_manager); |
| |
| // The rest of the code is to support the Mac Zoom feature tracking the |
| // text caret; we can skip it if that feature is not currently enabled. |
| if (!UAZoomEnabled()) |
| return; |
| |
| RenderWidgetHostViewBase* focused_view = GetFocusedViewForTextSelection(); |
| if (!focused_view) |
| return; |
| |
| const TextInputManager::SelectionRegion* region = |
| GetTextInputManager()->GetSelectionRegion(focused_view); |
| if (!region) |
| return; |
| |
| NSWindow* enclosing_window = ApparentWindowForView(cocoa_view_); |
| if (!enclosing_window) |
| return; |
| |
| // Create a rectangle for the edge of the selection focus, which will be |
| // the same as the caret position if the selection is collapsed. That's |
| // what we want to try to keep centered on-screen if possible. |
| gfx::Rect gfx_caret_rect(region->focus.edge_top_rounded().x(), |
| region->focus.edge_top_rounded().y(), |
| 1, region->focus.GetHeight()); |
| |
| // Convert the caret rect to CG-style flipped widget-relative coordinates. |
| NSRect caret_rect = NSRectFromCGRect(gfx_caret_rect.ToCGRect()); |
| caret_rect.origin.y = NSHeight([cocoa_view_ bounds]) - |
| (caret_rect.origin.y + caret_rect.size.height); |
| |
| // Now convert that to screen coordinates. |
| caret_rect = [cocoa_view_ convertRect:caret_rect toView:nil]; |
| caret_rect = [enclosing_window convertRectToScreen:caret_rect]; |
| |
| // Finally, flip it again because UAZoomChangeFocus wants unflipped screen |
| // coordinates, and call UAZoomChangeFocus to initiate the scroll. |
| caret_rect.origin.y = FlipYFromRectToScreen( |
| caret_rect.origin.y, caret_rect.size.height); |
| UAZoomChangeFocus(&caret_rect, &caret_rect, kUAZoomFocusTypeInsertionPoint); |
| } |
| |
| void RenderWidgetHostViewMac::OnTextSelectionChanged( |
| TextInputManager* text_input_manager, |
| RenderWidgetHostViewBase* updated_view) { |
| DCHECK_EQ(GetTextInputManager(), text_input_manager); |
| |
| const TextInputManager::TextSelection* selection = GetTextSelection(); |
| if (!selection) |
| return; |
| |
| [cocoa_view_ setSelectedRange:selection->range().ToNSRange()]; |
| // Updates markedRange when there is no marked text so that retrieving |
| // markedRange immediately after calling setMarkdText: returns the current |
| // caret position. |
| if (![cocoa_view_ hasMarkedText]) { |
| [cocoa_view_ setMarkedRange:selection->range().ToNSRange()]; |
| } |
| } |
| |
| void RenderWidgetHostViewMac::RenderProcessGone(base::TerminationStatus status, |
| int error_code) { |
| Destroy(); |
| } |
| |
| void RenderWidgetHostViewMac::Destroy() { |
| // FrameSinkIds registered with RenderWidgetHostInputEventRouter |
| // have already been cleared when RenderWidgetHostViewBase notified its |
| // observers of our impending destruction. |
| [[NSNotificationCenter defaultCenter] |
| removeObserver:cocoa_view_ |
| name:NSWindowWillCloseNotification |
| object:popup_window_]; |
| |
| // We've been told to destroy. |
| [cocoa_view_ retain]; |
| [cocoa_view_ removeFromSuperview]; |
| [cocoa_view_ autorelease]; |
| |
| [popup_window_ close]; |
| popup_window_.autorelease(); |
| |
| [fullscreen_window_manager_ exitFullscreenMode]; |
| fullscreen_window_manager_.reset(); |
| [pepper_fullscreen_window_ close]; |
| |
| // This can be called as part of processing the window's responder |
| // chain, for instance |-performKeyEquivalent:|. In that case the |
| // object needs to survive until the stack unwinds. |
| pepper_fullscreen_window_.autorelease(); |
| |
| // Delete the delegated frame state, which will reach back into |
| // render_widget_host_. |
| browser_compositor_.reset(); |
| |
| // Make sure none of our observers send events for us to process after |
| // we release render_widget_host_. |
| NotifyObserversAboutShutdown(); |
| |
| if (text_input_manager_) |
| text_input_manager_->RemoveObserver(this); |
| |
| // We get this call just before |render_widget_host_| deletes |
| // itself. But we are owned by |cocoa_view_|, which may be retained |
| // by some other code. Examples are WebContentsViewMac's |
| // |latent_focus_view_| and TabWindowController's |
| // |cachedContentView_|. |
| render_widget_host_ = NULL; |
| } |
| |
| // Called from the renderer to tell us what the tooltip text should be. It |
| // calls us frequently so we need to cache the value to prevent doing a lot |
| // of repeat work. |
| void RenderWidgetHostViewMac::SetTooltipText( |
| const base::string16& tooltip_text) { |
| if (tooltip_text != tooltip_text_ && [[cocoa_view_ window] isKeyWindow]) { |
| tooltip_text_ = tooltip_text; |
| |
| // Clamp the tooltip length to kMaxTooltipLength. It's a DOS issue on |
| // Windows; we're just trying to be polite. Don't persist the trimmed |
| // string, as then the comparison above will always fail and we'll try to |
| // set it again every single time the mouse moves. |
| base::string16 display_text = tooltip_text_; |
| if (tooltip_text_.length() > kMaxTooltipLength) |
| display_text = tooltip_text_.substr(0, kMaxTooltipLength); |
| |
| NSString* tooltip_nsstring = base::SysUTF16ToNSString(display_text); |
| [cocoa_view_ setToolTipAtMousePoint:tooltip_nsstring]; |
| } |
| } |
| |
| bool RenderWidgetHostViewMac::SupportsSpeech() const { |
| return [NSApp respondsToSelector:@selector(speakString:)] && |
| [NSApp respondsToSelector:@selector(stopSpeaking:)]; |
| } |
| |
| void RenderWidgetHostViewMac::SpeakSelection() { |
| if (![NSApp respondsToSelector:@selector(speakString:)]) |
| return; |
| |
| const TextInputManager::TextSelection* selection = GetTextSelection(); |
| if (!selection) |
| return; |
| |
| if (selection->selected_text().empty() && render_widget_host_) { |
| // TODO: This will not work with OOPIFs (https://crbug.com/659753). |
| // If there's no selection, speak all text. Send an asynchronous IPC |
| // request for fetching all the text for a webcontent. |
| // ViewMsg_GetRenderedTextCompleted is sent back to IPC Message receiver. |
| render_widget_host_->Send( |
| new ViewMsg_GetRenderedText(render_widget_host_->GetRoutingID())); |
| return; |
| } |
| |
| SpeakText(base::UTF16ToUTF8(selection->selected_text())); |
| } |
| |
| bool RenderWidgetHostViewMac::IsSpeaking() const { |
| return [NSApp respondsToSelector:@selector(isSpeaking)] && [NSApp isSpeaking]; |
| } |
| |
| void RenderWidgetHostViewMac::StopSpeaking() { |
| if ([NSApp respondsToSelector:@selector(stopSpeaking:)]) |
| [NSApp stopSpeaking:cocoa_view_]; |
| } |
| |
| // |
| // RenderWidgetHostViewCocoa uses the stored selection text, |
| // which implements NSServicesRequests protocol. |
| // |
| |
| void RenderWidgetHostViewMac::SetShowingContextMenu(bool showing) { |
| // Create a fake mouse event to inform the render widget that the mouse |
| // left or entered. |
| NSWindow* window = [cocoa_view_ window]; |
| // TODO(asvitkine): If the location outside of the event stream doesn't |
| // correspond to the current event (due to delayed event processing), then |
| // this may result in a cursor flicker if there are later mouse move events |
| // in the pipeline. Find a way to use the mouse location from the event that |
| // dismissed the context menu. |
| NSPoint location = [window mouseLocationOutsideOfEventStream]; |
| NSTimeInterval event_time = [[NSApp currentEvent] timestamp]; |
| NSEvent* event = [NSEvent mouseEventWithType:NSMouseMoved |
| location:location |
| modifierFlags:0 |
| timestamp:event_time |
| windowNumber:window_number() |
| context:nil |
| eventNumber:0 |
| clickCount:0 |
| pressure:0]; |
| WebMouseEvent web_event = WebMouseEventBuilder::Build(event, cocoa_view_); |
| if (showing) |
| web_event.SetType(WebInputEvent::kMouseLeave); |
| ForwardMouseEvent(web_event); |
| } |
| |
| bool RenderWidgetHostViewMac::IsPopup() const { |
| return popup_type_ != blink::kWebPopupTypeNone; |
| } |
| |
| void RenderWidgetHostViewMac::CopyFromSurface( |
| const gfx::Rect& src_subrect, |
| const gfx::Size& dst_size, |
| const ReadbackRequestCallback& callback, |
| const SkColorType preferred_color_type) { |
| browser_compositor_->CopyFromCompositingSurface( |
| src_subrect, dst_size, callback, preferred_color_type); |
| } |
| |
| void RenderWidgetHostViewMac::CopyFromSurfaceToVideoFrame( |
| const gfx::Rect& src_subrect, |
| scoped_refptr<media::VideoFrame> target, |
| const base::Callback<void(const gfx::Rect&, bool)>& callback) { |
| browser_compositor_->CopyFromCompositingSurfaceToVideoFrame( |
| src_subrect, std::move(target), callback); |
| } |
| |
| void RenderWidgetHostViewMac::BeginFrameSubscription( |
| std::unique_ptr<RenderWidgetHostViewFrameSubscriber> subscriber) { |
| browser_compositor_->GetDelegatedFrameHost()->BeginFrameSubscription( |
| std::move(subscriber)); |
| } |
| |
| void RenderWidgetHostViewMac::EndFrameSubscription() { |
| browser_compositor_->GetDelegatedFrameHost()->EndFrameSubscription(); |
| } |
| |
| ui::AcceleratedWidgetMac* RenderWidgetHostViewMac::GetAcceleratedWidgetMac() |
| const { |
| return browser_compositor_->GetAcceleratedWidgetMac(); |
| } |
| |
| void RenderWidgetHostViewMac::ForwardMouseEvent(const WebMouseEvent& event) { |
| if (render_widget_host_) |
| render_widget_host_->ForwardMouseEvent(event); |
| |
| if (event.GetType() == WebInputEvent::kMouseLeave) { |
| [cocoa_view_ setToolTipAtMousePoint:nil]; |
| tooltip_text_.clear(); |
| } |
| } |
| |
| void RenderWidgetHostViewMac::SetNeedsBeginFrames(bool needs_begin_frames) { |
| needs_begin_frames_ = needs_begin_frames; |
| UpdateNeedsBeginFramesInternal(); |
| } |
| |
| void RenderWidgetHostViewMac::UpdateNeedsBeginFramesInternal() { |
| browser_compositor_->SetNeedsBeginFrames(needs_begin_frames_); |
| } |
| |
| void RenderWidgetHostViewMac::KillSelf() { |
| if (!weak_factory_.HasWeakPtrs()) { |
| [cocoa_view_ setHidden:YES]; |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::Bind(&RenderWidgetHostViewMac::ShutdownHost, |
| weak_factory_.GetWeakPtr())); |
| } |
| } |
| |
| bool RenderWidgetHostViewMac::GetLineBreakIndex( |
| const std::vector<gfx::Rect>& bounds, |
| const gfx::Range& range, |
| size_t* line_break_point) { |
| DCHECK(line_break_point); |
| if (range.start() >= bounds.size() || range.is_reversed() || range.is_empty()) |
| return false; |
| |
| // We can't check line breaking completely from only rectangle array. Thus we |
| // assume the line breaking as the next character's y offset is larger than |
| // a threshold. Currently the threshold is determined as minimum y offset plus |
| // 75% of maximum height. |
| // TODO(nona): Check the threshold is reliable or not. |
| // TODO(nona): Bidi support. |
| const size_t loop_end_idx = |
| std::min(bounds.size(), static_cast<size_t>(range.end())); |
| int max_height = 0; |
| int min_y_offset = std::numeric_limits<int32_t>::max(); |
| for (size_t idx = range.start(); idx < loop_end_idx; ++idx) { |
| max_height = std::max(max_height, bounds[idx].height()); |
| min_y_offset = std::min(min_y_offset, bounds[idx].y()); |
| } |
| int line_break_threshold = min_y_offset + (max_height * 3 / 4); |
| for (size_t idx = range.start(); idx < loop_end_idx; ++idx) { |
| if (bounds[idx].y() > line_break_threshold) { |
| *line_break_point = idx; |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| gfx::Rect RenderWidgetHostViewMac::GetFirstRectForCompositionRange( |
| const gfx::Range& range, |
| gfx::Range* actual_range) { |
| const TextInputManager::CompositionRangeInfo* composition_info = |
| GetCompositionRangeInfo(); |
| if (!composition_info) |
| return gfx::Rect(); |
| |
| DCHECK(actual_range); |
| DCHECK(!composition_info->character_bounds.empty()); |
| DCHECK(range.start() <= composition_info->character_bounds.size()); |
| DCHECK(range.end() <= composition_info->character_bounds.size()); |
| |
| if (range.is_empty()) { |
| *actual_range = range; |
| if (range.start() == composition_info->character_bounds.size()) { |
| return gfx::Rect( |
| composition_info->character_bounds[range.start() - 1].right(), |
| composition_info->character_bounds[range.start() - 1].y(), 0, |
| composition_info->character_bounds[range.start() - 1].height()); |
| } else { |
| return gfx::Rect( |
| composition_info->character_bounds[range.start()].x(), |
| composition_info->character_bounds[range.start()].y(), 0, |
| composition_info->character_bounds[range.start()].height()); |
| } |
| } |
| |
| size_t end_idx; |
| if (!GetLineBreakIndex(composition_info->character_bounds, range, &end_idx)) { |
| end_idx = range.end(); |
| } |
| *actual_range = gfx::Range(range.start(), end_idx); |
| gfx::Rect rect = composition_info->character_bounds[range.start()]; |
| for (size_t i = range.start() + 1; i < end_idx; ++i) { |
| rect.Union(composition_info->character_bounds[i]); |
| } |
| return rect; |
| } |
| |
| gfx::Range RenderWidgetHostViewMac::ConvertCharacterRangeToCompositionRange( |
| const gfx::Range& request_range) { |
| const TextInputManager::CompositionRangeInfo* composition_info = |
| GetCompositionRangeInfo(); |
| if (!composition_info) |
| return gfx::Range::InvalidRange(); |
| |
| if (composition_info->range.is_empty()) |
| return gfx::Range::InvalidRange(); |
| |
| if (composition_info->range.is_reversed()) |
| return gfx::Range::InvalidRange(); |
| |
| if (request_range.start() < composition_info->range.start() || |
| request_range.start() > composition_info->range.end() || |
| request_range.end() > composition_info->range.end()) { |
| return gfx::Range::InvalidRange(); |
| } |
| |
| return gfx::Range(request_range.start() - composition_info->range.start(), |
| request_range.end() - composition_info->range.start()); |
| } |
| |
| WebContents* RenderWidgetHostViewMac::GetWebContents() { |
| return WebContents::FromRenderViewHost( |
| RenderViewHost::From(render_widget_host_)); |
| } |
| |
| bool RenderWidgetHostViewMac::GetCachedFirstRectForCharacterRange( |
| NSRange range, |
| NSRect* rect, |
| NSRange* actual_range) { |
| if (!GetTextInputManager()) |
| return false; |
| |
| DCHECK(rect); |
| // This exists to make IMEs more responsive, see http://crbug.com/115920 |
| TRACE_EVENT0("browser", |
| "RenderWidgetHostViewMac::GetFirstRectForCharacterRange"); |
| |
| const TextInputManager::TextSelection* selection = GetTextSelection(); |
| if (!selection) |
| return false; |
| |
| const gfx::Range requested_range(range); |
| // If requested range is same as caret location, we can just return it. |
| if (selection->range().is_empty() && requested_range == selection->range()) { |
| DCHECK(GetFocusedWidget()); |
| if (actual_range) |
| *actual_range = range; |
| *rect = |
| NSRectFromCGRect(GetTextInputManager() |
| ->GetSelectionRegion(GetFocusedWidget()->GetView()) |
| ->caret_rect.ToCGRect()); |
| return true; |
| } |
| |
| const TextInputManager::CompositionRangeInfo* composition_info = |
| GetCompositionRangeInfo(); |
| if (!composition_info || composition_info->range.is_empty()) { |
| if (!selection->range().Contains(requested_range)) |
| return false; |
| DCHECK(GetFocusedWidget()); |
| if (actual_range) |
| *actual_range = selection->range().ToNSRange(); |
| *rect = |
| NSRectFromCGRect(GetTextInputManager() |
| ->GetSelectionRegion(GetFocusedWidget()->GetView()) |
| ->first_selection_rect.ToCGRect()); |
| return true; |
| } |
| |
| const gfx::Range request_range_in_composition = |
| ConvertCharacterRangeToCompositionRange(requested_range); |
| if (request_range_in_composition == gfx::Range::InvalidRange()) |
| return false; |
| |
| // If firstRectForCharacterRange in WebFrame is failed in renderer, |
| // ImeCompositionRangeChanged will be sent with empty vector. |
| if (!composition_info || composition_info->character_bounds.empty()) |
| return false; |
| DCHECK_EQ(composition_info->character_bounds.size(), |
| composition_info->range.length()); |
| |
| gfx::Range ui_actual_range; |
| *rect = NSRectFromCGRect(GetFirstRectForCompositionRange( |
| request_range_in_composition, |
| &ui_actual_range).ToCGRect()); |
| if (actual_range) { |
| *actual_range = |
| gfx::Range(composition_info->range.start() + ui_actual_range.start(), |
| composition_info->range.start() + ui_actual_range.end()) |
| .ToNSRange(); |
| } |
| return true; |
| } |
| |
| bool RenderWidgetHostViewMac::HasAcceleratedSurface( |
| const gfx::Size& desired_size) { |
| ui::AcceleratedWidgetMac* accelerated_widget_mac = |
| browser_compositor_->GetAcceleratedWidgetMac(); |
| if (accelerated_widget_mac) |
| return accelerated_widget_mac->HasFrameOfSize(desired_size); |
| return false; |
| } |
| |
| void RenderWidgetHostViewMac::FocusedNodeChanged( |
| bool is_editable_node, |
| const gfx::Rect& node_bounds_in_screen) { |
| // If the Mac Zoom feature is enabled, update it with the bounds of the |
| // current focused node so that it can ensure that it's scrolled into view. |
| // Don't do anything if it's an editable node, as this will be handled by |
| // OnSelectionBoundsChanged instead. |
| if (UAZoomEnabled() && !is_editable_node) { |
| NSRect bounds = NSRectFromCGRect(node_bounds_in_screen.ToCGRect()); |
| UAZoomChangeFocus(&bounds, NULL, kUAZoomFocusTypeOther); |
| } |
| } |
| |
| void RenderWidgetHostViewMac::DidCreateNewRendererCompositorFrameSink( |
| cc::mojom::CompositorFrameSinkClient* renderer_compositor_frame_sink) { |
| browser_compositor_->DidCreateNewRendererCompositorFrameSink( |
| renderer_compositor_frame_sink); |
| } |
| |
| void RenderWidgetHostViewMac::SubmitCompositorFrame( |
| const cc::LocalSurfaceId& local_surface_id, |
| cc::CompositorFrame frame) { |
| TRACE_EVENT0("browser", "RenderWidgetHostViewMac::OnSwapCompositorFrame"); |
| |
| last_frame_root_background_color_ = frame.metadata.root_background_color; |
| last_scroll_offset_ = frame.metadata.root_scroll_offset; |
| |
| page_at_minimum_scale_ = |
| frame.metadata.page_scale_factor == frame.metadata.min_page_scale_factor; |
| browser_compositor_->SubmitCompositorFrame(local_surface_id, |
| std::move(frame)); |
| UpdateDisplayVSyncParameters(); |
| } |
| |
| void RenderWidgetHostViewMac::OnDidNotProduceFrame( |
| const cc::BeginFrameAck& ack) { |
| browser_compositor_->OnDidNotProduceFrame(ack); |
| } |
| |
| void RenderWidgetHostViewMac::ClearCompositorFrame() { |
| browser_compositor_->GetDelegatedFrameHost()->ClearDelegatedFrame(); |
| } |
| |
| gfx::Rect RenderWidgetHostViewMac::GetBoundsInRootWindow() { |
| // TODO(shess): In case of !window, the view has been removed from |
| // the view hierarchy because the tab isn't main. Could retrieve |
| // the information from the main tab for our window. |
| NSWindow* enclosing_window = ApparentWindowForView(cocoa_view_); |
| if (!enclosing_window) |
| return gfx::Rect(); |
| |
| NSRect bounds = [enclosing_window frame]; |
| return FlipNSRectToRectScreen(bounds); |
| } |
| |
| bool RenderWidgetHostViewMac::LockMouse() { |
| if (mouse_locked_) |
| return true; |
| |
| mouse_locked_ = true; |
| |
| // Lock position of mouse cursor and hide it. |
| CGAssociateMouseAndMouseCursorPosition(NO); |
| [NSCursor hide]; |
| |
| // Clear the tooltip window. |
| SetTooltipText(base::string16()); |
| |
| return true; |
| } |
| |
| void RenderWidgetHostViewMac::UnlockMouse() { |
| if (!mouse_locked_) |
| return; |
| mouse_locked_ = false; |
| |
| // Unlock position of mouse cursor and unhide it. |
| CGAssociateMouseAndMouseCursorPosition(YES); |
| [NSCursor unhide]; |
| |
| if (render_widget_host_) |
| render_widget_host_->LostMouseLock(); |
| } |
| |
| void RenderWidgetHostViewMac::GestureEventAck( |
| const blink::WebGestureEvent& event, |
| InputEventAckState ack_result) { |
| bool consumed = ack_result == INPUT_EVENT_ACK_STATE_CONSUMED; |
| switch (event.GetType()) { |
| case blink::WebInputEvent::kGestureScrollBegin: |
| case blink::WebInputEvent::kGestureScrollUpdate: |
| case blink::WebInputEvent::kGestureScrollEnd: |
| [cocoa_view_ processedGestureScrollEvent:event consumed:consumed]; |
| return; |
| default: |
| break; |
| } |
| } |
| |
| std::unique_ptr<SyntheticGestureTarget> |
| RenderWidgetHostViewMac::CreateSyntheticGestureTarget() { |
| RenderWidgetHostImpl* host = |
| RenderWidgetHostImpl::From(GetRenderWidgetHost()); |
| return std::unique_ptr<SyntheticGestureTarget>( |
| new SyntheticGestureTargetMac(host, cocoa_view_)); |
| } |
| |
| cc::FrameSinkId RenderWidgetHostViewMac::GetFrameSinkId() { |
| return browser_compositor_->GetDelegatedFrameHost()->GetFrameSinkId(); |
| } |
| |
| cc::FrameSinkId RenderWidgetHostViewMac::FrameSinkIdAtPoint( |
| cc::SurfaceHittestDelegate* delegate, |
| const gfx::Point& point, |
| gfx::Point* transformed_point) { |
| // The surface hittest happens in device pixels, so we need to convert the |
| // |point| from DIPs to pixels before hittesting. |
| float scale_factor = ui::GetScaleFactorForNativeView(cocoa_view_); |
| gfx::Point point_in_pixels = gfx::ConvertPointToPixel(scale_factor, point); |
| cc::SurfaceId id = |
| browser_compositor_->GetDelegatedFrameHost()->SurfaceIdAtPoint( |
| delegate, point_in_pixels, transformed_point); |
| *transformed_point = gfx::ConvertPointToDIP(scale_factor, *transformed_point); |
| |
| // It is possible that the renderer has not yet produced a surface, in which |
| // case we return our current namespace. |
| if (!id.is_valid()) |
| return GetFrameSinkId(); |
| return id.frame_sink_id(); |
| } |
| |
| bool RenderWidgetHostViewMac::ShouldRouteEvent( |
| const WebInputEvent& event) const { |
| // See also RenderWidgetHostViewAura::ShouldRouteEvent. |
| // TODO(wjmaclean): Update this function if RenderWidgetHostViewMac implements |
| // OnTouchEvent(), to match what we are doing in RenderWidgetHostViewAura. |
| DCHECK(WebInputEvent::IsMouseEventType(event.GetType()) || |
| event.GetType() == WebInputEvent::kMouseWheel); |
| return render_widget_host_->delegate() && |
| render_widget_host_->delegate()->GetInputEventRouter() && |
| SiteIsolationPolicy::AreCrossProcessFramesPossible(); |
| } |
| |
| void RenderWidgetHostViewMac::ProcessMouseEvent( |
| const blink::WebMouseEvent& event, |
| const ui::LatencyInfo& latency) { |
| render_widget_host_->ForwardMouseEventWithLatencyInfo(event, latency); |
| } |
| void RenderWidgetHostViewMac::ProcessMouseWheelEvent( |
| const blink::WebMouseWheelEvent& event, |
| const ui::LatencyInfo& latency) { |
| render_widget_host_->ForwardWheelEventWithLatencyInfo(event, latency); |
| } |
| |
| void RenderWidgetHostViewMac::ProcessTouchEvent( |
| const blink::WebTouchEvent& event, |
| const ui::LatencyInfo& latency) { |
| render_widget_host_->ForwardTouchEventWithLatencyInfo(event, latency); |
| } |
| |
| void RenderWidgetHostViewMac::ProcessGestureEvent( |
| const blink::WebGestureEvent& event, |
| const ui::LatencyInfo& latency) { |
| render_widget_host_->ForwardGestureEventWithLatencyInfo(event, latency); |
| } |
| |
| bool RenderWidgetHostViewMac::TransformPointToLocalCoordSpace( |
| const gfx::Point& point, |
| const cc::SurfaceId& original_surface, |
| gfx::Point* transformed_point) { |
| // Transformations use physical pixels rather than DIP, so conversion |
| // is necessary. |
| float scale_factor = ui::GetScaleFactorForNativeView(cocoa_view_); |
| gfx::Point point_in_pixels = gfx::ConvertPointToPixel(scale_factor, point); |
| if (!browser_compositor_->GetDelegatedFrameHost() |
| ->TransformPointToLocalCoordSpace(point_in_pixels, original_surface, |
| transformed_point)) |
| return false; |
| *transformed_point = gfx::ConvertPointToDIP(scale_factor, *transformed_point); |
| return true; |
| } |
| |
| bool RenderWidgetHostViewMac::TransformPointToCoordSpaceForView( |
| const gfx::Point& point, |
| RenderWidgetHostViewBase* target_view, |
| gfx::Point* transformed_point) { |
| if (target_view == this) { |
| *transformed_point = point; |
| return true; |
| } |
| |
| return browser_compositor_->GetDelegatedFrameHost() |
| ->TransformPointToCoordSpaceForView(point, target_view, |
| transformed_point); |
| } |
| |
| bool RenderWidgetHostViewMac::Send(IPC::Message* message) { |
| if (render_widget_host_) |
| return render_widget_host_->Send(message); |
| delete message; |
| return false; |
| } |
| |
| void RenderWidgetHostViewMac::ShutdownHost() { |
| weak_factory_.InvalidateWeakPtrs(); |
| render_widget_host_->ShutdownAndDestroyWidget(true); |
| // Do not touch any members at this point, |this| has been deleted. |
| } |
| |
| void RenderWidgetHostViewMac::SetActive(bool active) { |
| if (render_widget_host_) { |
| render_widget_host_->SetActive(active); |
| if (active) { |
| if (HasFocus()) |
| render_widget_host_->Focus(); |
| } else { |
| render_widget_host_->Blur(); |
| } |
| } |
| if (HasFocus()) |
| SetTextInputActive(active); |
| if (!active) |
| UnlockMouse(); |
| } |
| |
| void RenderWidgetHostViewMac::ShowDefinitionForSelection() { |
| RenderWidgetHostViewMacDictionaryHelper helper(this); |
| helper.ShowDefinitionForSelection(); |
| } |
| |
| void RenderWidgetHostViewMac::SetBackgroundColor(SkColor color) { |
| if (color == background_color_) |
| return; |
| |
| // The renderer will feed its color back to us with the first CompositorFrame. |
| // We short-cut here to show a sensible color before that happens. |
| UpdateBackgroundColorFromRenderer(color); |
| |
| DCHECK(SkColorGetA(color) == SK_AlphaOPAQUE || |
| SkColorGetA(color) == SK_AlphaTRANSPARENT); |
| bool opaque = SkColorGetA(color) == SK_AlphaOPAQUE; |
| if (render_widget_host_) |
| render_widget_host_->SetBackgroundOpaque(opaque); |
| } |
| |
| SkColor RenderWidgetHostViewMac::background_color() const { |
| return background_color_; |
| } |
| |
| void RenderWidgetHostViewMac::UpdateBackgroundColorFromRenderer(SkColor color) { |
| if (color == background_color_) |
| return; |
| background_color_ = color; |
| |
| bool opaque = SkColorGetA(color) == SK_AlphaOPAQUE; |
| |
| [cocoa_view_ setOpaque:opaque]; |
| |
| browser_compositor_->SetHasTransparentBackground(!opaque); |
| |
| ScopedCAActionDisabler disabler; |
| base::ScopedCFTypeRef<CGColorRef> cg_color( |
| skia::CGColorCreateFromSkColor(color)); |
| [background_layer_ setBackgroundColor:cg_color]; |
| } |
| |
| BrowserAccessibilityManager* |
| RenderWidgetHostViewMac::CreateBrowserAccessibilityManager( |
| BrowserAccessibilityDelegate* delegate, bool for_root_frame) { |
| return new BrowserAccessibilityManagerMac( |
| BrowserAccessibilityManagerMac::GetEmptyDocument(), delegate); |
| } |
| |
| gfx::Point RenderWidgetHostViewMac::AccessibilityOriginInScreen( |
| const gfx::Rect& bounds) { |
| NSPoint origin = NSMakePoint(bounds.x(), bounds.y()); |
| NSSize size = NSMakeSize(bounds.width(), bounds.height()); |
| origin.y = NSHeight([cocoa_view_ bounds]) - origin.y; |
| NSPoint originInWindow = [cocoa_view_ convertPoint:origin toView:nil]; |
| NSPoint originInScreen = |
| ui::ConvertPointFromWindowToScreen([cocoa_view_ window], originInWindow); |
| originInScreen.y = originInScreen.y - size.height; |
| return gfx::Point(originInScreen.x, originInScreen.y); |
| } |
| |
| NSView* RenderWidgetHostViewMac::AccessibilityGetAcceleratedWidget() { |
| return cocoa_view_; |
| } |
| |
| void RenderWidgetHostViewMac::SetTextInputActive(bool active) { |
| if (active) { |
| if (GetTextInputType() == ui::TEXT_INPUT_TYPE_PASSWORD) |
| EnablePasswordInput(); |
| else |
| DisablePasswordInput(); |
| } else { |
| if (GetTextInputType() == ui::TEXT_INPUT_TYPE_PASSWORD) |
| DisablePasswordInput(); |
| } |
| } |
| |
| void RenderWidgetHostViewMac::OnGetRenderedTextCompleted( |
| const std::string& text) { |
| SpeakText(text); |
| } |
| |
| void RenderWidgetHostViewMac::PauseForPendingResizeOrRepaintsAndDraw() { |
| if (!render_widget_host_ || render_widget_host_->is_hidden()) |
| return; |
| |
| // Pausing for one view prevents others from receiving frames. |
| // This may lead to large delays, causing overlaps. See crbug.com/352020. |
| if (!allow_pause_for_resize_or_repaint_) |
| return; |
| |
| // Wait for a frame of the right size to come in. |
| render_widget_host_->PauseForPendingResizeOrRepaints(); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // display::DisplayObserver, public: |
| |
| void RenderWidgetHostViewMac::OnDisplayAdded(const display::Display& display) {} |
| |
| void RenderWidgetHostViewMac::OnDisplayRemoved( |
| const display::Display& display) {} |
| |
| void RenderWidgetHostViewMac::OnDisplayMetricsChanged( |
| const display::Display& display, |
| uint32_t changed_metrics) { |
| display::Screen* screen = display::Screen::GetScreen(); |
| if (display.id() != screen->GetDisplayNearestView(cocoa_view_).id()) |
| return; |
| |
| if (changed_metrics & DisplayObserver::DISPLAY_METRIC_DEVICE_SCALE_FACTOR) { |
| RenderWidgetHostImpl* host = |
| RenderWidgetHostImpl::From(GetRenderWidgetHost()); |
| if (host && host->delegate()) |
| host->delegate()->UpdateDeviceScaleFactor(display.device_scale_factor()); |
| } |
| |
| UpdateBackingStoreProperties(); |
| } |
| |
| } // namespace content |
| |
| // RenderWidgetHostViewCocoa --------------------------------------------------- |
| |
| @implementation RenderWidgetHostViewCocoa |
| @synthesize selectedRange = selectedRange_; |
| @synthesize suppressNextEscapeKeyUp = suppressNextEscapeKeyUp_; |
| @synthesize markedRange = markedRange_; |
| |
| - (id)initWithRenderWidgetHostViewMac:(RenderWidgetHostViewMac*)r { |
| self = [super initWithFrame:NSZeroRect]; |
| if (self) { |
| self.acceptsTouchEvents = YES; |
| editCommand_helper_.reset(new RenderWidgetHostViewMacEditCommandHelper); |
| editCommand_helper_->AddEditingSelectorsToClass([self class]); |
| |
| renderWidgetHostView_.reset(r); |
| canBeKeyView_ = YES; |
| opaque_ = NO; |
| pinchHasReachedZoomThreshold_ = false; |
| isStylusEnteringProximity_ = false; |
| |
| // OpenGL support: |
| if ([self respondsToSelector: |
| @selector(setWantsBestResolutionOpenGLSurface:)]) { |
| [self setWantsBestResolutionOpenGLSurface:YES]; |
| } |
| [[NSNotificationCenter defaultCenter] |
| addObserver:self |
| selector:@selector(didChangeScreenParameters:) |
| name:NSApplicationDidChangeScreenParametersNotification |
| object:nil]; |
| } |
| return self; |
| } |
| |
| - (void)dealloc { |
| if (responderDelegate_ && |
| [responderDelegate_ respondsToSelector:@selector(viewGone:)]) |
| [responderDelegate_ viewGone:self]; |
| responderDelegate_.reset(); |
| |
| [[NSNotificationCenter defaultCenter] removeObserver:self]; |
| |
| [super dealloc]; |
| } |
| |
| - (void)didChangeScreenParameters:(NSNotification*)notify { |
| g_screen_info_up_to_date = false; |
| } |
| |
| - (void)setResponderDelegate: |
| (NSObject<RenderWidgetHostViewMacDelegate>*)delegate { |
| DCHECK(!responderDelegate_); |
| responderDelegate_.reset([delegate retain]); |
| } |
| |
| - (void)resetCursorRects { |
| if (currentCursor_) { |
| [self addCursorRect:[self visibleRect] cursor:currentCursor_]; |
| [currentCursor_ setOnMouseEntered:YES]; |
| } |
| } |
| |
| - (void)processedWheelEvent:(const blink::WebMouseWheelEvent&)event |
| consumed:(BOOL)consumed { |
| [responderDelegate_ rendererHandledWheelEvent:event consumed:consumed]; |
| } |
| |
| - (void)processedGestureScrollEvent:(const blink::WebGestureEvent&)event |
| consumed:(BOOL)consumed { |
| [responderDelegate_ rendererHandledGestureScrollEvent:event |
| consumed:consumed]; |
| } |
| |
| - (BOOL)respondsToSelector:(SEL)selector { |
| // Trickiness: this doesn't mean "does this object's superclass respond to |
| // this selector" but rather "does the -respondsToSelector impl from the |
| // superclass say that this class responds to the selector". |
| if ([super respondsToSelector:selector]) |
| return YES; |
| |
| if (responderDelegate_) |
| return [responderDelegate_ respondsToSelector:selector]; |
| |
| return NO; |
| } |
| |
| - (id)forwardingTargetForSelector:(SEL)selector { |
| if ([responderDelegate_ respondsToSelector:selector]) |
| return responderDelegate_.get(); |
| |
| return [super forwardingTargetForSelector:selector]; |
| } |
| |
| - (void)setCanBeKeyView:(BOOL)can { |
| canBeKeyView_ = can; |
| } |
| |
| - (BOOL)acceptsMouseEventsWhenInactive { |
| // Some types of windows (balloons, always-on-top panels) want to accept mouse |
| // clicks w/o the first click being treated as 'activation'. Same applies to |
| // mouse move events. |
| return [[self window] level] > NSNormalWindowLevel; |
| } |
| |
| - (BOOL)acceptsFirstMouse:(NSEvent*)theEvent { |
| return [self acceptsMouseEventsWhenInactive]; |
| } |
| |
| - (void)setCloseOnDeactivate:(BOOL)b { |
| closeOnDeactivate_ = b; |
| } |
| |
| - (void)setOpaque:(BOOL)opaque { |
| opaque_ = opaque; |
| } |
| |
| - (BOOL)shouldIgnoreMouseEvent:(NSEvent*)theEvent { |
| NSWindow* window = [self window]; |
| // If this is a background window, don't handle mouse movement events. This |
| // is the expected behavior on the Mac as evidenced by other applications. |
| if ([theEvent type] == NSMouseMoved && |
| ![self acceptsMouseEventsWhenInactive] && |
| ![window isKeyWindow]) { |
| return YES; |
| } |
| |
| // Use hitTest to check whether the mouse is over a nonWebContentView - in |
| // which case the mouse event should not be handled by the render host. |
| const SEL nonWebContentViewSelector = @selector(nonWebContentView); |
| NSView* contentView = [window contentView]; |
| NSView* view = [contentView hitTest:[theEvent locationInWindow]]; |
| // Traverse the superview hierarchy as the hitTest will return the frontmost |
| // view, such as an NSTextView, while nonWebContentView may be specified by |
| // its parent view. |
| while (view) { |
| if ([view respondsToSelector:nonWebContentViewSelector] && |
| [view performSelector:nonWebContentViewSelector]) { |
| // The cursor is over a nonWebContentView - ignore this mouse event. |
| return YES; |
| } |
| if ([view isKindOfClass:[self class]] && ![view isEqual:self] && |
| !hasOpenMouseDown_) { |
| // The cursor is over an overlapping render widget. This check is done by |
| // both views so the one that's returned by -hitTest: will end up |
| // processing the event. |
| // Note that while dragging, we only get events for the render view where |
| // drag started, even if mouse is actually over another view or outside |
| // the window. Cocoa does this for us. We should handle these events and |
| // not ignore (since there is no other render view to handle them). Thus |
| // the |!hasOpenMouseDown_| check above. |
| return YES; |
| } |
| view = [view superview]; |
| } |
| return NO; |
| } |
| |
| - (void)mouseEvent:(NSEvent*)theEvent { |
| TRACE_EVENT0("browser", "RenderWidgetHostViewCocoa::mouseEvent"); |
| if (responderDelegate_ && |
| [responderDelegate_ respondsToSelector:@selector(handleEvent:)]) { |
| BOOL handled = [responderDelegate_ handleEvent:theEvent]; |
| if (handled) |
| return; |
| } |
| |
| // Set the pointer type when we are receiving a NSMouseEntered event and the |
| // following NSMouseExited event should have the same pointer type. |
| // For NSMouseExited and NSMouseEntered events, they do not have a subtype. |
| // We decide their pointer types by checking if we recevied a |
| // NSTabletProximity event. |
| NSEventType type = [theEvent type]; |
| if (type == NSMouseEntered || type == NSMouseExited) { |
| pointerType_ = isStylusEnteringProximity_ |
| ? pointerType_ |
| : blink::WebPointerProperties::PointerType::kMouse; |
| } else { |
| NSEventSubtype subtype = [theEvent subtype]; |
| // For other mouse events and touchpad events, the pointer type is mouse. |
| if (subtype != NSTabletPointEventSubtype && |
| subtype != NSTabletProximityEventSubtype) { |
| pointerType_ = blink::WebPointerProperties::PointerType::kMouse; |
| } |
| } |
| |
| if ([self shouldIgnoreMouseEvent:theEvent]) { |
| // If this is the first such event, send a mouse exit to the host view. |
| if (!mouseEventWasIgnored_ && renderWidgetHostView_->render_widget_host_) { |
| WebMouseEvent exitEvent = |
| WebMouseEventBuilder::Build(theEvent, self, pointerType_); |
| exitEvent.SetType(WebInputEvent::kMouseLeave); |
| exitEvent.button = WebMouseEvent::Button::kNoButton; |
| renderWidgetHostView_->ForwardMouseEvent(exitEvent); |
| } |
| mouseEventWasIgnored_ = YES; |
| return; |
| } |
| |
| if (mouseEventWasIgnored_) { |
| // If this is the first mouse event after a previous event that was ignored |
| // due to the hitTest, send a mouse enter event to the host view. |
| if (renderWidgetHostView_->render_widget_host_) { |
| WebMouseEvent enterEvent = |
| WebMouseEventBuilder::Build(theEvent, self, pointerType_); |
| enterEvent.SetType(WebInputEvent::kMouseMove); |
| enterEvent.button = WebMouseEvent::Button::kNoButton; |
| ui::LatencyInfo latency_info(ui::SourceEventType::OTHER); |
| latency_info.AddLatencyNumber(ui::INPUT_EVENT_LATENCY_UI_COMPONENT, 0, 0); |
| if (renderWidgetHostView_->ShouldRouteEvent(enterEvent)) { |
| renderWidgetHostView_->render_widget_host_->delegate() |
| ->GetInputEventRouter() |
| ->RouteMouseEvent(renderWidgetHostView_.get(), &enterEvent, |
| latency_info); |
| } else { |
| renderWidgetHostView_->ProcessMouseEvent(enterEvent, latency_info); |
| } |
| } |
| } |
| mouseEventWasIgnored_ = NO; |
| |
| // Don't cancel child popups; killing them on a mouse click would prevent the |
| // user from positioning the insertion point in the text field spawning the |
| // popup. A click outside the text field would cause the text field to drop |
| // the focus, and then EditorClientImpl::textFieldDidEndEditing() would cancel |
| // the popup anyway, so we're OK. |
| if (type == NSLeftMouseDown) |
| hasOpenMouseDown_ = YES; |
| else if (type == NSLeftMouseUp) |
| hasOpenMouseDown_ = NO; |
| |
| // TODO(suzhe): We should send mouse events to the input method first if it |
| // wants to handle them. But it won't work without implementing method |
| // - (NSUInteger)characterIndexForPoint:. |
| // See: http://code.google.com/p/chromium/issues/detail?id=47141 |
| // Instead of sending mouse events to the input method first, we now just |
| // simply confirm all ongoing composition here. |
| if (type == NSLeftMouseDown || type == NSRightMouseDown || |
| type == NSOtherMouseDown) { |
| [self finishComposingText]; |
| } |
| |
| WebMouseEvent event = |
| WebMouseEventBuilder::Build(theEvent, self, pointerType_); |
| ui::LatencyInfo latency_info(ui::SourceEventType::OTHER); |
| latency_info.AddLatencyNumber(ui::INPUT_EVENT_LATENCY_UI_COMPONENT, 0, 0); |
| if (renderWidgetHostView_->ShouldRouteEvent(event)) { |
| renderWidgetHostView_->render_widget_host_->delegate() |
| ->GetInputEventRouter() |
| ->RouteMouseEvent(renderWidgetHostView_.get(), &event, latency_info); |
| } else { |
| renderWidgetHostView_->ProcessMouseEvent(event, latency_info); |
| } |
| } |
| |
| - (void)tabletEvent:(NSEvent*)theEvent { |
| if ([theEvent type] == NSTabletProximity) { |
| isStylusEnteringProximity_ = [theEvent isEnteringProximity]; |
| NSPointingDeviceType deviceType = [theEvent pointingDeviceType]; |
| // For all tablet events, the pointer type will be pen or eraser. |
| pointerType_ = deviceType == NSEraserPointingDevice |
| ? blink::WebPointerProperties::PointerType::kEraser |
| : blink::WebPointerProperties::PointerType::kPen; |
| } |
| } |
| |
| - (BOOL)performKeyEquivalent:(NSEvent*)theEvent { |
| // |performKeyEquivalent:| is sent to all views of a window, not only down the |
| // responder chain (cf. "Handling Key Equivalents" in |
| // http://developer.apple.com/mac/library/documentation/Cocoa/Conceptual/EventOverview/HandlingKeyEvents/HandlingKeyEvents.html |
| // ). A |performKeyEquivalent:| may also bubble up from a dialog child window |
| // to perform browser commands such as switching tabs. We only want to handle |
| // key equivalents if we're first responder in the keyWindow. |
| if (![[self window] isKeyWindow] || [[self window] firstResponder] != self) |
| return NO; |
| |
| // If the event is reserved by the system, then do not pass it to web content. |
| if (EventIsReservedBySystem(theEvent)) |
| return NO; |
| |
| // If we return |NO| from this function, cocoa will send the key event to |
| // the menu and only if the menu does not process the event to |keyDown:|. We |
| // want to send the event to a renderer _before_ sending it to the menu, so |
| // we need to return |YES| for all events that might be swallowed by the menu. |
| // We do not return |YES| for every keypress because we don't get |keyDown:| |
| // events for keys that we handle this way. |
| NSUInteger modifierFlags = [theEvent modifierFlags]; |
| if ((modifierFlags & NSCommandKeyMask) == 0) { |
| // Make sure the menu does not contain key equivalents that don't |
| // contain cmd. |
| DCHECK(![[NSApp mainMenu] performKeyEquivalent:theEvent]); |
| return NO; |
| } |
| |
| // Command key combinations are sent via performKeyEquivalent rather than |
| // keyDown:. We just forward this on and if WebCore doesn't want to handle |
| // it, we let the WebContentsView figure out how to reinject it. |
| [self keyEvent:theEvent wasKeyEquivalent:YES]; |
| return YES; |
| } |
| |
| - (BOOL)_wantsKeyDownForEvent:(NSEvent*)event { |
| // This is a SPI that AppKit apparently calls after |performKeyEquivalent:| |
| // returned NO. If this function returns |YES|, Cocoa sends the event to |
| // |keyDown:| instead of doing other things with it. Ctrl-tab will be sent |
| // to us instead of doing key view loop control, ctrl-left/right get handled |
| // correctly, etc. |
| // (However, there are still some keys that Cocoa swallows, e.g. the key |
| // equivalent that Cocoa uses for toggling the input language. In this case, |
| // that's actually a good thing, though -- see http://crbug.com/26115 .) |
| return YES; |
| } |
| |
| - (EventHandled)keyEvent:(NSEvent*)theEvent { |
| if (responderDelegate_ && |
| [responderDelegate_ respondsToSelector:@selector(handleEvent:)]) { |
| BOOL handled = [responderDelegate_ handleEvent:theEvent]; |
| if (handled) |
| return kEventHandled; |
| } |
| |
| [self keyEvent:theEvent wasKeyEquivalent:NO]; |
| return kEventHandled; |
| } |
| |
| - (void)keyEvent:(NSEvent*)theEvent wasKeyEquivalent:(BOOL)equiv { |
| TRACE_EVENT0("browser", "RenderWidgetHostViewCocoa::keyEvent"); |
| |
| // If the user changes the system hotkey mapping after Chrome has been |
| // launched, then it is possible that a formerly reserved system hotkey is no |
| // longer reserved. The hotkey would have skipped the renderer, but would |
| // also have not been handled by the system. If this is the case, immediately |
| // return. |
| // TODO(erikchen): SystemHotkeyHelperMac should use the File System Events |
| // api to monitor changes to system hotkeys. This logic will have to be |
| // updated. |
| // http://crbug.com/383558. |
| if (EventIsReservedBySystem(theEvent)) |
| return; |
| |
| DCHECK([theEvent type] != NSKeyDown || |
| !equiv == !([theEvent modifierFlags] & NSCommandKeyMask)); |
| |
| if ([theEvent type] == NSFlagsChanged) { |
| // Ignore NSFlagsChanged events from the NumLock and Fn keys as |
| // Safari does in -[WebHTMLView flagsChanged:] (of "WebHTMLView.mm"). |
| // Also ignore unsupported |keyCode| (255) generated by Convert, NonConvert |
| // and KanaMode from JIS PC keyboard. |
| int keyCode = [theEvent keyCode]; |
| if (!keyCode || keyCode == 10 || keyCode == 63 || keyCode == 255) |
| return; |
| } |
| |
| // Don't cancel child popups; the key events are probably what's triggering |
| // the popup in the first place. |
| |
| RenderWidgetHostImpl* widgetHost = renderWidgetHostView_->render_widget_host_; |
| DCHECK(widgetHost); |
| |
| NativeWebKeyboardEvent event(theEvent); |
| ui::LatencyInfo latency_info; |
| if (event.GetType() == blink::WebInputEvent::kRawKeyDown || |
| event.GetType() == blink::WebInputEvent::kChar) { |
| latency_info.set_source_event_type(ui::SourceEventType::KEY_PRESS); |
| } |
| |
| latency_info.AddLatencyNumber(ui::INPUT_EVENT_LATENCY_UI_COMPONENT, 0, 0); |
| |
| // Force fullscreen windows to close on Escape so they won't keep the keyboard |
| // grabbed or be stuck onscreen if the renderer is hanging. |
| if (event.GetType() == NativeWebKeyboardEvent::kRawKeyDown && |
| event.windows_key_code == ui::VKEY_ESCAPE && |
| renderWidgetHostView_->pepper_fullscreen_window()) { |
| RenderWidgetHostViewMac* parent = |
| renderWidgetHostView_->fullscreen_parent_host_view(); |
| if (parent) |
| parent->cocoa_view()->suppressNextEscapeKeyUp_ = YES; |
| widgetHost->ShutdownAndDestroyWidget(true); |
| return; |
| } |
| |
| // If there are multiple widgets on the page (such as when there are |
| // out-of-process iframes), pick the one that should process this event. |
| if (widgetHost->delegate()) |
| widgetHost = widgetHost->delegate()->GetFocusedRenderWidgetHost(widgetHost); |
| if (!widgetHost) |
| return; |
| |
| // Suppress the escape key up event if necessary. |
| if (event.windows_key_code == ui::VKEY_ESCAPE && suppressNextEscapeKeyUp_) { |
| if (event.GetType() == NativeWebKeyboardEvent::kKeyUp) |
| suppressNextEscapeKeyUp_ = NO; |
| return; |
| } |
| |
| // Do not forward key up events unless preceded by a matching key down, |
| // otherwise we might get an event from releasing the return key in the |
| // omnibox (http://crbug.com/338736). |
| if ([theEvent type] == NSKeyUp) { |
| auto numErased = keyDownCodes_.erase([theEvent keyCode]); |
| if (numErased < 1) |
| return; |
| } |
| |
| // We only handle key down events and just simply forward other events. |
| if ([theEvent type] != NSKeyDown) { |
| widgetHost->ForwardKeyboardEventWithLatencyInfo(event, latency_info); |
| |
| // Possibly autohide the cursor. |
| if ([self shouldAutohideCursorForEvent:theEvent]) |
| [NSCursor setHiddenUntilMouseMoves:YES]; |
| |
| return; |
| } |
| |
| keyDownCodes_.insert([theEvent keyCode]); |
| |
| base::scoped_nsobject<RenderWidgetHostViewCocoa> keepSelfAlive([self retain]); |
| |
| // Records the current marked text state, so that we can know if the marked |
| // text was deleted or not after handling the key down event. |
| BOOL oldHasMarkedText = hasMarkedText_; |
| |
| // This method should not be called recursively. |
| DCHECK(!handlingKeyDown_); |
| |
| // Tells insertText: and doCommandBySelector: that we are handling a key |
| // down event. |
| handlingKeyDown_ = YES; |
| |
| // These variables might be set when handling the keyboard event. |
| // Clear them here so that we can know whether they have changed afterwards. |
| textToBeInserted_.clear(); |
| markedText_.clear(); |
| markedTextSelectedRange_ = NSMakeRange(NSNotFound, 0); |
| underlines_.clear(); |
| setMarkedTextReplacementRange_ = gfx::Range::InvalidRange(); |
| unmarkTextCalled_ = NO; |
| hasEditCommands_ = NO; |
| editCommands_.clear(); |
| |
| // Sends key down events to input method first, then we can decide what should |
| // be done according to input method's feedback. |
| [self interpretKeyEvents:[NSArray arrayWithObject:theEvent]]; |
| |
| handlingKeyDown_ = NO; |
| |
| // Indicates if we should send the key event and corresponding editor commands |
| // after processing the input method result. |
| BOOL delayEventUntilAfterImeCompostion = NO; |
| |
| // To emulate Windows, over-write |event.windowsKeyCode| to VK_PROCESSKEY |
| // while an input method is composing or inserting a text. |
| // Gmail checks this code in its onkeydown handler to stop auto-completing |
| // e-mail addresses while composing a CJK text. |
| // If the text to be inserted has only one character, then we don't need this |
| // trick, because we'll send the text as a key press event instead. |
| if (hasMarkedText_ || oldHasMarkedText || textToBeInserted_.length() > 1) { |
| NativeWebKeyboardEvent fakeEvent = event; |
| fakeEvent.windows_key_code = 0xE5; // VKEY_PROCESSKEY |
| fakeEvent.skip_in_browser = true; |
| widgetHost->ForwardKeyboardEventWithLatencyInfo(fakeEvent, latency_info); |
| // If this key event was handled by the input method, but |
| // -doCommandBySelector: (invoked by the call to -interpretKeyEvents: above) |
| // enqueued edit commands, then in order to let webkit handle them |
| // correctly, we need to send the real key event and corresponding edit |
| // commands after processing the input method result. |
| // We shouldn't do this if a new marked text was set by the input method, |
| // otherwise the new marked text might be cancelled by webkit. |
| if (hasEditCommands_ && !hasMarkedText_) |
| delayEventUntilAfterImeCompostion = YES; |
| } else { |
| widgetHost->ForwardKeyboardEventWithCommands(event, latency_info, |
| &editCommands_); |
| } |
| |
| // Calling ForwardKeyboardEventWithCommands() could have destroyed the |
| // widget. When the widget was destroyed, |
| // |renderWidgetHostView_->render_widget_host_| will be set to NULL. So we |
| // check it here and return immediately if it's NULL. |
| if (!renderWidgetHostView_->render_widget_host_) |
| return; |
| |
| // Then send keypress and/or composition related events. |
| // If there was a marked text or the text to be inserted is longer than 1 |
| // character, then we send the text by calling FinishComposingText(). |
| // Otherwise, if the text to be inserted only contains 1 character, then we |
| // can just send a keypress event which is fabricated by changing the type of |
| // the keydown event, so that we can retain all necessary informations, such |
| // as unmodifiedText, etc. And we need to set event.skip_in_browser to true to |
| // prevent the browser from handling it again. |
| // Note that, |textToBeInserted_| is a UTF-16 string, but it's fine to only |
| // handle BMP characters here, as we can always insert non-BMP characters as |
| // text. |
| BOOL textInserted = NO; |
| if (textToBeInserted_.length() > |
| ((hasMarkedText_ || oldHasMarkedText) ? 0u : 1u)) { |
| widgetHost->ImeCommitText(textToBeInserted_, |
| std::vector<blink::WebCompositionUnderline>(), |
| gfx::Range::InvalidRange(), 0); |
| textInserted = YES; |
| } |
| |
| // Updates or cancels the composition. If some text has been inserted, then |
| // we don't need to cancel the composition explicitly. |
| if (hasMarkedText_ && markedText_.length()) { |
| // Sends the updated marked text to the renderer so it can update the |
| // composition node in WebKit. |
| // When marked text is available, |markedTextSelectedRange_| will be the |
| // range being selected inside the marked text. |
| widgetHost->ImeSetComposition(markedText_, underlines_, |
| setMarkedTextReplacementRange_, |
| markedTextSelectedRange_.location, |
| NSMaxRange(markedTextSelectedRange_)); |
| } else if (oldHasMarkedText && !hasMarkedText_ && !textInserted) { |
| if (unmarkTextCalled_) { |
| widgetHost->ImeFinishComposingText(false); |
| } else { |
| widgetHost->ImeCancelComposition(); |
| } |
| } |
| |
| // Clear information from |interpretKeyEvents:| |
| setMarkedTextReplacementRange_ = gfx::Range::InvalidRange(); |
| |
| // If the key event was handled by the input method but it also generated some |
| // edit commands, then we need to send the real key event and corresponding |
| // edit commands here. This usually occurs when the input method wants to |
| // finish current composition session but still wants the application to |
| // handle the key event. See http://crbug.com/48161 for reference. |
| if (delayEventUntilAfterImeCompostion) { |
| // If |delayEventUntilAfterImeCompostion| is YES, then a fake key down event |
| // with windowsKeyCode == 0xE5 has already been sent to webkit. |
| // So before sending the real key down event, we need to send a fake key up |
| // event to balance it. |
| NativeWebKeyboardEvent fakeEvent = event; |
| fakeEvent.SetType(blink::WebInputEvent::kKeyUp); |
| fakeEvent.skip_in_browser = true; |
| widgetHost->ForwardKeyboardEventWithLatencyInfo(fakeEvent, latency_info); |
| // Not checking |renderWidgetHostView_->render_widget_host_| here because |
| // a key event with |skip_in_browser| == true won't be handled by browser, |
| // thus it won't destroy the widget. |
| |
| widgetHost->ForwardKeyboardEventWithCommands(event, latency_info, |
| &editCommands_); |
| |
| // Calling ForwardKeyboardEventWithCommands() could have destroyed the |
| // widget. When the widget was destroyed, |
| // |renderWidgetHostView_->render_widget_host_| will be set to NULL. So we |
| // check it here and return immediately if it's NULL. |
| if (!renderWidgetHostView_->render_widget_host_) |
| return; |
| } |
| |
| const NSUInteger kCtrlCmdKeyMask = NSControlKeyMask | NSCommandKeyMask; |
| // Only send a corresponding key press event if there is no marked text. |
| if (!hasMarkedText_) { |
| if (!textInserted && textToBeInserted_.length() == 1) { |
| // If a single character was inserted, then we just send it as a keypress |
| // event. |
| event.SetType(blink::WebInputEvent::kChar); |
| event.text[0] = textToBeInserted_[0]; |
| event.text[1] = 0; |
| event.skip_in_browser = true; |
| widgetHost->ForwardKeyboardEventWithLatencyInfo(event, latency_info); |
| } else if ((!textInserted || delayEventUntilAfterImeCompostion) && |
| event.text[0] != '\0' && |
| (([theEvent modifierFlags] & kCtrlCmdKeyMask) || |
| (hasEditCommands_ && editCommands_.empty()))) { |
| // We don't get insertText: calls if ctrl or cmd is down, or the key event |
| // generates an insert command. So synthesize a keypress event for these |
| // cases, unless the key event generated any other command. |
| event.SetType(blink::WebInputEvent::kChar); |
| event.skip_in_browser = true; |
| widgetHost->ForwardKeyboardEventWithLatencyInfo(event, latency_info); |
| } |
| } |
| |
| // Possibly autohide the cursor. |
| if ([self shouldAutohideCursorForEvent:theEvent]) |
| [NSCursor setHiddenUntilMouseMoves:YES]; |
| } |
| |
| - (void)forceTouchEvent:(NSEvent*)theEvent { |
| if (ui::ForceClickInvokesQuickLook()) |
| [self quickLookWithEvent:theEvent]; |
| } |
| |
| - (void)shortCircuitScrollWheelEvent:(NSEvent*)event { |
| if ([event phase] != NSEventPhaseEnded && |
| [event phase] != NSEventPhaseCancelled) { |
| return; |
| } |
| |
| if (renderWidgetHostView_->render_widget_host_) { |
| // History-swiping is not possible if the logic reaches this point. |
| WebMouseWheelEvent webEvent = WebMouseWheelEventBuilder::Build( |
| event, self); |
| webEvent.rails_mode = mouseWheelFilter_.UpdateRailsMode(webEvent); |
| if (renderWidgetHostView_->wheel_scroll_latching_enabled()) { |
| renderWidgetHostView_->mouse_wheel_phase_handler_ |
| .AddPhaseIfNeededAndScheduleEndEvent(webEvent, false); |
| } else { |
| ui::LatencyInfo latency_info(ui::SourceEventType::WHEEL); |
| latency_info.AddLatencyNumber(ui::INPUT_EVENT_LATENCY_UI_COMPONENT, 0, 0); |
| renderWidgetHostView_->render_widget_host_ |
| ->ForwardWheelEventWithLatencyInfo(webEvent, latency_info); |
| } |
| } |
| |
| if (endWheelMonitor_) { |
| [NSEvent removeMonitor:endWheelMonitor_]; |
| endWheelMonitor_ = nil; |
| } |
| } |
| |
| - (void)beginGestureWithEvent:(NSEvent*)event { |
| [responderDelegate_ beginGestureWithEvent:event]; |
| gestureBeginEvent_.reset( |
| new WebGestureEvent(WebGestureEventBuilder::Build(event, self))); |
| |
| // If the page is at the minimum zoom level, require a threshold be reached |
| // before the pinch has an effect. |
| if (renderWidgetHostView_->page_at_minimum_scale_) { |
| pinchHasReachedZoomThreshold_ = false; |
| pinchUnusedAmount_ = 1; |
| } |
| } |
| |
| - (void)endGestureWithEvent:(NSEvent*)event { |
| [responderDelegate_ endGestureWithEvent:event]; |
| gestureBeginEvent_.reset(); |
| |
| if (!renderWidgetHostView_->render_widget_host_) |
| return; |
| |
| if (gestureBeginPinchSent_) { |
| WebGestureEvent endEvent(WebGestureEventBuilder::Build(event, self)); |
| endEvent.SetType(WebInputEvent::kGesturePinchEnd); |
| renderWidgetHostView_->render_widget_host_->ForwardGestureEvent(endEvent); |
| gestureBeginPinchSent_ = NO; |
| } |
| } |
| |
| - (void)touchesMovedWithEvent:(NSEvent*)event { |
| [responderDelegate_ touchesMovedWithEvent:event]; |
| } |
| |
| - (void)touchesBeganWithEvent:(NSEvent*)event { |
| [responderDelegate_ touchesBeganWithEvent:event]; |
| } |
| |
| - (void)touchesCancelledWithEvent:(NSEvent*)event { |
| [responderDelegate_ touchesCancelledWithEvent:event]; |
| } |
| |
| - (void)touchesEndedWithEvent:(NSEvent*)event { |
| [responderDelegate_ touchesEndedWithEvent:event]; |
| } |
| |
| - (void)smartMagnifyWithEvent:(NSEvent*)event { |
| const WebGestureEvent& smartMagnifyEvent = |
| WebGestureEventBuilder::Build(event, self); |
| if (renderWidgetHostView_ && renderWidgetHostView_->render_widget_host_) { |
| renderWidgetHostView_->render_widget_host_->ForwardGestureEvent( |
| smartMagnifyEvent); |
| } |
| } |
| |
| - (void)showLookUpDictionaryOverlayInternal:(NSAttributedString*) string |
| baselinePoint:(NSPoint) baselinePoint |
| targetView:(NSView*) view { |
| if ([string length] == 0) { |
| // The PDF plugin does not support getting the attributed string at point. |
| // Until it does, use NSPerformService(), which opens Dictionary.app. |
| // TODO(shuchen): Support GetStringAtPoint() & GetStringFromRange() for PDF. |
| // See crbug.com/152438. |
| NSString* text = nil; |
| if (auto* selection = renderWidgetHostView_->GetTextSelection()) |
| text = base::SysUTF16ToNSString(selection->selected_text()); |
| |
| if ([text length] == 0) |
| return; |
| scoped_refptr<ui::UniquePasteboard> pasteboard = new ui::UniquePasteboard; |
| NSArray* types = [NSArray arrayWithObject:NSStringPboardType]; |
| [pasteboard->get() declareTypes:types owner:nil]; |
| if ([pasteboard->get() setString:text forType:NSStringPboardType]) |
| NSPerformService(@"Look Up in Dictionary", pasteboard->get()); |
| return; |
| } |
| NSPoint flippedBaselinePoint = { |
| baselinePoint.x, [view frame].size.height - baselinePoint.y, |
| }; |
| [view showDefinitionForAttributedString:string atPoint:flippedBaselinePoint]; |
| } |
| |
| - (void)showLookUpDictionaryOverlayFromRange:(NSRange)range |
| targetView:(NSView*)targetView { |
| content::RenderWidgetHostViewBase* focusedView = |
| renderWidgetHostView_->GetFocusedViewForTextSelection(); |
| if (!focusedView) |
| return; |
| |
| RenderWidgetHostImpl* widgetHost = |
| RenderWidgetHostImpl::From(focusedView->GetRenderWidgetHost()); |
| if (!widgetHost) |
| return; |
| |
| TextInputClientMac::GetInstance()->GetStringFromRange( |
| widgetHost, range, ^(NSAttributedString* string, NSPoint baselinePoint) { |
| if (auto* rwhv = widgetHost->GetView()) { |
| gfx::Point pointInRootView = rwhv->TransformPointToRootCoordSpace( |
| gfx::Point(baselinePoint.x, baselinePoint.y)); |
| baselinePoint.x = pointInRootView.x(); |
| baselinePoint.y = pointInRootView.y(); |
| } |
| [self showLookUpDictionaryOverlayInternal:string |
| baselinePoint:baselinePoint |
| targetView:targetView]; |
| }); |
| } |
| |
| - (void)showLookUpDictionaryOverlayAtPoint:(NSPoint)point { |
| gfx::Point rootPoint(point.x, NSHeight([self frame]) - point.y); |
| gfx::Point transformedPoint; |
| if (!renderWidgetHostView_->render_widget_host_ || |
| !renderWidgetHostView_->render_widget_host_->delegate() || |
| !renderWidgetHostView_->render_widget_host_->delegate() |
| ->GetInputEventRouter()) |
| return; |
| |
| RenderWidgetHostImpl* widgetHost = |
| renderWidgetHostView_->render_widget_host_->delegate() |
| ->GetInputEventRouter() |
| ->GetRenderWidgetHostAtPoint(renderWidgetHostView_.get(), rootPoint, |
| &transformedPoint); |
| if (!widgetHost) |
| return; |
| |
| TextInputClientMac::GetInstance()->GetStringAtPoint( |
| widgetHost, transformedPoint, |
| ^(NSAttributedString* string, NSPoint baselinePoint) { |
| if (auto* rwhv = widgetHost->GetView()) { |
| gfx::Point pointInRootView = rwhv->TransformPointToRootCoordSpace( |
| gfx::Point(baselinePoint.x, baselinePoint.y)); |
| baselinePoint.x = pointInRootView.x(); |
| baselinePoint.y = pointInRootView.y(); |
| } |
| [self showLookUpDictionaryOverlayInternal:string |
| baselinePoint:baselinePoint |
| targetView:self]; |
| }); |
| } |
| |
| // This is invoked only on 10.8 or newer when the user taps a word using |
| // three fingers. |
| - (void)quickLookWithEvent:(NSEvent*)event { |
| NSPoint point = [self convertPoint:[event locationInWindow] fromView:nil]; |
| [self showLookUpDictionaryOverlayAtPoint:point]; |
| } |
| |
| // This method handles 2 different types of hardware events. |
| // (Apple does not distinguish between them). |
| // a. Scrolling the middle wheel of a mouse. |
| // b. Swiping on the track pad. |
| // |
| // This method is responsible for 2 types of behavior: |
| // a. Scrolling the content of window. |
| // b. Navigating forwards/backwards in history. |
| // |
| // This is a brief description of the logic: |
| // 1. If the content can be scrolled, scroll the content. |
| // (This requires a roundtrip to blink to determine whether the content |
| // can be scrolled.) |
| // Once this logic is triggered, the navigate logic cannot be triggered |
| // until the gesture finishes. |
| // 2. If the user is making a horizontal swipe, start the navigate |
| // forward/backwards UI. |
| // Once this logic is triggered, the user can either cancel or complete |
| // the gesture. If the user completes the gesture, all remaining touches |
| // are swallowed, and not allowed to scroll the content. If the user |
| // cancels the gesture, all remaining touches are forwarded to the content |
| // scroll logic. The user cannot trigger the navigation logic again. |
| - (void)scrollWheel:(NSEvent*)event { |
| if (responderDelegate_ && |
| [responderDelegate_ respondsToSelector:@selector(handleEvent:)]) { |
| BOOL handled = [responderDelegate_ handleEvent:event]; |
| if (handled) |
| return; |
| } |
| |
| // Compute Event.Latency.OS.MOUSE_WHEEL histogram. |
| ui::ComputeEventLatencyOS(event); |
| |
| // Use an NSEvent monitor to listen for the wheel-end end. This ensures that |
| // the event is received even when the mouse cursor is no longer over the view |
| // when the scrolling ends (e.g. if the tab was switched). This is necessary |
| // for ending rubber-banding in such cases. |
| if ([event phase] == NSEventPhaseBegan && !endWheelMonitor_) { |
| endWheelMonitor_ = |
| [NSEvent addLocalMonitorForEventsMatchingMask:NSScrollWheelMask |
| handler:^(NSEvent* blockEvent) { |
| [self shortCircuitScrollWheelEvent:blockEvent]; |
| return blockEvent; |
| }]; |
| } |
| |
| // This is responsible for content scrolling! |
| if (renderWidgetHostView_->render_widget_host_) { |
| WebMouseWheelEvent webEvent = WebMouseWheelEventBuilder::Build(event, self); |
| webEvent.rails_mode = mouseWheelFilter_.UpdateRailsMode(webEvent); |
| ui::LatencyInfo latency_info(ui::SourceEventType::WHEEL); |
| latency_info.AddLatencyNumber(ui::INPUT_EVENT_LATENCY_UI_COMPONENT, 0, 0); |
| if (renderWidgetHostView_->wheel_scroll_latching_enabled()) { |
| renderWidgetHostView_->mouse_wheel_phase_handler_ |
| .AddPhaseIfNeededAndScheduleEndEvent( |
| webEvent, renderWidgetHostView_->ShouldRouteEvent(webEvent)); |
| if (webEvent.phase == blink::WebMouseWheelEvent::kPhaseEnded) { |
| // A wheel end event is scheduled and will get dispatched if momentum |
| // phase doesn't start in 100ms. Don't sent the wheel end event |
| // immediately. |
| return; |
| } |
| } |
| |
| if (renderWidgetHostView_->ShouldRouteEvent(webEvent)) { |
| renderWidgetHostView_->render_widget_host_->delegate() |
| ->GetInputEventRouter() |
| ->RouteMouseWheelEvent(renderWidgetHostView_.get(), &webEvent, |
| latency_info); |
| } else { |
| renderWidgetHostView_->ProcessMouseWheelEvent(webEvent, latency_info); |
| } |
| } |
| } |
| |
| // Called repeatedly during a pinch gesture, with incremental change values. |
| - (void)magnifyWithEvent:(NSEvent*)event { |
| if (!renderWidgetHostView_->render_widget_host_) |
| return; |
| |
| // If, due to nesting of multiple gestures (e.g, from multiple touch |
| // devices), the beginning of the gesture has been lost, skip the remainder |
| // of the gesture. |
| if (!gestureBeginEvent_) |
| return; |
| |
| if (!pinchHasReachedZoomThreshold_) { |
| pinchUnusedAmount_ *= (1 + [event magnification]); |
| if (pinchUnusedAmount_ < 0.667 || pinchUnusedAmount_ > 1.5) |
| pinchHasReachedZoomThreshold_ = true; |
| } |
| |
| // Send a GesturePinchBegin event if none has been sent yet. |
| if (!gestureBeginPinchSent_) { |
| WebGestureEvent beginEvent(*gestureBeginEvent_); |
| beginEvent.SetType(WebInputEvent::kGesturePinchBegin); |
| renderWidgetHostView_->render_widget_host_->ForwardGestureEvent(beginEvent); |
| gestureBeginPinchSent_ = YES; |
| } |
| |
| // Send a GesturePinchUpdate event. |
| WebGestureEvent updateEvent = WebGestureEventBuilder::Build(event, self); |
| updateEvent.data.pinch_update.zoom_disabled = !pinchHasReachedZoomThreshold_; |
| renderWidgetHostView_->render_widget_host_->ForwardGestureEvent(updateEvent); |
| } |
| |
| - (void)viewWillMoveToWindow:(NSWindow*)newWindow { |
| NSWindow* oldWindow = [self window]; |
| |
| NSNotificationCenter* notificationCenter = |
| [NSNotificationCenter defaultCenter]; |
| |
| if (oldWindow) { |
| [notificationCenter |
| removeObserver:self |
| name:NSWindowDidChangeBackingPropertiesNotification |
| object:oldWindow]; |
| [notificationCenter |
| removeObserver:self |
| name:NSWindowDidMoveNotification |
| object:oldWindow]; |
| [notificationCenter |
| removeObserver:self |
| name:NSWindowDidEndLiveResizeNotification |
| object:oldWindow]; |
| [notificationCenter |
| removeObserver:self |
| name:NSWindowDidBecomeKeyNotification |
| object:oldWindow]; |
| [notificationCenter |
| removeObserver:self |
| name:NSWindowDidResignKeyNotification |
| object:oldWindow]; |
| } |
| if (newWindow) { |
| [notificationCenter |
| addObserver:self |
| selector:@selector(windowDidChangeBackingProperties:) |
| name:NSWindowDidChangeBackingPropertiesNotification |
| object:newWindow]; |
| [notificationCenter |
| addObserver:self |
| selector:@selector(windowChangedGlobalFrame:) |
| name:NSWindowDidMoveNotification |
| object:newWindow]; |
| [notificationCenter |
| addObserver:self |
| selector:@selector(windowChangedGlobalFrame:) |
| name:NSWindowDidEndLiveResizeNotification |
| object:newWindow]; |
| [notificationCenter addObserver:self |
| selector:@selector(windowDidBecomeKey:) |
| name:NSWindowDidBecomeKeyNotification |
| object:newWindow]; |
| [notificationCenter addObserver:self |
| selector:@selector(windowDidResignKey:) |
| name:NSWindowDidResignKeyNotification |
| object:newWindow]; |
| } |
| } |
| |
| - (void)updateScreenProperties{ |
| renderWidgetHostView_->UpdateBackingStoreProperties(); |
| renderWidgetHostView_->UpdateDisplayLink(); |
| } |
| |
| // http://developer.apple.com/library/mac/#documentation/GraphicsAnimation/Conceptual/HighResolutionOSX/CapturingScreenContents/CapturingScreenContents.html#//apple_ref/doc/uid/TP40012302-CH10-SW4 |
| - (void)windowDidChangeBackingProperties:(NSNotification*)notification { |
| // Background tabs check if their screen scale factor, color profile, and |
| // vsync properties changed when they are added to a window. |
| |
| // Allocating a CGLayerRef with the current scale factor immediately from |
| // this handler doesn't work. Schedule the backing store update on the |
| // next runloop cycle, then things are read for CGLayerRef allocations to |
| // work. |
| [self performSelector:@selector(updateScreenProperties) |
| withObject:nil |
| afterDelay:0]; |
| } |
| |
| - (void)windowChangedGlobalFrame:(NSNotification*)notification { |
| renderWidgetHostView_->UpdateBackingStoreProperties(); |
| } |
| |
| - (void)setFrameSize:(NSSize)newSize { |
| TRACE_EVENT0("browser", "RenderWidgetHostViewCocoa::setFrameSize"); |
| |
| // NB: -[NSView setFrame:] calls through -setFrameSize:, so overriding |
| // -setFrame: isn't neccessary. |
| [super setFrameSize:newSize]; |
| |
| if (!renderWidgetHostView_->render_widget_host_) |
| return; |
| |
| if (renderWidgetHostView_->render_widget_host_->delegate()) |
| renderWidgetHostView_->render_widget_host_->delegate()->SendScreenRects(); |
| else |
| renderWidgetHostView_->render_widget_host_->SendScreenRects(); |
| renderWidgetHostView_->render_widget_host_->WasResized(); |
| renderWidgetHostView_->browser_compositor_->GetDelegatedFrameHost() |
| ->WasResized(); |
| |
| // Wait for the frame that WasResize might have requested. If the view is |
| // being made visible at a new size, then this call will have no effect |
| // because the view widget is still hidden, and the pause call in WasShown |
| // will have this effect for us. |
| renderWidgetHostView_->PauseForPendingResizeOrRepaintsAndDraw(); |
| } |
| |
| - (BOOL)canBecomeKeyView { |
| if (!renderWidgetHostView_->render_widget_host_) |
| return NO; |
| |
| return canBeKeyView_; |
| } |
| |
| - (BOOL)acceptsFirstResponder { |
| if (!renderWidgetHostView_->render_widget_host_) |
| return NO; |
| |
| return canBeKeyView_; |
| } |
| |
| - (void)windowDidBecomeKey:(NSNotification*)notification { |
| DCHECK([self window]); |
| DCHECK_EQ([self window], [notification object]); |
| if ([[self window] firstResponder] == self) |
| renderWidgetHostView_->SetActive(true); |
| } |
| |
| - (void)windowDidResignKey:(NSNotification*)notification { |
| DCHECK([self window]); |
| DCHECK_EQ([self window], [notification object]); |
| |
| // If our app is still active and we're still the key window, ignore this |
| // message, since it just means that a menu extra (on the "system status bar") |
| // was activated; we'll get another |-windowDidResignKey| if we ever really |
| // lose key window status. |
| if ([NSApp isActive] && ([NSApp keyWindow] == [self window])) |
| return; |
| |
| if ([[self window] firstResponder] == self) |
| renderWidgetHostView_->SetActive(false); |
| } |
| |
| - (BOOL)becomeFirstResponder { |
| if (!renderWidgetHostView_->render_widget_host_) |
| return NO; |
| |
| renderWidgetHostView_->render_widget_host_->GotFocus(); |
| renderWidgetHostView_->SetTextInputActive(true); |
| |
| // Cancel any onging composition text which was left before we lost focus. |
| // TODO(suzhe): We should do it in -resignFirstResponder: method, but |
| // somehow that method won't be called when switching among different tabs. |
| // See http://crbug.com/47209 |
| [self cancelComposition]; |
| |
| NSNumber* direction = [NSNumber numberWithUnsignedInteger: |
| [[self window] keyViewSelectionDirection]]; |
| NSDictionary* userInfo = |
| [NSDictionary dictionaryWithObject:direction |
| forKey:kSelectionDirection]; |
| [[NSNotificationCenter defaultCenter] |
| postNotificationName:kViewDidBecomeFirstResponder |
| object:self |
| userInfo:userInfo]; |
| |
| return YES; |
| } |
| |
| - (BOOL)resignFirstResponder { |
| renderWidgetHostView_->SetTextInputActive(false); |
| if (!renderWidgetHostView_->render_widget_host_) |
| return YES; |
| |
| if (closeOnDeactivate_) |
| renderWidgetHostView_->KillSelf(); |
| |
| renderWidgetHostView_->render_widget_host_->Blur(); |
| |
| // We should cancel any onging composition whenever RWH's Blur() method gets |
| // called, because in this case, webkit will confirm the ongoing composition |
| // internally. |
| [self cancelComposition]; |
| |
| return YES; |
| } |
| |
| - (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)item { |
| if (responderDelegate_ && |
| [responderDelegate_ |
| respondsToSelector:@selector(validateUserInterfaceItem: |
| isValidItem:)]) { |
| BOOL valid; |
| BOOL known = |
| [responderDelegate_ validateUserInterfaceItem:item isValidItem:&valid]; |
| if (known) |
| return valid; |
| } |
| |
| SEL action = [item action]; |
| BOOL is_render_view = |
| RenderViewHost::From(renderWidgetHostView_->render_widget_host_) != |
| nullptr; |
| |
| if (action == @selector(stopSpeaking:)) |
| return is_render_view && renderWidgetHostView_->IsSpeaking(); |
| |
| if (action == @selector(startSpeaking:)) |
| return is_render_view && renderWidgetHostView_->SupportsSpeech(); |
| |
| // For now, these actions are always enabled for render view, |
| // this is sub-optimal. |
| // TODO(suzhe): Plumb the "can*" methods up from WebCore. |
| if (action == @selector(undo:) || |
| action == @selector(redo:) || |
| action == @selector(cut:) || |
| action == @selector(copy:) || |
| action == @selector(copyToFindPboard:) || |
| action == @selector(paste:) || |
| action == @selector(pasteAndMatchStyle:)) { |
| return is_render_view; |
| } |
| |
| return editCommand_helper_->IsMenuItemEnabled(action, self); |
| } |
| |
| - (RenderWidgetHostViewMac*)renderWidgetHostViewMac { |
| return renderWidgetHostView_.get(); |
| } |
| |
| // Determine whether we should autohide the cursor (i.e., hide it until mouse |
| // move) for the given event. Customize here to be more selective about which |
| // key presses to autohide on. |
| - (BOOL)shouldAutohideCursorForEvent:(NSEvent*)event { |
| return (renderWidgetHostView_->GetTextInputType() != |
| ui::TEXT_INPUT_TYPE_NONE && |
| [event type] == NSKeyDown && |
| !([event modifierFlags] & NSCommandKeyMask)) |
| ? YES |
| : NO; |
| } |
| |
| - (NSArray *)accessibilityArrayAttributeValues:(NSString *)attribute |
| index:(NSUInteger)index |
| maxCount:(NSUInteger)maxCount { |
| NSArray* fullArray = [self accessibilityAttributeValue:attribute]; |
| NSUInteger totalLength = [fullArray count]; |
| if (index >= totalLength) |
| return nil; |
| NSUInteger length = MIN(totalLength - index, maxCount); |
| return [fullArray subarrayWithRange:NSMakeRange(index, length)]; |
| } |
| |
| - (NSUInteger)accessibilityArrayAttributeCount:(NSString *)attribute { |
| NSArray* fullArray = [self accessibilityAttributeValue:attribute]; |
| return [fullArray count]; |
| } |
| |
| - (id)accessibilityAttributeValue:(NSString *)attribute { |
| BrowserAccessibilityManager* manager = |
| renderWidgetHostView_->render_widget_host_ |
| ->GetRootBrowserAccessibilityManager(); |
| |
| // Contents specifies document view of RenderWidgetHostViewCocoa provided by |
| // BrowserAccessibilityManager. Children includes all subviews in addition to |
| // contents. Currently we do not have subviews besides the document view. |
| if (([attribute isEqualToString:NSAccessibilityChildrenAttribute] || |
| [attribute isEqualToString:NSAccessibilityContentsAttribute]) && |
| manager) { |
| return [NSArray arrayWithObjects:ToBrowserAccessibilityCocoa( |
| manager->GetRoot()), nil]; |
| } else if ([attribute isEqualToString:NSAccessibilityRoleAttribute]) { |
| return NSAccessibilityScrollAreaRole; |
| } |
| id ret = [super accessibilityAttributeValue:attribute]; |
| return ret; |
| } |
| |
| - (NSArray*)accessibilityAttributeNames { |
| NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease]; |
| [ret addObject:NSAccessibilityContentsAttribute]; |
| [ret addObjectsFromArray:[super accessibilityAttributeNames]]; |
| return ret; |
| } |
| |
| - (id)accessibilityHitTest:(NSPoint)point { |
| BrowserAccessibilityManager* manager = |
| renderWidgetHostView_->render_widget_host_ |
| ->GetRootBrowserAccessibilityManager(); |
| if (!manager) |
| return self; |
| NSPoint pointInWindow = |
| ui::ConvertPointFromScreenToWindow([self window], point); |
| NSPoint localPoint = [self convertPoint:pointInWindow fromView:nil]; |
| localPoint.y = NSHeight([self bounds]) - localPoint.y; |
| BrowserAccessibilityCocoa* root = |
| ToBrowserAccessibilityCocoa(manager->GetRoot()); |
| id obj = [root accessibilityHitTest:localPoint]; |
| return obj; |
| } |
| |
| - (BOOL)accessibilityIsIgnored { |
| BrowserAccessibilityManager* manager = |
| renderWidgetHostView_->render_widget_host_ |
| ->GetRootBrowserAccessibilityManager(); |
| return !manager; |
| } |
| |
| - (NSUInteger)accessibilityGetIndexOf:(id)child { |
| BrowserAccessibilityManager* manager = |
| renderWidgetHostView_->render_widget_host_ |
| ->GetRootBrowserAccessibilityManager(); |
| // Only child is root. |
| if (manager && |
| ToBrowserAccessibilityCocoa(manager->GetRoot()) == child) { |
| return 0; |
| } else { |
| return NSNotFound; |
| } |
| } |
| |
| - (id)accessibilityFocusedUIElement { |
| BrowserAccessibilityManager* manager = |
| renderWidgetHostView_->render_widget_host_ |
| ->GetRootBrowserAccessibilityManager(); |
| if (manager) { |
| BrowserAccessibility* focused_item = manager->GetFocus(); |
| DCHECK(focused_item); |
| if (focused_item) { |
| BrowserAccessibilityCocoa* focused_item_cocoa = |
| ToBrowserAccessibilityCocoa(focused_item); |
| DCHECK(focused_item_cocoa); |
| if (focused_item_cocoa) |
| return focused_item_cocoa; |
| } |
| } |
| return [super accessibilityFocusedUIElement]; |
| } |
| |
| // Below is our NSTextInputClient implementation. |
| // |
| // When WebHTMLView receives a NSKeyDown event, WebHTMLView calls the following |
| // functions to process this event. |
| // |
| // [WebHTMLView keyDown] -> |
| // EventHandler::keyEvent() -> |
| // ... |
| // [WebEditorClient handleKeyboardEvent] -> |
| // [WebHTMLView _interceptEditingKeyEvent] -> |
| // [NSResponder interpretKeyEvents] -> |
| // [WebHTMLView insertText] -> |
| // Editor::insertText() |
| // |
| // Unfortunately, it is hard for Chromium to use this implementation because |
| // it causes key-typing jank. |
| // RenderWidgetHostViewMac is running in a browser process. On the other |
| // hand, Editor and EventHandler are running in a renderer process. |
| // So, if we used this implementation, a NSKeyDown event is dispatched to |
| // the following functions of Chromium. |
| // |
| // [RenderWidgetHostViewMac keyEvent] (browser) -> |
| // |Sync IPC (KeyDown)| (*1) -> |
| // EventHandler::keyEvent() (renderer) -> |
| // ... |
| // EditorClientImpl::handleKeyboardEvent() (renderer) -> |
| // |Sync IPC| (*2) -> |
| // [RenderWidgetHostViewMac _interceptEditingKeyEvent] (browser) -> |
| // [self interpretKeyEvents] -> |
| // [RenderWidgetHostViewMac insertText] (browser) -> |
| // |Async IPC| -> |
| // Editor::insertText() (renderer) |
| // |
| // (*1) we need to wait until this call finishes since WebHTMLView uses the |
| // result of EventHandler::keyEvent(). |
| // (*2) we need to wait until this call finishes since WebEditorClient uses |
| // the result of [WebHTMLView _interceptEditingKeyEvent]. |
| // |
| // This needs many sync IPC messages sent between a browser and a renderer for |
| // each key event, which would probably result in key-typing jank. |
| // To avoid this problem, this implementation processes key events (and input |
| // method events) totally in a browser process and sends asynchronous input |
| // events, almost same as KeyboardEvents (and TextEvents) of DOM Level 3, to a |
| // renderer process. |
| // |
| // [RenderWidgetHostViewMac keyEvent] (browser) -> |
| // |Async IPC (RawKeyDown)| -> |
| // [self interpretKeyEvents] -> |
| // [RenderWidgetHostViewMac insertText] (browser) -> |
| // |Async IPC (Char)| -> |
| // Editor::insertText() (renderer) |
| // |
| // Since this implementation doesn't have to wait any IPC calls, this doesn't |
| // make any key-typing jank. --hbono 7/23/09 |
| // |
| extern "C" { |
| extern NSString *NSTextInputReplacementRangeAttributeName; |
| } |
| |
| - (NSArray *)validAttributesForMarkedText { |
| // This code is just copied from WebKit except renaming variables. |
| if (!validAttributesForMarkedText_) { |
| validAttributesForMarkedText_.reset([[NSArray alloc] initWithObjects: |
| NSUnderlineStyleAttributeName, |
| NSUnderlineColorAttributeName, |
| NSMarkedClauseSegmentAttributeName, |
| NSTextInputReplacementRangeAttributeName, |
| nil]); |
| } |
| return validAttributesForMarkedText_.get(); |
| } |
| |
| - (NSUInteger)characterIndexForPoint:(NSPoint)thePoint { |
| DCHECK([self window]); |
| // |thePoint| is in screen coordinates, but needs to be converted to WebKit |
| // coordinates (upper left origin). Scroll offsets will be taken care of in |
| // the renderer. |
| thePoint = ui::ConvertPointFromScreenToWindow([self window], thePoint); |
| thePoint = [self convertPoint:thePoint fromView:nil]; |
| thePoint.y = NSHeight([self frame]) - thePoint.y; |
| |
| if (!renderWidgetHostView_->render_widget_host_ || |
| !renderWidgetHostView_->render_widget_host_->delegate() || |
| !renderWidgetHostView_->render_widget_host_->delegate() |
| ->GetInputEventRouter()) |
| return NSNotFound; |
| |
| gfx::Point rootPoint(thePoint.x, thePoint.y); |
| gfx::Point transformedPoint; |
| RenderWidgetHostImpl* widgetHost = |
| renderWidgetHostView_->render_widget_host_->delegate() |
| ->GetInputEventRouter() |
| ->GetRenderWidgetHostAtPoint(renderWidgetHostView_.get(), rootPoint, |
| &transformedPoint); |
| if (!widgetHost) |
| return NSNotFound; |
| |
| NSUInteger index = |
| TextInputClientMac::GetInstance()->GetCharacterIndexAtPoint( |
| widgetHost, transformedPoint); |
| return index; |
| } |
| |
| - (NSRect)firstViewRectForCharacterRange:(NSRange)theRange |
| actualRange:(NSRangePointer)actualRange { |
| NSRect rect; |
| if (!renderWidgetHostView_->GetCachedFirstRectForCharacterRange( |
| theRange, |
| &rect, |
| actualRange)) { |
| rect = TextInputClientMac::GetInstance()->GetFirstRectForRange( |
| renderWidgetHostView_->GetFocusedWidget(), theRange); |
| |
| // TODO(thakis): Pipe |actualRange| through TextInputClientMac machinery. |
| if (actualRange) |
| *actualRange = theRange; |
| } |
| |
| // The returned rectangle is in WebKit coordinates (upper left origin), so |
| // flip the coordinate system. |
| NSRect viewFrame = [self frame]; |
| rect.origin.y = NSHeight(viewFrame) - NSMaxY(rect); |
| return rect; |
| } |
| |
| - (NSRect)firstRectForCharacterRange:(NSRange)theRange |
| actualRange:(NSRangePointer)actualRange { |
| // During tab closure, events can arrive after RenderWidgetHostViewMac:: |
| // Destroy() is called, which will have set |render_widget_host_| to null. |
| if (!renderWidgetHostView_->GetFocusedWidget()) { |
| [self cancelComposition]; |
| return NSZeroRect; |
| } |
| |
| NSRect rect = [self firstViewRectForCharacterRange:theRange |
| actualRange:actualRange]; |
| |
| // Convert into screen coordinates for return. |
| rect = [self convertRect:rect toView:nil]; |
| rect = [[self window] convertRectToScreen:rect]; |
| return rect; |
| } |
| |
| - (NSRange)selectedRange { |
| if (selectedRange_.location == NSNotFound) |
| return NSMakeRange(NSNotFound, 0); |
| return selectedRange_; |
| } |
| |
| - (NSRange)markedRange { |
| // An input method calls this method to check if an application really has |
| // a text being composed when hasMarkedText call returns true. |
| // Returns the range saved in the setMarkedText method so the input method |
| // calls the setMarkedText method and we can update the composition node |
| // there. (When this method returns an empty range, the input method doesn't |
| // call the setMarkedText method.) |
| return hasMarkedText_ ? markedRange_ : NSMakeRange(NSNotFound, 0); |
| } |
| |
| - (NSAttributedString*)attributedSubstringForProposedRange:(NSRange)range |
| actualRange:(NSRangePointer)actualRange { |
| // Prepare |actualRange| as if the proposed range is invalid. If it is valid, |
| // then |actualRange| will be updated again. |
| if (actualRange) |
| *actualRange = NSMakeRange(NSNotFound, 0); |
| |
| // The caller of this method is allowed to pass nonsensical ranges. These |
| // can't even be converted into gfx::Ranges. |
| if (range.location == NSNotFound || range.length == 0) |
| return nil; |
| if (range.length >= std::numeric_limits<NSUInteger>::max() - range.location) |
| return nil; |
| |
| const gfx::Range requested_range(range); |
| if (requested_range.is_reversed()) |
| return nil; |
| |
| gfx::Range expected_range; |
| const base::string16* expected_text; |
| const content::TextInputManager::CompositionRangeInfo* compositionInfo = |
| renderWidgetHostView_->GetCompositionRangeInfo(); |
| const content::TextInputManager::TextSelection* selection = |
| renderWidgetHostView_->GetTextSelection(); |
| if (!selection) |
| return nil; |
| |
| if (compositionInfo && !compositionInfo->range.is_empty()) { |
| // This method might get called after TextInputState.type is reset to none, |
| // in which case there will be no composition range information |
| // (https://crbug.com/698672). |
| expected_text = &markedText_; |
| expected_range = compositionInfo->range; |
| } else { |
| expected_text = &selection->text(); |
| size_t offset = selection->offset(); |
| expected_range = gfx::Range(offset, offset + expected_text->size()); |
| } |
| |
| gfx::Range actual_range = expected_range.Intersect(requested_range); |
| if (!actual_range.IsValid()) |
| return nil; |
| |
| // Gets the raw bytes to avoid unnecessary string copies for generating |
| // NSString. |
| const base::char16* bytes = |
| &(*expected_text)[actual_range.start() - expected_range.start()]; |
| // Avoid integer overflow. |
| base::CheckedNumeric<size_t> requested_len = actual_range.length(); |
| requested_len *= sizeof(base::char16); |
| NSUInteger bytes_len = base::strict_cast<NSUInteger, size_t>( |
| requested_len.ValueOrDefault(0)); |
| base::scoped_nsobject<NSString> ns_string( |
| [[NSString alloc] initWithBytes:bytes |
| length:bytes_len |
| encoding:NSUTF16LittleEndianStringEncoding]); |
| if (actualRange) |
| *actualRange = actual_range.ToNSRange(); |
| |
| return [[[NSAttributedString alloc] initWithString:ns_string] autorelease]; |
| } |
| |
| - (NSInteger)conversationIdentifier { |
| return reinterpret_cast<NSInteger>(self); |
| } |
| |
| // Each RenderWidgetHostViewCocoa has its own input context, but we return |
| // nil when the caret is in non-editable content or password box to avoid |
| // making input methods do their work. |
| - (NSTextInputContext *)inputContext { |
| switch (renderWidgetHostView_->GetTextInputType()) { |
| case ui::TEXT_INPUT_TYPE_NONE: |
| case ui::TEXT_INPUT_TYPE_PASSWORD: |
| return nil; |
| default: |
| return [super inputContext]; |
| } |
| } |
| |
| - (BOOL)hasMarkedText { |
| // An input method calls this function to figure out whether or not an |
| // application is really composing a text. If it is composing, it calls |
| // the markedRange method, and maybe calls the setMarkedText method. |
| // It seems an input method usually calls this function when it is about to |
| // cancel an ongoing composition. If an application has a non-empty marked |
| // range, it calls the setMarkedText method to delete the range. |
| return hasMarkedText_; |
| } |
| |
| - (void)unmarkText { |
| // Delete the composition node of the renderer and finish an ongoing |
| // composition. |
| // It seems an input method calls the setMarkedText method and set an empty |
| // text when it cancels an ongoing composition, i.e. I have never seen an |
| // input method calls this method. |
| hasMarkedText_ = NO; |
| markedText_.clear(); |
| markedTextSelectedRange_ = NSMakeRange(NSNotFound, 0); |
| underlines_.clear(); |
| |
| // If we are handling a key down event, then FinishComposingText() will be |
| // called in keyEvent: method. |
| if (!handlingKeyDown_) { |
| if (renderWidgetHostView_->GetActiveWidget()) { |
| renderWidgetHostView_->GetActiveWidget()->ImeFinishComposingText(false); |
| } |
| } else { |
| unmarkTextCalled_ = YES; |
| } |
| } |
| |
| - (void)setMarkedText:(id)string selectedRange:(NSRange)newSelRange |
| replacementRange:(NSRange)replacementRange { |
| // An input method updates the composition string. |
| // We send the given text and range to the renderer so it can update the |
| // composition node of WebKit. |
| // TODO(suzhe): It's hard for us to support replacementRange without accessing |
| // the full web content. |
| BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]]; |
| NSString* im_text = isAttributedString ? [string string] : string; |
| int length = [im_text length]; |
| |
| // |markedRange_| will get set on a callback from ImeSetComposition(). |
| markedTextSelectedRange_ = newSelRange; |
| markedText_ = base::SysNSStringToUTF16(im_text); |
| hasMarkedText_ = (length > 0); |
| |
| underlines_.clear(); |
| if (isAttributedString) { |
| ExtractUnderlines(string, &underlines_); |
| } else { |
| // Use a thin black underline by default. |
| underlines_.push_back(blink::WebCompositionUnderline( |
| 0, length, SK_ColorBLACK, false, SK_ColorTRANSPARENT)); |
| } |
| |
| // If we are handling a key down event, then SetComposition() will be |
| // called in keyEvent: method. |
| // Input methods of Mac use setMarkedText calls with an empty text to cancel |
| // an ongoing composition. So, we should check whether or not the given text |
| // is empty to update the input method state. (Our input method backend |
| // automatically cancels an ongoing composition when we send an empty text. |
| // So, it is OK to send an empty text to the renderer.) |
| if (handlingKeyDown_) { |
| setMarkedTextReplacementRange_ = gfx::Range(replacementRange); |
| } else { |
| if (renderWidgetHostView_->GetActiveWidget()) { |
| renderWidgetHostView_->GetActiveWidget()->ImeSetComposition( |
| markedText_, underlines_, gfx::Range(replacementRange), |
| newSelRange.location, NSMaxRange(newSelRange)); |
| } |
| } |
| } |
| |
| - (void)doCommandBySelector:(SEL)selector { |
| // An input method calls this function to dispatch an editing command to be |
| // handled by this view. |
| if (selector == @selector(noop:)) |
| return; |
| |
| std::string command( |
| [RenderWidgetHostViewMacEditCommandHelper:: |
| CommandNameForSelector(selector) UTF8String]); |
| |
| // If this method is called when handling a key down event, then we need to |
| // handle the command in the key event handler. Otherwise we can just handle |
| // it here. |
| if (handlingKeyDown_) { |
| hasEditCommands_ = YES; |
| // We ignore commands that insert characters, because this was causing |
| // strange behavior (e.g. tab always inserted a tab rather than moving to |
| // the next field on the page). |
| if (!base::StartsWith(command, "insert", |
| base::CompareCase::INSENSITIVE_ASCII)) |
| editCommands_.push_back(EditCommand(command, "")); |
| } else { |
| RenderWidgetHostImpl* rwh = renderWidgetHostView_->render_widget_host_; |
| rwh->Send(new InputMsg_ExecuteEditCommand(rwh->GetRoutingID(), |
| command, "")); |
| } |
| } |
| |
| - (void)insertText:(id)string replacementRange:(NSRange)replacementRange { |
| // An input method has characters to be inserted. |
| // Same as Linux, Mac calls this method not only: |
| // * when an input method finishes composing text, but also; |
| // * when we type an ASCII character (without using input methods). |
| // When we aren't using input methods, we should send the given character as |
| // a Char event so it is dispatched to an onkeypress() event handler of |
| // JavaScript. |
| // On the other hand, when we are using input methods, we should send the |
| // given characters as an input method event and prevent the characters from |
| // being dispatched to onkeypress() event handlers. |
| // Text inserting might be initiated by other source instead of keyboard |
| // events, such as the Characters dialog. In this case the text should be |
| // sent as an input method event as well. |
| // TODO(suzhe): It's hard for us to support replacementRange without accessing |
| // the full web content. |
| BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]]; |
| NSString* im_text = isAttributedString ? [string string] : string; |
| if (handlingKeyDown_) { |
| textToBeInserted_.append(base::SysNSStringToUTF16(im_text)); |
| } else { |
| gfx::Range replacement_range(replacementRange); |
| if (renderWidgetHostView_->GetActiveWidget()) { |
| renderWidgetHostView_->GetActiveWidget()->ImeCommitText( |
| base::SysNSStringToUTF16(im_text), |
| std::vector<blink::WebCompositionUnderline>(), replacement_range, 0); |
| } |
| } |
| |
| // Inserting text will delete all marked text automatically. |
| hasMarkedText_ = NO; |
| } |
| |
| - (void)insertText:(id)string { |
| [self insertText:string replacementRange:NSMakeRange(NSNotFound, 0)]; |
| } |
| |
| - (void)viewDidMoveToWindow { |
| if ([self window]) |
| [self updateScreenProperties]; |
| renderWidgetHostView_->browser_compositor_->SetNSViewAttachedToWindow( |
| [self window]); |
| |
| // If we switch windows (or are removed from the view hierarchy), cancel any |
| // open mouse-downs. |
| if (hasOpenMouseDown_) { |
| WebMouseEvent event(WebInputEvent::kMouseUp, WebInputEvent::kNoModifiers, |
| ui::EventTimeStampToSeconds(ui::EventTimeForNow())); |
| event.button = WebMouseEvent::Button::kLeft; |
| renderWidgetHostView_->ForwardMouseEvent(event); |
| |
| hasOpenMouseDown_ = NO; |
| } |
| } |
| |
| - (void)undo:(id)sender { |
| WebContents* web_contents = renderWidgetHostView_->GetWebContents(); |
| if (web_contents) |
| web_contents->Undo(); |
| } |
| |
| - (void)redo:(id)sender { |
| WebContents* web_contents = renderWidgetHostView_->GetWebContents(); |
| if (web_contents) |
| web_contents->Redo(); |
| } |
| |
| - (void)cut:(id)sender { |
| if (auto* delegate = |
| renderWidgetHostView_->GetFocusedRenderWidgetHostDelegate()) { |
| delegate->Cut(); |
| } |
| } |
| |
| - (void)copy:(id)sender { |
| if (auto* delegate = |
| renderWidgetHostView_->GetFocusedRenderWidgetHostDelegate()) { |
| delegate->Copy(); |
| } |
| } |
| |
| - (void)copyToFindPboard:(id)sender { |
| WebContents* web_contents = renderWidgetHostView_->GetWebContents(); |
| if (web_contents) |
| web_contents->CopyToFindPboard(); |
| } |
| |
| - (void)paste:(id)sender { |
| if (auto* delegate = |
| renderWidgetHostView_->GetFocusedRenderWidgetHostDelegate()) { |
| delegate->Paste(); |
| } |
| } |
| |
| - (void)pasteAndMatchStyle:(id)sender { |
| WebContents* web_contents = renderWidgetHostView_->GetWebContents(); |
| if (web_contents) |
| web_contents->PasteAndMatchStyle(); |
| } |
| |
| - (void)selectAll:(id)sender { |
| // editCommand_helper_ adds implementations for most NSResponder methods |
| // dynamically. But the renderer side only sends selection results back to |
| // the browser if they were triggered by a keyboard event or went through |
| // one of the Select methods on RWH. Since selectAll: is called from the |
| // menu handler, neither is true. |
| // Explicitly call SelectAll() here to make sure the renderer returns |
| // selection results. |
| if (auto* delegate = |
| renderWidgetHostView_->GetFocusedRenderWidgetHostDelegate()) { |
| delegate->SelectAll(); |
| } |
| } |
| |
| - (void)startSpeaking:(id)sender { |
| GetRenderWidgetHostViewToUse(renderWidgetHostView_.get())->SpeakSelection(); |
| } |
| |
| - (void)stopSpeaking:(id)sender { |
| GetRenderWidgetHostViewToUse(renderWidgetHostView_.get())->StopSpeaking(); |
| } |
| |
| - (void)cancelComposition { |
| if (!hasMarkedText_) |
| return; |
| |
| NSTextInputContext* inputContext = [self inputContext]; |
| [inputContext discardMarkedText]; |
| |
| hasMarkedText_ = NO; |
| // Should not call [self unmarkText] here, because it'll send unnecessary |
| // cancel composition IPC message to the renderer. |
| } |
| |
| - (void)finishComposingText { |
| if (!hasMarkedText_) |
| return; |
| |
| if (renderWidgetHostView_->GetActiveWidget()) { |
| renderWidgetHostView_->GetActiveWidget()->ImeFinishComposingText(false); |
| } |
| |
| [self cancelComposition]; |
| } |
| |
| // Overriding a NSResponder method to support application services. |
| |
| - (id)validRequestorForSendType:(NSString*)sendType |
| returnType:(NSString*)returnType { |
| id requestor = nil; |
| BOOL sendTypeIsString = [sendType isEqual:NSStringPboardType]; |
| BOOL returnTypeIsString = [returnType isEqual:NSStringPboardType]; |
| const content::TextInputManager::TextSelection* selection = |
| renderWidgetHostView_->GetTextSelection(); |
| BOOL hasText = selection && !selection->selected_text().empty(); |
| BOOL takesText = |
| renderWidgetHostView_->GetTextInputType() != ui::TEXT_INPUT_TYPE_NONE; |
| |
| if (sendTypeIsString && hasText && !returnType) { |
| requestor = self; |
| } else if (!sendType && returnTypeIsString && takesText) { |
| requestor = self; |
| } else if (sendTypeIsString && returnTypeIsString && hasText && takesText) { |
| requestor = self; |
| } else { |
| requestor = [super validRequestorForSendType:sendType |
| returnType:returnType]; |
| } |
| return requestor; |
| } |
| |
| - (void)updateCursor:(NSCursor*)cursor { |
| if (currentCursor_ == cursor) |
| return; |
| |
| currentCursor_.reset([cursor retain]); |
| [[self window] invalidateCursorRectsForView:self]; |
| } |
| |
| - (void)popupWindowWillClose:(NSNotification *)notification { |
| renderWidgetHostView_->KillSelf(); |
| } |
| |
| @end |
| |
| // |
| // Supporting application services |
| // |
| @implementation RenderWidgetHostViewCocoa(NSServicesRequests) |
| |
| - (BOOL)writeSelectionToPasteboard:(NSPasteboard*)pboard |
| types:(NSArray*)types { |
| const content::TextInputManager::TextSelection* selection = |
| renderWidgetHostView_->GetTextSelection(); |
| if (!selection || selection->selected_text().empty() || |
| ![types containsObject:NSStringPboardType]) { |
| return NO; |
| } |
| |
| base::scoped_nsobject<NSString> text([[NSString alloc] |
| initWithUTF8String:base::UTF16ToUTF8(selection->selected_text()) |
| .c_str()]); |
| NSArray* toDeclare = [NSArray arrayWithObject:NSStringPboardType]; |
| [pboard declareTypes:toDeclare owner:nil]; |
| return [pboard setString:text forType:NSStringPboardType]; |
| } |
| |
| - (BOOL)readSelectionFromPasteboard:(NSPasteboard*)pboard { |
| NSString *string = [pboard stringForType:NSStringPboardType]; |
| if (!string) return NO; |
| |
| // If the user is currently using an IME, confirm the IME input, |
| // and then insert the text from the service, the same as TextEdit and Safari. |
| [self finishComposingText]; |
| [self insertText:string]; |
| return YES; |
| } |
| |
| - (BOOL)isOpaque { |
| return opaque_; |
| } |
| |
| // "-webkit-app-region: drag | no-drag" is implemented on Mac by excluding |
| // regions that are not draggable. (See ControlRegionView in |
| // native_app_window_cocoa.mm). This requires the render host view to be |
| // draggable by default. |
| - (BOOL)mouseDownCanMoveWindow { |
| return YES; |
| } |
| |
| @end |