1 /* -*- Mode: objc; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "mozilla/ArrayUtils.h"
8 #include "mozilla/Logging.h"
9 #include "mozilla/Unused.h"
14 #include "nsChildView.h"
15 #include "nsCocoaWindow.h"
17 #include "mozilla/Maybe.h"
18 #include "mozilla/MiscEvents.h"
19 #include "mozilla/MouseEvents.h"
20 #include "mozilla/NativeKeyBindingsType.h"
21 #include "mozilla/PresShell.h"
22 #include "mozilla/SwipeTracker.h"
23 #include "mozilla/TextEventDispatcher.h"
24 #include "mozilla/TextEvents.h"
25 #include "mozilla/TouchEvents.h"
26 #include "mozilla/WheelHandlingHelper.h" // for WheelDeltaAdjustmentStrategy
27 #include "mozilla/WritingModes.h"
28 #include "mozilla/dom/DataTransfer.h"
29 #include "mozilla/dom/MouseEventBinding.h"
30 #include "mozilla/dom/SimpleGestureEventBinding.h"
31 #include "mozilla/dom/WheelEventBinding.h"
33 #include "nsArrayUtils.h"
34 #include "nsExceptionHandler.h"
35 #include "nsObjCExceptions.h"
37 #include "nsThreadUtils.h"
38 #include "nsToolkit.h"
41 #include "nsFontMetrics.h"
42 #include "nsIRollupListener.h"
43 #include "nsViewManager.h"
45 #include "nsILocalFileMac.h"
46 #include "nsGfxCIID.h"
47 #include "nsStyleConsts.h"
48 #include "nsIWidgetListener.h"
49 #include "nsIScreen.h"
51 #include "nsDragService.h"
52 #include "nsClipboard.h"
53 #include "nsCursorManager.h"
54 #include "nsWindowMap.h"
55 #include "nsCocoaUtils.h"
56 #include "nsMenuUtilsX.h"
57 #include "nsMenuBarX.h"
58 #include "NativeKeyBindings.h"
59 #include "MacThemeGeometryType.h"
61 #include "gfxContext.h"
62 #include "gfxQuartzSurface.h"
65 #include "GfxTexturesReporter.h"
66 #include "GLTextureImage.h"
67 #include "GLContextProvider.h"
68 #include "GLContextCGL.h"
69 #include "OGLShaderProgram.h"
70 #include "ScopedGLHelpers.h"
71 #include "HeapCopyOfStackArray.h"
72 #include "mozilla/layers/IAPZCTreeManager.h"
73 #include "mozilla/layers/APZInputBridge.h"
74 #include "mozilla/layers/APZThreadUtils.h"
75 #include "mozilla/layers/CompositorOGL.h"
76 #include "mozilla/layers/CompositorBridgeParent.h"
77 #include "mozilla/layers/InputAPZContext.h"
78 #include "mozilla/layers/IpcResourceUpdateQueue.h"
79 #include "mozilla/layers/NativeLayerCA.h"
80 #include "mozilla/layers/WebRenderBridgeChild.h"
81 #include "mozilla/layers/WebRenderLayerManager.h"
82 #include "mozilla/webrender/WebRenderAPI.h"
83 #include "mozilla/widget/CompositorWidget.h"
84 #include "mozilla/widget/Screen.h"
86 #include "mozilla/gfx/2D.h"
87 #include "mozilla/gfx/BorrowedContext.h"
89 # include "nsAccessibilityService.h"
90 # include "mozilla/a11y/Platform.h"
93 #include "mozilla/Preferences.h"
94 #include "mozilla/StaticPrefs_apz.h"
95 #include "mozilla/StaticPrefs_browser.h"
96 #include "mozilla/StaticPrefs_general.h"
97 #include "mozilla/StaticPrefs_gfx.h"
98 #include "mozilla/StaticPrefs_ui.h"
102 #include <ApplicationServices/ApplicationServices.h>
104 #include "GeckoProfiler.h"
106 #include "mozilla/layers/ChromeProcessController.h"
107 #include "nsLayoutUtils.h"
108 #include "InputData.h"
109 #include "VibrancyManager.h"
110 #include "nsNativeThemeCocoa.h"
111 #include "nsIDOMWindowUtils.h"
113 #include "UnitTransforms.h"
114 #include "mozilla/UniquePtrExtensions.h"
115 #include "CustomCocoaEvents.h"
116 #include "NativeMenuSupport.h"
118 using namespace mozilla;
119 using namespace mozilla::layers;
120 using namespace mozilla::gl;
121 using namespace mozilla::widget;
123 using mozilla::gfx::Matrix4x4;
126 #undef INVALIDATE_DEBUGGING // flash areas as they are invalidated
128 // Don't put more than this many rects in the dirty region, just fluff
129 // out to the bounding-box if there are more
130 #define MAX_RECTS_IN_REGION 100
132 LazyLogModule sCocoaLog("nsCocoaWidgets");
135 CG_EXTERN void CGContextResetCTM(CGContextRef);
136 CG_EXTERN void CGContextSetCTM(CGContextRef, CGAffineTransform);
137 CG_EXTERN void CGContextResetClip(CGContextRef);
139 typedef CFTypeRef CGSRegionObj;
140 CGError CGSNewRegionWithRect(const CGRect* rect, CGSRegionObj* outRegion);
141 CGError CGSNewRegionWithRectList(const CGRect* rects, int rectCount,
142 CGSRegionObj* outRegion);
145 // defined in nsMenuBarX.mm
146 extern NSMenu* sApplicationMenu; // Application menu shared by all menubars
148 extern nsIArray* gDraggedTransferables;
150 ChildView* ChildViewMouseTracker::sLastMouseEventView = nil;
151 NSEvent* ChildViewMouseTracker::sLastMouseMoveEvent = nil;
152 NSWindow* ChildViewMouseTracker::sWindowUnderMouse = nil;
153 NSPoint ChildViewMouseTracker::sLastScrollEventScreenLocation = NSZeroPoint;
155 #ifdef INVALIDATE_DEBUGGING
156 static void blinkRect(Rect* r);
157 static void blinkRgn(RgnHandle rgn);
160 bool gUserCancelledDrag = false;
162 uint32_t nsChildView::sLastInputEventCount = 0;
164 static bool sIsTabletPointerActivated = false;
166 static uint32_t sUniqueKeyEventId = 0;
168 // The view that will do our drawing or host our NSOpenGLContext or Core
170 @interface PixelHostingView : NSView {
175 @interface ChildView (Private)
177 // sets up our view, attaching it to its owning gecko view
178 - (id)initWithFrame:(NSRect)inFrame geckoChild:(nsChildView*)inChild;
180 // set up a gecko mouse event based on a cocoa mouse event
181 - (void)convertCocoaMouseWheelEvent:(NSEvent*)aMouseEvent
182 toGeckoEvent:(WidgetWheelEvent*)outWheelEvent;
183 - (void)convertCocoaMouseEvent:(NSEvent*)aMouseEvent
184 toGeckoEvent:(WidgetInputEvent*)outGeckoEvent;
185 - (void)convertCocoaTabletPointerEvent:(NSEvent*)aMouseEvent
186 toGeckoEvent:(WidgetMouseEvent*)outGeckoEvent;
187 - (NSMenu*)contextMenu;
189 - (void)markLayerForDisplay;
190 - (CALayer*)rootCALayer;
191 - (void)updateRootCALayer;
194 - (id<mozAccessible>)accessible;
197 - (LayoutDeviceIntPoint)convertWindowCoordinates:(NSPoint)aPoint;
198 - (LayoutDeviceIntPoint)convertWindowCoordinatesRoundDown:(NSPoint)aPoint;
200 - (BOOL)inactiveWindowAcceptsMouseEvent:(NSEvent*)aEvent;
201 - (void)updateWindowDraggableState;
203 - (bool)beginOrEndGestureForEventPhase:(NSEvent*)aEvent;
209 // Flips a screen coordinate from a point in the cocoa coordinate system
210 // (bottom-left rect) to a point that is a "flipped" cocoa coordinate system
211 // (starts in the top-left).
212 static inline void FlipCocoaScreenCoordinate(NSPoint& inPoint) {
213 inPoint.y = nsCocoaUtils::FlippedScreenY(inPoint.y);
218 nsChildView::nsChildView()
222 mParentWidget(nullptr),
223 mCompositingLock("ChildViewCompositing"),
224 mBackingScaleFactor(0.0),
226 mSizeMode(nsSizeMode_Normal),
228 mIsDispatchPaint(false) {}
230 nsChildView::~nsChildView() {
231 // Notify the children that we're gone. childView->ResetParent() can change
232 // our list of children while it's being iterated, so the way we iterate the
233 // list must allow for this.
234 for (nsIWidget* kid = mLastChild; kid;) {
235 nsChildView* childView = static_cast<nsChildView*>(kid);
236 kid = kid->GetPrevSibling();
237 childView->ResetParent();
240 NS_WARNING_ASSERTION(
242 "nsChildView object destroyed without calling Destroy()");
245 mNativeLayerRoot->RemoveLayer(mContentLayer); // safe if already removed
250 // An nsChildView object that was in use can be destroyed without Destroy()
251 // ever being called on it. So we also need to do a quick, safe cleanup
252 // here (it's too late to just call Destroy(), which can cause crashes).
253 // It's particularly important to make sure widgetDestroyed is called on our
254 // mView -- this method NULLs mView's mGeckoChild, and NULL checks on
255 // mGeckoChild are used throughout the ChildView class to tell if it's safe
256 // to use a ChildView object.
257 [mView widgetDestroyed]; // Safe if mView is nil.
259 TearDownView(); // Safe if called twice.
262 nsresult nsChildView::Create(nsIWidget* aParent, nsNativeWidget aNativeParent,
263 const LayoutDeviceIntRect& aRect,
264 widget::InitData* aInitData) {
265 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
267 // Because the hidden window is created outside of an event loop,
268 // we need to provide an autorelease pool to avoid leaking cocoa objects
270 nsAutoreleasePool localPool;
274 // Ensure that the toolkit is created.
275 nsToolkit::GetToolkit();
277 BaseCreate(aParent, aInitData);
281 // This is the popup window case. aParent is the nsCocoaWindow for the
282 // popup window, and mParentView will be its content view.
283 mParentView = (NSView*)aParent->GetNativeData(NS_NATIVE_WIDGET);
284 mParentWidget = aParent;
286 // This is the top-level window case.
287 // aNativeParent will be the contentView of our window, since that's what
288 // nsCocoaWindow returns when asked for an NS_NATIVE_VIEW.
289 // We do not have a direct "parent widget" association with the top level
290 // window's nsCocoaWindow object.
291 mParentView = reinterpret_cast<NSView*>(aNativeParent);
294 // create our parallel NSView and hook it up to our parent. Recall
295 // that NS_NATIVE_WIDGET is the NSView.
296 CGFloat scaleFactor = nsCocoaUtils::GetBackingScaleFactor(mParentView);
297 NSRect r = nsCocoaUtils::DevPixelsToCocoaPoints(mBounds, scaleFactor);
298 mView = [[ChildView alloc] initWithFrame:r geckoChild:this];
300 mNativeLayerRoot = NativeLayerRootCA::CreateForCALayer([mView rootCALayer]);
301 mNativeLayerRoot->SetBackingScale(scaleFactor);
303 // If this view was created in a Gecko view hierarchy, the initial state
304 // is hidden. If the view is attached only to a native NSView but has
305 // no Gecko parent (as in embedding), the initial state is visible.
307 [mView setHidden:YES];
311 // Hook it up in the NSView hierarchy.
313 [mParentView addSubview:mView];
316 // if this is a ChildView, make sure that our per-window data
318 if ([mView isKindOfClass:[ChildView class]])
319 [[WindowDataMap sharedWindowDataMap] ensureDataForWindow:[mView window]];
321 NS_ASSERTION(!mTextInputHandler, "mTextInputHandler has already existed");
322 mTextInputHandler = new TextInputHandler(this, mView);
326 NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
329 void nsChildView::TearDownView() {
330 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
334 NSWindow* win = [mView window];
335 NSResponder* responder = [win firstResponder];
337 // We're being unhooked from the view hierarchy, don't leave our view
338 // or a child view as the window first responder.
339 if (responder && [responder isKindOfClass:[NSView class]] &&
340 [(NSView*)responder isDescendantOf:mView]) {
341 [win makeFirstResponder:[mView superview]];
344 // If mView is win's contentView, win (mView's NSWindow) "owns" mView --
345 // win has retained mView, and will detach it from the view hierarchy and
346 // release it when necessary (when win is itself destroyed (in a call to
347 // [win dealloc])). So all we need to do here is call [mView release] (to
348 // match the call to [mView retain] in nsChildView::StandardCreate()).
349 // Also calling [mView removeFromSuperviewWithoutNeedingDisplay] causes
350 // mView to be released again and dealloced, while remaining win's
351 // contentView. So if we do that here, win will (for a short while) have
352 // an invalid contentView (for the consequences see bmo bugs 381087 and
354 if ([mView isEqual:[win contentView]]) {
357 // Stop NSView hierarchy being changed during [ChildView drawRect:]
358 [mView performSelectorOnMainThread:@selector(delayedTearDown)
360 waitUntilDone:false];
364 NS_OBJC_END_TRY_IGNORE_BLOCK;
367 nsCocoaWindow* nsChildView::GetAppWindowWidget() const {
368 id windowDelegate = [[mView window] delegate];
369 if (windowDelegate && [windowDelegate isKindOfClass:[WindowDelegate class]]) {
370 return [(WindowDelegate*)windowDelegate geckoWidget];
375 void nsChildView::Destroy() {
376 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
378 if (mOnDestroyCalled) return;
379 mOnDestroyCalled = true;
381 // Stuff below may delete the last ref to this
382 nsCOMPtr<nsIWidget> kungFuDeathGrip(this);
385 // Make sure that no composition is in progress while disconnecting
386 // ourselves from the view.
387 MutexAutoLock lock(mCompositingLock);
389 [mView widgetDestroyed];
392 nsBaseWidget::Destroy();
394 NotifyWindowDestroyed();
399 nsBaseWidget::OnDestroy();
401 NS_OBJC_END_TRY_IGNORE_BLOCK;
407 static void PrintViewHierarchy(NSView *view)
410 NSLog(@" view is %x, frame %@", view, NSStringFromRect([view frame]));
411 view = [view superview];
416 // Return native data according to aDataType
417 void* nsChildView::GetNativeData(uint32_t aDataType) {
418 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
420 void* retVal = nullptr;
423 case NS_NATIVE_WIDGET:
424 retVal = (void*)mView;
427 case NS_NATIVE_WINDOW:
428 retVal = [mView window];
431 case NS_NATIVE_GRAPHIC:
432 NS_ERROR("Requesting NS_NATIVE_GRAPHIC on a Mac OS X child view!");
436 case NS_NATIVE_OFFSETX:
440 case NS_NATIVE_OFFSETY:
444 case NS_RAW_NATIVE_IME_CONTEXT:
445 retVal = GetPseudoIMEContext();
449 retVal = [mView inputContext];
450 // If input context isn't available on this widget, we should set |this|
451 // instead of nullptr since if this returns nullptr, IMEStateManager
452 // cannot manage composition with TextComposition instance. Although,
453 // this case shouldn't occur.
454 if (NS_WARN_IF(!retVal)) {
459 case NS_NATIVE_WINDOW_WEBRTC_DEVICE_ID: {
460 NSWindow* win = [mView window];
462 retVal = (void*)[win windowNumber];
470 NS_OBJC_END_TRY_BLOCK_RETURN(nullptr);
475 void nsChildView::SuppressAnimation(bool aSuppress) {
476 GetAppWindowWidget()->SuppressAnimation(aSuppress);
479 bool nsChildView::IsVisible() const {
480 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
486 if (!GetAppWindowWidget()->IsVisible()) {
490 // mVisible does not accurately reflect the state of a hidden tabbed view
491 // so verify that the view has a window as well
492 // then check native widget hierarchy visibility
493 return ([mView window] != nil) && !NSIsEmptyRect([mView visibleRect]);
495 NS_OBJC_END_TRY_BLOCK_RETURN(false);
498 // Some NSView methods (e.g. setFrame and setHidden) invalidate the view's
499 // bounds in our window. However, we don't want these invalidations because
500 // they are unnecessary and because they actually slow us down since we
501 // block on the compositor inside drawRect.
502 // When we actually need something invalidated, there will be an explicit call
503 // to Invalidate from Gecko, so turning these automatic invalidations off
504 // won't hurt us in the non-OMTC case.
505 // The invalidations inside these NSView methods happen via a call to the
506 // private method -[NSWindow _setNeedsDisplayInRect:]. Our BaseWindow
507 // implementation of that method is augmented to let us ignore those calls
508 // using -[BaseWindow disable/enableSetNeedsDisplay].
509 static void ManipulateViewWithoutNeedingDisplay(NSView* aView,
510 void (^aCallback)()) {
511 BaseWindow* win = nil;
512 if ([[aView window] isKindOfClass:[BaseWindow class]]) {
513 win = (BaseWindow*)[aView window];
515 [win disableSetNeedsDisplay];
517 [win enableSetNeedsDisplay];
520 // Hide or show this component
521 void nsChildView::Show(bool aState) {
522 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
524 if (aState != mVisible) {
525 // Provide an autorelease pool because this gets called during startup
526 // on the "hidden window", resulting in cocoa object leakage if there's
528 nsAutoreleasePool localPool;
530 ManipulateViewWithoutNeedingDisplay(mView, ^{
531 [mView setHidden:!aState];
537 NS_OBJC_END_TRY_IGNORE_BLOCK;
540 // Change the parent of this widget
541 void nsChildView::SetParent(nsIWidget* aNewParent) {
542 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
544 if (mOnDestroyCalled) return;
546 nsCOMPtr<nsIWidget> kungFuDeathGrip(this);
549 mParentWidget->RemoveChild(this);
553 ReparentNativeWidget(aNewParent);
555 [mView removeFromSuperview];
559 mParentWidget = aNewParent;
562 mParentWidget->AddChild(this);
565 NS_OBJC_END_TRY_IGNORE_BLOCK;
568 void nsChildView::ReparentNativeWidget(nsIWidget* aNewParent) {
569 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
571 MOZ_ASSERT(aNewParent, "null widget");
573 if (mOnDestroyCalled) return;
575 NSView<mozView>* newParentView =
576 (NSView<mozView>*)aNewParent->GetNativeData(NS_NATIVE_WIDGET);
577 NS_ENSURE_TRUE_VOID(newParentView);
579 // we hold a ref to mView, so this is safe
580 [mView removeFromSuperview];
581 mParentView = newParentView;
582 [mParentView addSubview:mView];
584 NS_OBJC_END_TRY_IGNORE_BLOCK;
587 void nsChildView::ResetParent() {
588 if (!mOnDestroyCalled) {
589 if (mParentWidget) mParentWidget->RemoveChild(this);
590 if (mView) [mView removeFromSuperview];
592 mParentWidget = nullptr;
595 nsIWidget* nsChildView::GetParent() { return mParentWidget; }
597 float nsChildView::GetDPI() {
599 nsCOMPtr<nsIScreen> screen = GetWidgetScreen();
601 screen->GetDpi(&dpi);
606 void nsChildView::Enable(bool aState) {}
608 bool nsChildView::IsEnabled() const { return true; }
610 void nsChildView::SetFocus(Raise, mozilla::dom::CallerType aCallerType) {
611 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
613 NSWindow* window = [mView window];
614 if (window) [window makeFirstResponder:mView];
615 NS_OBJC_END_TRY_IGNORE_BLOCK;
618 // Override to set the cursor on the mac
619 void nsChildView::SetCursor(const Cursor& aCursor) {
620 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
622 if ([mView isDragInProgress]) {
623 return; // Don't change the cursor during dragging.
626 nsBaseWidget::SetCursor(aCursor);
628 if (NS_SUCCEEDED([[nsCursorManager sharedInstance]
629 setCustomCursor:aCursor
630 widgetScaleFactor:BackingScaleFactor()])) {
634 [[nsCursorManager sharedInstance] setNonCustomCursor:aCursor];
636 NS_OBJC_END_TRY_IGNORE_BLOCK;
641 // Get this component dimension
642 LayoutDeviceIntRect nsChildView::GetBounds() {
643 return !mView ? mBounds : CocoaPointsToDevPixels([mView frame]);
646 LayoutDeviceIntRect nsChildView::GetClientBounds() {
647 LayoutDeviceIntRect rect = GetBounds();
648 if (!mParentWidget) {
649 // For top level widgets we want the position on screen, not the position
650 // of this view inside the window.
651 rect.MoveTo(WidgetToScreenOffset());
656 LayoutDeviceIntRect nsChildView::GetScreenBounds() {
657 LayoutDeviceIntRect rect = GetBounds();
658 rect.MoveTo(WidgetToScreenOffset());
662 double nsChildView::GetDefaultScaleInternal() { return BackingScaleFactor(); }
664 CGFloat nsChildView::BackingScaleFactor() const {
665 if (mBackingScaleFactor > 0.0) {
666 return mBackingScaleFactor;
671 mBackingScaleFactor = nsCocoaUtils::GetBackingScaleFactor(mView);
672 return mBackingScaleFactor;
675 void nsChildView::BackingScaleFactorChanged() {
676 CGFloat newScale = nsCocoaUtils::GetBackingScaleFactor(mView);
678 // ignore notification if it hasn't really changed (or maybe we have
679 // disabled HiDPI mode via prefs)
680 if (mBackingScaleFactor == newScale) {
684 SuspendAsyncCATransactions();
685 mBackingScaleFactor = newScale;
686 NSRect frame = [mView frame];
687 mBounds = nsCocoaUtils::CocoaRectToGeckoRectDevPix(frame, newScale);
689 mNativeLayerRoot->SetBackingScale(mBackingScaleFactor);
691 if (mWidgetListener && !mWidgetListener->GetAppWindow()) {
692 if (PresShell* presShell = mWidgetListener->GetPresShell()) {
693 presShell->BackingScaleFactorChanged();
698 int32_t nsChildView::RoundsWidgetCoordinatesTo() {
699 if (BackingScaleFactor() == 2.0) {
705 // Move this component, aX and aY are in the parent widget coordinate system
706 void nsChildView::Move(double aX, double aY) {
707 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
709 int32_t x = NSToIntRound(aX);
710 int32_t y = NSToIntRound(aY);
712 if (!mView || (mBounds.x == x && mBounds.y == y)) return;
717 ManipulateViewWithoutNeedingDisplay(mView, ^{
718 [mView setFrame:DevPixelsToCocoaPoints(mBounds)];
723 NS_OBJC_END_TRY_IGNORE_BLOCK;
726 void nsChildView::Resize(double aWidth, double aHeight, bool aRepaint) {
727 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
729 int32_t width = NSToIntRound(aWidth);
730 int32_t height = NSToIntRound(aHeight);
732 if (!mView || (mBounds.width == width && mBounds.height == height)) return;
734 SuspendAsyncCATransactions();
735 mBounds.width = width;
736 mBounds.height = height;
738 ManipulateViewWithoutNeedingDisplay(mView, ^{
739 [mView setFrame:DevPixelsToCocoaPoints(mBounds)];
742 if (mVisible && aRepaint) {
743 [[mView pixelHostingView] setNeedsDisplay:YES];
748 NS_OBJC_END_TRY_IGNORE_BLOCK;
751 void nsChildView::Resize(double aX, double aY, double aWidth, double aHeight,
753 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
755 int32_t x = NSToIntRound(aX);
756 int32_t y = NSToIntRound(aY);
757 int32_t width = NSToIntRound(aWidth);
758 int32_t height = NSToIntRound(aHeight);
760 BOOL isMoving = (mBounds.x != x || mBounds.y != y);
761 BOOL isResizing = (mBounds.width != width || mBounds.height != height);
762 if (!mView || (!isMoving && !isResizing)) return;
769 SuspendAsyncCATransactions();
770 mBounds.width = width;
771 mBounds.height = height;
773 CALayer* layer = [mView rootCALayer];
774 double scale = BackingScaleFactor();
775 layer.bounds = CGRectMake(0, 0, width / scale, height / scale);
778 ManipulateViewWithoutNeedingDisplay(mView, ^{
779 [mView setFrame:DevPixelsToCocoaPoints(mBounds)];
782 if (mVisible && aRepaint) {
783 [[mView pixelHostingView] setNeedsDisplay:YES];
788 if (mOnDestroyCalled) return;
790 if (isResizing) ReportSizeEvent();
792 NS_OBJC_END_TRY_IGNORE_BLOCK;
795 // The following three methods are primarily an attempt to avoid glitches during
797 // Here's some background on how these glitches come to be:
798 // CoreAnimation transactions are per-thread. They don't nest across threads.
799 // If you submit a transaction on the main thread and a transaction on a
800 // different thread, the two will race to the window server and show up on the
801 // screen in the order that they happen to arrive in at the window server.
802 // When the window size changes, there's another event that needs to be
803 // synchronized with: the window "shape" change. Cocoa has built-in
804 // synchronization mechanics that make sure that *main thread* window paints
805 // during window resizes are synchronized properly with the window shape change.
806 // But no such built-in synchronization exists for CATransactions that are
807 // triggered on a non-main thread. To cope with this, we define a "danger zone"
808 // during which we simply avoid triggering any CATransactions on a non-main
809 // thread (called "async" CATransactions here). This danger zone starts at the
810 // earliest opportunity at which we know about the size change, which is
811 // nsChildView::Resize, and ends at a point at which we know for sure that the
812 // paint has been handled completely, which is when we return to the event loop
813 // after layer display.
814 void nsChildView::SuspendAsyncCATransactions() {
815 if (mUnsuspendAsyncCATransactionsRunnable) {
816 mUnsuspendAsyncCATransactionsRunnable->Cancel();
817 mUnsuspendAsyncCATransactionsRunnable = nullptr;
820 // Make sure that there actually will be a CATransaction on the main thread
821 // during which we get a chance to schedule unsuspension. Otherwise we might
822 // accidentally stay suspended indefinitely.
823 [mView markLayerForDisplay];
825 mNativeLayerRoot->SuspendOffMainThreadCommits();
828 void nsChildView::MaybeScheduleUnsuspendAsyncCATransactions() {
829 if (mNativeLayerRoot->AreOffMainThreadCommitsSuspended() &&
830 !mUnsuspendAsyncCATransactionsRunnable) {
831 mUnsuspendAsyncCATransactionsRunnable = NewCancelableRunnableMethod(
832 "nsChildView::MaybeScheduleUnsuspendAsyncCATransactions", this,
833 &nsChildView::UnsuspendAsyncCATransactions);
834 NS_DispatchToMainThread(mUnsuspendAsyncCATransactionsRunnable);
838 void nsChildView::UnsuspendAsyncCATransactions() {
839 mUnsuspendAsyncCATransactionsRunnable = nullptr;
841 if (mNativeLayerRoot->UnsuspendOffMainThreadCommits()) {
842 // We need to call mNativeLayerRoot->CommitToScreen() at the next available
844 // The easiest way to handle this request is to mark the layer as needing
845 // display, because this will schedule a main thread CATransaction, during
846 // which HandleMainThreadCATransaction will call CommitToScreen().
847 [mView markLayerForDisplay];
851 void nsChildView::UpdateFullscreen(bool aFullscreen) {
852 if (mNativeLayerRoot) {
853 mNativeLayerRoot->SetWindowIsFullscreen(aFullscreen);
857 nsresult nsChildView::SynthesizeNativeKeyEvent(
858 int32_t aNativeKeyboardLayout, int32_t aNativeKeyCode,
859 uint32_t aModifierFlags, const nsAString& aCharacters,
860 const nsAString& aUnmodifiedCharacters, nsIObserver* aObserver) {
861 AutoObserverNotifier notifier(aObserver, "keyevent");
862 return mTextInputHandler->SynthesizeNativeKeyEvent(
863 aNativeKeyboardLayout, aNativeKeyCode, aModifierFlags, aCharacters,
864 aUnmodifiedCharacters);
867 nsresult nsChildView::SynthesizeNativeMouseEvent(
868 LayoutDeviceIntPoint aPoint, NativeMouseMessage aNativeMessage,
869 MouseButton aButton, nsIWidget::Modifiers aModifierFlags,
870 nsIObserver* aObserver) {
871 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
873 AutoObserverNotifier notifier(aObserver, "mouseevent");
876 nsCocoaUtils::DevPixelsToCocoaPoints(aPoint, BackingScaleFactor());
878 // Move the mouse cursor to the requested position and reconnect it to the
880 CGWarpMouseCursorPosition(NSPointToCGPoint(pt));
881 CGAssociateMouseAndMouseCursorPosition(true);
883 // aPoint is given with the origin on the top left, but convertScreenToBase
884 // expects a point in a coordinate system that has its origin on the bottom
886 NSPoint screenPoint = NSMakePoint(pt.x, nsCocoaUtils::FlippedScreenY(pt.y));
887 NSPoint windowPoint =
888 nsCocoaUtils::ConvertPointFromScreen([mView window], screenPoint);
889 NSEventModifierFlags modifierFlags =
890 nsCocoaUtils::ConvertWidgetModifiersToMacModifierFlags(aModifierFlags);
892 if (aButton == MouseButton::eX1 || aButton == MouseButton::eX2) {
893 // NSEvent has `buttonNumber` for `NSEventTypeOther*`. However, it seems
894 // that there is no way to specify it. Therefore, we should return error
896 return NS_ERROR_INVALID_ARG;
899 NSEventType nativeEventType;
900 switch (aNativeMessage) {
901 case NativeMouseMessage::ButtonDown:
902 case NativeMouseMessage::ButtonUp: {
904 case MouseButton::ePrimary:
905 nativeEventType = aNativeMessage == NativeMouseMessage::ButtonDown
906 ? NSEventTypeLeftMouseDown
907 : NSEventTypeLeftMouseUp;
909 case MouseButton::eMiddle:
910 nativeEventType = aNativeMessage == NativeMouseMessage::ButtonDown
911 ? NSEventTypeOtherMouseDown
912 : NSEventTypeOtherMouseUp;
914 case MouseButton::eSecondary:
915 nativeEventType = aNativeMessage == NativeMouseMessage::ButtonDown
916 ? NSEventTypeRightMouseDown
917 : NSEventTypeRightMouseUp;
920 return NS_ERROR_INVALID_ARG;
924 case NativeMouseMessage::Move:
925 nativeEventType = NSEventTypeMouseMoved;
927 case NativeMouseMessage::EnterWindow:
928 nativeEventType = NSEventTypeMouseEntered;
930 case NativeMouseMessage::LeaveWindow:
931 nativeEventType = NSEventTypeMouseExited;
936 [NSEvent mouseEventWithType:nativeEventType
938 modifierFlags:modifierFlags
939 timestamp:[[NSProcessInfo processInfo] systemUptime]
940 windowNumber:[[mView window] windowNumber]
946 if (!event) return NS_ERROR_FAILURE;
948 if ([[mView window] isKindOfClass:[BaseWindow class]]) {
949 // Tracking area events don't end up in their tracking areas when sent
950 // through [NSApp sendEvent:], so pass them directly to the right methods.
951 BaseWindow* window = (BaseWindow*)[mView window];
952 if (nativeEventType == NSEventTypeMouseEntered) {
953 [window mouseEntered:event];
956 if (nativeEventType == NSEventTypeMouseExited) {
957 [window mouseExited:event];
960 if (nativeEventType == NSEventTypeMouseMoved) {
961 [window mouseMoved:event];
966 [NSApp sendEvent:event];
969 NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
972 nsresult nsChildView::SynthesizeNativeMouseScrollEvent(
973 mozilla::LayoutDeviceIntPoint aPoint, uint32_t aNativeMessage,
974 double aDeltaX, double aDeltaY, double aDeltaZ, uint32_t aModifierFlags,
975 uint32_t aAdditionalFlags, nsIObserver* aObserver) {
976 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
978 AutoObserverNotifier notifier(aObserver, "mousescrollevent");
981 nsCocoaUtils::DevPixelsToCocoaPoints(aPoint, BackingScaleFactor());
983 // Move the mouse cursor to the requested position and reconnect it to the
985 CGWarpMouseCursorPosition(NSPointToCGPoint(pt));
986 CGAssociateMouseAndMouseCursorPosition(true);
988 // Mostly copied from http://stackoverflow.com/a/6130349
989 CGScrollEventUnit units =
990 (aAdditionalFlags & nsIDOMWindowUtils::MOUSESCROLL_SCROLL_LINES)
991 ? kCGScrollEventUnitLine
992 : kCGScrollEventUnitPixel;
993 CGEventRef cgEvent = CGEventCreateScrollWheelEvent(
994 NULL, units, 3, (int32_t)aDeltaY, (int32_t)aDeltaX, (int32_t)aDeltaZ);
996 return NS_ERROR_FAILURE;
999 if (aNativeMessage) {
1000 CGEventSetIntegerValueField(cgEvent, kCGScrollWheelEventScrollPhase,
1004 // On macOS 10.14 and up CGEventPost won't work because of changes in macOS
1005 // to improve security. This code makes an NSEvent corresponding to the
1006 // wheel event and dispatches it directly to the scrollWheel handler. Some
1007 // fiddling is needed with the coordinates in order to simulate what macOS
1008 // would do; this code adapted from the Chromium equivalent function at
1009 // https://chromium.googlesource.com/chromium/src.git/+/62.0.3178.1/ui/events/test/cocoa_test_event_utils.mm#38
1010 CGPoint location = CGEventGetLocation(cgEvent);
1011 location.y += NSMinY([[mView window] frame]);
1012 location.x -= NSMinX([[mView window] frame]);
1013 CGEventSetLocation(cgEvent, location);
1015 uint64_t kNanosPerSec = 1000000000L;
1016 CGEventSetTimestamp(
1017 cgEvent, [[NSProcessInfo processInfo] systemUptime] * kNanosPerSec);
1019 NSEvent* event = [NSEvent eventWithCGEvent:cgEvent];
1020 [event setValue:[mView window] forKey:@"_window"];
1021 [mView scrollWheel:event];
1026 NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
1029 nsresult nsChildView::SynthesizeNativeTouchPoint(
1030 uint32_t aPointerId, TouchPointerState aPointerState,
1031 mozilla::LayoutDeviceIntPoint aPoint, double aPointerPressure,
1032 uint32_t aPointerOrientation, nsIObserver* aObserver) {
1033 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
1035 AutoObserverNotifier notifier(aObserver, "touchpoint");
1037 MOZ_ASSERT(NS_IsMainThread());
1038 if (aPointerState == TOUCH_HOVER) {
1039 return NS_ERROR_UNEXPECTED;
1042 if (!mSynthesizedTouchInput) {
1043 mSynthesizedTouchInput = MakeUnique<MultiTouchInput>();
1046 LayoutDeviceIntPoint pointInWindow = aPoint - WidgetToScreenOffset();
1047 MultiTouchInput inputToDispatch = UpdateSynthesizedTouchState(
1048 mSynthesizedTouchInput.get(), TimeStamp::Now(), aPointerId, aPointerState,
1049 pointInWindow, aPointerPressure, aPointerOrientation);
1050 DispatchTouchInput(inputToDispatch);
1053 NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
1056 nsresult nsChildView::SynthesizeNativeTouchpadDoubleTap(
1057 mozilla::LayoutDeviceIntPoint aPoint, uint32_t aModifierFlags) {
1058 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
1060 DispatchDoubleTapGesture(TimeStamp::Now(), aPoint - WidgetToScreenOffset(),
1061 static_cast<Modifiers>(aModifierFlags));
1065 NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
1068 bool nsChildView::SendEventToNativeMenuSystem(NSEvent* aEvent) {
1069 bool handled = false;
1070 nsCocoaWindow* widget = GetAppWindowWidget();
1072 nsMenuBarX* mb = widget->GetMenuBar();
1074 // Check if main menu wants to handle the event.
1075 handled = mb->PerformKeyEquivalent(aEvent);
1079 if (!handled && sApplicationMenu) {
1080 // Check if application menu wants to handle the event.
1081 handled = [sApplicationMenu performKeyEquivalent:aEvent];
1087 void nsChildView::PostHandleKeyEvent(mozilla::WidgetKeyboardEvent* aEvent) {
1088 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1090 // We always allow keyboard events to propagate to keyDown: but if they are
1091 // not handled we give menu items a chance to act. This allows for handling of
1092 // custom shortcuts. Note that existing shortcuts cannot be reassigned yet and
1093 // will have been handled by keyDown: before we get here.
1094 NSMutableDictionary* nativeKeyEventsMap = [ChildView sNativeKeyEventsMap];
1095 NSEvent* cocoaEvent = [nativeKeyEventsMap objectForKey:@(aEvent->mUniqueId)];
1100 // If the escape key is pressed, the expectations are as follows:
1101 // 1. If the page is loading, interrupt loading.
1102 // 2. Give a website an opportunity to handle the event and call
1103 // preventDefault() on it.
1104 // 3. If the browser is fullscreen and the page isn't loading, exit
1107 // Case 1 and 2 are handled before we get here. Below, we handle case 3.
1108 if (StaticPrefs::browser_fullscreen_exit_on_escape() &&
1109 [cocoaEvent keyCode] == kVK_Escape &&
1110 [[mView window] styleMask] & NSWindowStyleMaskFullScreen) {
1111 [[mView window] toggleFullScreen:nil];
1114 if (SendEventToNativeMenuSystem(cocoaEvent)) {
1115 aEvent->PreventDefault();
1117 [nativeKeyEventsMap removeObjectForKey:@(aEvent->mUniqueId)];
1119 NS_OBJC_END_TRY_IGNORE_BLOCK;
1122 // Used for testing native menu system structure and event handling.
1123 nsresult nsChildView::ActivateNativeMenuItemAt(const nsAString& indexString) {
1124 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
1126 nsMenuUtilsX::CheckNativeMenuConsistency([NSApp mainMenu]);
1128 NSString* locationString =
1129 [NSString stringWithCharacters:reinterpret_cast<const unichar*>(
1130 indexString.BeginReading())
1131 length:indexString.Length()];
1132 NSMenuItem* item = nsMenuUtilsX::NativeMenuItemWithLocation(
1133 [NSApp mainMenu], locationString, true);
1134 // We can't perform an action on an item with a submenu, that will raise
1135 // an obj-c exception.
1136 if (item && ![item hasSubmenu]) {
1137 NSMenu* parent = [item menu];
1139 // NSLog(@"Performing action for native menu item titled: %@\n",
1140 // [[currentSubmenu itemAtIndex:targetIndex] title]);
1141 mozilla::AutoRestore<bool> autoRestore(
1142 nsMenuUtilsX::gIsSynchronouslyActivatingNativeMenuItemDuringTest);
1143 nsMenuUtilsX::gIsSynchronouslyActivatingNativeMenuItemDuringTest = true;
1144 [parent performActionForItemAtIndex:[parent indexOfItem:item]];
1148 return NS_ERROR_FAILURE;
1150 NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
1153 // Used for testing native menu system structure and event handling.
1154 nsresult nsChildView::ForceUpdateNativeMenuAt(const nsAString& indexString) {
1155 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
1157 nsCocoaWindow* widget = GetAppWindowWidget();
1159 nsMenuBarX* mb = widget->GetMenuBar();
1161 if (indexString.IsEmpty())
1162 mb->ForceNativeMenuReload();
1164 mb->ForceUpdateNativeMenuAt(indexString);
1169 NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
1174 #ifdef INVALIDATE_DEBUGGING
1176 static Boolean KeyDown(const UInt8 theKey) {
1179 return ((*((UInt8*)map + (theKey >> 3)) >> (theKey & 7)) & 1) != 0;
1182 static Boolean caps_lock() { return KeyDown(0x39); }
1184 static void blinkRect(Rect* r) {
1185 StRegionFromPool oldClip;
1186 if (oldClip != NULL) ::GetClip(oldClip);
1190 UInt32 end = ::TickCount() + 5;
1191 while (::TickCount() < end)
1195 if (oldClip != NULL) ::SetClip(oldClip);
1198 static void blinkRgn(RgnHandle rgn) {
1199 StRegionFromPool oldClip;
1200 if (oldClip != NULL) ::GetClip(oldClip);
1204 UInt32 end = ::TickCount() + 5;
1205 while (::TickCount() < end)
1209 if (oldClip != NULL) ::SetClip(oldClip);
1214 // Invalidate this component's visible area
1215 void nsChildView::Invalidate(const LayoutDeviceIntRect& aRect) {
1216 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1218 if (!mView || !mVisible) return;
1221 GetWindowRenderer()->GetBackendType() != LayersBackend::LAYERS_WR,
1222 "Shouldn't need to invalidate with accelerated OMTC layers!");
1224 EnsureContentLayerForMainThreadPainting();
1225 mContentLayerInvalidRegion.OrWith(aRect.Intersect(GetBounds()));
1226 [mView markLayerForDisplay];
1228 NS_OBJC_END_TRY_IGNORE_BLOCK;
1231 bool nsChildView::WidgetTypeSupportsAcceleration() {
1232 // All widget types support acceleration.
1236 bool nsChildView::ShouldUseOffMainThreadCompositing() {
1237 // We need to enable OMTC in popups which contain remote layer
1238 // trees, since the remote content won't be rendered at all otherwise.
1239 if (HasRemoteContent()) {
1243 // Don't use OMTC for popup windows, because we do not want context menus to
1244 // pay the overhead of starting up a compositor. With the OpenGL compositor,
1245 // new windows are expensive because of shader re-compilation, and with
1246 // WebRender, new windows are expensive because they create their own threads
1247 // and texture caches.
1248 // Using OMTC with BasicCompositor for context menus would probably be fine
1249 // but isn't a well-tested configuration.
1250 if ([mView window] && [[mView window] isKindOfClass:[PopupWindow class]]) {
1251 // Use main-thread BasicLayerManager for drawing menus.
1255 return nsBaseWidget::ShouldUseOffMainThreadCompositing();
1260 // Invokes callback and ProcessEvent methods on Event Listener object
1261 nsresult nsChildView::DispatchEvent(WidgetGUIEvent* event,
1262 nsEventStatus& aStatus) {
1263 RefPtr<nsChildView> kungFuDeathGrip(this);
1266 debug_DumpEvent(stdout, event->mWidget, event, "something", 0);
1269 if (event->mFlags.mIsSynthesizedForTests) {
1270 WidgetKeyboardEvent* keyEvent = event->AsKeyboardEvent();
1272 nsresult rv = mTextInputHandler->AttachNativeKeyEvent(*keyEvent);
1273 NS_ENSURE_SUCCESS(rv, rv);
1277 aStatus = nsEventStatus_eIgnore;
1279 nsIWidgetListener* listener = mWidgetListener;
1281 // If the listener is NULL, check if the parent is a popup. If it is, then
1282 // this child is the popup content view attached to a popup. Get the
1283 // listener from the parent popup instead.
1284 nsCOMPtr<nsIWidget> parentWidget = mParentWidget;
1285 if (!listener && parentWidget) {
1286 if (parentWidget->GetWindowType() == WindowType::Popup) {
1287 // Check just in case event->mWidget isn't this widget
1288 if (event->mWidget) {
1289 listener = event->mWidget->GetWidgetListener();
1292 event->mWidget = parentWidget;
1293 listener = parentWidget->GetWidgetListener();
1298 if (listener) aStatus = listener->HandleEvent(event, mUseAttachedEvents);
1303 nsIWidget* nsChildView::GetWidgetForListenerEvents() {
1304 // If there is no listener, use the parent popup's listener if that exists.
1305 if (!mWidgetListener && mParentWidget &&
1306 mParentWidget->GetWindowType() == WindowType::Popup) {
1307 return mParentWidget;
1313 void nsChildView::WillPaintWindow() {
1314 nsCOMPtr<nsIWidget> widget = GetWidgetForListenerEvents();
1316 nsIWidgetListener* listener = widget->GetWidgetListener();
1318 listener->WillPaintWindow(widget);
1322 bool nsChildView::PaintWindow(LayoutDeviceIntRegion aRegion) {
1323 nsCOMPtr<nsIWidget> widget = GetWidgetForListenerEvents();
1325 nsIWidgetListener* listener = widget->GetWidgetListener();
1326 if (!listener) return false;
1328 bool returnValue = false;
1329 bool oldDispatchPaint = mIsDispatchPaint;
1330 mIsDispatchPaint = true;
1331 returnValue = listener->PaintWindow(widget, aRegion);
1333 listener = widget->GetWidgetListener();
1335 listener->DidPaintWindow();
1338 mIsDispatchPaint = oldDispatchPaint;
1342 bool nsChildView::PaintWindowInDrawTarget(gfx::DrawTarget* aDT,
1343 const LayoutDeviceIntRegion& aRegion,
1344 const gfx::IntSize& aSurfaceSize) {
1345 if (!aDT || !aDT->IsValid()) {
1348 gfxContext targetContext(aDT);
1350 // Set up the clip region and clear existing contents in the backing surface.
1351 targetContext.NewPath();
1352 for (auto iter = aRegion.RectIter(); !iter.Done(); iter.Next()) {
1353 const LayoutDeviceIntRect& r = iter.Get();
1354 targetContext.Rectangle(gfxRect(r.x, r.y, r.width, r.height));
1355 aDT->ClearRect(gfx::Rect(r.ToUnknownRect()));
1357 targetContext.Clip();
1359 nsAutoRetainCocoaObject kungFuDeathGrip(mView);
1360 if (GetWindowRenderer()->GetBackendType() == LayersBackend::LAYERS_NONE) {
1361 nsBaseWidget::AutoLayerManagerSetup setupLayerManager(
1362 this, &targetContext, BufferMode::BUFFER_NONE);
1363 return PaintWindow(aRegion);
1368 void nsChildView::EnsureContentLayerForMainThreadPainting() {
1369 // Ensure we have an mContentLayer of the correct size.
1370 // The content layer gets created on demand for BasicLayers windows. We do
1371 // not create it during widget creation because, for non-BasicLayers windows,
1372 // the compositing layer manager will create any layers it needs.
1373 gfx::IntSize size = GetBounds().Size().ToUnknownSize();
1374 if (mContentLayer && mContentLayer->GetSize() != size) {
1375 mNativeLayerRoot->RemoveLayer(mContentLayer);
1376 mContentLayer = nullptr;
1378 if (!mContentLayer) {
1379 mPoolHandle = SurfacePool::Create(0)->GetHandleForGL(nullptr);
1380 RefPtr<NativeLayer> contentLayer =
1381 mNativeLayerRoot->CreateLayer(size, false, mPoolHandle);
1382 mNativeLayerRoot->AppendLayer(contentLayer);
1383 mContentLayer = contentLayer->AsNativeLayerCA();
1384 mContentLayer->SetSurfaceIsFlipped(false);
1385 mContentLayerInvalidRegion = GetBounds();
1389 void nsChildView::PaintWindowInContentLayer() {
1390 EnsureContentLayerForMainThreadPainting();
1391 mPoolHandle->OnBeginFrame();
1392 RefPtr<DrawTarget> dt = mContentLayer->NextSurfaceAsDrawTarget(
1393 gfx::IntRect({}, mContentLayer->GetSize()),
1394 mContentLayerInvalidRegion.ToUnknownRegion(), gfx::BackendType::SKIA);
1399 PaintWindowInDrawTarget(dt, mContentLayerInvalidRegion, dt->GetSize());
1400 mContentLayer->NotifySurfaceReady();
1401 mContentLayerInvalidRegion.SetEmpty();
1402 mPoolHandle->OnEndFrame();
1405 void nsChildView::HandleMainThreadCATransaction() {
1408 if (GetWindowRenderer()->GetBackendType() == LayersBackend::LAYERS_NONE) {
1409 // We're in BasicLayers mode, i.e. main thread software compositing.
1410 // Composite the window into our layer's surface.
1411 PaintWindowInContentLayer();
1413 // Trigger a synchronous OMTC composite. This will call NextSurface and
1414 // NotifySurfaceReady on the compositor thread to update mNativeLayerRoot's
1415 // contents, and the main thread (this thread) will wait inside PaintWindow
1416 // during that time.
1417 PaintWindow(LayoutDeviceIntRegion(GetBounds()));
1421 // Apply the changes inside mNativeLayerRoot to the underlying CALayers. Now
1422 // is a good time to call this because we know we're currently inside a main
1423 // thread CATransaction, and the lock makes sure that no composition is
1424 // currently in progress, so we won't present half-composited state to the
1426 MutexAutoLock lock(mCompositingLock);
1427 mNativeLayerRoot->CommitToScreen();
1430 MaybeScheduleUnsuspendAsyncCATransactions();
1435 void nsChildView::ReportMoveEvent() { NotifyWindowMoved(mBounds.x, mBounds.y); }
1437 void nsChildView::ReportSizeEvent() {
1438 if (mWidgetListener)
1439 mWidgetListener->WindowResized(this, mBounds.width, mBounds.height);
1444 LayoutDeviceIntPoint nsChildView::GetClientOffset() {
1445 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
1447 NSPoint origin = [mView convertPoint:NSMakePoint(0, 0) toView:nil];
1448 origin.y = [[mView window] frame].size.height - origin.y;
1449 return CocoaPointsToDevPixels(origin);
1451 NS_OBJC_END_TRY_BLOCK_RETURN(LayoutDeviceIntPoint(0, 0));
1454 // Return the offset between this child view and the screen.
1455 // @return -- widget origin in device-pixel coords
1456 LayoutDeviceIntPoint nsChildView::WidgetToScreenOffset() {
1457 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
1459 NSPoint origin = NSMakePoint(0, 0);
1461 // 1. First translate view origin point into window coords.
1462 // The returned point is in bottom-left coordinates.
1463 origin = [mView convertPoint:origin toView:nil];
1465 // 2. We turn the window-coord rect's origin into screen (still bottom-left)
1467 origin = nsCocoaUtils::ConvertPointToScreen([mView window], origin);
1469 // 3. Since we're dealing in bottom-left coords, we need to make it top-left
1471 // before we pass it back to Gecko.
1472 FlipCocoaScreenCoordinate(origin);
1474 // convert to device pixels
1475 return CocoaPointsToDevPixels(origin);
1477 NS_OBJC_END_TRY_BLOCK_RETURN(LayoutDeviceIntPoint(0, 0));
1480 nsresult nsChildView::SetTitle(const nsAString& title) {
1481 // child views don't have titles
1485 nsresult nsChildView::GetAttention(int32_t aCycleCount) {
1486 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
1488 [NSApp requestUserAttention:NSInformationalRequest];
1491 NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
1495 bool nsChildView::DoHasPendingInputEvent() {
1496 return sLastInputEventCount != GetCurrentInputEventCount();
1500 uint32_t nsChildView::GetCurrentInputEventCount() {
1501 // Can't use kCGAnyInputEventType because that updates too rarely for us (and
1502 // always in increments of 30+!) and because apparently it's sort of broken
1503 // on Tiger. So just go ahead and query the counters we care about.
1504 static const CGEventType eventTypes[] = {kCGEventLeftMouseDown,
1505 kCGEventLeftMouseUp,
1506 kCGEventRightMouseDown,
1507 kCGEventRightMouseUp,
1509 kCGEventLeftMouseDragged,
1510 kCGEventRightMouseDragged,
1513 kCGEventScrollWheel,
1514 kCGEventTabletPointer,
1515 kCGEventOtherMouseDown,
1516 kCGEventOtherMouseUp,
1517 kCGEventOtherMouseDragged};
1519 uint32_t eventCount = 0;
1520 for (uint32_t i = 0; i < ArrayLength(eventTypes); ++i) {
1521 eventCount += CGEventSourceCounterForEventType(
1522 kCGEventSourceStateCombinedSessionState, eventTypes[i]);
1528 void nsChildView::UpdateCurrentInputEventCount() {
1529 sLastInputEventCount = GetCurrentInputEventCount();
1532 bool nsChildView::HasPendingInputEvent() { return DoHasPendingInputEvent(); }
1536 void nsChildView::SetInputContext(const InputContext& aContext,
1537 const InputContextAction& aAction) {
1538 NS_ENSURE_TRUE_VOID(mTextInputHandler);
1540 if (mTextInputHandler->IsFocused()) {
1541 if (aContext.IsPasswordEditor()) {
1542 TextInputHandler::EnableSecureEventInput();
1544 TextInputHandler::EnsureSecureEventInputDisabled();
1548 // IMEInputHandler::IsEditableContent() returns false when both
1549 // IsASCIICableOnly() and IsIMEEnabled() return false. So, be careful
1550 // when you change the following code. You might need to change
1551 // IMEInputHandler::IsEditableContent() too.
1552 mInputContext = aContext;
1553 switch (aContext.mIMEState.mEnabled) {
1554 case IMEEnabled::Enabled:
1555 mTextInputHandler->SetASCIICapableOnly(false);
1556 mTextInputHandler->EnableIME(true);
1557 if (mInputContext.mIMEState.mOpen != IMEState::DONT_CHANGE_OPEN_STATE) {
1558 mTextInputHandler->SetIMEOpenState(mInputContext.mIMEState.mOpen ==
1562 case IMEEnabled::Disabled:
1563 mTextInputHandler->SetASCIICapableOnly(false);
1564 mTextInputHandler->EnableIME(false);
1566 case IMEEnabled::Password:
1567 mTextInputHandler->SetASCIICapableOnly(true);
1568 mTextInputHandler->EnableIME(false);
1571 NS_ERROR("not implemented!");
1575 InputContext nsChildView::GetInputContext() {
1576 switch (mInputContext.mIMEState.mEnabled) {
1577 case IMEEnabled::Enabled:
1578 if (mTextInputHandler) {
1579 mInputContext.mIMEState.mOpen = mTextInputHandler->IsIMEOpened()
1584 // If mTextInputHandler is null, set CLOSED instead...
1587 mInputContext.mIMEState.mOpen = IMEState::CLOSED;
1590 return mInputContext;
1593 TextEventDispatcherListener*
1594 nsChildView::GetNativeTextEventDispatcherListener() {
1595 if (NS_WARN_IF(!mTextInputHandler)) {
1598 return mTextInputHandler;
1601 nsresult nsChildView::AttachNativeKeyEvent(
1602 mozilla::WidgetKeyboardEvent& aEvent) {
1603 NS_ENSURE_TRUE(mTextInputHandler, NS_ERROR_NOT_AVAILABLE);
1604 return mTextInputHandler->AttachNativeKeyEvent(aEvent);
1607 bool nsChildView::GetEditCommands(NativeKeyBindingsType aType,
1608 const WidgetKeyboardEvent& aEvent,
1609 nsTArray<CommandInt>& aCommands) {
1610 // Validate the arguments.
1611 if (NS_WARN_IF(!nsIWidget::GetEditCommands(aType, aEvent, aCommands))) {
1615 Maybe<WritingMode> writingMode;
1616 if (aEvent.NeedsToRemapNavigationKey()) {
1617 if (RefPtr<TextEventDispatcher> dispatcher = GetTextEventDispatcher()) {
1618 writingMode = dispatcher->MaybeQueryWritingModeAtSelection();
1622 NativeKeyBindings* keyBindings = NativeKeyBindings::GetInstance(aType);
1623 keyBindings->GetEditCommands(aEvent, writingMode, aCommands);
1627 NSView<mozView>* nsChildView::GetEditorView() {
1628 NSView<mozView>* editorView = mView;
1629 // We need to get editor's view. E.g., when the focus is in the bookmark
1630 // dialog, the view is <panel> element of the dialog. At this time, the key
1631 // events are processed the parent window's view that has native focus.
1632 WidgetQueryContentEvent queryContentState(true, eQueryContentState, this);
1633 // This may be called during creating a menu popup frame due to creating
1634 // widget synchronously and that causes Cocoa asking current window level.
1635 // In this case, it's not safe to flush layout on the document and we don't
1636 // need any layout information right now.
1637 queryContentState.mNeedsToFlushLayout = false;
1638 DispatchWindowEvent(queryContentState);
1639 if (queryContentState.Succeeded() &&
1640 queryContentState.mReply->mFocusedWidget) {
1641 NSView<mozView>* view = static_cast<NSView<mozView>*>(
1642 queryContentState.mReply->mFocusedWidget->GetNativeData(
1644 if (view) editorView = view;
1651 void nsChildView::CreateCompositor() {
1652 nsBaseWidget::CreateCompositor();
1653 if (mCompositorBridgeChild) {
1654 [mView setUsingOMTCompositor:true];
1658 void nsChildView::ConfigureAPZCTreeManager() {
1659 nsBaseWidget::ConfigureAPZCTreeManager();
1662 void nsChildView::ConfigureAPZControllerThread() {
1663 nsBaseWidget::ConfigureAPZControllerThread();
1666 bool nsChildView::PreRender(WidgetRenderingContext* aContext)
1667 MOZ_NO_THREAD_SAFETY_ANALYSIS {
1668 // The lock makes sure that we don't attempt to tear down the view while
1669 // compositing. That would make us unable to call postRender on it when the
1670 // composition is done, thus keeping the GL context locked forever.
1671 mCompositingLock.Lock();
1673 if (aContext->mGL && gfxPlatform::CanMigrateMacGPUs()) {
1674 GLContextCGL::Cast(aContext->mGL)->MigrateToActiveGPU();
1680 void nsChildView::PostRender(WidgetRenderingContext* aContext)
1681 MOZ_NO_THREAD_SAFETY_ANALYSIS {
1682 mCompositingLock.Unlock();
1685 RefPtr<layers::NativeLayerRoot> nsChildView::GetNativeLayerRoot() {
1686 return mNativeLayerRoot;
1689 static int32_t FindTitlebarBottom(
1690 const nsTArray<nsIWidget::ThemeGeometry>& aThemeGeometries,
1691 int32_t aWindowWidth) {
1692 int32_t titlebarBottom = 0;
1693 for (auto& g : aThemeGeometries) {
1694 if (g.mType == eThemeGeometryTypeTitlebar && g.mRect.X() <= 0 &&
1695 g.mRect.XMost() >= aWindowWidth && g.mRect.Y() <= 0) {
1696 titlebarBottom = std::max(titlebarBottom, g.mRect.YMost());
1699 return titlebarBottom;
1702 static int32_t FindUnifiedToolbarBottom(
1703 const nsTArray<nsIWidget::ThemeGeometry>& aThemeGeometries,
1704 int32_t aWindowWidth, int32_t aTitlebarBottom) {
1705 int32_t unifiedToolbarBottom = aTitlebarBottom;
1706 for (uint32_t i = 0; i < aThemeGeometries.Length(); ++i) {
1707 const nsIWidget::ThemeGeometry& g = aThemeGeometries[i];
1708 if ((g.mType == eThemeGeometryTypeToolbar) && g.mRect.X() <= 0 &&
1709 g.mRect.XMost() >= aWindowWidth && g.mRect.Y() <= aTitlebarBottom) {
1710 unifiedToolbarBottom = std::max(unifiedToolbarBottom, g.mRect.YMost());
1713 return unifiedToolbarBottom;
1716 static LayoutDeviceIntRect FindFirstRectOfType(
1717 const nsTArray<nsIWidget::ThemeGeometry>& aThemeGeometries,
1718 nsITheme::ThemeGeometryType aThemeGeometryType) {
1719 for (uint32_t i = 0; i < aThemeGeometries.Length(); ++i) {
1720 const nsIWidget::ThemeGeometry& g = aThemeGeometries[i];
1721 if (g.mType == aThemeGeometryType) {
1725 return LayoutDeviceIntRect();
1728 void nsChildView::UpdateThemeGeometries(
1729 const nsTArray<ThemeGeometry>& aThemeGeometries) {
1730 if (![mView window]) return;
1732 UpdateVibrancy(aThemeGeometries);
1734 if (![[mView window] isKindOfClass:[ToolbarWindow class]]) return;
1736 // Update unified toolbar height and sheet attachment position.
1737 int32_t windowWidth = mBounds.width;
1738 int32_t titlebarBottom = FindTitlebarBottom(aThemeGeometries, windowWidth);
1739 int32_t unifiedToolbarBottom =
1740 FindUnifiedToolbarBottom(aThemeGeometries, windowWidth, titlebarBottom);
1741 int32_t toolboxBottom =
1742 FindFirstRectOfType(aThemeGeometries, eThemeGeometryTypeToolbox).YMost();
1744 ToolbarWindow* win = (ToolbarWindow*)[mView window];
1745 int32_t titlebarHeight = [win drawsContentsIntoWindowFrame]
1747 : CocoaPointsToDevPixels([win titlebarHeight]);
1748 int32_t devUnifiedHeight = titlebarHeight + unifiedToolbarBottom;
1749 [win setUnifiedToolbarHeight:DevPixelsToCocoaPoints(devUnifiedHeight)];
1751 int32_t sheetPositionDevPx = std::max(toolboxBottom, unifiedToolbarBottom);
1752 NSPoint sheetPositionView = {0, DevPixelsToCocoaPoints(sheetPositionDevPx)};
1753 NSPoint sheetPositionWindow = [mView convertPoint:sheetPositionView
1755 [win setSheetAttachmentPosition:sheetPositionWindow.y];
1757 // Update titlebar control offsets.
1758 LayoutDeviceIntRect windowButtonRect =
1759 FindFirstRectOfType(aThemeGeometries, eThemeGeometryTypeWindowButtons);
1760 [win placeWindowButtons:[mView convertRect:DevPixelsToCocoaPoints(
1765 static Maybe<VibrancyType> ThemeGeometryTypeToVibrancyType(
1766 nsITheme::ThemeGeometryType aThemeGeometryType) {
1767 switch (aThemeGeometryType) {
1768 case eThemeGeometryTypeTooltip:
1769 return Some(VibrancyType::TOOLTIP);
1770 case eThemeGeometryTypeMenu:
1771 return Some(VibrancyType::MENU);
1772 case eThemeGeometryTypeHighlightedMenuItem:
1773 return Some(VibrancyType::HIGHLIGHTED_MENUITEM);
1774 case eThemeGeometryTypeSourceList:
1775 return Some(VibrancyType::SOURCE_LIST);
1776 case eThemeGeometryTypeSourceListSelection:
1777 return Some(VibrancyType::SOURCE_LIST_SELECTION);
1778 case eThemeGeometryTypeActiveSourceListSelection:
1779 return Some(VibrancyType::ACTIVE_SOURCE_LIST_SELECTION);
1785 static LayoutDeviceIntRegion GatherVibrantRegion(
1786 const nsTArray<nsIWidget::ThemeGeometry>& aThemeGeometries,
1787 VibrancyType aVibrancyType) {
1788 LayoutDeviceIntRegion region;
1789 for (auto& geometry : aThemeGeometries) {
1790 if (ThemeGeometryTypeToVibrancyType(geometry.mType) ==
1791 Some(aVibrancyType)) {
1792 region.OrWith(geometry.mRect);
1798 template <typename Region>
1799 static void MakeRegionsNonOverlappingImpl(Region& aOutUnion) {}
1801 template <typename Region, typename... Regions>
1802 static void MakeRegionsNonOverlappingImpl(Region& aOutUnion, Region& aFirst,
1803 Regions&... aRest) {
1804 MakeRegionsNonOverlappingImpl(aOutUnion, aRest...);
1805 aFirst.SubOut(aOutUnion);
1806 aOutUnion.OrWith(aFirst);
1809 // Subtracts parts from regions in such a way that they don't have any overlap.
1810 // Each region in the argument list will have the union of all the regions
1811 // *following* it subtracted from itself. In other words, the arguments are
1812 // sorted low priority to high priority.
1813 template <typename Region, typename... Regions>
1814 static void MakeRegionsNonOverlapping(Region& aFirst, Regions&... aRest) {
1816 MakeRegionsNonOverlappingImpl(unionOfAll, aFirst, aRest...);
1819 void nsChildView::UpdateVibrancy(
1820 const nsTArray<ThemeGeometry>& aThemeGeometries) {
1821 LayoutDeviceIntRegion menuRegion =
1822 GatherVibrantRegion(aThemeGeometries, VibrancyType::MENU);
1823 LayoutDeviceIntRegion tooltipRegion =
1824 GatherVibrantRegion(aThemeGeometries, VibrancyType::TOOLTIP);
1825 LayoutDeviceIntRegion highlightedMenuItemRegion =
1826 GatherVibrantRegion(aThemeGeometries, VibrancyType::HIGHLIGHTED_MENUITEM);
1827 LayoutDeviceIntRegion sourceListRegion =
1828 GatherVibrantRegion(aThemeGeometries, VibrancyType::SOURCE_LIST);
1829 LayoutDeviceIntRegion sourceListSelectionRegion = GatherVibrantRegion(
1830 aThemeGeometries, VibrancyType::SOURCE_LIST_SELECTION);
1831 LayoutDeviceIntRegion activeSourceListSelectionRegion = GatherVibrantRegion(
1832 aThemeGeometries, VibrancyType::ACTIVE_SOURCE_LIST_SELECTION);
1834 MakeRegionsNonOverlapping(
1835 menuRegion, tooltipRegion, highlightedMenuItemRegion, sourceListRegion,
1836 sourceListSelectionRegion, activeSourceListSelectionRegion);
1838 auto& vm = EnsureVibrancyManager();
1839 bool changed = false;
1840 changed |= vm.UpdateVibrantRegion(VibrancyType::MENU, menuRegion);
1841 changed |= vm.UpdateVibrantRegion(VibrancyType::TOOLTIP, tooltipRegion);
1842 changed |= vm.UpdateVibrantRegion(VibrancyType::HIGHLIGHTED_MENUITEM,
1843 highlightedMenuItemRegion);
1845 vm.UpdateVibrantRegion(VibrancyType::SOURCE_LIST, sourceListRegion);
1846 changed |= vm.UpdateVibrantRegion(VibrancyType::SOURCE_LIST_SELECTION,
1847 sourceListSelectionRegion);
1848 changed |= vm.UpdateVibrantRegion(VibrancyType::ACTIVE_SOURCE_LIST_SELECTION,
1849 activeSourceListSelectionRegion);
1852 SuspendAsyncCATransactions();
1856 mozilla::VibrancyManager& nsChildView::EnsureVibrancyManager() {
1857 MOZ_ASSERT(mView, "Only call this once we have a view!");
1858 if (!mVibrancyManager) {
1860 MakeUnique<VibrancyManager>(*this, [mView vibrancyViewsContainer]);
1862 return *mVibrancyManager;
1865 void nsChildView::UpdateBoundsFromView() {
1866 auto oldSize = mBounds.Size();
1867 mBounds = CocoaPointsToDevPixels([mView frame]);
1868 if (mBounds.Size() != oldSize) {
1869 SuspendAsyncCATransactions();
1873 @interface NonDraggableView : NSView
1876 @implementation NonDraggableView
1877 - (BOOL)mouseDownCanMoveWindow {
1880 - (NSView*)hitTest:(NSPoint)aPoint {
1883 - (NSRect)_opaqueRectForWindowMoveWhenInTitlebar {
1884 // In NSWindows that use NSWindowStyleMaskFullSizeContentView, NSViews which
1885 // overlap the titlebar do not disable window dragging in the overlapping
1886 // areas even if they return NO from mouseDownCanMoveWindow. This can have
1887 // unfortunate effects: For example, dragging tabs in a browser window would
1888 // move the window if those tabs are in the titlebar.
1889 // macOS does not seem to offer a documented way to opt-out of the forced
1890 // window dragging in the titlebar.
1891 // Overriding _opaqueRectForWindowMoveWhenInTitlebar is an undocumented way
1892 // of opting out of this behavior. This method was added in 10.11 and is used
1893 // by some NSControl subclasses to prevent window dragging in the titlebar.
1894 // The function which assembles the draggable area of the window calls
1895 // _opaqueRect for the content area and _opaqueRectForWindowMoveWhenInTitlebar
1896 // for the titlebar area, on all visible NSViews. The default implementation
1897 // of _opaqueRect returns [self visibleRect], and the default implementation
1898 // of _opaqueRectForWindowMoveWhenInTitlebar returns NSZeroRect unless it's
1901 // Since this view is constructed and used such that its entire bounds is the
1902 // relevant region, we just return our bounds.
1907 void nsChildView::UpdateWindowDraggingRegion(
1908 const LayoutDeviceIntRegion& aRegion) {
1909 // mView returns YES from mouseDownCanMoveWindow, so we need to put NSViews
1910 // that return NO from mouseDownCanMoveWindow in the places that shouldn't
1911 // be draggable. We can't do it the other way round because returning
1912 // YES from mouseDownCanMoveWindow doesn't have any effect if there's a
1913 // superview that returns NO.
1914 LayoutDeviceIntRegion nonDraggable;
1915 nonDraggable.Sub(LayoutDeviceIntRect(0, 0, mBounds.width, mBounds.height),
1918 __block bool changed = false;
1920 // Suppress calls to setNeedsDisplay during NSView geometry changes.
1921 ManipulateViewWithoutNeedingDisplay(mView, ^() {
1922 changed = mNonDraggableRegion.UpdateRegion(
1923 nonDraggable, *this, [mView nonDraggableViewsContainer], ^() {
1924 return [[NonDraggableView alloc] initWithFrame:NSZeroRect];
1929 // Trigger an update to the window server. This will call
1930 // mouseDownCanMoveWindow.
1931 // Doing this manually is only necessary because we're suppressing
1932 // setNeedsDisplay calls above.
1933 [[mView window] setMovableByWindowBackground:NO];
1934 [[mView window] setMovableByWindowBackground:YES];
1938 nsEventStatus nsChildView::DispatchAPZInputEvent(InputData& aEvent) {
1939 APZEventResult result;
1942 result = mAPZC->InputBridge()->ReceiveInputEvent(aEvent);
1945 if (result.GetStatus() == nsEventStatus_eConsumeNoDefault) {
1946 return result.GetStatus();
1949 if (aEvent.mInputType == PINCHGESTURE_INPUT) {
1950 PinchGestureInput& pinchEvent = aEvent.AsPinchGestureInput();
1951 WidgetWheelEvent wheelEvent = pinchEvent.ToWidgetEvent(this);
1952 ProcessUntransformedAPZEvent(&wheelEvent, result);
1953 } else if (aEvent.mInputType == TAPGESTURE_INPUT) {
1954 TapGestureInput& tapEvent = aEvent.AsTapGestureInput();
1955 WidgetSimpleGestureEvent gestureEvent = tapEvent.ToWidgetEvent(this);
1956 ProcessUntransformedAPZEvent(&gestureEvent, result);
1958 MOZ_ASSERT_UNREACHABLE();
1961 return result.GetStatus();
1964 void nsChildView::DispatchAPZWheelInputEvent(InputData& aEvent) {
1965 if (mSwipeTracker && aEvent.mInputType == PANGESTURE_INPUT) {
1966 // Give the swipe tracker a first pass at the event. If a new pan gesture
1967 // has been started since the beginning of the swipe, the swipe tracker
1968 // will know to ignore the event.
1969 nsEventStatus status =
1970 mSwipeTracker->ProcessEvent(aEvent.AsPanGestureInput());
1971 if (status == nsEventStatus_eConsumeNoDefault) {
1976 WidgetWheelEvent event(true, eWheel, this);
1979 APZEventResult result;
1981 switch (aEvent.mInputType) {
1982 case PANGESTURE_INPUT: {
1983 result = mAPZC->InputBridge()->ReceiveInputEvent(aEvent);
1984 if (result.GetStatus() == nsEventStatus_eConsumeNoDefault) {
1988 event = MayStartSwipeForAPZ(aEvent.AsPanGestureInput(), result);
1991 case SCROLLWHEEL_INPUT: {
1992 // For wheel events on OS X, send it to APZ using the WidgetInputEvent
1993 // variant of ReceiveInputEvent, because the APZInputBridge version of
1994 // that function has special handling (for delta multipliers etc.) that
1995 // we need to run. Using the InputData variant would bypass that and
1996 // go straight to the APZCTreeManager subclass.
1997 event = aEvent.AsScrollWheelInput().ToWidgetEvent(this);
1998 result = mAPZC->InputBridge()->ReceiveInputEvent(event);
1999 if (result.GetStatus() == nsEventStatus_eConsumeNoDefault) {
2005 MOZ_CRASH("unsupported event type");
2008 if (event.mMessage == eWheel &&
2009 (event.mDeltaX != 0 || event.mDeltaY != 0)) {
2010 ProcessUntransformedAPZEvent(&event, result);
2015 nsEventStatus status;
2016 switch (aEvent.mInputType) {
2017 case PANGESTURE_INPUT: {
2018 if (MayStartSwipeForNonAPZ(aEvent.AsPanGestureInput())) {
2021 event = aEvent.AsPanGestureInput().ToWidgetEvent(this);
2024 case SCROLLWHEEL_INPUT: {
2025 event = aEvent.AsScrollWheelInput().ToWidgetEvent(this);
2029 MOZ_CRASH("unexpected event type");
2032 if (event.mMessage == eWheel && (event.mDeltaX != 0 || event.mDeltaY != 0)) {
2033 DispatchEvent(&event, status);
2037 void nsChildView::DispatchDoubleTapGesture(TimeStamp aEventTimeStamp,
2038 LayoutDeviceIntPoint aScreenPosition,
2039 mozilla::Modifiers aModifiers) {
2040 if (StaticPrefs::apz_mac_enable_double_tap_zoom_touchpad_gesture()) {
2041 TapGestureInput event{
2042 TapGestureInput::TAPGESTURE_DOUBLE, aEventTimeStamp,
2043 ViewAs<ScreenPixel>(
2045 PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent),
2048 DispatchAPZInputEvent(event);
2050 // Setup the "double tap" event.
2051 WidgetSimpleGestureEvent geckoEvent(true, eTapGesture, this);
2052 // do what convertCocoaMouseEvent does basically.
2053 geckoEvent.mRefPoint = aScreenPosition;
2054 geckoEvent.mModifiers = aModifiers;
2055 geckoEvent.mTimeStamp = aEventTimeStamp;
2056 geckoEvent.mClickCount = 1;
2059 DispatchWindowEvent(geckoEvent);
2063 void nsChildView::LookUpDictionary(
2064 const nsAString& aText, const nsTArray<mozilla::FontRange>& aFontRangeArray,
2065 const bool aIsVertical, const LayoutDeviceIntPoint& aPoint) {
2066 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
2068 NSMutableAttributedString* attrStr =
2069 nsCocoaUtils::GetNSMutableAttributedString(
2070 aText, aFontRangeArray, aIsVertical, BackingScaleFactor());
2072 nsCocoaUtils::DevPixelsToCocoaPoints(aPoint, BackingScaleFactor());
2073 NSDictionary* attributes = [attrStr attributesAtIndex:0 effectiveRange:nil];
2074 NSFont* font = [attributes objectForKey:NSFontAttributeName];
2077 pt.x -= [font descender];
2079 pt.y += [font ascender];
2083 [mView showDefinitionForAttributedString:attrStr atPoint:pt];
2085 NS_OBJC_END_TRY_IGNORE_BLOCK;
2088 #ifdef ACCESSIBILITY
2089 already_AddRefed<a11y::LocalAccessible> nsChildView::GetDocumentAccessible() {
2090 if (!mozilla::a11y::ShouldA11yBeEnabled()) return nullptr;
2092 // mAccessible might be dead if accessibility was previously disabled and is
2093 // now being enabled again.
2094 if (mAccessible && mAccessible->IsAlive()) {
2095 RefPtr<a11y::LocalAccessible> ret;
2096 CallQueryReferent(mAccessible.get(), static_cast<a11y::LocalAccessible**>(
2097 getter_AddRefs(ret)));
2098 return ret.forget();
2101 // need to fetch the accessible anew, because it has gone away.
2102 // cache the accessible in our weak ptr
2103 RefPtr<a11y::LocalAccessible> acc = GetRootAccessible();
2104 mAccessible = do_GetWeakReference(acc.get());
2106 return acc.forget();
2110 class WidgetsReleaserRunnable final : public mozilla::Runnable {
2112 explicit WidgetsReleaserRunnable(nsTArray<nsCOMPtr<nsIWidget>>&& aWidgetArray)
2113 : mozilla::Runnable("WidgetsReleaserRunnable"),
2114 mWidgetArray(std::move(aWidgetArray)) {}
2116 // Do nothing; all this runnable does is hold a reference the widgets in
2117 // mWidgetArray, and those references will be dropped when this runnable
2121 nsTArray<nsCOMPtr<nsIWidget>> mWidgetArray;
2126 // ViewRegionContainerView is a view class for certain subviews of ChildView
2127 // which contain the NSViews created for ViewRegions (see ViewRegion.h).
2128 // It doesn't do anything interesting, it only acts as a container so that it's
2129 // easier for ChildView to control the z order of its children.
2130 @interface ViewRegionContainerView : NSView {
2134 @implementation ViewRegionContainerView
2136 - (NSView*)hitTest:(NSPoint)aPoint {
2137 return nil; // Be transparent to mouse events.
2141 return [[self superview] isFlipped];
2144 - (BOOL)mouseDownCanMoveWindow {
2145 return [[self superview] mouseDownCanMoveWindow];
2150 @implementation ChildView
2152 // globalDragPboard is non-null during native drag sessions that did not
2153 // originate in our native NSView (it is set in |draggingEntered:|). It is unset
2154 // when the drag session ends for this view, either with the mouse exiting or
2155 // when a drop occurs in this view.
2156 NSPasteboard* globalDragPboard = nil;
2158 // gLastDragView and gLastDragMouseDownEvent are used to communicate information
2159 // to the drag service during drag invocation (starting a drag in from the
2160 // view). gLastDragView is only non-null while a mouse button is pressed, so
2161 // between mouseDown and mouseUp.
2162 NSView* gLastDragView = nil; // [weak]
2163 NSEvent* gLastDragMouseDownEvent = nil; // [strong]
2165 + (void)initialize {
2166 static BOOL initialized = NO;
2169 // Inform the OS about the types of services (from the "Services" menu)
2170 // that we can handle.
2172 [UTIHelper stringFromPboardType:NSPasteboardTypeString],
2173 [UTIHelper stringFromPboardType:NSPasteboardTypeHTML]
2175 [NSApp registerServicesMenuSendTypes:types returnTypes:types];
2180 + (void)registerViewForDraggedTypes:(NSView*)aView {
2182 registerForDraggedTypes:
2185 [UTIHelper stringFromPboardType:NSFilenamesPboardType],
2186 [UTIHelper stringFromPboardType:kMozFileUrlsPboardType],
2187 [UTIHelper stringFromPboardType:NSPasteboardTypeString],
2188 [UTIHelper stringFromPboardType:NSPasteboardTypeHTML],
2190 stringFromPboardType:(NSString*)
2191 kPasteboardTypeFileURLPromise],
2192 [UTIHelper stringFromPboardType:kMozWildcardPboardType],
2193 [UTIHelper stringFromPboardType:kPublicUrlPboardType],
2194 [UTIHelper stringFromPboardType:kPublicUrlNamePboardType],
2195 [UTIHelper stringFromPboardType:kUrlsWithTitlesPboardType],
2199 // initWithFrame:geckoChild:
2200 - (id)initWithFrame:(NSRect)inFrame geckoChild:(nsChildView*)inChild {
2201 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
2203 if ((self = [super initWithFrame:inFrame])) {
2204 mGeckoChild = inChild;
2205 mBlockedLastMouseDown = NO;
2206 mExpectingWheelStop = NO;
2208 mLastMouseDownEvent = nil;
2209 mLastKeyDownEvent = nil;
2210 mClickThroughMouseDownEvent = nil;
2211 mDragService = nullptr;
2213 mGestureState = eGestureState_None;
2214 mCumulativeRotation = 0.0;
2216 mIsUpdatingLayer = NO;
2218 [self setFocusRingType:NSFocusRingTypeNone];
2221 mCancelSwipeAnimation = nil;
2224 mNonDraggableViewsContainer =
2225 [[ViewRegionContainerView alloc] initWithFrame:[self bounds]];
2226 mVibrancyViewsContainer =
2227 [[ViewRegionContainerView alloc] initWithFrame:[self bounds]];
2229 [mNonDraggableViewsContainer
2230 setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
2231 [mVibrancyViewsContainer
2232 setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
2234 [self addSubview:mNonDraggableViewsContainer];
2235 [self addSubview:mVibrancyViewsContainer];
2237 mPixelHostingView = [[PixelHostingView alloc] initWithFrame:[self bounds]];
2239 setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
2241 [self addSubview:mPixelHostingView];
2243 mRootCALayer = [[CALayer layer] retain];
2244 mRootCALayer.position = NSZeroPoint;
2245 mRootCALayer.bounds = NSZeroRect;
2246 mRootCALayer.anchorPoint = NSZeroPoint;
2247 mRootCALayer.contentsGravity = kCAGravityTopLeft;
2248 [[mPixelHostingView layer] addSublayer:mRootCALayer];
2250 mLastPressureStage = 0;
2253 // register for things we'll take from other applications
2254 [ChildView registerViewForDraggedTypes:self];
2258 NS_OBJC_END_TRY_BLOCK_RETURN(nil);
2261 - (NSTextInputContext*)inputContext {
2263 // -[ChildView widgetDestroyed] has been called, but
2264 // -[ChildView delayedTearDown] has not yet completed. Accessing
2265 // [super inputContext] now would uselessly recreate a text input context
2266 // for us, under which -[ChildView validAttributesForMarkedText] would
2267 // be called and the assertion checking for mTextInputHandler would fail.
2268 // We return nil to avoid that.
2271 return [super inputContext];
2274 - (void)installTextInputHandler:(TextInputHandler*)aHandler {
2275 mTextInputHandler = aHandler;
2278 - (void)uninstallTextInputHandler {
2279 mTextInputHandler = nullptr;
2282 - (NSView*)vibrancyViewsContainer {
2283 return mVibrancyViewsContainer;
2286 - (NSView*)nonDraggableViewsContainer {
2287 return mNonDraggableViewsContainer;
2290 - (NSView*)pixelHostingView {
2291 return mPixelHostingView;
2295 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
2297 [mLastMouseDownEvent release];
2298 [mLastKeyDownEvent release];
2299 [mClickThroughMouseDownEvent release];
2300 ChildViewMouseTracker::OnDestroyView(self);
2302 [mVibrancyViewsContainer removeFromSuperview];
2303 [mVibrancyViewsContainer release];
2304 [mNonDraggableViewsContainer removeFromSuperview];
2305 [mNonDraggableViewsContainer release];
2306 [mPixelHostingView removeFromSuperview];
2307 [mPixelHostingView release];
2308 [mRootCALayer release];
2310 if (gLastDragView == self) {
2311 gLastDragView = nil;
2316 NS_OBJC_END_TRY_IGNORE_BLOCK;
2319 - (void)widgetDestroyed {
2320 if (mTextInputHandler) {
2321 mTextInputHandler->OnDestroyWidget(mGeckoChild);
2322 mTextInputHandler = nullptr;
2324 mGeckoChild = nullptr;
2326 // Just in case we're destroyed abruptly and missed the draggingExited
2327 // or performDragOperation message.
2328 NS_IF_RELEASE(mDragService);
2331 // mozView method, return our gecko child view widget. Note this does not
2333 - (nsIWidget*)widget {
2334 return static_cast<nsIWidget*>(mGeckoChild);
2337 - (NSString*)description {
2338 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
2340 return [NSString stringWithFormat:@"ChildView %p, gecko child %p, frame %@",
2342 NSStringFromRect([self frame])];
2344 NS_OBJC_END_TRY_BLOCK_RETURN(nil);
2347 // Make the origin of this view the topLeft corner (gecko origin) rather
2348 // than the bottomLeft corner (standard cocoa origin).
2353 // We accept key and mouse events, so don't keep passing them up the chain.
2354 // Allow this to be a 'focused' widget for event dispatch.
2355 - (BOOL)acceptsFirstResponder {
2359 // Accept mouse down events on background windows
2360 - (BOOL)acceptsFirstMouse:(NSEvent*)aEvent {
2361 if (![[self window] isKindOfClass:[PopupWindow class]]) {
2362 // We rely on this function to tell us that the mousedown was on a
2363 // background window. Inside mouseDown we can't tell whether we were
2364 // inactive because at that point we've already been made active.
2365 // Unfortunately, acceptsFirstMouse is called for PopupWindows even when
2366 // their parent window is active, so ignore this on them for now.
2367 mClickThroughMouseDownEvent = [aEvent retain];
2372 - (BOOL)mouseDownCanMoveWindow {
2373 // Return YES so that parts of this view can be draggable. The non-draggable
2374 // parts will be covered by NSViews that return NO from
2375 // mouseDownCanMoveWindow and thus override draggability from the inside.
2376 // These views are assembled in nsChildView::UpdateWindowDraggingRegion.
2380 - (void)viewDidChangeBackingProperties {
2381 [super viewDidChangeBackingProperties];
2383 // actually, it could be the color space that's changed,
2384 // but we can't tell the difference here except by retrieving
2385 // the backing scale factor and comparing to the old value
2386 mGeckoChild->BackingScaleFactorChanged();
2390 - (BOOL)isCoveringTitlebar {
2391 return [[self window] isKindOfClass:[BaseWindow class]] &&
2392 [(BaseWindow*)[self window] mainChildView] == self &&
2393 [(BaseWindow*)[self window] drawsContentsIntoWindowFrame];
2396 - (void)viewWillStartLiveResize {
2397 nsCocoaWindow* windowWidget =
2398 mGeckoChild ? mGeckoChild->GetAppWindowWidget() : nullptr;
2400 windowWidget->NotifyLiveResizeStarted();
2404 - (void)viewDidEndLiveResize {
2405 // mGeckoChild may legitimately be null here. It should also have been null
2406 // in viewWillStartLiveResize, so there's no problem. However if we run into
2407 // cases where the windowWidget was non-null in viewWillStartLiveResize but
2408 // is null here, that might be problematic because we might get stuck with
2409 // a content process that has the displayport suppressed. If that scenario
2410 // arises (I'm not sure that it does) we will need to handle it gracefully.
2411 nsCocoaWindow* windowWidget =
2412 mGeckoChild ? mGeckoChild->GetAppWindowWidget() : nullptr;
2414 windowWidget->NotifyLiveResizeStopped();
2418 - (void)markLayerForDisplay {
2419 MOZ_RELEASE_ASSERT(NS_IsMainThread());
2420 if (!mIsUpdatingLayer) {
2421 // This call will cause updateRootCALayer to be called during the upcoming
2422 // main thread CoreAnimation transaction. It will also trigger a transaction
2423 // if no transaction is currently pending.
2424 [[mPixelHostingView layer] setNeedsDisplay];
2428 - (void)ensureNextCompositeIsAtomicWithMainThreadPaint {
2429 MOZ_RELEASE_ASSERT(NS_IsMainThread());
2431 mGeckoChild->SuspendAsyncCATransactions();
2435 - (void)updateRootCALayer {
2436 if (NS_IsMainThread() && mGeckoChild) {
2437 MOZ_RELEASE_ASSERT(!mIsUpdatingLayer, "Re-entrant layer display?");
2438 mIsUpdatingLayer = YES;
2439 mGeckoChild->HandleMainThreadCATransaction();
2440 mIsUpdatingLayer = NO;
2444 - (CALayer*)rootCALayer {
2445 return mRootCALayer;
2448 // If we've just created a non-native context menu, we need to mark it as
2449 // such and let the OS (and other programs) know when it opens and closes
2450 // (this is how the OS knows to close other programs' context menus when
2451 // ours open). We send the initial notification here, but others are sent
2452 // in nsCocoaWindow::Show().
2453 - (void)maybeInitContextMenuTracking {
2454 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
2456 if (mozilla::widget::NativeMenuSupport::ShouldUseNativeContextMenus()) {
2460 nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener();
2461 NS_ENSURE_TRUE_VOID(rollupListener);
2462 nsCOMPtr<nsIWidget> widget = rollupListener->GetRollupWidget();
2463 NS_ENSURE_TRUE_VOID(widget);
2465 NSWindow* popupWindow = (NSWindow*)widget->GetNativeData(NS_NATIVE_WINDOW);
2466 if (!popupWindow || ![popupWindow isKindOfClass:[PopupWindow class]]) return;
2468 [[NSDistributedNotificationCenter defaultCenter]
2469 postNotificationName:@"com.apple.HIToolbox.beginMenuTrackingNotification"
2470 object:@"org.mozilla.gecko.PopupWindow"];
2471 [(PopupWindow*)popupWindow setIsContextMenu:YES];
2473 NS_OBJC_END_TRY_IGNORE_BLOCK;
2476 // Returns true if the event should no longer be processed, false otherwise.
2477 // This does not return whether or not anything was rolled up.
2478 - (BOOL)maybeRollup:(NSEvent*)theEvent {
2479 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
2481 BOOL consumeEvent = NO;
2483 nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener();
2484 NS_ENSURE_TRUE(rollupListener, false);
2486 BOOL isWheelTypeEvent = [theEvent type] == NSEventTypeScrollWheel ||
2487 [theEvent type] == NSEventTypeMagnify ||
2488 [theEvent type] == NSEventTypeSmartMagnify;
2490 if (!isWheelTypeEvent && rollupListener->RollupNativeMenu()) {
2491 // A native menu was rolled up.
2492 // Don't consume this event; if the menu wanted to consume this event it
2493 // would already have done so and we wouldn't even get here. For example, we
2494 // won't get here for left clicks that close native menus (because the
2495 // native menu consumes it), but we will get here for right clicks that
2496 // close native menus, and we do not want to consume those right clicks.
2500 nsCOMPtr<nsIWidget> rollupWidget = rollupListener->GetRollupWidget();
2501 if (!rollupWidget) {
2502 return consumeEvent;
2505 NSWindow* currentPopup =
2506 static_cast<NSWindow*>(rollupWidget->GetNativeData(NS_NATIVE_WINDOW));
2507 if (nsCocoaUtils::IsEventOverWindow(theEvent, currentPopup)) {
2508 return consumeEvent;
2511 // Check to see if scroll/zoom events should roll up the popup
2512 if (isWheelTypeEvent) {
2513 // consume scroll events that aren't over the popup unless the popup is an
2515 consumeEvent = rollupListener->ShouldConsumeOnMouseWheelEvent();
2516 if (!rollupListener->ShouldRollupOnMouseWheelEvent()) {
2517 return consumeEvent;
2521 // if we're dealing with menus, we probably have submenus and
2522 // we don't want to rollup if the click is in a parent menu of
2523 // the current submenu
2524 uint32_t popupsToRollup = UINT32_MAX;
2525 AutoTArray<nsIWidget*, 5> widgetChain;
2526 uint32_t sameTypeCount = rollupListener->GetSubmenuWidgetChain(&widgetChain);
2527 for (uint32_t i = 0; i < widgetChain.Length(); i++) {
2528 nsIWidget* widget = widgetChain[i];
2529 NSWindow* currWindow = (NSWindow*)widget->GetNativeData(NS_NATIVE_WINDOW);
2530 if (nsCocoaUtils::IsEventOverWindow(theEvent, currWindow)) {
2531 // don't roll up if the mouse event occurred within a menu of the
2532 // same type. If the mouse event occurred in a menu higher than
2533 // that, roll up, but pass the number of popups to Rollup so
2534 // that only those of the same type close up.
2535 if (i < sameTypeCount) {
2536 return consumeEvent;
2538 popupsToRollup = sameTypeCount;
2543 LayoutDeviceIntPoint devPoint;
2544 nsIRollupListener::RollupOptions rollupOptions{
2545 popupsToRollup, nsIRollupListener::FlushViews::Yes};
2546 if ([theEvent type] == NSEventTypeLeftMouseDown) {
2547 NSPoint point = [NSEvent mouseLocation];
2548 FlipCocoaScreenCoordinate(point);
2549 devPoint = mGeckoChild->CocoaPointsToDevPixels(point);
2550 rollupOptions.mPoint = &devPoint;
2552 consumeEvent = (BOOL)rollupListener->Rollup(rollupOptions);
2553 return consumeEvent;
2555 NS_OBJC_END_TRY_BLOCK_RETURN(NO);
2558 - (void)swipeWithEvent:(NSEvent*)anEvent {
2559 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
2561 if (!anEvent || !mGeckoChild) {
2565 nsAutoRetainCocoaObject kungFuDeathGrip(self);
2567 float deltaX = [anEvent deltaX]; // left=1.0, right=-1.0
2568 float deltaY = [anEvent deltaY]; // up=1.0, down=-1.0
2570 // Setup the "swipe" event.
2571 WidgetSimpleGestureEvent geckoEvent(true, eSwipeGesture, mGeckoChild);
2572 [self convertCocoaMouseEvent:anEvent toGeckoEvent:&geckoEvent];
2574 // Record the left/right direction.
2576 geckoEvent.mDirection |= dom::SimpleGestureEvent_Binding::DIRECTION_LEFT;
2577 else if (deltaX < 0.0)
2578 geckoEvent.mDirection |= dom::SimpleGestureEvent_Binding::DIRECTION_RIGHT;
2580 // Record the up/down direction.
2582 geckoEvent.mDirection |= dom::SimpleGestureEvent_Binding::DIRECTION_UP;
2583 else if (deltaY < 0.0)
2584 geckoEvent.mDirection |= dom::SimpleGestureEvent_Binding::DIRECTION_DOWN;
2587 mGeckoChild->DispatchWindowEvent(geckoEvent);
2589 NS_OBJC_END_TRY_IGNORE_BLOCK;
2592 // Pinch zoom gesture.
2593 - (void)magnifyWithEvent:(NSEvent*)anEvent {
2594 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
2596 if ([self maybeRollup:anEvent]) {
2604 // Instead of calling beginOrEndGestureForEventPhase we basically inline
2605 // the effects of it here, because that function doesn't play too well with
2606 // how we create PinchGestureInput events below. The main point of that
2607 // function is to avoid flip-flopping between rotation/magnify gestures, which
2608 // we can do by checking and setting mGestureState appropriately. A secondary
2609 // result of that function is to send the final eMagnifyGesture event when
2610 // the gesture ends, but APZ takes care of that for us.
2611 if (mGestureState == eGestureState_RotateGesture &&
2612 [anEvent phase] != NSEventPhaseBegan) {
2613 // If we're already in a rotation and not "starting" a magnify, abort.
2616 mGestureState = eGestureState_MagnifyGesture;
2618 NSPoint locationInWindow =
2619 nsCocoaUtils::EventLocationForWindow(anEvent, [self window]);
2620 ScreenPoint position = ViewAs<ScreenPixel>(
2621 [self convertWindowCoordinatesRoundDown:locationInWindow],
2622 PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent);
2623 ExternalPoint screenOffset = ViewAs<ExternalPixel>(
2624 mGeckoChild->WidgetToScreenOffset(),
2625 PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent);
2627 TimeStamp eventTimeStamp =
2628 nsCocoaUtils::GetEventTimeStamp([anEvent timestamp]);
2629 NSEventPhase eventPhase = [anEvent phase];
2630 PinchGestureInput::PinchGestureType pinchGestureType;
2632 switch (eventPhase) {
2633 case NSEventPhaseBegan: {
2634 pinchGestureType = PinchGestureInput::PINCHGESTURE_START;
2637 case NSEventPhaseChanged: {
2638 pinchGestureType = PinchGestureInput::PINCHGESTURE_SCALE;
2641 case NSEventPhaseEnded: {
2642 pinchGestureType = PinchGestureInput::PINCHGESTURE_END;
2643 mGestureState = eGestureState_None;
2647 NS_WARNING("Unexpected phase for pinch gesture event.");
2652 PinchGestureInput event{pinchGestureType,
2653 PinchGestureInput::TRACKPAD,
2658 100.0 * (1.0 - [anEvent magnification]),
2659 nsCocoaUtils::ModifiersForEvent(anEvent)};
2661 mGeckoChild->DispatchAPZInputEvent(event);
2663 NS_OBJC_END_TRY_IGNORE_BLOCK;
2666 // Smart zoom gesture, i.e. two-finger double tap on trackpads.
2667 - (void)smartMagnifyWithEvent:(NSEvent*)anEvent {
2668 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
2670 if (!anEvent || !mGeckoChild ||
2671 [self beginOrEndGestureForEventPhase:anEvent]) {
2675 if ([self maybeRollup:anEvent]) {
2679 nsAutoRetainCocoaObject kungFuDeathGrip(self);
2681 if (StaticPrefs::apz_mac_enable_double_tap_zoom_touchpad_gesture()) {
2682 TimeStamp eventTimeStamp =
2683 nsCocoaUtils::GetEventTimeStamp([anEvent timestamp]);
2684 NSPoint locationInWindow =
2685 nsCocoaUtils::EventLocationForWindow(anEvent, [self window]);
2686 LayoutDevicePoint position =
2687 [self convertWindowCoordinatesRoundDown:locationInWindow];
2689 mGeckoChild->DispatchDoubleTapGesture(
2690 eventTimeStamp, RoundedToInt(position),
2691 nsCocoaUtils::ModifiersForEvent(anEvent));
2693 // Setup the "double tap" event.
2694 WidgetSimpleGestureEvent geckoEvent(true, eTapGesture, mGeckoChild);
2695 [self convertCocoaMouseEvent:anEvent toGeckoEvent:&geckoEvent];
2696 geckoEvent.mClickCount = 1;
2699 mGeckoChild->DispatchWindowEvent(geckoEvent);
2702 // Clear the gesture state
2703 mGestureState = eGestureState_None;
2705 NS_OBJC_END_TRY_IGNORE_BLOCK;
2708 - (void)rotateWithEvent:(NSEvent*)anEvent {
2709 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
2711 if (!anEvent || !mGeckoChild ||
2712 [self beginOrEndGestureForEventPhase:anEvent]) {
2716 nsAutoRetainCocoaObject kungFuDeathGrip(self);
2718 float rotation = [anEvent rotation];
2721 switch (mGestureState) {
2722 case eGestureState_StartGesture:
2723 msg = eRotateGestureStart;
2724 mGestureState = eGestureState_RotateGesture;
2727 case eGestureState_RotateGesture:
2728 msg = eRotateGestureUpdate;
2731 case eGestureState_None:
2732 case eGestureState_MagnifyGesture:
2738 WidgetSimpleGestureEvent geckoEvent(true, msg, mGeckoChild);
2739 [self convertCocoaMouseEvent:anEvent toGeckoEvent:&geckoEvent];
2740 geckoEvent.mDelta = -rotation;
2741 if (rotation > 0.0) {
2742 geckoEvent.mDirection =
2743 dom::SimpleGestureEvent_Binding::ROTATION_COUNTERCLOCKWISE;
2745 geckoEvent.mDirection = dom::SimpleGestureEvent_Binding::ROTATION_CLOCKWISE;
2749 mGeckoChild->DispatchWindowEvent(geckoEvent);
2751 // Keep track of the cumulative rotation for the final "rotate" event.
2752 mCumulativeRotation += rotation;
2754 NS_OBJC_END_TRY_IGNORE_BLOCK;
2757 // `beginGestureWithEvent` and `endGestureWithEvent` are not called for
2758 // applications that link against the macOS 10.11 or later SDK when we're
2759 // running on macOS 10.11 or later. For compatibility with all supported macOS
2760 // versions, we have to call {begin,end}GestureWithEvent ourselves based on
2761 // the event phase when we're handling gestures.
2762 - (bool)beginOrEndGestureForEventPhase:(NSEvent*)aEvent {
2767 if (aEvent.phase == NSEventPhaseBegan) {
2768 [self beginGestureWithEvent:aEvent];
2772 if (aEvent.phase == NSEventPhaseEnded ||
2773 aEvent.phase == NSEventPhaseCancelled) {
2774 [self endGestureWithEvent:aEvent];
2781 - (void)beginGestureWithEvent:(NSEvent*)aEvent {
2786 mGestureState = eGestureState_StartGesture;
2787 mCumulativeRotation = 0.0;
2790 - (void)endGestureWithEvent:(NSEvent*)anEvent {
2791 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
2793 if (!anEvent || !mGeckoChild) {
2794 // Clear the gestures state if we cannot send an event.
2795 mGestureState = eGestureState_None;
2796 mCumulativeRotation = 0.0;
2800 nsAutoRetainCocoaObject kungFuDeathGrip(self);
2802 switch (mGestureState) {
2803 case eGestureState_RotateGesture: {
2804 // Setup the "rotate" event.
2805 WidgetSimpleGestureEvent geckoEvent(true, eRotateGesture, mGeckoChild);
2806 [self convertCocoaMouseEvent:anEvent toGeckoEvent:&geckoEvent];
2807 geckoEvent.mDelta = -mCumulativeRotation;
2808 if (mCumulativeRotation > 0.0) {
2809 geckoEvent.mDirection =
2810 dom::SimpleGestureEvent_Binding::ROTATION_COUNTERCLOCKWISE;
2812 geckoEvent.mDirection =
2813 dom::SimpleGestureEvent_Binding::ROTATION_CLOCKWISE;
2817 mGeckoChild->DispatchWindowEvent(geckoEvent);
2820 case eGestureState_MagnifyGesture: // APZ handles sending the widget events
2821 case eGestureState_None:
2822 case eGestureState_StartGesture:
2827 // Clear the gestures state.
2828 mGestureState = eGestureState_None;
2829 mCumulativeRotation = 0.0;
2831 NS_OBJC_END_TRY_IGNORE_BLOCK;
2834 - (void)setUsingOMTCompositor:(BOOL)aUseOMTC {
2835 mUsingOMTCompositor = aUseOMTC;
2838 // Returning NO from this method only disallows ordering on mousedown - in order
2839 // to prevent it for mouseup too, we need to call [NSApp preventWindowOrdering]
2840 // when handling the mousedown event.
2841 - (BOOL)shouldDelayWindowOrderingForEvent:(NSEvent*)aEvent {
2842 // Always using system-provided window ordering for normal windows.
2843 if (![[self window] isKindOfClass:[PopupWindow class]]) return NO;
2845 // Don't reorder when we don't have a parent window, like when we're a
2846 // context menu or a tooltip.
2847 return ![[self window] parentWindow];
2850 - (void)mouseDown:(NSEvent*)theEvent {
2851 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
2852 mPerformedDrag = NO;
2854 if ([self shouldDelayWindowOrderingForEvent:theEvent]) {
2855 [NSApp preventWindowOrdering];
2858 // If we've already seen this event due to direct dispatch from menuForEvent:
2859 // just bail; if not, remember it.
2860 if (mLastMouseDownEvent == theEvent) {
2861 [mLastMouseDownEvent release];
2862 mLastMouseDownEvent = nil;
2865 [mLastMouseDownEvent release];
2866 mLastMouseDownEvent = [theEvent retain];
2869 [gLastDragMouseDownEvent release];
2870 gLastDragMouseDownEvent = [theEvent retain];
2871 gLastDragView = self;
2873 // We need isClickThrough because at this point the window we're in might
2874 // already have become main, so the check for isMainWindow in
2875 // WindowAcceptsEvent isn't enough. It also has to check isClickThrough.
2876 BOOL isClickThrough = (theEvent == mClickThroughMouseDownEvent);
2877 [mClickThroughMouseDownEvent release];
2878 mClickThroughMouseDownEvent = nil;
2880 nsAutoRetainCocoaObject kungFuDeathGrip(self);
2882 if ([self maybeRollup:theEvent] ||
2883 !ChildViewMouseTracker::WindowAcceptsEvent([self window], theEvent, self,
2885 // Remember blocking because that means we want to block mouseup as well.
2886 mBlockedLastMouseDown = YES;
2890 // in order to send gecko events we'll need a gecko widget
2891 if (!mGeckoChild) return;
2892 if (mTextInputHandler->OnHandleEvent(theEvent)) {
2896 WidgetMouseEvent geckoEvent(true, eMouseDown, mGeckoChild,
2897 WidgetMouseEvent::eReal);
2898 [self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent];
2900 NSInteger clickCount = [theEvent clickCount];
2901 if (mBlockedLastMouseDown && clickCount > 1) {
2902 // Don't send a double click if the first click of the double click was
2906 geckoEvent.mClickCount = clickCount;
2908 if (!StaticPrefs::dom_event_treat_ctrl_click_as_right_click_disabled() &&
2909 geckoEvent.IsControl()) {
2910 geckoEvent.mButton = MouseButton::eSecondary;
2912 geckoEvent.mButton = MouseButton::ePrimary;
2913 // Don't send a click if ctrl key is pressed.
2914 geckoEvent.mClickEventPrevented = geckoEvent.IsControl();
2917 mGeckoChild->DispatchInputEvent(&geckoEvent);
2918 mBlockedLastMouseDown = NO;
2920 // XXX maybe call markedTextSelectionChanged:client: here?
2922 NS_OBJC_END_TRY_IGNORE_BLOCK;
2925 - (void)mouseUp:(NSEvent*)theEvent {
2926 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
2928 gLastDragView = nil;
2930 if (!mGeckoChild || mBlockedLastMouseDown || mPerformedDrag) {
2931 // There is case that mouseUp event will be fired right after DnD on OSX. As
2932 // mPerformedDrag will be YES at end of DnD processing, ignore this mouseUp
2933 // event fired right after DnD.
2937 if (mTextInputHandler->OnHandleEvent(theEvent)) {
2941 nsAutoRetainCocoaObject kungFuDeathGrip(self);
2943 WidgetMouseEvent geckoEvent(true, eMouseUp, mGeckoChild,
2944 WidgetMouseEvent::eReal);
2945 [self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent];
2947 if (!StaticPrefs::dom_event_treat_ctrl_click_as_right_click_disabled() &&
2948 ([theEvent modifierFlags] & NSEventModifierFlagControl)) {
2949 geckoEvent.mButton = MouseButton::eSecondary;
2951 geckoEvent.mButton = MouseButton::ePrimary;
2954 // Remember the event's position before calling DispatchInputEvent, because
2955 // that call can mutate it and convert it into a different coordinate space.
2956 LayoutDeviceIntPoint pos = geckoEvent.mRefPoint;
2958 // This might destroy our widget (and null out mGeckoChild).
2959 bool defaultPrevented =
2960 (mGeckoChild->DispatchInputEvent(&geckoEvent).mContentStatus ==
2961 nsEventStatus_eConsumeNoDefault);
2967 // Check to see if we are double-clicking in draggable parts of the window.
2968 if (!defaultPrevented && [theEvent clickCount] == 2 &&
2969 !mGeckoChild->GetNonDraggableRegion().Contains(pos.x, pos.y)) {
2970 if (nsCocoaUtils::ShouldZoomOnTitlebarDoubleClick()) {
2971 [[self window] performZoom:nil];
2972 } else if (nsCocoaUtils::ShouldMinimizeOnTitlebarDoubleClick()) {
2973 [[self window] performMiniaturize:nil];
2977 NS_OBJC_END_TRY_IGNORE_BLOCK;
2980 - (void)sendMouseEnterOrExitEvent:(NSEvent*)aEvent
2982 exitFrom:(WidgetMouseEvent::ExitFrom)aExitFrom {
2983 if (!mGeckoChild) return;
2985 NSPoint windowEventLocation =
2986 nsCocoaUtils::EventLocationForWindow(aEvent, [self window]);
2987 NSPoint localEventLocation = [self convertPoint:windowEventLocation
2990 EventMessage msg = aEnter ? eMouseEnterIntoWidget : eMouseExitFromWidget;
2991 WidgetMouseEvent event(true, msg, mGeckoChild, WidgetMouseEvent::eReal);
2992 event.mRefPoint = mGeckoChild->CocoaPointsToDevPixels(localEventLocation);
2993 if (event.mMessage == eMouseExitFromWidget) {
2994 event.mExitFrom = Some(aExitFrom);
2996 nsEventStatus status; // ignored
2997 mGeckoChild->DispatchEvent(&event, status);
3000 - (void)handleMouseMoved:(NSEvent*)theEvent {
3001 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
3003 if (!mGeckoChild) return;
3004 if (mTextInputHandler->OnHandleEvent(theEvent)) {
3008 WidgetMouseEvent geckoEvent(true, eMouseMove, mGeckoChild,
3009 WidgetMouseEvent::eReal);
3010 [self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent];
3012 mGeckoChild->DispatchInputEvent(&geckoEvent);
3014 NS_OBJC_END_TRY_IGNORE_BLOCK;
3017 - (void)mouseDragged:(NSEvent*)theEvent {
3018 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
3020 if (!mGeckoChild) return;
3021 if (mTextInputHandler->OnHandleEvent(theEvent)) {
3025 WidgetMouseEvent geckoEvent(true, eMouseMove, mGeckoChild,
3026 WidgetMouseEvent::eReal);
3027 [self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent];
3029 mGeckoChild->DispatchInputEvent(&geckoEvent);
3031 // Note, sending the above event might have destroyed our widget since we
3032 // didn't retain. Fine so long as we don't access any local variables from
3035 // XXX maybe call markedTextSelectionChanged:client: here?
3037 NS_OBJC_END_TRY_IGNORE_BLOCK;
3040 - (void)rightMouseDown:(NSEvent*)theEvent {
3041 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
3042 mPerformedDrag = NO;
3044 nsAutoRetainCocoaObject kungFuDeathGrip(self);
3046 [self maybeRollup:theEvent];
3047 if (!mGeckoChild) return;
3048 if (mTextInputHandler->OnHandleEvent(theEvent)) {
3052 // The right mouse went down, fire off a right mouse down event to gecko
3053 WidgetMouseEvent geckoEvent(true, eMouseDown, mGeckoChild,
3054 WidgetMouseEvent::eReal);
3055 [self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent];
3056 geckoEvent.mButton = MouseButton::eSecondary;
3057 geckoEvent.mClickCount = [theEvent clickCount];
3059 nsIWidget::ContentAndAPZEventStatus eventStatus =
3060 mGeckoChild->DispatchInputEvent(&geckoEvent);
3061 if (!mGeckoChild) return;
3063 if (!StaticPrefs::ui_context_menus_after_mouseup() &&
3064 eventStatus.mApzStatus != nsEventStatus_eConsumeNoDefault) {
3065 // Let the superclass do the context menu stuff.
3066 [super rightMouseDown:theEvent];
3069 NS_OBJC_END_TRY_IGNORE_BLOCK;
3072 - (void)rightMouseUp:(NSEvent*)theEvent {
3073 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
3075 if (!mGeckoChild) return;
3076 if (mTextInputHandler->OnHandleEvent(theEvent)) {
3080 WidgetMouseEvent geckoEvent(true, eMouseUp, mGeckoChild,
3081 WidgetMouseEvent::eReal);
3082 [self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent];
3083 geckoEvent.mButton = MouseButton::eSecondary;
3084 geckoEvent.mClickCount = [theEvent clickCount];
3086 nsAutoRetainCocoaObject kungFuDeathGrip(self);
3087 nsIWidget::ContentAndAPZEventStatus eventStatus =
3088 mGeckoChild->DispatchInputEvent(&geckoEvent);
3089 if (!mGeckoChild) return;
3091 if (StaticPrefs::ui_context_menus_after_mouseup() &&
3092 eventStatus.mApzStatus != nsEventStatus_eConsumeNoDefault) {
3093 // Let the superclass do the context menu stuff, but pretend it's
3095 NSEvent* dupeEvent = [NSEvent mouseEventWithType:NSEventTypeRightMouseDown
3096 location:theEvent.locationInWindow
3097 modifierFlags:theEvent.modifierFlags
3098 timestamp:theEvent.timestamp
3099 windowNumber:theEvent.windowNumber
3101 eventNumber:theEvent.eventNumber
3102 clickCount:theEvent.clickCount
3103 pressure:theEvent.pressure];
3105 [super rightMouseDown:dupeEvent];
3108 NS_OBJC_END_TRY_IGNORE_BLOCK;
3111 - (void)rightMouseDragged:(NSEvent*)theEvent {
3112 if (!mGeckoChild) return;
3113 if (mTextInputHandler->OnHandleEvent(theEvent)) {
3117 WidgetMouseEvent geckoEvent(true, eMouseMove, mGeckoChild,
3118 WidgetMouseEvent::eReal);
3119 [self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent];
3120 geckoEvent.mButton = MouseButton::eSecondary;
3122 // send event into Gecko by going directly to the
3124 mGeckoChild->DispatchInputEvent(&geckoEvent);
3127 static bool ShouldDispatchBackForwardCommandForMouseButton(int16_t aButton) {
3128 return (aButton == MouseButton::eX1 &&
3129 Preferences::GetBool("mousebutton.4th.enabled", true)) ||
3130 (aButton == MouseButton::eX2 &&
3131 Preferences::GetBool("mousebutton.5th.enabled", true));
3134 - (void)otherMouseDown:(NSEvent*)theEvent {
3135 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
3136 mPerformedDrag = NO;
3138 nsAutoRetainCocoaObject kungFuDeathGrip(self);
3140 if ([self maybeRollup:theEvent] ||
3141 !ChildViewMouseTracker::WindowAcceptsEvent([self window], theEvent, self))
3144 if (!mGeckoChild) return;
3145 if (mTextInputHandler->OnHandleEvent(theEvent)) {
3149 int16_t button = nsCocoaUtils::ButtonForEvent(theEvent);
3150 if (ShouldDispatchBackForwardCommandForMouseButton(button)) {
3151 WidgetCommandEvent appCommandEvent(
3153 (button == MouseButton::eX2) ? nsGkAtoms::Forward : nsGkAtoms::Back,
3155 mGeckoChild->DispatchWindowEvent(appCommandEvent);
3159 WidgetMouseEvent geckoEvent(true, eMouseDown, mGeckoChild,
3160 WidgetMouseEvent::eReal);
3161 [self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent];
3162 geckoEvent.mButton = button;
3163 geckoEvent.mClickCount = [theEvent clickCount];
3165 mGeckoChild->DispatchInputEvent(&geckoEvent);
3167 NS_OBJC_END_TRY_IGNORE_BLOCK;
3170 - (void)otherMouseUp:(NSEvent*)theEvent {
3171 if (!mGeckoChild) return;
3172 if (mTextInputHandler->OnHandleEvent(theEvent)) {
3176 int16_t button = nsCocoaUtils::ButtonForEvent(theEvent);
3177 if (ShouldDispatchBackForwardCommandForMouseButton(button)) {
3181 WidgetMouseEvent geckoEvent(true, eMouseUp, mGeckoChild,
3182 WidgetMouseEvent::eReal);
3183 [self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent];
3184 geckoEvent.mButton = button;
3186 nsAutoRetainCocoaObject kungFuDeathGrip(self);
3187 mGeckoChild->DispatchInputEvent(&geckoEvent);
3190 - (void)otherMouseDragged:(NSEvent*)theEvent {
3191 if (!mGeckoChild) return;
3192 if (mTextInputHandler->OnHandleEvent(theEvent)) {
3196 WidgetMouseEvent geckoEvent(true, eMouseMove, mGeckoChild,
3197 WidgetMouseEvent::eReal);
3198 [self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent];
3199 int16_t button = nsCocoaUtils::ButtonForEvent(theEvent);
3200 geckoEvent.mButton = button;
3202 // send event into Gecko by going directly to the
3204 mGeckoChild->DispatchInputEvent(&geckoEvent);
3207 - (void)sendWheelStartOrStop:(EventMessage)msg forEvent:(NSEvent*)theEvent {
3208 WidgetWheelEvent wheelEvent(true, msg, mGeckoChild);
3209 [self convertCocoaMouseWheelEvent:theEvent toGeckoEvent:&wheelEvent];
3210 mExpectingWheelStop = (msg == eWheelOperationStart);
3211 mGeckoChild->DispatchInputEvent(wheelEvent.AsInputEvent());
3214 - (void)sendWheelCondition:(BOOL)condition
3215 first:(EventMessage)first
3216 second:(EventMessage)second
3217 forEvent:(NSEvent*)theEvent {
3218 if (mExpectingWheelStop == condition) {
3219 [self sendWheelStartOrStop:first forEvent:theEvent];
3221 [self sendWheelStartOrStop:second forEvent:theEvent];
3224 static int32_t RoundUp(double aDouble) {
3225 return aDouble < 0 ? static_cast<int32_t>(floor(aDouble))
3226 : static_cast<int32_t>(ceil(aDouble));
3229 static gfx::IntPoint GetIntegerDeltaForEvent(NSEvent* aEvent) {
3230 if ([aEvent hasPreciseScrollingDeltas]) {
3231 // Pixel scroll events (events with hasPreciseScrollingDeltas == YES)
3232 // carry pixel deltas in the scrollingDeltaX/Y fields and line scroll
3233 // information in the deltaX/Y fields.
3234 // Prior to 10.12, these line scroll fields would be zero for most pixel
3235 // scroll events and non-zero for some, whenever at least a full line
3236 // worth of pixel scrolling had accumulated. That's the behavior we want.
3237 // Starting with 10.12 however, pixel scroll events no longer accumulate
3238 // deltaX and deltaY; they just report floating point values for every
3239 // single event. So we need to do our own accumulation.
3240 return PanGestureInput::GetIntegerDeltaForEvent(
3241 [aEvent phase] == NSEventPhaseBegan, [aEvent deltaX], [aEvent deltaY]);
3244 // For line scrolls, or pre-10.12, just use the rounded up value of deltaX /
3246 return gfx::IntPoint(RoundUp([aEvent deltaX]), RoundUp([aEvent deltaY]));
3249 - (void)scrollWheel:(NSEvent*)theEvent {
3250 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
3252 nsAutoRetainCocoaObject kungFuDeathGrip(self);
3254 ChildViewMouseTracker::MouseScrolled(theEvent);
3256 if ([self maybeRollup:theEvent]) {
3264 NSEventPhase phase = [theEvent phase];
3265 // Fire eWheelOperationStart/End events when 2 fingers touch/release the
3267 if (phase & NSEventPhaseMayBegin) {
3268 [self sendWheelCondition:YES
3269 first:eWheelOperationEnd
3270 second:eWheelOperationStart
3272 } else if (phase & (NSEventPhaseEnded | NSEventPhaseCancelled)) {
3273 [self sendWheelCondition:NO
3274 first:eWheelOperationStart
3275 second:eWheelOperationEnd
3282 RefPtr<nsChildView> geckoChildDeathGrip(mGeckoChild);
3284 NSPoint locationInWindow =
3285 nsCocoaUtils::EventLocationForWindow(theEvent, [self window]);
3287 // Use convertWindowCoordinatesRoundDown when converting the position to
3288 // integer screen pixels in order to ensure that coordinates which are just
3289 // inside the right / bottom edges of the window don't end up outside of the
3290 // window after rounding.
3291 ScreenPoint position = ViewAs<ScreenPixel>(
3292 [self convertWindowCoordinatesRoundDown:locationInWindow],
3293 PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent);
3295 bool usePreciseDeltas =
3296 [theEvent hasPreciseScrollingDeltas] &&
3297 Preferences::GetBool("mousewheel.enable_pixel_scrolling", true);
3298 bool hasPhaseInformation = nsCocoaUtils::EventHasPhaseInformation(theEvent);
3300 gfx::IntPoint lineOrPageDelta = -GetIntegerDeltaForEvent(theEvent);
3302 Modifiers modifiers = nsCocoaUtils::ModifiersForEvent(theEvent);
3304 TimeStamp eventTimeStamp =
3305 nsCocoaUtils::GetEventTimeStamp([theEvent timestamp]);
3307 ScreenPoint preciseDelta;
3308 if (usePreciseDeltas) {
3309 CGFloat pixelDeltaX = [theEvent scrollingDeltaX];
3310 CGFloat pixelDeltaY = [theEvent scrollingDeltaY];
3311 double scale = geckoChildDeathGrip->BackingScaleFactor();
3312 preciseDelta = ScreenPoint(-pixelDeltaX * scale, -pixelDeltaY * scale);
3315 if (usePreciseDeltas && hasPhaseInformation) {
3316 PanGestureInput panEvent = nsCocoaUtils::CreatePanGestureEvent(
3317 theEvent, eventTimeStamp, position, preciseDelta, lineOrPageDelta,
3320 geckoChildDeathGrip->DispatchAPZWheelInputEvent(panEvent);
3321 } else if (usePreciseDeltas) {
3322 // This is on 10.6 or old touchpads that don't have any phase information.
3323 ScrollWheelInput wheelEvent(eventTimeStamp, modifiers,
3324 ScrollWheelInput::SCROLLMODE_INSTANT,
3325 ScrollWheelInput::SCROLLDELTA_PIXEL, position,
3326 preciseDelta.x, preciseDelta.y, false,
3327 // This parameter is used for wheel delta
3328 // adjustment, such as auto-dir scrolling,
3329 // but we do't need to do anything special here
3330 // since this wheel event is sent to
3331 // DispatchAPZWheelInputEvent, which turns this
3332 // ScrollWheelInput back into a WidgetWheelEvent
3333 // and then it goes through the regular handling
3334 // in APZInputBridge. So passing |eNone| won't
3335 // pass up the necessary wheel delta adjustment.
3336 WheelDeltaAdjustmentStrategy::eNone);
3337 wheelEvent.mLineOrPageDeltaX = lineOrPageDelta.x;
3338 wheelEvent.mLineOrPageDeltaY = lineOrPageDelta.y;
3339 wheelEvent.mIsMomentum = nsCocoaUtils::IsMomentumScrollEvent(theEvent);
3340 geckoChildDeathGrip->DispatchAPZWheelInputEvent(wheelEvent);
3342 ScrollWheelInput::ScrollMode scrollMode =
3343 ScrollWheelInput::SCROLLMODE_INSTANT;
3344 if (StaticPrefs::general_smoothScroll() &&
3345 StaticPrefs::general_smoothScroll_mouseWheel()) {
3346 scrollMode = ScrollWheelInput::SCROLLMODE_SMOOTH;
3348 ScrollWheelInput wheelEvent(eventTimeStamp, modifiers, scrollMode,
3349 ScrollWheelInput::SCROLLDELTA_LINE, position,
3350 lineOrPageDelta.x, lineOrPageDelta.y, false,
3351 // This parameter is used for wheel delta
3352 // adjustment, such as auto-dir scrolling,
3353 // but we do't need to do anything special here
3354 // since this wheel event is sent to
3355 // DispatchAPZWheelInputEvent, which turns this
3356 // ScrollWheelInput back into a WidgetWheelEvent
3357 // and then it goes through the regular handling
3358 // in APZInputBridge. So passing |eNone| won't
3359 // pass up the necessary wheel delta adjustment.
3360 WheelDeltaAdjustmentStrategy::eNone);
3361 wheelEvent.mLineOrPageDeltaX = lineOrPageDelta.x;
3362 wheelEvent.mLineOrPageDeltaY = lineOrPageDelta.y;
3363 geckoChildDeathGrip->DispatchAPZWheelInputEvent(wheelEvent);
3366 NS_OBJC_END_TRY_IGNORE_BLOCK;
3369 - (NSMenu*)menuForEvent:(NSEvent*)theEvent {
3370 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
3372 if (!mGeckoChild) return nil;
3374 nsAutoRetainCocoaObject kungFuDeathGrip(self);
3376 [self maybeRollup:theEvent];
3377 if (!mGeckoChild) return nil;
3379 // Cocoa doesn't always dispatch a mouseDown: for a control-click event,
3380 // depends on what we return from menuForEvent:. Gecko always expects one
3381 // and expects the mouse down event before the context menu event, so
3382 // get that event sent first if this is a left mouse click.
3383 if ([theEvent type] == NSEventTypeLeftMouseDown) {
3384 [self mouseDown:theEvent];
3385 if (!mGeckoChild) return nil;
3388 WidgetMouseEvent geckoEvent(true, eContextMenu, mGeckoChild,
3389 WidgetMouseEvent::eReal);
3390 [self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent];
3391 if (StaticPrefs::dom_event_treat_ctrl_click_as_right_click_disabled() &&
3392 [theEvent type] == NSEventTypeLeftMouseDown) {
3393 geckoEvent.mContextMenuTrigger = WidgetMouseEvent::eControlClick;
3394 geckoEvent.mButton = MouseButton::ePrimary;
3396 geckoEvent.mButton = MouseButton::eSecondary;
3399 mGeckoChild->DispatchInputEvent(&geckoEvent);
3400 if (!mGeckoChild) return nil;
3402 [self maybeInitContextMenuTracking];
3404 // We never return an actual NSMenu* for the context menu. Gecko might have
3405 // responded to the eContextMenu event by putting up a fake context menu.
3408 NS_OBJC_END_TRY_BLOCK_RETURN(nil);
3411 - (void)willOpenMenu:(NSMenu*)aMenu withEvent:(NSEvent*)aEvent {
3412 ChildViewMouseTracker::NativeMenuOpened();
3415 - (void)didCloseMenu:(NSMenu*)aMenu withEvent:(NSEvent*)aEvent {
3416 ChildViewMouseTracker::NativeMenuClosed();
3419 - (void)convertCocoaMouseWheelEvent:(NSEvent*)aMouseEvent
3420 toGeckoEvent:(WidgetWheelEvent*)outWheelEvent {
3421 [self convertCocoaMouseEvent:aMouseEvent toGeckoEvent:outWheelEvent];
3423 bool usePreciseDeltas =
3424 [aMouseEvent hasPreciseScrollingDeltas] &&
3425 Preferences::GetBool("mousewheel.enable_pixel_scrolling", true);
3427 outWheelEvent->mDeltaMode = usePreciseDeltas
3428 ? dom::WheelEvent_Binding::DOM_DELTA_PIXEL
3429 : dom::WheelEvent_Binding::DOM_DELTA_LINE;
3430 outWheelEvent->mIsMomentum = nsCocoaUtils::IsMomentumScrollEvent(aMouseEvent);
3433 - (void)convertCocoaMouseEvent:(NSEvent*)aMouseEvent
3434 toGeckoEvent:(WidgetInputEvent*)outGeckoEvent {
3435 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
3439 "convertCocoaMouseEvent:toGeckoEvent: requires non-null aoutGeckoEvent");
3440 if (!outGeckoEvent) return;
3442 nsCocoaUtils::InitInputEvent(*outGeckoEvent, aMouseEvent);
3444 // convert point to view coordinate system
3445 NSPoint locationInWindow =
3446 nsCocoaUtils::EventLocationForWindow(aMouseEvent, [self window]);
3448 outGeckoEvent->mRefPoint = [self convertWindowCoordinates:locationInWindow];
3450 WidgetMouseEventBase* mouseEvent = outGeckoEvent->AsMouseEventBase();
3451 mouseEvent->mButtons = 0;
3452 NSUInteger mouseButtons = [NSEvent pressedMouseButtons];
3454 if (mouseButtons & 0x01) {
3455 mouseEvent->mButtons |= MouseButtonsFlag::ePrimaryFlag;
3457 if (mouseButtons & 0x02) {
3458 mouseEvent->mButtons |= MouseButtonsFlag::eSecondaryFlag;
3460 if (mouseButtons & 0x04) {
3461 mouseEvent->mButtons |= MouseButtonsFlag::eMiddleFlag;
3463 if (mouseButtons & 0x08) {
3464 mouseEvent->mButtons |= MouseButtonsFlag::e4thFlag;
3466 if (mouseButtons & 0x10) {
3467 mouseEvent->mButtons |= MouseButtonsFlag::e5thFlag;
3470 switch ([aMouseEvent type]) {
3471 case NSEventTypeLeftMouseDown:
3472 case NSEventTypeLeftMouseUp:
3473 case NSEventTypeLeftMouseDragged:
3474 case NSEventTypeRightMouseDown:
3475 case NSEventTypeRightMouseUp:
3476 case NSEventTypeRightMouseDragged:
3477 case NSEventTypeOtherMouseDown:
3478 case NSEventTypeOtherMouseUp:
3479 case NSEventTypeOtherMouseDragged:
3480 case NSEventTypeMouseMoved:
3481 if ([aMouseEvent subtype] == NSEventSubtypeTabletPoint) {
3482 [self convertCocoaTabletPointerEvent:aMouseEvent
3483 toGeckoEvent:mouseEvent->AsMouseEvent()];
3488 // Don't check other NSEvents for pressure.
3492 NS_OBJC_END_TRY_IGNORE_BLOCK;
3495 - (void)convertCocoaTabletPointerEvent:(NSEvent*)aPointerEvent
3496 toGeckoEvent:(WidgetMouseEvent*)aOutGeckoEvent {
3497 NS_OBJC_BEGIN_TRY_BLOCK_RETURN
3498 if (!aOutGeckoEvent || !sIsTabletPointerActivated) {
3501 if ([aPointerEvent type] != NSEventTypeMouseMoved) {
3502 aOutGeckoEvent->mPressure = [aPointerEvent pressure];
3503 MOZ_ASSERT(aOutGeckoEvent->mPressure >= 0.0 &&
3504 aOutGeckoEvent->mPressure <= 1.0);
3506 aOutGeckoEvent->mInputSource = dom::MouseEvent_Binding::MOZ_SOURCE_PEN;
3507 aOutGeckoEvent->tiltX = (int32_t)lround([aPointerEvent tilt].x * 90);
3508 aOutGeckoEvent->tiltY = (int32_t)lround([aPointerEvent tilt].y * 90);
3509 aOutGeckoEvent->tangentialPressure = [aPointerEvent tangentialPressure];
3510 // Make sure the twist value is in the range of 0-359.
3511 int32_t twist = (int32_t)fmod([aPointerEvent rotation], 360);
3512 aOutGeckoEvent->twist = twist >= 0 ? twist : twist + 360;
3513 NS_OBJC_END_TRY_IGNORE_BLOCK;
3516 - (void)tabletProximity:(NSEvent*)theEvent {
3517 NS_OBJC_BEGIN_TRY_BLOCK_RETURN
3518 sIsTabletPointerActivated = [theEvent isEnteringProximity];
3519 NS_OBJC_END_TRY_IGNORE_BLOCK
3523 // NSTextInputClient implementation
3525 - (NSRange)markedRange {
3526 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
3528 NS_ENSURE_TRUE(mTextInputHandler, NSMakeRange(NSNotFound, 0));
3529 return mTextInputHandler->MarkedRange();
3531 NS_OBJC_END_TRY_BLOCK_RETURN(NSMakeRange(0, 0));
3534 - (NSRange)selectedRange {
3535 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
3537 NS_ENSURE_TRUE(mTextInputHandler, NSMakeRange(NSNotFound, 0));
3538 return mTextInputHandler->SelectedRange();
3540 NS_OBJC_END_TRY_BLOCK_RETURN(NSMakeRange(0, 0));
3543 - (BOOL)drawsVerticallyForCharacterAtIndex:(NSUInteger)charIndex {
3544 NS_ENSURE_TRUE(mTextInputHandler, NO);
3545 if (charIndex == NSNotFound) {
3548 return mTextInputHandler->DrawsVerticallyForCharacterAtIndex(charIndex);
3551 - (NSUInteger)characterIndexForPoint:(NSPoint)thePoint {
3552 NS_ENSURE_TRUE(mTextInputHandler, 0);
3553 return mTextInputHandler->CharacterIndexForPoint(thePoint);
3556 - (NSArray*)validAttributesForMarkedText {
3557 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
3559 NS_ENSURE_TRUE(mTextInputHandler, [NSArray array]);
3560 return mTextInputHandler->GetValidAttributesForMarkedText();
3562 NS_OBJC_END_TRY_BLOCK_RETURN(nil);
3565 - (void)insertText:(id)aString replacementRange:(NSRange)replacementRange {
3566 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
3568 NS_ENSURE_TRUE_VOID(mGeckoChild);
3570 nsAutoRetainCocoaObject kungFuDeathGrip(self);
3572 NSAttributedString* attrStr;
3573 if ([aString isKindOfClass:[NSAttributedString class]]) {
3574 attrStr = static_cast<NSAttributedString*>(aString);
3576 attrStr = [[[NSAttributedString alloc] initWithString:aString] autorelease];
3579 mTextInputHandler->InsertText(attrStr, &replacementRange);
3581 NS_OBJC_END_TRY_IGNORE_BLOCK;
3584 - (void)doCommandBySelector:(SEL)aSelector {
3585 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
3587 if (!mGeckoChild || !mTextInputHandler) {
3591 const char* sel = reinterpret_cast<const char*>(aSelector);
3592 if (!mTextInputHandler->DoCommandBySelector(sel)) {
3593 [super doCommandBySelector:aSelector];
3596 NS_OBJC_END_TRY_IGNORE_BLOCK;
3599 - (void)unmarkText {
3600 NS_ENSURE_TRUE_VOID(mTextInputHandler);
3601 mTextInputHandler->CommitIMEComposition();
3604 - (BOOL)hasMarkedText {
3605 NS_ENSURE_TRUE(mTextInputHandler, NO);
3606 return mTextInputHandler->HasMarkedText();
3609 - (void)setMarkedText:(id)aString
3610 selectedRange:(NSRange)selectedRange
3611 replacementRange:(NSRange)replacementRange {
3612 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
3614 NS_ENSURE_TRUE_VOID(mTextInputHandler);
3616 nsAutoRetainCocoaObject kungFuDeathGrip(self);
3618 NSAttributedString* attrStr;
3619 if ([aString isKindOfClass:[NSAttributedString class]]) {
3620 attrStr = static_cast<NSAttributedString*>(aString);
3622 attrStr = [[[NSAttributedString alloc] initWithString:aString] autorelease];
3625 mTextInputHandler->SetMarkedText(attrStr, selectedRange, &replacementRange);
3627 NS_OBJC_END_TRY_IGNORE_BLOCK;
3630 - (NSAttributedString*)attributedSubstringForProposedRange:(NSRange)aRange
3632 (NSRangePointer)actualRange {
3633 NS_ENSURE_TRUE(mTextInputHandler, nil);
3634 return mTextInputHandler->GetAttributedSubstringFromRange(aRange,
3638 - (NSRect)firstRectForCharacterRange:(NSRange)aRange
3639 actualRange:(NSRangePointer)actualRange {
3640 NS_ENSURE_TRUE(mTextInputHandler, NSMakeRect(0.0, 0.0, 0.0, 0.0));
3641 return mTextInputHandler->FirstRectForCharacterRange(aRange, actualRange);
3644 - (void)quickLookWithEvent:(NSEvent*)event {
3645 // Show dictionary by current point
3646 WidgetContentCommandEvent contentCommandEvent(
3647 true, eContentCommandLookUpDictionary, mGeckoChild);
3648 NSPoint point = [self convertPoint:[event locationInWindow] fromView:nil];
3649 contentCommandEvent.mRefPoint = mGeckoChild->CocoaPointsToDevPixels(point);
3650 mGeckoChild->DispatchWindowEvent(contentCommandEvent);
3651 // The widget might have been destroyed.
3654 - (NSInteger)windowLevel {
3655 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
3657 NS_ENSURE_TRUE(mTextInputHandler, [[self window] level]);
3658 return mTextInputHandler->GetWindowLevel();
3660 NS_OBJC_END_TRY_BLOCK_RETURN(NSNormalWindowLevel);
3665 // This is a private API that Cocoa uses.
3666 // Cocoa will call this after the menu system returns "NO" for
3667 // "performKeyEquivalent:". We want all they key events we can get so just
3668 // return YES. In particular, this fixes ctrl-tab - we don't get a "keyDown:"
3669 // call for that without this.
3670 - (BOOL)_wantsKeyDownForEvent:(NSEvent*)event {
3674 - (NSEvent*)lastKeyDownEvent {
3675 return mLastKeyDownEvent;
3678 - (void)keyDown:(NSEvent*)theEvent {
3679 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
3681 [mLastKeyDownEvent release];
3682 mLastKeyDownEvent = [theEvent retain];
3684 // Weird things can happen on keyboard input if the key window isn't in the
3685 // current space. For example see bug 1056251. To get around this, always
3686 // make sure that, if our window is key, it's also made frontmost. Doing
3687 // this automatically switches to whatever space our window is in. Safari
3688 // does something similar. Our window should normally always be key --
3689 // otherwise why is the OS sending us a key down event? But it's just
3690 // possible we're in Gecko's hidden window, so we check first.
3691 NSWindow* viewWindow = [self window];
3692 if (viewWindow && [viewWindow isKeyWindow]) {
3693 [viewWindow orderWindow:NSWindowAbove relativeTo:0];
3696 #if !defined(RELEASE_OR_BETA) || defined(DEBUG)
3697 if (!Preferences::GetBool("intl.allow-insecure-text-input", false) &&
3698 mGeckoChild && mTextInputHandler && mTextInputHandler->IsFocused()) {
3699 NSWindow* window = [self window];
3700 NSString* info = [NSString
3701 stringWithFormat:@"\nview [%@], window [%@], window is key %i, is "
3702 @"fullscreen %i, app is active %i",
3703 self, window, [window isKeyWindow],
3704 ([window styleMask] & NSWindowStyleMaskFullScreen) !=
3707 nsAutoCString additionalInfo([info UTF8String]);
3709 if (mGeckoChild->GetInputContext().IsPasswordEditor() &&
3710 !TextInputHandler::IsSecureEventInputEnabled()) {
3711 # define CRASH_MESSAGE \
3712 "A password editor has focus, but not in secure input mode"
3714 CrashReporter::AppendAppNotesToCrashReport(
3715 "\nBug 893973: "_ns + nsLiteralCString(CRASH_MESSAGE));
3716 CrashReporter::AppendAppNotesToCrashReport(additionalInfo);
3718 MOZ_CRASH(CRASH_MESSAGE);
3719 # undef CRASH_MESSAGE
3720 } else if (!mGeckoChild->GetInputContext().IsPasswordEditor() &&
3721 TextInputHandler::IsSecureEventInputEnabled()) {
3722 # define CRASH_MESSAGE \
3723 "A non-password editor has focus, but in secure input mode"
3725 CrashReporter::AppendAppNotesToCrashReport(
3726 "\nBug 893973: "_ns + nsLiteralCString(CRASH_MESSAGE));
3727 CrashReporter::AppendAppNotesToCrashReport(additionalInfo);
3729 MOZ_CRASH(CRASH_MESSAGE);
3730 # undef CRASH_MESSAGE
3733 #endif // #if !defined(RELEASE_OR_BETA) || defined(DEBUG)
3735 nsAutoRetainCocoaObject kungFuDeathGrip(self);
3737 if (mTextInputHandler) {
3738 sUniqueKeyEventId++;
3739 NSMutableDictionary* nativeKeyEventsMap = [ChildView sNativeKeyEventsMap];
3740 [nativeKeyEventsMap setObject:theEvent forKey:@(sUniqueKeyEventId)];
3741 // Purge old native events, in case we're still holding on to them. We
3742 // keep at most 10 references to 10 different native events.
3743 [nativeKeyEventsMap removeObjectForKey:@(sUniqueKeyEventId - 10)];
3744 mTextInputHandler->HandleKeyDownEvent(theEvent, sUniqueKeyEventId);
3746 // There was no text input handler. Offer the event to the native menu
3747 // system to check if there are any registered custom shortcuts for this
3749 mGeckoChild->SendEventToNativeMenuSystem(theEvent);
3753 NS_OBJC_END_TRY_IGNORE_BLOCK;
3756 - (void)keyUp:(NSEvent*)theEvent {
3757 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
3759 NS_ENSURE_TRUE(mGeckoChild, );
3761 nsAutoRetainCocoaObject kungFuDeathGrip(self);
3763 mTextInputHandler->HandleKeyUpEvent(theEvent);
3765 NS_OBJC_END_TRY_IGNORE_BLOCK;
3768 - (void)insertNewline:(id)sender {
3769 if (mTextInputHandler) {
3770 mTextInputHandler->HandleCommand(Command::InsertParagraph);
3774 - (void)insertLineBreak:(id)sender {
3775 // Ctrl + Enter in the default settings.
3776 if (mTextInputHandler) {
3777 mTextInputHandler->HandleCommand(Command::InsertLineBreak);
3781 - (void)deleteBackward:(id)sender {
3782 // Backspace in the default settings.
3783 if (mTextInputHandler) {
3784 mTextInputHandler->HandleCommand(Command::DeleteCharBackward);
3788 - (void)deleteBackwardByDecomposingPreviousCharacter:(id)sender {
3789 // Ctrl + Backspace in the default settings.
3790 if (mTextInputHandler) {
3791 mTextInputHandler->HandleCommand(Command::DeleteCharBackward);
3795 - (void)deleteWordBackward:(id)sender {
3796 // Alt + Backspace in the default settings.
3797 if (mTextInputHandler) {
3798 mTextInputHandler->HandleCommand(Command::DeleteWordBackward);
3802 - (void)deleteToBeginningOfBackward:(id)sender {
3803 // Command + Backspace in the default settings.
3804 if (mTextInputHandler) {
3805 mTextInputHandler->HandleCommand(Command::DeleteToBeginningOfLine);
3809 - (void)deleteForward:(id)sender {
3810 // Delete in the default settings.
3811 if (mTextInputHandler) {
3812 mTextInputHandler->HandleCommand(Command::DeleteCharForward);
3816 - (void)deleteWordForward:(id)sender {
3817 // Alt + Delete in the default settings.
3818 if (mTextInputHandler) {
3819 mTextInputHandler->HandleCommand(Command::DeleteWordForward);
3823 - (void)insertTab:(id)sender {
3824 // Tab in the default settings.
3825 if (mTextInputHandler) {
3826 mTextInputHandler->HandleCommand(Command::InsertTab);
3830 - (void)insertBacktab:(id)sender {
3831 // Shift + Tab in the default settings.
3832 if (mTextInputHandler) {
3833 mTextInputHandler->HandleCommand(Command::InsertBacktab);
3837 - (void)moveRight:(id)sender {
3838 // RightArrow in the default settings.
3839 if (mTextInputHandler) {
3840 mTextInputHandler->HandleCommand(Command::CharNext);
3844 - (void)moveRightAndModifySelection:(id)sender {
3845 // Shift + RightArrow in the default settings.
3846 if (mTextInputHandler) {
3847 mTextInputHandler->HandleCommand(Command::SelectCharNext);
3851 - (void)moveWordRight:(id)sender {
3852 // Alt + RightArrow in the default settings.
3853 if (mTextInputHandler) {
3854 mTextInputHandler->HandleCommand(Command::WordNext);
3858 - (void)moveWordRightAndModifySelection:(id)sender {
3859 // Alt + Shift + RightArrow in the default settings.
3860 if (mTextInputHandler) {
3861 mTextInputHandler->HandleCommand(Command::SelectWordNext);
3865 - (void)moveToRightEndOfLine:(id)sender {
3866 // Command + RightArrow in the default settings.
3867 if (mTextInputHandler) {
3868 mTextInputHandler->HandleCommand(Command::EndLine);
3872 - (void)moveToRightEndOfLineAndModifySelection:(id)sender {
3873 // Command + Shift + RightArrow in the default settings.
3874 if (mTextInputHandler) {
3875 mTextInputHandler->HandleCommand(Command::SelectEndLine);
3879 - (void)moveLeft:(id)sender {
3880 // LeftArrow in the default settings.
3881 if (mTextInputHandler) {
3882 mTextInputHandler->HandleCommand(Command::CharPrevious);
3886 - (void)moveLeftAndModifySelection:(id)sender {
3887 // Shift + LeftArrow in the default settings.
3888 if (mTextInputHandler) {
3889 mTextInputHandler->HandleCommand(Command::SelectCharPrevious);
3893 - (void)moveWordLeft:(id)sender {
3894 // Alt + LeftArrow in the default settings.
3895 if (mTextInputHandler) {
3896 mTextInputHandler->HandleCommand(Command::WordPrevious);
3900 - (void)moveWordLeftAndModifySelection:(id)sender {
3901 // Alt + Shift + LeftArrow in the default settings.
3902 if (mTextInputHandler) {
3903 mTextInputHandler->HandleCommand(Command::SelectWordPrevious);
3907 - (void)moveToLeftEndOfLine:(id)sender {
3908 // Command + LeftArrow in the default settings.
3909 if (mTextInputHandler) {
3910 mTextInputHandler->HandleCommand(Command::BeginLine);
3914 - (void)moveToLeftEndOfLineAndModifySelection:(id)sender {
3915 // Command + Shift + LeftArrow in the default settings.
3916 if (mTextInputHandler) {
3917 mTextInputHandler->HandleCommand(Command::SelectBeginLine);
3921 - (void)moveUp:(id)sender {
3922 // ArrowUp in the default settings.
3923 if (mTextInputHandler) {
3924 mTextInputHandler->HandleCommand(Command::LinePrevious);
3928 - (void)moveUpAndModifySelection:(id)sender {
3929 // Shift + ArrowUp in the default settings.
3930 if (mTextInputHandler) {
3931 mTextInputHandler->HandleCommand(Command::SelectLinePrevious);
3935 - (void)moveToBeginningOfDocument:(id)sender {
3936 // Command + ArrowUp in the default settings.
3937 if (mTextInputHandler) {
3938 mTextInputHandler->HandleCommand(Command::MoveTop);
3942 - (void)moveToBeginningOfDocumentAndModifySelection:(id)sender {
3943 // Command + Shift + ArrowUp or Shift + Home in the default settings.
3944 if (mTextInputHandler) {
3945 mTextInputHandler->HandleCommand(Command::SelectTop);
3949 - (void)moveDown:(id)sender {
3950 // ArrowDown in the default settings.
3951 if (mTextInputHandler) {
3952 mTextInputHandler->HandleCommand(Command::LineNext);
3956 - (void)moveDownAndModifySelection:(id)sender {
3957 // Shift + ArrowDown in the default settings.
3958 if (mTextInputHandler) {
3959 mTextInputHandler->HandleCommand(Command::SelectLineNext);
3963 - (void)moveToEndOfDocument:(id)sender {
3964 // Command + ArrowDown in the default settings.
3965 if (mTextInputHandler) {
3966 mTextInputHandler->HandleCommand(Command::MoveBottom);
3970 - (void)moveToEndOfDocumentAndModifySelection:(id)sender {
3971 // Command + Shift + ArrowDown or Shift + End in the default settings.
3972 if (mTextInputHandler) {
3973 mTextInputHandler->HandleCommand(Command::SelectBottom);
3977 - (void)scrollPageUp:(id)sender {
3978 // PageUp in the default settings.
3979 if (mTextInputHandler) {
3980 mTextInputHandler->HandleCommand(Command::ScrollPageUp);
3984 - (void)pageUpAndModifySelection:(id)sender {
3985 // Shift + PageUp in the default settings.
3986 if (mTextInputHandler) {
3987 mTextInputHandler->HandleCommand(Command::SelectPageUp);
3991 - (void)scrollPageDown:(id)sender {
3992 // PageDown in the default settings.
3993 if (mTextInputHandler) {
3994 mTextInputHandler->HandleCommand(Command::ScrollPageDown);
3998 - (void)pageDownAndModifySelection:(id)sender {
3999 // Shift + PageDown in the default settings.
4000 if (mTextInputHandler) {
4001 mTextInputHandler->HandleCommand(Command::SelectPageDown);
4005 - (void)scrollToEndOfDocument:(id)sender {
4006 // End in the default settings.
4007 if (mTextInputHandler) {
4008 mTextInputHandler->HandleCommand(Command::ScrollBottom);
4012 - (void)scrollToBeginningOfDocument:(id)sender {
4013 // Home in the default settings.
4014 if (mTextInputHandler) {
4015 mTextInputHandler->HandleCommand(Command::ScrollTop);
4019 // XXX Don't decleare nor implement calcelOperation: because it
4020 // causes not calling keyDown: for Command + Period.
4021 // We need to handle it from doCommandBySelector:.
4023 - (void)complete:(id)sender {
4024 // Alt + Escape or Alt + Shift + Escape in the default settings.
4025 if (mTextInputHandler) {
4026 mTextInputHandler->HandleCommand(Command::Complete);
4030 - (void)flagsChanged:(NSEvent*)theEvent {
4031 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
4033 NS_ENSURE_TRUE(mGeckoChild, );
4035 nsAutoRetainCocoaObject kungFuDeathGrip(self);
4036 mTextInputHandler->HandleFlagsChanged(theEvent);
4038 NS_OBJC_END_TRY_IGNORE_BLOCK;
4041 - (BOOL)isFirstResponder {
4042 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
4044 NSResponder* resp = [[self window] firstResponder];
4045 return (resp == (NSResponder*)self);
4047 NS_OBJC_END_TRY_BLOCK_RETURN(NO);
4050 - (BOOL)isDragInProgress {
4051 if (!mDragService) return NO;
4053 nsCOMPtr<nsIDragSession> dragSession;
4054 mDragService->GetCurrentSession(getter_AddRefs(dragSession));
4055 return dragSession != nullptr;
4058 - (BOOL)inactiveWindowAcceptsMouseEvent:(NSEvent*)aEvent {
4059 // If we're being destroyed assume the default -- return YES.
4060 if (!mGeckoChild) return YES;
4062 WidgetMouseEvent geckoEvent(true, eMouseActivate, mGeckoChild,
4063 WidgetMouseEvent::eReal);
4064 [self convertCocoaMouseEvent:aEvent toGeckoEvent:&geckoEvent];
4065 return (mGeckoChild->DispatchInputEvent(&geckoEvent).mContentStatus !=
4066 nsEventStatus_eConsumeNoDefault);
4069 // We must always call through to our superclass, even when mGeckoChild is
4070 // nil -- otherwise the keyboard focus can end up in the wrong NSView.
4071 - (BOOL)becomeFirstResponder {
4072 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
4074 return [super becomeFirstResponder];
4076 NS_OBJC_END_TRY_BLOCK_RETURN(YES);
4079 - (void)viewsWindowDidBecomeKey {
4080 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
4082 if (!mGeckoChild) return;
4084 nsAutoRetainCocoaObject kungFuDeathGrip(self);
4086 // check to see if the window implements the mozWindow protocol. This
4087 // allows embedders to avoid re-entrant calls to -makeKeyAndOrderFront,
4088 // which can happen because these activate calls propagate out
4089 // to the embedder via nsIEmbeddingSiteWindow::SetFocus().
4091 [[self window] respondsToSelector:@selector(setSuppressMakeKeyFront:)];
4092 if (isMozWindow) [[self window] setSuppressMakeKeyFront:YES];
4094 nsIWidgetListener* listener = mGeckoChild->GetWidgetListener();
4095 if (listener) listener->WindowActivated();
4097 if (isMozWindow) [[self window] setSuppressMakeKeyFront:NO];
4099 if (mGeckoChild->GetInputContext().IsPasswordEditor()) {
4100 TextInputHandler::EnableSecureEventInput();
4102 TextInputHandler::EnsureSecureEventInputDisabled();
4105 NS_OBJC_END_TRY_IGNORE_BLOCK;
4108 - (void)viewsWindowDidResignKey {
4109 if (!mGeckoChild) return;
4111 nsAutoRetainCocoaObject kungFuDeathGrip(self);
4113 nsIWidgetListener* listener = mGeckoChild->GetWidgetListener();
4114 if (listener) listener->WindowDeactivated();
4116 TextInputHandler::EnsureSecureEventInputDisabled();
4119 // If the call to removeFromSuperview isn't delayed from nsChildView::
4120 // TearDownView(), the NSView hierarchy might get changed during calls to
4121 // [ChildView drawRect:], which leads to "beyond bounds" exceptions in
4122 // NSCFArray. For more info see bmo bug 373122. Apple's docs claim that
4123 // removeFromSuperviewWithoutNeedingDisplay "can be safely invoked during
4124 // display" (whatever "display" means). But it's _not_ true that it can be
4125 // safely invoked during calls to [NSView drawRect:]. We use
4126 // removeFromSuperview here because there's no longer any danger of being
4127 // "invoked during display", and because doing do clears up bmo bug 384343.
4128 - (void)delayedTearDown {
4129 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
4131 [self removeFromSuperview];
4134 NS_OBJC_END_TRY_IGNORE_BLOCK;
4139 // drag'n'drop stuff
4140 #define kDragServiceContractID "@mozilla.org/widget/dragservice;1"
4142 - (NSDragOperation)dragOperationFromDragAction:(int32_t)aDragAction {
4143 if (nsIDragService::DRAGDROP_ACTION_LINK & aDragAction)
4144 return NSDragOperationLink;
4145 if (nsIDragService::DRAGDROP_ACTION_COPY & aDragAction)
4146 return NSDragOperationCopy;
4147 if (nsIDragService::DRAGDROP_ACTION_MOVE & aDragAction)
4148 return NSDragOperationGeneric;
4149 return NSDragOperationNone;
4152 - (LayoutDeviceIntPoint)convertWindowCoordinates:(NSPoint)aPoint {
4154 return LayoutDeviceIntPoint(0, 0);
4157 NSPoint localPoint = [self convertPoint:aPoint fromView:nil];
4158 return mGeckoChild->CocoaPointsToDevPixels(localPoint);
4161 - (LayoutDeviceIntPoint)convertWindowCoordinatesRoundDown:(NSPoint)aPoint {
4163 return LayoutDeviceIntPoint(0, 0);
4166 NSPoint localPoint = [self convertPoint:aPoint fromView:nil];
4167 return mGeckoChild->CocoaPointsToDevPixelsRoundDown(localPoint);
4170 // This is a utility function used by NSView drag event methods
4171 // to send events. It contains all of the logic needed for Gecko
4172 // dragging to work. Returns the appropriate cocoa drag operation code.
4173 - (NSDragOperation)doDragAction:(EventMessage)aMessage sender:(id)aSender {
4174 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
4176 if (!mGeckoChild) return NSDragOperationNone;
4178 MOZ_LOG(sCocoaLog, LogLevel::Info, ("ChildView doDragAction: entered\n"));
4180 if (!mDragService) {
4181 CallGetService(kDragServiceContractID, &mDragService);
4182 NS_ASSERTION(mDragService, "Couldn't get a drag service - big problem!");
4183 if (!mDragService) return NSDragOperationNone;
4186 if (aMessage == eDragEnter) {
4187 mDragService->StartDragSession();
4190 nsCOMPtr<nsIDragSession> dragSession;
4191 mDragService->GetCurrentSession(getter_AddRefs(dragSession));
4193 if (aMessage == eDragOver) {
4194 // fire the drag event at the source. Just ignore whether it was
4195 // cancelled or not as there isn't actually a means to stop the drag
4196 nsCOMPtr<nsIDragService> dragService = mDragService;
4197 dragService->FireDragEventAtSource(
4198 eDrag, nsCocoaUtils::ModifiersForEvent([NSApp currentEvent]));
4199 dragSession->SetCanDrop(false);
4200 } else if (aMessage == eDrop) {
4201 // We make the assumption that the dragOver handlers have correctly set
4202 // the |canDrop| property of the Drag Session.
4203 bool canDrop = false;
4204 if (!NS_SUCCEEDED(dragSession->GetCanDrop(&canDrop)) || !canDrop) {
4205 [self doDragAction:eDragExit sender:aSender];
4207 nsCOMPtr<nsINode> sourceNode;
4208 dragSession->GetSourceNode(getter_AddRefs(sourceNode));
4210 nsCOMPtr<nsIDragService> dragService = mDragService;
4211 dragService->EndDragSession(
4212 false, nsCocoaUtils::ModifiersForEvent([NSApp currentEvent]));
4214 return NSDragOperationNone;
4218 unsigned int modifierFlags = [[NSApp currentEvent] modifierFlags];
4219 uint32_t action = nsIDragService::DRAGDROP_ACTION_MOVE;
4220 // force copy = option, alias = cmd-option, default is move
4221 if (modifierFlags & NSEventModifierFlagOption) {
4222 if (modifierFlags & NSEventModifierFlagCommand)
4223 action = nsIDragService::DRAGDROP_ACTION_LINK;
4225 action = nsIDragService::DRAGDROP_ACTION_COPY;
4227 dragSession->SetDragAction(action);
4230 // set up gecko event
4231 WidgetDragEvent geckoEvent(true, aMessage, mGeckoChild);
4232 nsCocoaUtils::InitInputEvent(geckoEvent, [NSApp currentEvent]);
4234 // Use our own coordinates in the gecko event.
4235 // Convert event from gecko global coords to gecko view coords.
4236 NSPoint draggingLoc = [aSender draggingLocation];
4238 geckoEvent.mRefPoint = [self convertWindowCoordinates:draggingLoc];
4240 nsAutoRetainCocoaObject kungFuDeathGrip(self);
4241 mGeckoChild->DispatchInputEvent(&geckoEvent);
4242 if (!mGeckoChild) return NSDragOperationNone;
4248 uint32_t dragAction;
4249 dragSession->GetDragAction(&dragAction);
4251 // If TakeChildProcessDragAction returns something other than
4252 // DRAGDROP_ACTION_UNINITIALIZED, it means that the last event was sent
4253 // to the child process and this event is also being sent to the child
4254 // process. In this case, use the last event's action instead.
4255 nsDragService* dragService = static_cast<nsDragService*>(mDragService);
4256 int32_t childDragAction = dragService->TakeChildProcessDragAction();
4257 if (childDragAction != nsIDragService::DRAGDROP_ACTION_UNINITIALIZED) {
4258 dragAction = childDragAction;
4261 return [self dragOperationFromDragAction:dragAction];
4265 nsCOMPtr<nsINode> sourceNode;
4266 dragSession->GetSourceNode(getter_AddRefs(sourceNode));
4268 // We're leaving a window while doing a drag that was
4269 // initiated in a different app. End the drag session,
4270 // since we're done with it for now (until the user
4271 // drags back into mozilla).
4272 nsCOMPtr<nsIDragService> dragService = mDragService;
4273 dragService->EndDragSession(
4274 false, nsCocoaUtils::ModifiersForEvent([NSApp currentEvent]));
4283 return NSDragOperationGeneric;
4285 NS_OBJC_END_TRY_BLOCK_RETURN(NSDragOperationNone);
4288 // NSDraggingDestination
4289 - (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender {
4290 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
4292 MOZ_LOG(sCocoaLog, LogLevel::Info, ("ChildView draggingEntered: entered\n"));
4294 // there should never be a globalDragPboard when "draggingEntered:" is
4295 // called, but just in case we'll take care of it here.
4296 [globalDragPboard release];
4298 // Set the global drag pasteboard that will be used for this drag session.
4299 // This will be set back to nil when the drag session ends (mouse exits
4300 // the view or a drop happens within the view).
4301 globalDragPboard = [[sender draggingPasteboard] retain];
4303 return [self doDragAction:eDragEnter sender:sender];
4305 NS_OBJC_END_TRY_BLOCK_RETURN(NSDragOperationNone);
4308 // NSDraggingDestination
4309 - (NSDragOperation)draggingUpdated:(id<NSDraggingInfo>)sender {
4310 MOZ_LOG(sCocoaLog, LogLevel::Info, ("ChildView draggingUpdated: entered\n"));
4311 return [self doDragAction:eDragOver sender:sender];
4314 // NSDraggingDestination
4315 - (void)draggingExited:(id<NSDraggingInfo>)sender {
4316 MOZ_LOG(sCocoaLog, LogLevel::Info, ("ChildView draggingExited: entered\n"));
4318 nsAutoRetainCocoaObject kungFuDeathGrip(self);
4319 [self doDragAction:eDragExit sender:sender];
4320 NS_IF_RELEASE(mDragService);
4323 // NSDraggingDestination
4324 - (BOOL)performDragOperation:(id<NSDraggingInfo>)sender {
4325 nsAutoRetainCocoaObject kungFuDeathGrip(self);
4326 BOOL handled = [self doDragAction:eDrop sender:sender] != NSDragOperationNone;
4327 NS_IF_RELEASE(mDragService);
4332 // This is just implemented so we comply with the NSDraggingSource protocol.
4333 - (NSDragOperation)draggingSession:(NSDraggingSession*)session
4334 sourceOperationMaskForDraggingContext:(NSDraggingContext)context {
4339 - (BOOL)ignoreModifierKeysForDraggingSession:(NSDraggingSession*)session {
4344 - (void)draggingSession:(NSDraggingSession*)aSession
4345 endedAtPoint:(NSPoint)aPoint
4346 operation:(NSDragOperation)aOperation {
4347 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
4349 #ifdef NIGHTLY_BUILD
4350 MOZ_RELEASE_ASSERT(NS_IsMainThread());
4353 gDraggedTransferables = nullptr;
4355 NSEvent* currentEvent = [NSApp currentEvent];
4356 gUserCancelledDrag = ([currentEvent type] == NSEventTypeKeyDown &&
4357 [currentEvent keyCode] == kVK_Escape);
4359 if (!mDragService) {
4360 CallGetService(kDragServiceContractID, &mDragService);
4361 NS_ASSERTION(mDragService, "Couldn't get a drag service - big problem!");
4365 RefPtr<nsDragService> dragService =
4366 static_cast<nsDragService*>(mDragService);
4368 // Set the dragend point from the current mouse location
4369 // FIXME(emilio): Weird that we wouldn't use aPoint instead? Seems to work
4370 // locally as well...
4371 // NSPoint pnt = aPoint;
4372 NSPoint pnt = [NSEvent mouseLocation];
4373 NSPoint locationInWindow =
4374 nsCocoaUtils::ConvertPointFromScreen([self window], pnt);
4375 FlipCocoaScreenCoordinate(pnt);
4376 dragService->SetDragEndPoint(
4377 [self convertWindowCoordinates:locationInWindow]);
4379 // XXX: dropEffect should be updated per |aOperation|.
4380 // As things stand though, |aOperation| isn't well handled within "our"
4381 // events, that is, when the drop happens within the window: it is set
4382 // either to NSDragOperationGeneric or to NSDragOperationNone.
4383 // For that reason, it's not yet possible to override dropEffect per the
4384 // given OS value, and it's also unclear what's the correct dropEffect
4385 // value for NSDragOperationGeneric that is passed by other applications.
4386 // All that said, NSDragOperationNone is still reliable.
4387 if (aOperation == NSDragOperationNone) {
4388 RefPtr<dom::DataTransfer> dataTransfer = dragService->GetDataTransfer();
4390 dataTransfer->SetDropEffectInt(nsIDragService::DRAGDROP_ACTION_NONE);
4394 dragService->EndDragSession(true,
4395 nsCocoaUtils::ModifiersForEvent(currentEvent));
4396 NS_RELEASE(mDragService);
4399 [globalDragPboard release];
4400 globalDragPboard = nil;
4401 [gLastDragMouseDownEvent release];
4402 gLastDragMouseDownEvent = nil;
4403 mPerformedDrag = YES;
4405 NS_OBJC_END_TRY_IGNORE_BLOCK;
4409 - (void)draggingSession:(NSDraggingSession*)aSession
4410 movedToPoint:(NSPoint)aPoint {
4411 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
4413 // Get the drag service if it isn't already cached. The drag service
4414 // isn't cached when dragging over a different application.
4415 nsCOMPtr<nsIDragService> dragService = mDragService;
4417 dragService = do_GetService(kDragServiceContractID);
4421 nsDragService* ds = static_cast<nsDragService*>(dragService.get());
4422 ds->DragMovedWithView(aSession, aPoint);
4425 NS_OBJC_END_TRY_IGNORE_BLOCK;
4429 - (void)draggingSession:(NSDraggingSession*)aSession
4430 willBeginAtPoint:(NSPoint)aPoint {
4431 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
4433 // there should never be a globalDragPboard when "willBeginAtPoint:" is
4434 // called, but just in case we'll take care of it here.
4435 [globalDragPboard release];
4437 // Set the global drag pasteboard that will be used for this drag session.
4438 // This will be set back to nil when the drag session ends (mouse exits
4439 // the view or a drop happens within the view).
4440 globalDragPboard = [[aSession draggingPasteboard] retain];
4442 NS_OBJC_END_TRY_IGNORE_BLOCK;
4445 // Get the paste location from the low level pasteboard.
4446 static CFTypeRefPtr<CFURLRef> GetPasteLocation(NSPasteboard* aPasteboard) {
4447 PasteboardRef pboardRef = nullptr;
4448 PasteboardCreate((CFStringRef)[aPasteboard name], &pboardRef);
4453 auto pasteBoard = CFTypeRefPtr<PasteboardRef>::WrapUnderCreateRule(pboardRef);
4454 PasteboardSynchronize(pasteBoard.get());
4456 CFURLRef urlRef = nullptr;
4457 PasteboardCopyPasteLocation(pasteBoard.get(), &urlRef);
4458 return CFTypeRefPtr<CFURLRef>::WrapUnderCreateRule(urlRef);
4461 // NSPasteboardItemDataProvider
4462 - (void)pasteboard:(NSPasteboard*)aPasteboard
4463 item:(NSPasteboardItem*)aItem
4464 provideDataForType:(NSString*)aType {
4465 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
4467 #ifdef NIGHTLY_BUILD
4468 MOZ_RELEASE_ASSERT(NS_IsMainThread());
4471 if (!gDraggedTransferables) {
4476 gDraggedTransferables->GetLength(&count);
4478 for (uint32_t j = 0; j < count; j++) {
4479 nsCOMPtr<nsITransferable> currentTransferable =
4480 do_QueryElementAt(gDraggedTransferables, j);
4481 if (!currentTransferable) {
4485 // Transform the transferable to an NSDictionary.
4486 NSDictionary* pasteboardOutputDict =
4487 nsClipboard::PasteboardDictFromTransferable(currentTransferable);
4488 if (!pasteboardOutputDict) {
4492 // Write everything out to the pasteboard.
4493 unsigned int typeCount = [pasteboardOutputDict count];
4494 NSMutableArray* types = [NSMutableArray arrayWithCapacity:typeCount + 1];
4495 [types addObjectsFromArray:[pasteboardOutputDict allKeys]];
4496 [types addObject:[UTIHelper stringFromPboardType:kMozWildcardPboardType]];
4497 for (unsigned int k = 0; k < typeCount; k++) {
4498 NSString* curType = [types objectAtIndex:k];
4499 if ([curType isEqualToString:[UTIHelper stringFromPboardType:
4500 NSPasteboardTypeString]] ||
4502 isEqualToString:[UTIHelper
4503 stringFromPboardType:kPublicUrlPboardType]] ||
4504 [curType isEqualToString:[UTIHelper stringFromPboardType:
4505 kPublicUrlNamePboardType]] ||
4507 isEqualToString:[UTIHelper
4508 stringFromPboardType:(NSString*)
4510 [aPasteboard setString:[pasteboardOutputDict valueForKey:curType]
4512 } else if ([curType isEqualToString:[UTIHelper
4513 stringFromPboardType:
4514 kUrlsWithTitlesPboardType]]) {
4515 [aPasteboard setPropertyList:[pasteboardOutputDict valueForKey:curType]
4518 isEqualToString:[UTIHelper stringFromPboardType:
4519 NSPasteboardTypeHTML]]) {
4520 [aPasteboard setString:(nsClipboard::WrapHtmlForSystemPasteboard(
4521 [pasteboardOutputDict valueForKey:curType]))
4524 isEqualToString:[UTIHelper stringFromPboardType:
4525 NSPasteboardTypeTIFF]] ||
4526 [curType isEqualToString:[UTIHelper
4527 stringFromPboardType:
4528 kMozCustomTypesPboardType]]) {
4529 [aPasteboard setData:[pasteboardOutputDict valueForKey:curType]
4532 isEqualToString:[UTIHelper stringFromPboardType:
4533 kMozFileUrlsPboardType]]) {
4534 [aPasteboard writeObjects:[pasteboardOutputDict valueForKey:curType]];
4538 stringFromPboardType:
4539 (NSString*)kPasteboardTypeFileURLPromise]]) {
4540 nsCOMPtr<nsIFile> targFile;
4541 NS_NewLocalFile(u""_ns, true, getter_AddRefs(targFile));
4542 nsCOMPtr<nsILocalFileMac> macLocalFile = do_QueryInterface(targFile);
4543 if (!macLocalFile) {
4544 NS_ERROR("No Mac local file");
4548 CFTypeRefPtr<CFURLRef> url = GetPasteLocation(aPasteboard);
4553 if (!NS_SUCCEEDED(macLocalFile->InitWithCFURL(url.get()))) {
4554 NS_ERROR("failed InitWithCFURL");
4558 if (!gDraggedTransferables) {
4562 uint32_t transferableCount;
4563 nsresult rv = gDraggedTransferables->GetLength(&transferableCount);
4564 if (NS_FAILED(rv)) {
4568 for (uint32_t i = 0; i < transferableCount; i++) {
4569 nsCOMPtr<nsITransferable> item =
4570 do_QueryElementAt(gDraggedTransferables, i);
4572 NS_ERROR("no transferable");
4576 item->SetTransferData(kFilePromiseDirectoryMime, macLocalFile);
4578 // Now request the kFilePromiseMime data, which will invoke the data
4579 // provider. If successful, the file will have been created.
4580 nsCOMPtr<nsISupports> fileDataPrimitive;
4581 Unused << item->GetTransferData(kFilePromiseMime,
4582 getter_AddRefs(fileDataPrimitive));
4585 [aPasteboard setPropertyList:[pasteboardOutputDict valueForKey:curType]
4591 NS_OBJC_END_TRY_IGNORE_BLOCK;
4596 // Support for the "Services" menu. We currently only support sending strings
4597 // and HTML to system services.
4598 // This method can be called on any thread (see bug 1751687). We can only
4599 // usefully handle it on the main thread.
4600 - (id)validRequestorForSendType:(NSString*)sendType
4601 returnType:(NSString*)returnType {
4602 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
4604 if (!NS_IsMainThread()) {
4605 // We don't have any thread-safe ways of checking whether we can send
4606 // or receive content. Just say no. In normal cases, we expect this
4607 // method to be called on the main thread.
4608 return [super validRequestorForSendType:sendType returnType:returnType];
4611 // sendType contains the type of data that the service would like this
4612 // application to send to it. sendType is nil if the service is not
4613 // requesting any data.
4615 // returnType contains the type of data the the service would like to
4616 // return to this application (e.g., to overwrite the selection).
4617 // returnType is nil if the service will not return any data.
4619 // The following condition thus triggers when the service expects a string
4620 // or HTML from us or no data at all AND when the service will either not
4621 // send back any data to us or will send a string or HTML back to us.
4625 NSString* stringType =
4626 [UTIHelper stringFromPboardType:NSPasteboardTypeString];
4627 NSString* htmlType = [UTIHelper stringFromPboardType:NSPasteboardTypeHTML];
4628 if ((!sendType || [sendType isEqualToString:stringType] ||
4629 [sendType isEqualToString:htmlType]) &&
4630 (!returnType || [returnType isEqualToString:stringType] ||
4631 [returnType isEqualToString:htmlType])) {
4633 // Assume that this object will be able to handle this request.
4636 // Keep the ChildView alive during this operation.
4637 nsAutoRetainCocoaObject kungFuDeathGrip(self);
4640 // Determine if there is a current selection (chrome/content).
4641 if (!nsClipboard::sSelectionCache) {
4646 // Determine if we can paste (if receiving data from the service).
4647 if (mGeckoChild && returnType) {
4648 WidgetContentCommandEvent command(
4649 true, eContentCommandPasteTransferable, mGeckoChild, true);
4650 // This might possibly destroy our widget (and null out mGeckoChild).
4651 mGeckoChild->DispatchWindowEvent(command);
4652 if (!mGeckoChild || !command.mSucceeded || !command.mIsEnabled)
4658 // Give the superclass a chance if this object will not handle this request.
4660 result = [super validRequestorForSendType:sendType returnType:returnType];
4664 NS_OBJC_END_TRY_BLOCK_RETURN(nil);
4667 - (BOOL)writeSelectionToPasteboard:(NSPasteboard*)pboard types:(NSArray*)types {
4668 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
4670 nsAutoRetainCocoaObject kungFuDeathGrip(self);
4672 // Make sure that the service will accept strings or HTML.
4674 containsObject:[UTIHelper stringFromPboardType:NSStringPboardType]] &&
4676 containsObject:[UTIHelper
4677 stringFromPboardType:NSPasteboardTypeString]] &&
4678 ![types containsObject:[UTIHelper
4679 stringFromPboardType:NSPasteboardTypeHTML]]) {
4683 // Bail out if there is no Gecko object.
4684 if (!mGeckoChild) return NO;
4686 // Transform the transferable to an NSDictionary.
4687 NSDictionary* pasteboardOutputDict = nullptr;
4689 pasteboardOutputDict =
4690 nsClipboard::PasteboardDictFromTransferable(nsClipboard::sSelectionCache);
4692 if (!pasteboardOutputDict) return NO;
4694 // Declare the pasteboard types.
4695 unsigned int typeCount = [pasteboardOutputDict count];
4696 NSMutableArray* declaredTypes = [NSMutableArray arrayWithCapacity:typeCount];
4697 [declaredTypes addObjectsFromArray:[pasteboardOutputDict allKeys]];
4698 [pboard declareTypes:declaredTypes owner:nil];
4700 // Write the data to the pasteboard.
4701 for (unsigned int i = 0; i < typeCount; i++) {
4702 NSString* currentKey = [declaredTypes objectAtIndex:i];
4703 id currentValue = [pasteboardOutputDict valueForKey:currentKey];
4706 isEqualToString:[UTIHelper
4707 stringFromPboardType:NSPasteboardTypeString]] ||
4709 isEqualToString:[UTIHelper
4710 stringFromPboardType:kPublicUrlPboardType]] ||
4711 [currentKey isEqualToString:[UTIHelper stringFromPboardType:
4712 kPublicUrlNamePboardType]]) {
4713 [pboard setString:currentValue forType:currentKey];
4714 } else if ([currentKey
4716 [UTIHelper stringFromPboardType:NSPasteboardTypeHTML]]) {
4717 [pboard setString:(nsClipboard::WrapHtmlForSystemPasteboard(currentValue))
4718 forType:currentKey];
4719 } else if ([currentKey
4721 [UTIHelper stringFromPboardType:NSPasteboardTypeTIFF]]) {
4722 [pboard setData:currentValue forType:currentKey];
4723 } else if ([currentKey
4726 stringFromPboardType:
4727 (NSString*)kPasteboardTypeFileURLPromise]] ||
4729 isEqualToString:[UTIHelper stringFromPboardType:
4730 kUrlsWithTitlesPboardType]]) {
4731 [pboard setPropertyList:currentValue forType:currentKey];
4736 NS_OBJC_END_TRY_BLOCK_RETURN(NO);
4739 // Called if the service wants us to replace the current selection.
4740 - (BOOL)readSelectionFromPasteboard:(NSPasteboard*)pboard {
4742 nsCOMPtr<nsITransferable> trans =
4743 do_CreateInstance("@mozilla.org/widget/transferable;1", &rv);
4744 if (NS_FAILED(rv)) return NO;
4745 trans->Init(nullptr);
4747 trans->AddDataFlavor(kTextMime);
4748 trans->AddDataFlavor(kHTMLMime);
4750 rv = nsClipboard::TransferableFromPasteboard(trans, pboard);
4751 if (NS_FAILED(rv)) return NO;
4753 NS_ENSURE_TRUE(mGeckoChild, false);
4755 WidgetContentCommandEvent command(true, eContentCommandPasteTransferable,
4757 command.mTransferable = trans;
4758 mGeckoChild->DispatchWindowEvent(command);
4760 return command.mSucceeded && command.mIsEnabled;
4763 - (void)pressureChangeWithEvent:(NSEvent*)event {
4764 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK
4766 NSInteger stage = [event stage];
4767 if (mLastPressureStage == 1 && stage == 2) {
4768 NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults];
4769 if ([userDefaults integerForKey:@"com.apple.trackpad.forceClick"] == 1) {
4770 // This is no public API to get configuration for current force click.
4771 // This is filed as radar 29294285.
4772 [self quickLookWithEvent:event];
4775 mLastPressureStage = stage;
4777 NS_OBJC_END_TRY_IGNORE_BLOCK
4780 nsresult nsChildView::GetSelectionAsPlaintext(nsAString& aResult) {
4781 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
4783 if (!nsClipboard::sSelectionCache) {
4784 MOZ_ASSERT(aResult.IsEmpty());
4788 // Get the current chrome or content selection.
4789 NSDictionary* pasteboardOutputDict = nullptr;
4790 pasteboardOutputDict =
4791 nsClipboard::PasteboardDictFromTransferable(nsClipboard::sSelectionCache);
4793 if (NS_WARN_IF(!pasteboardOutputDict)) {
4794 return NS_ERROR_FAILURE;
4797 // Declare the pasteboard types.
4798 unsigned int typeCount = [pasteboardOutputDict count];
4799 NSMutableArray* declaredTypes = [NSMutableArray arrayWithCapacity:typeCount];
4800 [declaredTypes addObjectsFromArray:[pasteboardOutputDict allKeys]];
4801 NSString* currentKey = [declaredTypes objectAtIndex:0];
4802 NSString* currentValue = [pasteboardOutputDict valueForKey:currentKey];
4803 const char* textSelection = [currentValue UTF8String];
4804 aResult = NS_ConvertUTF8toUTF16(textSelection);
4808 NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
4812 nsresult nsChildView::SetHiDPIMode(bool aHiDPI) {
4813 nsCocoaUtils::InvalidateHiDPIState();
4814 Preferences::SetInt("gfx.hidpi.enabled", aHiDPI ? 1 : 0);
4815 BackingScaleFactorChanged();
4819 nsresult nsChildView::RestoreHiDPIMode() {
4820 nsCocoaUtils::InvalidateHiDPIState();
4821 Preferences::ClearUser("gfx.hidpi.enabled");
4822 BackingScaleFactorChanged();
4829 #ifdef ACCESSIBILITY
4831 /* Every ChildView has a corresponding mozDocAccessible object that is doing all
4832 the heavy lifting. The topmost ChildView corresponds to a mozRootAccessible
4835 All ChildView needs to do is to route all accessibility calls (from the
4836 NSAccessibility APIs) down to its object, pretending that they are the same.
4838 - (id<mozAccessible>)accessible {
4839 if (!mGeckoChild) return nil;
4841 id<mozAccessible> nativeAccessible = nil;
4843 nsAutoRetainCocoaObject kungFuDeathGrip(self);
4844 RefPtr<nsChildView> geckoChild(mGeckoChild);
4845 RefPtr<a11y::LocalAccessible> accessible =
4846 geckoChild->GetDocumentAccessible();
4847 if (!accessible) return nil;
4849 accessible->GetNativeInterface((void**)&nativeAccessible);
4852 NSAssert(![nativeAccessible isExpired], @"native acc is expired!!!");
4855 return nativeAccessible;
4858 /* Implementation of formal mozAccessible formal protocol (enabling mozViews
4859 to talk to mozAccessible objects in the accessibility module). */
4861 - (BOOL)hasRepresentedView {
4865 - (id)representedView {
4870 return [[self accessible] isRoot];
4877 - (BOOL)isAccessibilityElement {
4878 if (!mozilla::a11y::ShouldA11yBeEnabled())
4879 return [super isAccessibilityElement];
4881 return [[self accessible] isAccessibilityElement];
4884 - (id)accessibilityHitTest:(NSPoint)point {
4885 if (!mozilla::a11y::ShouldA11yBeEnabled())
4886 return [super accessibilityHitTest:point];
4888 return [[self accessible] accessibilityHitTest:point];
4891 - (id)accessibilityFocusedUIElement {
4892 if (!mozilla::a11y::ShouldA11yBeEnabled())
4893 return [super accessibilityFocusedUIElement];
4895 return [[self accessible] accessibilityFocusedUIElement];
4900 - (NSArray*)accessibilityActionNames {
4901 if (!mozilla::a11y::ShouldA11yBeEnabled())
4902 return [super accessibilityActionNames];
4904 return [[self accessible] accessibilityActionNames];
4907 - (NSString*)accessibilityActionDescription:(NSString*)action {
4908 if (!mozilla::a11y::ShouldA11yBeEnabled())
4909 return [super accessibilityActionDescription:action];
4911 return [[self accessible] accessibilityActionDescription:action];
4914 - (void)accessibilityPerformAction:(NSString*)action {
4915 if (!mozilla::a11y::ShouldA11yBeEnabled())
4916 return [super accessibilityPerformAction:action];
4918 return [[self accessible] accessibilityPerformAction:action];
4923 - (NSArray*)accessibilityAttributeNames {
4924 if (!mozilla::a11y::ShouldA11yBeEnabled())
4925 return [super accessibilityAttributeNames];
4927 return [[self accessible] accessibilityAttributeNames];
4930 - (BOOL)accessibilityIsAttributeSettable:(NSString*)attribute {
4931 if (!mozilla::a11y::ShouldA11yBeEnabled())
4932 return [super accessibilityIsAttributeSettable:attribute];
4934 return [[self accessible] accessibilityIsAttributeSettable:attribute];
4937 - (id)accessibilityAttributeValue:(NSString*)attribute {
4938 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
4940 if (!mozilla::a11y::ShouldA11yBeEnabled())
4941 return [super accessibilityAttributeValue:attribute];
4943 id<mozAccessible> accessible = [self accessible];
4945 // if we're the root (topmost) accessible, we need to return our native
4946 // AXParent as we traverse outside to the hierarchy of whoever embeds us.
4947 // thus, fall back on NSView's default implementation for this attribute.
4948 if ([attribute isEqualToString:NSAccessibilityParentAttribute] &&
4949 [accessible isRoot]) {
4950 id parentAccessible = [super accessibilityAttributeValue:attribute];
4951 return parentAccessible;
4954 return [accessible accessibilityAttributeValue:attribute];
4956 NS_OBJC_END_TRY_BLOCK_RETURN(nil);
4959 #endif /* ACCESSIBILITY */
4961 + (uint32_t)sUniqueKeyEventId {
4962 return sUniqueKeyEventId;
4965 + (NSMutableDictionary*)sNativeKeyEventsMap {
4966 // This dictionary is "leaked".
4967 static NSMutableDictionary* sNativeKeyEventsMap =
4968 [[NSMutableDictionary alloc] init];
4969 return sNativeKeyEventsMap;
4974 @implementation PixelHostingView
4976 - (id)initWithFrame:(NSRect)aRect {
4977 self = [super initWithFrame:aRect];
4979 self.wantsLayer = YES;
4980 self.layerContentsRedrawPolicy = NSViewLayerContentsRedrawDuringViewResize;
4989 - (NSView*)hitTest:(NSPoint)aPoint {
4993 - (void)drawRect:(NSRect)aRect {
4994 NS_WARNING("Unexpected call to drawRect: This view returns YES from "
4995 "wantsUpdateLayer, so "
4996 "drawRect should not be called.");
4999 - (BOOL)wantsUpdateLayer {
5003 - (void)updateLayer {
5004 [(ChildView*)[self superview] updateRootCALayer];
5007 - (BOOL)wantsBestResolutionOpenGLSurface {
5008 return nsCocoaUtils::HiDPIEnabled() ? YES : NO;
5015 void ChildViewMouseTracker::OnDestroyView(ChildView* aView) {
5016 if (sLastMouseEventView == aView) {
5017 sLastMouseEventView = nil;
5018 [sLastMouseMoveEvent release];
5019 sLastMouseMoveEvent = nil;
5023 void ChildViewMouseTracker::OnDestroyWindow(NSWindow* aWindow) {
5024 if (sWindowUnderMouse == aWindow) {
5025 sWindowUnderMouse = nil;
5029 void ChildViewMouseTracker::MouseEnteredWindow(NSEvent* aEvent) {
5030 sWindowUnderMouse = [aEvent window];
5031 ReEvaluateMouseEnterState(aEvent);
5034 void ChildViewMouseTracker::MouseExitedWindow(NSEvent* aEvent) {
5035 if (sWindowUnderMouse == [aEvent window]) {
5036 sWindowUnderMouse = nil;
5037 [sLastMouseMoveEvent release];
5038 sLastMouseMoveEvent = nil;
5039 ReEvaluateMouseEnterState(aEvent);
5043 void ChildViewMouseTracker::NativeMenuOpened() {
5044 // Send a mouse exit event now.
5045 // The menu consumes all mouse events while it's open, and we don't want to be
5046 // stuck thinking the mouse is still hovering our window after the mouse has
5047 // already moved. This could result in unintended cursor changes or tooltips.
5048 sWindowUnderMouse = nil;
5049 ReEvaluateMouseEnterState(nil);
5052 void ChildViewMouseTracker::NativeMenuClosed() {
5053 // If a window was hovered before the menu opened, re-enter that window at the
5054 // last known mouse position. After -[NSView didCloseMenu:withEvent:] is
5055 // called, any NSTrackingArea updates that were buffered while the menu was
5056 // open will be replayed.
5057 if (sLastMouseMoveEvent) {
5058 sWindowUnderMouse = sLastMouseMoveEvent.window;
5059 ReEvaluateMouseEnterState(sLastMouseMoveEvent);
5063 void ChildViewMouseTracker::ReEvaluateMouseEnterState(NSEvent* aEvent,
5064 ChildView* aOldView) {
5065 ChildView* oldView = aOldView ? aOldView : sLastMouseEventView;
5066 sLastMouseEventView = ViewForEvent(aEvent);
5067 if (sLastMouseEventView != oldView) {
5068 // Send enter and / or exit events.
5069 WidgetMouseEvent::ExitFrom exitFrom =
5070 [sLastMouseEventView window] == [oldView window]
5071 ? WidgetMouseEvent::ePlatformChild
5072 : WidgetMouseEvent::ePlatformTopLevel;
5073 [oldView sendMouseEnterOrExitEvent:aEvent enter:NO exitFrom:exitFrom];
5074 // After the cursor exits the window set it to a visible regular arrow
5076 if (exitFrom == WidgetMouseEvent::ePlatformTopLevel) {
5077 [[nsCursorManager sharedInstance]
5078 setNonCustomCursor:nsIWidget::Cursor{eCursor_standard}];
5080 [sLastMouseEventView sendMouseEnterOrExitEvent:aEvent
5086 void ChildViewMouseTracker::ResendLastMouseMoveEvent() {
5087 if (sLastMouseMoveEvent) {
5088 MouseMoved(sLastMouseMoveEvent);
5092 void ChildViewMouseTracker::MouseMoved(NSEvent* aEvent) {
5093 MouseEnteredWindow(aEvent);
5094 [sLastMouseEventView handleMouseMoved:aEvent];
5095 if (sLastMouseMoveEvent != aEvent) {
5096 [sLastMouseMoveEvent release];
5097 sLastMouseMoveEvent = [aEvent retain];
5101 void ChildViewMouseTracker::MouseScrolled(NSEvent* aEvent) {
5102 if (!nsCocoaUtils::IsMomentumScrollEvent(aEvent)) {
5103 // Store the position so we can pin future momentum scroll events.
5104 sLastScrollEventScreenLocation =
5105 nsCocoaUtils::ScreenLocationForEvent(aEvent);
5109 ChildView* ChildViewMouseTracker::ViewForEvent(NSEvent* aEvent) {
5110 NSWindow* window = sWindowUnderMouse;
5111 if (!window) return nil;
5113 NSPoint windowEventLocation =
5114 nsCocoaUtils::EventLocationForWindow(aEvent, window);
5115 NSView* view = [[[window contentView] superview] hitTest:windowEventLocation];
5117 if (![view isKindOfClass:[ChildView class]]) return nil;
5119 ChildView* childView = (ChildView*)view;
5120 // If childView is being destroyed return nil.
5121 if (![childView widget]) return nil;
5122 return WindowAcceptsEvent(window, aEvent, childView) ? childView : nil;
5125 BOOL ChildViewMouseTracker::WindowAcceptsEvent(NSWindow* aWindow,
5128 BOOL aIsClickThrough) {
5129 // Right mouse down events may get through to all windows, even to a top level
5130 // window with an open sheet.
5131 if (!aWindow || [aEvent type] == NSEventTypeRightMouseDown) return YES;
5133 id delegate = [aWindow delegate];
5134 if (!delegate || ![delegate isKindOfClass:[WindowDelegate class]]) return YES;
5136 nsIWidget* windowWidget = [(WindowDelegate*)delegate geckoWidget];
5137 if (!windowWidget) return YES;
5139 NSWindow* topLevelWindow = nil;
5141 switch (windowWidget->GetWindowType()) {
5142 case WindowType::Popup:
5143 // If this is a context menu, it won't have a parent. So we'll always
5144 // accept mouse move events on context menus even when none of our windows
5145 // is active, which is the right thing to do.
5146 // For panels, the parent window is the XUL window that owns the panel.
5147 return WindowAcceptsEvent([aWindow parentWindow], aEvent, aView,
5150 case WindowType::TopLevel:
5151 case WindowType::Dialog:
5152 if ([aWindow attachedSheet]) return NO;
5154 topLevelWindow = aWindow;
5156 case WindowType::Sheet: {
5157 nsIWidget* parentWidget = windowWidget->GetSheetWindowParent();
5158 if (!parentWidget) return YES;
5160 topLevelWindow = (NSWindow*)parentWidget->GetNativeData(NS_NATIVE_WINDOW);
5168 if (!topLevelWindow || ([topLevelWindow isMainWindow] && !aIsClickThrough) ||
5169 [aEvent type] == NSEventTypeOtherMouseDown ||
5170 (([aEvent modifierFlags] & NSEventModifierFlagCommand) != 0 &&
5171 [aEvent type] != NSEventTypeMouseMoved))
5174 // If we're here then we're dealing with a left click or mouse move on an
5175 // inactive window or something similar. Ask Gecko what to do.
5176 return [aView inactiveWindowAcceptsMouseEvent:aEvent];