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/. */ 5 6#include "mozilla/ArrayUtils.h" 7 8#include "mozilla/Logging.h" 9#include "mozilla/Unused.h" 10 11#include <unistd.h> 12#include <math.h> 13 14#include "nsChildView.h" 15#include "nsCocoaWindow.h" 16 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" 32 33#include "nsArrayUtils.h" 34#include "nsExceptionHandler.h" 35#include "nsObjCExceptions.h" 36#include "nsCOMPtr.h" 37#include "nsThreadUtils.h" 38#include "nsToolkit.h" 39#include "nsCRT.h" 40 41#include "nsFontMetrics.h" 42#include "nsIRollupListener.h" 43#include "nsViewManager.h" 44#include "nsIFile.h" 45#include "nsILocalFileMac.h" 46#include "nsGfxCIID.h" 47#include "nsStyleConsts.h" 48#include "nsIWidgetListener.h" 49#include "nsIScreen.h" 50 51#include "nsDragService.h" 52#include "nsClipboard.h" 53#include "nsCursorManager.h" 54#include "nsWindowMap.h" 55#include "nsCocoaFeatures.h" 56#include "nsCocoaUtils.h" 57#include "nsMenuUtilsX.h" 58#include "nsMenuBarX.h" 59#include "NativeKeyBindings.h" 60#include "MacThemeGeometryType.h" 61 62#include "gfxContext.h" 63#include "gfxQuartzSurface.h" 64#include "gfxUtils.h" 65#include "nsRegion.h" 66#include "Layers.h" 67#include "GfxTexturesReporter.h" 68#include "GLTextureImage.h" 69#include "GLContextProvider.h" 70#include "GLContextCGL.h" 71#include "OGLShaderProgram.h" 72#include "ScopedGLHelpers.h" 73#include "HeapCopyOfStackArray.h" 74#include "mozilla/layers/IAPZCTreeManager.h" 75#include "mozilla/layers/APZInputBridge.h" 76#include "mozilla/layers/APZThreadUtils.h" 77#include "mozilla/layers/CompositorOGL.h" 78#include "mozilla/layers/CompositorBridgeParent.h" 79#include "mozilla/layers/InputAPZContext.h" 80#include "mozilla/layers/IpcResourceUpdateQueue.h" 81#include "mozilla/layers/NativeLayerCA.h" 82#include "mozilla/layers/WebRenderBridgeChild.h" 83#include "mozilla/layers/WebRenderLayerManager.h" 84#include "mozilla/webrender/WebRenderAPI.h" 85#include "mozilla/widget/CompositorWidget.h" 86#include "gfxUtils.h" 87#include "mozilla/gfx/2D.h" 88#include "mozilla/gfx/BorrowedContext.h" 89#ifdef ACCESSIBILITY 90# include "nsAccessibilityService.h" 91# include "mozilla/a11y/Platform.h" 92#endif 93 94#include "mozilla/Preferences.h" 95#include "mozilla/StaticPrefs_apz.h" 96#include "mozilla/StaticPrefs_browser.h" 97#include "mozilla/StaticPrefs_general.h" 98#include "mozilla/StaticPrefs_gfx.h" 99#include "mozilla/StaticPrefs_ui.h" 100 101#include <dlfcn.h> 102 103#include <ApplicationServices/ApplicationServices.h> 104 105#include "GeckoProfiler.h" 106 107#include "mozilla/layers/ChromeProcessController.h" 108#include "nsLayoutUtils.h" 109#include "InputData.h" 110#include "VibrancyManager.h" 111#include "nsNativeThemeCocoa.h" 112#include "nsIDOMWindowUtils.h" 113#include "Units.h" 114#include "UnitTransforms.h" 115#include "mozilla/UniquePtrExtensions.h" 116#include "CustomCocoaEvents.h" 117#include "NativeMenuSupport.h" 118 119using namespace mozilla; 120using namespace mozilla::layers; 121using namespace mozilla::gl; 122using namespace mozilla::widget; 123 124using mozilla::gfx::Matrix4x4; 125 126#undef DEBUG_UPDATE 127#undef INVALIDATE_DEBUGGING // flash areas as they are invalidated 128 129// Don't put more than this many rects in the dirty region, just fluff 130// out to the bounding-box if there are more 131#define MAX_RECTS_IN_REGION 100 132 133LazyLogModule sCocoaLog("nsCocoaWidgets"); 134 135extern "C" { 136CG_EXTERN void CGContextResetCTM(CGContextRef); 137CG_EXTERN void CGContextSetCTM(CGContextRef, CGAffineTransform); 138CG_EXTERN void CGContextResetClip(CGContextRef); 139 140typedef CFTypeRef CGSRegionObj; 141CGError CGSNewRegionWithRect(const CGRect* rect, CGSRegionObj* outRegion); 142CGError CGSNewRegionWithRectList(const CGRect* rects, int rectCount, CGSRegionObj* outRegion); 143} 144 145// defined in nsMenuBarX.mm 146extern NSMenu* sApplicationMenu; // Application menu shared by all menubars 147 148extern nsIArray* gDraggedTransferables; 149 150ChildView* ChildViewMouseTracker::sLastMouseEventView = nil; 151NSEvent* ChildViewMouseTracker::sLastMouseMoveEvent = nil; 152NSWindow* ChildViewMouseTracker::sWindowUnderMouse = nil; 153NSPoint ChildViewMouseTracker::sLastScrollEventScreenLocation = NSZeroPoint; 154 155#ifdef INVALIDATE_DEBUGGING 156static void blinkRect(Rect* r); 157static void blinkRgn(RgnHandle rgn); 158#endif 159 160bool gUserCancelledDrag = false; 161 162uint32_t nsChildView::sLastInputEventCount = 0; 163 164static bool sIsTabletPointerActivated = false; 165 166static uint32_t sUniqueKeyEventId = 0; 167 168// The view that will do our drawing or host our NSOpenGLContext or Core Animation layer. 169@interface PixelHostingView : NSView { 170} 171 172@end 173 174@interface ChildView (Private) 175 176// sets up our view, attaching it to its owning gecko view 177- (id)initWithFrame:(NSRect)inFrame geckoChild:(nsChildView*)inChild; 178 179// set up a gecko mouse event based on a cocoa mouse event 180- (void)convertCocoaMouseWheelEvent:(NSEvent*)aMouseEvent 181 toGeckoEvent:(WidgetWheelEvent*)outWheelEvent; 182- (void)convertCocoaMouseEvent:(NSEvent*)aMouseEvent toGeckoEvent:(WidgetInputEvent*)outGeckoEvent; 183- (void)convertCocoaTabletPointerEvent:(NSEvent*)aMouseEvent 184 toGeckoEvent:(WidgetMouseEvent*)outGeckoEvent; 185- (NSMenu*)contextMenu; 186 187- (void)markLayerForDisplay; 188- (CALayer*)rootCALayer; 189- (void)updateRootCALayer; 190 191#ifdef ACCESSIBILITY 192- (id<mozAccessible>)accessible; 193#endif 194 195- (LayoutDeviceIntPoint)convertWindowCoordinates:(NSPoint)aPoint; 196- (LayoutDeviceIntPoint)convertWindowCoordinatesRoundDown:(NSPoint)aPoint; 197 198- (BOOL)inactiveWindowAcceptsMouseEvent:(NSEvent*)aEvent; 199- (void)updateWindowDraggableState; 200 201- (bool)beginOrEndGestureForEventPhase:(NSEvent*)aEvent; 202 203- (bool)shouldConsiderStartingSwipeFromEvent:(NSEvent*)aEvent; 204 205@end 206 207#pragma mark - 208 209// Flips a screen coordinate from a point in the cocoa coordinate system (bottom-left rect) to a 210// point that is a "flipped" cocoa coordinate system (starts in the top-left). 211static inline void FlipCocoaScreenCoordinate(NSPoint& inPoint) { 212 inPoint.y = nsCocoaUtils::FlippedScreenY(inPoint.y); 213} 214 215#pragma mark - 216 217nsChildView::nsChildView() 218 : nsBaseWidget(), 219 mView(nullptr), 220 mParentView(nil), 221 mParentWidget(nullptr), 222 mCompositingLock("ChildViewCompositing"), 223 mBackingScaleFactor(0.0), 224 mVisible(false), 225 mDrawing(false), 226 mIsDispatchPaint(false) {} 227 228nsChildView::~nsChildView() { 229 // Notify the children that we're gone. childView->ResetParent() can change 230 // our list of children while it's being iterated, so the way we iterate the 231 // list must allow for this. 232 for (nsIWidget* kid = mLastChild; kid;) { 233 nsChildView* childView = static_cast<nsChildView*>(kid); 234 kid = kid->GetPrevSibling(); 235 childView->ResetParent(); 236 } 237 238 NS_WARNING_ASSERTION(mOnDestroyCalled, "nsChildView object destroyed without calling Destroy()"); 239 240 if (mContentLayer) { 241 mNativeLayerRoot->RemoveLayer(mContentLayer); // safe if already removed 242 } 243 244 DestroyCompositor(); 245 246 // An nsChildView object that was in use can be destroyed without Destroy() 247 // ever being called on it. So we also need to do a quick, safe cleanup 248 // here (it's too late to just call Destroy(), which can cause crashes). 249 // It's particularly important to make sure widgetDestroyed is called on our 250 // mView -- this method NULLs mView's mGeckoChild, and NULL checks on 251 // mGeckoChild are used throughout the ChildView class to tell if it's safe 252 // to use a ChildView object. 253 [mView widgetDestroyed]; // Safe if mView is nil. 254 mParentWidget = nil; 255 TearDownView(); // Safe if called twice. 256} 257 258nsresult nsChildView::Create(nsIWidget* aParent, nsNativeWidget aNativeParent, 259 const LayoutDeviceIntRect& aRect, nsWidgetInitData* aInitData) { 260 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; 261 262 // Because the hidden window is created outside of an event loop, 263 // we need to provide an autorelease pool to avoid leaking cocoa objects 264 // (see bug 559075). 265 nsAutoreleasePool localPool; 266 267 mBounds = aRect; 268 269 // Ensure that the toolkit is created. 270 nsToolkit::GetToolkit(); 271 272 BaseCreate(aParent, aInitData); 273 274 mParentView = nil; 275 if (aParent) { 276 // This is the popup window case. aParent is the nsCocoaWindow for the 277 // popup window, and mParentView will be its content view. 278 mParentView = (NSView*)aParent->GetNativeData(NS_NATIVE_WIDGET); 279 mParentWidget = aParent; 280 } else { 281 // This is the top-level window case. 282 // aNativeParent will be the contentView of our window, since that's what 283 // nsCocoaWindow returns when asked for an NS_NATIVE_VIEW. 284 // We do not have a direct "parent widget" association with the top level 285 // window's nsCocoaWindow object. 286 mParentView = reinterpret_cast<NSView*>(aNativeParent); 287 } 288 289 // create our parallel NSView and hook it up to our parent. Recall 290 // that NS_NATIVE_WIDGET is the NSView. 291 CGFloat scaleFactor = nsCocoaUtils::GetBackingScaleFactor(mParentView); 292 NSRect r = nsCocoaUtils::DevPixelsToCocoaPoints(mBounds, scaleFactor); 293 mView = [[ChildView alloc] initWithFrame:r geckoChild:this]; 294 295 mNativeLayerRoot = NativeLayerRootCA::CreateForCALayer([mView rootCALayer]); 296 mNativeLayerRoot->SetBackingScale(scaleFactor); 297 298 // If this view was created in a Gecko view hierarchy, the initial state 299 // is hidden. If the view is attached only to a native NSView but has 300 // no Gecko parent (as in embedding), the initial state is visible. 301 if (mParentWidget) 302 [mView setHidden:YES]; 303 else 304 mVisible = true; 305 306 // Hook it up in the NSView hierarchy. 307 if (mParentView) { 308 [mParentView addSubview:mView]; 309 } 310 311 // if this is a ChildView, make sure that our per-window data 312 // is set up 313 if ([mView isKindOfClass:[ChildView class]]) 314 [[WindowDataMap sharedWindowDataMap] ensureDataForWindow:[mView window]]; 315 316 NS_ASSERTION(!mTextInputHandler, "mTextInputHandler has already existed"); 317 mTextInputHandler = new TextInputHandler(this, mView); 318 319 return NS_OK; 320 321 NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE); 322} 323 324void nsChildView::TearDownView() { 325 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; 326 327 if (!mView) return; 328 329 NSWindow* win = [mView window]; 330 NSResponder* responder = [win firstResponder]; 331 332 // We're being unhooked from the view hierarchy, don't leave our view 333 // or a child view as the window first responder. 334 if (responder && [responder isKindOfClass:[NSView class]] && 335 [(NSView*)responder isDescendantOf:mView]) { 336 [win makeFirstResponder:[mView superview]]; 337 } 338 339 // If mView is win's contentView, win (mView's NSWindow) "owns" mView -- 340 // win has retained mView, and will detach it from the view hierarchy and 341 // release it when necessary (when win is itself destroyed (in a call to 342 // [win dealloc])). So all we need to do here is call [mView release] (to 343 // match the call to [mView retain] in nsChildView::StandardCreate()). 344 // Also calling [mView removeFromSuperviewWithoutNeedingDisplay] causes 345 // mView to be released again and dealloced, while remaining win's 346 // contentView. So if we do that here, win will (for a short while) have 347 // an invalid contentView (for the consequences see bmo bugs 381087 and 348 // 374260). 349 if ([mView isEqual:[win contentView]]) { 350 [mView release]; 351 } else { 352 // Stop NSView hierarchy being changed during [ChildView drawRect:] 353 [mView performSelectorOnMainThread:@selector(delayedTearDown) 354 withObject:nil 355 waitUntilDone:false]; 356 } 357 mView = nil; 358 359 NS_OBJC_END_TRY_IGNORE_BLOCK; 360} 361 362nsCocoaWindow* nsChildView::GetAppWindowWidget() const { 363 id windowDelegate = [[mView window] delegate]; 364 if (windowDelegate && [windowDelegate isKindOfClass:[WindowDelegate class]]) { 365 return [(WindowDelegate*)windowDelegate geckoWidget]; 366 } 367 return nullptr; 368} 369 370void nsChildView::Destroy() { 371 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; 372 373 // Make sure that no composition is in progress while disconnecting 374 // ourselves from the view. 375 MutexAutoLock lock(mCompositingLock); 376 377 if (mOnDestroyCalled) return; 378 mOnDestroyCalled = true; 379 380 // Stuff below may delete the last ref to this 381 nsCOMPtr<nsIWidget> kungFuDeathGrip(this); 382 383 [mView widgetDestroyed]; 384 385 nsBaseWidget::Destroy(); 386 387 NotifyWindowDestroyed(); 388 mParentWidget = nil; 389 390 TearDownView(); 391 392 nsBaseWidget::OnDestroy(); 393 394 NS_OBJC_END_TRY_IGNORE_BLOCK; 395} 396 397#pragma mark - 398 399#if 0 400static void PrintViewHierarchy(NSView *view) 401{ 402 while (view) { 403 NSLog(@" view is %x, frame %@", view, NSStringFromRect([view frame])); 404 view = [view superview]; 405 } 406} 407#endif 408 409// Return native data according to aDataType 410void* nsChildView::GetNativeData(uint32_t aDataType) { 411 NS_OBJC_BEGIN_TRY_BLOCK_RETURN; 412 413 void* retVal = nullptr; 414 415 switch (aDataType) { 416 case NS_NATIVE_WIDGET: 417 case NS_NATIVE_DISPLAY: 418 retVal = (void*)mView; 419 break; 420 421 case NS_NATIVE_WINDOW: 422 retVal = [mView window]; 423 break; 424 425 case NS_NATIVE_GRAPHIC: 426 NS_ERROR("Requesting NS_NATIVE_GRAPHIC on a Mac OS X child view!"); 427 retVal = nullptr; 428 break; 429 430 case NS_NATIVE_OFFSETX: 431 retVal = 0; 432 break; 433 434 case NS_NATIVE_OFFSETY: 435 retVal = 0; 436 break; 437 438 case NS_RAW_NATIVE_IME_CONTEXT: 439 retVal = GetPseudoIMEContext(); 440 if (retVal) { 441 break; 442 } 443 retVal = [mView inputContext]; 444 // If input context isn't available on this widget, we should set |this| 445 // instead of nullptr since if this returns nullptr, IMEStateManager 446 // cannot manage composition with TextComposition instance. Although, 447 // this case shouldn't occur. 448 if (NS_WARN_IF(!retVal)) { 449 retVal = this; 450 } 451 break; 452 453 case NS_NATIVE_WINDOW_WEBRTC_DEVICE_ID: { 454 NSWindow* win = [mView window]; 455 if (win) { 456 retVal = (void*)[win windowNumber]; 457 } 458 break; 459 } 460 } 461 462 return retVal; 463 464 NS_OBJC_END_TRY_BLOCK_RETURN(nullptr); 465} 466 467#pragma mark - 468 469void nsChildView::SuppressAnimation(bool aSuppress) { 470 GetAppWindowWidget()->SuppressAnimation(aSuppress); 471} 472 473bool nsChildView::IsVisible() const { 474 NS_OBJC_BEGIN_TRY_BLOCK_RETURN; 475 476 if (!mVisible) { 477 return mVisible; 478 } 479 480 if (!GetAppWindowWidget()->IsVisible()) { 481 return false; 482 } 483 484 // mVisible does not accurately reflect the state of a hidden tabbed view 485 // so verify that the view has a window as well 486 // then check native widget hierarchy visibility 487 return ([mView window] != nil) && !NSIsEmptyRect([mView visibleRect]); 488 489 NS_OBJC_END_TRY_BLOCK_RETURN(false); 490} 491 492// Some NSView methods (e.g. setFrame and setHidden) invalidate the view's 493// bounds in our window. However, we don't want these invalidations because 494// they are unnecessary and because they actually slow us down since we 495// block on the compositor inside drawRect. 496// When we actually need something invalidated, there will be an explicit call 497// to Invalidate from Gecko, so turning these automatic invalidations off 498// won't hurt us in the non-OMTC case. 499// The invalidations inside these NSView methods happen via a call to the 500// private method -[NSWindow _setNeedsDisplayInRect:]. Our BaseWindow 501// implementation of that method is augmented to let us ignore those calls 502// using -[BaseWindow disable/enableSetNeedsDisplay]. 503static void ManipulateViewWithoutNeedingDisplay(NSView* aView, void (^aCallback)()) { 504 BaseWindow* win = nil; 505 if ([[aView window] isKindOfClass:[BaseWindow class]]) { 506 win = (BaseWindow*)[aView window]; 507 } 508 [win disableSetNeedsDisplay]; 509 aCallback(); 510 [win enableSetNeedsDisplay]; 511} 512 513// Hide or show this component 514void nsChildView::Show(bool aState) { 515 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; 516 517 if (aState != mVisible) { 518 // Provide an autorelease pool because this gets called during startup 519 // on the "hidden window", resulting in cocoa object leakage if there's 520 // no pool in place. 521 nsAutoreleasePool localPool; 522 523 ManipulateViewWithoutNeedingDisplay(mView, ^{ 524 [mView setHidden:!aState]; 525 }); 526 527 mVisible = aState; 528 } 529 530 NS_OBJC_END_TRY_IGNORE_BLOCK; 531} 532 533// Change the parent of this widget 534void nsChildView::SetParent(nsIWidget* aNewParent) { 535 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; 536 537 if (mOnDestroyCalled) return; 538 539 nsCOMPtr<nsIWidget> kungFuDeathGrip(this); 540 541 if (mParentWidget) { 542 mParentWidget->RemoveChild(this); 543 } 544 545 if (aNewParent) { 546 ReparentNativeWidget(aNewParent); 547 } else { 548 [mView removeFromSuperview]; 549 mParentView = nil; 550 } 551 552 mParentWidget = aNewParent; 553 554 if (mParentWidget) { 555 mParentWidget->AddChild(this); 556 } 557 558 NS_OBJC_END_TRY_IGNORE_BLOCK; 559} 560 561void nsChildView::ReparentNativeWidget(nsIWidget* aNewParent) { 562 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; 563 564 MOZ_ASSERT(aNewParent, "null widget"); 565 566 if (mOnDestroyCalled) return; 567 568 NSView<mozView>* newParentView = (NSView<mozView>*)aNewParent->GetNativeData(NS_NATIVE_WIDGET); 569 NS_ENSURE_TRUE_VOID(newParentView); 570 571 // we hold a ref to mView, so this is safe 572 [mView removeFromSuperview]; 573 mParentView = newParentView; 574 [mParentView addSubview:mView]; 575 576 NS_OBJC_END_TRY_IGNORE_BLOCK; 577} 578 579void nsChildView::ResetParent() { 580 if (!mOnDestroyCalled) { 581 if (mParentWidget) mParentWidget->RemoveChild(this); 582 if (mView) [mView removeFromSuperview]; 583 } 584 mParentWidget = nullptr; 585} 586 587nsIWidget* nsChildView::GetParent() { return mParentWidget; } 588 589float nsChildView::GetDPI() { 590 float dpi = 96.0; 591 nsCOMPtr<nsIScreen> screen = GetWidgetScreen(); 592 if (screen) { 593 screen->GetDpi(&dpi); 594 } 595 return dpi; 596} 597 598void nsChildView::Enable(bool aState) {} 599 600bool nsChildView::IsEnabled() const { return true; } 601 602void nsChildView::SetFocus(Raise, mozilla::dom::CallerType aCallerType) { 603 NS_OBJC_BEGIN_TRY_BLOCK_RETURN; 604 605 NSWindow* window = [mView window]; 606 if (window) [window makeFirstResponder:mView]; 607 NS_OBJC_END_TRY_IGNORE_BLOCK; 608} 609 610// Override to set the cursor on the mac 611void nsChildView::SetCursor(const Cursor& aCursor) { 612 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; 613 614 if ([mView isDragInProgress]) { 615 return; // Don't change the cursor during dragging. 616 } 617 618 nsBaseWidget::SetCursor(aCursor); 619 620 if (NS_SUCCEEDED([[nsCursorManager sharedInstance] setCustomCursor:aCursor 621 widgetScaleFactor:BackingScaleFactor()])) { 622 return; 623 } 624 625 [[nsCursorManager sharedInstance] setNonCustomCursor:aCursor]; 626 627 NS_OBJC_END_TRY_IGNORE_BLOCK; 628} 629 630#pragma mark - 631 632// Get this component dimension 633LayoutDeviceIntRect nsChildView::GetBounds() { 634 return !mView ? mBounds : CocoaPointsToDevPixels([mView frame]); 635} 636 637LayoutDeviceIntRect nsChildView::GetClientBounds() { 638 LayoutDeviceIntRect rect = GetBounds(); 639 if (!mParentWidget) { 640 // For top level widgets we want the position on screen, not the position 641 // of this view inside the window. 642 rect.MoveTo(WidgetToScreenOffset()); 643 } 644 return rect; 645} 646 647LayoutDeviceIntRect nsChildView::GetScreenBounds() { 648 LayoutDeviceIntRect rect = GetBounds(); 649 rect.MoveTo(WidgetToScreenOffset()); 650 return rect; 651} 652 653double nsChildView::GetDefaultScaleInternal() { return BackingScaleFactor(); } 654 655CGFloat nsChildView::BackingScaleFactor() const { 656 if (mBackingScaleFactor > 0.0) { 657 return mBackingScaleFactor; 658 } 659 if (!mView) { 660 return 1.0; 661 } 662 mBackingScaleFactor = nsCocoaUtils::GetBackingScaleFactor(mView); 663 return mBackingScaleFactor; 664} 665 666void nsChildView::BackingScaleFactorChanged() { 667 CGFloat newScale = nsCocoaUtils::GetBackingScaleFactor(mView); 668 669 // ignore notification if it hasn't really changed (or maybe we have 670 // disabled HiDPI mode via prefs) 671 if (mBackingScaleFactor == newScale) { 672 return; 673 } 674 675 SuspendAsyncCATransactions(); 676 mBackingScaleFactor = newScale; 677 NSRect frame = [mView frame]; 678 mBounds = nsCocoaUtils::CocoaRectToGeckoRectDevPix(frame, newScale); 679 680 mNativeLayerRoot->SetBackingScale(mBackingScaleFactor); 681 682 if (mWidgetListener && !mWidgetListener->GetAppWindow()) { 683 if (PresShell* presShell = mWidgetListener->GetPresShell()) { 684 presShell->BackingScaleFactorChanged(); 685 } 686 } 687} 688 689int32_t nsChildView::RoundsWidgetCoordinatesTo() { 690 if (BackingScaleFactor() == 2.0) { 691 return 2; 692 } 693 return 1; 694} 695 696// Move this component, aX and aY are in the parent widget coordinate system 697void nsChildView::Move(double aX, double aY) { 698 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; 699 700 int32_t x = NSToIntRound(aX); 701 int32_t y = NSToIntRound(aY); 702 703 if (!mView || (mBounds.x == x && mBounds.y == y)) return; 704 705 mBounds.x = x; 706 mBounds.y = y; 707 708 ManipulateViewWithoutNeedingDisplay(mView, ^{ 709 [mView setFrame:DevPixelsToCocoaPoints(mBounds)]; 710 }); 711 712 NotifyRollupGeometryChange(); 713 ReportMoveEvent(); 714 715 NS_OBJC_END_TRY_IGNORE_BLOCK; 716} 717 718void nsChildView::Resize(double aWidth, double aHeight, bool aRepaint) { 719 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; 720 721 int32_t width = NSToIntRound(aWidth); 722 int32_t height = NSToIntRound(aHeight); 723 724 if (!mView || (mBounds.width == width && mBounds.height == height)) return; 725 726 SuspendAsyncCATransactions(); 727 mBounds.width = width; 728 mBounds.height = height; 729 730 ManipulateViewWithoutNeedingDisplay(mView, ^{ 731 [mView setFrame:DevPixelsToCocoaPoints(mBounds)]; 732 }); 733 734 if (mVisible && aRepaint) { 735 [[mView pixelHostingView] setNeedsDisplay:YES]; 736 } 737 738 NotifyRollupGeometryChange(); 739 ReportSizeEvent(); 740 741 NS_OBJC_END_TRY_IGNORE_BLOCK; 742} 743 744void nsChildView::Resize(double aX, double aY, double aWidth, double aHeight, bool aRepaint) { 745 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; 746 747 int32_t x = NSToIntRound(aX); 748 int32_t y = NSToIntRound(aY); 749 int32_t width = NSToIntRound(aWidth); 750 int32_t height = NSToIntRound(aHeight); 751 752 BOOL isMoving = (mBounds.x != x || mBounds.y != y); 753 BOOL isResizing = (mBounds.width != width || mBounds.height != height); 754 if (!mView || (!isMoving && !isResizing)) return; 755 756 if (isMoving) { 757 mBounds.x = x; 758 mBounds.y = y; 759 } 760 if (isResizing) { 761 SuspendAsyncCATransactions(); 762 mBounds.width = width; 763 mBounds.height = height; 764 765 CALayer* layer = [mView rootCALayer]; 766 double scale = BackingScaleFactor(); 767 layer.bounds = CGRectMake(0, 0, width / scale, height / scale); 768 } 769 770 ManipulateViewWithoutNeedingDisplay(mView, ^{ 771 [mView setFrame:DevPixelsToCocoaPoints(mBounds)]; 772 }); 773 774 if (mVisible && aRepaint) { 775 [[mView pixelHostingView] setNeedsDisplay:YES]; 776 } 777 778 NotifyRollupGeometryChange(); 779 if (isMoving) { 780 ReportMoveEvent(); 781 if (mOnDestroyCalled) return; 782 } 783 if (isResizing) ReportSizeEvent(); 784 785 NS_OBJC_END_TRY_IGNORE_BLOCK; 786} 787 788// The following three methods are primarily an attempt to avoid glitches during 789// window resizing. 790// Here's some background on how these glitches come to be: 791// CoreAnimation transactions are per-thread. They don't nest across threads. 792// If you submit a transaction on the main thread and a transaction on a 793// different thread, the two will race to the window server and show up on the 794// screen in the order that they happen to arrive in at the window server. 795// When the window size changes, there's another event that needs to be 796// synchronized with: the window "shape" change. Cocoa has built-in synchronization 797// mechanics that make sure that *main thread* window paints during window resizes 798// are synchronized properly with the window shape change. But no such built-in 799// synchronization exists for CATransactions that are triggered on a non-main 800// thread. 801// To cope with this, we define a "danger zone" during which we simply avoid 802// triggering any CATransactions on a non-main thread (called "async" CATransactions 803// here). This danger zone starts at the earliest opportunity at which we know 804// about the size change, which is nsChildView::Resize, and ends at a point at 805// which we know for sure that the paint has been handled completely, which is 806// when we return to the event loop after layer display. 807void nsChildView::SuspendAsyncCATransactions() { 808 if (mUnsuspendAsyncCATransactionsRunnable) { 809 mUnsuspendAsyncCATransactionsRunnable->Cancel(); 810 mUnsuspendAsyncCATransactionsRunnable = nullptr; 811 } 812 813 // Make sure that there actually will be a CATransaction on the main thread 814 // during which we get a chance to schedule unsuspension. Otherwise we might 815 // accidentally stay suspended indefinitely. 816 [mView markLayerForDisplay]; 817 818 mNativeLayerRoot->SuspendOffMainThreadCommits(); 819} 820 821void nsChildView::MaybeScheduleUnsuspendAsyncCATransactions() { 822 if (mNativeLayerRoot->AreOffMainThreadCommitsSuspended() && 823 !mUnsuspendAsyncCATransactionsRunnable) { 824 mUnsuspendAsyncCATransactionsRunnable = 825 NewCancelableRunnableMethod("nsChildView::MaybeScheduleUnsuspendAsyncCATransactions", this, 826 &nsChildView::UnsuspendAsyncCATransactions); 827 NS_DispatchToMainThread(mUnsuspendAsyncCATransactionsRunnable); 828 } 829} 830 831void nsChildView::UnsuspendAsyncCATransactions() { 832 mUnsuspendAsyncCATransactionsRunnable = nullptr; 833 834 if (mNativeLayerRoot->UnsuspendOffMainThreadCommits()) { 835 // We need to call mNativeLayerRoot->CommitToScreen() at the next available 836 // opportunity. 837 // The easiest way to handle this request is to mark the layer as needing 838 // display, because this will schedule a main thread CATransaction, during 839 // which HandleMainThreadCATransaction will call CommitToScreen(). 840 [mView markLayerForDisplay]; 841 } 842} 843 844void nsChildView::UpdateFullscreen(bool aFullscreen) { 845 if (mNativeLayerRoot) { 846 mNativeLayerRoot->SetWindowIsFullscreen(aFullscreen); 847 } 848} 849 850void nsChildView::NoteMouseMoveAtTime(const mozilla::TimeStamp& aTime) { 851 if (mNativeLayerRoot) { 852 mNativeLayerRoot->NoteMouseMoveAtTime(aTime); 853 } 854} 855 856nsresult nsChildView::SynthesizeNativeKeyEvent(int32_t aNativeKeyboardLayout, 857 int32_t aNativeKeyCode, uint32_t aModifierFlags, 858 const nsAString& aCharacters, 859 const nsAString& aUnmodifiedCharacters, 860 nsIObserver* aObserver) { 861 AutoObserverNotifier notifier(aObserver, "keyevent"); 862 return mTextInputHandler->SynthesizeNativeKeyEvent( 863 aNativeKeyboardLayout, aNativeKeyCode, aModifierFlags, aCharacters, aUnmodifiedCharacters); 864} 865 866nsresult nsChildView::SynthesizeNativeMouseEvent(LayoutDeviceIntPoint aPoint, 867 NativeMouseMessage aNativeMessage, 868 MouseButton aButton, 869 nsIWidget::Modifiers aModifierFlags, 870 nsIObserver* aObserver) { 871 NS_OBJC_BEGIN_TRY_BLOCK_RETURN; 872 873 AutoObserverNotifier notifier(aObserver, "mouseevent"); 874 875 NSPoint pt = nsCocoaUtils::DevPixelsToCocoaPoints(aPoint, BackingScaleFactor()); 876 877 // Move the mouse cursor to the requested position and reconnect it to the mouse. 878 CGWarpMouseCursorPosition(NSPointToCGPoint(pt)); 879 CGAssociateMouseAndMouseCursorPosition(true); 880 881 // aPoint is given with the origin on the top left, but convertScreenToBase 882 // expects a point in a coordinate system that has its origin on the bottom left. 883 NSPoint screenPoint = NSMakePoint(pt.x, nsCocoaUtils::FlippedScreenY(pt.y)); 884 NSPoint windowPoint = nsCocoaUtils::ConvertPointFromScreen([mView window], screenPoint); 885 NSEventModifierFlags modifierFlags = 886 nsCocoaUtils::ConvertWidgetModifiersToMacModifierFlags(aModifierFlags); 887 888 if (aButton == MouseButton::eX1 || aButton == MouseButton::eX2) { 889 // NSEvent has `buttonNumber` for `NSEventTypeOther*`. However, it seems that 890 // there is no way to specify it. Therefore, we should return error for now. 891 return NS_ERROR_INVALID_ARG; 892 } 893 894 NSEventType nativeEventType; 895 switch (aNativeMessage) { 896 case NativeMouseMessage::ButtonDown: 897 case NativeMouseMessage::ButtonUp: { 898 switch (aButton) { 899 case MouseButton::ePrimary: 900 nativeEventType = aNativeMessage == NativeMouseMessage::ButtonDown 901 ? NSEventTypeLeftMouseDown 902 : NSEventTypeLeftMouseUp; 903 break; 904 case MouseButton::eMiddle: 905 nativeEventType = aNativeMessage == NativeMouseMessage::ButtonDown 906 ? NSEventTypeOtherMouseDown 907 : NSEventTypeOtherMouseUp; 908 break; 909 case MouseButton::eSecondary: 910 nativeEventType = aNativeMessage == NativeMouseMessage::ButtonDown 911 ? NSEventTypeRightMouseDown 912 : NSEventTypeRightMouseUp; 913 break; 914 default: 915 return NS_ERROR_INVALID_ARG; 916 } 917 break; 918 } 919 case NativeMouseMessage::Move: 920 nativeEventType = NSEventTypeMouseMoved; 921 break; 922 case NativeMouseMessage::EnterWindow: 923 nativeEventType = NSEventTypeMouseEntered; 924 break; 925 case NativeMouseMessage::LeaveWindow: 926 nativeEventType = NSEventTypeMouseExited; 927 break; 928 } 929 930 NSEvent* event = [NSEvent mouseEventWithType:nativeEventType 931 location:windowPoint 932 modifierFlags:modifierFlags 933 timestamp:[[NSProcessInfo processInfo] systemUptime] 934 windowNumber:[[mView window] windowNumber] 935 context:nil 936 eventNumber:0 937 clickCount:1 938 pressure:0.0]; 939 940 if (!event) return NS_ERROR_FAILURE; 941 942 if ([[mView window] isKindOfClass:[BaseWindow class]]) { 943 // Tracking area events don't end up in their tracking areas when sent 944 // through [NSApp sendEvent:], so pass them directly to the right methods. 945 BaseWindow* window = (BaseWindow*)[mView window]; 946 if (nativeEventType == NSEventTypeMouseEntered) { 947 [window mouseEntered:event]; 948 return NS_OK; 949 } 950 if (nativeEventType == NSEventTypeMouseExited) { 951 [window mouseExited:event]; 952 return NS_OK; 953 } 954 if (nativeEventType == NSEventTypeMouseMoved) { 955 [window mouseMoved:event]; 956 return NS_OK; 957 } 958 } 959 960 [NSApp sendEvent:event]; 961 return NS_OK; 962 963 NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE); 964} 965 966nsresult nsChildView::SynthesizeNativeMouseScrollEvent( 967 mozilla::LayoutDeviceIntPoint aPoint, uint32_t aNativeMessage, double aDeltaX, double aDeltaY, 968 double aDeltaZ, uint32_t aModifierFlags, uint32_t aAdditionalFlags, nsIObserver* aObserver) { 969 NS_OBJC_BEGIN_TRY_BLOCK_RETURN; 970 971 AutoObserverNotifier notifier(aObserver, "mousescrollevent"); 972 973 NSPoint pt = nsCocoaUtils::DevPixelsToCocoaPoints(aPoint, BackingScaleFactor()); 974 975 // Move the mouse cursor to the requested position and reconnect it to the mouse. 976 CGWarpMouseCursorPosition(NSPointToCGPoint(pt)); 977 CGAssociateMouseAndMouseCursorPosition(true); 978 979 // Mostly copied from http://stackoverflow.com/a/6130349 980 CGScrollEventUnit units = (aAdditionalFlags & nsIDOMWindowUtils::MOUSESCROLL_SCROLL_LINES) 981 ? kCGScrollEventUnitLine 982 : kCGScrollEventUnitPixel; 983 CGEventRef cgEvent = CGEventCreateScrollWheelEvent(NULL, units, 3, (int32_t)aDeltaY, 984 (int32_t)aDeltaX, (int32_t)aDeltaZ); 985 if (!cgEvent) { 986 return NS_ERROR_FAILURE; 987 } 988 989 if (aNativeMessage) { 990 CGEventSetIntegerValueField(cgEvent, kCGScrollWheelEventScrollPhase, aNativeMessage); 991 } 992 993 // On macOS 10.14 and up CGEventPost won't work because of changes in macOS 994 // to improve security. This code makes an NSEvent corresponding to the 995 // wheel event and dispatches it directly to the scrollWheel handler. Some 996 // fiddling is needed with the coordinates in order to simulate what macOS 997 // would do; this code adapted from the Chromium equivalent function at 998 // https://chromium.googlesource.com/chromium/src.git/+/62.0.3178.1/ui/events/test/cocoa_test_event_utils.mm#38 999 CGPoint location = CGEventGetLocation(cgEvent); 1000 location.y += NSMinY([[mView window] frame]); 1001 location.x -= NSMinX([[mView window] frame]); 1002 CGEventSetLocation(cgEvent, location); 1003 1004 uint64_t kNanosPerSec = 1000000000L; 1005 CGEventSetTimestamp(cgEvent, [[NSProcessInfo processInfo] systemUptime] * kNanosPerSec); 1006 1007 NSEvent* event = [NSEvent eventWithCGEvent:cgEvent]; 1008 [event setValue:[mView window] forKey:@"_window"]; 1009 [mView scrollWheel:event]; 1010 1011 CFRelease(cgEvent); 1012 return NS_OK; 1013 1014 NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE); 1015} 1016 1017nsresult nsChildView::SynthesizeNativeTouchPoint( 1018 uint32_t aPointerId, TouchPointerState aPointerState, mozilla::LayoutDeviceIntPoint aPoint, 1019 double aPointerPressure, uint32_t aPointerOrientation, nsIObserver* aObserver) { 1020 NS_OBJC_BEGIN_TRY_BLOCK_RETURN; 1021 1022 AutoObserverNotifier notifier(aObserver, "touchpoint"); 1023 1024 MOZ_ASSERT(NS_IsMainThread()); 1025 if (aPointerState == TOUCH_HOVER) { 1026 return NS_ERROR_UNEXPECTED; 1027 } 1028 1029 if (!mSynthesizedTouchInput) { 1030 mSynthesizedTouchInput = MakeUnique<MultiTouchInput>(); 1031 } 1032 1033 LayoutDeviceIntPoint pointInWindow = aPoint - WidgetToScreenOffset(); 1034 MultiTouchInput inputToDispatch = UpdateSynthesizedTouchState( 1035 mSynthesizedTouchInput.get(), PR_IntervalNow(), TimeStamp::Now(), aPointerId, aPointerState, 1036 pointInWindow, aPointerPressure, aPointerOrientation); 1037 DispatchTouchInput(inputToDispatch); 1038 return NS_OK; 1039 1040 NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE); 1041} 1042 1043nsresult nsChildView::SynthesizeNativeTouchpadDoubleTap(mozilla::LayoutDeviceIntPoint aPoint, 1044 uint32_t aModifierFlags) { 1045 NS_OBJC_BEGIN_TRY_BLOCK_RETURN; 1046 1047 DispatchDoubleTapGesture(TimeStamp::Now(), aPoint - WidgetToScreenOffset(), 1048 static_cast<Modifiers>(aModifierFlags)); 1049 1050 return NS_OK; 1051 1052 NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE); 1053} 1054 1055bool nsChildView::SendEventToNativeMenuSystem(NSEvent* aEvent) { 1056 bool handled = false; 1057 nsCocoaWindow* widget = GetAppWindowWidget(); 1058 if (widget) { 1059 nsMenuBarX* mb = widget->GetMenuBar(); 1060 if (mb) { 1061 // Check if main menu wants to handle the event. 1062 handled = mb->PerformKeyEquivalent(aEvent); 1063 } 1064 } 1065 1066 if (!handled && sApplicationMenu) { 1067 // Check if application menu wants to handle the event. 1068 handled = [sApplicationMenu performKeyEquivalent:aEvent]; 1069 } 1070 1071 return handled; 1072} 1073 1074void nsChildView::PostHandleKeyEvent(mozilla::WidgetKeyboardEvent* aEvent) { 1075 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; 1076 1077 // We always allow keyboard events to propagate to keyDown: but if they are 1078 // not handled we give menu items a chance to act. This allows for handling of 1079 // custom shortcuts. Note that existing shortcuts cannot be reassigned yet and 1080 // will have been handled by keyDown: before we get here. 1081 NSMutableDictionary* nativeKeyEventsMap = [ChildView sNativeKeyEventsMap]; 1082 NSEvent* cocoaEvent = [nativeKeyEventsMap objectForKey:@(aEvent->mUniqueId)]; 1083 if (!cocoaEvent) { 1084 return; 1085 } 1086 1087 // If the escape key is pressed, the expectations are as follows: 1088 // 1. If the page is loading, interrupt loading. 1089 // 2. Give a website an opportunity to handle the event and call 1090 // preventDefault() on it. 1091 // 3. If the browser is fullscreen and the page isn't loading, exit 1092 // fullscreen. 1093 // 4. Ignore. 1094 // Case 1 and 2 are handled before we get here. Below, we handle case 3. 1095 if (StaticPrefs::browser_fullscreen_exit_on_escape() && [cocoaEvent keyCode] == kVK_Escape && 1096 [[mView window] styleMask] & NSWindowStyleMaskFullScreen) { 1097 [[mView window] toggleFullScreen:nil]; 1098 } 1099 1100 if (SendEventToNativeMenuSystem(cocoaEvent)) { 1101 aEvent->PreventDefault(); 1102 } 1103 [nativeKeyEventsMap removeObjectForKey:@(aEvent->mUniqueId)]; 1104 1105 NS_OBJC_END_TRY_IGNORE_BLOCK; 1106} 1107 1108// Used for testing native menu system structure and event handling. 1109nsresult nsChildView::ActivateNativeMenuItemAt(const nsAString& indexString) { 1110 NS_OBJC_BEGIN_TRY_BLOCK_RETURN; 1111 1112 nsMenuUtilsX::CheckNativeMenuConsistency([NSApp mainMenu]); 1113 1114 NSString* locationString = 1115 [NSString stringWithCharacters:reinterpret_cast<const unichar*>(indexString.BeginReading()) 1116 length:indexString.Length()]; 1117 NSMenuItem* item = 1118 nsMenuUtilsX::NativeMenuItemWithLocation([NSApp mainMenu], locationString, true); 1119 // We can't perform an action on an item with a submenu, that will raise 1120 // an obj-c exception. 1121 if (item && ![item hasSubmenu]) { 1122 NSMenu* parent = [item menu]; 1123 if (parent) { 1124 // NSLog(@"Performing action for native menu item titled: %@\n", 1125 // [[currentSubmenu itemAtIndex:targetIndex] title]); 1126 [parent performActionForItemAtIndex:[parent indexOfItem:item]]; 1127 return NS_OK; 1128 } 1129 } 1130 return NS_ERROR_FAILURE; 1131 1132 NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE); 1133} 1134 1135// Used for testing native menu system structure and event handling. 1136nsresult nsChildView::ForceUpdateNativeMenuAt(const nsAString& indexString) { 1137 NS_OBJC_BEGIN_TRY_BLOCK_RETURN; 1138 1139 nsCocoaWindow* widget = GetAppWindowWidget(); 1140 if (widget) { 1141 nsMenuBarX* mb = widget->GetMenuBar(); 1142 if (mb) { 1143 if (indexString.IsEmpty()) 1144 mb->ForceNativeMenuReload(); 1145 else 1146 mb->ForceUpdateNativeMenuAt(indexString); 1147 } 1148 } 1149 return NS_OK; 1150 1151 NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE); 1152} 1153 1154#pragma mark - 1155 1156#ifdef INVALIDATE_DEBUGGING 1157 1158static Boolean KeyDown(const UInt8 theKey) { 1159 KeyMap map; 1160 GetKeys(map); 1161 return ((*((UInt8*)map + (theKey >> 3)) >> (theKey & 7)) & 1) != 0; 1162} 1163 1164static Boolean caps_lock() { return KeyDown(0x39); } 1165 1166static void blinkRect(Rect* r) { 1167 StRegionFromPool oldClip; 1168 if (oldClip != NULL) ::GetClip(oldClip); 1169 1170 ::ClipRect(r); 1171 ::InvertRect(r); 1172 UInt32 end = ::TickCount() + 5; 1173 while (::TickCount() < end) 1174 ; 1175 ::InvertRect(r); 1176 1177 if (oldClip != NULL) ::SetClip(oldClip); 1178} 1179 1180static void blinkRgn(RgnHandle rgn) { 1181 StRegionFromPool oldClip; 1182 if (oldClip != NULL) ::GetClip(oldClip); 1183 1184 ::SetClip(rgn); 1185 ::InvertRgn(rgn); 1186 UInt32 end = ::TickCount() + 5; 1187 while (::TickCount() < end) 1188 ; 1189 ::InvertRgn(rgn); 1190 1191 if (oldClip != NULL) ::SetClip(oldClip); 1192} 1193 1194#endif 1195 1196// Invalidate this component's visible area 1197void nsChildView::Invalidate(const LayoutDeviceIntRect& aRect) { 1198 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; 1199 1200 if (!mView || !mVisible) return; 1201 1202 NS_ASSERTION(GetWindowRenderer()->GetBackendType() != LayersBackend::LAYERS_WR, 1203 "Shouldn't need to invalidate with accelerated OMTC layers!"); 1204 1205 EnsureContentLayerForMainThreadPainting(); 1206 mContentLayerInvalidRegion.OrWith(aRect.Intersect(GetBounds())); 1207 [mView markLayerForDisplay]; 1208 1209 NS_OBJC_END_TRY_IGNORE_BLOCK; 1210} 1211 1212bool nsChildView::WidgetTypeSupportsAcceleration() { 1213 // All widget types support acceleration. 1214 return true; 1215} 1216 1217bool nsChildView::ShouldUseOffMainThreadCompositing() { 1218 // We need to enable OMTC in popups which contain remote layer 1219 // trees, since the remote content won't be rendered at all otherwise. 1220 if (HasRemoteContent()) { 1221 return true; 1222 } 1223 1224 // Don't use OMTC for popup windows, because we do not want context menus to 1225 // pay the overhead of starting up a compositor. With the OpenGL compositor, 1226 // new windows are expensive because of shader re-compilation, and with 1227 // WebRender, new windows are expensive because they create their own threads 1228 // and texture caches. 1229 // Using OMTC with BasicCompositor for context menus would probably be fine 1230 // but isn't a well-tested configuration. 1231 if ([mView window] && [[mView window] isKindOfClass:[PopupWindow class]]) { 1232 // Use main-thread BasicLayerManager for drawing menus. 1233 return false; 1234 } 1235 1236 return nsBaseWidget::ShouldUseOffMainThreadCompositing(); 1237} 1238 1239#pragma mark - 1240 1241// Invokes callback and ProcessEvent methods on Event Listener object 1242nsresult nsChildView::DispatchEvent(WidgetGUIEvent* event, nsEventStatus& aStatus) { 1243 RefPtr<nsChildView> kungFuDeathGrip(this); 1244 1245#ifdef DEBUG 1246 debug_DumpEvent(stdout, event->mWidget, event, "something", 0); 1247#endif 1248 1249 if (event->mFlags.mIsSynthesizedForTests) { 1250 WidgetKeyboardEvent* keyEvent = event->AsKeyboardEvent(); 1251 if (keyEvent) { 1252 nsresult rv = mTextInputHandler->AttachNativeKeyEvent(*keyEvent); 1253 NS_ENSURE_SUCCESS(rv, rv); 1254 } 1255 } 1256 1257 aStatus = nsEventStatus_eIgnore; 1258 1259 nsIWidgetListener* listener = mWidgetListener; 1260 1261 // If the listener is NULL, check if the parent is a popup. If it is, then 1262 // this child is the popup content view attached to a popup. Get the 1263 // listener from the parent popup instead. 1264 nsCOMPtr<nsIWidget> parentWidget = mParentWidget; 1265 if (!listener && parentWidget) { 1266 if (parentWidget->WindowType() == eWindowType_popup) { 1267 // Check just in case event->mWidget isn't this widget 1268 if (event->mWidget) { 1269 listener = event->mWidget->GetWidgetListener(); 1270 } 1271 if (!listener) { 1272 event->mWidget = parentWidget; 1273 listener = parentWidget->GetWidgetListener(); 1274 } 1275 } 1276 } 1277 1278 if (listener) aStatus = listener->HandleEvent(event, mUseAttachedEvents); 1279 1280 return NS_OK; 1281} 1282 1283nsIWidget* nsChildView::GetWidgetForListenerEvents() { 1284 // If there is no listener, use the parent popup's listener if that exists. 1285 if (!mWidgetListener && mParentWidget && mParentWidget->WindowType() == eWindowType_popup) { 1286 return mParentWidget; 1287 } 1288 1289 return this; 1290} 1291 1292void nsChildView::WillPaintWindow() { 1293 nsCOMPtr<nsIWidget> widget = GetWidgetForListenerEvents(); 1294 1295 nsIWidgetListener* listener = widget->GetWidgetListener(); 1296 if (listener) { 1297 listener->WillPaintWindow(widget); 1298 } 1299} 1300 1301bool nsChildView::PaintWindow(LayoutDeviceIntRegion aRegion) { 1302 nsCOMPtr<nsIWidget> widget = GetWidgetForListenerEvents(); 1303 1304 nsIWidgetListener* listener = widget->GetWidgetListener(); 1305 if (!listener) return false; 1306 1307 bool returnValue = false; 1308 bool oldDispatchPaint = mIsDispatchPaint; 1309 mIsDispatchPaint = true; 1310 returnValue = listener->PaintWindow(widget, aRegion); 1311 1312 listener = widget->GetWidgetListener(); 1313 if (listener) { 1314 listener->DidPaintWindow(); 1315 } 1316 1317 mIsDispatchPaint = oldDispatchPaint; 1318 return returnValue; 1319} 1320 1321bool nsChildView::PaintWindowInDrawTarget(gfx::DrawTarget* aDT, 1322 const LayoutDeviceIntRegion& aRegion, 1323 const gfx::IntSize& aSurfaceSize) { 1324 RefPtr<gfxContext> targetContext = gfxContext::CreateOrNull(aDT); 1325 MOZ_ASSERT(targetContext); 1326 1327 // Set up the clip region and clear existing contents in the backing surface. 1328 targetContext->NewPath(); 1329 for (auto iter = aRegion.RectIter(); !iter.Done(); iter.Next()) { 1330 const LayoutDeviceIntRect& r = iter.Get(); 1331 targetContext->Rectangle(gfxRect(r.x, r.y, r.width, r.height)); 1332 aDT->ClearRect(gfx::Rect(r.ToUnknownRect())); 1333 } 1334 targetContext->Clip(); 1335 1336 nsAutoRetainCocoaObject kungFuDeathGrip(mView); 1337 if (GetWindowRenderer()->GetBackendType() == LayersBackend::LAYERS_NONE) { 1338 nsBaseWidget::AutoLayerManagerSetup setupLayerManager(this, targetContext, 1339 BufferMode::BUFFER_NONE); 1340 return PaintWindow(aRegion); 1341 } 1342 return false; 1343} 1344 1345void nsChildView::EnsureContentLayerForMainThreadPainting() { 1346 // Ensure we have an mContentLayer of the correct size. 1347 // The content layer gets created on demand for BasicLayers windows. We do 1348 // not create it during widget creation because, for non-BasicLayers windows, 1349 // the compositing layer manager will create any layers it needs. 1350 gfx::IntSize size = GetBounds().Size().ToUnknownSize(); 1351 if (mContentLayer && mContentLayer->GetSize() != size) { 1352 mNativeLayerRoot->RemoveLayer(mContentLayer); 1353 mContentLayer = nullptr; 1354 } 1355 if (!mContentLayer) { 1356 mPoolHandle = SurfacePool::Create(0)->GetHandleForGL(nullptr); 1357 RefPtr<NativeLayer> contentLayer = mNativeLayerRoot->CreateLayer(size, false, mPoolHandle); 1358 mNativeLayerRoot->AppendLayer(contentLayer); 1359 mContentLayer = contentLayer->AsNativeLayerCA(); 1360 mContentLayer->SetSurfaceIsFlipped(false); 1361 mContentLayerInvalidRegion = GetBounds(); 1362 } 1363} 1364 1365void nsChildView::PaintWindowInContentLayer() { 1366 EnsureContentLayerForMainThreadPainting(); 1367 mPoolHandle->OnBeginFrame(); 1368 RefPtr<DrawTarget> dt = mContentLayer->NextSurfaceAsDrawTarget( 1369 gfx::IntRect({}, mContentLayer->GetSize()), mContentLayerInvalidRegion.ToUnknownRegion(), 1370 gfx::BackendType::SKIA); 1371 if (!dt) { 1372 return; 1373 } 1374 1375 PaintWindowInDrawTarget(dt, mContentLayerInvalidRegion, dt->GetSize()); 1376 mContentLayer->NotifySurfaceReady(); 1377 mContentLayerInvalidRegion.SetEmpty(); 1378 mPoolHandle->OnEndFrame(); 1379} 1380 1381void nsChildView::HandleMainThreadCATransaction() { 1382 WillPaintWindow(); 1383 1384 if (GetWindowRenderer()->GetBackendType() == LayersBackend::LAYERS_NONE) { 1385 // We're in BasicLayers mode, i.e. main thread software compositing. 1386 // Composite the window into our layer's surface. 1387 PaintWindowInContentLayer(); 1388 } else { 1389 // Trigger a synchronous OMTC composite. This will call NextSurface and 1390 // NotifySurfaceReady on the compositor thread to update mNativeLayerRoot's 1391 // contents, and the main thread (this thread) will wait inside PaintWindow 1392 // during that time. 1393 PaintWindow(LayoutDeviceIntRegion(GetBounds())); 1394 } 1395 1396 { 1397 // Apply the changes inside mNativeLayerRoot to the underlying CALayers. Now is a 1398 // good time to call this because we know we're currently inside a main thread 1399 // CATransaction, and the lock makes sure that no composition is currently in 1400 // progress, so we won't present half-composited state to the screen. 1401 MutexAutoLock lock(mCompositingLock); 1402 mNativeLayerRoot->CommitToScreen(); 1403 } 1404 1405 MaybeScheduleUnsuspendAsyncCATransactions(); 1406} 1407 1408#pragma mark - 1409 1410void nsChildView::ReportMoveEvent() { NotifyWindowMoved(mBounds.x, mBounds.y); } 1411 1412void nsChildView::ReportSizeEvent() { 1413 if (mWidgetListener) mWidgetListener->WindowResized(this, mBounds.width, mBounds.height); 1414} 1415 1416#pragma mark - 1417 1418LayoutDeviceIntPoint nsChildView::GetClientOffset() { 1419 NS_OBJC_BEGIN_TRY_BLOCK_RETURN; 1420 1421 NSPoint origin = [mView convertPoint:NSMakePoint(0, 0) toView:nil]; 1422 origin.y = [[mView window] frame].size.height - origin.y; 1423 return CocoaPointsToDevPixels(origin); 1424 1425 NS_OBJC_END_TRY_BLOCK_RETURN(LayoutDeviceIntPoint(0, 0)); 1426} 1427 1428// Return the offset between this child view and the screen. 1429// @return -- widget origin in device-pixel coords 1430LayoutDeviceIntPoint nsChildView::WidgetToScreenOffset() { 1431 NS_OBJC_BEGIN_TRY_BLOCK_RETURN; 1432 1433 NSPoint origin = NSMakePoint(0, 0); 1434 1435 // 1. First translate view origin point into window coords. 1436 // The returned point is in bottom-left coordinates. 1437 origin = [mView convertPoint:origin toView:nil]; 1438 1439 // 2. We turn the window-coord rect's origin into screen (still bottom-left) coords. 1440 origin = nsCocoaUtils::ConvertPointToScreen([mView window], origin); 1441 1442 // 3. Since we're dealing in bottom-left coords, we need to make it top-left coords 1443 // before we pass it back to Gecko. 1444 FlipCocoaScreenCoordinate(origin); 1445 1446 // convert to device pixels 1447 return CocoaPointsToDevPixels(origin); 1448 1449 NS_OBJC_END_TRY_BLOCK_RETURN(LayoutDeviceIntPoint(0, 0)); 1450} 1451 1452nsresult nsChildView::SetTitle(const nsAString& title) { 1453 // child views don't have titles 1454 return NS_OK; 1455} 1456 1457nsresult nsChildView::GetAttention(int32_t aCycleCount) { 1458 NS_OBJC_BEGIN_TRY_BLOCK_RETURN; 1459 1460 [NSApp requestUserAttention:NSInformationalRequest]; 1461 return NS_OK; 1462 1463 NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE); 1464} 1465 1466/* static */ 1467bool nsChildView::DoHasPendingInputEvent() { 1468 return sLastInputEventCount != GetCurrentInputEventCount(); 1469} 1470 1471/* static */ 1472uint32_t nsChildView::GetCurrentInputEventCount() { 1473 // Can't use kCGAnyInputEventType because that updates too rarely for us (and 1474 // always in increments of 30+!) and because apparently it's sort of broken 1475 // on Tiger. So just go ahead and query the counters we care about. 1476 static const CGEventType eventTypes[] = { 1477 kCGEventLeftMouseDown, kCGEventLeftMouseUp, kCGEventRightMouseDown, 1478 kCGEventRightMouseUp, kCGEventMouseMoved, kCGEventLeftMouseDragged, 1479 kCGEventRightMouseDragged, kCGEventKeyDown, kCGEventKeyUp, 1480 kCGEventScrollWheel, kCGEventTabletPointer, kCGEventOtherMouseDown, 1481 kCGEventOtherMouseUp, kCGEventOtherMouseDragged}; 1482 1483 uint32_t eventCount = 0; 1484 for (uint32_t i = 0; i < ArrayLength(eventTypes); ++i) { 1485 eventCount += 1486 CGEventSourceCounterForEventType(kCGEventSourceStateCombinedSessionState, eventTypes[i]); 1487 } 1488 return eventCount; 1489} 1490 1491/* static */ 1492void nsChildView::UpdateCurrentInputEventCount() { 1493 sLastInputEventCount = GetCurrentInputEventCount(); 1494} 1495 1496bool nsChildView::HasPendingInputEvent() { return DoHasPendingInputEvent(); } 1497 1498#pragma mark - 1499 1500void nsChildView::SetInputContext(const InputContext& aContext, const InputContextAction& aAction) { 1501 NS_ENSURE_TRUE_VOID(mTextInputHandler); 1502 1503 if (mTextInputHandler->IsFocused()) { 1504 if (aContext.IsPasswordEditor()) { 1505 TextInputHandler::EnableSecureEventInput(); 1506 } else { 1507 TextInputHandler::EnsureSecureEventInputDisabled(); 1508 } 1509 } 1510 1511 // IMEInputHandler::IsEditableContent() returns false when both 1512 // IsASCIICableOnly() and IsIMEEnabled() return false. So, be careful 1513 // when you change the following code. You might need to change 1514 // IMEInputHandler::IsEditableContent() too. 1515 mInputContext = aContext; 1516 switch (aContext.mIMEState.mEnabled) { 1517 case IMEEnabled::Enabled: 1518 mTextInputHandler->SetASCIICapableOnly(false); 1519 mTextInputHandler->EnableIME(true); 1520 if (mInputContext.mIMEState.mOpen != IMEState::DONT_CHANGE_OPEN_STATE) { 1521 mTextInputHandler->SetIMEOpenState(mInputContext.mIMEState.mOpen == IMEState::OPEN); 1522 } 1523 break; 1524 case IMEEnabled::Disabled: 1525 mTextInputHandler->SetASCIICapableOnly(false); 1526 mTextInputHandler->EnableIME(false); 1527 break; 1528 case IMEEnabled::Password: 1529 mTextInputHandler->SetASCIICapableOnly(true); 1530 mTextInputHandler->EnableIME(false); 1531 break; 1532 default: 1533 NS_ERROR("not implemented!"); 1534 } 1535} 1536 1537InputContext nsChildView::GetInputContext() { 1538 switch (mInputContext.mIMEState.mEnabled) { 1539 case IMEEnabled::Enabled: 1540 if (mTextInputHandler) { 1541 mInputContext.mIMEState.mOpen = 1542 mTextInputHandler->IsIMEOpened() ? IMEState::OPEN : IMEState::CLOSED; 1543 break; 1544 } 1545 // If mTextInputHandler is null, set CLOSED instead... 1546 [[fallthrough]]; 1547 default: 1548 mInputContext.mIMEState.mOpen = IMEState::CLOSED; 1549 break; 1550 } 1551 return mInputContext; 1552} 1553 1554TextEventDispatcherListener* nsChildView::GetNativeTextEventDispatcherListener() { 1555 if (NS_WARN_IF(!mTextInputHandler)) { 1556 return nullptr; 1557 } 1558 return mTextInputHandler; 1559} 1560 1561nsresult nsChildView::AttachNativeKeyEvent(mozilla::WidgetKeyboardEvent& aEvent) { 1562 NS_ENSURE_TRUE(mTextInputHandler, NS_ERROR_NOT_AVAILABLE); 1563 return mTextInputHandler->AttachNativeKeyEvent(aEvent); 1564} 1565 1566bool nsChildView::GetEditCommands(NativeKeyBindingsType aType, const WidgetKeyboardEvent& aEvent, 1567 nsTArray<CommandInt>& aCommands) { 1568 // Validate the arguments. 1569 if (NS_WARN_IF(!nsIWidget::GetEditCommands(aType, aEvent, aCommands))) { 1570 return false; 1571 } 1572 1573 Maybe<WritingMode> writingMode; 1574 if (aEvent.NeedsToRemapNavigationKey()) { 1575 if (RefPtr<TextEventDispatcher> dispatcher = GetTextEventDispatcher()) { 1576 writingMode = dispatcher->MaybeQueryWritingModeAtSelection(); 1577 } 1578 } 1579 1580 NativeKeyBindings* keyBindings = NativeKeyBindings::GetInstance(aType); 1581 keyBindings->GetEditCommands(aEvent, writingMode, aCommands); 1582 return true; 1583} 1584 1585NSView<mozView>* nsChildView::GetEditorView() { 1586 NSView<mozView>* editorView = mView; 1587 // We need to get editor's view. E.g., when the focus is in the bookmark 1588 // dialog, the view is <panel> element of the dialog. At this time, the key 1589 // events are processed the parent window's view that has native focus. 1590 WidgetQueryContentEvent queryContentState(true, eQueryContentState, this); 1591 // This may be called during creating a menu popup frame due to creating 1592 // widget synchronously and that causes Cocoa asking current window level. 1593 // In this case, it's not safe to flush layout on the document and we don't 1594 // need any layout information right now. 1595 queryContentState.mNeedsToFlushLayout = false; 1596 DispatchWindowEvent(queryContentState); 1597 if (queryContentState.Succeeded() && queryContentState.mReply->mFocusedWidget) { 1598 NSView<mozView>* view = static_cast<NSView<mozView>*>( 1599 queryContentState.mReply->mFocusedWidget->GetNativeData(NS_NATIVE_WIDGET)); 1600 if (view) editorView = view; 1601 } 1602 return editorView; 1603} 1604 1605#pragma mark - 1606 1607void nsChildView::CreateCompositor() { 1608 nsBaseWidget::CreateCompositor(); 1609 if (mCompositorBridgeChild) { 1610 [mView setUsingOMTCompositor:true]; 1611 } 1612} 1613 1614void nsChildView::ConfigureAPZCTreeManager() { nsBaseWidget::ConfigureAPZCTreeManager(); } 1615 1616void nsChildView::ConfigureAPZControllerThread() { nsBaseWidget::ConfigureAPZControllerThread(); } 1617 1618bool nsChildView::PreRender(WidgetRenderingContext* aContext) { 1619 // The lock makes sure that we don't attempt to tear down the view while 1620 // compositing. That would make us unable to call postRender on it when the 1621 // composition is done, thus keeping the GL context locked forever. 1622 mCompositingLock.Lock(); 1623 1624 if (aContext->mGL && gfxPlatform::CanMigrateMacGPUs()) { 1625 GLContextCGL::Cast(aContext->mGL)->MigrateToActiveGPU(); 1626 } 1627 1628 return true; 1629} 1630 1631void nsChildView::PostRender(WidgetRenderingContext* aContext) { mCompositingLock.Unlock(); } 1632 1633RefPtr<layers::NativeLayerRoot> nsChildView::GetNativeLayerRoot() { return mNativeLayerRoot; } 1634 1635static int32_t FindTitlebarBottom(const nsTArray<nsIWidget::ThemeGeometry>& aThemeGeometries, 1636 int32_t aWindowWidth) { 1637 int32_t titlebarBottom = 0; 1638 for (auto& g : aThemeGeometries) { 1639 if ((g.mType == eThemeGeometryTypeTitlebar || 1640 g.mType == eThemeGeometryTypeVibrantTitlebarLight || 1641 g.mType == eThemeGeometryTypeVibrantTitlebarDark) && 1642 g.mRect.X() <= 0 && g.mRect.XMost() >= aWindowWidth && g.mRect.Y() <= 0) { 1643 titlebarBottom = std::max(titlebarBottom, g.mRect.YMost()); 1644 } 1645 } 1646 return titlebarBottom; 1647} 1648 1649static int32_t FindUnifiedToolbarBottom(const nsTArray<nsIWidget::ThemeGeometry>& aThemeGeometries, 1650 int32_t aWindowWidth, int32_t aTitlebarBottom) { 1651 int32_t unifiedToolbarBottom = aTitlebarBottom; 1652 for (uint32_t i = 0; i < aThemeGeometries.Length(); ++i) { 1653 const nsIWidget::ThemeGeometry& g = aThemeGeometries[i]; 1654 if ((g.mType == eThemeGeometryTypeToolbar) && g.mRect.X() <= 0 && 1655 g.mRect.XMost() >= aWindowWidth && g.mRect.Y() <= aTitlebarBottom) { 1656 unifiedToolbarBottom = std::max(unifiedToolbarBottom, g.mRect.YMost()); 1657 } 1658 } 1659 return unifiedToolbarBottom; 1660} 1661 1662static LayoutDeviceIntRect FindFirstRectOfType( 1663 const nsTArray<nsIWidget::ThemeGeometry>& aThemeGeometries, 1664 nsITheme::ThemeGeometryType aThemeGeometryType) { 1665 for (uint32_t i = 0; i < aThemeGeometries.Length(); ++i) { 1666 const nsIWidget::ThemeGeometry& g = aThemeGeometries[i]; 1667 if (g.mType == aThemeGeometryType) { 1668 return g.mRect; 1669 } 1670 } 1671 return LayoutDeviceIntRect(); 1672} 1673 1674void nsChildView::UpdateThemeGeometries(const nsTArray<ThemeGeometry>& aThemeGeometries) { 1675 if (![mView window]) return; 1676 1677 UpdateVibrancy(aThemeGeometries); 1678 1679 if (![[mView window] isKindOfClass:[ToolbarWindow class]]) return; 1680 1681 // Update unified toolbar height and sheet attachment position. 1682 int32_t windowWidth = mBounds.width; 1683 int32_t titlebarBottom = FindTitlebarBottom(aThemeGeometries, windowWidth); 1684 int32_t unifiedToolbarBottom = 1685 FindUnifiedToolbarBottom(aThemeGeometries, windowWidth, titlebarBottom); 1686 int32_t toolboxBottom = FindFirstRectOfType(aThemeGeometries, eThemeGeometryTypeToolbox).YMost(); 1687 1688 ToolbarWindow* win = (ToolbarWindow*)[mView window]; 1689 int32_t titlebarHeight = 1690 [win drawsContentsIntoWindowFrame] ? 0 : CocoaPointsToDevPixels([win titlebarHeight]); 1691 int32_t devUnifiedHeight = titlebarHeight + unifiedToolbarBottom; 1692 [win setUnifiedToolbarHeight:DevPixelsToCocoaPoints(devUnifiedHeight)]; 1693 1694 int32_t sheetPositionDevPx = std::max(toolboxBottom, unifiedToolbarBottom); 1695 NSPoint sheetPositionView = {0, DevPixelsToCocoaPoints(sheetPositionDevPx)}; 1696 NSPoint sheetPositionWindow = [mView convertPoint:sheetPositionView toView:nil]; 1697 [win setSheetAttachmentPosition:sheetPositionWindow.y]; 1698 1699 // Update titlebar control offsets. 1700 LayoutDeviceIntRect windowButtonRect = 1701 FindFirstRectOfType(aThemeGeometries, eThemeGeometryTypeWindowButtons); 1702 [win placeWindowButtons:[mView convertRect:DevPixelsToCocoaPoints(windowButtonRect) toView:nil]]; 1703} 1704 1705static Maybe<VibrancyType> ThemeGeometryTypeToVibrancyType( 1706 nsITheme::ThemeGeometryType aThemeGeometryType) { 1707 switch (aThemeGeometryType) { 1708 case eThemeGeometryTypeVibrantTitlebarLight: 1709 return Some(VibrancyType::TITLEBAR_LIGHT); 1710 case eThemeGeometryTypeVibrantTitlebarDark: 1711 return Some(VibrancyType::TITLEBAR_DARK); 1712 case eThemeGeometryTypeTooltip: 1713 return Some(VibrancyType::TOOLTIP); 1714 case eThemeGeometryTypeMenu: 1715 return Some(VibrancyType::MENU); 1716 case eThemeGeometryTypeHighlightedMenuItem: 1717 return Some(VibrancyType::HIGHLIGHTED_MENUITEM); 1718 case eThemeGeometryTypeSourceList: 1719 return Some(VibrancyType::SOURCE_LIST); 1720 case eThemeGeometryTypeSourceListSelection: 1721 return Some(VibrancyType::SOURCE_LIST_SELECTION); 1722 case eThemeGeometryTypeActiveSourceListSelection: 1723 return Some(VibrancyType::ACTIVE_SOURCE_LIST_SELECTION); 1724 default: 1725 return Nothing(); 1726 } 1727} 1728 1729static LayoutDeviceIntRegion GatherVibrantRegion( 1730 const nsTArray<nsIWidget::ThemeGeometry>& aThemeGeometries, VibrancyType aVibrancyType) { 1731 LayoutDeviceIntRegion region; 1732 for (auto& geometry : aThemeGeometries) { 1733 if (ThemeGeometryTypeToVibrancyType(geometry.mType) == Some(aVibrancyType)) { 1734 region.OrWith(geometry.mRect); 1735 } 1736 } 1737 return region; 1738} 1739 1740template <typename Region> 1741static void MakeRegionsNonOverlappingImpl(Region& aOutUnion) {} 1742 1743template <typename Region, typename... Regions> 1744static void MakeRegionsNonOverlappingImpl(Region& aOutUnion, Region& aFirst, Regions&... aRest) { 1745 MakeRegionsNonOverlappingImpl(aOutUnion, aRest...); 1746 aFirst.SubOut(aOutUnion); 1747 aOutUnion.OrWith(aFirst); 1748} 1749 1750// Subtracts parts from regions in such a way that they don't have any overlap. 1751// Each region in the argument list will have the union of all the regions 1752// *following* it subtracted from itself. In other words, the arguments are 1753// sorted low priority to high priority. 1754template <typename Region, typename... Regions> 1755static void MakeRegionsNonOverlapping(Region& aFirst, Regions&... aRest) { 1756 Region unionOfAll; 1757 MakeRegionsNonOverlappingImpl(unionOfAll, aFirst, aRest...); 1758} 1759 1760void nsChildView::UpdateVibrancy(const nsTArray<ThemeGeometry>& aThemeGeometries) { 1761 LayoutDeviceIntRegion vibrantTitlebarLightRegion = 1762 GatherVibrantRegion(aThemeGeometries, VibrancyType::TITLEBAR_LIGHT); 1763 LayoutDeviceIntRegion vibrantTitlebarDarkRegion = 1764 GatherVibrantRegion(aThemeGeometries, VibrancyType::TITLEBAR_DARK); 1765 LayoutDeviceIntRegion menuRegion = GatherVibrantRegion(aThemeGeometries, VibrancyType::MENU); 1766 LayoutDeviceIntRegion tooltipRegion = 1767 GatherVibrantRegion(aThemeGeometries, VibrancyType::TOOLTIP); 1768 LayoutDeviceIntRegion highlightedMenuItemRegion = 1769 GatherVibrantRegion(aThemeGeometries, VibrancyType::HIGHLIGHTED_MENUITEM); 1770 LayoutDeviceIntRegion sourceListRegion = 1771 GatherVibrantRegion(aThemeGeometries, VibrancyType::SOURCE_LIST); 1772 LayoutDeviceIntRegion sourceListSelectionRegion = 1773 GatherVibrantRegion(aThemeGeometries, VibrancyType::SOURCE_LIST_SELECTION); 1774 LayoutDeviceIntRegion activeSourceListSelectionRegion = 1775 GatherVibrantRegion(aThemeGeometries, VibrancyType::ACTIVE_SOURCE_LIST_SELECTION); 1776 1777 MakeRegionsNonOverlapping(vibrantTitlebarLightRegion, vibrantTitlebarDarkRegion, menuRegion, 1778 tooltipRegion, highlightedMenuItemRegion, sourceListRegion, 1779 sourceListSelectionRegion, activeSourceListSelectionRegion); 1780 1781 auto& vm = EnsureVibrancyManager(); 1782 bool changed = false; 1783 changed |= vm.UpdateVibrantRegion(VibrancyType::TITLEBAR_LIGHT, vibrantTitlebarLightRegion); 1784 changed |= vm.UpdateVibrantRegion(VibrancyType::TITLEBAR_DARK, vibrantTitlebarDarkRegion); 1785 changed |= vm.UpdateVibrantRegion(VibrancyType::MENU, menuRegion); 1786 changed |= vm.UpdateVibrantRegion(VibrancyType::TOOLTIP, tooltipRegion); 1787 changed |= vm.UpdateVibrantRegion(VibrancyType::HIGHLIGHTED_MENUITEM, highlightedMenuItemRegion); 1788 changed |= vm.UpdateVibrantRegion(VibrancyType::SOURCE_LIST, sourceListRegion); 1789 changed |= vm.UpdateVibrantRegion(VibrancyType::SOURCE_LIST_SELECTION, sourceListSelectionRegion); 1790 changed |= vm.UpdateVibrantRegion(VibrancyType::ACTIVE_SOURCE_LIST_SELECTION, 1791 activeSourceListSelectionRegion); 1792 1793 if (changed) { 1794 SuspendAsyncCATransactions(); 1795 } 1796} 1797 1798mozilla::VibrancyManager& nsChildView::EnsureVibrancyManager() { 1799 MOZ_ASSERT(mView, "Only call this once we have a view!"); 1800 if (!mVibrancyManager) { 1801 mVibrancyManager = MakeUnique<VibrancyManager>(*this, [mView vibrancyViewsContainer]); 1802 } 1803 return *mVibrancyManager; 1804} 1805 1806void nsChildView::UpdateBoundsFromView() { 1807 auto oldSize = mBounds.Size(); 1808 mBounds = CocoaPointsToDevPixels([mView frame]); 1809 if (mBounds.Size() != oldSize) { 1810 SuspendAsyncCATransactions(); 1811 } 1812} 1813 1814@interface NonDraggableView : NSView 1815@end 1816 1817@implementation NonDraggableView 1818- (BOOL)mouseDownCanMoveWindow { 1819 return NO; 1820} 1821- (NSView*)hitTest:(NSPoint)aPoint { 1822 return nil; 1823} 1824- (NSRect)_opaqueRectForWindowMoveWhenInTitlebar { 1825 // In NSWindows that use NSWindowStyleMaskFullSizeContentView, NSViews which 1826 // overlap the titlebar do not disable window dragging in the overlapping 1827 // areas even if they return NO from mouseDownCanMoveWindow. This can have 1828 // unfortunate effects: For example, dragging tabs in a browser window would 1829 // move the window if those tabs are in the titlebar. 1830 // macOS does not seem to offer a documented way to opt-out of the forced 1831 // window dragging in the titlebar. 1832 // Overriding _opaqueRectForWindowMoveWhenInTitlebar is an undocumented way 1833 // of opting out of this behavior. This method was added in 10.11 and is used 1834 // by some NSControl subclasses to prevent window dragging in the titlebar. 1835 // The function which assembles the draggable area of the window calls 1836 // _opaqueRect for the content area and _opaqueRectForWindowMoveWhenInTitlebar 1837 // for the titlebar area, on all visible NSViews. The default implementation 1838 // of _opaqueRect returns [self visibleRect], and the default implementation 1839 // of _opaqueRectForWindowMoveWhenInTitlebar returns NSZeroRect unless it's 1840 // overridden. 1841 return [self visibleRect]; 1842} 1843@end 1844 1845void nsChildView::UpdateWindowDraggingRegion(const LayoutDeviceIntRegion& aRegion) { 1846 // mView returns YES from mouseDownCanMoveWindow, so we need to put NSViews 1847 // that return NO from mouseDownCanMoveWindow in the places that shouldn't 1848 // be draggable. We can't do it the other way round because returning 1849 // YES from mouseDownCanMoveWindow doesn't have any effect if there's a 1850 // superview that returns NO. 1851 LayoutDeviceIntRegion nonDraggable; 1852 nonDraggable.Sub(LayoutDeviceIntRect(0, 0, mBounds.width, mBounds.height), aRegion); 1853 1854 __block bool changed = false; 1855 1856 // Suppress calls to setNeedsDisplay during NSView geometry changes. 1857 ManipulateViewWithoutNeedingDisplay(mView, ^() { 1858 changed = mNonDraggableRegion.UpdateRegion( 1859 nonDraggable, *this, [mView nonDraggableViewsContainer], ^() { 1860 return [[NonDraggableView alloc] initWithFrame:NSZeroRect]; 1861 }); 1862 }); 1863 1864 if (changed) { 1865 // Trigger an update to the window server. This will call 1866 // mouseDownCanMoveWindow. 1867 // Doing this manually is only necessary because we're suppressing 1868 // setNeedsDisplay calls above. 1869 [[mView window] setMovableByWindowBackground:NO]; 1870 [[mView window] setMovableByWindowBackground:YES]; 1871 } 1872} 1873 1874nsEventStatus nsChildView::DispatchAPZInputEvent(InputData& aEvent) { 1875 APZEventResult result; 1876 1877 if (mAPZC) { 1878 result = mAPZC->InputBridge()->ReceiveInputEvent(aEvent); 1879 } 1880 1881 if (result.GetStatus() == nsEventStatus_eConsumeNoDefault) { 1882 return result.GetStatus(); 1883 } 1884 1885 if (aEvent.mInputType == PINCHGESTURE_INPUT) { 1886 PinchGestureInput& pinchEvent = aEvent.AsPinchGestureInput(); 1887 WidgetWheelEvent wheelEvent = pinchEvent.ToWidgetEvent(this); 1888 ProcessUntransformedAPZEvent(&wheelEvent, result); 1889 } else if (aEvent.mInputType == TAPGESTURE_INPUT) { 1890 TapGestureInput& tapEvent = aEvent.AsTapGestureInput(); 1891 WidgetSimpleGestureEvent gestureEvent = tapEvent.ToWidgetEvent(this); 1892 ProcessUntransformedAPZEvent(&gestureEvent, result); 1893 } else { 1894 MOZ_ASSERT_UNREACHABLE(); 1895 } 1896 1897 return result.GetStatus(); 1898} 1899 1900void nsChildView::DispatchAPZWheelInputEvent(InputData& aEvent, bool aCanTriggerSwipe) { 1901 if (mSwipeTracker && aEvent.mInputType == PANGESTURE_INPUT) { 1902 // Give the swipe tracker a first pass at the event. If a new pan gesture 1903 // has been started since the beginning of the swipe, the swipe tracker 1904 // will know to ignore the event. 1905 nsEventStatus status = mSwipeTracker->ProcessEvent(aEvent.AsPanGestureInput()); 1906 if (status == nsEventStatus_eConsumeNoDefault) { 1907 return; 1908 } 1909 } 1910 1911 WidgetWheelEvent event(true, eWheel, this); 1912 1913 if (mAPZC) { 1914 APZEventResult result; 1915 1916 switch (aEvent.mInputType) { 1917 case PANGESTURE_INPUT: { 1918 result = mAPZC->InputBridge()->ReceiveInputEvent(aEvent); 1919 if (result.GetStatus() == nsEventStatus_eConsumeNoDefault) { 1920 return; 1921 } 1922 1923 event = MayStartSwipeForAPZ(aEvent.AsPanGestureInput(), result, 1924 CanTriggerSwipe{aCanTriggerSwipe}); 1925 break; 1926 } 1927 case SCROLLWHEEL_INPUT: { 1928 // For wheel events on OS X, send it to APZ using the WidgetInputEvent 1929 // variant of ReceiveInputEvent, because the APZInputBridge version of 1930 // that function has special handling (for delta multipliers etc.) that 1931 // we need to run. Using the InputData variant would bypass that and 1932 // go straight to the APZCTreeManager subclass. 1933 event = aEvent.AsScrollWheelInput().ToWidgetEvent(this); 1934 result = mAPZC->InputBridge()->ReceiveInputEvent(event); 1935 if (result.GetStatus() == nsEventStatus_eConsumeNoDefault) { 1936 return; 1937 } 1938 break; 1939 }; 1940 default: 1941 MOZ_CRASH("unsupported event type"); 1942 return; 1943 } 1944 if (event.mMessage == eWheel && (event.mDeltaX != 0 || event.mDeltaY != 0)) { 1945 ProcessUntransformedAPZEvent(&event, result); 1946 } 1947 return; 1948 } 1949 1950 nsEventStatus status; 1951 switch (aEvent.mInputType) { 1952 case PANGESTURE_INPUT: { 1953 if (MayStartSwipeForNonAPZ(aEvent.AsPanGestureInput(), CanTriggerSwipe{aCanTriggerSwipe})) { 1954 return; 1955 } 1956 event = aEvent.AsPanGestureInput().ToWidgetEvent(this); 1957 break; 1958 } 1959 case SCROLLWHEEL_INPUT: { 1960 event = aEvent.AsScrollWheelInput().ToWidgetEvent(this); 1961 break; 1962 } 1963 default: 1964 MOZ_CRASH("unexpected event type"); 1965 return; 1966 } 1967 if (event.mMessage == eWheel && (event.mDeltaX != 0 || event.mDeltaY != 0)) { 1968 DispatchEvent(&event, status); 1969 } 1970} 1971 1972void nsChildView::DispatchDoubleTapGesture(TimeStamp aEventTimeStamp, 1973 LayoutDeviceIntPoint aScreenPosition, 1974 mozilla::Modifiers aModifiers) { 1975 if (StaticPrefs::apz_mac_enable_double_tap_zoom_touchpad_gesture()) { 1976 PRIntervalTime eventIntervalTime = PR_IntervalNow(); 1977 1978 TapGestureInput event{ 1979 TapGestureInput::TAPGESTURE_DOUBLE, eventIntervalTime, aEventTimeStamp, 1980 ViewAs<ScreenPixel>(aScreenPosition, 1981 PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent), 1982 aModifiers}; 1983 1984 DispatchAPZInputEvent(event); 1985 } else { 1986 // Setup the "double tap" event. 1987 WidgetSimpleGestureEvent geckoEvent(true, eTapGesture, this); 1988 // do what convertCocoaMouseEvent does basically. 1989 geckoEvent.mRefPoint = aScreenPosition; 1990 geckoEvent.mModifiers = aModifiers; 1991 geckoEvent.mTime = PR_IntervalNow(); 1992 geckoEvent.mTimeStamp = aEventTimeStamp; 1993 geckoEvent.mClickCount = 1; 1994 1995 // Send the event. 1996 DispatchWindowEvent(geckoEvent); 1997 } 1998} 1999 2000void nsChildView::LookUpDictionary(const nsAString& aText, 2001 const nsTArray<mozilla::FontRange>& aFontRangeArray, 2002 const bool aIsVertical, const LayoutDeviceIntPoint& aPoint) { 2003 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; 2004 2005 NSMutableAttributedString* attrStr = nsCocoaUtils::GetNSMutableAttributedString( 2006 aText, aFontRangeArray, aIsVertical, BackingScaleFactor()); 2007 NSPoint pt = nsCocoaUtils::DevPixelsToCocoaPoints(aPoint, BackingScaleFactor()); 2008 NSDictionary* attributes = [attrStr attributesAtIndex:0 effectiveRange:nil]; 2009 NSFont* font = [attributes objectForKey:NSFontAttributeName]; 2010 if (font) { 2011 if (aIsVertical) { 2012 pt.x -= [font descender]; 2013 } else { 2014 pt.y += [font ascender]; 2015 } 2016 } 2017 2018 [mView showDefinitionForAttributedString:attrStr atPoint:pt]; 2019 2020 NS_OBJC_END_TRY_IGNORE_BLOCK; 2021} 2022 2023#ifdef ACCESSIBILITY 2024already_AddRefed<a11y::LocalAccessible> nsChildView::GetDocumentAccessible() { 2025 if (!mozilla::a11y::ShouldA11yBeEnabled()) return nullptr; 2026 2027 // mAccessible might be dead if accessibility was previously disabled and is 2028 // now being enabled again. 2029 if (mAccessible && mAccessible->IsAlive()) { 2030 RefPtr<a11y::LocalAccessible> ret; 2031 CallQueryReferent(mAccessible.get(), static_cast<a11y::LocalAccessible**>(getter_AddRefs(ret))); 2032 return ret.forget(); 2033 } 2034 2035 // need to fetch the accessible anew, because it has gone away. 2036 // cache the accessible in our weak ptr 2037 RefPtr<a11y::LocalAccessible> acc = GetRootAccessible(); 2038 mAccessible = do_GetWeakReference(acc.get()); 2039 2040 return acc.forget(); 2041} 2042#endif 2043 2044class WidgetsReleaserRunnable final : public mozilla::Runnable { 2045 public: 2046 explicit WidgetsReleaserRunnable(nsTArray<nsCOMPtr<nsIWidget>>&& aWidgetArray) 2047 : mozilla::Runnable("WidgetsReleaserRunnable"), mWidgetArray(std::move(aWidgetArray)) {} 2048 2049 // Do nothing; all this runnable does is hold a reference the widgets in 2050 // mWidgetArray, and those references will be dropped when this runnable 2051 // is destroyed. 2052 2053 private: 2054 nsTArray<nsCOMPtr<nsIWidget>> mWidgetArray; 2055}; 2056 2057#pragma mark - 2058 2059// ViewRegionContainerView is a view class for certain subviews of ChildView 2060// which contain the NSViews created for ViewRegions (see ViewRegion.h). 2061// It doesn't do anything interesting, it only acts as a container so that it's 2062// easier for ChildView to control the z order of its children. 2063@interface ViewRegionContainerView : NSView { 2064} 2065@end 2066 2067@implementation ViewRegionContainerView 2068 2069- (NSView*)hitTest:(NSPoint)aPoint { 2070 return nil; // Be transparent to mouse events. 2071} 2072 2073- (BOOL)isFlipped { 2074 return [[self superview] isFlipped]; 2075} 2076 2077- (BOOL)mouseDownCanMoveWindow { 2078 return [[self superview] mouseDownCanMoveWindow]; 2079} 2080 2081@end 2082 2083@implementation ChildView 2084 2085// globalDragPboard is non-null during native drag sessions that did not originate 2086// in our native NSView (it is set in |draggingEntered:|). It is unset when the 2087// drag session ends for this view, either with the mouse exiting or when a drop 2088// occurs in this view. 2089NSPasteboard* globalDragPboard = nil; 2090 2091// gLastDragView and gLastDragMouseDownEvent are used to communicate information 2092// to the drag service during drag invocation (starting a drag in from the view). 2093// gLastDragView is only non-null while a mouse button is pressed, so between 2094// mouseDown and mouseUp. 2095NSView* gLastDragView = nil; // [weak] 2096NSEvent* gLastDragMouseDownEvent = nil; // [strong] 2097 2098+ (void)initialize { 2099 static BOOL initialized = NO; 2100 2101 if (!initialized) { 2102 // Inform the OS about the types of services (from the "Services" menu) 2103 // that we can handle. 2104 NSArray* types = @[ 2105 [UTIHelper stringFromPboardType:NSPasteboardTypeString], 2106 [UTIHelper stringFromPboardType:NSPasteboardTypeHTML] 2107 ]; 2108 [NSApp registerServicesMenuSendTypes:types returnTypes:types]; 2109 initialized = YES; 2110 } 2111} 2112 2113+ (void)registerViewForDraggedTypes:(NSView*)aView { 2114 [aView 2115 registerForDraggedTypes: 2116 [NSArray 2117 arrayWithObjects:[UTIHelper stringFromPboardType:NSFilenamesPboardType], 2118 [UTIHelper stringFromPboardType:kMozFileUrlsPboardType], 2119 [UTIHelper stringFromPboardType:NSPasteboardTypeString], 2120 [UTIHelper stringFromPboardType:NSPasteboardTypeHTML], 2121 [UTIHelper 2122 stringFromPboardType:(NSString*)kPasteboardTypeFileURLPromise], 2123 [UTIHelper stringFromPboardType:kMozWildcardPboardType], 2124 [UTIHelper stringFromPboardType:kPublicUrlPboardType], 2125 [UTIHelper stringFromPboardType:kPublicUrlNamePboardType], 2126 [UTIHelper stringFromPboardType:kUrlsWithTitlesPboardType], nil]]; 2127} 2128 2129// initWithFrame:geckoChild: 2130- (id)initWithFrame:(NSRect)inFrame geckoChild:(nsChildView*)inChild { 2131 NS_OBJC_BEGIN_TRY_BLOCK_RETURN; 2132 2133 if ((self = [super initWithFrame:inFrame])) { 2134 mGeckoChild = inChild; 2135 mBlockedLastMouseDown = NO; 2136 mExpectingWheelStop = NO; 2137 2138 mLastMouseDownEvent = nil; 2139 mLastKeyDownEvent = nil; 2140 mClickThroughMouseDownEvent = nil; 2141 mDragService = nullptr; 2142 2143 mGestureState = eGestureState_None; 2144 mCumulativeRotation = 0.0; 2145 2146 mIsUpdatingLayer = NO; 2147 2148 [self setFocusRingType:NSFocusRingTypeNone]; 2149 2150#ifdef __LP64__ 2151 mCancelSwipeAnimation = nil; 2152#endif 2153 2154 mNonDraggableViewsContainer = [[ViewRegionContainerView alloc] initWithFrame:[self bounds]]; 2155 mVibrancyViewsContainer = [[ViewRegionContainerView alloc] initWithFrame:[self bounds]]; 2156 2157 [mNonDraggableViewsContainer setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; 2158 [mVibrancyViewsContainer setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; 2159 2160 [self addSubview:mNonDraggableViewsContainer]; 2161 [self addSubview:mVibrancyViewsContainer]; 2162 2163 mPixelHostingView = [[PixelHostingView alloc] initWithFrame:[self bounds]]; 2164 [mPixelHostingView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; 2165 2166 [self addSubview:mPixelHostingView]; 2167 2168 mRootCALayer = [[CALayer layer] retain]; 2169 mRootCALayer.position = NSZeroPoint; 2170 mRootCALayer.bounds = NSZeroRect; 2171 mRootCALayer.anchorPoint = NSZeroPoint; 2172 mRootCALayer.contentsGravity = kCAGravityTopLeft; 2173 [[mPixelHostingView layer] addSublayer:mRootCALayer]; 2174 2175 mLastPressureStage = 0; 2176 } 2177 2178 // register for things we'll take from other applications 2179 [ChildView registerViewForDraggedTypes:self]; 2180 2181 return self; 2182 2183 NS_OBJC_END_TRY_BLOCK_RETURN(nil); 2184} 2185 2186- (NSTextInputContext*)inputContext { 2187 if (!mGeckoChild) { 2188 // -[ChildView widgetDestroyed] has been called, but 2189 // -[ChildView delayedTearDown] has not yet completed. Accessing 2190 // [super inputContext] now would uselessly recreate a text input context 2191 // for us, under which -[ChildView validAttributesForMarkedText] would 2192 // be called and the assertion checking for mTextInputHandler would fail. 2193 // We return nil to avoid that. 2194 return nil; 2195 } 2196 return [super inputContext]; 2197} 2198 2199- (void)installTextInputHandler:(TextInputHandler*)aHandler { 2200 mTextInputHandler = aHandler; 2201} 2202 2203- (void)uninstallTextInputHandler { 2204 mTextInputHandler = nullptr; 2205} 2206 2207- (NSView*)vibrancyViewsContainer { 2208 return mVibrancyViewsContainer; 2209} 2210 2211- (NSView*)nonDraggableViewsContainer { 2212 return mNonDraggableViewsContainer; 2213} 2214 2215- (NSView*)pixelHostingView { 2216 return mPixelHostingView; 2217} 2218 2219- (void)dealloc { 2220 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; 2221 2222 [mLastMouseDownEvent release]; 2223 [mLastKeyDownEvent release]; 2224 [mClickThroughMouseDownEvent release]; 2225 ChildViewMouseTracker::OnDestroyView(self); 2226 2227 [mVibrancyViewsContainer removeFromSuperview]; 2228 [mVibrancyViewsContainer release]; 2229 [mNonDraggableViewsContainer removeFromSuperview]; 2230 [mNonDraggableViewsContainer release]; 2231 [mPixelHostingView removeFromSuperview]; 2232 [mPixelHostingView release]; 2233 [mRootCALayer release]; 2234 2235 if (gLastDragView == self) { 2236 gLastDragView = nil; 2237 } 2238 2239 [super dealloc]; 2240 2241 NS_OBJC_END_TRY_IGNORE_BLOCK; 2242} 2243 2244- (void)widgetDestroyed { 2245 if (mTextInputHandler) { 2246 mTextInputHandler->OnDestroyWidget(mGeckoChild); 2247 mTextInputHandler = nullptr; 2248 } 2249 mGeckoChild = nullptr; 2250 2251 // Just in case we're destroyed abruptly and missed the draggingExited 2252 // or performDragOperation message. 2253 NS_IF_RELEASE(mDragService); 2254} 2255 2256// mozView method, return our gecko child view widget. Note this does not AddRef. 2257- (nsIWidget*)widget { 2258 return static_cast<nsIWidget*>(mGeckoChild); 2259} 2260 2261- (NSString*)description { 2262 NS_OBJC_BEGIN_TRY_BLOCK_RETURN; 2263 2264 return [NSString stringWithFormat:@"ChildView %p, gecko child %p, frame %@", self, mGeckoChild, 2265 NSStringFromRect([self frame])]; 2266 2267 NS_OBJC_END_TRY_BLOCK_RETURN(nil); 2268} 2269 2270// Make the origin of this view the topLeft corner (gecko origin) rather 2271// than the bottomLeft corner (standard cocoa origin). 2272- (BOOL)isFlipped { 2273 return YES; 2274} 2275 2276// We accept key and mouse events, so don't keep passing them up the chain. Allow 2277// this to be a 'focused' widget for event dispatch. 2278- (BOOL)acceptsFirstResponder { 2279 return YES; 2280} 2281 2282// Accept mouse down events on background windows 2283- (BOOL)acceptsFirstMouse:(NSEvent*)aEvent { 2284 if (![[self window] isKindOfClass:[PopupWindow class]]) { 2285 // We rely on this function to tell us that the mousedown was on a 2286 // background window. Inside mouseDown we can't tell whether we were 2287 // inactive because at that point we've already been made active. 2288 // Unfortunately, acceptsFirstMouse is called for PopupWindows even when 2289 // their parent window is active, so ignore this on them for now. 2290 mClickThroughMouseDownEvent = [aEvent retain]; 2291 } 2292 return YES; 2293} 2294 2295- (BOOL)mouseDownCanMoveWindow { 2296 // Return YES so that parts of this view can be draggable. The non-draggable 2297 // parts will be covered by NSViews that return NO from 2298 // mouseDownCanMoveWindow and thus override draggability from the inside. 2299 // These views are assembled in nsChildView::UpdateWindowDraggingRegion. 2300 return YES; 2301} 2302 2303- (void)viewDidChangeBackingProperties { 2304 [super viewDidChangeBackingProperties]; 2305 if (mGeckoChild) { 2306 // actually, it could be the color space that's changed, 2307 // but we can't tell the difference here except by retrieving 2308 // the backing scale factor and comparing to the old value 2309 mGeckoChild->BackingScaleFactorChanged(); 2310 } 2311} 2312 2313- (BOOL)isCoveringTitlebar { 2314 return [[self window] isKindOfClass:[BaseWindow class]] && 2315 [(BaseWindow*)[self window] mainChildView] == self && 2316 [(BaseWindow*)[self window] drawsContentsIntoWindowFrame]; 2317} 2318 2319- (void)viewWillStartLiveResize { 2320 nsCocoaWindow* windowWidget = mGeckoChild ? mGeckoChild->GetAppWindowWidget() : nullptr; 2321 if (windowWidget) { 2322 windowWidget->NotifyLiveResizeStarted(); 2323 } 2324} 2325 2326- (void)viewDidEndLiveResize { 2327 // mGeckoChild may legitimately be null here. It should also have been null 2328 // in viewWillStartLiveResize, so there's no problem. However if we run into 2329 // cases where the windowWidget was non-null in viewWillStartLiveResize but 2330 // is null here, that might be problematic because we might get stuck with 2331 // a content process that has the displayport suppressed. If that scenario 2332 // arises (I'm not sure that it does) we will need to handle it gracefully. 2333 nsCocoaWindow* windowWidget = mGeckoChild ? mGeckoChild->GetAppWindowWidget() : nullptr; 2334 if (windowWidget) { 2335 windowWidget->NotifyLiveResizeStopped(); 2336 } 2337} 2338 2339- (void)markLayerForDisplay { 2340 MOZ_RELEASE_ASSERT(NS_IsMainThread()); 2341 if (!mIsUpdatingLayer) { 2342 // This call will cause updateRootCALayer to be called during the upcoming 2343 // main thread CoreAnimation transaction. It will also trigger a transaction 2344 // if no transaction is currently pending. 2345 [[mPixelHostingView layer] setNeedsDisplay]; 2346 } 2347} 2348 2349- (void)ensureNextCompositeIsAtomicWithMainThreadPaint { 2350 MOZ_RELEASE_ASSERT(NS_IsMainThread()); 2351 if (mGeckoChild) { 2352 mGeckoChild->SuspendAsyncCATransactions(); 2353 } 2354} 2355 2356- (void)updateRootCALayer { 2357 if (NS_IsMainThread() && mGeckoChild) { 2358 MOZ_RELEASE_ASSERT(!mIsUpdatingLayer, "Re-entrant layer display?"); 2359 mIsUpdatingLayer = YES; 2360 mGeckoChild->HandleMainThreadCATransaction(); 2361 mIsUpdatingLayer = NO; 2362 } 2363} 2364 2365- (CALayer*)rootCALayer { 2366 return mRootCALayer; 2367} 2368 2369// If we've just created a non-native context menu, we need to mark it as 2370// such and let the OS (and other programs) know when it opens and closes 2371// (this is how the OS knows to close other programs' context menus when 2372// ours open). We send the initial notification here, but others are sent 2373// in nsCocoaWindow::Show(). 2374- (void)maybeInitContextMenuTracking { 2375 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; 2376 2377 if (mozilla::widget::NativeMenuSupport::ShouldUseNativeContextMenus()) { 2378 return; 2379 } 2380 2381 nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener(); 2382 NS_ENSURE_TRUE_VOID(rollupListener); 2383 nsCOMPtr<nsIWidget> widget = rollupListener->GetRollupWidget(); 2384 NS_ENSURE_TRUE_VOID(widget); 2385 2386 NSWindow* popupWindow = (NSWindow*)widget->GetNativeData(NS_NATIVE_WINDOW); 2387 if (!popupWindow || ![popupWindow isKindOfClass:[PopupWindow class]]) return; 2388 2389 [[NSDistributedNotificationCenter defaultCenter] 2390 postNotificationName:@"com.apple.HIToolbox.beginMenuTrackingNotification" 2391 object:@"org.mozilla.gecko.PopupWindow"]; 2392 [(PopupWindow*)popupWindow setIsContextMenu:YES]; 2393 2394 NS_OBJC_END_TRY_IGNORE_BLOCK; 2395} 2396 2397// Returns true if the event should no longer be processed, false otherwise. 2398// This does not return whether or not anything was rolled up. 2399- (BOOL)maybeRollup:(NSEvent*)theEvent { 2400 NS_OBJC_BEGIN_TRY_BLOCK_RETURN; 2401 2402 BOOL consumeEvent = NO; 2403 2404 nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener(); 2405 NS_ENSURE_TRUE(rollupListener, false); 2406 2407 BOOL isWheelTypeEvent = [theEvent type] == NSEventTypeScrollWheel || 2408 [theEvent type] == NSEventTypeMagnify || 2409 [theEvent type] == NSEventTypeSmartMagnify; 2410 2411 if (!isWheelTypeEvent && rollupListener->RollupNativeMenu()) { 2412 // A native menu was rolled up. 2413 // Don't consume this event; if the menu wanted to consume this event it would already have done 2414 // so and we wouldn't even get here. For example, we won't get here for left clicks that close 2415 // native menus (because the native menu consumes it), but we will get here for right clicks 2416 // that close native menus, and we do not want to consume those right clicks. 2417 return NO; 2418 } 2419 2420 nsCOMPtr<nsIWidget> rollupWidget = rollupListener->GetRollupWidget(); 2421 if (rollupWidget) { 2422 NSWindow* currentPopup = static_cast<NSWindow*>(rollupWidget->GetNativeData(NS_NATIVE_WINDOW)); 2423 if (!nsCocoaUtils::IsEventOverWindow(theEvent, currentPopup)) { 2424 // event is not over the rollup window, default is to roll up 2425 bool shouldRollup = true; 2426 2427 // check to see if scroll/zoom events should roll up the popup 2428 if (isWheelTypeEvent) { 2429 shouldRollup = rollupListener->ShouldRollupOnMouseWheelEvent(); 2430 // consume scroll events that aren't over the popup 2431 // unless the popup is an arrow panel 2432 consumeEvent = rollupListener->ShouldConsumeOnMouseWheelEvent(); 2433 } 2434 2435 // if we're dealing with menus, we probably have submenus and 2436 // we don't want to rollup if the click is in a parent menu of 2437 // the current submenu 2438 uint32_t popupsToRollup = UINT32_MAX; 2439 AutoTArray<nsIWidget*, 5> widgetChain; 2440 uint32_t sameTypeCount = rollupListener->GetSubmenuWidgetChain(&widgetChain); 2441 for (uint32_t i = 0; i < widgetChain.Length(); i++) { 2442 nsIWidget* widget = widgetChain[i]; 2443 NSWindow* currWindow = (NSWindow*)widget->GetNativeData(NS_NATIVE_WINDOW); 2444 if (nsCocoaUtils::IsEventOverWindow(theEvent, currWindow)) { 2445 // don't roll up if the mouse event occurred within a menu of the 2446 // same type. If the mouse event occurred in a menu higher than 2447 // that, roll up, but pass the number of popups to Rollup so 2448 // that only those of the same type close up. 2449 if (i < sameTypeCount) { 2450 shouldRollup = false; 2451 } else { 2452 popupsToRollup = sameTypeCount; 2453 } 2454 break; 2455 } 2456 } 2457 2458 if (shouldRollup) { 2459 if ([theEvent type] == NSEventTypeLeftMouseDown) { 2460 NSPoint point = [NSEvent mouseLocation]; 2461 FlipCocoaScreenCoordinate(point); 2462 LayoutDeviceIntPoint devPoint = mGeckoChild->CocoaPointsToDevPixels(point); 2463 consumeEvent = (BOOL)rollupListener->Rollup(popupsToRollup, true, &devPoint, nullptr); 2464 } else { 2465 consumeEvent = (BOOL)rollupListener->Rollup(popupsToRollup, true, nullptr, nullptr); 2466 } 2467 } 2468 } 2469 } 2470 2471 return consumeEvent; 2472 2473 NS_OBJC_END_TRY_BLOCK_RETURN(NO); 2474} 2475 2476- (void)swipeWithEvent:(NSEvent*)anEvent { 2477 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; 2478 2479 if (!anEvent || !mGeckoChild) { 2480 return; 2481 } 2482 2483 nsAutoRetainCocoaObject kungFuDeathGrip(self); 2484 2485 float deltaX = [anEvent deltaX]; // left=1.0, right=-1.0 2486 float deltaY = [anEvent deltaY]; // up=1.0, down=-1.0 2487 2488 // Setup the "swipe" event. 2489 WidgetSimpleGestureEvent geckoEvent(true, eSwipeGesture, mGeckoChild); 2490 [self convertCocoaMouseEvent:anEvent toGeckoEvent:&geckoEvent]; 2491 2492 // Record the left/right direction. 2493 if (deltaX > 0.0) 2494 geckoEvent.mDirection |= dom::SimpleGestureEvent_Binding::DIRECTION_LEFT; 2495 else if (deltaX < 0.0) 2496 geckoEvent.mDirection |= dom::SimpleGestureEvent_Binding::DIRECTION_RIGHT; 2497 2498 // Record the up/down direction. 2499 if (deltaY > 0.0) 2500 geckoEvent.mDirection |= dom::SimpleGestureEvent_Binding::DIRECTION_UP; 2501 else if (deltaY < 0.0) 2502 geckoEvent.mDirection |= dom::SimpleGestureEvent_Binding::DIRECTION_DOWN; 2503 2504 // Send the event. 2505 mGeckoChild->DispatchWindowEvent(geckoEvent); 2506 2507 NS_OBJC_END_TRY_IGNORE_BLOCK; 2508} 2509 2510// Pinch zoom gesture. 2511- (void)magnifyWithEvent:(NSEvent*)anEvent { 2512 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; 2513 2514 if ([self maybeRollup:anEvent]) { 2515 return; 2516 } 2517 2518 if (!mGeckoChild) { 2519 return; 2520 } 2521 2522 // Instead of calling beginOrEndGestureForEventPhase we basically inline 2523 // the effects of it here, because that function doesn't play too well with 2524 // how we create PinchGestureInput events below. The main point of that 2525 // function is to avoid flip-flopping between rotation/magnify gestures, which 2526 // we can do by checking and setting mGestureState appropriately. A secondary 2527 // result of that function is to send the final eMagnifyGesture event when 2528 // the gesture ends, but APZ takes care of that for us. 2529 if (mGestureState == eGestureState_RotateGesture && [anEvent phase] != NSEventPhaseBegan) { 2530 // If we're already in a rotation and not "starting" a magnify, abort. 2531 return; 2532 } 2533 mGestureState = eGestureState_MagnifyGesture; 2534 2535 NSPoint locationInWindow = nsCocoaUtils::EventLocationForWindow(anEvent, [self window]); 2536 ScreenPoint position = 2537 ViewAs<ScreenPixel>([self convertWindowCoordinatesRoundDown:locationInWindow], 2538 PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent); 2539 ExternalPoint screenOffset = 2540 ViewAs<ExternalPixel>(mGeckoChild->WidgetToScreenOffset(), 2541 PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent); 2542 2543 PRIntervalTime eventIntervalTime = PR_IntervalNow(); 2544 TimeStamp eventTimeStamp = nsCocoaUtils::GetEventTimeStamp([anEvent timestamp]); 2545 NSEventPhase eventPhase = [anEvent phase]; 2546 PinchGestureInput::PinchGestureType pinchGestureType; 2547 2548 switch (eventPhase) { 2549 case NSEventPhaseBegan: { 2550 pinchGestureType = PinchGestureInput::PINCHGESTURE_START; 2551 break; 2552 } 2553 case NSEventPhaseChanged: { 2554 pinchGestureType = PinchGestureInput::PINCHGESTURE_SCALE; 2555 break; 2556 } 2557 case NSEventPhaseEnded: { 2558 pinchGestureType = PinchGestureInput::PINCHGESTURE_END; 2559 mGestureState = eGestureState_None; 2560 break; 2561 } 2562 default: { 2563 NS_WARNING("Unexpected phase for pinch gesture event."); 2564 return; 2565 } 2566 } 2567 2568 PinchGestureInput event{pinchGestureType, 2569 PinchGestureInput::TRACKPAD, 2570 eventIntervalTime, 2571 eventTimeStamp, 2572 screenOffset, 2573 position, 2574 100.0, 2575 100.0 * (1.0 - [anEvent magnification]), 2576 nsCocoaUtils::ModifiersForEvent(anEvent)}; 2577 2578 mGeckoChild->DispatchAPZInputEvent(event); 2579 2580 NS_OBJC_END_TRY_IGNORE_BLOCK; 2581} 2582 2583// Smart zoom gesture, i.e. two-finger double tap on trackpads. 2584- (void)smartMagnifyWithEvent:(NSEvent*)anEvent { 2585 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; 2586 2587 if (!anEvent || !mGeckoChild || [self beginOrEndGestureForEventPhase:anEvent]) { 2588 return; 2589 } 2590 2591 if ([self maybeRollup:anEvent]) { 2592 return; 2593 } 2594 2595 nsAutoRetainCocoaObject kungFuDeathGrip(self); 2596 2597 if (StaticPrefs::apz_mac_enable_double_tap_zoom_touchpad_gesture()) { 2598 TimeStamp eventTimeStamp = nsCocoaUtils::GetEventTimeStamp([anEvent timestamp]); 2599 NSPoint locationInWindow = nsCocoaUtils::EventLocationForWindow(anEvent, [self window]); 2600 LayoutDevicePoint position = [self convertWindowCoordinatesRoundDown:locationInWindow]; 2601 2602 mGeckoChild->DispatchDoubleTapGesture(eventTimeStamp, RoundedToInt(position), 2603 nsCocoaUtils::ModifiersForEvent(anEvent)); 2604 } else { 2605 // Setup the "double tap" event. 2606 WidgetSimpleGestureEvent geckoEvent(true, eTapGesture, mGeckoChild); 2607 [self convertCocoaMouseEvent:anEvent toGeckoEvent:&geckoEvent]; 2608 geckoEvent.mClickCount = 1; 2609 2610 // Send the event. 2611 mGeckoChild->DispatchWindowEvent(geckoEvent); 2612 } 2613 2614 // Clear the gesture state 2615 mGestureState = eGestureState_None; 2616 2617 NS_OBJC_END_TRY_IGNORE_BLOCK; 2618} 2619 2620- (void)rotateWithEvent:(NSEvent*)anEvent { 2621 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; 2622 2623 if (!anEvent || !mGeckoChild || [self beginOrEndGestureForEventPhase:anEvent]) { 2624 return; 2625 } 2626 2627 nsAutoRetainCocoaObject kungFuDeathGrip(self); 2628 2629 float rotation = [anEvent rotation]; 2630 2631 EventMessage msg; 2632 switch (mGestureState) { 2633 case eGestureState_StartGesture: 2634 msg = eRotateGestureStart; 2635 mGestureState = eGestureState_RotateGesture; 2636 break; 2637 2638 case eGestureState_RotateGesture: 2639 msg = eRotateGestureUpdate; 2640 break; 2641 2642 case eGestureState_None: 2643 case eGestureState_MagnifyGesture: 2644 default: 2645 return; 2646 } 2647 2648 // Setup the event. 2649 WidgetSimpleGestureEvent geckoEvent(true, msg, mGeckoChild); 2650 [self convertCocoaMouseEvent:anEvent toGeckoEvent:&geckoEvent]; 2651 geckoEvent.mDelta = -rotation; 2652 if (rotation > 0.0) { 2653 geckoEvent.mDirection = dom::SimpleGestureEvent_Binding::ROTATION_COUNTERCLOCKWISE; 2654 } else { 2655 geckoEvent.mDirection = dom::SimpleGestureEvent_Binding::ROTATION_CLOCKWISE; 2656 } 2657 2658 // Send the event. 2659 mGeckoChild->DispatchWindowEvent(geckoEvent); 2660 2661 // Keep track of the cumulative rotation for the final "rotate" event. 2662 mCumulativeRotation += rotation; 2663 2664 NS_OBJC_END_TRY_IGNORE_BLOCK; 2665} 2666 2667// `beginGestureWithEvent` and `endGestureWithEvent` are not called for 2668// applications that link against the macOS 10.11 or later SDK when we're 2669// running on macOS 10.11 or later. For compatibility with all supported macOS 2670// versions, we have to call {begin,end}GestureWithEvent ourselves based on 2671// the event phase when we're handling gestures. 2672- (bool)beginOrEndGestureForEventPhase:(NSEvent*)aEvent { 2673 if (!aEvent) { 2674 return false; 2675 } 2676 2677 if (aEvent.phase == NSEventPhaseBegan) { 2678 [self beginGestureWithEvent:aEvent]; 2679 return true; 2680 } 2681 2682 if (aEvent.phase == NSEventPhaseEnded || aEvent.phase == NSEventPhaseCancelled) { 2683 [self endGestureWithEvent:aEvent]; 2684 return true; 2685 } 2686 2687 return false; 2688} 2689 2690- (void)beginGestureWithEvent:(NSEvent*)aEvent { 2691 if (!aEvent) { 2692 return; 2693 } 2694 2695 mGestureState = eGestureState_StartGesture; 2696 mCumulativeRotation = 0.0; 2697} 2698 2699- (void)endGestureWithEvent:(NSEvent*)anEvent { 2700 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; 2701 2702 if (!anEvent || !mGeckoChild) { 2703 // Clear the gestures state if we cannot send an event. 2704 mGestureState = eGestureState_None; 2705 mCumulativeRotation = 0.0; 2706 return; 2707 } 2708 2709 nsAutoRetainCocoaObject kungFuDeathGrip(self); 2710 2711 switch (mGestureState) { 2712 case eGestureState_RotateGesture: { 2713 // Setup the "rotate" event. 2714 WidgetSimpleGestureEvent geckoEvent(true, eRotateGesture, mGeckoChild); 2715 [self convertCocoaMouseEvent:anEvent toGeckoEvent:&geckoEvent]; 2716 geckoEvent.mDelta = -mCumulativeRotation; 2717 if (mCumulativeRotation > 0.0) { 2718 geckoEvent.mDirection = dom::SimpleGestureEvent_Binding::ROTATION_COUNTERCLOCKWISE; 2719 } else { 2720 geckoEvent.mDirection = dom::SimpleGestureEvent_Binding::ROTATION_CLOCKWISE; 2721 } 2722 2723 // Send the event. 2724 mGeckoChild->DispatchWindowEvent(geckoEvent); 2725 } break; 2726 2727 case eGestureState_MagnifyGesture: // APZ handles sending the widget events 2728 case eGestureState_None: 2729 case eGestureState_StartGesture: 2730 default: 2731 break; 2732 } 2733 2734 // Clear the gestures state. 2735 mGestureState = eGestureState_None; 2736 mCumulativeRotation = 0.0; 2737 2738 NS_OBJC_END_TRY_IGNORE_BLOCK; 2739} 2740 2741- (bool)shouldConsiderStartingSwipeFromEvent:(NSEvent*)anEvent { 2742 // This method checks whether the AppleEnableSwipeNavigateWithScrolls global 2743 // preference is set. If it isn't, fluid swipe tracking is disabled, and a 2744 // horizontal two-finger gesture is always a scroll (even in Safari). This 2745 // preference can't (currently) be set from the Preferences UI -- only using 2746 // 'defaults write'. 2747 if (![NSEvent isSwipeTrackingFromScrollEventsEnabled]) { 2748 return false; 2749 } 2750 2751 // Only initiate horizontal tracking for gestures that have just begun -- 2752 // otherwise a scroll to one side of the page can have a swipe tacked on 2753 // to it. 2754 NSEventPhase eventPhase = [anEvent phase]; 2755 return [anEvent type] == NSEventTypeScrollWheel && eventPhase == NSEventPhaseBegan && 2756 [anEvent hasPreciseScrollingDeltas]; 2757} 2758 2759- (void)setUsingOMTCompositor:(BOOL)aUseOMTC { 2760 mUsingOMTCompositor = aUseOMTC; 2761} 2762 2763// Returning NO from this method only disallows ordering on mousedown - in order 2764// to prevent it for mouseup too, we need to call [NSApp preventWindowOrdering] 2765// when handling the mousedown event. 2766- (BOOL)shouldDelayWindowOrderingForEvent:(NSEvent*)aEvent { 2767 // Always using system-provided window ordering for normal windows. 2768 if (![[self window] isKindOfClass:[PopupWindow class]]) return NO; 2769 2770 // Don't reorder when we don't have a parent window, like when we're a 2771 // context menu or a tooltip. 2772 return ![[self window] parentWindow]; 2773} 2774 2775- (void)mouseDown:(NSEvent*)theEvent { 2776 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; 2777 2778 if ([self shouldDelayWindowOrderingForEvent:theEvent]) { 2779 [NSApp preventWindowOrdering]; 2780 } 2781 2782 // If we've already seen this event due to direct dispatch from menuForEvent: 2783 // just bail; if not, remember it. 2784 if (mLastMouseDownEvent == theEvent) { 2785 [mLastMouseDownEvent release]; 2786 mLastMouseDownEvent = nil; 2787 return; 2788 } else { 2789 [mLastMouseDownEvent release]; 2790 mLastMouseDownEvent = [theEvent retain]; 2791 } 2792 2793 [gLastDragMouseDownEvent release]; 2794 gLastDragMouseDownEvent = [theEvent retain]; 2795 gLastDragView = self; 2796 2797 // We need isClickThrough because at this point the window we're in might 2798 // already have become main, so the check for isMainWindow in 2799 // WindowAcceptsEvent isn't enough. It also has to check isClickThrough. 2800 BOOL isClickThrough = (theEvent == mClickThroughMouseDownEvent); 2801 [mClickThroughMouseDownEvent release]; 2802 mClickThroughMouseDownEvent = nil; 2803 2804 nsAutoRetainCocoaObject kungFuDeathGrip(self); 2805 2806 if ([self maybeRollup:theEvent] || 2807 !ChildViewMouseTracker::WindowAcceptsEvent([self window], theEvent, self, isClickThrough)) { 2808 // Remember blocking because that means we want to block mouseup as well. 2809 mBlockedLastMouseDown = YES; 2810 return; 2811 } 2812 2813 // in order to send gecko events we'll need a gecko widget 2814 if (!mGeckoChild) return; 2815 if (mTextInputHandler->OnHandleEvent(theEvent)) { 2816 return; 2817 } 2818 2819 WidgetMouseEvent geckoEvent(true, eMouseDown, mGeckoChild, WidgetMouseEvent::eReal); 2820 [self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent]; 2821 2822 NSInteger clickCount = [theEvent clickCount]; 2823 if (mBlockedLastMouseDown && clickCount > 1) { 2824 // Don't send a double click if the first click of the double click was 2825 // blocked. 2826 clickCount--; 2827 } 2828 geckoEvent.mClickCount = clickCount; 2829 2830 if (!StaticPrefs::dom_event_treat_ctrl_click_as_right_click_disabled() && 2831 geckoEvent.IsControl()) { 2832 geckoEvent.mButton = MouseButton::eSecondary; 2833 } else { 2834 geckoEvent.mButton = MouseButton::ePrimary; 2835 // Don't send a click if ctrl key is pressed. 2836 geckoEvent.mClickEventPrevented = geckoEvent.IsControl(); 2837 } 2838 2839 mGeckoChild->DispatchInputEvent(&geckoEvent); 2840 mBlockedLastMouseDown = NO; 2841 2842 // XXX maybe call markedTextSelectionChanged:client: here? 2843 2844 NS_OBJC_END_TRY_IGNORE_BLOCK; 2845} 2846 2847- (void)mouseUp:(NSEvent*)theEvent { 2848 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; 2849 2850 gLastDragView = nil; 2851 2852 if (!mGeckoChild || mBlockedLastMouseDown) return; 2853 if (mTextInputHandler->OnHandleEvent(theEvent)) { 2854 return; 2855 } 2856 2857 nsAutoRetainCocoaObject kungFuDeathGrip(self); 2858 2859 WidgetMouseEvent geckoEvent(true, eMouseUp, mGeckoChild, WidgetMouseEvent::eReal); 2860 [self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent]; 2861 2862 if (!StaticPrefs::dom_event_treat_ctrl_click_as_right_click_disabled() && 2863 ([theEvent modifierFlags] & NSEventModifierFlagControl)) { 2864 geckoEvent.mButton = MouseButton::eSecondary; 2865 } else { 2866 geckoEvent.mButton = MouseButton::ePrimary; 2867 } 2868 2869 // Remember the event's position before calling DispatchInputEvent, because 2870 // that call can mutate it and convert it into a different coordinate space. 2871 LayoutDeviceIntPoint pos = geckoEvent.mRefPoint; 2872 2873 // This might destroy our widget (and null out mGeckoChild). 2874 bool defaultPrevented = (mGeckoChild->DispatchInputEvent(&geckoEvent).mContentStatus == 2875 nsEventStatus_eConsumeNoDefault); 2876 2877 if (!mGeckoChild) { 2878 return; 2879 } 2880 2881 // Check to see if we are double-clicking in draggable parts of the window. 2882 if (!defaultPrevented && [theEvent clickCount] == 2 && 2883 !mGeckoChild->GetNonDraggableRegion().Contains(pos.x, pos.y)) { 2884 if (nsCocoaUtils::ShouldZoomOnTitlebarDoubleClick()) { 2885 [[self window] performZoom:nil]; 2886 } else if (nsCocoaUtils::ShouldMinimizeOnTitlebarDoubleClick()) { 2887 [[self window] performMiniaturize:nil]; 2888 } 2889 } 2890 2891 NS_OBJC_END_TRY_IGNORE_BLOCK; 2892} 2893 2894- (void)sendMouseEnterOrExitEvent:(NSEvent*)aEvent 2895 enter:(BOOL)aEnter 2896 exitFrom:(WidgetMouseEvent::ExitFrom)aExitFrom { 2897 if (!mGeckoChild) return; 2898 2899 NSPoint windowEventLocation = nsCocoaUtils::EventLocationForWindow(aEvent, [self window]); 2900 NSPoint localEventLocation = [self convertPoint:windowEventLocation fromView:nil]; 2901 2902 EventMessage msg = aEnter ? eMouseEnterIntoWidget : eMouseExitFromWidget; 2903 WidgetMouseEvent event(true, msg, mGeckoChild, WidgetMouseEvent::eReal); 2904 event.mRefPoint = mGeckoChild->CocoaPointsToDevPixels(localEventLocation); 2905 if (event.mMessage == eMouseExitFromWidget) { 2906 event.mExitFrom = Some(aExitFrom); 2907 } 2908 nsEventStatus status; // ignored 2909 mGeckoChild->DispatchEvent(&event, status); 2910} 2911 2912- (void)handleMouseMoved:(NSEvent*)theEvent { 2913 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; 2914 2915 if (!mGeckoChild) return; 2916 if (mTextInputHandler->OnHandleEvent(theEvent)) { 2917 return; 2918 } 2919 2920 WidgetMouseEvent geckoEvent(true, eMouseMove, mGeckoChild, WidgetMouseEvent::eReal); 2921 [self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent]; 2922 2923 mGeckoChild->NoteMouseMoveAtTime(geckoEvent.mTimeStamp); 2924 mGeckoChild->DispatchInputEvent(&geckoEvent); 2925 2926 NS_OBJC_END_TRY_IGNORE_BLOCK; 2927} 2928 2929- (void)mouseDragged:(NSEvent*)theEvent { 2930 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; 2931 2932 if (!mGeckoChild) return; 2933 if (mTextInputHandler->OnHandleEvent(theEvent)) { 2934 return; 2935 } 2936 2937 WidgetMouseEvent geckoEvent(true, eMouseMove, mGeckoChild, WidgetMouseEvent::eReal); 2938 [self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent]; 2939 2940 mGeckoChild->DispatchInputEvent(&geckoEvent); 2941 2942 // Note, sending the above event might have destroyed our widget since we didn't retain. 2943 // Fine so long as we don't access any local variables from here on. 2944 2945 // XXX maybe call markedTextSelectionChanged:client: here? 2946 2947 NS_OBJC_END_TRY_IGNORE_BLOCK; 2948} 2949 2950- (void)rightMouseDown:(NSEvent*)theEvent { 2951 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; 2952 2953 nsAutoRetainCocoaObject kungFuDeathGrip(self); 2954 2955 [self maybeRollup:theEvent]; 2956 if (!mGeckoChild) return; 2957 if (mTextInputHandler->OnHandleEvent(theEvent)) { 2958 return; 2959 } 2960 2961 // The right mouse went down, fire off a right mouse down event to gecko 2962 WidgetMouseEvent geckoEvent(true, eMouseDown, mGeckoChild, WidgetMouseEvent::eReal); 2963 [self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent]; 2964 geckoEvent.mButton = MouseButton::eSecondary; 2965 geckoEvent.mClickCount = [theEvent clickCount]; 2966 2967 nsIWidget::ContentAndAPZEventStatus eventStatus = mGeckoChild->DispatchInputEvent(&geckoEvent); 2968 if (!mGeckoChild) return; 2969 2970 if (!StaticPrefs::ui_context_menus_after_mouseup() && 2971 eventStatus.mApzStatus != nsEventStatus_eConsumeNoDefault) { 2972 // Let the superclass do the context menu stuff. 2973 [super rightMouseDown:theEvent]; 2974 } 2975 2976 NS_OBJC_END_TRY_IGNORE_BLOCK; 2977} 2978 2979- (void)rightMouseUp:(NSEvent*)theEvent { 2980 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; 2981 2982 if (!mGeckoChild) return; 2983 if (mTextInputHandler->OnHandleEvent(theEvent)) { 2984 return; 2985 } 2986 2987 WidgetMouseEvent geckoEvent(true, eMouseUp, mGeckoChild, WidgetMouseEvent::eReal); 2988 [self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent]; 2989 geckoEvent.mButton = MouseButton::eSecondary; 2990 geckoEvent.mClickCount = [theEvent clickCount]; 2991 2992 nsAutoRetainCocoaObject kungFuDeathGrip(self); 2993 nsIWidget::ContentAndAPZEventStatus eventStatus = mGeckoChild->DispatchInputEvent(&geckoEvent); 2994 if (!mGeckoChild) return; 2995 2996 if (StaticPrefs::ui_context_menus_after_mouseup() && 2997 eventStatus.mApzStatus != nsEventStatus_eConsumeNoDefault) { 2998 // Let the superclass do the context menu stuff, but pretend it's rightMouseDown. 2999 NSEvent* dupeEvent = [NSEvent mouseEventWithType:NSEventTypeRightMouseDown 3000 location:theEvent.locationInWindow 3001 modifierFlags:theEvent.modifierFlags 3002 timestamp:theEvent.timestamp 3003 windowNumber:theEvent.windowNumber 3004 context:nil 3005 eventNumber:theEvent.eventNumber 3006 clickCount:theEvent.clickCount 3007 pressure:theEvent.pressure]; 3008 3009 [super rightMouseDown:dupeEvent]; 3010 } 3011 3012 NS_OBJC_END_TRY_IGNORE_BLOCK; 3013} 3014 3015- (void)rightMouseDragged:(NSEvent*)theEvent { 3016 if (!mGeckoChild) return; 3017 if (mTextInputHandler->OnHandleEvent(theEvent)) { 3018 return; 3019 } 3020 3021 WidgetMouseEvent geckoEvent(true, eMouseMove, mGeckoChild, WidgetMouseEvent::eReal); 3022 [self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent]; 3023 geckoEvent.mButton = MouseButton::eSecondary; 3024 3025 // send event into Gecko by going directly to the 3026 // the widget. 3027 mGeckoChild->DispatchInputEvent(&geckoEvent); 3028} 3029 3030static bool ShouldDispatchBackForwardCommandForMouseButton(int16_t aButton) { 3031 return (aButton == MouseButton::eX1 && Preferences::GetBool("mousebutton.4th.enabled", true)) || 3032 (aButton == MouseButton::eX2 && Preferences::GetBool("mousebutton.5th.enabled", true)); 3033} 3034 3035- (void)otherMouseDown:(NSEvent*)theEvent { 3036 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; 3037 3038 nsAutoRetainCocoaObject kungFuDeathGrip(self); 3039 3040 if ([self maybeRollup:theEvent] || 3041 !ChildViewMouseTracker::WindowAcceptsEvent([self window], theEvent, self)) 3042 return; 3043 3044 if (!mGeckoChild) return; 3045 if (mTextInputHandler->OnHandleEvent(theEvent)) { 3046 return; 3047 } 3048 3049 int16_t button = nsCocoaUtils::ButtonForEvent(theEvent); 3050 if (ShouldDispatchBackForwardCommandForMouseButton(button)) { 3051 WidgetCommandEvent appCommandEvent( 3052 true, (button == MouseButton::eX2) ? nsGkAtoms::Forward : nsGkAtoms::Back, mGeckoChild); 3053 mGeckoChild->DispatchWindowEvent(appCommandEvent); 3054 return; 3055 } 3056 3057 WidgetMouseEvent geckoEvent(true, eMouseDown, mGeckoChild, WidgetMouseEvent::eReal); 3058 [self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent]; 3059 geckoEvent.mButton = button; 3060 geckoEvent.mClickCount = [theEvent clickCount]; 3061 3062 mGeckoChild->DispatchInputEvent(&geckoEvent); 3063 3064 NS_OBJC_END_TRY_IGNORE_BLOCK; 3065} 3066 3067- (void)otherMouseUp:(NSEvent*)theEvent { 3068 if (!mGeckoChild) return; 3069 if (mTextInputHandler->OnHandleEvent(theEvent)) { 3070 return; 3071 } 3072 3073 int16_t button = nsCocoaUtils::ButtonForEvent(theEvent); 3074 if (ShouldDispatchBackForwardCommandForMouseButton(button)) { 3075 return; 3076 } 3077 3078 WidgetMouseEvent geckoEvent(true, eMouseUp, mGeckoChild, WidgetMouseEvent::eReal); 3079 [self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent]; 3080 geckoEvent.mButton = button; 3081 3082 nsAutoRetainCocoaObject kungFuDeathGrip(self); 3083 mGeckoChild->DispatchInputEvent(&geckoEvent); 3084} 3085 3086- (void)otherMouseDragged:(NSEvent*)theEvent { 3087 if (!mGeckoChild) return; 3088 if (mTextInputHandler->OnHandleEvent(theEvent)) { 3089 return; 3090 } 3091 3092 WidgetMouseEvent geckoEvent(true, eMouseMove, mGeckoChild, WidgetMouseEvent::eReal); 3093 [self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent]; 3094 int16_t button = nsCocoaUtils::ButtonForEvent(theEvent); 3095 geckoEvent.mButton = button; 3096 3097 // send event into Gecko by going directly to the 3098 // the widget. 3099 mGeckoChild->DispatchInputEvent(&geckoEvent); 3100} 3101 3102- (void)sendWheelStartOrStop:(EventMessage)msg forEvent:(NSEvent*)theEvent { 3103 WidgetWheelEvent wheelEvent(true, msg, mGeckoChild); 3104 [self convertCocoaMouseWheelEvent:theEvent toGeckoEvent:&wheelEvent]; 3105 mExpectingWheelStop = (msg == eWheelOperationStart); 3106 mGeckoChild->DispatchInputEvent(wheelEvent.AsInputEvent()); 3107} 3108 3109- (void)sendWheelCondition:(BOOL)condition 3110 first:(EventMessage)first 3111 second:(EventMessage)second 3112 forEvent:(NSEvent*)theEvent { 3113 if (mExpectingWheelStop == condition) { 3114 [self sendWheelStartOrStop:first forEvent:theEvent]; 3115 } 3116 [self sendWheelStartOrStop:second forEvent:theEvent]; 3117} 3118 3119static PanGestureInput::PanGestureType PanGestureTypeForEvent(NSEvent* aEvent) { 3120 switch ([aEvent phase]) { 3121 case NSEventPhaseMayBegin: 3122 return PanGestureInput::PANGESTURE_MAYSTART; 3123 case NSEventPhaseCancelled: 3124 return PanGestureInput::PANGESTURE_CANCELLED; 3125 case NSEventPhaseBegan: 3126 return PanGestureInput::PANGESTURE_START; 3127 case NSEventPhaseChanged: 3128 return PanGestureInput::PANGESTURE_PAN; 3129 case NSEventPhaseEnded: 3130 return PanGestureInput::PANGESTURE_END; 3131 case NSEventPhaseNone: 3132 switch ([aEvent momentumPhase]) { 3133 case NSEventPhaseBegan: 3134 return PanGestureInput::PANGESTURE_MOMENTUMSTART; 3135 case NSEventPhaseChanged: 3136 return PanGestureInput::PANGESTURE_MOMENTUMPAN; 3137 case NSEventPhaseEnded: 3138 return PanGestureInput::PANGESTURE_MOMENTUMEND; 3139 default: 3140 NS_ERROR("unexpected event phase"); 3141 return PanGestureInput::PANGESTURE_PAN; 3142 } 3143 default: 3144 NS_ERROR("unexpected event phase"); 3145 return PanGestureInput::PANGESTURE_PAN; 3146 } 3147} 3148 3149static int32_t RoundUp(double aDouble) { 3150 return aDouble < 0 ? static_cast<int32_t>(floor(aDouble)) : static_cast<int32_t>(ceil(aDouble)); 3151} 3152 3153static gfx::IntPoint GetIntegerDeltaForEvent(NSEvent* aEvent) { 3154 if ([aEvent hasPreciseScrollingDeltas]) { 3155 // Pixel scroll events (events with hasPreciseScrollingDeltas == YES) 3156 // carry pixel deltas in the scrollingDeltaX/Y fields and line scroll 3157 // information in the deltaX/Y fields. 3158 // Prior to 10.12, these line scroll fields would be zero for most pixel 3159 // scroll events and non-zero for some, whenever at least a full line 3160 // worth of pixel scrolling had accumulated. That's the behavior we want. 3161 // Starting with 10.12 however, pixel scroll events no longer accumulate 3162 // deltaX and deltaY; they just report floating point values for every 3163 // single event. So we need to do our own accumulation. 3164 return PanGestureInput::GetIntegerDeltaForEvent([aEvent phase] == NSEventPhaseBegan, 3165 [aEvent deltaX], [aEvent deltaY]); 3166 } 3167 3168 // For line scrolls, or pre-10.12, just use the rounded up value of deltaX / deltaY. 3169 return gfx::IntPoint(RoundUp([aEvent deltaX]), RoundUp([aEvent deltaY])); 3170} 3171 3172- (void)scrollWheel:(NSEvent*)theEvent { 3173 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; 3174 3175 nsAutoRetainCocoaObject kungFuDeathGrip(self); 3176 3177 ChildViewMouseTracker::MouseScrolled(theEvent); 3178 3179 if ([self maybeRollup:theEvent]) { 3180 return; 3181 } 3182 3183 if (!mGeckoChild) { 3184 return; 3185 } 3186 3187 NSEventPhase phase = [theEvent phase]; 3188 // Fire eWheelOperationStart/End events when 2 fingers touch/release the 3189 // touchpad. 3190 if (phase & NSEventPhaseMayBegin) { 3191 [self sendWheelCondition:YES 3192 first:eWheelOperationEnd 3193 second:eWheelOperationStart 3194 forEvent:theEvent]; 3195 } else if (phase & (NSEventPhaseEnded | NSEventPhaseCancelled)) { 3196 [self sendWheelCondition:NO 3197 first:eWheelOperationStart 3198 second:eWheelOperationEnd 3199 forEvent:theEvent]; 3200 } 3201 3202 if (!mGeckoChild) { 3203 return; 3204 } 3205 RefPtr<nsChildView> geckoChildDeathGrip(mGeckoChild); 3206 3207 NSPoint locationInWindow = nsCocoaUtils::EventLocationForWindow(theEvent, [self window]); 3208 3209 // Use convertWindowCoordinatesRoundDown when converting the position to 3210 // integer screen pixels in order to ensure that coordinates which are just 3211 // inside the right / bottom edges of the window don't end up outside of the 3212 // window after rounding. 3213 ScreenPoint position = 3214 ViewAs<ScreenPixel>([self convertWindowCoordinatesRoundDown:locationInWindow], 3215 PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent); 3216 3217 bool usePreciseDeltas = [theEvent hasPreciseScrollingDeltas] && 3218 Preferences::GetBool("mousewheel.enable_pixel_scrolling", true); 3219 bool hasPhaseInformation = nsCocoaUtils::EventHasPhaseInformation(theEvent); 3220 3221 gfx::IntPoint lineOrPageDelta = -GetIntegerDeltaForEvent(theEvent); 3222 3223 Modifiers modifiers = nsCocoaUtils::ModifiersForEvent(theEvent); 3224 3225 PRIntervalTime eventIntervalTime = PR_IntervalNow(); 3226 TimeStamp eventTimeStamp = nsCocoaUtils::GetEventTimeStamp([theEvent timestamp]); 3227 3228 ScreenPoint preciseDelta; 3229 if (usePreciseDeltas) { 3230 CGFloat pixelDeltaX = [theEvent scrollingDeltaX]; 3231 CGFloat pixelDeltaY = [theEvent scrollingDeltaY]; 3232 double scale = geckoChildDeathGrip->BackingScaleFactor(); 3233 preciseDelta = ScreenPoint(-pixelDeltaX * scale, -pixelDeltaY * scale); 3234 } 3235 3236 if (usePreciseDeltas && hasPhaseInformation) { 3237 PanGestureInput panEvent(PanGestureTypeForEvent(theEvent), eventIntervalTime, eventTimeStamp, 3238 position, ScreenPoint(), modifiers); 3239 3240 // Always force zero deltas on event types that shouldn't cause any scrolling, 3241 // so that we don't dispatch DOM wheel events for them. 3242 bool shouldIgnoreDeltas = panEvent.mType == PanGestureInput::PANGESTURE_MAYSTART || 3243 panEvent.mType == PanGestureInput::PANGESTURE_CANCELLED; 3244 3245 if (!shouldIgnoreDeltas) { 3246 panEvent.mPanDisplacement = preciseDelta; 3247 panEvent.SetLineOrPageDeltas(lineOrPageDelta.x, lineOrPageDelta.y); 3248 } 3249 3250 bool canTriggerSwipe = [self shouldConsiderStartingSwipeFromEvent:theEvent] && 3251 SwipeTracker::CanTriggerSwipe(panEvent); 3252 ; 3253 panEvent.mRequiresContentResponseIfCannotScrollHorizontallyInStartDirection = canTriggerSwipe; 3254 geckoChildDeathGrip->DispatchAPZWheelInputEvent(panEvent, canTriggerSwipe); 3255 } else if (usePreciseDeltas) { 3256 // This is on 10.6 or old touchpads that don't have any phase information. 3257 ScrollWheelInput wheelEvent( 3258 eventIntervalTime, eventTimeStamp, modifiers, ScrollWheelInput::SCROLLMODE_INSTANT, 3259 ScrollWheelInput::SCROLLDELTA_PIXEL, position, preciseDelta.x, preciseDelta.y, false, 3260 // This parameter is used for wheel delta 3261 // adjustment, such as auto-dir scrolling, 3262 // but we do't need to do anything special here 3263 // since this wheel event is sent to 3264 // DispatchAPZWheelInputEvent, which turns this 3265 // ScrollWheelInput back into a WidgetWheelEvent 3266 // and then it goes through the regular handling 3267 // in APZInputBridge. So passing |eNone| won't 3268 // pass up the necessary wheel delta adjustment. 3269 WheelDeltaAdjustmentStrategy::eNone); 3270 wheelEvent.mLineOrPageDeltaX = lineOrPageDelta.x; 3271 wheelEvent.mLineOrPageDeltaY = lineOrPageDelta.y; 3272 wheelEvent.mIsMomentum = nsCocoaUtils::IsMomentumScrollEvent(theEvent); 3273 geckoChildDeathGrip->DispatchAPZWheelInputEvent(wheelEvent, false); 3274 } else { 3275 ScrollWheelInput::ScrollMode scrollMode = ScrollWheelInput::SCROLLMODE_INSTANT; 3276 if (StaticPrefs::general_smoothScroll() && StaticPrefs::general_smoothScroll_mouseWheel()) { 3277 scrollMode = ScrollWheelInput::SCROLLMODE_SMOOTH; 3278 } 3279 ScrollWheelInput wheelEvent(eventIntervalTime, eventTimeStamp, modifiers, scrollMode, 3280 ScrollWheelInput::SCROLLDELTA_LINE, position, lineOrPageDelta.x, 3281 lineOrPageDelta.y, false, 3282 // This parameter is used for wheel delta 3283 // adjustment, such as auto-dir scrolling, 3284 // but we do't need to do anything special here 3285 // since this wheel event is sent to 3286 // DispatchAPZWheelInputEvent, which turns this 3287 // ScrollWheelInput back into a WidgetWheelEvent 3288 // and then it goes through the regular handling 3289 // in APZInputBridge. So passing |eNone| won't 3290 // pass up the necessary wheel delta adjustment. 3291 WheelDeltaAdjustmentStrategy::eNone); 3292 wheelEvent.mLineOrPageDeltaX = lineOrPageDelta.x; 3293 wheelEvent.mLineOrPageDeltaY = lineOrPageDelta.y; 3294 geckoChildDeathGrip->DispatchAPZWheelInputEvent(wheelEvent, false); 3295 } 3296 3297 NS_OBJC_END_TRY_IGNORE_BLOCK; 3298} 3299 3300- (NSMenu*)menuForEvent:(NSEvent*)theEvent { 3301 NS_OBJC_BEGIN_TRY_BLOCK_RETURN; 3302 3303 if (!mGeckoChild) return nil; 3304 3305 nsAutoRetainCocoaObject kungFuDeathGrip(self); 3306 3307 [self maybeRollup:theEvent]; 3308 if (!mGeckoChild) return nil; 3309 3310 // Cocoa doesn't always dispatch a mouseDown: for a control-click event, 3311 // depends on what we return from menuForEvent:. Gecko always expects one 3312 // and expects the mouse down event before the context menu event, so 3313 // get that event sent first if this is a left mouse click. 3314 if ([theEvent type] == NSEventTypeLeftMouseDown) { 3315 [self mouseDown:theEvent]; 3316 if (!mGeckoChild) return nil; 3317 } 3318 3319 WidgetMouseEvent geckoEvent(true, eContextMenu, mGeckoChild, WidgetMouseEvent::eReal); 3320 [self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent]; 3321 if (StaticPrefs::dom_event_treat_ctrl_click_as_right_click_disabled() && 3322 [theEvent type] == NSEventTypeLeftMouseDown) { 3323 geckoEvent.mContextMenuTrigger = WidgetMouseEvent::eControlClick; 3324 geckoEvent.mButton = MouseButton::ePrimary; 3325 } else { 3326 geckoEvent.mButton = MouseButton::eSecondary; 3327 } 3328 3329 mGeckoChild->DispatchInputEvent(&geckoEvent); 3330 if (!mGeckoChild) return nil; 3331 3332 [self maybeInitContextMenuTracking]; 3333 3334 // We never return an actual NSMenu* for the context menu. Gecko might have 3335 // responded to the eContextMenu event by putting up a fake context menu. 3336 return nil; 3337 3338 NS_OBJC_END_TRY_BLOCK_RETURN(nil); 3339} 3340 3341- (void)willOpenMenu:(NSMenu*)aMenu withEvent:(NSEvent*)aEvent { 3342 ChildViewMouseTracker::NativeMenuOpened(); 3343} 3344 3345- (void)didCloseMenu:(NSMenu*)aMenu withEvent:(NSEvent*)aEvent { 3346 ChildViewMouseTracker::NativeMenuClosed(); 3347} 3348 3349- (void)convertCocoaMouseWheelEvent:(NSEvent*)aMouseEvent 3350 toGeckoEvent:(WidgetWheelEvent*)outWheelEvent { 3351 [self convertCocoaMouseEvent:aMouseEvent toGeckoEvent:outWheelEvent]; 3352 3353 bool usePreciseDeltas = [aMouseEvent hasPreciseScrollingDeltas] && 3354 Preferences::GetBool("mousewheel.enable_pixel_scrolling", true); 3355 3356 outWheelEvent->mDeltaMode = usePreciseDeltas ? dom::WheelEvent_Binding::DOM_DELTA_PIXEL 3357 : dom::WheelEvent_Binding::DOM_DELTA_LINE; 3358 outWheelEvent->mIsMomentum = nsCocoaUtils::IsMomentumScrollEvent(aMouseEvent); 3359} 3360 3361- (void)convertCocoaMouseEvent:(NSEvent*)aMouseEvent toGeckoEvent:(WidgetInputEvent*)outGeckoEvent { 3362 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; 3363 3364 NS_ASSERTION(outGeckoEvent, 3365 "convertCocoaMouseEvent:toGeckoEvent: requires non-null aoutGeckoEvent"); 3366 if (!outGeckoEvent) return; 3367 3368 nsCocoaUtils::InitInputEvent(*outGeckoEvent, aMouseEvent); 3369 3370 // convert point to view coordinate system 3371 NSPoint locationInWindow = nsCocoaUtils::EventLocationForWindow(aMouseEvent, [self window]); 3372 3373 outGeckoEvent->mRefPoint = [self convertWindowCoordinates:locationInWindow]; 3374 3375 WidgetMouseEventBase* mouseEvent = outGeckoEvent->AsMouseEventBase(); 3376 mouseEvent->mButtons = 0; 3377 NSUInteger mouseButtons = [NSEvent pressedMouseButtons]; 3378 3379 if (mouseButtons & 0x01) { 3380 mouseEvent->mButtons |= MouseButtonsFlag::ePrimaryFlag; 3381 } 3382 if (mouseButtons & 0x02) { 3383 mouseEvent->mButtons |= MouseButtonsFlag::eSecondaryFlag; 3384 } 3385 if (mouseButtons & 0x04) { 3386 mouseEvent->mButtons |= MouseButtonsFlag::eMiddleFlag; 3387 } 3388 if (mouseButtons & 0x08) { 3389 mouseEvent->mButtons |= MouseButtonsFlag::e4thFlag; 3390 } 3391 if (mouseButtons & 0x10) { 3392 mouseEvent->mButtons |= MouseButtonsFlag::e5thFlag; 3393 } 3394 3395 switch ([aMouseEvent type]) { 3396 case NSEventTypeLeftMouseDown: 3397 case NSEventTypeLeftMouseUp: 3398 case NSEventTypeLeftMouseDragged: 3399 case NSEventTypeRightMouseDown: 3400 case NSEventTypeRightMouseUp: 3401 case NSEventTypeRightMouseDragged: 3402 case NSEventTypeOtherMouseDown: 3403 case NSEventTypeOtherMouseUp: 3404 case NSEventTypeOtherMouseDragged: 3405 case NSEventTypeMouseMoved: 3406 if ([aMouseEvent subtype] == NSEventSubtypeTabletPoint) { 3407 [self convertCocoaTabletPointerEvent:aMouseEvent toGeckoEvent:mouseEvent->AsMouseEvent()]; 3408 } 3409 break; 3410 3411 default: 3412 // Don't check other NSEvents for pressure. 3413 break; 3414 } 3415 3416 NS_OBJC_END_TRY_IGNORE_BLOCK; 3417} 3418 3419- (void)convertCocoaTabletPointerEvent:(NSEvent*)aPointerEvent 3420 toGeckoEvent:(WidgetMouseEvent*)aOutGeckoEvent { 3421 NS_OBJC_BEGIN_TRY_BLOCK_RETURN 3422 if (!aOutGeckoEvent || !sIsTabletPointerActivated) { 3423 return; 3424 } 3425 if ([aPointerEvent type] != NSEventTypeMouseMoved) { 3426 aOutGeckoEvent->mPressure = [aPointerEvent pressure]; 3427 MOZ_ASSERT(aOutGeckoEvent->mPressure >= 0.0 && aOutGeckoEvent->mPressure <= 1.0); 3428 } 3429 aOutGeckoEvent->mInputSource = dom::MouseEvent_Binding::MOZ_SOURCE_PEN; 3430 aOutGeckoEvent->tiltX = lround([aPointerEvent tilt].x * 90); 3431 aOutGeckoEvent->tiltY = lround([aPointerEvent tilt].y * 90); 3432 aOutGeckoEvent->tangentialPressure = [aPointerEvent tangentialPressure]; 3433 // Make sure the twist value is in the range of 0-359. 3434 int32_t twist = fmod([aPointerEvent rotation], 360); 3435 aOutGeckoEvent->twist = twist >= 0 ? twist : twist + 360; 3436 NS_OBJC_END_TRY_IGNORE_BLOCK; 3437} 3438 3439- (void)tabletProximity:(NSEvent*)theEvent { 3440 NS_OBJC_BEGIN_TRY_BLOCK_RETURN 3441 sIsTabletPointerActivated = [theEvent isEnteringProximity]; 3442 NS_OBJC_END_TRY_IGNORE_BLOCK 3443} 3444 3445#pragma mark - 3446// NSTextInputClient implementation 3447 3448- (NSRange)markedRange { 3449 NS_OBJC_BEGIN_TRY_BLOCK_RETURN; 3450 3451 NS_ENSURE_TRUE(mTextInputHandler, NSMakeRange(NSNotFound, 0)); 3452 return mTextInputHandler->MarkedRange(); 3453 3454 NS_OBJC_END_TRY_BLOCK_RETURN(NSMakeRange(0, 0)); 3455} 3456 3457- (NSRange)selectedRange { 3458 NS_OBJC_BEGIN_TRY_BLOCK_RETURN; 3459 3460 NS_ENSURE_TRUE(mTextInputHandler, NSMakeRange(NSNotFound, 0)); 3461 return mTextInputHandler->SelectedRange(); 3462 3463 NS_OBJC_END_TRY_BLOCK_RETURN(NSMakeRange(0, 0)); 3464} 3465 3466- (BOOL)drawsVerticallyForCharacterAtIndex:(NSUInteger)charIndex { 3467 NS_ENSURE_TRUE(mTextInputHandler, NO); 3468 if (charIndex == NSNotFound) { 3469 return NO; 3470 } 3471 return mTextInputHandler->DrawsVerticallyForCharacterAtIndex(charIndex); 3472} 3473 3474- (NSUInteger)characterIndexForPoint:(NSPoint)thePoint { 3475 NS_ENSURE_TRUE(mTextInputHandler, 0); 3476 return mTextInputHandler->CharacterIndexForPoint(thePoint); 3477} 3478 3479- (NSArray*)validAttributesForMarkedText { 3480 NS_OBJC_BEGIN_TRY_BLOCK_RETURN; 3481 3482 NS_ENSURE_TRUE(mTextInputHandler, [NSArray array]); 3483 return mTextInputHandler->GetValidAttributesForMarkedText(); 3484 3485 NS_OBJC_END_TRY_BLOCK_RETURN(nil); 3486} 3487 3488- (void)insertText:(id)aString replacementRange:(NSRange)replacementRange { 3489 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; 3490 3491 NS_ENSURE_TRUE_VOID(mGeckoChild); 3492 3493 nsAutoRetainCocoaObject kungFuDeathGrip(self); 3494 3495 NSAttributedString* attrStr; 3496 if ([aString isKindOfClass:[NSAttributedString class]]) { 3497 attrStr = static_cast<NSAttributedString*>(aString); 3498 } else { 3499 attrStr = [[[NSAttributedString alloc] initWithString:aString] autorelease]; 3500 } 3501 3502 mTextInputHandler->InsertText(attrStr, &replacementRange); 3503 3504 NS_OBJC_END_TRY_IGNORE_BLOCK; 3505} 3506 3507- (void)doCommandBySelector:(SEL)aSelector { 3508 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; 3509 3510 if (!mGeckoChild || !mTextInputHandler) { 3511 return; 3512 } 3513 3514 const char* sel = reinterpret_cast<const char*>(aSelector); 3515 if (!mTextInputHandler->DoCommandBySelector(sel)) { 3516 [super doCommandBySelector:aSelector]; 3517 } 3518 3519 NS_OBJC_END_TRY_IGNORE_BLOCK; 3520} 3521 3522- (void)unmarkText { 3523 NS_ENSURE_TRUE_VOID(mTextInputHandler); 3524 mTextInputHandler->CommitIMEComposition(); 3525} 3526 3527- (BOOL)hasMarkedText { 3528 NS_ENSURE_TRUE(mTextInputHandler, NO); 3529 return mTextInputHandler->HasMarkedText(); 3530} 3531 3532- (void)setMarkedText:(id)aString 3533 selectedRange:(NSRange)selectedRange 3534 replacementRange:(NSRange)replacementRange { 3535 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; 3536 3537 NS_ENSURE_TRUE_VOID(mTextInputHandler); 3538 3539 nsAutoRetainCocoaObject kungFuDeathGrip(self); 3540 3541 NSAttributedString* attrStr; 3542 if ([aString isKindOfClass:[NSAttributedString class]]) { 3543 attrStr = static_cast<NSAttributedString*>(aString); 3544 } else { 3545 attrStr = [[[NSAttributedString alloc] initWithString:aString] autorelease]; 3546 } 3547 3548 mTextInputHandler->SetMarkedText(attrStr, selectedRange, &replacementRange); 3549 3550 NS_OBJC_END_TRY_IGNORE_BLOCK; 3551} 3552 3553- (NSAttributedString*)attributedSubstringForProposedRange:(NSRange)aRange 3554 actualRange:(NSRangePointer)actualRange { 3555 NS_ENSURE_TRUE(mTextInputHandler, nil); 3556 return mTextInputHandler->GetAttributedSubstringFromRange(aRange, actualRange); 3557} 3558 3559- (NSRect)firstRectForCharacterRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange { 3560 NS_ENSURE_TRUE(mTextInputHandler, NSMakeRect(0.0, 0.0, 0.0, 0.0)); 3561 return mTextInputHandler->FirstRectForCharacterRange(aRange, actualRange); 3562} 3563 3564- (void)quickLookWithEvent:(NSEvent*)event { 3565 // Show dictionary by current point 3566 WidgetContentCommandEvent contentCommandEvent(true, eContentCommandLookUpDictionary, mGeckoChild); 3567 NSPoint point = [self convertPoint:[event locationInWindow] fromView:nil]; 3568 contentCommandEvent.mRefPoint = mGeckoChild->CocoaPointsToDevPixels(point); 3569 mGeckoChild->DispatchWindowEvent(contentCommandEvent); 3570 // The widget might have been destroyed. 3571} 3572 3573- (NSInteger)windowLevel { 3574 NS_OBJC_BEGIN_TRY_BLOCK_RETURN; 3575 3576 NS_ENSURE_TRUE(mTextInputHandler, [[self window] level]); 3577 return mTextInputHandler->GetWindowLevel(); 3578 3579 NS_OBJC_END_TRY_BLOCK_RETURN(NSNormalWindowLevel); 3580} 3581 3582#pragma mark - 3583 3584// This is a private API that Cocoa uses. 3585// Cocoa will call this after the menu system returns "NO" for "performKeyEquivalent:". 3586// We want all they key events we can get so just return YES. In particular, this fixes 3587// ctrl-tab - we don't get a "keyDown:" call for that without this. 3588- (BOOL)_wantsKeyDownForEvent:(NSEvent*)event { 3589 return YES; 3590} 3591 3592- (NSEvent*)lastKeyDownEvent { 3593 return mLastKeyDownEvent; 3594} 3595 3596- (void)keyDown:(NSEvent*)theEvent { 3597 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; 3598 3599 [mLastKeyDownEvent release]; 3600 mLastKeyDownEvent = [theEvent retain]; 3601 3602 // Weird things can happen on keyboard input if the key window isn't in the 3603 // current space. For example see bug 1056251. To get around this, always 3604 // make sure that, if our window is key, it's also made frontmost. Doing 3605 // this automatically switches to whatever space our window is in. Safari 3606 // does something similar. Our window should normally always be key -- 3607 // otherwise why is the OS sending us a key down event? But it's just 3608 // possible we're in Gecko's hidden window, so we check first. 3609 NSWindow* viewWindow = [self window]; 3610 if (viewWindow && [viewWindow isKeyWindow]) { 3611 [viewWindow orderWindow:NSWindowAbove relativeTo:0]; 3612 } 3613 3614#if !defined(RELEASE_OR_BETA) || defined(DEBUG) 3615 if (!Preferences::GetBool("intl.allow-insecure-text-input", false) && mGeckoChild && 3616 mTextInputHandler && mTextInputHandler->IsFocused()) { 3617 NSWindow* window = [self window]; 3618 NSString* info = [NSString 3619 stringWithFormat: 3620 @"\nview [%@], window [%@], window is key %i, is fullscreen %i, app is active %i", self, 3621 window, [window isKeyWindow], ([window styleMask] & NSWindowStyleMaskFullScreen) != 0, 3622 [NSApp isActive]]; 3623 nsAutoCString additionalInfo([info UTF8String]); 3624 3625 if (mGeckoChild->GetInputContext().IsPasswordEditor() && 3626 !TextInputHandler::IsSecureEventInputEnabled()) { 3627# define CRASH_MESSAGE "A password editor has focus, but not in secure input mode" 3628 3629 CrashReporter::AppendAppNotesToCrashReport("\nBug 893973: "_ns + 3630 nsLiteralCString(CRASH_MESSAGE)); 3631 CrashReporter::AppendAppNotesToCrashReport(additionalInfo); 3632 3633 MOZ_CRASH(CRASH_MESSAGE); 3634# undef CRASH_MESSAGE 3635 } else if (!mGeckoChild->GetInputContext().IsPasswordEditor() && 3636 TextInputHandler::IsSecureEventInputEnabled()) { 3637# define CRASH_MESSAGE "A non-password editor has focus, but in secure input mode" 3638 3639 CrashReporter::AppendAppNotesToCrashReport("\nBug 893973: "_ns + 3640 nsLiteralCString(CRASH_MESSAGE)); 3641 CrashReporter::AppendAppNotesToCrashReport(additionalInfo); 3642 3643 MOZ_CRASH(CRASH_MESSAGE); 3644# undef CRASH_MESSAGE 3645 } 3646 } 3647#endif // #if !defined(RELEASE_OR_BETA) || defined(DEBUG) 3648 3649 nsAutoRetainCocoaObject kungFuDeathGrip(self); 3650 if (mGeckoChild) { 3651 if (mTextInputHandler) { 3652 sUniqueKeyEventId++; 3653 NSMutableDictionary* nativeKeyEventsMap = [ChildView sNativeKeyEventsMap]; 3654 [nativeKeyEventsMap setObject:theEvent forKey:@(sUniqueKeyEventId)]; 3655 // Purge old native events, in case we're still holding on to them. We 3656 // keep at most 10 references to 10 different native events. 3657 [nativeKeyEventsMap removeObjectForKey:@(sUniqueKeyEventId - 10)]; 3658 mTextInputHandler->HandleKeyDownEvent(theEvent, sUniqueKeyEventId); 3659 } else { 3660 // There was no text input handler. Offer the event to the native menu 3661 // system to check if there are any registered custom shortcuts for this 3662 // event. 3663 mGeckoChild->SendEventToNativeMenuSystem(theEvent); 3664 } 3665 } 3666 3667 NS_OBJC_END_TRY_IGNORE_BLOCK; 3668} 3669 3670- (void)keyUp:(NSEvent*)theEvent { 3671 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; 3672 3673 NS_ENSURE_TRUE(mGeckoChild, ); 3674 3675 nsAutoRetainCocoaObject kungFuDeathGrip(self); 3676 3677 mTextInputHandler->HandleKeyUpEvent(theEvent); 3678 3679 NS_OBJC_END_TRY_IGNORE_BLOCK; 3680} 3681 3682- (void)insertNewline:(id)sender { 3683 if (mTextInputHandler) { 3684 mTextInputHandler->HandleCommand(Command::InsertParagraph); 3685 } 3686} 3687 3688- (void)insertLineBreak:(id)sender { 3689 // Ctrl + Enter in the default settings. 3690 if (mTextInputHandler) { 3691 mTextInputHandler->HandleCommand(Command::InsertLineBreak); 3692 } 3693} 3694 3695- (void)deleteBackward:(id)sender { 3696 // Backspace in the default settings. 3697 if (mTextInputHandler) { 3698 mTextInputHandler->HandleCommand(Command::DeleteCharBackward); 3699 } 3700} 3701 3702- (void)deleteBackwardByDecomposingPreviousCharacter:(id)sender { 3703 // Ctrl + Backspace in the default settings. 3704 if (mTextInputHandler) { 3705 mTextInputHandler->HandleCommand(Command::DeleteCharBackward); 3706 } 3707} 3708 3709- (void)deleteWordBackward:(id)sender { 3710 // Alt + Backspace in the default settings. 3711 if (mTextInputHandler) { 3712 mTextInputHandler->HandleCommand(Command::DeleteWordBackward); 3713 } 3714} 3715 3716- (void)deleteToBeginningOfBackward:(id)sender { 3717 // Command + Backspace in the default settings. 3718 if (mTextInputHandler) { 3719 mTextInputHandler->HandleCommand(Command::DeleteToBeginningOfLine); 3720 } 3721} 3722 3723- (void)deleteForward:(id)sender { 3724 // Delete in the default settings. 3725 if (mTextInputHandler) { 3726 mTextInputHandler->HandleCommand(Command::DeleteCharForward); 3727 } 3728} 3729 3730- (void)deleteWordForward:(id)sender { 3731 // Alt + Delete in the default settings. 3732 if (mTextInputHandler) { 3733 mTextInputHandler->HandleCommand(Command::DeleteWordForward); 3734 } 3735} 3736 3737- (void)insertTab:(id)sender { 3738 // Tab in the default settings. 3739 if (mTextInputHandler) { 3740 mTextInputHandler->HandleCommand(Command::InsertTab); 3741 } 3742} 3743 3744- (void)insertBacktab:(id)sender { 3745 // Shift + Tab in the default settings. 3746 if (mTextInputHandler) { 3747 mTextInputHandler->HandleCommand(Command::InsertBacktab); 3748 } 3749} 3750 3751- (void)moveRight:(id)sender { 3752 // RightArrow in the default settings. 3753 if (mTextInputHandler) { 3754 mTextInputHandler->HandleCommand(Command::CharNext); 3755 } 3756} 3757 3758- (void)moveRightAndModifySelection:(id)sender { 3759 // Shift + RightArrow in the default settings. 3760 if (mTextInputHandler) { 3761 mTextInputHandler->HandleCommand(Command::SelectCharNext); 3762 } 3763} 3764 3765- (void)moveWordRight:(id)sender { 3766 // Alt + RightArrow in the default settings. 3767 if (mTextInputHandler) { 3768 mTextInputHandler->HandleCommand(Command::WordNext); 3769 } 3770} 3771 3772- (void)moveWordRightAndModifySelection:(id)sender { 3773 // Alt + Shift + RightArrow in the default settings. 3774 if (mTextInputHandler) { 3775 mTextInputHandler->HandleCommand(Command::SelectWordNext); 3776 } 3777} 3778 3779- (void)moveToRightEndOfLine:(id)sender { 3780 // Command + RightArrow in the default settings. 3781 if (mTextInputHandler) { 3782 mTextInputHandler->HandleCommand(Command::EndLine); 3783 } 3784} 3785 3786- (void)moveToRightEndOfLineAndModifySelection:(id)sender { 3787 // Command + Shift + RightArrow in the default settings. 3788 if (mTextInputHandler) { 3789 mTextInputHandler->HandleCommand(Command::SelectEndLine); 3790 } 3791} 3792 3793- (void)moveLeft:(id)sender { 3794 // LeftArrow in the default settings. 3795 if (mTextInputHandler) { 3796 mTextInputHandler->HandleCommand(Command::CharPrevious); 3797 } 3798} 3799 3800- (void)moveLeftAndModifySelection:(id)sender { 3801 // Shift + LeftArrow in the default settings. 3802 if (mTextInputHandler) { 3803 mTextInputHandler->HandleCommand(Command::SelectCharPrevious); 3804 } 3805} 3806 3807- (void)moveWordLeft:(id)sender { 3808 // Alt + LeftArrow in the default settings. 3809 if (mTextInputHandler) { 3810 mTextInputHandler->HandleCommand(Command::WordPrevious); 3811 } 3812} 3813 3814- (void)moveWordLeftAndModifySelection:(id)sender { 3815 // Alt + Shift + LeftArrow in the default settings. 3816 if (mTextInputHandler) { 3817 mTextInputHandler->HandleCommand(Command::SelectWordPrevious); 3818 } 3819} 3820 3821- (void)moveToLeftEndOfLine:(id)sender { 3822 // Command + LeftArrow in the default settings. 3823 if (mTextInputHandler) { 3824 mTextInputHandler->HandleCommand(Command::BeginLine); 3825 } 3826} 3827 3828- (void)moveToLeftEndOfLineAndModifySelection:(id)sender { 3829 // Command + Shift + LeftArrow in the default settings. 3830 if (mTextInputHandler) { 3831 mTextInputHandler->HandleCommand(Command::SelectBeginLine); 3832 } 3833} 3834 3835- (void)moveUp:(id)sender { 3836 // ArrowUp in the default settings. 3837 if (mTextInputHandler) { 3838 mTextInputHandler->HandleCommand(Command::LinePrevious); 3839 } 3840} 3841 3842- (void)moveUpAndModifySelection:(id)sender { 3843 // Shift + ArrowUp in the default settings. 3844 if (mTextInputHandler) { 3845 mTextInputHandler->HandleCommand(Command::SelectLinePrevious); 3846 } 3847} 3848 3849- (void)moveToBeginningOfDocument:(id)sender { 3850 // Command + ArrowUp in the default settings. 3851 if (mTextInputHandler) { 3852 mTextInputHandler->HandleCommand(Command::MoveTop); 3853 } 3854} 3855 3856- (void)moveToBeginningOfDocumentAndModifySelection:(id)sender { 3857 // Command + Shift + ArrowUp or Shift + Home in the default settings. 3858 if (mTextInputHandler) { 3859 mTextInputHandler->HandleCommand(Command::SelectTop); 3860 } 3861} 3862 3863- (void)moveDown:(id)sender { 3864 // ArrowDown in the default settings. 3865 if (mTextInputHandler) { 3866 mTextInputHandler->HandleCommand(Command::LineNext); 3867 } 3868} 3869 3870- (void)moveDownAndModifySelection:(id)sender { 3871 // Shift + ArrowDown in the default settings. 3872 if (mTextInputHandler) { 3873 mTextInputHandler->HandleCommand(Command::SelectLineNext); 3874 } 3875} 3876 3877- (void)moveToEndOfDocument:(id)sender { 3878 // Command + ArrowDown in the default settings. 3879 if (mTextInputHandler) { 3880 mTextInputHandler->HandleCommand(Command::MoveBottom); 3881 } 3882} 3883 3884- (void)moveToEndOfDocumentAndModifySelection:(id)sender { 3885 // Command + Shift + ArrowDown or Shift + End in the default settings. 3886 if (mTextInputHandler) { 3887 mTextInputHandler->HandleCommand(Command::SelectBottom); 3888 } 3889} 3890 3891- (void)scrollPageUp:(id)sender { 3892 // PageUp in the default settings. 3893 if (mTextInputHandler) { 3894 mTextInputHandler->HandleCommand(Command::ScrollPageUp); 3895 } 3896} 3897 3898- (void)pageUpAndModifySelection:(id)sender { 3899 // Shift + PageUp in the default settings. 3900 if (mTextInputHandler) { 3901 mTextInputHandler->HandleCommand(Command::SelectPageUp); 3902 } 3903} 3904 3905- (void)scrollPageDown:(id)sender { 3906 // PageDown in the default settings. 3907 if (mTextInputHandler) { 3908 mTextInputHandler->HandleCommand(Command::ScrollPageDown); 3909 } 3910} 3911 3912- (void)pageDownAndModifySelection:(id)sender { 3913 // Shift + PageDown in the default settings. 3914 if (mTextInputHandler) { 3915 mTextInputHandler->HandleCommand(Command::SelectPageDown); 3916 } 3917} 3918 3919- (void)scrollToEndOfDocument:(id)sender { 3920 // End in the default settings. 3921 if (mTextInputHandler) { 3922 mTextInputHandler->HandleCommand(Command::ScrollBottom); 3923 } 3924} 3925 3926- (void)scrollToBeginningOfDocument:(id)sender { 3927 // Home in the default settings. 3928 if (mTextInputHandler) { 3929 mTextInputHandler->HandleCommand(Command::ScrollTop); 3930 } 3931} 3932 3933// XXX Don't decleare nor implement calcelOperation: because it 3934// causes not calling keyDown: for Command + Period. 3935// We need to handle it from doCommandBySelector:. 3936 3937- (void)complete:(id)sender { 3938 // Alt + Escape or Alt + Shift + Escape in the default settings. 3939 if (mTextInputHandler) { 3940 mTextInputHandler->HandleCommand(Command::Complete); 3941 } 3942} 3943 3944- (void)flagsChanged:(NSEvent*)theEvent { 3945 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; 3946 3947 NS_ENSURE_TRUE(mGeckoChild, ); 3948 3949 nsAutoRetainCocoaObject kungFuDeathGrip(self); 3950 mTextInputHandler->HandleFlagsChanged(theEvent); 3951 3952 NS_OBJC_END_TRY_IGNORE_BLOCK; 3953} 3954 3955- (BOOL)isFirstResponder { 3956 NS_OBJC_BEGIN_TRY_BLOCK_RETURN; 3957 3958 NSResponder* resp = [[self window] firstResponder]; 3959 return (resp == (NSResponder*)self); 3960 3961 NS_OBJC_END_TRY_BLOCK_RETURN(NO); 3962} 3963 3964- (BOOL)isDragInProgress { 3965 if (!mDragService) return NO; 3966 3967 nsCOMPtr<nsIDragSession> dragSession; 3968 mDragService->GetCurrentSession(getter_AddRefs(dragSession)); 3969 return dragSession != nullptr; 3970} 3971 3972- (BOOL)inactiveWindowAcceptsMouseEvent:(NSEvent*)aEvent { 3973 // If we're being destroyed assume the default -- return YES. 3974 if (!mGeckoChild) return YES; 3975 3976 WidgetMouseEvent geckoEvent(true, eMouseActivate, mGeckoChild, WidgetMouseEvent::eReal); 3977 [self convertCocoaMouseEvent:aEvent toGeckoEvent:&geckoEvent]; 3978 return (mGeckoChild->DispatchInputEvent(&geckoEvent).mContentStatus != 3979 nsEventStatus_eConsumeNoDefault); 3980} 3981 3982// We must always call through to our superclass, even when mGeckoChild is 3983// nil -- otherwise the keyboard focus can end up in the wrong NSView. 3984- (BOOL)becomeFirstResponder { 3985 NS_OBJC_BEGIN_TRY_BLOCK_RETURN; 3986 3987 return [super becomeFirstResponder]; 3988 3989 NS_OBJC_END_TRY_BLOCK_RETURN(YES); 3990} 3991 3992- (void)viewsWindowDidBecomeKey { 3993 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; 3994 3995 if (!mGeckoChild) return; 3996 3997 nsAutoRetainCocoaObject kungFuDeathGrip(self); 3998 3999 // check to see if the window implements the mozWindow protocol. This 4000 // allows embedders to avoid re-entrant calls to -makeKeyAndOrderFront, 4001 // which can happen because these activate calls propagate out 4002 // to the embedder via nsIEmbeddingSiteWindow::SetFocus(). 4003 BOOL isMozWindow = [[self window] respondsToSelector:@selector(setSuppressMakeKeyFront:)]; 4004 if (isMozWindow) [[self window] setSuppressMakeKeyFront:YES]; 4005 4006 nsIWidgetListener* listener = mGeckoChild->GetWidgetListener(); 4007 if (listener) listener->WindowActivated(); 4008 4009 if (isMozWindow) [[self window] setSuppressMakeKeyFront:NO]; 4010 4011 if (mGeckoChild->GetInputContext().IsPasswordEditor()) { 4012 TextInputHandler::EnableSecureEventInput(); 4013 } else { 4014 TextInputHandler::EnsureSecureEventInputDisabled(); 4015 } 4016 4017 NS_OBJC_END_TRY_IGNORE_BLOCK; 4018} 4019 4020- (void)viewsWindowDidResignKey { 4021 if (!mGeckoChild) return; 4022 4023 nsAutoRetainCocoaObject kungFuDeathGrip(self); 4024 4025 nsIWidgetListener* listener = mGeckoChild->GetWidgetListener(); 4026 if (listener) listener->WindowDeactivated(); 4027 4028 TextInputHandler::EnsureSecureEventInputDisabled(); 4029} 4030 4031// If the call to removeFromSuperview isn't delayed from nsChildView:: 4032// TearDownView(), the NSView hierarchy might get changed during calls to 4033// [ChildView drawRect:], which leads to "beyond bounds" exceptions in 4034// NSCFArray. For more info see bmo bug 373122. Apple's docs claim that 4035// removeFromSuperviewWithoutNeedingDisplay "can be safely invoked during 4036// display" (whatever "display" means). But it's _not_ true that it can be 4037// safely invoked during calls to [NSView drawRect:]. We use 4038// removeFromSuperview here because there's no longer any danger of being 4039// "invoked during display", and because doing do clears up bmo bug 384343. 4040- (void)delayedTearDown { 4041 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; 4042 4043 [self removeFromSuperview]; 4044 [self release]; 4045 4046 NS_OBJC_END_TRY_IGNORE_BLOCK; 4047} 4048 4049#pragma mark - 4050 4051// drag'n'drop stuff 4052#define kDragServiceContractID "@mozilla.org/widget/dragservice;1" 4053 4054- (NSDragOperation)dragOperationFromDragAction:(int32_t)aDragAction { 4055 if (nsIDragService::DRAGDROP_ACTION_LINK & aDragAction) return NSDragOperationLink; 4056 if (nsIDragService::DRAGDROP_ACTION_COPY & aDragAction) return NSDragOperationCopy; 4057 if (nsIDragService::DRAGDROP_ACTION_MOVE & aDragAction) return NSDragOperationGeneric; 4058 return NSDragOperationNone; 4059} 4060 4061- (LayoutDeviceIntPoint)convertWindowCoordinates:(NSPoint)aPoint { 4062 if (!mGeckoChild) { 4063 return LayoutDeviceIntPoint(0, 0); 4064 } 4065 4066 NSPoint localPoint = [self convertPoint:aPoint fromView:nil]; 4067 return mGeckoChild->CocoaPointsToDevPixels(localPoint); 4068} 4069 4070- (LayoutDeviceIntPoint)convertWindowCoordinatesRoundDown:(NSPoint)aPoint { 4071 if (!mGeckoChild) { 4072 return LayoutDeviceIntPoint(0, 0); 4073 } 4074 4075 NSPoint localPoint = [self convertPoint:aPoint fromView:nil]; 4076 return mGeckoChild->CocoaPointsToDevPixelsRoundDown(localPoint); 4077} 4078 4079// This is a utility function used by NSView drag event methods 4080// to send events. It contains all of the logic needed for Gecko 4081// dragging to work. Returns the appropriate cocoa drag operation code. 4082- (NSDragOperation)doDragAction:(EventMessage)aMessage sender:(id)aSender { 4083 NS_OBJC_BEGIN_TRY_BLOCK_RETURN; 4084 4085 if (!mGeckoChild) return NSDragOperationNone; 4086 4087 MOZ_LOG(sCocoaLog, LogLevel::Info, ("ChildView doDragAction: entered\n")); 4088 4089 if (!mDragService) { 4090 CallGetService(kDragServiceContractID, &mDragService); 4091 NS_ASSERTION(mDragService, "Couldn't get a drag service - big problem!"); 4092 if (!mDragService) return NSDragOperationNone; 4093 } 4094 4095 if (aMessage == eDragEnter) { 4096 mDragService->StartDragSession(); 4097 } 4098 4099 nsCOMPtr<nsIDragSession> dragSession; 4100 mDragService->GetCurrentSession(getter_AddRefs(dragSession)); 4101 if (dragSession) { 4102 if (aMessage == eDragOver) { 4103 // fire the drag event at the source. Just ignore whether it was 4104 // cancelled or not as there isn't actually a means to stop the drag 4105 nsCOMPtr<nsIDragService> dragService = mDragService; 4106 dragService->FireDragEventAtSource(eDrag, 4107 nsCocoaUtils::ModifiersForEvent([NSApp currentEvent])); 4108 dragSession->SetCanDrop(false); 4109 } else if (aMessage == eDrop) { 4110 // We make the assumption that the dragOver handlers have correctly set 4111 // the |canDrop| property of the Drag Session. 4112 bool canDrop = false; 4113 if (!NS_SUCCEEDED(dragSession->GetCanDrop(&canDrop)) || !canDrop) { 4114 [self doDragAction:eDragExit sender:aSender]; 4115 4116 nsCOMPtr<nsINode> sourceNode; 4117 dragSession->GetSourceNode(getter_AddRefs(sourceNode)); 4118 if (!sourceNode) { 4119 nsCOMPtr<nsIDragService> dragService = mDragService; 4120 dragService->EndDragSession(false, nsCocoaUtils::ModifiersForEvent([NSApp currentEvent])); 4121 } 4122 return NSDragOperationNone; 4123 } 4124 } 4125 4126 unsigned int modifierFlags = [[NSApp currentEvent] modifierFlags]; 4127 uint32_t action = nsIDragService::DRAGDROP_ACTION_MOVE; 4128 // force copy = option, alias = cmd-option, default is move 4129 if (modifierFlags & NSEventModifierFlagOption) { 4130 if (modifierFlags & NSEventModifierFlagCommand) 4131 action = nsIDragService::DRAGDROP_ACTION_LINK; 4132 else 4133 action = nsIDragService::DRAGDROP_ACTION_COPY; 4134 } 4135 dragSession->SetDragAction(action); 4136 } 4137 4138 // set up gecko event 4139 WidgetDragEvent geckoEvent(true, aMessage, mGeckoChild); 4140 nsCocoaUtils::InitInputEvent(geckoEvent, [NSApp currentEvent]); 4141 4142 // Use our own coordinates in the gecko event. 4143 // Convert event from gecko global coords to gecko view coords. 4144 NSPoint draggingLoc = [aSender draggingLocation]; 4145 4146 geckoEvent.mRefPoint = [self convertWindowCoordinates:draggingLoc]; 4147 4148 nsAutoRetainCocoaObject kungFuDeathGrip(self); 4149 mGeckoChild->DispatchInputEvent(&geckoEvent); 4150 if (!mGeckoChild) return NSDragOperationNone; 4151 4152 if (dragSession) { 4153 switch (aMessage) { 4154 case eDragEnter: 4155 case eDragOver: { 4156 uint32_t dragAction; 4157 dragSession->GetDragAction(&dragAction); 4158 4159 // If TakeChildProcessDragAction returns something other than 4160 // DRAGDROP_ACTION_UNINITIALIZED, it means that the last event was sent 4161 // to the child process and this event is also being sent to the child 4162 // process. In this case, use the last event's action instead. 4163 nsDragService* dragService = static_cast<nsDragService*>(mDragService); 4164 int32_t childDragAction = dragService->TakeChildProcessDragAction(); 4165 if (childDragAction != nsIDragService::DRAGDROP_ACTION_UNINITIALIZED) { 4166 dragAction = childDragAction; 4167 } 4168 4169 return [self dragOperationFromDragAction:dragAction]; 4170 } 4171 case eDragExit: 4172 case eDrop: { 4173 nsCOMPtr<nsINode> sourceNode; 4174 dragSession->GetSourceNode(getter_AddRefs(sourceNode)); 4175 if (!sourceNode) { 4176 // We're leaving a window while doing a drag that was 4177 // initiated in a different app. End the drag session, 4178 // since we're done with it for now (until the user 4179 // drags back into mozilla). 4180 nsCOMPtr<nsIDragService> dragService = mDragService; 4181 dragService->EndDragSession(false, nsCocoaUtils::ModifiersForEvent([NSApp currentEvent])); 4182 } 4183 break; 4184 } 4185 default: 4186 break; 4187 } 4188 } 4189 4190 return NSDragOperationGeneric; 4191 4192 NS_OBJC_END_TRY_BLOCK_RETURN(NSDragOperationNone); 4193} 4194 4195// NSDraggingDestination 4196- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender { 4197 NS_OBJC_BEGIN_TRY_BLOCK_RETURN; 4198 4199 MOZ_LOG(sCocoaLog, LogLevel::Info, ("ChildView draggingEntered: entered\n")); 4200 4201 // there should never be a globalDragPboard when "draggingEntered:" is 4202 // called, but just in case we'll take care of it here. 4203 [globalDragPboard release]; 4204 4205 // Set the global drag pasteboard that will be used for this drag session. 4206 // This will be set back to nil when the drag session ends (mouse exits 4207 // the view or a drop happens within the view). 4208 globalDragPboard = [[sender draggingPasteboard] retain]; 4209 4210 return [self doDragAction:eDragEnter sender:sender]; 4211 4212 NS_OBJC_END_TRY_BLOCK_RETURN(NSDragOperationNone); 4213} 4214 4215// NSDraggingDestination 4216- (NSDragOperation)draggingUpdated:(id<NSDraggingInfo>)sender { 4217 MOZ_LOG(sCocoaLog, LogLevel::Info, ("ChildView draggingUpdated: entered\n")); 4218 return [self doDragAction:eDragOver sender:sender]; 4219} 4220 4221// NSDraggingDestination 4222- (void)draggingExited:(id<NSDraggingInfo>)sender { 4223 MOZ_LOG(sCocoaLog, LogLevel::Info, ("ChildView draggingExited: entered\n")); 4224 4225 nsAutoRetainCocoaObject kungFuDeathGrip(self); 4226 [self doDragAction:eDragExit sender:sender]; 4227 NS_IF_RELEASE(mDragService); 4228} 4229 4230// NSDraggingDestination 4231- (BOOL)performDragOperation:(id<NSDraggingInfo>)sender { 4232 nsAutoRetainCocoaObject kungFuDeathGrip(self); 4233 BOOL handled = [self doDragAction:eDrop sender:sender] != NSDragOperationNone; 4234 NS_IF_RELEASE(mDragService); 4235 return handled; 4236} 4237 4238// NSDraggingSource 4239// This is just implemented so we comply with the NSDraggingSource protocol. 4240- (NSDragOperation)draggingSession:(NSDraggingSession*)session 4241 sourceOperationMaskForDraggingContext:(NSDraggingContext)context { 4242 return UINT_MAX; 4243} 4244 4245// NSDraggingSource 4246- (BOOL)ignoreModifierKeysForDraggingSession:(NSDraggingSession*)session { 4247 return YES; 4248} 4249 4250// NSDraggingSource 4251- (void)draggingSession:(NSDraggingSession*)aSession 4252 endedAtPoint:(NSPoint)aPoint 4253 operation:(NSDragOperation)aOperation { 4254 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; 4255 4256#ifdef NIGHTLY_BUILD 4257 MOZ_RELEASE_ASSERT(NS_IsMainThread()); 4258#endif 4259 4260 gDraggedTransferables = nullptr; 4261 4262 NSEvent* currentEvent = [NSApp currentEvent]; 4263 gUserCancelledDrag = 4264 ([currentEvent type] == NSEventTypeKeyDown && [currentEvent keyCode] == kVK_Escape); 4265 4266 if (!mDragService) { 4267 CallGetService(kDragServiceContractID, &mDragService); 4268 NS_ASSERTION(mDragService, "Couldn't get a drag service - big problem!"); 4269 } 4270 4271 if (mDragService) { 4272 RefPtr<nsDragService> dragService = static_cast<nsDragService*>(mDragService); 4273 4274 // Set the dragend point from the current mouse location 4275 // FIXME(emilio): Weird that we wouldn't use aPoint instead? Seems to work 4276 // locally as well... 4277 // NSPoint pnt = aPoint; 4278 NSPoint pnt = [NSEvent mouseLocation]; 4279 NSPoint locationInWindow = nsCocoaUtils::ConvertPointFromScreen([self window], pnt); 4280 FlipCocoaScreenCoordinate(pnt); 4281 dragService->SetDragEndPoint([self convertWindowCoordinates:locationInWindow]); 4282 4283 // XXX: dropEffect should be updated per |aOperation|. 4284 // As things stand though, |aOperation| isn't well handled within "our" 4285 // events, that is, when the drop happens within the window: it is set 4286 // either to NSDragOperationGeneric or to NSDragOperationNone. 4287 // For that reason, it's not yet possible to override dropEffect per the 4288 // given OS value, and it's also unclear what's the correct dropEffect 4289 // value for NSDragOperationGeneric that is passed by other applications. 4290 // All that said, NSDragOperationNone is still reliable. 4291 if (aOperation == NSDragOperationNone) { 4292 RefPtr<dom::DataTransfer> dataTransfer = dragService->GetDataTransfer(); 4293 if (dataTransfer) { 4294 dataTransfer->SetDropEffectInt(nsIDragService::DRAGDROP_ACTION_NONE); 4295 } 4296 } 4297 4298 dragService->EndDragSession(true, nsCocoaUtils::ModifiersForEvent(currentEvent)); 4299 NS_RELEASE(mDragService); 4300 } 4301 4302 [globalDragPboard release]; 4303 globalDragPboard = nil; 4304 [gLastDragMouseDownEvent release]; 4305 gLastDragMouseDownEvent = nil; 4306 4307 NS_OBJC_END_TRY_IGNORE_BLOCK; 4308} 4309 4310// NSDraggingSource 4311- (void)draggingSession:(NSDraggingSession*)aSession movedToPoint:(NSPoint)aPoint { 4312 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; 4313 4314 // Get the drag service if it isn't already cached. The drag service 4315 // isn't cached when dragging over a different application. 4316 nsCOMPtr<nsIDragService> dragService = mDragService; 4317 if (!dragService) { 4318 dragService = do_GetService(kDragServiceContractID); 4319 } 4320 4321 if (dragService) { 4322 nsDragService* ds = static_cast<nsDragService*>(dragService.get()); 4323 ds->DragMovedWithView(aSession, aPoint); 4324 } 4325 4326 NS_OBJC_END_TRY_IGNORE_BLOCK; 4327} 4328 4329// NSDraggingSource 4330- (void)draggingSession:(NSDraggingSession*)aSession willBeginAtPoint:(NSPoint)aPoint { 4331 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; 4332 4333 // there should never be a globalDragPboard when "willBeginAtPoint:" is 4334 // called, but just in case we'll take care of it here. 4335 [globalDragPboard release]; 4336 4337 // Set the global drag pasteboard that will be used for this drag session. 4338 // This will be set back to nil when the drag session ends (mouse exits 4339 // the view or a drop happens within the view). 4340 globalDragPboard = [[aSession draggingPasteboard] retain]; 4341 4342 NS_OBJC_END_TRY_IGNORE_BLOCK; 4343} 4344 4345// Get the paste location from the low level pasteboard. 4346static CFTypeRefPtr<CFURLRef> GetPasteLocation(NSPasteboard* aPasteboard) { 4347 PasteboardRef pboardRef = nullptr; 4348 PasteboardCreate((CFStringRef)[aPasteboard name], &pboardRef); 4349 if (!pboardRef) { 4350 return nullptr; 4351 } 4352 4353 auto pasteBoard = CFTypeRefPtr<PasteboardRef>::WrapUnderCreateRule(pboardRef); 4354 PasteboardSynchronize(pasteBoard.get()); 4355 4356 CFURLRef urlRef = nullptr; 4357 PasteboardCopyPasteLocation(pasteBoard.get(), &urlRef); 4358 return CFTypeRefPtr<CFURLRef>::WrapUnderCreateRule(urlRef); 4359} 4360 4361// NSPasteboardItemDataProvider 4362- (void)pasteboard:(NSPasteboard*)aPasteboard 4363 item:(NSPasteboardItem*)aItem 4364 provideDataForType:(NSString*)aType { 4365 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; 4366 4367#ifdef NIGHTLY_BUILD 4368 MOZ_RELEASE_ASSERT(NS_IsMainThread()); 4369#endif 4370 4371 if (!gDraggedTransferables) { 4372 return; 4373 } 4374 4375 uint32_t count = 0; 4376 gDraggedTransferables->GetLength(&count); 4377 4378 for (uint32_t j = 0; j < count; j++) { 4379 nsCOMPtr<nsITransferable> currentTransferable = do_QueryElementAt(gDraggedTransferables, j); 4380 if (!currentTransferable) { 4381 return; 4382 } 4383 4384 // Transform the transferable to an NSDictionary. 4385 NSDictionary* pasteboardOutputDict = 4386 nsClipboard::PasteboardDictFromTransferable(currentTransferable); 4387 if (!pasteboardOutputDict) { 4388 return; 4389 } 4390 4391 // Write everything out to the pasteboard. 4392 unsigned int typeCount = [pasteboardOutputDict count]; 4393 NSMutableArray* types = [NSMutableArray arrayWithCapacity:typeCount + 1]; 4394 [types addObjectsFromArray:[pasteboardOutputDict allKeys]]; 4395 [types addObject:[UTIHelper stringFromPboardType:kMozWildcardPboardType]]; 4396 for (unsigned int k = 0; k < typeCount; k++) { 4397 NSString* curType = [types objectAtIndex:k]; 4398 if ([curType isEqualToString:[UTIHelper stringFromPboardType:NSPasteboardTypeString]] || 4399 [curType isEqualToString:[UTIHelper stringFromPboardType:kPublicUrlPboardType]] || 4400 [curType isEqualToString:[UTIHelper stringFromPboardType:kPublicUrlNamePboardType]] || 4401 [curType isEqualToString:[UTIHelper stringFromPboardType:(NSString*)kUTTypeFileURL]]) { 4402 [aPasteboard setString:[pasteboardOutputDict valueForKey:curType] forType:curType]; 4403 } else if ([curType 4404 isEqualToString:[UTIHelper stringFromPboardType:kUrlsWithTitlesPboardType]]) { 4405 [aPasteboard setPropertyList:[pasteboardOutputDict valueForKey:curType] forType:curType]; 4406 } else if ([curType isEqualToString:[UTIHelper stringFromPboardType:NSPasteboardTypeHTML]]) { 4407 [aPasteboard setString:(nsClipboard::WrapHtmlForSystemPasteboard( 4408 [pasteboardOutputDict valueForKey:curType])) 4409 forType:curType]; 4410 } else if ([curType isEqualToString:[UTIHelper stringFromPboardType:NSPasteboardTypeTIFF]] || 4411 [curType 4412 isEqualToString:[UTIHelper stringFromPboardType:kMozCustomTypesPboardType]]) { 4413 [aPasteboard setData:[pasteboardOutputDict valueForKey:curType] forType:curType]; 4414 } else if ([curType 4415 isEqualToString:[UTIHelper stringFromPboardType:kMozFileUrlsPboardType]]) { 4416 [aPasteboard writeObjects:[pasteboardOutputDict valueForKey:curType]]; 4417 } else if ([curType 4418 isEqualToString:[UTIHelper 4419 stringFromPboardType:(NSString*) 4420 kPasteboardTypeFileURLPromise]]) { 4421 nsCOMPtr<nsIFile> targFile; 4422 NS_NewLocalFile(u""_ns, true, getter_AddRefs(targFile)); 4423 nsCOMPtr<nsILocalFileMac> macLocalFile = do_QueryInterface(targFile); 4424 if (!macLocalFile) { 4425 NS_ERROR("No Mac local file"); 4426 continue; 4427 } 4428 4429 CFTypeRefPtr<CFURLRef> url = GetPasteLocation(aPasteboard); 4430 if (!url) { 4431 continue; 4432 } 4433 4434 if (!NS_SUCCEEDED(macLocalFile->InitWithCFURL(url.get()))) { 4435 NS_ERROR("failed InitWithCFURL"); 4436 continue; 4437 } 4438 4439 if (!gDraggedTransferables) { 4440 continue; 4441 } 4442 4443 uint32_t transferableCount; 4444 nsresult rv = gDraggedTransferables->GetLength(&transferableCount); 4445 if (NS_FAILED(rv)) { 4446 continue; 4447 } 4448 4449 for (uint32_t i = 0; i < transferableCount; i++) { 4450 nsCOMPtr<nsITransferable> item = do_QueryElementAt(gDraggedTransferables, i); 4451 if (!item) { 4452 NS_ERROR("no transferable"); 4453 continue; 4454 } 4455 4456 item->SetTransferData(kFilePromiseDirectoryMime, macLocalFile); 4457 4458 // Now request the kFilePromiseMime data, which will invoke the data 4459 // provider. If successful, the file will have been created. 4460 nsCOMPtr<nsISupports> fileDataPrimitive; 4461 Unused << item->GetTransferData(kFilePromiseMime, getter_AddRefs(fileDataPrimitive)); 4462 } 4463 4464 [aPasteboard setPropertyList:[pasteboardOutputDict valueForKey:curType] forType:curType]; 4465 } 4466 } 4467 } 4468 4469 NS_OBJC_END_TRY_IGNORE_BLOCK; 4470} 4471 4472#pragma mark - 4473 4474// Support for the "Services" menu. We currently only support sending strings 4475// and HTML to system services. 4476// This method can be called on any thread (see bug 1751687). We can only usefully 4477// handle it on the main thread. 4478- (id)validRequestorForSendType:(NSString*)sendType returnType:(NSString*)returnType { 4479 NS_OBJC_BEGIN_TRY_BLOCK_RETURN; 4480 4481 if (!NS_IsMainThread()) { 4482 // We don't have any thread-safe ways of checking whether we can send 4483 // or receive content. Just say no. In normal cases, we expect this 4484 // method to be called on the main thread. 4485 return [super validRequestorForSendType:sendType returnType:returnType]; 4486 } 4487 4488 // sendType contains the type of data that the service would like this 4489 // application to send to it. sendType is nil if the service is not 4490 // requesting any data. 4491 // 4492 // returnType contains the type of data the the service would like to 4493 // return to this application (e.g., to overwrite the selection). 4494 // returnType is nil if the service will not return any data. 4495 // 4496 // The following condition thus triggers when the service expects a string 4497 // or HTML from us or no data at all AND when the service will either not 4498 // send back any data to us or will send a string or HTML back to us. 4499 4500 id result = nil; 4501 4502 NSString* stringType = [UTIHelper stringFromPboardType:NSPasteboardTypeString]; 4503 NSString* htmlType = [UTIHelper stringFromPboardType:NSPasteboardTypeHTML]; 4504 if ((!sendType || [sendType isEqualToString:stringType] || [sendType isEqualToString:htmlType]) && 4505 (!returnType || [returnType isEqualToString:stringType] || 4506 [returnType isEqualToString:htmlType])) { 4507 if (mGeckoChild) { 4508 // Assume that this object will be able to handle this request. 4509 result = self; 4510 4511 // Keep the ChildView alive during this operation. 4512 nsAutoRetainCocoaObject kungFuDeathGrip(self); 4513 4514 if (sendType) { 4515 // Determine if there is a current selection (chrome/content). 4516 if (!nsClipboard::sSelectionCache) { 4517 result = nil; 4518 } 4519 } 4520 4521 // Determine if we can paste (if receiving data from the service). 4522 if (mGeckoChild && returnType) { 4523 WidgetContentCommandEvent command(true, eContentCommandPasteTransferable, mGeckoChild, 4524 true); 4525 // This might possibly destroy our widget (and null out mGeckoChild). 4526 mGeckoChild->DispatchWindowEvent(command); 4527 if (!mGeckoChild || !command.mSucceeded || !command.mIsEnabled) result = nil; 4528 } 4529 } 4530 } 4531 4532 // Give the superclass a chance if this object will not handle this request. 4533 if (!result) result = [super validRequestorForSendType:sendType returnType:returnType]; 4534 4535 return result; 4536 4537 NS_OBJC_END_TRY_BLOCK_RETURN(nil); 4538} 4539 4540- (BOOL)writeSelectionToPasteboard:(NSPasteboard*)pboard types:(NSArray*)types { 4541 NS_OBJC_BEGIN_TRY_BLOCK_RETURN; 4542 4543 nsAutoRetainCocoaObject kungFuDeathGrip(self); 4544 4545 // Make sure that the service will accept strings or HTML. 4546 if (![types containsObject:[UTIHelper stringFromPboardType:NSStringPboardType]] && 4547 ![types containsObject:[UTIHelper stringFromPboardType:NSPasteboardTypeString]] && 4548 ![types containsObject:[UTIHelper stringFromPboardType:NSPasteboardTypeHTML]]) { 4549 return NO; 4550 } 4551 4552 // Bail out if there is no Gecko object. 4553 if (!mGeckoChild) return NO; 4554 4555 // Transform the transferable to an NSDictionary. 4556 NSDictionary* pasteboardOutputDict = nullptr; 4557 4558 pasteboardOutputDict = nsClipboard::PasteboardDictFromTransferable(nsClipboard::sSelectionCache); 4559 4560 if (!pasteboardOutputDict) return NO; 4561 4562 // Declare the pasteboard types. 4563 unsigned int typeCount = [pasteboardOutputDict count]; 4564 NSMutableArray* declaredTypes = [NSMutableArray arrayWithCapacity:typeCount]; 4565 [declaredTypes addObjectsFromArray:[pasteboardOutputDict allKeys]]; 4566 [pboard declareTypes:declaredTypes owner:nil]; 4567 4568 // Write the data to the pasteboard. 4569 for (unsigned int i = 0; i < typeCount; i++) { 4570 NSString* currentKey = [declaredTypes objectAtIndex:i]; 4571 id currentValue = [pasteboardOutputDict valueForKey:currentKey]; 4572 4573 if ([currentKey isEqualToString:[UTIHelper stringFromPboardType:NSPasteboardTypeString]] || 4574 [currentKey isEqualToString:[UTIHelper stringFromPboardType:kPublicUrlPboardType]] || 4575 [currentKey isEqualToString:[UTIHelper stringFromPboardType:kPublicUrlNamePboardType]]) { 4576 [pboard setString:currentValue forType:currentKey]; 4577 } else if ([currentKey isEqualToString:[UTIHelper stringFromPboardType:NSPasteboardTypeHTML]]) { 4578 [pboard setString:(nsClipboard::WrapHtmlForSystemPasteboard(currentValue)) 4579 forType:currentKey]; 4580 } else if ([currentKey isEqualToString:[UTIHelper stringFromPboardType:NSPasteboardTypeTIFF]]) { 4581 [pboard setData:currentValue forType:currentKey]; 4582 } else if ([currentKey 4583 isEqualToString:[UTIHelper 4584 stringFromPboardType:(NSString*) 4585 kPasteboardTypeFileURLPromise]] || 4586 [currentKey 4587 isEqualToString:[UTIHelper stringFromPboardType:kUrlsWithTitlesPboardType]]) { 4588 [pboard setPropertyList:currentValue forType:currentKey]; 4589 } 4590 } 4591 return YES; 4592 4593 NS_OBJC_END_TRY_BLOCK_RETURN(NO); 4594} 4595 4596// Called if the service wants us to replace the current selection. 4597- (BOOL)readSelectionFromPasteboard:(NSPasteboard*)pboard { 4598 nsresult rv; 4599 nsCOMPtr<nsITransferable> trans = do_CreateInstance("@mozilla.org/widget/transferable;1", &rv); 4600 if (NS_FAILED(rv)) return NO; 4601 trans->Init(nullptr); 4602 4603 trans->AddDataFlavor(kUnicodeMime); 4604 trans->AddDataFlavor(kHTMLMime); 4605 4606 rv = nsClipboard::TransferableFromPasteboard(trans, pboard); 4607 if (NS_FAILED(rv)) return NO; 4608 4609 NS_ENSURE_TRUE(mGeckoChild, false); 4610 4611 WidgetContentCommandEvent command(true, eContentCommandPasteTransferable, mGeckoChild); 4612 command.mTransferable = trans; 4613 mGeckoChild->DispatchWindowEvent(command); 4614 4615 return command.mSucceeded && command.mIsEnabled; 4616} 4617 4618- (void)pressureChangeWithEvent:(NSEvent*)event { 4619 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK 4620 4621 NSInteger stage = [event stage]; 4622 if (mLastPressureStage == 1 && stage == 2) { 4623 NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults]; 4624 if ([userDefaults integerForKey:@"com.apple.trackpad.forceClick"] == 1) { 4625 // This is no public API to get configuration for current force click. 4626 // This is filed as radar 29294285. 4627 [self quickLookWithEvent:event]; 4628 } 4629 } 4630 mLastPressureStage = stage; 4631 4632 NS_OBJC_END_TRY_IGNORE_BLOCK 4633} 4634 4635nsresult nsChildView::GetSelectionAsPlaintext(nsAString& aResult) { 4636 NS_OBJC_BEGIN_TRY_BLOCK_RETURN; 4637 4638 if (!nsClipboard::sSelectionCache) { 4639 MOZ_ASSERT(aResult.IsEmpty()); 4640 return NS_OK; 4641 } 4642 4643 // Get the current chrome or content selection. 4644 NSDictionary* pasteboardOutputDict = nullptr; 4645 pasteboardOutputDict = nsClipboard::PasteboardDictFromTransferable(nsClipboard::sSelectionCache); 4646 4647 if (NS_WARN_IF(!pasteboardOutputDict)) { 4648 return NS_ERROR_FAILURE; 4649 } 4650 4651 // Declare the pasteboard types. 4652 unsigned int typeCount = [pasteboardOutputDict count]; 4653 NSMutableArray* declaredTypes = [NSMutableArray arrayWithCapacity:typeCount]; 4654 [declaredTypes addObjectsFromArray:[pasteboardOutputDict allKeys]]; 4655 NSString* currentKey = [declaredTypes objectAtIndex:0]; 4656 NSString* currentValue = [pasteboardOutputDict valueForKey:currentKey]; 4657 const char* textSelection = [currentValue UTF8String]; 4658 aResult = NS_ConvertUTF8toUTF16(textSelection); 4659 4660 return NS_OK; 4661 4662 NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE); 4663} 4664 4665#pragma mark - 4666 4667#ifdef ACCESSIBILITY 4668 4669/* Every ChildView has a corresponding mozDocAccessible object that is doing all 4670 the heavy lifting. The topmost ChildView corresponds to a mozRootAccessible 4671 object. 4672 4673 All ChildView needs to do is to route all accessibility calls (from the NSAccessibility APIs) 4674 down to its object, pretending that they are the same. 4675*/ 4676- (id<mozAccessible>)accessible { 4677 if (!mGeckoChild) return nil; 4678 4679 id<mozAccessible> nativeAccessible = nil; 4680 4681 nsAutoRetainCocoaObject kungFuDeathGrip(self); 4682 RefPtr<nsChildView> geckoChild(mGeckoChild); 4683 RefPtr<a11y::LocalAccessible> accessible = geckoChild->GetDocumentAccessible(); 4684 if (!accessible) return nil; 4685 4686 accessible->GetNativeInterface((void**)&nativeAccessible); 4687 4688# ifdef DEBUG_hakan 4689 NSAssert(![nativeAccessible isExpired], @"native acc is expired!!!"); 4690# endif 4691 4692 return nativeAccessible; 4693} 4694 4695/* Implementation of formal mozAccessible formal protocol (enabling mozViews 4696 to talk to mozAccessible objects in the accessibility module). */ 4697 4698- (BOOL)hasRepresentedView { 4699 return YES; 4700} 4701 4702- (id)representedView { 4703 return self; 4704} 4705 4706- (BOOL)isRoot { 4707 return [[self accessible] isRoot]; 4708} 4709 4710# pragma mark - 4711 4712// general 4713 4714- (BOOL)isAccessibilityElement { 4715 if (!mozilla::a11y::ShouldA11yBeEnabled()) return [super isAccessibilityElement]; 4716 4717 return [[self accessible] isAccessibilityElement]; 4718} 4719 4720- (id)accessibilityHitTest:(NSPoint)point { 4721 if (!mozilla::a11y::ShouldA11yBeEnabled()) return [super accessibilityHitTest:point]; 4722 4723 return [[self accessible] accessibilityHitTest:point]; 4724} 4725 4726- (id)accessibilityFocusedUIElement { 4727 if (!mozilla::a11y::ShouldA11yBeEnabled()) return [super accessibilityFocusedUIElement]; 4728 4729 return [[self accessible] accessibilityFocusedUIElement]; 4730} 4731 4732// actions 4733 4734- (NSArray*)accessibilityActionNames { 4735 if (!mozilla::a11y::ShouldA11yBeEnabled()) return [super accessibilityActionNames]; 4736 4737 return [[self accessible] accessibilityActionNames]; 4738} 4739 4740- (NSString*)accessibilityActionDescription:(NSString*)action { 4741 if (!mozilla::a11y::ShouldA11yBeEnabled()) return [super accessibilityActionDescription:action]; 4742 4743 return [[self accessible] accessibilityActionDescription:action]; 4744} 4745 4746- (void)accessibilityPerformAction:(NSString*)action { 4747 if (!mozilla::a11y::ShouldA11yBeEnabled()) return [super accessibilityPerformAction:action]; 4748 4749 return [[self accessible] accessibilityPerformAction:action]; 4750} 4751 4752// attributes 4753 4754- (NSArray*)accessibilityAttributeNames { 4755 if (!mozilla::a11y::ShouldA11yBeEnabled()) return [super accessibilityAttributeNames]; 4756 4757 return [[self accessible] accessibilityAttributeNames]; 4758} 4759 4760- (BOOL)accessibilityIsAttributeSettable:(NSString*)attribute { 4761 if (!mozilla::a11y::ShouldA11yBeEnabled()) 4762 return [super accessibilityIsAttributeSettable:attribute]; 4763 4764 return [[self accessible] accessibilityIsAttributeSettable:attribute]; 4765} 4766 4767- (id)accessibilityAttributeValue:(NSString*)attribute { 4768 NS_OBJC_BEGIN_TRY_BLOCK_RETURN; 4769 4770 if (!mozilla::a11y::ShouldA11yBeEnabled()) return [super accessibilityAttributeValue:attribute]; 4771 4772 id<mozAccessible> accessible = [self accessible]; 4773 4774 // if we're the root (topmost) accessible, we need to return our native AXParent as we 4775 // traverse outside to the hierarchy of whoever embeds us. thus, fall back on NSView's 4776 // default implementation for this attribute. 4777 if ([attribute isEqualToString:NSAccessibilityParentAttribute] && [accessible isRoot]) { 4778 id parentAccessible = [super accessibilityAttributeValue:attribute]; 4779 return parentAccessible; 4780 } 4781 4782 return [accessible accessibilityAttributeValue:attribute]; 4783 4784 NS_OBJC_END_TRY_BLOCK_RETURN(nil); 4785} 4786 4787#endif /* ACCESSIBILITY */ 4788 4789+ (uint32_t)sUniqueKeyEventId { 4790 return sUniqueKeyEventId; 4791} 4792 4793+ (NSMutableDictionary*)sNativeKeyEventsMap { 4794 // This dictionary is "leaked". 4795 static NSMutableDictionary* sNativeKeyEventsMap = [[NSMutableDictionary alloc] init]; 4796 return sNativeKeyEventsMap; 4797} 4798 4799@end 4800 4801@implementation PixelHostingView 4802 4803- (id)initWithFrame:(NSRect)aRect { 4804 self = [super initWithFrame:aRect]; 4805 4806 self.wantsLayer = YES; 4807 self.layerContentsRedrawPolicy = NSViewLayerContentsRedrawDuringViewResize; 4808 4809 return self; 4810} 4811 4812- (BOOL)isFlipped { 4813 return YES; 4814} 4815 4816- (NSView*)hitTest:(NSPoint)aPoint { 4817 return nil; 4818} 4819 4820- (void)drawRect:(NSRect)aRect { 4821 NS_WARNING("Unexpected call to drawRect: This view returns YES from wantsUpdateLayer, so " 4822 "drawRect should not be called."); 4823} 4824 4825- (BOOL)wantsUpdateLayer { 4826 return YES; 4827} 4828 4829- (void)updateLayer { 4830 [(ChildView*)[self superview] updateRootCALayer]; 4831} 4832 4833- (BOOL)wantsBestResolutionOpenGLSurface { 4834 return nsCocoaUtils::HiDPIEnabled() ? YES : NO; 4835} 4836 4837@end 4838 4839#pragma mark - 4840 4841void ChildViewMouseTracker::OnDestroyView(ChildView* aView) { 4842 if (sLastMouseEventView == aView) { 4843 sLastMouseEventView = nil; 4844 [sLastMouseMoveEvent release]; 4845 sLastMouseMoveEvent = nil; 4846 } 4847} 4848 4849void ChildViewMouseTracker::OnDestroyWindow(NSWindow* aWindow) { 4850 if (sWindowUnderMouse == aWindow) { 4851 sWindowUnderMouse = nil; 4852 } 4853} 4854 4855void ChildViewMouseTracker::MouseEnteredWindow(NSEvent* aEvent) { 4856 sWindowUnderMouse = [aEvent window]; 4857 ReEvaluateMouseEnterState(aEvent); 4858} 4859 4860void ChildViewMouseTracker::MouseExitedWindow(NSEvent* aEvent) { 4861 if (sWindowUnderMouse == [aEvent window]) { 4862 sWindowUnderMouse = nil; 4863 [sLastMouseMoveEvent release]; 4864 sLastMouseMoveEvent = nil; 4865 ReEvaluateMouseEnterState(aEvent); 4866 } 4867} 4868 4869void ChildViewMouseTracker::NativeMenuOpened() { 4870 // Send a mouse exit event now. 4871 // The menu consumes all mouse events while it's open, and we don't want to be stuck thinking the 4872 // mouse is still hovering our window after the mouse has already moved. This could result in 4873 // unintended cursor changes or tooltips. 4874 sWindowUnderMouse = nil; 4875 ReEvaluateMouseEnterState(nil); 4876} 4877 4878void ChildViewMouseTracker::NativeMenuClosed() { 4879 // If a window was hovered before the menu opened, re-enter that window at the last known mouse 4880 // position. 4881 // After -[NSView didCloseMenu:withEvent:] is called, any NSTrackingArea updates that were 4882 // buffered while the menu was open will be replayed. 4883 if (sLastMouseMoveEvent) { 4884 sWindowUnderMouse = sLastMouseMoveEvent.window; 4885 ReEvaluateMouseEnterState(sLastMouseMoveEvent); 4886 } 4887} 4888 4889void ChildViewMouseTracker::ReEvaluateMouseEnterState(NSEvent* aEvent, ChildView* aOldView) { 4890 ChildView* oldView = aOldView ? aOldView : sLastMouseEventView; 4891 sLastMouseEventView = ViewForEvent(aEvent); 4892 if (sLastMouseEventView != oldView) { 4893 // Send enter and / or exit events. 4894 WidgetMouseEvent::ExitFrom exitFrom = [sLastMouseEventView window] == [oldView window] 4895 ? WidgetMouseEvent::ePlatformChild 4896 : WidgetMouseEvent::ePlatformTopLevel; 4897 [oldView sendMouseEnterOrExitEvent:aEvent enter:NO exitFrom:exitFrom]; 4898 // After the cursor exits the window set it to a visible regular arrow cursor. 4899 if (exitFrom == WidgetMouseEvent::ePlatformTopLevel) { 4900 [[nsCursorManager sharedInstance] setNonCustomCursor:nsIWidget::Cursor{eCursor_standard}]; 4901 } 4902 [sLastMouseEventView sendMouseEnterOrExitEvent:aEvent enter:YES exitFrom:exitFrom]; 4903 } 4904} 4905 4906void ChildViewMouseTracker::ResendLastMouseMoveEvent() { 4907 if (sLastMouseMoveEvent) { 4908 MouseMoved(sLastMouseMoveEvent); 4909 } 4910} 4911 4912void ChildViewMouseTracker::MouseMoved(NSEvent* aEvent) { 4913 MouseEnteredWindow(aEvent); 4914 [sLastMouseEventView handleMouseMoved:aEvent]; 4915 if (sLastMouseMoveEvent != aEvent) { 4916 [sLastMouseMoveEvent release]; 4917 sLastMouseMoveEvent = [aEvent retain]; 4918 } 4919} 4920 4921void ChildViewMouseTracker::MouseScrolled(NSEvent* aEvent) { 4922 if (!nsCocoaUtils::IsMomentumScrollEvent(aEvent)) { 4923 // Store the position so we can pin future momentum scroll events. 4924 sLastScrollEventScreenLocation = nsCocoaUtils::ScreenLocationForEvent(aEvent); 4925 } 4926} 4927 4928ChildView* ChildViewMouseTracker::ViewForEvent(NSEvent* aEvent) { 4929 NSWindow* window = sWindowUnderMouse; 4930 if (!window) return nil; 4931 4932 NSPoint windowEventLocation = nsCocoaUtils::EventLocationForWindow(aEvent, window); 4933 NSView* view = [[[window contentView] superview] hitTest:windowEventLocation]; 4934 4935 if (![view isKindOfClass:[ChildView class]]) return nil; 4936 4937 ChildView* childView = (ChildView*)view; 4938 // If childView is being destroyed return nil. 4939 if (![childView widget]) return nil; 4940 return WindowAcceptsEvent(window, aEvent, childView) ? childView : nil; 4941} 4942 4943BOOL ChildViewMouseTracker::WindowAcceptsEvent(NSWindow* aWindow, NSEvent* aEvent, ChildView* aView, 4944 BOOL aIsClickThrough) { 4945 // Right mouse down events may get through to all windows, even to a top level 4946 // window with an open sheet. 4947 if (!aWindow || [aEvent type] == NSEventTypeRightMouseDown) return YES; 4948 4949 id delegate = [aWindow delegate]; 4950 if (!delegate || ![delegate isKindOfClass:[WindowDelegate class]]) return YES; 4951 4952 nsIWidget* windowWidget = [(WindowDelegate*)delegate geckoWidget]; 4953 if (!windowWidget) return YES; 4954 4955 NSWindow* topLevelWindow = nil; 4956 4957 switch (windowWidget->WindowType()) { 4958 case eWindowType_popup: 4959 // If this is a context menu, it won't have a parent. So we'll always 4960 // accept mouse move events on context menus even when none of our windows 4961 // is active, which is the right thing to do. 4962 // For panels, the parent window is the XUL window that owns the panel. 4963 return WindowAcceptsEvent([aWindow parentWindow], aEvent, aView, aIsClickThrough); 4964 4965 case eWindowType_toplevel: 4966 case eWindowType_dialog: 4967 if ([aWindow attachedSheet]) return NO; 4968 4969 topLevelWindow = aWindow; 4970 break; 4971 case eWindowType_sheet: { 4972 nsIWidget* parentWidget = windowWidget->GetSheetWindowParent(); 4973 if (!parentWidget) return YES; 4974 4975 topLevelWindow = (NSWindow*)parentWidget->GetNativeData(NS_NATIVE_WINDOW); 4976 break; 4977 } 4978 4979 default: 4980 return YES; 4981 } 4982 4983 if (!topLevelWindow || ([topLevelWindow isMainWindow] && !aIsClickThrough) || 4984 [aEvent type] == NSEventTypeOtherMouseDown || 4985 (([aEvent modifierFlags] & NSEventModifierFlagCommand) != 0 && 4986 [aEvent type] != NSEventTypeMouseMoved)) 4987 return YES; 4988 4989 // If we're here then we're dealing with a left click or mouse move on an 4990 // inactive window or something similar. Ask Gecko what to do. 4991 return [aView inactiveWindowAcceptsMouseEvent:aEvent]; 4992} 4993 4994#pragma mark - 4995