Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
1 /* -*- Mode: objc; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "mozilla/ArrayUtils.h"
8 #ifdef MOZ_LOGGING
9 #define FORCE_PR_LOG
10 #endif
11 #include "prlog.h"
13 #include <unistd.h>
14 #include <math.h>
16 #include "nsChildView.h"
17 #include "nsCocoaWindow.h"
19 #include "mozilla/MiscEvents.h"
20 #include "mozilla/MouseEvents.h"
21 #include "mozilla/TextEvents.h"
22 #include "mozilla/TouchEvents.h"
24 #include "nsObjCExceptions.h"
25 #include "nsCOMPtr.h"
26 #include "nsToolkit.h"
27 #include "nsCRT.h"
29 #include "nsFontMetrics.h"
30 #include "nsIRollupListener.h"
31 #include "nsViewManager.h"
32 #include "nsIInterfaceRequestor.h"
33 #include "nsIFile.h"
34 #include "nsILocalFileMac.h"
35 #include "nsGfxCIID.h"
36 #include "nsIDOMSimpleGestureEvent.h"
37 #include "nsNPAPIPluginInstance.h"
38 #include "nsThemeConstants.h"
39 #include "nsIWidgetListener.h"
40 #include "nsIPresShell.h"
42 #include "nsDragService.h"
43 #include "nsClipboard.h"
44 #include "nsCursorManager.h"
45 #include "nsWindowMap.h"
46 #include "nsCocoaFeatures.h"
47 #include "nsCocoaUtils.h"
48 #include "nsMenuUtilsX.h"
49 #include "nsMenuBarX.h"
50 #ifdef __LP64__
51 #include "ComplexTextInputPanel.h"
52 #endif
53 #include "NativeKeyBindings.h"
55 #include "gfxContext.h"
56 #include "gfxQuartzSurface.h"
57 #include "gfxUtils.h"
58 #include "nsRegion.h"
59 #include "Layers.h"
60 #include "ClientLayerManager.h"
61 #include "mozilla/layers/LayerManagerComposite.h"
62 #include "GLTextureImage.h"
63 #include "GLContextProvider.h"
64 #include "GLContextCGL.h"
65 #include "GLUploadHelpers.h"
66 #include "ScopedGLHelpers.h"
67 #include "mozilla/layers/GLManager.h"
68 #include "mozilla/layers/CompositorOGL.h"
69 #include "mozilla/layers/BasicCompositor.h"
70 #include "gfxUtils.h"
71 #include "mozilla/gfx/2D.h"
72 #include "mozilla/gfx/BorrowedContext.h"
73 #ifdef ACCESSIBILITY
74 #include "nsAccessibilityService.h"
75 #include "mozilla/a11y/Platform.h"
76 #endif
77 #ifdef MOZ_CRASHREPORTER
78 #include "nsExceptionHandler.h"
79 #endif
81 #include "mozilla/Preferences.h"
83 #include <dlfcn.h>
85 #include <ApplicationServices/ApplicationServices.h>
87 #include "GeckoProfiler.h"
89 #include "nsIDOMWheelEvent.h"
91 using namespace mozilla;
92 using namespace mozilla::layers;
93 using namespace mozilla::gl;
94 using namespace mozilla::widget;
96 #undef DEBUG_UPDATE
97 #undef INVALIDATE_DEBUGGING // flash areas as they are invalidated
99 // Don't put more than this many rects in the dirty region, just fluff
100 // out to the bounding-box if there are more
101 #define MAX_RECTS_IN_REGION 100
103 #ifdef PR_LOGGING
104 PRLogModuleInfo* sCocoaLog = nullptr;
105 #endif
107 extern "C" {
108 CG_EXTERN void CGContextResetCTM(CGContextRef);
109 CG_EXTERN void CGContextSetCTM(CGContextRef, CGAffineTransform);
110 CG_EXTERN void CGContextResetClip(CGContextRef);
112 typedef CFTypeRef CGSRegionObj;
113 CGError CGSNewRegionWithRect(const CGRect *rect, CGSRegionObj *outRegion);
114 }
116 // defined in nsMenuBarX.mm
117 extern NSMenu* sApplicationMenu; // Application menu shared by all menubars
119 bool gChildViewMethodsSwizzled = false;
121 extern nsISupportsArray *gDraggedTransferables;
123 ChildView* ChildViewMouseTracker::sLastMouseEventView = nil;
124 NSEvent* ChildViewMouseTracker::sLastMouseMoveEvent = nil;
125 NSWindow* ChildViewMouseTracker::sWindowUnderMouse = nil;
126 NSPoint ChildViewMouseTracker::sLastScrollEventScreenLocation = NSZeroPoint;
128 #ifdef INVALIDATE_DEBUGGING
129 static void blinkRect(Rect* r);
130 static void blinkRgn(RgnHandle rgn);
131 #endif
133 bool gUserCancelledDrag = false;
135 uint32_t nsChildView::sLastInputEventCount = 0;
137 @interface ChildView(Private)
139 // sets up our view, attaching it to its owning gecko view
140 - (id)initWithFrame:(NSRect)inFrame geckoChild:(nsChildView*)inChild;
141 - (void)forceRefreshOpenGL;
143 // set up a gecko mouse event based on a cocoa mouse event
144 - (void) convertCocoaMouseWheelEvent:(NSEvent*)aMouseEvent
145 toGeckoEvent:(WidgetWheelEvent*)outWheelEvent;
146 - (void) convertCocoaMouseEvent:(NSEvent*)aMouseEvent
147 toGeckoEvent:(WidgetInputEvent*)outGeckoEvent;
149 - (NSMenu*)contextMenu;
151 - (void)setIsPluginView:(BOOL)aIsPlugin;
152 - (void)setPluginEventModel:(NPEventModel)eventModel;
153 - (void)setPluginDrawingModel:(NPDrawingModel)drawingModel;
154 - (NPDrawingModel)pluginDrawingModel;
156 - (BOOL)isRectObscuredBySubview:(NSRect)inRect;
158 - (void)processPendingRedraws;
160 - (void)drawRect:(NSRect)aRect inContext:(CGContextRef)aContext;
161 - (nsIntRegion)nativeDirtyRegionWithBoundingRect:(NSRect)aRect;
162 - (BOOL)isUsingMainThreadOpenGL;
163 - (BOOL)isUsingOpenGL;
164 - (void)drawUsingOpenGL;
165 - (void)drawUsingOpenGLCallback;
167 - (BOOL)hasRoundedBottomCorners;
168 - (CGFloat)cornerRadius;
169 - (void)clearCorners;
171 // Overlay drawing functions for traditional CGContext drawing
172 - (void)drawTitleString;
173 - (void)drawTitlebarHighlight;
174 - (void)maskTopCornersInContext:(CGContextRef)aContext;
176 // Called using performSelector:withObject:afterDelay:0 to release
177 // aWidgetArray (and its contents) the next time through the run loop.
178 - (void)releaseWidgets:(NSArray*)aWidgetArray;
180 #if USE_CLICK_HOLD_CONTEXTMENU
181 // called on a timer two seconds after a mouse down to see if we should display
182 // a context menu (click-hold)
183 - (void)clickHoldCallback:(id)inEvent;
184 #endif
186 #ifdef ACCESSIBILITY
187 - (id<mozAccessible>)accessible;
188 #endif
190 - (BOOL)inactiveWindowAcceptsMouseEvent:(NSEvent*)aEvent;
192 @end
194 @interface NSView(NSThemeFrameCornerRadius)
195 - (float)roundedCornerRadius;
196 @end
198 // Starting with 10.7 the bottom corners of all windows are rounded.
199 // Unfortunately, the standard rounding that OS X applies to OpenGL views
200 // does not use anti-aliasing and looks very crude. Since we want a smooth,
201 // anti-aliased curve, we'll draw it ourselves.
202 // Additionally, we need to turn off the OS-supplied rounding because it
203 // eats into our corner's curve. We do that by overriding an NSSurface method.
204 @interface NSSurface @end
206 @implementation NSSurface(DontCutOffCorners)
207 - (CGSRegionObj)_createRoundedBottomRegionForRect:(CGRect)rect
208 {
209 // Create a normal rect region without rounded bottom corners.
210 CGSRegionObj region;
211 CGSNewRegionWithRect(&rect, ®ion);
212 return region;
213 }
214 @end
216 #pragma mark -
218 /* Convenience routine to go from a Gecko rect to Cocoa NSRect.
219 *
220 * Gecko rects (nsRect) contain an origin (x,y) in a coordinate
221 * system with (0,0) in the top-left of the screen. Cocoa rects
222 * (NSRect) contain an origin (x,y) in a coordinate system with
223 * (0,0) in the bottom-left of the screen. Both nsRect and NSRect
224 * contain width/height info, with no difference in their use.
225 * If a Cocoa rect is from a flipped view, there is no need to
226 * convert coordinate systems.
227 */
228 #ifndef __LP64__
229 static inline void
230 ConvertGeckoRectToMacRect(const nsIntRect& aRect, Rect& outMacRect)
231 {
232 outMacRect.left = aRect.x;
233 outMacRect.top = aRect.y;
234 outMacRect.right = aRect.x + aRect.width;
235 outMacRect.bottom = aRect.y + aRect.height;
236 }
237 #endif
239 // Flips a screen coordinate from a point in the cocoa coordinate system (bottom-left rect) to a point
240 // that is a "flipped" cocoa coordinate system (starts in the top-left).
241 static inline void
242 FlipCocoaScreenCoordinate(NSPoint &inPoint)
243 {
244 inPoint.y = nsCocoaUtils::FlippedScreenY(inPoint.y);
245 }
247 void EnsureLogInitialized()
248 {
249 #ifdef PR_LOGGING
250 if (!sCocoaLog) {
251 sCocoaLog = PR_NewLogModule("nsCocoaWidgets");
252 }
253 #endif // PR_LOGGING
254 }
256 namespace {
258 // Manages a texture which can resize dynamically, binds to the
259 // LOCAL_GL_TEXTURE_RECTANGLE_ARB texture target and is automatically backed
260 // by a power-of-two size GL texture. The latter two features are used for
261 // compatibility with older Mac hardware which we block GL layers on.
262 // RectTextureImages are used both for accelerated GL layers drawing and for
263 // OMTC BasicLayers drawing.
264 class RectTextureImage {
265 public:
266 RectTextureImage(GLContext* aGLContext)
267 : mGLContext(aGLContext)
268 , mTexture(0)
269 , mInUpdate(false)
270 {}
272 virtual ~RectTextureImage();
274 TemporaryRef<gfx::DrawTarget>
275 BeginUpdate(const nsIntSize& aNewSize,
276 const nsIntRegion& aDirtyRegion = nsIntRegion());
277 void EndUpdate(bool aKeepSurface = false);
279 void UpdateIfNeeded(const nsIntSize& aNewSize,
280 const nsIntRegion& aDirtyRegion,
281 void (^aCallback)(gfx::DrawTarget*, const nsIntRegion&))
282 {
283 RefPtr<gfx::DrawTarget> drawTarget = BeginUpdate(aNewSize, aDirtyRegion);
284 if (drawTarget) {
285 aCallback(drawTarget, GetUpdateRegion());
286 EndUpdate();
287 }
288 }
290 void UpdateFromCGContext(const nsIntSize& aNewSize,
291 const nsIntRegion& aDirtyRegion,
292 CGContextRef aCGContext);
294 void UpdateFromDrawTarget(const nsIntSize& aNewSize,
295 const nsIntRegion& aDirtyRegion,
296 gfx::DrawTarget* aFromDrawTarget);
298 nsIntRegion GetUpdateRegion() {
299 MOZ_ASSERT(mInUpdate, "update region only valid during update");
300 return mUpdateRegion;
301 }
303 void Draw(mozilla::layers::GLManager* aManager,
304 const nsIntPoint& aLocation,
305 const gfx3DMatrix& aTransform = gfx3DMatrix());
307 static nsIntSize TextureSizeForSize(const nsIntSize& aSize);
309 protected:
311 RefPtr<gfx::DrawTarget> mUpdateDrawTarget;
312 GLContext* mGLContext;
313 nsIntRegion mUpdateRegion;
314 nsIntSize mUsedSize;
315 nsIntSize mBufferSize;
316 nsIntSize mTextureSize;
317 GLuint mTexture;
318 bool mInUpdate;
319 };
321 // Used for OpenGL drawing from the compositor thread for OMTC BasicLayers.
322 // We need to use OpenGL for this because there seems to be no other robust
323 // way of drawing from a secondary thread without locking, which would cause
324 // deadlocks in our setup. See bug 882523.
325 class GLPresenter : public GLManager
326 {
327 public:
328 static GLPresenter* CreateForWindow(nsIWidget* aWindow)
329 {
330 nsRefPtr<GLContext> context = gl::GLContextProvider::CreateForWindow(aWindow);
331 return context ? new GLPresenter(context) : nullptr;
332 }
334 GLPresenter(GLContext* aContext);
335 virtual ~GLPresenter();
337 virtual GLContext* gl() const MOZ_OVERRIDE { return mGLContext; }
338 virtual ShaderProgramOGL* GetProgram(GLenum aTarget, gfx::SurfaceFormat aFormat) MOZ_OVERRIDE
339 {
340 MOZ_ASSERT(aTarget == LOCAL_GL_TEXTURE_RECTANGLE_ARB);
341 MOZ_ASSERT(aFormat == gfx::SurfaceFormat::R8G8B8A8);
342 return mRGBARectProgram;
343 }
344 virtual const gfx::Matrix4x4& GetProjMatrix() const MOZ_OVERRIDE
345 {
346 return mProjMatrix;
347 }
348 virtual void BindAndDrawQuad(ShaderProgramOGL *aProg) MOZ_OVERRIDE;
350 void BeginFrame(nsIntSize aRenderSize);
351 void EndFrame();
353 NSOpenGLContext* GetNSOpenGLContext()
354 {
355 return GLContextCGL::Cast(mGLContext)->GetNSOpenGLContext();
356 }
358 protected:
359 nsRefPtr<mozilla::gl::GLContext> mGLContext;
360 nsAutoPtr<mozilla::layers::ShaderProgramOGL> mRGBARectProgram;
361 gfx::Matrix4x4 mProjMatrix;
362 GLuint mQuadVBO;
363 };
365 } // unnamed namespace
367 #pragma mark -
369 nsChildView::nsChildView() : nsBaseWidget()
370 , mView(nullptr)
371 , mParentView(nullptr)
372 , mParentWidget(nullptr)
373 , mViewTearDownLock("ChildViewTearDown")
374 , mEffectsLock("WidgetEffects")
375 , mShowsResizeIndicator(false)
376 , mHasRoundedBottomCorners(false)
377 , mIsCoveringTitlebar(false)
378 , mIsFullscreen(false)
379 , mTitlebarCGContext(nullptr)
380 , mBackingScaleFactor(0.0)
381 , mVisible(false)
382 , mDrawing(false)
383 , mPluginDrawing(false)
384 , mIsDispatchPaint(false)
385 , mPluginInstanceOwner(nullptr)
386 {
387 EnsureLogInitialized();
389 memset(&mPluginCGContext, 0, sizeof(mPluginCGContext));
390 }
392 nsChildView::~nsChildView()
393 {
394 ReleaseTitlebarCGContext();
396 // Notify the children that we're gone. childView->ResetParent() can change
397 // our list of children while it's being iterated, so the way we iterate the
398 // list must allow for this.
399 for (nsIWidget* kid = mLastChild; kid;) {
400 nsChildView* childView = static_cast<nsChildView*>(kid);
401 kid = kid->GetPrevSibling();
402 childView->ResetParent();
403 }
405 NS_WARN_IF_FALSE(mOnDestroyCalled, "nsChildView object destroyed without calling Destroy()");
407 DestroyCompositor();
409 // An nsChildView object that was in use can be destroyed without Destroy()
410 // ever being called on it. So we also need to do a quick, safe cleanup
411 // here (it's too late to just call Destroy(), which can cause crashes).
412 // It's particularly important to make sure widgetDestroyed is called on our
413 // mView -- this method NULLs mView's mGeckoChild, and NULL checks on
414 // mGeckoChild are used throughout the ChildView class to tell if it's safe
415 // to use a ChildView object.
416 [mView widgetDestroyed]; // Safe if mView is nil.
417 mParentWidget = nil;
418 TearDownView(); // Safe if called twice.
419 }
421 void
422 nsChildView::ReleaseTitlebarCGContext()
423 {
424 if (mTitlebarCGContext) {
425 CGContextRelease(mTitlebarCGContext);
426 mTitlebarCGContext = nullptr;
427 }
428 }
430 NS_IMPL_ISUPPORTS_INHERITED(nsChildView, nsBaseWidget, nsIPluginWidget)
432 nsresult nsChildView::Create(nsIWidget *aParent,
433 nsNativeWidget aNativeParent,
434 const nsIntRect &aRect,
435 nsDeviceContext *aContext,
436 nsWidgetInitData *aInitData)
437 {
438 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
440 // Because the hidden window is created outside of an event loop,
441 // we need to provide an autorelease pool to avoid leaking cocoa objects
442 // (see bug 559075).
443 nsAutoreleasePool localPool;
445 // See NSView (MethodSwizzling) below.
446 if (!gChildViewMethodsSwizzled) {
447 nsToolkit::SwizzleMethods([NSView class], @selector(mouseDownCanMoveWindow),
448 @selector(nsChildView_NSView_mouseDownCanMoveWindow));
449 #ifdef __LP64__
450 if (nsCocoaFeatures::OnLionOrLater()) {
451 nsToolkit::SwizzleMethods([NSEvent class], @selector(addLocalMonitorForEventsMatchingMask:handler:),
452 @selector(nsChildView_NSEvent_addLocalMonitorForEventsMatchingMask:handler:),
453 true);
454 nsToolkit::SwizzleMethods([NSEvent class], @selector(removeMonitor:),
455 @selector(nsChildView_NSEvent_removeMonitor:), true);
456 }
457 #else
458 TextInputHandler::SwizzleMethods();
459 #endif
460 gChildViewMethodsSwizzled = true;
461 }
463 mBounds = aRect;
465 // Ensure that the toolkit is created.
466 nsToolkit::GetToolkit();
468 BaseCreate(aParent, aRect, aContext, aInitData);
470 // inherit things from the parent view and create our parallel
471 // NSView in the Cocoa display system
472 mParentView = nil;
473 if (aParent) {
474 // inherit the top-level window. NS_NATIVE_WIDGET is always a NSView
475 // regardless of if we're asking a window or a view (for compatibility
476 // with windows).
477 mParentView = (NSView<mozView>*)aParent->GetNativeData(NS_NATIVE_WIDGET);
478 mParentWidget = aParent;
479 } else {
480 // This is the normal case. When we're the root widget of the view hiararchy,
481 // aNativeParent will be the contentView of our window, since that's what
482 // nsCocoaWindow returns when asked for an NS_NATIVE_VIEW.
483 mParentView = reinterpret_cast<NSView<mozView>*>(aNativeParent);
484 }
486 // create our parallel NSView and hook it up to our parent. Recall
487 // that NS_NATIVE_WIDGET is the NSView.
488 CGFloat scaleFactor = nsCocoaUtils::GetBackingScaleFactor(mParentView);
489 NSRect r = nsCocoaUtils::DevPixelsToCocoaPoints(mBounds, scaleFactor);
490 mView = [(NSView<mozView>*)CreateCocoaView(r) retain];
491 if (!mView) {
492 return NS_ERROR_FAILURE;
493 }
495 [(ChildView*)mView setIsPluginView:(mWindowType == eWindowType_plugin)];
497 // If this view was created in a Gecko view hierarchy, the initial state
498 // is hidden. If the view is attached only to a native NSView but has
499 // no Gecko parent (as in embedding), the initial state is visible.
500 if (mParentWidget)
501 [mView setHidden:YES];
502 else
503 mVisible = true;
505 // Hook it up in the NSView hierarchy.
506 if (mParentView) {
507 [mParentView addSubview:mView];
508 }
510 // if this is a ChildView, make sure that our per-window data
511 // is set up
512 if ([mView isKindOfClass:[ChildView class]])
513 [[WindowDataMap sharedWindowDataMap] ensureDataForWindow:[mView window]];
515 NS_ASSERTION(!mTextInputHandler, "mTextInputHandler has already existed");
516 mTextInputHandler = new TextInputHandler(this, mView);
518 return NS_OK;
520 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
521 }
523 // Creates the appropriate child view. Override to create something other than
524 // our |ChildView| object. Autoreleases, so caller must retain.
525 NSView*
526 nsChildView::CreateCocoaView(NSRect inFrame)
527 {
528 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
530 return [[[ChildView alloc] initWithFrame:inFrame geckoChild:this] autorelease];
532 NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
533 }
535 void nsChildView::TearDownView()
536 {
537 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
539 if (!mView)
540 return;
542 NSWindow* win = [mView window];
543 NSResponder* responder = [win firstResponder];
545 // We're being unhooked from the view hierarchy, don't leave our view
546 // or a child view as the window first responder.
547 if (responder && [responder isKindOfClass:[NSView class]] &&
548 [(NSView*)responder isDescendantOf:mView]) {
549 [win makeFirstResponder:[mView superview]];
550 }
552 // If mView is win's contentView, win (mView's NSWindow) "owns" mView --
553 // win has retained mView, and will detach it from the view hierarchy and
554 // release it when necessary (when win is itself destroyed (in a call to
555 // [win dealloc])). So all we need to do here is call [mView release] (to
556 // match the call to [mView retain] in nsChildView::StandardCreate()).
557 // Also calling [mView removeFromSuperviewWithoutNeedingDisplay] causes
558 // mView to be released again and dealloced, while remaining win's
559 // contentView. So if we do that here, win will (for a short while) have
560 // an invalid contentView (for the consequences see bmo bugs 381087 and
561 // 374260).
562 if ([mView isEqual:[win contentView]]) {
563 [mView release];
564 } else {
565 // Stop NSView hierarchy being changed during [ChildView drawRect:]
566 [mView performSelectorOnMainThread:@selector(delayedTearDown) withObject:nil waitUntilDone:false];
567 }
568 mView = nil;
570 NS_OBJC_END_TRY_ABORT_BLOCK;
571 }
573 nsCocoaWindow*
574 nsChildView::GetXULWindowWidget()
575 {
576 id windowDelegate = [[mView window] delegate];
577 if (windowDelegate && [windowDelegate isKindOfClass:[WindowDelegate class]]) {
578 return [(WindowDelegate *)windowDelegate geckoWidget];
579 }
580 return nullptr;
581 }
583 NS_IMETHODIMP nsChildView::Destroy()
584 {
585 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
587 // Make sure that no composition is in progress while disconnecting
588 // ourselves from the view.
589 MutexAutoLock lock(mViewTearDownLock);
591 if (mOnDestroyCalled)
592 return NS_OK;
593 mOnDestroyCalled = true;
595 [mView widgetDestroyed];
597 nsBaseWidget::Destroy();
599 NotifyWindowDestroyed();
600 mParentWidget = nil;
602 TearDownView();
604 nsBaseWidget::OnDestroy();
606 return NS_OK;
608 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
609 }
611 #pragma mark -
613 #if 0
614 static void PrintViewHierarchy(NSView *view)
615 {
616 while (view) {
617 NSLog(@" view is %x, frame %@", view, NSStringFromRect([view frame]));
618 view = [view superview];
619 }
620 }
621 #endif
623 // Return native data according to aDataType
624 void* nsChildView::GetNativeData(uint32_t aDataType)
625 {
626 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSNULL;
628 void* retVal = nullptr;
630 switch (aDataType)
631 {
632 case NS_NATIVE_WIDGET:
633 case NS_NATIVE_DISPLAY:
634 retVal = (void*)mView;
635 break;
637 case NS_NATIVE_WINDOW:
638 retVal = [mView window];
639 break;
641 case NS_NATIVE_GRAPHIC:
642 NS_ERROR("Requesting NS_NATIVE_GRAPHIC on a Mac OS X child view!");
643 retVal = nullptr;
644 break;
646 case NS_NATIVE_OFFSETX:
647 retVal = 0;
648 break;
650 case NS_NATIVE_OFFSETY:
651 retVal = 0;
652 break;
654 case NS_NATIVE_PLUGIN_PORT:
655 case NS_NATIVE_PLUGIN_PORT_CG:
656 {
657 // The NP_CGContext pointer should always be NULL in the Cocoa event model.
658 if ([(ChildView*)mView pluginEventModel] == NPEventModelCocoa)
659 return nullptr;
661 UpdatePluginPort();
662 retVal = (void*)&mPluginCGContext;
663 break;
664 }
665 }
667 return retVal;
669 NS_OBJC_END_TRY_ABORT_BLOCK_NSNULL;
670 }
672 #pragma mark -
674 nsTransparencyMode nsChildView::GetTransparencyMode()
675 {
676 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
678 nsCocoaWindow* windowWidget = GetXULWindowWidget();
679 return windowWidget ? windowWidget->GetTransparencyMode() : eTransparencyOpaque;
681 NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(eTransparencyOpaque);
682 }
684 // This is called by nsContainerFrame on the root widget for all window types
685 // except popup windows (when nsCocoaWindow::SetTransparencyMode is used instead).
686 void nsChildView::SetTransparencyMode(nsTransparencyMode aMode)
687 {
688 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
690 nsCocoaWindow* windowWidget = GetXULWindowWidget();
691 if (windowWidget) {
692 windowWidget->SetTransparencyMode(aMode);
693 }
695 NS_OBJC_END_TRY_ABORT_BLOCK;
696 }
698 bool nsChildView::IsVisible() const
699 {
700 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
702 if (!mVisible) {
703 return mVisible;
704 }
706 // mVisible does not accurately reflect the state of a hidden tabbed view
707 // so verify that the view has a window as well
708 // then check native widget hierarchy visibility
709 return ([mView window] != nil) && !NSIsEmptyRect([mView visibleRect]);
711 NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false);
712 }
714 void nsChildView::HidePlugin()
715 {
716 NS_ASSERTION(mWindowType == eWindowType_plugin,
717 "HidePlugin called on non-plugin view");
718 }
720 void nsChildView::UpdatePluginPort()
721 {
722 NS_ASSERTION(mWindowType == eWindowType_plugin,
723 "UpdatePluginPort called on non-plugin view");
725 // [NSGraphicsContext currentContext] is supposed to "return the
726 // current graphics context of the current thread." But sometimes
727 // (when called while mView isn't focused for drawing) it returns a
728 // graphics context for the wrong window. [window graphicsContext]
729 // (which "provides the graphics context associated with the window
730 // for the current thread") seems always to return the "right"
731 // graphics context. See bug 500130.
732 mPluginCGContext.context = NULL;
733 mPluginCGContext.window = NULL;
734 }
736 static void HideChildPluginViews(NSView* aView)
737 {
738 NSArray* subviews = [aView subviews];
740 for (unsigned int i = 0; i < [subviews count]; ++i) {
741 NSView* view = [subviews objectAtIndex: i];
743 if (![view isKindOfClass:[ChildView class]])
744 continue;
746 ChildView* childview = static_cast<ChildView*>(view);
747 if ([childview isPluginView]) {
748 nsChildView* widget = static_cast<nsChildView*>([childview widget]);
749 if (widget) {
750 widget->HidePlugin();
751 }
752 } else {
753 HideChildPluginViews(view);
754 }
755 }
756 }
758 // Some NSView methods (e.g. setFrame and setHidden) invalidate the view's
759 // bounds in our window. However, we don't want these invalidations because
760 // they are unnecessary and because they actually slow us down since we
761 // block on the compositor inside drawRect.
762 // When we actually need something invalidated, there will be an explicit call
763 // to Invalidate from Gecko, so turning these automatic invalidations off
764 // won't hurt us in the non-OMTC case.
765 // The invalidations inside these NSView methods happen via a call to the
766 // private method -[NSWindow _setNeedsDisplayInRect:]. Our BaseWindow
767 // implementation of that method is augmented to let us ignore those calls
768 // using -[BaseWindow disable/enableSetNeedsDisplay].
769 static void
770 ManipulateViewWithoutNeedingDisplay(NSView* aView, void (^aCallback)())
771 {
772 BaseWindow* win = nil;
773 if ([[aView window] isKindOfClass:[BaseWindow class]]) {
774 win = (BaseWindow*)[aView window];
775 }
776 [win disableSetNeedsDisplay];
777 aCallback();
778 [win enableSetNeedsDisplay];
779 }
781 // Hide or show this component
782 NS_IMETHODIMP nsChildView::Show(bool aState)
783 {
784 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
786 if (aState != mVisible) {
787 // Provide an autorelease pool because this gets called during startup
788 // on the "hidden window", resulting in cocoa object leakage if there's
789 // no pool in place.
790 nsAutoreleasePool localPool;
792 ManipulateViewWithoutNeedingDisplay(mView, ^{
793 [mView setHidden:!aState];
794 });
796 mVisible = aState;
797 if (!mVisible && IsPluginView())
798 HidePlugin();
799 }
800 return NS_OK;
802 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
803 }
805 // Change the parent of this widget
806 NS_IMETHODIMP
807 nsChildView::SetParent(nsIWidget* aNewParent)
808 {
809 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
811 if (mOnDestroyCalled)
812 return NS_OK;
814 nsCOMPtr<nsIWidget> kungFuDeathGrip(this);
816 if (mParentWidget) {
817 mParentWidget->RemoveChild(this);
818 }
820 if (aNewParent) {
821 ReparentNativeWidget(aNewParent);
822 } else {
823 [mView removeFromSuperview];
824 mParentView = nil;
825 }
827 mParentWidget = aNewParent;
829 if (mParentWidget) {
830 mParentWidget->AddChild(this);
831 }
833 return NS_OK;
835 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
836 }
838 NS_IMETHODIMP
839 nsChildView::ReparentNativeWidget(nsIWidget* aNewParent)
840 {
841 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
843 NS_PRECONDITION(aNewParent, "");
845 if (mOnDestroyCalled)
846 return NS_OK;
848 NSView<mozView>* newParentView =
849 (NSView<mozView>*)aNewParent->GetNativeData(NS_NATIVE_WIDGET);
850 NS_ENSURE_TRUE(newParentView, NS_ERROR_FAILURE);
852 // we hold a ref to mView, so this is safe
853 [mView removeFromSuperview];
854 mParentView = newParentView;
855 [mParentView addSubview:mView];
856 return NS_OK;
858 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
859 }
861 void nsChildView::ResetParent()
862 {
863 if (!mOnDestroyCalled) {
864 if (mParentWidget)
865 mParentWidget->RemoveChild(this);
866 if (mView)
867 [mView removeFromSuperview];
868 }
869 mParentWidget = nullptr;
870 }
872 nsIWidget*
873 nsChildView::GetParent()
874 {
875 return mParentWidget;
876 }
878 float
879 nsChildView::GetDPI()
880 {
881 NSWindow* window = [mView window];
882 if (window && [window isKindOfClass:[BaseWindow class]]) {
883 return [(BaseWindow*)window getDPI];
884 }
886 return 96.0;
887 }
889 NS_IMETHODIMP nsChildView::Enable(bool aState)
890 {
891 return NS_OK;
892 }
894 bool nsChildView::IsEnabled() const
895 {
896 return true;
897 }
899 NS_IMETHODIMP nsChildView::SetFocus(bool aRaise)
900 {
901 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
903 NSWindow* window = [mView window];
904 if (window)
905 [window makeFirstResponder:mView];
906 return NS_OK;
908 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
909 }
911 // Override to set the cursor on the mac
912 NS_IMETHODIMP nsChildView::SetCursor(nsCursor aCursor)
913 {
914 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
916 if ([mView isDragInProgress])
917 return NS_OK; // Don't change the cursor during dragging.
919 nsBaseWidget::SetCursor(aCursor);
920 return [[nsCursorManager sharedInstance] setCursor:aCursor];
922 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
923 }
925 // implement to fix "hidden virtual function" warning
926 NS_IMETHODIMP nsChildView::SetCursor(imgIContainer* aCursor,
927 uint32_t aHotspotX, uint32_t aHotspotY)
928 {
929 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
931 nsBaseWidget::SetCursor(aCursor, aHotspotX, aHotspotY);
932 return [[nsCursorManager sharedInstance] setCursorWithImage:aCursor hotSpotX:aHotspotX hotSpotY:aHotspotY scaleFactor:BackingScaleFactor()];
934 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
935 }
937 #pragma mark -
939 // Get this component dimension
940 NS_IMETHODIMP nsChildView::GetBounds(nsIntRect &aRect)
941 {
942 if (!mView) {
943 aRect = mBounds;
944 } else {
945 aRect = CocoaPointsToDevPixels([mView frame]);
946 }
947 return NS_OK;
948 }
950 NS_IMETHODIMP nsChildView::GetClientBounds(nsIntRect &aRect)
951 {
952 GetBounds(aRect);
953 if (!mParentWidget) {
954 // For top level widgets we want the position on screen, not the position
955 // of this view inside the window.
956 MOZ_ASSERT(mWindowType != eWindowType_plugin, "plugin widgets should have parents");
957 aRect.MoveTo(WidgetToScreenOffset());
958 }
959 return NS_OK;
960 }
962 NS_IMETHODIMP nsChildView::GetScreenBounds(nsIntRect &aRect)
963 {
964 GetBounds(aRect);
965 aRect.MoveTo(WidgetToScreenOffset());
966 return NS_OK;
967 }
969 double
970 nsChildView::GetDefaultScaleInternal()
971 {
972 return BackingScaleFactor();
973 }
975 CGFloat
976 nsChildView::BackingScaleFactor()
977 {
978 if (mBackingScaleFactor > 0.0) {
979 return mBackingScaleFactor;
980 }
981 if (!mView) {
982 return 1.0;
983 }
984 mBackingScaleFactor = nsCocoaUtils::GetBackingScaleFactor(mView);
985 return mBackingScaleFactor;
986 }
988 void
989 nsChildView::BackingScaleFactorChanged()
990 {
991 CGFloat newScale = nsCocoaUtils::GetBackingScaleFactor(mView);
993 // ignore notification if it hasn't really changed (or maybe we have
994 // disabled HiDPI mode via prefs)
995 if (mBackingScaleFactor == newScale) {
996 return;
997 }
999 mBackingScaleFactor = newScale;
1001 if (mWidgetListener && !mWidgetListener->GetXULWindow()) {
1002 nsIPresShell* presShell = mWidgetListener->GetPresShell();
1003 if (presShell) {
1004 presShell->BackingScaleFactorChanged();
1005 }
1006 }
1008 if (IsPluginView()) {
1009 nsEventStatus status = nsEventStatus_eIgnore;
1010 WidgetGUIEvent guiEvent(true, NS_PLUGIN_RESOLUTION_CHANGED, this);
1011 guiEvent.time = PR_IntervalNow();
1012 DispatchEvent(&guiEvent, status);
1013 }
1014 }
1016 int32_t
1017 nsChildView::RoundsWidgetCoordinatesTo()
1018 {
1019 if (BackingScaleFactor() == 2.0) {
1020 return 2;
1021 }
1022 return 1;
1023 }
1025 NS_IMETHODIMP nsChildView::ConstrainPosition(bool aAllowSlop,
1026 int32_t *aX, int32_t *aY)
1027 {
1028 return NS_OK;
1029 }
1031 // Move this component, aX and aY are in the parent widget coordinate system
1032 NS_IMETHODIMP nsChildView::Move(double aX, double aY)
1033 {
1034 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
1036 int32_t x = NSToIntRound(aX);
1037 int32_t y = NSToIntRound(aY);
1039 if (!mView || (mBounds.x == x && mBounds.y == y))
1040 return NS_OK;
1042 mBounds.x = x;
1043 mBounds.y = y;
1045 ManipulateViewWithoutNeedingDisplay(mView, ^{
1046 [mView setFrame:DevPixelsToCocoaPoints(mBounds)];
1047 });
1049 NotifyRollupGeometryChange();
1050 ReportMoveEvent();
1052 return NS_OK;
1054 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
1055 }
1057 NS_IMETHODIMP nsChildView::Resize(double aWidth, double aHeight, bool aRepaint)
1058 {
1059 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
1061 int32_t width = NSToIntRound(aWidth);
1062 int32_t height = NSToIntRound(aHeight);
1064 if (!mView || (mBounds.width == width && mBounds.height == height))
1065 return NS_OK;
1067 mBounds.width = width;
1068 mBounds.height = height;
1070 ManipulateViewWithoutNeedingDisplay(mView, ^{
1071 [mView setFrame:DevPixelsToCocoaPoints(mBounds)];
1072 });
1074 if (mVisible && aRepaint)
1075 [mView setNeedsDisplay:YES];
1077 NotifyRollupGeometryChange();
1078 ReportSizeEvent();
1080 return NS_OK;
1082 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
1083 }
1085 NS_IMETHODIMP nsChildView::Resize(double aX, double aY,
1086 double aWidth, double aHeight, bool aRepaint)
1087 {
1088 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
1090 int32_t x = NSToIntRound(aX);
1091 int32_t y = NSToIntRound(aY);
1092 int32_t width = NSToIntRound(aWidth);
1093 int32_t height = NSToIntRound(aHeight);
1095 BOOL isMoving = (mBounds.x != x || mBounds.y != y);
1096 BOOL isResizing = (mBounds.width != width || mBounds.height != height);
1097 if (!mView || (!isMoving && !isResizing))
1098 return NS_OK;
1100 if (isMoving) {
1101 mBounds.x = x;
1102 mBounds.y = y;
1103 }
1104 if (isResizing) {
1105 mBounds.width = width;
1106 mBounds.height = height;
1107 }
1109 ManipulateViewWithoutNeedingDisplay(mView, ^{
1110 [mView setFrame:DevPixelsToCocoaPoints(mBounds)];
1111 });
1113 if (mVisible && aRepaint)
1114 [mView setNeedsDisplay:YES];
1116 NotifyRollupGeometryChange();
1117 if (isMoving) {
1118 ReportMoveEvent();
1119 if (mOnDestroyCalled)
1120 return NS_OK;
1121 }
1122 if (isResizing)
1123 ReportSizeEvent();
1125 return NS_OK;
1127 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
1128 }
1130 static const int32_t resizeIndicatorWidth = 15;
1131 static const int32_t resizeIndicatorHeight = 15;
1132 bool nsChildView::ShowsResizeIndicator(nsIntRect* aResizerRect)
1133 {
1134 NSView *topLevelView = mView, *superView = nil;
1135 while ((superView = [topLevelView superview]))
1136 topLevelView = superView;
1138 if (![[topLevelView window] showsResizeIndicator] ||
1139 !([[topLevelView window] styleMask] & NSResizableWindowMask))
1140 return false;
1142 if (aResizerRect) {
1143 NSSize bounds = [topLevelView bounds].size;
1144 NSPoint corner = NSMakePoint(bounds.width, [topLevelView isFlipped] ? bounds.height : 0);
1145 corner = [topLevelView convertPoint:corner toView:mView];
1146 aResizerRect->SetRect(NSToIntRound(corner.x) - resizeIndicatorWidth,
1147 NSToIntRound(corner.y) - resizeIndicatorHeight,
1148 resizeIndicatorWidth, resizeIndicatorHeight);
1149 }
1150 return true;
1151 }
1153 // In QuickDraw mode the coordinate system used here should be that of the
1154 // browser window's content region (defined as everything but the 22-pixel
1155 // high titlebar). But in CoreGraphics mode the coordinate system should be
1156 // that of the browser window as a whole (including its titlebar). Both
1157 // coordinate systems have a top-left origin. See bmo bug 474491.
1158 //
1159 // There's a bug in this method's code -- it currently uses the QuickDraw
1160 // coordinate system for both the QuickDraw and CoreGraphics drawing modes.
1161 // This bug is fixed by the patch for bug 474491. But the Flash plugin (both
1162 // version 10.0.12.36 from Adobe and version 9.0 r151 from Apple) has Mozilla-
1163 // specific code to work around this bug, which breaks when we fix it (see bmo
1164 // bug 477077). So we'll need to coordinate releasing a fix for this bug with
1165 // Adobe and other major plugin vendors that support the CoreGraphics mode.
1166 //
1167 // outClipRect and outOrigin are in display pixels, not device pixels.
1168 NS_IMETHODIMP nsChildView::GetPluginClipRect(nsIntRect& outClipRect, nsIntPoint& outOrigin, bool& outWidgetVisible)
1169 {
1170 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
1172 NS_ASSERTION(mWindowType == eWindowType_plugin,
1173 "GetPluginClipRect must only be called on a plugin widget");
1174 if (mWindowType != eWindowType_plugin) return NS_ERROR_FAILURE;
1176 NSWindow* window = [mView window];
1177 if (!window) return NS_ERROR_FAILURE;
1179 NSPoint viewOrigin = [mView convertPoint:NSZeroPoint toView:nil];
1180 NSRect frame = [[window contentView] frame];
1181 viewOrigin.y = frame.size.height - viewOrigin.y;
1183 // set up the clipping region for plugins.
1184 NSRect visibleBounds = [mView visibleRect];
1185 NSPoint clipOrigin = [mView convertPoint:visibleBounds.origin toView:nil];
1187 // Convert from cocoa to QuickDraw coordinates
1188 clipOrigin.y = frame.size.height - clipOrigin.y;
1190 outClipRect.x = NSToIntRound(clipOrigin.x);
1191 outClipRect.y = NSToIntRound(clipOrigin.y);
1193 // need to convert view's origin to window coordinates.
1194 // then, encode as "SetOrigin" ready values.
1195 outOrigin.x = -NSToIntRound(viewOrigin.x);
1196 outOrigin.y = -NSToIntRound(viewOrigin.y);
1198 if (IsVisible() && [mView window] != nil) {
1199 outClipRect.width = NSToIntRound(visibleBounds.origin.x + visibleBounds.size.width) - NSToIntRound(visibleBounds.origin.x);
1200 outClipRect.height = NSToIntRound(visibleBounds.origin.y + visibleBounds.size.height) - NSToIntRound(visibleBounds.origin.y);
1202 if (mClipRects) {
1203 nsIntRect clipBounds;
1204 for (uint32_t i = 0; i < mClipRectCount; ++i) {
1205 NSRect cocoaPoints = DevPixelsToCocoaPoints(mClipRects[i]);
1206 clipBounds.UnionRect(clipBounds, nsIntRect(NSToIntRound(cocoaPoints.origin.x),
1207 NSToIntRound(cocoaPoints.origin.y),
1208 NSToIntRound(cocoaPoints.size.width),
1209 NSToIntRound(cocoaPoints.size.height)));
1210 }
1211 outClipRect.IntersectRect(outClipRect, clipBounds - outOrigin);
1212 }
1214 // XXXroc should this be !outClipRect.IsEmpty()?
1215 outWidgetVisible = true;
1216 }
1217 else {
1218 outClipRect.width = 0;
1219 outClipRect.height = 0;
1220 outWidgetVisible = false;
1221 }
1223 return NS_OK;
1225 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
1226 }
1228 NS_IMETHODIMP nsChildView::StartDrawPlugin()
1229 {
1230 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
1232 NS_ASSERTION(mWindowType == eWindowType_plugin,
1233 "StartDrawPlugin must only be called on a plugin widget");
1234 if (mWindowType != eWindowType_plugin) return NS_ERROR_FAILURE;
1236 // This code is necessary for CoreGraphics in 32-bit builds.
1237 // See comments below about why. In 64-bit CoreGraphics mode we will not keep
1238 // this region up to date, plugins should not depend on it.
1239 #ifndef __LP64__
1240 NSWindow* window = [mView window];
1241 if (!window)
1242 return NS_ERROR_FAILURE;
1244 // In QuickDraw drawing mode we used to prevent reentrant handling of any
1245 // plugin event. But in CoreGraphics drawing mode only do this if the current
1246 // plugin event isn't an update/paint event. This allows popupcontextmenu()
1247 // to work properly from a plugin that supports the Cocoa event model,
1248 // without regressing bug 409615. See bug 435041. (StartDrawPlugin() and
1249 // EndDrawPlugin() wrap every call to nsIPluginInstance::HandleEvent() --
1250 // not just calls that "draw" or paint.)
1251 if (mIsDispatchPaint && mPluginDrawing) {
1252 return NS_ERROR_FAILURE;
1253 }
1255 // It appears that the WindowRef from which we get the plugin port undergoes the
1256 // traditional BeginUpdate/EndUpdate cycle, which, if you recall, sets the visible
1257 // region to the intersection of the visible region and the update region. Since
1258 // we don't know here if we're being drawn inside a BeginUpdate/EndUpdate pair
1259 // (which seem to occur in [NSWindow display]), and we don't want to have the burden
1260 // of correctly doing Carbon invalidates of the plugin rect, we manually set the
1261 // visible region to be the entire port every time. It is necessary to set up our
1262 // window's port even for CoreGraphics plugins, because they may still use Carbon
1263 // internally (see bug #420527 for details).
1264 //
1265 // Don't use this code if any of the QuickDraw APIs it currently requires are
1266 // missing (as they probably will be on OS X 10.8 and up).
1267 if (::NewRgn && ::GetQDGlobalsThePort && ::GetGWorld && ::SetGWorld &&
1268 ::IsPortOffscreen && ::GetMainDevice && ::SetOrigin && ::RectRgn &&
1269 ::SetPortVisibleRegion && ::SetPortClipRegion && ::DisposeRgn) {
1270 RgnHandle pluginRegion = ::NewRgn();
1271 if (pluginRegion) {
1272 CGrafPtr port = ::GetWindowPort(WindowRef([window windowRef]));
1273 bool portChanged = (port != CGrafPtr(::GetQDGlobalsThePort()));
1274 CGrafPtr oldPort;
1275 GDHandle oldDevice;
1277 if (portChanged) {
1278 ::GetGWorld(&oldPort, &oldDevice);
1279 ::SetGWorld(port, ::IsPortOffscreen(port) ? nullptr : ::GetMainDevice());
1280 }
1282 ::SetOrigin(0, 0);
1284 nsIntRect clipRect; // this is in native window coordinates
1285 nsIntPoint origin;
1286 bool visible;
1287 GetPluginClipRect(clipRect, origin, visible);
1289 // XXX if we're not visible, set an empty clip region?
1290 Rect pluginRect;
1291 ConvertGeckoRectToMacRect(clipRect, pluginRect);
1293 ::RectRgn(pluginRegion, &pluginRect);
1294 ::SetPortVisibleRegion(port, pluginRegion);
1295 ::SetPortClipRegion(port, pluginRegion);
1297 // now set up the origin for the plugin
1298 ::SetOrigin(origin.x, origin.y);
1300 ::DisposeRgn(pluginRegion);
1302 if (portChanged) {
1303 ::SetGWorld(oldPort, oldDevice);
1304 }
1305 }
1306 }
1307 #endif
1309 mPluginDrawing = true;
1310 return NS_OK;
1312 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
1313 }
1315 NS_IMETHODIMP nsChildView::EndDrawPlugin()
1316 {
1317 NS_ASSERTION(mWindowType == eWindowType_plugin,
1318 "EndDrawPlugin must only be called on a plugin widget");
1319 if (mWindowType != eWindowType_plugin) return NS_ERROR_FAILURE;
1321 mPluginDrawing = false;
1322 return NS_OK;
1323 }
1325 NS_IMETHODIMP nsChildView::SetPluginInstanceOwner(nsIPluginInstanceOwner* aInstanceOwner)
1326 {
1327 mPluginInstanceOwner = aInstanceOwner;
1329 return NS_OK;
1330 }
1332 NS_IMETHODIMP nsChildView::SetPluginEventModel(int inEventModel)
1333 {
1334 [(ChildView*)mView setPluginEventModel:(NPEventModel)inEventModel];
1335 return NS_OK;
1336 }
1338 NS_IMETHODIMP nsChildView::GetPluginEventModel(int* outEventModel)
1339 {
1340 *outEventModel = [(ChildView*)mView pluginEventModel];
1341 return NS_OK;
1342 }
1344 NS_IMETHODIMP nsChildView::SetPluginDrawingModel(int inDrawingModel)
1345 {
1346 [(ChildView*)mView setPluginDrawingModel:(NPDrawingModel)inDrawingModel];
1347 return NS_OK;
1348 }
1350 NS_IMETHODIMP nsChildView::StartComplexTextInputForCurrentEvent()
1351 {
1352 return mTextInputHandler->StartComplexTextInputForCurrentEvent();
1353 }
1355 nsresult nsChildView::SynthesizeNativeKeyEvent(int32_t aNativeKeyboardLayout,
1356 int32_t aNativeKeyCode,
1357 uint32_t aModifierFlags,
1358 const nsAString& aCharacters,
1359 const nsAString& aUnmodifiedCharacters)
1360 {
1361 return mTextInputHandler->SynthesizeNativeKeyEvent(aNativeKeyboardLayout,
1362 aNativeKeyCode,
1363 aModifierFlags,
1364 aCharacters,
1365 aUnmodifiedCharacters);
1366 }
1368 nsresult nsChildView::SynthesizeNativeMouseEvent(nsIntPoint aPoint,
1369 uint32_t aNativeMessage,
1370 uint32_t aModifierFlags)
1371 {
1372 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
1374 NSPoint pt =
1375 nsCocoaUtils::DevPixelsToCocoaPoints(aPoint, BackingScaleFactor());
1377 // Move the mouse cursor to the requested position and reconnect it to the mouse.
1378 CGWarpMouseCursorPosition(NSPointToCGPoint(pt));
1379 CGAssociateMouseAndMouseCursorPosition(true);
1381 // aPoint is given with the origin on the top left, but convertScreenToBase
1382 // expects a point in a coordinate system that has its origin on the bottom left.
1383 NSPoint screenPoint = NSMakePoint(pt.x, nsCocoaUtils::FlippedScreenY(pt.y));
1384 NSPoint windowPoint = [[mView window] convertScreenToBase:screenPoint];
1386 NSEvent* event = [NSEvent mouseEventWithType:(NSEventType)aNativeMessage
1387 location:windowPoint
1388 modifierFlags:aModifierFlags
1389 timestamp:[NSDate timeIntervalSinceReferenceDate]
1390 windowNumber:[[mView window] windowNumber]
1391 context:nil
1392 eventNumber:0
1393 clickCount:1
1394 pressure:0.0];
1396 if (!event)
1397 return NS_ERROR_FAILURE;
1399 if ([[mView window] isKindOfClass:[BaseWindow class]]) {
1400 // Tracking area events don't end up in their tracking areas when sent
1401 // through [NSApp sendEvent:], so pass them directly to the right methods.
1402 BaseWindow* window = (BaseWindow*)[mView window];
1403 if (aNativeMessage == NSMouseEntered) {
1404 [window mouseEntered:event];
1405 return NS_OK;
1406 }
1407 if (aNativeMessage == NSMouseExited) {
1408 [window mouseExited:event];
1409 return NS_OK;
1410 }
1411 if (aNativeMessage == NSMouseMoved) {
1412 [window mouseMoved:event];
1413 return NS_OK;
1414 }
1415 }
1417 [NSApp sendEvent:event];
1418 return NS_OK;
1420 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
1421 }
1423 // First argument has to be an NSMenu representing the application's top-level
1424 // menu bar. The returned item is *not* retained.
1425 static NSMenuItem* NativeMenuItemWithLocation(NSMenu* menubar, NSString* locationString)
1426 {
1427 NSArray* indexes = [locationString componentsSeparatedByString:@"|"];
1428 unsigned int indexCount = [indexes count];
1429 if (indexCount == 0)
1430 return nil;
1432 NSMenu* currentSubmenu = [NSApp mainMenu];
1433 for (unsigned int i = 0; i < indexCount; i++) {
1434 int targetIndex;
1435 // We remove the application menu from consideration for the top-level menu
1436 if (i == 0)
1437 targetIndex = [[indexes objectAtIndex:i] intValue] + 1;
1438 else
1439 targetIndex = [[indexes objectAtIndex:i] intValue];
1440 int itemCount = [currentSubmenu numberOfItems];
1441 if (targetIndex < itemCount) {
1442 NSMenuItem* menuItem = [currentSubmenu itemAtIndex:targetIndex];
1443 // if this is the last index just return the menu item
1444 if (i == (indexCount - 1))
1445 return menuItem;
1446 // if this is not the last index find the submenu and keep going
1447 if ([menuItem hasSubmenu])
1448 currentSubmenu = [menuItem submenu];
1449 else
1450 return nil;
1451 }
1452 }
1454 return nil;
1455 }
1457 // Used for testing native menu system structure and event handling.
1458 NS_IMETHODIMP nsChildView::ActivateNativeMenuItemAt(const nsAString& indexString)
1459 {
1460 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
1462 NSString* locationString = [NSString stringWithCharacters:reinterpret_cast<const unichar*>(indexString.BeginReading())
1463 length:indexString.Length()];
1464 NSMenuItem* item = NativeMenuItemWithLocation([NSApp mainMenu], locationString);
1465 // We can't perform an action on an item with a submenu, that will raise
1466 // an obj-c exception.
1467 if (item && ![item hasSubmenu]) {
1468 NSMenu* parent = [item menu];
1469 if (parent) {
1470 // NSLog(@"Performing action for native menu item titled: %@\n",
1471 // [[currentSubmenu itemAtIndex:targetIndex] title]);
1472 [parent performActionForItemAtIndex:[parent indexOfItem:item]];
1473 return NS_OK;
1474 }
1475 }
1476 return NS_ERROR_FAILURE;
1478 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
1479 }
1481 // Used for testing native menu system structure and event handling.
1482 NS_IMETHODIMP nsChildView::ForceUpdateNativeMenuAt(const nsAString& indexString)
1483 {
1484 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
1486 nsCocoaWindow *widget = GetXULWindowWidget();
1487 if (widget) {
1488 nsMenuBarX* mb = widget->GetMenuBar();
1489 if (mb) {
1490 if (indexString.IsEmpty())
1491 mb->ForceNativeMenuReload();
1492 else
1493 mb->ForceUpdateNativeMenuAt(indexString);
1494 }
1495 }
1496 return NS_OK;
1498 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
1499 }
1501 #pragma mark -
1503 #ifdef INVALIDATE_DEBUGGING
1505 static Boolean KeyDown(const UInt8 theKey)
1506 {
1507 KeyMap map;
1508 GetKeys(map);
1509 return ((*((UInt8 *)map + (theKey >> 3)) >> (theKey & 7)) & 1) != 0;
1510 }
1512 static Boolean caps_lock()
1513 {
1514 return KeyDown(0x39);
1515 }
1517 static void blinkRect(Rect* r)
1518 {
1519 StRegionFromPool oldClip;
1520 if (oldClip != NULL)
1521 ::GetClip(oldClip);
1523 ::ClipRect(r);
1524 ::InvertRect(r);
1525 UInt32 end = ::TickCount() + 5;
1526 while (::TickCount() < end) ;
1527 ::InvertRect(r);
1529 if (oldClip != NULL)
1530 ::SetClip(oldClip);
1531 }
1533 static void blinkRgn(RgnHandle rgn)
1534 {
1535 StRegionFromPool oldClip;
1536 if (oldClip != NULL)
1537 ::GetClip(oldClip);
1539 ::SetClip(rgn);
1540 ::InvertRgn(rgn);
1541 UInt32 end = ::TickCount() + 5;
1542 while (::TickCount() < end) ;
1543 ::InvertRgn(rgn);
1545 if (oldClip != NULL)
1546 ::SetClip(oldClip);
1547 }
1549 #endif
1551 // Invalidate this component's visible area
1552 NS_IMETHODIMP nsChildView::Invalidate(const nsIntRect &aRect)
1553 {
1554 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
1556 if (!mView || !mVisible)
1557 return NS_OK;
1559 NS_ASSERTION(GetLayerManager()->GetBackendType() != LayersBackend::LAYERS_CLIENT,
1560 "Shouldn't need to invalidate with accelerated OMTC layers!");
1562 if ([NSView focusView]) {
1563 // if a view is focussed (i.e. being drawn), then postpone the invalidate so that we
1564 // don't lose it.
1565 [mView setNeedsPendingDisplayInRect:DevPixelsToCocoaPoints(aRect)];
1566 }
1567 else {
1568 [mView setNeedsDisplayInRect:DevPixelsToCocoaPoints(aRect)];
1569 }
1571 return NS_OK;
1573 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
1574 }
1576 bool
1577 nsChildView::ComputeShouldAccelerate(bool aDefault)
1578 {
1579 // Don't use OpenGL for transparent windows or for popup windows.
1580 if (!mView || ![[mView window] isOpaque] ||
1581 [[mView window] isKindOfClass:[PopupWindow class]])
1582 return false;
1584 return nsBaseWidget::ComputeShouldAccelerate(aDefault);
1585 }
1587 bool
1588 nsChildView::ShouldUseOffMainThreadCompositing()
1589 {
1590 // Don't use OMTC for transparent windows or for popup windows.
1591 if (!mView || ![[mView window] isOpaque] ||
1592 [[mView window] isKindOfClass:[PopupWindow class]])
1593 return false;
1595 return nsBaseWidget::ShouldUseOffMainThreadCompositing();
1596 }
1598 inline uint16_t COLOR8TOCOLOR16(uint8_t color8)
1599 {
1600 // return (color8 == 0xFF ? 0xFFFF : (color8 << 8));
1601 return (color8 << 8) | color8; /* (color8 * 257) == (color8 * 0x0101) */
1602 }
1604 #pragma mark -
1606 nsresult nsChildView::ConfigureChildren(const nsTArray<Configuration>& aConfigurations)
1607 {
1608 for (uint32_t i = 0; i < aConfigurations.Length(); ++i) {
1609 const Configuration& config = aConfigurations[i];
1610 nsChildView* child = static_cast<nsChildView*>(config.mChild);
1611 #ifdef DEBUG
1612 nsWindowType kidType = child->WindowType();
1613 #endif
1614 NS_ASSERTION(kidType == eWindowType_plugin,
1615 "Configured widget is not a plugin type");
1616 NS_ASSERTION(child->GetParent() == this,
1617 "Configured widget is not a child of the right widget");
1619 // nsIWidget::Show() doesn't get called on plugin widgets unless we call
1620 // it from here. See bug 592563.
1621 child->Show(!config.mClipRegion.IsEmpty());
1623 child->Resize(
1624 config.mBounds.x, config.mBounds.y,
1625 config.mBounds.width, config.mBounds.height,
1626 false);
1628 // Store the clip region here in case GetPluginClipRect needs it.
1629 child->StoreWindowClipRegion(config.mClipRegion);
1630 }
1631 return NS_OK;
1632 }
1634 // Invokes callback and ProcessEvent methods on Event Listener object
1635 NS_IMETHODIMP nsChildView::DispatchEvent(WidgetGUIEvent* event,
1636 nsEventStatus& aStatus)
1637 {
1638 #ifdef DEBUG
1639 debug_DumpEvent(stdout, event->widget, event, nsAutoCString("something"), 0);
1640 #endif
1642 NS_ASSERTION(!(mTextInputHandler && mTextInputHandler->IsIMEComposing() &&
1643 event->HasKeyEventMessage()),
1644 "Any key events should not be fired during IME composing");
1646 if (event->mFlags.mIsSynthesizedForTests) {
1647 WidgetKeyboardEvent* keyEvent = event->AsKeyboardEvent();
1648 if (keyEvent) {
1649 nsresult rv = mTextInputHandler->AttachNativeKeyEvent(*keyEvent);
1650 NS_ENSURE_SUCCESS(rv, rv);
1651 }
1652 }
1654 aStatus = nsEventStatus_eIgnore;
1656 nsIWidgetListener* listener = mWidgetListener;
1658 // If the listener is NULL, check if the parent is a popup. If it is, then
1659 // this child is the popup content view attached to a popup. Get the
1660 // listener from the parent popup instead.
1661 nsCOMPtr<nsIWidget> kungFuDeathGrip = do_QueryInterface(mParentWidget ? mParentWidget : this);
1662 if (!listener && mParentWidget) {
1663 if (mParentWidget->WindowType() == eWindowType_popup) {
1664 // Check just in case event->widget isn't this widget
1665 if (event->widget)
1666 listener = event->widget->GetWidgetListener();
1667 if (!listener) {
1668 event->widget = mParentWidget;
1669 listener = mParentWidget->GetWidgetListener();
1670 }
1671 }
1672 }
1674 if (listener)
1675 aStatus = listener->HandleEvent(event, mUseAttachedEvents);
1677 return NS_OK;
1678 }
1680 bool nsChildView::DispatchWindowEvent(WidgetGUIEvent& event)
1681 {
1682 nsEventStatus status;
1683 DispatchEvent(&event, status);
1684 return ConvertStatus(status);
1685 }
1687 nsIWidget*
1688 nsChildView::GetWidgetForListenerEvents()
1689 {
1690 // If there is no listener, use the parent popup's listener if that exists.
1691 if (!mWidgetListener && mParentWidget &&
1692 mParentWidget->WindowType() == eWindowType_popup) {
1693 return mParentWidget;
1694 }
1696 return this;
1697 }
1699 void nsChildView::WillPaintWindow()
1700 {
1701 nsCOMPtr<nsIWidget> widget = GetWidgetForListenerEvents();
1703 nsIWidgetListener* listener = widget->GetWidgetListener();
1704 if (listener) {
1705 listener->WillPaintWindow(widget);
1706 }
1707 }
1709 bool nsChildView::PaintWindow(nsIntRegion aRegion)
1710 {
1711 nsCOMPtr<nsIWidget> widget = GetWidgetForListenerEvents();
1713 nsIWidgetListener* listener = widget->GetWidgetListener();
1714 if (!listener)
1715 return false;
1717 bool returnValue = false;
1718 bool oldDispatchPaint = mIsDispatchPaint;
1719 mIsDispatchPaint = true;
1720 returnValue = listener->PaintWindow(widget, aRegion);
1722 listener = widget->GetWidgetListener();
1723 if (listener) {
1724 listener->DidPaintWindow();
1725 }
1727 mIsDispatchPaint = oldDispatchPaint;
1728 return returnValue;
1729 }
1731 #pragma mark -
1733 void nsChildView::ReportMoveEvent()
1734 {
1735 NotifyWindowMoved(mBounds.x, mBounds.y);
1736 }
1738 void nsChildView::ReportSizeEvent()
1739 {
1740 if (mWidgetListener)
1741 mWidgetListener->WindowResized(this, mBounds.width, mBounds.height);
1742 }
1744 #pragma mark -
1746 // Return the offset between this child view and the screen.
1747 // @return -- widget origin in device-pixel coords
1748 nsIntPoint nsChildView::WidgetToScreenOffset()
1749 {
1750 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
1752 NSPoint origin = NSMakePoint(0, 0);
1754 // 1. First translate view origin point into window coords.
1755 // The returned point is in bottom-left coordinates.
1756 origin = [mView convertPoint:origin toView:nil];
1758 // 2. We turn the window-coord rect's origin into screen (still bottom-left) coords.
1759 origin = [[mView window] convertBaseToScreen:origin];
1761 // 3. Since we're dealing in bottom-left coords, we need to make it top-left coords
1762 // before we pass it back to Gecko.
1763 FlipCocoaScreenCoordinate(origin);
1765 // convert to device pixels
1766 return CocoaPointsToDevPixels(origin);
1768 NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(nsIntPoint(0,0));
1769 }
1771 NS_IMETHODIMP nsChildView::CaptureRollupEvents(nsIRollupListener * aListener,
1772 bool aDoCapture)
1773 {
1774 // this never gets called, only top-level windows can be rollup widgets
1775 return NS_OK;
1776 }
1778 NS_IMETHODIMP nsChildView::SetTitle(const nsAString& title)
1779 {
1780 // child views don't have titles
1781 return NS_OK;
1782 }
1784 NS_IMETHODIMP nsChildView::GetAttention(int32_t aCycleCount)
1785 {
1786 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
1788 [NSApp requestUserAttention:NSInformationalRequest];
1789 return NS_OK;
1791 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
1792 }
1794 /* static */
1795 bool nsChildView::DoHasPendingInputEvent()
1796 {
1797 return sLastInputEventCount != GetCurrentInputEventCount();
1798 }
1800 /* static */
1801 uint32_t nsChildView::GetCurrentInputEventCount()
1802 {
1803 // Can't use kCGAnyInputEventType because that updates too rarely for us (and
1804 // always in increments of 30+!) and because apparently it's sort of broken
1805 // on Tiger. So just go ahead and query the counters we care about.
1806 static const CGEventType eventTypes[] = {
1807 kCGEventLeftMouseDown,
1808 kCGEventLeftMouseUp,
1809 kCGEventRightMouseDown,
1810 kCGEventRightMouseUp,
1811 kCGEventMouseMoved,
1812 kCGEventLeftMouseDragged,
1813 kCGEventRightMouseDragged,
1814 kCGEventKeyDown,
1815 kCGEventKeyUp,
1816 kCGEventScrollWheel,
1817 kCGEventTabletPointer,
1818 kCGEventOtherMouseDown,
1819 kCGEventOtherMouseUp,
1820 kCGEventOtherMouseDragged
1821 };
1823 uint32_t eventCount = 0;
1824 for (uint32_t i = 0; i < ArrayLength(eventTypes); ++i) {
1825 eventCount +=
1826 CGEventSourceCounterForEventType(kCGEventSourceStateCombinedSessionState,
1827 eventTypes[i]);
1828 }
1829 return eventCount;
1830 }
1832 /* static */
1833 void nsChildView::UpdateCurrentInputEventCount()
1834 {
1835 sLastInputEventCount = GetCurrentInputEventCount();
1836 }
1838 bool nsChildView::HasPendingInputEvent()
1839 {
1840 return DoHasPendingInputEvent();
1841 }
1843 #pragma mark -
1845 NS_IMETHODIMP
1846 nsChildView::NotifyIME(const IMENotification& aIMENotification)
1847 {
1848 switch (aIMENotification.mMessage) {
1849 case REQUEST_TO_COMMIT_COMPOSITION:
1850 NS_ENSURE_TRUE(mTextInputHandler, NS_ERROR_NOT_AVAILABLE);
1851 mTextInputHandler->CommitIMEComposition();
1852 return NS_OK;
1853 case REQUEST_TO_CANCEL_COMPOSITION:
1854 NS_ENSURE_TRUE(mTextInputHandler, NS_ERROR_NOT_AVAILABLE);
1855 mTextInputHandler->CancelIMEComposition();
1856 return NS_OK;
1857 case NOTIFY_IME_OF_FOCUS:
1858 if (mInputContext.IsPasswordEditor()) {
1859 TextInputHandler::EnableSecureEventInput();
1860 }
1862 NS_ENSURE_TRUE(mTextInputHandler, NS_ERROR_NOT_AVAILABLE);
1863 mTextInputHandler->OnFocusChangeInGecko(true);
1864 return NS_OK;
1865 case NOTIFY_IME_OF_BLUR:
1866 // When we're going to be deactive, we must disable the secure event input
1867 // mode, see the Carbon Event Manager Reference.
1868 TextInputHandler::EnsureSecureEventInputDisabled();
1870 NS_ENSURE_TRUE(mTextInputHandler, NS_ERROR_NOT_AVAILABLE);
1871 mTextInputHandler->OnFocusChangeInGecko(false);
1872 return NS_OK;
1873 case NOTIFY_IME_OF_SELECTION_CHANGE:
1874 NS_ENSURE_TRUE(mTextInputHandler, NS_ERROR_NOT_AVAILABLE);
1875 mTextInputHandler->OnSelectionChange();
1876 default:
1877 return NS_ERROR_NOT_IMPLEMENTED;
1878 }
1879 }
1881 NS_IMETHODIMP_(void)
1882 nsChildView::SetInputContext(const InputContext& aContext,
1883 const InputContextAction& aAction)
1884 {
1885 NS_ENSURE_TRUE_VOID(mTextInputHandler);
1887 if (mTextInputHandler->IsFocused()) {
1888 if (aContext.IsPasswordEditor()) {
1889 TextInputHandler::EnableSecureEventInput();
1890 } else {
1891 TextInputHandler::EnsureSecureEventInputDisabled();
1892 }
1893 }
1895 mInputContext = aContext;
1896 switch (aContext.mIMEState.mEnabled) {
1897 case IMEState::ENABLED:
1898 case IMEState::PLUGIN:
1899 mTextInputHandler->SetASCIICapableOnly(false);
1900 mTextInputHandler->EnableIME(true);
1901 if (mInputContext.mIMEState.mOpen != IMEState::DONT_CHANGE_OPEN_STATE) {
1902 mTextInputHandler->SetIMEOpenState(
1903 mInputContext.mIMEState.mOpen == IMEState::OPEN);
1904 }
1905 break;
1906 case IMEState::DISABLED:
1907 mTextInputHandler->SetASCIICapableOnly(false);
1908 mTextInputHandler->EnableIME(false);
1909 break;
1910 case IMEState::PASSWORD:
1911 mTextInputHandler->SetASCIICapableOnly(true);
1912 mTextInputHandler->EnableIME(false);
1913 break;
1914 default:
1915 NS_ERROR("not implemented!");
1916 }
1917 }
1919 NS_IMETHODIMP_(InputContext)
1920 nsChildView::GetInputContext()
1921 {
1922 switch (mInputContext.mIMEState.mEnabled) {
1923 case IMEState::ENABLED:
1924 case IMEState::PLUGIN:
1925 if (mTextInputHandler) {
1926 mInputContext.mIMEState.mOpen =
1927 mTextInputHandler->IsIMEOpened() ? IMEState::OPEN : IMEState::CLOSED;
1928 break;
1929 }
1930 // If mTextInputHandler is null, set CLOSED instead...
1931 default:
1932 mInputContext.mIMEState.mOpen = IMEState::CLOSED;
1933 break;
1934 }
1935 mInputContext.mNativeIMEContext = [mView inputContext];
1936 // If input context isn't available on this widget, we should set |this|
1937 // instead of nullptr since nullptr means that the platform has only one
1938 // context per process.
1939 if (!mInputContext.mNativeIMEContext) {
1940 mInputContext.mNativeIMEContext = this;
1941 }
1942 return mInputContext;
1943 }
1945 NS_IMETHODIMP
1946 nsChildView::AttachNativeKeyEvent(mozilla::WidgetKeyboardEvent& aEvent)
1947 {
1948 NS_ENSURE_TRUE(mTextInputHandler, NS_ERROR_NOT_AVAILABLE);
1949 return mTextInputHandler->AttachNativeKeyEvent(aEvent);
1950 }
1952 NS_IMETHODIMP_(bool)
1953 nsChildView::ExecuteNativeKeyBinding(NativeKeyBindingsType aType,
1954 const WidgetKeyboardEvent& aEvent,
1955 DoCommandCallback aCallback,
1956 void* aCallbackData)
1957 {
1958 NativeKeyBindings* keyBindings = NativeKeyBindings::GetInstance(aType);
1959 return keyBindings->Execute(aEvent, aCallback, aCallbackData);
1960 }
1962 nsIMEUpdatePreference
1963 nsChildView::GetIMEUpdatePreference()
1964 {
1965 return nsIMEUpdatePreference(nsIMEUpdatePreference::NOTIFY_SELECTION_CHANGE);
1966 }
1968 NS_IMETHODIMP nsChildView::GetToggledKeyState(uint32_t aKeyCode,
1969 bool* aLEDState)
1970 {
1971 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
1973 NS_ENSURE_ARG_POINTER(aLEDState);
1974 uint32_t key;
1975 switch (aKeyCode) {
1976 case NS_VK_CAPS_LOCK:
1977 key = alphaLock;
1978 break;
1979 case NS_VK_NUM_LOCK:
1980 key = kEventKeyModifierNumLockMask;
1981 break;
1982 // Mac doesn't support SCROLL_LOCK state.
1983 default:
1984 return NS_ERROR_NOT_IMPLEMENTED;
1985 }
1986 uint32_t modifierFlags = ::GetCurrentKeyModifiers();
1987 *aLEDState = (modifierFlags & key) != 0;
1988 return NS_OK;
1990 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
1991 }
1993 NSView<mozView>* nsChildView::GetEditorView()
1994 {
1995 NSView<mozView>* editorView = mView;
1996 // We need to get editor's view. E.g., when the focus is in the bookmark
1997 // dialog, the view is <panel> element of the dialog. At this time, the key
1998 // events are processed the parent window's view that has native focus.
1999 WidgetQueryContentEvent textContent(true, NS_QUERY_TEXT_CONTENT, this);
2000 textContent.InitForQueryTextContent(0, 0);
2001 DispatchWindowEvent(textContent);
2002 if (textContent.mSucceeded && textContent.mReply.mFocusedWidget) {
2003 NSView<mozView>* view = static_cast<NSView<mozView>*>(
2004 textContent.mReply.mFocusedWidget->GetNativeData(NS_NATIVE_WIDGET));
2005 if (view)
2006 editorView = view;
2007 }
2008 return editorView;
2009 }
2011 #pragma mark -
2013 void
2014 nsChildView::CreateCompositor()
2015 {
2016 nsBaseWidget::CreateCompositor();
2017 if (mCompositorChild) {
2018 [(ChildView *)mView setUsingOMTCompositor:true];
2019 }
2020 }
2022 gfxASurface*
2023 nsChildView::GetThebesSurface()
2024 {
2025 if (!mTempThebesSurface) {
2026 mTempThebesSurface = new gfxQuartzSurface(gfxSize(1, 1), gfxImageFormat::ARGB32);
2027 }
2029 return mTempThebesSurface;
2030 }
2032 void
2033 nsChildView::NotifyDirtyRegion(const nsIntRegion& aDirtyRegion)
2034 {
2035 if ([(ChildView*)mView isCoveringTitlebar]) {
2036 // We store the dirty region so that we know what to repaint in the titlebar.
2037 mDirtyTitlebarRegion.Or(mDirtyTitlebarRegion, aDirtyRegion);
2038 }
2039 }
2041 nsIntRect
2042 nsChildView::RectContainingTitlebarControls()
2043 {
2044 // Start with a thin strip at the top of the window for the highlight line.
2045 NSRect rect = NSMakeRect(0, 0, [mView bounds].size.width,
2046 [(ChildView*)mView cornerRadius]);
2048 // Add the rects of the titlebar controls.
2049 for (id view in [(BaseWindow*)[mView window] titlebarControls]) {
2050 rect = NSUnionRect(rect, [mView convertRect:[view bounds] fromView:view]);
2051 }
2052 return CocoaPointsToDevPixels(rect);
2053 }
2055 void
2056 nsChildView::PrepareWindowEffects()
2057 {
2058 MutexAutoLock lock(mEffectsLock);
2059 mShowsResizeIndicator = ShowsResizeIndicator(&mResizeIndicatorRect);
2060 mHasRoundedBottomCorners = [(ChildView*)mView hasRoundedBottomCorners];
2061 CGFloat cornerRadius = [(ChildView*)mView cornerRadius];
2062 mDevPixelCornerRadius = cornerRadius * BackingScaleFactor();
2063 mIsCoveringTitlebar = [(ChildView*)mView isCoveringTitlebar];
2064 mIsFullscreen = ([[mView window] styleMask] & NSFullScreenWindowMask) != 0;
2065 if (mIsCoveringTitlebar) {
2066 mTitlebarRect = RectContainingTitlebarControls();
2067 UpdateTitlebarCGContext();
2068 }
2069 }
2071 void
2072 nsChildView::CleanupWindowEffects()
2073 {
2074 mResizerImage = nullptr;
2075 mCornerMaskImage = nullptr;
2076 mTitlebarImage = nullptr;
2077 }
2079 bool
2080 nsChildView::PreRender(LayerManagerComposite* aManager)
2081 {
2082 nsAutoPtr<GLManager> manager(GLManager::CreateGLManager(aManager));
2083 if (!manager) {
2084 return true;
2085 }
2087 // The lock makes sure that we don't attempt to tear down the view while
2088 // compositing. That would make us unable to call postRender on it when the
2089 // composition is done, thus keeping the GL context locked forever.
2090 mViewTearDownLock.Lock();
2092 NSOpenGLContext *glContext = GLContextCGL::Cast(manager->gl())->GetNSOpenGLContext();
2094 if (![(ChildView*)mView preRender:glContext]) {
2095 mViewTearDownLock.Unlock();
2096 return false;
2097 }
2098 return true;
2099 }
2101 void
2102 nsChildView::PostRender(LayerManagerComposite* aManager)
2103 {
2104 nsAutoPtr<GLManager> manager(GLManager::CreateGLManager(aManager));
2105 if (!manager) {
2106 return;
2107 }
2108 NSOpenGLContext *glContext = GLContextCGL::Cast(manager->gl())->GetNSOpenGLContext();
2109 [(ChildView*)mView postRender:glContext];
2110 mViewTearDownLock.Unlock();
2111 }
2113 void
2114 nsChildView::DrawWindowOverlay(LayerManagerComposite* aManager, nsIntRect aRect)
2115 {
2116 nsAutoPtr<GLManager> manager(GLManager::CreateGLManager(aManager));
2117 if (manager) {
2118 DrawWindowOverlay(manager, aRect);
2119 }
2120 }
2122 void
2123 nsChildView::DrawWindowOverlay(GLManager* aManager, nsIntRect aRect)
2124 {
2125 GLContext* gl = aManager->gl();
2126 ScopedGLState scopedScissorTestState(gl, LOCAL_GL_SCISSOR_TEST, false);
2128 MaybeDrawTitlebar(aManager, aRect);
2129 MaybeDrawResizeIndicator(aManager, aRect);
2130 MaybeDrawRoundedCorners(aManager, aRect);
2131 }
2133 static void
2134 ClearRegion(gfx::DrawTarget *aDT, nsIntRegion aRegion)
2135 {
2136 gfxUtils::ClipToRegion(aDT, aRegion);
2137 aDT->ClearRect(gfx::Rect(0, 0, aDT->GetSize().width, aDT->GetSize().height));
2138 aDT->PopClip();
2139 }
2141 static void
2142 DrawResizer(CGContextRef aCtx)
2143 {
2144 CGContextSetShouldAntialias(aCtx, false);
2145 CGPoint points[6];
2146 points[0] = CGPointMake(13.0f, 4.0f);
2147 points[1] = CGPointMake(3.0f, 14.0f);
2148 points[2] = CGPointMake(13.0f, 8.0f);
2149 points[3] = CGPointMake(7.0f, 14.0f);
2150 points[4] = CGPointMake(13.0f, 12.0f);
2151 points[5] = CGPointMake(11.0f, 14.0f);
2152 CGContextSetRGBStrokeColor(aCtx, 0.00f, 0.00f, 0.00f, 0.15f);
2153 CGContextStrokeLineSegments(aCtx, points, 6);
2155 points[0] = CGPointMake(13.0f, 5.0f);
2156 points[1] = CGPointMake(4.0f, 14.0f);
2157 points[2] = CGPointMake(13.0f, 9.0f);
2158 points[3] = CGPointMake(8.0f, 14.0f);
2159 points[4] = CGPointMake(13.0f, 13.0f);
2160 points[5] = CGPointMake(12.0f, 14.0f);
2161 CGContextSetRGBStrokeColor(aCtx, 0.13f, 0.13f, 0.13f, 0.54f);
2162 CGContextStrokeLineSegments(aCtx, points, 6);
2164 points[0] = CGPointMake(13.0f, 6.0f);
2165 points[1] = CGPointMake(5.0f, 14.0f);
2166 points[2] = CGPointMake(13.0f, 10.0f);
2167 points[3] = CGPointMake(9.0f, 14.0f);
2168 points[5] = CGPointMake(13.0f, 13.9f);
2169 points[4] = CGPointMake(13.0f, 14.0f);
2170 CGContextSetRGBStrokeColor(aCtx, 0.84f, 0.84f, 0.84f, 0.55f);
2171 CGContextStrokeLineSegments(aCtx, points, 6);
2172 }
2174 void
2175 nsChildView::MaybeDrawResizeIndicator(GLManager* aManager, const nsIntRect& aRect)
2176 {
2177 MutexAutoLock lock(mEffectsLock);
2178 if (!mShowsResizeIndicator) {
2179 return;
2180 }
2182 if (!mResizerImage) {
2183 mResizerImage = new RectTextureImage(aManager->gl());
2184 }
2186 nsIntSize size = mResizeIndicatorRect.Size();
2187 mResizerImage->UpdateIfNeeded(size, nsIntRegion(), ^(gfx::DrawTarget* drawTarget, const nsIntRegion& updateRegion) {
2188 ClearRegion(drawTarget, updateRegion);
2189 gfx::BorrowedCGContext borrow(drawTarget);
2190 DrawResizer(borrow.cg);
2191 borrow.Finish();
2192 });
2194 mResizerImage->Draw(aManager, mResizeIndicatorRect.TopLeft());
2195 }
2197 // Draw the highlight line at the top of the titlebar.
2198 // This function draws into the current NSGraphicsContext and assumes flippedness.
2199 static void
2200 DrawTitlebarHighlight(NSSize aWindowSize, CGFloat aRadius, CGFloat aDevicePixelWidth)
2201 {
2202 [NSGraphicsContext saveGraphicsState];
2204 // Set up the clip path. We start with the outer rectangle and cut out a
2205 // slightly smaller inner rectangle with rounded corners.
2206 // The outer corners of the resulting path will be square, but they will be
2207 // masked away in a later step.
2208 NSBezierPath* path = [NSBezierPath bezierPath];
2209 [path setWindingRule:NSEvenOddWindingRule];
2210 NSRect pathRect = NSMakeRect(0, 0, aWindowSize.width, aRadius + 2);
2211 [path appendBezierPathWithRect:pathRect];
2212 pathRect = NSInsetRect(pathRect, aDevicePixelWidth, aDevicePixelWidth);
2213 CGFloat innerRadius = aRadius - aDevicePixelWidth;
2214 [path appendBezierPathWithRoundedRect:pathRect xRadius:innerRadius yRadius:innerRadius];
2215 [path addClip];
2217 // Now we fill the path with a subtle highlight gradient.
2218 // We don't use NSGradient because it's 5x to 15x slower than the manual fill,
2219 // as indicated by the performance test in bug 880620.
2220 for (CGFloat y = 0; y < aRadius; y += aDevicePixelWidth) {
2221 CGFloat t = y / aRadius;
2222 [[NSColor colorWithDeviceWhite:1.0 alpha:0.4 * (1.0 - t)] set];
2223 NSRectFill(NSMakeRect(0, y, aWindowSize.width, aDevicePixelWidth));
2224 }
2226 [NSGraphicsContext restoreGraphicsState];
2227 }
2229 static CGContextRef
2230 CreateCGContext(const nsIntSize& aSize)
2231 {
2232 CGColorSpaceRef cs = CGColorSpaceCreateDeviceRGB();
2233 CGContextRef ctx =
2234 CGBitmapContextCreate(NULL,
2235 aSize.width,
2236 aSize.height,
2237 8 /* bitsPerComponent */,
2238 aSize.width * 4,
2239 cs,
2240 kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst);
2241 CGColorSpaceRelease(cs);
2243 CGContextTranslateCTM(ctx, 0, aSize.height);
2244 CGContextScaleCTM(ctx, 1, -1);
2245 CGContextSetInterpolationQuality(ctx, kCGInterpolationLow);
2247 return ctx;
2248 }
2250 // When this method is entered, mEffectsLock is already being held.
2251 void
2252 nsChildView::UpdateTitlebarCGContext()
2253 {
2254 nsIntRegion dirtyTitlebarRegion;
2255 dirtyTitlebarRegion.And(mDirtyTitlebarRegion, mTitlebarRect);
2256 mDirtyTitlebarRegion.SetEmpty();
2258 nsIntSize texSize = RectTextureImage::TextureSizeForSize(mTitlebarRect.Size());
2259 if (!mTitlebarCGContext ||
2260 CGBitmapContextGetWidth(mTitlebarCGContext) != size_t(texSize.width) ||
2261 CGBitmapContextGetHeight(mTitlebarCGContext) != size_t(texSize.height)) {
2262 dirtyTitlebarRegion = mTitlebarRect;
2264 ReleaseTitlebarCGContext();
2266 mTitlebarCGContext = CreateCGContext(texSize);
2267 }
2269 if (dirtyTitlebarRegion.IsEmpty())
2270 return;
2272 CGContextRef ctx = mTitlebarCGContext;
2274 CGContextSaveGState(ctx);
2276 std::vector<CGRect> rects;
2277 nsIntRegionRectIterator iter(dirtyTitlebarRegion);
2278 for (;;) {
2279 const nsIntRect* r = iter.Next();
2280 if (!r)
2281 break;
2282 rects.push_back(CGRectMake(r->x, r->y, r->width, r->height));
2283 }
2284 CGContextClipToRects(ctx, rects.data(), rects.size());
2286 CGContextClearRect(ctx, CGRectMake(0, 0, texSize.width, texSize.height));
2288 double scale = BackingScaleFactor();
2289 CGContextScaleCTM(ctx, scale, scale);
2290 NSGraphicsContext* oldContext = [NSGraphicsContext currentContext];
2292 CGContextSaveGState(ctx);
2294 BaseWindow* window = (BaseWindow*)[mView window];
2295 NSView* frameView = [[window contentView] superview];
2296 if (![frameView isFlipped]) {
2297 CGContextTranslateCTM(ctx, 0, [frameView bounds].size.height);
2298 CGContextScaleCTM(ctx, 1, -1);
2299 }
2300 NSGraphicsContext* context = [NSGraphicsContext graphicsContextWithGraphicsPort:ctx flipped:[frameView isFlipped]];
2301 [NSGraphicsContext setCurrentContext:context];
2303 // Draw the title string.
2304 if ([window wantsTitleDrawn] && [frameView respondsToSelector:@selector(_drawTitleBar:)]) {
2305 [frameView _drawTitleBar:[frameView bounds]];
2306 }
2308 // Draw the titlebar controls into the titlebar image.
2309 for (id view in [window titlebarControls]) {
2310 NSRect viewFrame = [view frame];
2311 nsIntRect viewRect = CocoaPointsToDevPixels([mView convertRect:viewFrame fromView:frameView]);
2312 if (!dirtyTitlebarRegion.Intersects(viewRect)) {
2313 continue;
2314 }
2315 // All of the titlebar controls we're interested in are subclasses of
2316 // NSButton.
2317 if (![view isKindOfClass:[NSButton class]]) {
2318 continue;
2319 }
2320 NSButton *button = (NSButton *) view;
2321 id cellObject = [button cell];
2322 if (![cellObject isKindOfClass:[NSCell class]]) {
2323 continue;
2324 }
2325 NSCell *cell = (NSCell *) cellObject;
2327 CGContextSaveGState(ctx);
2328 CGContextTranslateCTM(ctx, viewFrame.origin.x, viewFrame.origin.y);
2330 if ([context isFlipped] != [view isFlipped]) {
2331 CGContextTranslateCTM(ctx, 0, viewFrame.size.height);
2332 CGContextScaleCTM(ctx, 1, -1);
2333 }
2335 [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:ctx flipped:[view isFlipped]]];
2337 [cell drawWithFrame:[button bounds] inView:button];
2339 [NSGraphicsContext setCurrentContext:context];
2340 CGContextRestoreGState(ctx);
2341 }
2343 CGContextRestoreGState(ctx);
2345 DrawTitlebarHighlight([frameView bounds].size, [(ChildView*)mView cornerRadius],
2346 DevPixelsToCocoaPoints(1));
2348 [NSGraphicsContext setCurrentContext:oldContext];
2350 CGContextRestoreGState(ctx);
2352 mUpdatedTitlebarRegion.Or(mUpdatedTitlebarRegion, dirtyTitlebarRegion);
2353 }
2355 // This method draws an overlay in the top of the window which contains the
2356 // titlebar controls (e.g. close, min, zoom, fullscreen) and the titlebar
2357 // highlight effect.
2358 // This is necessary because the real titlebar controls are covered by our
2359 // OpenGL context. Note that in terms of the NSView hierarchy, our ChildView
2360 // is actually below the titlebar controls - that's why hovering and clicking
2361 // them works as expected - but their visual representation is only drawn into
2362 // the normal window buffer, and the window buffer surface lies below the
2363 // GLContext surface. In order to make the titlebar controls visible, we have
2364 // to redraw them inside the OpenGL context surface.
2365 void
2366 nsChildView::MaybeDrawTitlebar(GLManager* aManager, const nsIntRect& aRect)
2367 {
2368 MutexAutoLock lock(mEffectsLock);
2369 if (!mIsCoveringTitlebar || mIsFullscreen) {
2370 return;
2371 }
2373 nsIntRegion updatedTitlebarRegion;
2374 updatedTitlebarRegion.And(mUpdatedTitlebarRegion, mTitlebarRect);
2375 mUpdatedTitlebarRegion.SetEmpty();
2377 if (!mTitlebarImage) {
2378 mTitlebarImage = new RectTextureImage(aManager->gl());
2379 }
2381 mTitlebarImage->UpdateFromCGContext(mTitlebarRect.Size(),
2382 updatedTitlebarRegion,
2383 mTitlebarCGContext);
2385 mTitlebarImage->Draw(aManager, mTitlebarRect.TopLeft());
2386 }
2388 static void
2389 DrawTopLeftCornerMask(CGContextRef aCtx, int aRadius)
2390 {
2391 CGContextSetRGBFillColor(aCtx, 1.0, 1.0, 1.0, 1.0);
2392 CGContextFillEllipseInRect(aCtx, CGRectMake(0, 0, aRadius * 2, aRadius * 2));
2393 }
2395 void
2396 nsChildView::MaybeDrawRoundedCorners(GLManager* aManager, const nsIntRect& aRect)
2397 {
2398 MutexAutoLock lock(mEffectsLock);
2400 if (!mCornerMaskImage) {
2401 mCornerMaskImage = new RectTextureImage(aManager->gl());
2402 }
2404 nsIntSize size(mDevPixelCornerRadius, mDevPixelCornerRadius);
2405 mCornerMaskImage->UpdateIfNeeded(size, nsIntRegion(), ^(gfx::DrawTarget* drawTarget, const nsIntRegion& updateRegion) {
2406 ClearRegion(drawTarget, updateRegion);
2407 RefPtr<gfx::PathBuilder> builder = drawTarget->CreatePathBuilder();
2408 builder->Arc(gfx::Point(mDevPixelCornerRadius, mDevPixelCornerRadius), mDevPixelCornerRadius, 0, 2.0f * M_PI);
2409 RefPtr<gfx::Path> path = builder->Finish();
2410 drawTarget->Fill(path,
2411 gfx::ColorPattern(gfx::Color(1.0, 1.0, 1.0, 1.0)),
2412 gfx::DrawOptions(1.0f, gfx::CompositionOp::OP_SOURCE));
2413 });
2415 // Use operator destination in: multiply all 4 channels with source alpha.
2416 aManager->gl()->fBlendFuncSeparate(LOCAL_GL_ZERO, LOCAL_GL_SRC_ALPHA,
2417 LOCAL_GL_ZERO, LOCAL_GL_SRC_ALPHA);
2419 gfx3DMatrix flipX = gfx3DMatrix::ScalingMatrix(-1, 1, 1);
2420 gfx3DMatrix flipY = gfx3DMatrix::ScalingMatrix(1, -1, 1);
2422 if (mIsCoveringTitlebar && !mIsFullscreen) {
2423 // Mask the top corners.
2424 mCornerMaskImage->Draw(aManager, aRect.TopLeft());
2425 mCornerMaskImage->Draw(aManager, aRect.TopRight(), flipX);
2426 }
2428 if (mHasRoundedBottomCorners && !mIsFullscreen) {
2429 // Mask the bottom corners.
2430 mCornerMaskImage->Draw(aManager, aRect.BottomLeft(), flipY);
2431 mCornerMaskImage->Draw(aManager, aRect.BottomRight(), flipY * flipX);
2432 }
2434 // Reset blend mode.
2435 aManager->gl()->fBlendFuncSeparate(LOCAL_GL_ONE, LOCAL_GL_ONE_MINUS_SRC_ALPHA,
2436 LOCAL_GL_ONE, LOCAL_GL_ONE);
2437 }
2439 static int32_t
2440 FindTitlebarBottom(const nsTArray<nsIWidget::ThemeGeometry>& aThemeGeometries,
2441 int32_t aWindowWidth)
2442 {
2443 int32_t titlebarBottom = 0;
2444 for (uint32_t i = 0; i < aThemeGeometries.Length(); ++i) {
2445 const nsIWidget::ThemeGeometry& g = aThemeGeometries[i];
2446 if ((g.mWidgetType == NS_THEME_WINDOW_TITLEBAR) &&
2447 g.mRect.X() <= 0 &&
2448 g.mRect.XMost() >= aWindowWidth &&
2449 g.mRect.Y() <= 0) {
2450 titlebarBottom = std::max(titlebarBottom, g.mRect.YMost());
2451 }
2452 }
2453 return titlebarBottom;
2454 }
2456 static int32_t
2457 FindUnifiedToolbarBottom(const nsTArray<nsIWidget::ThemeGeometry>& aThemeGeometries,
2458 int32_t aWindowWidth, int32_t aTitlebarBottom)
2459 {
2460 int32_t unifiedToolbarBottom = aTitlebarBottom;
2461 for (uint32_t i = 0; i < aThemeGeometries.Length(); ++i) {
2462 const nsIWidget::ThemeGeometry& g = aThemeGeometries[i];
2463 if ((g.mWidgetType == NS_THEME_MOZ_MAC_UNIFIED_TOOLBAR ||
2464 g.mWidgetType == NS_THEME_TOOLBAR) &&
2465 g.mRect.X() <= 0 &&
2466 g.mRect.XMost() >= aWindowWidth &&
2467 g.mRect.Y() <= aTitlebarBottom) {
2468 unifiedToolbarBottom = std::max(unifiedToolbarBottom, g.mRect.YMost());
2469 }
2470 }
2471 return unifiedToolbarBottom;
2472 }
2474 static nsIntRect
2475 FindFirstRectOfType(const nsTArray<nsIWidget::ThemeGeometry>& aThemeGeometries,
2476 uint8_t aWidgetType)
2477 {
2478 for (uint32_t i = 0; i < aThemeGeometries.Length(); ++i) {
2479 const nsIWidget::ThemeGeometry& g = aThemeGeometries[i];
2480 if (g.mWidgetType == aWidgetType) {
2481 return g.mRect;
2482 }
2483 }
2484 return nsIntRect();
2485 }
2487 void
2488 nsChildView::UpdateThemeGeometries(const nsTArray<ThemeGeometry>& aThemeGeometries)
2489 {
2490 if (![mView window] || ![[mView window] isKindOfClass:[ToolbarWindow class]])
2491 return;
2493 // Update unified toolbar height.
2494 int32_t windowWidth = mBounds.width;
2495 int32_t titlebarBottom = FindTitlebarBottom(aThemeGeometries, windowWidth);
2496 int32_t unifiedToolbarBottom =
2497 FindUnifiedToolbarBottom(aThemeGeometries, windowWidth, titlebarBottom);
2499 ToolbarWindow* win = (ToolbarWindow*)[mView window];
2500 bool drawsContentsIntoWindowFrame = [win drawsContentsIntoWindowFrame];
2501 int32_t titlebarHeight = CocoaPointsToDevPixels([win titlebarHeight]);
2502 int32_t contentOffset = drawsContentsIntoWindowFrame ? titlebarHeight : 0;
2503 int32_t devUnifiedHeight = titlebarHeight + unifiedToolbarBottom - contentOffset;
2504 [win setUnifiedToolbarHeight:DevPixelsToCocoaPoints(devUnifiedHeight)];
2506 // Update titlebar control offsets.
2507 nsIntRect windowButtonRect = FindFirstRectOfType(aThemeGeometries, NS_THEME_WINDOW_BUTTON_BOX);
2508 [win placeWindowButtons:[mView convertRect:DevPixelsToCocoaPoints(windowButtonRect) toView:nil]];
2509 nsIntRect fullScreenButtonRect = FindFirstRectOfType(aThemeGeometries, NS_THEME_MOZ_MAC_FULLSCREEN_BUTTON);
2510 [win placeFullScreenButton:[mView convertRect:DevPixelsToCocoaPoints(fullScreenButtonRect) toView:nil]];
2511 }
2513 TemporaryRef<gfx::DrawTarget>
2514 nsChildView::StartRemoteDrawing()
2515 {
2516 if (!mGLPresenter) {
2517 mGLPresenter = GLPresenter::CreateForWindow(this);
2519 if (!mGLPresenter) {
2520 return nullptr;
2521 }
2522 }
2524 nsIntRegion dirtyRegion = mBounds;
2525 nsIntSize renderSize = mBounds.Size();
2527 if (!mBasicCompositorImage) {
2528 mBasicCompositorImage = new RectTextureImage(mGLPresenter->gl());
2529 }
2531 RefPtr<gfx::DrawTarget> drawTarget =
2532 mBasicCompositorImage->BeginUpdate(renderSize, dirtyRegion);
2534 if (!drawTarget) {
2535 // Composite unchanged textures.
2536 DoRemoteComposition(mBounds);
2537 return nullptr;
2538 }
2540 return drawTarget;
2541 }
2543 void
2544 nsChildView::EndRemoteDrawing()
2545 {
2546 mBasicCompositorImage->EndUpdate(true);
2547 DoRemoteComposition(mBounds);
2548 }
2550 void
2551 nsChildView::CleanupRemoteDrawing()
2552 {
2553 mBasicCompositorImage = nullptr;
2554 mCornerMaskImage = nullptr;
2555 mResizerImage = nullptr;
2556 mTitlebarImage = nullptr;
2557 mGLPresenter = nullptr;
2558 }
2560 void
2561 nsChildView::DoRemoteComposition(const nsIntRect& aRenderRect)
2562 {
2563 if (![(ChildView*)mView preRender:mGLPresenter->GetNSOpenGLContext()]) {
2564 return;
2565 }
2566 mGLPresenter->BeginFrame(aRenderRect.Size());
2568 // Draw the result from the basic compositor.
2569 mBasicCompositorImage->Draw(mGLPresenter, nsIntPoint(0, 0));
2571 // DrawWindowOverlay doesn't do anything for non-GL, so it didn't paint
2572 // anything during the basic compositor transaction. Draw the overlay now.
2573 DrawWindowOverlay(mGLPresenter, aRenderRect);
2575 mGLPresenter->EndFrame();
2577 [(ChildView*)mView postRender:mGLPresenter->GetNSOpenGLContext()];
2578 }
2580 #ifdef ACCESSIBILITY
2581 already_AddRefed<a11y::Accessible>
2582 nsChildView::GetDocumentAccessible()
2583 {
2584 if (!mozilla::a11y::ShouldA11yBeEnabled())
2585 return nullptr;
2587 if (mAccessible) {
2588 nsRefPtr<a11y::Accessible> ret;
2589 CallQueryReferent(mAccessible.get(),
2590 static_cast<a11y::Accessible**>(getter_AddRefs(ret)));
2591 return ret.forget();
2592 }
2594 // need to fetch the accessible anew, because it has gone away.
2595 // cache the accessible in our weak ptr
2596 nsRefPtr<a11y::Accessible> acc = GetRootAccessible();
2597 mAccessible = do_GetWeakReference(static_cast<nsIAccessible *>(acc.get()));
2599 return acc.forget();
2600 }
2601 #endif
2603 // RectTextureImage implementation
2605 RectTextureImage::~RectTextureImage()
2606 {
2607 if (mTexture) {
2608 mGLContext->MakeCurrent();
2609 mGLContext->fDeleteTextures(1, &mTexture);
2610 mTexture = 0;
2611 }
2612 }
2614 nsIntSize
2615 RectTextureImage::TextureSizeForSize(const nsIntSize& aSize)
2616 {
2617 return nsIntSize(gfx::NextPowerOfTwo(aSize.width),
2618 gfx::NextPowerOfTwo(aSize.height));
2619 }
2621 TemporaryRef<gfx::DrawTarget>
2622 RectTextureImage::BeginUpdate(const nsIntSize& aNewSize,
2623 const nsIntRegion& aDirtyRegion)
2624 {
2625 MOZ_ASSERT(!mInUpdate, "Beginning update during update!");
2626 mUpdateRegion = aDirtyRegion;
2627 if (aNewSize != mUsedSize) {
2628 mUsedSize = aNewSize;
2629 mUpdateRegion = nsIntRect(nsIntPoint(0, 0), aNewSize);
2630 }
2632 if (mUpdateRegion.IsEmpty()) {
2633 return nullptr;
2634 }
2636 nsIntSize neededBufferSize = TextureSizeForSize(mUsedSize);
2637 if (!mUpdateDrawTarget || mBufferSize != neededBufferSize) {
2638 gfx::IntSize size(neededBufferSize.width, neededBufferSize.height);
2639 mUpdateDrawTarget =
2640 gfx::Factory::CreateDrawTarget(gfx::BackendType::COREGRAPHICS, size,
2641 gfx::SurfaceFormat::B8G8R8A8);
2642 mBufferSize = neededBufferSize;
2643 }
2645 mInUpdate = true;
2647 RefPtr<gfx::DrawTarget> drawTarget = mUpdateDrawTarget;
2648 return drawTarget;
2649 }
2651 #define NSFoundationVersionWithProperStrideSupportForSubtextureUpload NSFoundationVersionNumber10_6_3
2653 static bool
2654 CanUploadSubtextures()
2655 {
2656 return NSFoundationVersionNumber >= NSFoundationVersionWithProperStrideSupportForSubtextureUpload;
2657 }
2659 void
2660 RectTextureImage::EndUpdate(bool aKeepSurface)
2661 {
2662 MOZ_ASSERT(mInUpdate, "Ending update while not in update");
2664 bool overwriteTexture = false;
2665 nsIntRegion updateRegion = mUpdateRegion;
2666 if (!mTexture || (mTextureSize != mBufferSize)) {
2667 overwriteTexture = true;
2668 mTextureSize = mBufferSize;
2669 }
2671 if (overwriteTexture || !CanUploadSubtextures()) {
2672 updateRegion = nsIntRect(nsIntPoint(0, 0), mTextureSize);
2673 }
2675 RefPtr<gfx::SourceSurface> snapshot = mUpdateDrawTarget->Snapshot();
2676 RefPtr<gfx::DataSourceSurface> dataSnapshot = snapshot->GetDataSurface();
2678 UploadSurfaceToTexture(mGLContext,
2679 dataSnapshot,
2680 updateRegion,
2681 mTexture,
2682 overwriteTexture,
2683 updateRegion.GetBounds().TopLeft(),
2684 false,
2685 LOCAL_GL_TEXTURE0,
2686 LOCAL_GL_TEXTURE_RECTANGLE_ARB);
2688 if (!aKeepSurface) {
2689 mUpdateDrawTarget = nullptr;
2690 }
2692 mInUpdate = false;
2693 }
2695 void
2696 RectTextureImage::UpdateFromCGContext(const nsIntSize& aNewSize,
2697 const nsIntRegion& aDirtyRegion,
2698 CGContextRef aCGContext)
2699 {
2700 gfx::IntSize size = gfx::IntSize(CGBitmapContextGetWidth(aCGContext),
2701 CGBitmapContextGetHeight(aCGContext));
2702 mBufferSize.SizeTo(size.width, size.height);
2703 RefPtr<gfx::DrawTarget> dt = BeginUpdate(aNewSize, aDirtyRegion);
2704 if (dt) {
2705 gfx::Rect rect(0, 0, size.width, size.height);
2706 gfxUtils::ClipToRegion(dt, GetUpdateRegion());
2707 RefPtr<gfx::SourceSurface> sourceSurface =
2708 dt->CreateSourceSurfaceFromData(static_cast<uint8_t *>(CGBitmapContextGetData(aCGContext)),
2709 size,
2710 CGBitmapContextGetBytesPerRow(aCGContext),
2711 gfx::SurfaceFormat::B8G8R8A8);
2712 dt->DrawSurface(sourceSurface, rect, rect,
2713 gfx::DrawSurfaceOptions(),
2714 gfx::DrawOptions(1.0, gfx::CompositionOp::OP_SOURCE));
2715 dt->PopClip();
2716 EndUpdate();
2717 }
2718 }
2720 void
2721 RectTextureImage::UpdateFromDrawTarget(const nsIntSize& aNewSize,
2722 const nsIntRegion& aDirtyRegion,
2723 gfx::DrawTarget* aFromDrawTarget)
2724 {
2725 mUpdateDrawTarget = aFromDrawTarget;
2726 mBufferSize.SizeTo(aFromDrawTarget->GetSize().width, aFromDrawTarget->GetSize().height);
2727 RefPtr<gfx::DrawTarget> drawTarget = BeginUpdate(aNewSize, aDirtyRegion);
2728 if (drawTarget) {
2729 if (drawTarget != aFromDrawTarget) {
2730 RefPtr<gfx::SourceSurface> source = aFromDrawTarget->Snapshot();
2731 gfx::Rect rect(0, 0, aFromDrawTarget->GetSize().width, aFromDrawTarget->GetSize().height);
2732 gfxUtils::ClipToRegion(drawTarget, GetUpdateRegion());
2733 drawTarget->DrawSurface(source, rect, rect,
2734 gfx::DrawSurfaceOptions(),
2735 gfx::DrawOptions(1.0, gfx::CompositionOp::OP_SOURCE));
2736 drawTarget->PopClip();
2737 }
2738 EndUpdate();
2739 }
2740 mUpdateDrawTarget = nullptr;
2741 }
2743 void
2744 RectTextureImage::Draw(GLManager* aManager,
2745 const nsIntPoint& aLocation,
2746 const gfx3DMatrix& aTransform)
2747 {
2748 ShaderProgramOGL* program = aManager->GetProgram(LOCAL_GL_TEXTURE_RECTANGLE_ARB,
2749 gfx::SurfaceFormat::R8G8B8A8);
2751 aManager->gl()->fBindTexture(LOCAL_GL_TEXTURE_RECTANGLE_ARB, mTexture);
2753 program->Activate();
2754 program->SetProjectionMatrix(aManager->GetProjMatrix());
2755 program->SetLayerQuadRect(nsIntRect(nsIntPoint(0, 0), mUsedSize));
2756 gfx::Matrix4x4 transform;
2757 gfx::ToMatrix4x4(aTransform, transform);
2758 program->SetLayerTransform(transform * gfx::Matrix4x4().Translate(aLocation.x, aLocation.y, 0));
2759 program->SetTextureTransform(gfx::Matrix4x4());
2760 program->SetRenderOffset(nsIntPoint(0, 0));
2761 program->SetTexCoordMultiplier(mUsedSize.width, mUsedSize.height);
2762 program->SetTextureUnit(0);
2764 aManager->BindAndDrawQuad(program);
2766 aManager->gl()->fBindTexture(LOCAL_GL_TEXTURE_RECTANGLE_ARB, 0);
2767 }
2769 // GLPresenter implementation
2771 GLPresenter::GLPresenter(GLContext* aContext)
2772 : mGLContext(aContext)
2773 {
2774 mGLContext->MakeCurrent();
2775 ShaderConfigOGL config;
2776 config.SetTextureTarget(LOCAL_GL_TEXTURE_RECTANGLE_ARB);
2777 mRGBARectProgram = new ShaderProgramOGL(mGLContext,
2778 ProgramProfileOGL::GetProfileFor(config));
2780 // Create mQuadVBO.
2781 mGLContext->fGenBuffers(1, &mQuadVBO);
2782 mGLContext->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, mQuadVBO);
2784 GLfloat vertices[] = {
2785 /* First quad vertices */
2786 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,
2787 /* Then quad texcoords */
2788 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,
2789 };
2790 mGLContext->fBufferData(LOCAL_GL_ARRAY_BUFFER, sizeof(vertices), vertices, LOCAL_GL_STATIC_DRAW);
2791 mGLContext->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, 0);
2792 }
2794 GLPresenter::~GLPresenter()
2795 {
2796 if (mQuadVBO) {
2797 mGLContext->MakeCurrent();
2798 mGLContext->fDeleteBuffers(1, &mQuadVBO);
2799 mQuadVBO = 0;
2800 }
2801 }
2803 void
2804 GLPresenter::BindAndDrawQuad(ShaderProgramOGL *aProgram)
2805 {
2806 mGLContext->MakeCurrent();
2808 GLuint vertAttribIndex = aProgram->AttribLocation(ShaderProgramOGL::VertexCoordAttrib);
2809 GLuint texCoordAttribIndex = aProgram->AttribLocation(ShaderProgramOGL::TexCoordAttrib);
2811 mGLContext->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, mQuadVBO);
2812 mGLContext->fVertexAttribPointer(vertAttribIndex, 2,
2813 LOCAL_GL_FLOAT, LOCAL_GL_FALSE, 0,
2814 (GLvoid*)0);
2815 mGLContext->fEnableVertexAttribArray(vertAttribIndex);
2816 mGLContext->fVertexAttribPointer(texCoordAttribIndex, 2,
2817 LOCAL_GL_FLOAT, LOCAL_GL_FALSE, 0,
2818 (GLvoid*) (sizeof(float)*4*2));
2819 mGLContext->fEnableVertexAttribArray(texCoordAttribIndex);
2820 mGLContext->fDrawArrays(LOCAL_GL_TRIANGLE_STRIP, 0, 4);
2821 mGLContext->fDisableVertexAttribArray(vertAttribIndex);
2822 mGLContext->fDisableVertexAttribArray(texCoordAttribIndex);
2823 }
2825 void
2826 GLPresenter::BeginFrame(nsIntSize aRenderSize)
2827 {
2828 mGLContext->MakeCurrent();
2830 mGLContext->fViewport(0, 0, aRenderSize.width, aRenderSize.height);
2832 // Matrix to transform (0, 0, width, height) to viewport space (-1.0, 1.0,
2833 // 2, 2) and flip the contents.
2834 gfx::Matrix viewMatrix;
2835 viewMatrix.Translate(-1.0, 1.0);
2836 viewMatrix.Scale(2.0f / float(aRenderSize.width), 2.0f / float(aRenderSize.height));
2837 viewMatrix.Scale(1.0f, -1.0f);
2839 gfx::Matrix4x4 matrix3d = gfx::Matrix4x4::From2D(viewMatrix);
2840 matrix3d._33 = 0.0f;
2842 // set the projection matrix for the next time the program is activated
2843 mProjMatrix = matrix3d;
2845 // Default blend function implements "OVER"
2846 mGLContext->fBlendFuncSeparate(LOCAL_GL_ONE, LOCAL_GL_ONE_MINUS_SRC_ALPHA,
2847 LOCAL_GL_ONE, LOCAL_GL_ONE);
2848 mGLContext->fEnable(LOCAL_GL_BLEND);
2850 mGLContext->fClearColor(0.0, 0.0, 0.0, 0.0);
2851 mGLContext->fClear(LOCAL_GL_COLOR_BUFFER_BIT | LOCAL_GL_DEPTH_BUFFER_BIT);
2853 mGLContext->fEnable(LOCAL_GL_TEXTURE_RECTANGLE_ARB);
2854 }
2856 void
2857 GLPresenter::EndFrame()
2858 {
2859 mGLContext->SwapBuffers();
2860 mGLContext->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, 0);
2861 }
2863 #pragma mark -
2865 @implementation ChildView
2867 // globalDragPboard is non-null during native drag sessions that did not originate
2868 // in our native NSView (it is set in |draggingEntered:|). It is unset when the
2869 // drag session ends for this view, either with the mouse exiting or when a drop
2870 // occurs in this view.
2871 NSPasteboard* globalDragPboard = nil;
2873 // gLastDragView and gLastDragMouseDownEvent are used to communicate information
2874 // to the drag service during drag invocation (starting a drag in from the view).
2875 // gLastDragView is only non-null while mouseDragged is on the call stack.
2876 NSView* gLastDragView = nil;
2877 NSEvent* gLastDragMouseDownEvent = nil;
2879 + (void)initialize
2880 {
2881 static BOOL initialized = NO;
2883 if (!initialized) {
2884 // Inform the OS about the types of services (from the "Services" menu)
2885 // that we can handle.
2887 NSArray *sendTypes = [[NSArray alloc] initWithObjects:NSStringPboardType,NSHTMLPboardType,nil];
2888 NSArray *returnTypes = [[NSArray alloc] initWithObjects:NSStringPboardType,NSHTMLPboardType,nil];
2890 [NSApp registerServicesMenuSendTypes:sendTypes returnTypes:returnTypes];
2892 [sendTypes release];
2893 [returnTypes release];
2895 initialized = YES;
2896 }
2897 }
2899 + (void)registerViewForDraggedTypes:(NSView*)aView
2900 {
2901 [aView registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType,
2902 NSStringPboardType,
2903 NSHTMLPboardType,
2904 NSURLPboardType,
2905 NSFilesPromisePboardType,
2906 kWildcardPboardType,
2907 kCorePboardType_url,
2908 kCorePboardType_urld,
2909 kCorePboardType_urln,
2910 nil]];
2911 }
2913 // initWithFrame:geckoChild:
2914 - (id)initWithFrame:(NSRect)inFrame geckoChild:(nsChildView*)inChild
2915 {
2916 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
2918 if ((self = [super initWithFrame:inFrame])) {
2919 mGeckoChild = inChild;
2920 mIsPluginView = NO;
2921 #ifndef NP_NO_CARBON
2922 // We don't support the Carbon event model but it's still the default
2923 // model for i386 per NPAPI.
2924 mPluginEventModel = NPEventModelCarbon;
2925 #else
2926 mPluginEventModel = NPEventModelCocoa;
2927 #endif
2928 #ifndef NP_NO_QUICKDRAW
2929 // We don't support the Quickdraw drawing model any more but it's still
2930 // the default model for i386 per NPAPI.
2931 mPluginDrawingModel = NPDrawingModelQuickDraw;
2932 #else
2933 mPluginDrawingModel = NPDrawingModelCoreGraphics;
2934 #endif
2935 mPendingDisplay = NO;
2936 mBlockedLastMouseDown = NO;
2937 mExpectingWheelStop = NO;
2939 mLastMouseDownEvent = nil;
2940 mClickThroughMouseDownEvent = nil;
2941 mDragService = nullptr;
2943 mGestureState = eGestureState_None;
2944 mCumulativeMagnification = 0.0;
2945 mCumulativeRotation = 0.0;
2947 // We can't call forceRefreshOpenGL here because, in order to work around
2948 // the bug, it seems we need to have a draw already happening. Therefore,
2949 // we call it in drawRect:inContext:, when we know that a draw is in
2950 // progress.
2951 mDidForceRefreshOpenGL = NO;
2953 [self setFocusRingType:NSFocusRingTypeNone];
2955 #ifdef __LP64__
2956 mCancelSwipeAnimation = nil;
2957 mCurrentSwipeDir = 0;
2958 #endif
2960 mTopLeftCornerMask = NULL;
2961 }
2963 // register for things we'll take from other applications
2964 [ChildView registerViewForDraggedTypes:self];
2966 [[NSNotificationCenter defaultCenter] addObserver:self
2967 selector:@selector(windowBecameMain:)
2968 name:NSWindowDidBecomeMainNotification
2969 object:nil];
2970 [[NSNotificationCenter defaultCenter] addObserver:self
2971 selector:@selector(windowResignedMain:)
2972 name:NSWindowDidResignMainNotification
2973 object:nil];
2974 [[NSNotificationCenter defaultCenter] addObserver:self
2975 selector:@selector(systemMetricsChanged)
2976 name:NSControlTintDidChangeNotification
2977 object:nil];
2978 [[NSNotificationCenter defaultCenter] addObserver:self
2979 selector:@selector(systemMetricsChanged)
2980 name:NSSystemColorsDidChangeNotification
2981 object:nil];
2982 // TODO: replace the string with the constant once we build with the 10.7 SDK
2983 [[NSNotificationCenter defaultCenter] addObserver:self
2984 selector:@selector(scrollbarSystemMetricChanged)
2985 name:@"NSPreferredScrollerStyleDidChangeNotification"
2986 object:nil];
2987 [[NSDistributedNotificationCenter defaultCenter] addObserver:self
2988 selector:@selector(systemMetricsChanged)
2989 name:@"AppleAquaScrollBarVariantChanged"
2990 object:nil
2991 suspensionBehavior:NSNotificationSuspensionBehaviorDeliverImmediately];
2992 [[NSNotificationCenter defaultCenter] addObserver:self
2993 selector:@selector(_surfaceNeedsUpdate:)
2994 name:NSViewGlobalFrameDidChangeNotification
2995 object:self];
2997 return self;
2999 NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
3000 }
3002 - (void)installTextInputHandler:(TextInputHandler*)aHandler
3003 {
3004 mTextInputHandler = aHandler;
3005 }
3007 - (void)uninstallTextInputHandler
3008 {
3009 mTextInputHandler = nullptr;
3010 }
3012 // Work around bug 603134.
3013 // OS X has a bug that causes new OpenGL windows to only paint once or twice,
3014 // then stop painting altogether. By clearing the drawable from the GL context,
3015 // and then resetting the view to ourselves, we convince OS X to start updating
3016 // again.
3017 // This can cause a flash in new windows - bug 631339 - but it's very hard to
3018 // fix that while maintaining this workaround.
3019 - (void)forceRefreshOpenGL
3020 {
3021 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
3023 [mGLContext clearDrawable];
3024 [self updateGLContext];
3026 NS_OBJC_END_TRY_ABORT_BLOCK;
3027 }
3029 - (void)setGLContext:(NSOpenGLContext *)aGLContext
3030 {
3031 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
3033 mGLContext = aGLContext;
3034 [mGLContext retain];
3036 NS_OBJC_END_TRY_ABORT_BLOCK;
3037 }
3039 - (bool)preRender:(NSOpenGLContext *)aGLContext
3040 {
3041 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
3043 if (![self window] ||
3044 ([[self window] isKindOfClass:[BaseWindow class]] &&
3045 ![(BaseWindow*)[self window] isVisibleOrBeingShown])) {
3046 // Before the window is shown, our GL context's front FBO is not
3047 // framebuffer complete, so we refuse to render.
3048 return false;
3049 }
3051 if (!mGLContext) {
3052 [self setGLContext:aGLContext];
3053 [self updateGLContext];
3054 }
3056 CGLLockContext((CGLContextObj)[aGLContext CGLContextObj]);
3058 return true;
3060 NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false);
3061 }
3063 - (void)postRender:(NSOpenGLContext *)aGLContext
3064 {
3065 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
3067 CGLUnlockContext((CGLContextObj)[aGLContext CGLContextObj]);
3069 NS_OBJC_END_TRY_ABORT_BLOCK;
3070 }
3072 - (void)dealloc
3073 {
3074 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
3076 [mGLContext release];
3077 [mPendingDirtyRects release];
3078 [mLastMouseDownEvent release];
3079 [mClickThroughMouseDownEvent release];
3080 CGImageRelease(mTopLeftCornerMask);
3081 ChildViewMouseTracker::OnDestroyView(self);
3083 [[NSNotificationCenter defaultCenter] removeObserver:self];
3084 [[NSDistributedNotificationCenter defaultCenter] removeObserver:self];
3086 [super dealloc];
3088 NS_OBJC_END_TRY_ABORT_BLOCK;
3089 }
3091 - (void)updatePluginTopLevelWindowStatus:(BOOL)hasMain
3092 {
3093 if (!mGeckoChild)
3094 return;
3096 WidgetPluginEvent pluginEvent(true, NS_PLUGIN_FOCUS_EVENT, mGeckoChild);
3097 NPCocoaEvent cocoaEvent;
3098 nsCocoaUtils::InitNPCocoaEvent(&cocoaEvent);
3099 cocoaEvent.type = NPCocoaEventWindowFocusChanged;
3100 cocoaEvent.data.focus.hasFocus = hasMain;
3101 nsCocoaUtils::InitPluginEvent(pluginEvent, cocoaEvent);
3102 mGeckoChild->DispatchWindowEvent(pluginEvent);
3103 }
3105 - (void)windowBecameMain:(NSNotification*)inNotification
3106 {
3107 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
3109 if (mIsPluginView && mPluginEventModel == NPEventModelCocoa) {
3110 if ((NSWindow*)[inNotification object] == [self window]) {
3111 [self updatePluginTopLevelWindowStatus:YES];
3112 }
3113 }
3115 NS_OBJC_END_TRY_ABORT_BLOCK;
3116 }
3118 - (void)windowResignedMain:(NSNotification*)inNotification
3119 {
3120 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
3122 if (mIsPluginView && mPluginEventModel == NPEventModelCocoa) {
3123 if ((NSWindow*)[inNotification object] == [self window]) {
3124 [self updatePluginTopLevelWindowStatus:NO];
3125 }
3126 }
3128 NS_OBJC_END_TRY_ABORT_BLOCK;
3129 }
3131 - (void)widgetDestroyed
3132 {
3133 if (mTextInputHandler) {
3134 mTextInputHandler->OnDestroyWidget(mGeckoChild);
3135 mTextInputHandler = nullptr;
3136 }
3137 mGeckoChild = nullptr;
3139 // Just in case we're destroyed abruptly and missed the draggingExited
3140 // or performDragOperation message.
3141 NS_IF_RELEASE(mDragService);
3142 }
3144 // mozView method, return our gecko child view widget. Note this does not AddRef.
3145 - (nsIWidget*) widget
3146 {
3147 return static_cast<nsIWidget*>(mGeckoChild);
3148 }
3150 - (void)systemMetricsChanged
3151 {
3152 if (mGeckoChild)
3153 mGeckoChild->NotifyThemeChanged();
3154 }
3156 - (void)scrollbarSystemMetricChanged
3157 {
3158 [self systemMetricsChanged];
3160 if (mGeckoChild) {
3161 nsIWidgetListener* listener = mGeckoChild->GetWidgetListener();
3162 if (listener) {
3163 listener->GetPresShell()->ReconstructFrames();
3164 }
3165 }
3166 }
3168 - (void)setNeedsPendingDisplay
3169 {
3170 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
3172 mPendingFullDisplay = YES;
3173 if (!mPendingDisplay) {
3174 [self performSelector:@selector(processPendingRedraws) withObject:nil afterDelay:0];
3175 mPendingDisplay = YES;
3176 }
3178 NS_OBJC_END_TRY_ABORT_BLOCK;
3179 }
3181 - (void)setNeedsPendingDisplayInRect:(NSRect)invalidRect
3182 {
3183 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
3185 if (!mPendingDirtyRects)
3186 mPendingDirtyRects = [[NSMutableArray alloc] initWithCapacity:1];
3187 [mPendingDirtyRects addObject:[NSValue valueWithRect:invalidRect]];
3188 if (!mPendingDisplay) {
3189 [self performSelector:@selector(processPendingRedraws) withObject:nil afterDelay:0];
3190 mPendingDisplay = YES;
3191 }
3193 NS_OBJC_END_TRY_ABORT_BLOCK;
3194 }
3196 // Clears the queue of any pending invalides
3197 - (void)processPendingRedraws
3198 {
3199 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
3201 if (mPendingFullDisplay) {
3202 [self setNeedsDisplay:YES];
3203 }
3204 else if (mPendingDirtyRects) {
3205 unsigned int count = [mPendingDirtyRects count];
3206 for (unsigned int i = 0; i < count; ++i) {
3207 [self setNeedsDisplayInRect:[[mPendingDirtyRects objectAtIndex:i] rectValue]];
3208 }
3209 }
3210 mPendingFullDisplay = NO;
3211 mPendingDisplay = NO;
3212 [mPendingDirtyRects release];
3213 mPendingDirtyRects = nil;
3215 NS_OBJC_END_TRY_ABORT_BLOCK;
3216 }
3218 - (void)setNeedsDisplayInRect:(NSRect)aRect
3219 {
3220 if (![self isUsingOpenGL]) {
3221 [super setNeedsDisplayInRect:aRect];
3222 return;
3223 }
3225 if ([[self window] isVisible] && [self isUsingMainThreadOpenGL]) {
3226 // Draw without calling drawRect. This prevent us from
3227 // needing to access the normal window buffer surface unnecessarily, so we
3228 // waste less time synchronizing the two surfaces. (These synchronizations
3229 // show up in a profiler as CGSDeviceLock / _CGSLockWindow /
3230 // _CGSSynchronizeWindowBackingStore.) It also means that Cocoa doesn't
3231 // have any potentially expensive invalid rect management for us.
3232 if (!mWaitingForPaint) {
3233 mWaitingForPaint = YES;
3234 // Use NSRunLoopCommonModes instead of the default NSDefaultRunLoopMode
3235 // so that the timer also fires while a native menu is open.
3236 [self performSelector:@selector(drawUsingOpenGLCallback)
3237 withObject:nil
3238 afterDelay:0
3239 inModes:[NSArray arrayWithObject:NSRunLoopCommonModes]];
3240 }
3241 }
3242 }
3244 - (NSString*)description
3245 {
3246 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
3248 return [NSString stringWithFormat:@"ChildView %p, gecko child %p, frame %@", self, mGeckoChild, NSStringFromRect([self frame])];
3250 NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
3251 }
3253 // Make the origin of this view the topLeft corner (gecko origin) rather
3254 // than the bottomLeft corner (standard cocoa origin).
3255 - (BOOL)isFlipped
3256 {
3257 return YES;
3258 }
3260 - (BOOL)isOpaque
3261 {
3262 return [[self window] isOpaque] && !mIsPluginView;
3263 }
3265 -(void)setIsPluginView:(BOOL)aIsPlugin
3266 {
3267 mIsPluginView = aIsPlugin;
3268 }
3270 -(BOOL)isPluginView
3271 {
3272 return mIsPluginView;
3273 }
3275 - (NSView *)hitTest:(NSPoint)aPoint
3276 {
3277 NSView* target = [super hitTest:aPoint];
3278 if ((target == self) && [self isPluginView] && mGeckoChild) {
3279 nsAutoRetainCocoaObject kungFuDeathGrip(self);
3281 NSPoint cocoaLoc = [[self superview] convertPoint:aPoint toView:self];
3282 LayoutDeviceIntPoint widgetLoc = LayoutDeviceIntPoint::FromUntyped(
3283 mGeckoChild->CocoaPointsToDevPixels(cocoaLoc));
3285 WidgetQueryContentEvent hitTest(true, NS_QUERY_DOM_WIDGET_HITTEST,
3286 mGeckoChild);
3287 hitTest.InitForQueryDOMWidgetHittest(widgetLoc);
3288 // This might destroy our widget.
3289 mGeckoChild->DispatchWindowEvent(hitTest);
3290 if (!mGeckoChild) {
3291 return nil;
3292 }
3293 if (hitTest.mSucceeded && !hitTest.mReply.mWidgetIsHit) {
3294 return nil;
3295 }
3296 }
3297 return target;
3298 }
3300 // Are we processing an NSLeftMouseDown event that will fail to click through?
3301 // If so, we shouldn't focus or unfocus a plugin.
3302 - (BOOL)isInFailingLeftClickThrough
3303 {
3304 if (!mGeckoChild)
3305 return NO;
3307 if (!mClickThroughMouseDownEvent ||
3308 [mClickThroughMouseDownEvent type] != NSLeftMouseDown)
3309 return NO;
3311 BOOL retval =
3312 !ChildViewMouseTracker::WindowAcceptsEvent([self window],
3313 mClickThroughMouseDownEvent,
3314 self, true);
3316 // If we return YES here, this will result in us not being focused,
3317 // which will stop us receiving mClickThroughMouseDownEvent in
3318 // [ChildView mouseDown:]. So we need to release and null-out
3319 // mClickThroughMouseDownEvent here.
3320 if (retval) {
3321 [mClickThroughMouseDownEvent release];
3322 mClickThroughMouseDownEvent = nil;
3323 }
3325 return retval;
3326 }
3328 - (void)setPluginEventModel:(NPEventModel)eventModel
3329 {
3330 mPluginEventModel = eventModel;
3331 }
3333 - (void)setPluginDrawingModel:(NPDrawingModel)drawingModel
3334 {
3335 mPluginDrawingModel = drawingModel;
3336 }
3338 - (NPEventModel)pluginEventModel
3339 {
3340 return mPluginEventModel;
3341 }
3343 - (NPDrawingModel)pluginDrawingModel
3344 {
3345 return mPluginDrawingModel;
3346 }
3348 - (void)sendFocusEvent:(uint32_t)eventType
3349 {
3350 if (!mGeckoChild)
3351 return;
3353 nsEventStatus status = nsEventStatus_eIgnore;
3354 WidgetGUIEvent focusGuiEvent(true, eventType, mGeckoChild);
3355 focusGuiEvent.time = PR_IntervalNow();
3356 mGeckoChild->DispatchEvent(&focusGuiEvent, status);
3357 }
3359 // We accept key and mouse events, so don't keep passing them up the chain. Allow
3360 // this to be a 'focused' widget for event dispatch.
3361 - (BOOL)acceptsFirstResponder
3362 {
3363 return YES;
3364 }
3366 // Accept mouse down events on background windows
3367 - (BOOL)acceptsFirstMouse:(NSEvent*)aEvent
3368 {
3369 if (![[self window] isKindOfClass:[PopupWindow class]]) {
3370 // We rely on this function to tell us that the mousedown was on a
3371 // background window. Inside mouseDown we can't tell whether we were
3372 // inactive because at that point we've already been made active.
3373 // Unfortunately, acceptsFirstMouse is called for PopupWindows even when
3374 // their parent window is active, so ignore this on them for now.
3375 mClickThroughMouseDownEvent = [aEvent retain];
3376 }
3377 return YES;
3378 }
3380 - (void)viewWillMoveToWindow:(NSWindow *)newWindow
3381 {
3382 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
3384 if (!newWindow)
3385 HideChildPluginViews(self);
3387 [super viewWillMoveToWindow:newWindow];
3389 NS_OBJC_END_TRY_ABORT_BLOCK;
3390 }
3392 - (void)viewDidMoveToWindow
3393 {
3394 if (mPluginEventModel == NPEventModelCocoa &&
3395 [self window] && [self isPluginView] && mGeckoChild) {
3396 mGeckoChild->UpdatePluginPort();
3397 }
3399 [super viewDidMoveToWindow];
3400 }
3402 - (void)scrollRect:(NSRect)aRect by:(NSSize)offset
3403 {
3404 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
3406 // Update any pending dirty rects to reflect the new scroll position
3407 if (mPendingDirtyRects) {
3408 unsigned int count = [mPendingDirtyRects count];
3409 for (unsigned int i = 0; i < count; ++i) {
3410 NSRect oldRect = [[mPendingDirtyRects objectAtIndex:i] rectValue];
3411 NSRect newRect = NSOffsetRect(oldRect, offset.width, offset.height);
3412 [mPendingDirtyRects replaceObjectAtIndex:i
3413 withObject:[NSValue valueWithRect:newRect]];
3414 }
3415 }
3416 [super scrollRect:aRect by:offset];
3418 NS_OBJC_END_TRY_ABORT_BLOCK;
3419 }
3421 - (BOOL)mouseDownCanMoveWindow
3422 {
3423 return [[self window] isMovableByWindowBackground];
3424 }
3426 -(void)updateGLContext
3427 {
3428 if (mGLContext) {
3429 CGLLockContext((CGLContextObj)[mGLContext CGLContextObj]);
3430 [mGLContext setView:self];
3431 [mGLContext update];
3432 CGLUnlockContext((CGLContextObj)[mGLContext CGLContextObj]);
3433 }
3434 }
3436 - (void)_surfaceNeedsUpdate:(NSNotification*)notification
3437 {
3438 [self updateGLContext];
3439 }
3441 - (BOOL)wantsBestResolutionOpenGLSurface
3442 {
3443 return nsCocoaUtils::HiDPIEnabled() ? YES : NO;
3444 }
3446 - (void)viewDidChangeBackingProperties
3447 {
3448 [super viewDidChangeBackingProperties];
3449 if (mGeckoChild) {
3450 // actually, it could be the color space that's changed,
3451 // but we can't tell the difference here except by retrieving
3452 // the backing scale factor and comparing to the old value
3453 mGeckoChild->BackingScaleFactorChanged();
3454 }
3455 }
3457 - (BOOL)isCoveringTitlebar
3458 {
3459 return [[self window] isKindOfClass:[BaseWindow class]] &&
3460 [(BaseWindow*)[self window] mainChildView] == self &&
3461 [(BaseWindow*)[self window] drawsContentsIntoWindowFrame];
3462 }
3464 - (nsIntRegion)nativeDirtyRegionWithBoundingRect:(NSRect)aRect
3465 {
3466 nsIntRect boundingRect = mGeckoChild->CocoaPointsToDevPixels(aRect);
3467 const NSRect *rects;
3468 NSInteger count;
3469 [self getRectsBeingDrawn:&rects count:&count];
3471 if (count > MAX_RECTS_IN_REGION) {
3472 return boundingRect;
3473 }
3475 nsIntRegion region;
3476 for (NSInteger i = 0; i < count; ++i) {
3477 region.Or(region, mGeckoChild->CocoaPointsToDevPixels(rects[i]));
3478 }
3479 region.And(region, boundingRect);
3480 return region;
3481 }
3483 // The display system has told us that a portion of our view is dirty. Tell
3484 // gecko to paint it
3485 - (void)drawRect:(NSRect)aRect
3486 {
3487 CGContextRef cgContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
3488 [self drawRect:aRect inContext:cgContext];
3490 // If we're a transparent window and our contents have changed, we need
3491 // to make sure the shadow is updated to the new contents.
3492 if ([[self window] isKindOfClass:[BaseWindow class]]) {
3493 [(BaseWindow*)[self window] deferredInvalidateShadow];
3494 }
3495 }
3497 - (void)drawRect:(NSRect)aRect inContext:(CGContextRef)aContext
3498 {
3499 if (!mGeckoChild || !mGeckoChild->IsVisible())
3500 return;
3502 // Don't ever draw plugin views explicitly; they'll be drawn as part of their parent widget.
3503 if (mIsPluginView)
3504 return;
3506 #ifdef DEBUG_UPDATE
3507 nsIntRect geckoBounds;
3508 mGeckoChild->GetBounds(geckoBounds);
3510 fprintf (stderr, "---- Update[%p][%p] [%f %f %f %f] cgc: %p\n gecko bounds: [%d %d %d %d]\n",
3511 self, mGeckoChild,
3512 aRect.origin.x, aRect.origin.y, aRect.size.width, aRect.size.height, aContext,
3513 geckoBounds.x, geckoBounds.y, geckoBounds.width, geckoBounds.height);
3515 CGAffineTransform xform = CGContextGetCTM(aContext);
3516 fprintf (stderr, " xform in: [%f %f %f %f %f %f]\n", xform.a, xform.b, xform.c, xform.d, xform.tx, xform.ty);
3517 #endif
3519 if ([self isUsingOpenGL]) {
3520 // For Gecko-initiated repaints in OpenGL mode, drawUsingOpenGL is
3521 // directly called from a delayed perform callback - without going through
3522 // drawRect.
3523 // Paints that come through here are triggered by something that Cocoa
3524 // controls, for example by window resizing or window focus changes.
3526 // Since this view is usually declared as opaque, the window's pixel
3527 // buffer may now contain garbage which we need to prevent from reaching
3528 // the screen. The only place where garbage can show is in the window
3529 // corners - the rest of the window is covered by opaque content in our
3530 // OpenGL surface.
3531 // So we need to clear the pixel buffer contents in the corners.
3532 [self clearCorners];
3534 // Do GL composition and return.
3535 [self drawUsingOpenGL];
3536 return;
3537 }
3539 PROFILER_LABEL("widget", "ChildView::drawRect");
3541 // The CGContext that drawRect supplies us with comes with a transform that
3542 // scales one user space unit to one Cocoa point, which can consist of
3543 // multiple dev pixels. But Gecko expects its supplied context to be scaled
3544 // to device pixels, so we need to reverse the scaling.
3545 double scale = mGeckoChild->BackingScaleFactor();
3546 CGContextSaveGState(aContext);
3547 CGContextScaleCTM(aContext, 1.0 / scale, 1.0 / scale);
3549 NSSize viewSize = [self bounds].size;
3550 nsIntSize backingSize(viewSize.width * scale, viewSize.height * scale);
3552 CGContextSaveGState(aContext);
3554 nsIntRegion region = [self nativeDirtyRegionWithBoundingRect:aRect];
3556 // Create Cairo objects.
3557 nsRefPtr<gfxQuartzSurface> targetSurface =
3558 new gfxQuartzSurface(aContext, backingSize);
3559 targetSurface->SetAllowUseAsSource(false);
3561 nsRefPtr<gfxContext> targetContext;
3562 if (gfxPlatform::GetPlatform()->SupportsAzureContentForType(gfx::BackendType::CAIRO)) {
3563 RefPtr<gfx::DrawTarget> dt =
3564 gfxPlatform::GetPlatform()->CreateDrawTargetForSurface(targetSurface,
3565 gfx::IntSize(backingSize.width,
3566 backingSize.height));
3567 dt->AddUserData(&gfxContext::sDontUseAsSourceKey, dt, nullptr);
3568 targetContext = new gfxContext(dt);
3569 } else if (gfxPlatform::GetPlatform()->SupportsAzureContentForType(gfx::BackendType::COREGRAPHICS)) {
3570 RefPtr<gfx::DrawTarget> dt =
3571 gfx::Factory::CreateDrawTargetForCairoCGContext(aContext,
3572 gfx::IntSize(backingSize.width,
3573 backingSize.height));
3574 dt->AddUserData(&gfxContext::sDontUseAsSourceKey, dt, nullptr);
3575 targetContext = new gfxContext(dt);
3576 } else {
3577 targetContext = new gfxContext(targetSurface);
3578 }
3580 // Set up the clip region.
3581 nsIntRegionRectIterator iter(region);
3582 targetContext->NewPath();
3583 for (;;) {
3584 const nsIntRect* r = iter.Next();
3585 if (!r)
3586 break;
3587 targetContext->Rectangle(gfxRect(r->x, r->y, r->width, r->height));
3588 }
3589 targetContext->Clip();
3591 nsAutoRetainCocoaObject kungFuDeathGrip(self);
3592 bool painted = false;
3593 if (mGeckoChild->GetLayerManager()->GetBackendType() == LayersBackend::LAYERS_BASIC) {
3594 nsBaseWidget::AutoLayerManagerSetup
3595 setupLayerManager(mGeckoChild, targetContext, BufferMode::BUFFER_NONE);
3596 painted = mGeckoChild->PaintWindow(region);
3597 } else if (mGeckoChild->GetLayerManager()->GetBackendType() == LayersBackend::LAYERS_CLIENT) {
3598 // We only need this so that we actually get DidPaintWindow fired
3599 painted = mGeckoChild->PaintWindow(region);
3600 }
3602 targetContext = nullptr;
3603 targetSurface = nullptr;
3605 CGContextRestoreGState(aContext);
3607 // Undo the scale transform so that from now on the context is in
3608 // CocoaPoints again.
3609 CGContextRestoreGState(aContext);
3611 if (!painted && [self isOpaque]) {
3612 // Gecko refused to draw, but we've claimed to be opaque, so we have to
3613 // draw something--fill with white.
3614 CGContextSetRGBFillColor(aContext, 1, 1, 1, 1);
3615 CGContextFillRect(aContext, NSRectToCGRect(aRect));
3616 }
3618 if ([self isCoveringTitlebar]) {
3619 [self drawTitleString];
3620 [self drawTitlebarHighlight];
3621 [self maskTopCornersInContext:aContext];
3622 }
3624 #ifdef DEBUG_UPDATE
3625 fprintf (stderr, "---- update done ----\n");
3627 #if 0
3628 CGContextSetRGBStrokeColor (aContext,
3629 ((((unsigned long)self) & 0xff)) / 255.0,
3630 ((((unsigned long)self) & 0xff00) >> 8) / 255.0,
3631 ((((unsigned long)self) & 0xff0000) >> 16) / 255.0,
3632 0.5);
3633 #endif
3634 CGContextSetRGBStrokeColor(aContext, 1, 0, 0, 0.8);
3635 CGContextSetLineWidth(aContext, 4.0);
3636 CGContextStrokeRect(aContext, NSRectToCGRect(aRect));
3637 #endif
3638 }
3640 - (BOOL)isUsingMainThreadOpenGL
3641 {
3642 if (!mGeckoChild || ![self window])
3643 return NO;
3645 return mGeckoChild->GetLayerManager(nullptr)->GetBackendType() == mozilla::layers::LayersBackend::LAYERS_OPENGL;
3646 }
3648 - (BOOL)isUsingOpenGL
3649 {
3650 if (!mGeckoChild || ![self window])
3651 return NO;
3653 return mGLContext || mUsingOMTCompositor || [self isUsingMainThreadOpenGL];
3654 }
3656 - (void)drawUsingOpenGL
3657 {
3658 PROFILER_LABEL("widget", "ChildView::drawUsingOpenGL");
3660 if (![self isUsingOpenGL] || !mGeckoChild->IsVisible())
3661 return;
3663 mWaitingForPaint = NO;
3665 nsIntRect geckoBounds;
3666 mGeckoChild->GetBounds(geckoBounds);
3667 nsIntRegion region(geckoBounds);
3669 mGeckoChild->PaintWindow(region);
3671 // Force OpenGL to refresh the very first time we draw. This works around a
3672 // Mac OS X bug that stops windows updating on OS X when we use OpenGL.
3673 if (!mDidForceRefreshOpenGL) {
3674 [self performSelector:@selector(forceRefreshOpenGL) withObject:nil afterDelay:0];
3675 mDidForceRefreshOpenGL = YES;
3676 }
3677 }
3679 // Called asynchronously after setNeedsDisplay in order to avoid entering the
3680 // normal drawing machinery.
3681 - (void)drawUsingOpenGLCallback
3682 {
3683 if (mWaitingForPaint) {
3684 [self drawUsingOpenGL];
3685 }
3686 }
3688 - (BOOL)hasRoundedBottomCorners
3689 {
3690 return [[self window] respondsToSelector:@selector(bottomCornerRounded)] &&
3691 [[self window] bottomCornerRounded];
3692 }
3694 - (CGFloat)cornerRadius
3695 {
3696 NSView* frameView = [[[self window] contentView] superview];
3697 if (!frameView || ![frameView respondsToSelector:@selector(roundedCornerRadius)])
3698 return 4.0f;
3699 return [frameView roundedCornerRadius];
3700 }
3702 // Accelerated windows have two NSSurfaces:
3703 // (1) The window's pixel buffer in the back and
3704 // (2) the OpenGL view in the front.
3705 // These two surfaces are composited by the window manager. Drawing into the
3706 // CGContext which is provided by drawRect ends up in (1).
3707 // When our window has rounded corners, the OpenGL view has transparent pixels
3708 // in the corners. In these places the contents of the window's pixel buffer
3709 // can show through. So we need to make sure that the pixel buffer is
3710 // transparent in the corners so that no garbage reaches the screen.
3711 // The contents of the pixel buffer in the rest of the window don't matter
3712 // because they're covered by opaque pixels of the OpenGL context.
3713 // Making the corners transparent works even though our window is
3714 // declared "opaque" (in the NSWindow's isOpaque method).
3715 - (void)clearCorners
3716 {
3717 CGFloat radius = [self cornerRadius];
3718 CGFloat w = [self bounds].size.width, h = [self bounds].size.height;
3719 [[NSColor clearColor] set];
3721 if ([self isCoveringTitlebar]) {
3722 NSRectFill(NSMakeRect(0, 0, radius, radius));
3723 NSRectFill(NSMakeRect(w - radius, 0, radius, radius));
3724 }
3726 if ([self hasRoundedBottomCorners]) {
3727 NSRectFill(NSMakeRect(0, h - radius, radius, radius));
3728 NSRectFill(NSMakeRect(w - radius, h - radius, radius, radius));
3729 }
3730 }
3732 // This is the analog of nsChildView::MaybeDrawRoundedCorners for CGContexts.
3733 // We only need to mask the top corners here because Cocoa does the masking
3734 // for the window's bottom corners automatically (starting with 10.7).
3735 - (void)maskTopCornersInContext:(CGContextRef)aContext
3736 {
3737 CGFloat radius = [self cornerRadius];
3738 int32_t devPixelCornerRadius = mGeckoChild->CocoaPointsToDevPixels(radius);
3740 // First make sure that mTopLeftCornerMask is set up.
3741 if (!mTopLeftCornerMask ||
3742 int32_t(CGImageGetWidth(mTopLeftCornerMask)) != devPixelCornerRadius) {
3743 CGImageRelease(mTopLeftCornerMask);
3744 CGColorSpaceRef rgb = CGColorSpaceCreateDeviceRGB();
3745 CGContextRef imgCtx = CGBitmapContextCreate(NULL,
3746 devPixelCornerRadius,
3747 devPixelCornerRadius,
3748 8, devPixelCornerRadius * 4,
3749 rgb, kCGImageAlphaPremultipliedFirst);
3750 CGColorSpaceRelease(rgb);
3751 DrawTopLeftCornerMask(imgCtx, devPixelCornerRadius);
3752 mTopLeftCornerMask = CGBitmapContextCreateImage(imgCtx);
3753 CGContextRelease(imgCtx);
3754 }
3756 // kCGBlendModeDestinationIn is the secret sauce which allows us to erase
3757 // already painted pixels. It's defined as R = D * Sa: multiply all channels
3758 // of the destination pixel with the alpha of the source pixel. In our case,
3759 // the source is mTopLeftCornerMask.
3760 CGContextSaveGState(aContext);
3761 CGContextSetBlendMode(aContext, kCGBlendModeDestinationIn);
3763 CGRect destRect = CGRectMake(0, 0, radius, radius);
3765 // Erase the top left corner...
3766 CGContextDrawImage(aContext, destRect, mTopLeftCornerMask);
3768 // ... and the top right corner.
3769 CGContextTranslateCTM(aContext, [self bounds].size.width, 0);
3770 CGContextScaleCTM(aContext, -1, 1);
3771 CGContextDrawImage(aContext, destRect, mTopLeftCornerMask);
3773 CGContextRestoreGState(aContext);
3774 }
3776 - (void)drawTitleString
3777 {
3778 BaseWindow* window = (BaseWindow*)[self window];
3779 if (![window wantsTitleDrawn]) {
3780 return;
3781 }
3783 NSView* frameView = [[window contentView] superview];
3784 if (![frameView respondsToSelector:@selector(_drawTitleBar:)]) {
3785 return;
3786 }
3788 NSGraphicsContext* oldContext = [NSGraphicsContext currentContext];
3789 CGContextRef ctx = (CGContextRef)[oldContext graphicsPort];
3790 CGContextSaveGState(ctx);
3791 if ([oldContext isFlipped] != [frameView isFlipped]) {
3792 CGContextTranslateCTM(ctx, 0, [self bounds].size.height);
3793 CGContextScaleCTM(ctx, 1, -1);
3794 }
3795 [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:ctx flipped:[frameView isFlipped]]];
3796 [frameView _drawTitleBar:[frameView bounds]];
3797 CGContextRestoreGState(ctx);
3798 [NSGraphicsContext setCurrentContext:oldContext];
3799 }
3801 - (void)drawTitlebarHighlight
3802 {
3803 DrawTitlebarHighlight([self bounds].size, [self cornerRadius],
3804 mGeckoChild->DevPixelsToCocoaPoints(1));
3805 }
3807 - (void)releaseWidgets:(NSArray*)aWidgetArray
3808 {
3809 if (!aWidgetArray) {
3810 return;
3811 }
3812 NSInteger count = [aWidgetArray count];
3813 for (NSInteger i = 0; i < count; ++i) {
3814 NSNumber* pointer = (NSNumber*) [aWidgetArray objectAtIndex:i];
3815 nsIWidget* widget = (nsIWidget*) [pointer unsignedIntegerValue];
3816 NS_RELEASE(widget);
3817 }
3818 }
3820 - (void)viewWillDraw
3821 {
3822 if (mGeckoChild) {
3823 // The OS normally *will* draw our NSWindow, no matter what we do here.
3824 // But Gecko can delete our parent widget(s) (along with mGeckoChild)
3825 // while processing a paint request, which closes our NSWindow and
3826 // makes the OS throw an NSInternalInconsistencyException assertion when
3827 // it tries to draw it. Sometimes the OS also aborts the browser process.
3828 // So we need to retain our parent(s) here and not release it/them until
3829 // the next time through the main thread's run loop. When we do this we
3830 // also need to retain and release mGeckoChild, which holds a strong
3831 // reference to us (otherwise we might have been deleted by the time
3832 // releaseWidgets: is called on us). See bug 550392.
3833 nsIWidget* parent = mGeckoChild->GetParent();
3834 if (parent) {
3835 NSMutableArray* widgetArray = [NSMutableArray arrayWithCapacity:3];
3836 while (parent) {
3837 NS_ADDREF(parent);
3838 [widgetArray addObject:[NSNumber numberWithUnsignedInteger:(NSUInteger)parent]];
3839 parent = parent->GetParent();
3840 }
3841 NS_ADDREF(mGeckoChild);
3842 [widgetArray addObject:[NSNumber numberWithUnsignedInteger:(NSUInteger)mGeckoChild]];
3843 [self performSelector:@selector(releaseWidgets:)
3844 withObject:widgetArray
3845 afterDelay:0];
3846 }
3848 if ([self isUsingOpenGL]) {
3849 // When our view covers the titlebar, we need to repaint the titlebar
3850 // texture buffer when, for example, the window buttons are hovered.
3851 // So we notify our nsChildView about any areas needing repainting.
3852 mGeckoChild->NotifyDirtyRegion([self nativeDirtyRegionWithBoundingRect:[self bounds]]);
3854 if (mGeckoChild->GetLayerManager()->GetBackendType() == LayersBackend::LAYERS_CLIENT) {
3855 ClientLayerManager *manager = static_cast<ClientLayerManager*>(mGeckoChild->GetLayerManager());
3856 manager->AsShadowForwarder()->WindowOverlayChanged();
3857 }
3858 }
3860 mGeckoChild->WillPaintWindow();
3861 }
3862 [super viewWillDraw];
3863 }
3865 #if USE_CLICK_HOLD_CONTEXTMENU
3866 //
3867 // -clickHoldCallback:
3868 //
3869 // called from a timer two seconds after a mouse down to see if we should display
3870 // a context menu (click-hold). |anEvent| is the original mouseDown event. If we're
3871 // still in that mouseDown by this time, put up the context menu, otherwise just
3872 // fuhgeddaboutit. |anEvent| has been retained by the OS until after this callback
3873 // fires so we're ok there.
3874 //
3875 // This code currently messes in a bunch of edge cases (bugs 234751, 232964, 232314)
3876 // so removing it until we get it straightened out.
3877 //
3878 - (void)clickHoldCallback:(id)theEvent;
3879 {
3880 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
3882 if( theEvent == [NSApp currentEvent] ) {
3883 // we're still in the middle of the same mousedown event here, activate
3884 // click-hold context menu by triggering the right mouseDown action.
3885 NSEvent* clickHoldEvent = [NSEvent mouseEventWithType:NSRightMouseDown
3886 location:[theEvent locationInWindow]
3887 modifierFlags:[theEvent modifierFlags]
3888 timestamp:[theEvent timestamp]
3889 windowNumber:[theEvent windowNumber]
3890 context:[theEvent context]
3891 eventNumber:[theEvent eventNumber]
3892 clickCount:[theEvent clickCount]
3893 pressure:[theEvent pressure]];
3894 [self rightMouseDown:clickHoldEvent];
3895 }
3897 NS_OBJC_END_TRY_ABORT_BLOCK;
3898 }
3899 #endif
3901 // If we've just created a non-native context menu, we need to mark it as
3902 // such and let the OS (and other programs) know when it opens and closes
3903 // (this is how the OS knows to close other programs' context menus when
3904 // ours open). We send the initial notification here, but others are sent
3905 // in nsCocoaWindow::Show().
3906 - (void)maybeInitContextMenuTracking
3907 {
3908 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
3910 #ifdef MOZ_USE_NATIVE_POPUP_WINDOWS
3911 return;
3912 #endif /* MOZ_USE_NATIVE_POPUP_WINDOWS */
3914 nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener();
3915 NS_ENSURE_TRUE_VOID(rollupListener);
3916 nsCOMPtr<nsIWidget> widget = rollupListener->GetRollupWidget();
3917 NS_ENSURE_TRUE_VOID(widget);
3919 NSWindow *popupWindow = (NSWindow*)widget->GetNativeData(NS_NATIVE_WINDOW);
3920 if (!popupWindow || ![popupWindow isKindOfClass:[PopupWindow class]])
3921 return;
3923 [[NSDistributedNotificationCenter defaultCenter]
3924 postNotificationName:@"com.apple.HIToolbox.beginMenuTrackingNotification"
3925 object:@"org.mozilla.gecko.PopupWindow"];
3926 [(PopupWindow*)popupWindow setIsContextMenu:YES];
3928 NS_OBJC_END_TRY_ABORT_BLOCK;
3929 }
3931 // Returns true if the event should no longer be processed, false otherwise.
3932 // This does not return whether or not anything was rolled up.
3933 - (BOOL)maybeRollup:(NSEvent*)theEvent
3934 {
3935 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
3937 BOOL consumeEvent = NO;
3939 nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener();
3940 NS_ENSURE_TRUE(rollupListener, false);
3941 nsCOMPtr<nsIWidget> rollupWidget = rollupListener->GetRollupWidget();
3942 if (rollupWidget) {
3943 NSWindow* currentPopup = static_cast<NSWindow*>(rollupWidget->GetNativeData(NS_NATIVE_WINDOW));
3944 if (!nsCocoaUtils::IsEventOverWindow(theEvent, currentPopup)) {
3945 // event is not over the rollup window, default is to roll up
3946 bool shouldRollup = true;
3948 // check to see if scroll events should roll up the popup
3949 if ([theEvent type] == NSScrollWheel) {
3950 shouldRollup = rollupListener->ShouldRollupOnMouseWheelEvent();
3951 // consume scroll events that aren't over the popup
3952 // unless the popup is an arrow panel
3953 consumeEvent = rollupListener->ShouldConsumeOnMouseWheelEvent();
3954 }
3956 // if we're dealing with menus, we probably have submenus and
3957 // we don't want to rollup if the click is in a parent menu of
3958 // the current submenu
3959 uint32_t popupsToRollup = UINT32_MAX;
3960 nsAutoTArray<nsIWidget*, 5> widgetChain;
3961 uint32_t sameTypeCount = rollupListener->GetSubmenuWidgetChain(&widgetChain);
3962 for (uint32_t i = 0; i < widgetChain.Length(); i++) {
3963 nsIWidget* widget = widgetChain[i];
3964 NSWindow* currWindow = (NSWindow*)widget->GetNativeData(NS_NATIVE_WINDOW);
3965 if (nsCocoaUtils::IsEventOverWindow(theEvent, currWindow)) {
3966 // don't roll up if the mouse event occurred within a menu of the
3967 // same type. If the mouse event occurred in a menu higher than
3968 // that, roll up, but pass the number of popups to Rollup so
3969 // that only those of the same type close up.
3970 if (i < sameTypeCount) {
3971 shouldRollup = false;
3972 }
3973 else {
3974 popupsToRollup = sameTypeCount;
3975 }
3976 break;
3977 }
3978 }
3980 if (shouldRollup) {
3981 if ([theEvent type] == NSLeftMouseDown) {
3982 NSPoint point = [NSEvent mouseLocation];
3983 FlipCocoaScreenCoordinate(point);
3984 nsIntPoint pos(point.x, point.y);
3985 consumeEvent = (BOOL)rollupListener->Rollup(popupsToRollup, &pos, nullptr);
3986 }
3987 else {
3988 consumeEvent = (BOOL)rollupListener->Rollup(popupsToRollup, nullptr, nullptr);
3989 }
3990 }
3991 }
3992 }
3994 return consumeEvent;
3996 NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO);
3997 }
3999 /*
4000 * In OS X Mountain Lion and above, smart zoom gestures are implemented in
4001 * smartMagnifyWithEvent. In OS X Lion, they are implemented in
4002 * magnifyWithEvent. See inline comments for more info.
4003 *
4004 * The prototypes swipeWithEvent, beginGestureWithEvent, magnifyWithEvent,
4005 * smartMagnifyWithEvent, rotateWithEvent, and endGestureWithEvent were
4006 * obtained from the following links:
4007 * https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ApplicationKit/Classes/NSResponder_Class/Reference/Reference.html
4008 * https://developer.apple.com/library/mac/#releasenotes/Cocoa/AppKit.html
4009 */
4011 - (void)swipeWithEvent:(NSEvent *)anEvent
4012 {
4013 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
4015 if (!anEvent || !mGeckoChild)
4016 return;
4018 nsAutoRetainCocoaObject kungFuDeathGrip(self);
4020 float deltaX = [anEvent deltaX]; // left=1.0, right=-1.0
4021 float deltaY = [anEvent deltaY]; // up=1.0, down=-1.0
4023 // Setup the "swipe" event.
4024 WidgetSimpleGestureEvent geckoEvent(true, NS_SIMPLE_GESTURE_SWIPE,
4025 mGeckoChild);
4026 [self convertCocoaMouseEvent:anEvent toGeckoEvent:&geckoEvent];
4028 // Record the left/right direction.
4029 if (deltaX > 0.0)
4030 geckoEvent.direction |= nsIDOMSimpleGestureEvent::DIRECTION_LEFT;
4031 else if (deltaX < 0.0)
4032 geckoEvent.direction |= nsIDOMSimpleGestureEvent::DIRECTION_RIGHT;
4034 // Record the up/down direction.
4035 if (deltaY > 0.0)
4036 geckoEvent.direction |= nsIDOMSimpleGestureEvent::DIRECTION_UP;
4037 else if (deltaY < 0.0)
4038 geckoEvent.direction |= nsIDOMSimpleGestureEvent::DIRECTION_DOWN;
4040 // Send the event.
4041 mGeckoChild->DispatchWindowEvent(geckoEvent);
4043 NS_OBJC_END_TRY_ABORT_BLOCK;
4044 }
4046 - (void)beginGestureWithEvent:(NSEvent *)anEvent
4047 {
4048 if (!anEvent)
4049 return;
4051 mGestureState = eGestureState_StartGesture;
4052 mCumulativeMagnification = 0;
4053 mCumulativeRotation = 0.0;
4054 }
4056 - (void)magnifyWithEvent:(NSEvent *)anEvent
4057 {
4058 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
4060 if (!anEvent || !mGeckoChild)
4061 return;
4063 /*
4064 * In OS X 10.7.* (Lion), smart zoom events come through magnifyWithEvent,
4065 * instead of smartMagnifyWithEvent. See bug 863841.
4066 */
4067 if ([ChildView isLionSmartMagnifyEvent: anEvent]) {
4068 [self smartMagnifyWithEvent: anEvent];
4069 return;
4070 }
4072 nsAutoRetainCocoaObject kungFuDeathGrip(self);
4074 float deltaZ = [anEvent deltaZ];
4076 uint32_t msg;
4077 switch (mGestureState) {
4078 case eGestureState_StartGesture:
4079 msg = NS_SIMPLE_GESTURE_MAGNIFY_START;
4080 mGestureState = eGestureState_MagnifyGesture;
4081 break;
4083 case eGestureState_MagnifyGesture:
4084 msg = NS_SIMPLE_GESTURE_MAGNIFY_UPDATE;
4085 break;
4087 case eGestureState_None:
4088 case eGestureState_RotateGesture:
4089 default:
4090 return;
4091 }
4093 // Setup the event.
4094 WidgetSimpleGestureEvent geckoEvent(true, msg, mGeckoChild);
4095 geckoEvent.delta = deltaZ;
4096 [self convertCocoaMouseEvent:anEvent toGeckoEvent:&geckoEvent];
4098 // Send the event.
4099 mGeckoChild->DispatchWindowEvent(geckoEvent);
4101 // Keep track of the cumulative magnification for the final "magnify" event.
4102 mCumulativeMagnification += deltaZ;
4104 NS_OBJC_END_TRY_ABORT_BLOCK;
4105 }
4107 - (void)smartMagnifyWithEvent:(NSEvent *)anEvent
4108 {
4109 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
4111 if (!anEvent || !mGeckoChild) {
4112 return;
4113 }
4115 nsAutoRetainCocoaObject kungFuDeathGrip(self);
4117 // Setup the "double tap" event.
4118 WidgetSimpleGestureEvent geckoEvent(true, NS_SIMPLE_GESTURE_TAP,
4119 mGeckoChild);
4120 [self convertCocoaMouseEvent:anEvent toGeckoEvent:&geckoEvent];
4121 geckoEvent.clickCount = 1;
4123 // Send the event.
4124 mGeckoChild->DispatchWindowEvent(geckoEvent);
4126 // Clear the gesture state
4127 mGestureState = eGestureState_None;
4129 NS_OBJC_END_TRY_ABORT_BLOCK;
4130 }
4132 - (void)rotateWithEvent:(NSEvent *)anEvent
4133 {
4134 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
4136 if (!anEvent || !mGeckoChild)
4137 return;
4139 nsAutoRetainCocoaObject kungFuDeathGrip(self);
4141 float rotation = [anEvent rotation];
4143 uint32_t msg;
4144 switch (mGestureState) {
4145 case eGestureState_StartGesture:
4146 msg = NS_SIMPLE_GESTURE_ROTATE_START;
4147 mGestureState = eGestureState_RotateGesture;
4148 break;
4150 case eGestureState_RotateGesture:
4151 msg = NS_SIMPLE_GESTURE_ROTATE_UPDATE;
4152 break;
4154 case eGestureState_None:
4155 case eGestureState_MagnifyGesture:
4156 default:
4157 return;
4158 }
4160 // Setup the event.
4161 WidgetSimpleGestureEvent geckoEvent(true, msg, mGeckoChild);
4162 [self convertCocoaMouseEvent:anEvent toGeckoEvent:&geckoEvent];
4163 geckoEvent.delta = -rotation;
4164 if (rotation > 0.0) {
4165 geckoEvent.direction = nsIDOMSimpleGestureEvent::ROTATION_COUNTERCLOCKWISE;
4166 } else {
4167 geckoEvent.direction = nsIDOMSimpleGestureEvent::ROTATION_CLOCKWISE;
4168 }
4170 // Send the event.
4171 mGeckoChild->DispatchWindowEvent(geckoEvent);
4173 // Keep track of the cumulative rotation for the final "rotate" event.
4174 mCumulativeRotation += rotation;
4176 NS_OBJC_END_TRY_ABORT_BLOCK;
4177 }
4179 - (void)endGestureWithEvent:(NSEvent *)anEvent
4180 {
4181 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
4183 if (!anEvent || !mGeckoChild) {
4184 // Clear the gestures state if we cannot send an event.
4185 mGestureState = eGestureState_None;
4186 mCumulativeMagnification = 0.0;
4187 mCumulativeRotation = 0.0;
4188 return;
4189 }
4191 nsAutoRetainCocoaObject kungFuDeathGrip(self);
4193 switch (mGestureState) {
4194 case eGestureState_MagnifyGesture:
4195 {
4196 // Setup the "magnify" event.
4197 WidgetSimpleGestureEvent geckoEvent(true, NS_SIMPLE_GESTURE_MAGNIFY,
4198 mGeckoChild);
4199 geckoEvent.delta = mCumulativeMagnification;
4200 [self convertCocoaMouseEvent:anEvent toGeckoEvent:&geckoEvent];
4202 // Send the event.
4203 mGeckoChild->DispatchWindowEvent(geckoEvent);
4204 }
4205 break;
4207 case eGestureState_RotateGesture:
4208 {
4209 // Setup the "rotate" event.
4210 WidgetSimpleGestureEvent geckoEvent(true, NS_SIMPLE_GESTURE_ROTATE,
4211 mGeckoChild);
4212 [self convertCocoaMouseEvent:anEvent toGeckoEvent:&geckoEvent];
4213 geckoEvent.delta = -mCumulativeRotation;
4214 if (mCumulativeRotation > 0.0) {
4215 geckoEvent.direction = nsIDOMSimpleGestureEvent::ROTATION_COUNTERCLOCKWISE;
4216 } else {
4217 geckoEvent.direction = nsIDOMSimpleGestureEvent::ROTATION_CLOCKWISE;
4218 }
4220 // Send the event.
4221 mGeckoChild->DispatchWindowEvent(geckoEvent);
4222 }
4223 break;
4225 case eGestureState_None:
4226 case eGestureState_StartGesture:
4227 default:
4228 break;
4229 }
4231 // Clear the gestures state.
4232 mGestureState = eGestureState_None;
4233 mCumulativeMagnification = 0.0;
4234 mCumulativeRotation = 0.0;
4236 NS_OBJC_END_TRY_ABORT_BLOCK;
4237 }
4239 + (BOOL)isLionSmartMagnifyEvent:(NSEvent*)anEvent
4240 {
4241 /*
4242 * On Lion, smart zoom events have type NSEventTypeGesture, subtype 0x16,
4243 * whereas pinch zoom events have type NSEventTypeMagnify. So, use that to
4244 * discriminate between the two. Smart zoom gestures do not call
4245 * beginGestureWithEvent or endGestureWithEvent, so mGestureState is not
4246 * changed. Documentation couldn't be found for the meaning of the subtype
4247 * 0x16, but it will probably never change. See bug 863841.
4248 */
4249 return nsCocoaFeatures::OnLionOrLater() &&
4250 !nsCocoaFeatures::OnMountainLionOrLater() &&
4251 [anEvent type] == NSEventTypeGesture &&
4252 [anEvent subtype] == 0x16;
4253 }
4255 #ifdef __LP64__
4256 - (bool)sendSwipeEvent:(NSEvent*)aEvent
4257 withKind:(uint32_t)aMsg
4258 allowedDirections:(uint32_t*)aAllowedDirections
4259 direction:(uint32_t)aDirection
4260 delta:(double)aDelta
4261 {
4262 if (!mGeckoChild)
4263 return false;
4265 WidgetSimpleGestureEvent geckoEvent(true, aMsg, mGeckoChild);
4266 geckoEvent.direction = aDirection;
4267 geckoEvent.delta = aDelta;
4268 geckoEvent.allowedDirections = *aAllowedDirections;
4269 [self convertCocoaMouseEvent:aEvent toGeckoEvent:&geckoEvent];
4270 bool eventCancelled = mGeckoChild->DispatchWindowEvent(geckoEvent);
4271 *aAllowedDirections = geckoEvent.allowedDirections;
4272 return eventCancelled; // event cancelled == swipe should start
4273 }
4275 - (void)sendSwipeEndEvent:(NSEvent *)anEvent
4276 allowedDirections:(uint32_t)aAllowedDirections
4277 {
4278 // Tear down animation overlay by sending a swipe end event.
4279 uint32_t allowedDirectionsCopy = aAllowedDirections;
4280 [self sendSwipeEvent:anEvent
4281 withKind:NS_SIMPLE_GESTURE_SWIPE_END
4282 allowedDirections:&allowedDirectionsCopy
4283 direction:0
4284 delta:0.0];
4285 }
4287 // Support fluid swipe tracking on OS X 10.7 and higher. We must be careful
4288 // to only invoke this support on a two-finger gesture that really
4289 // is a swipe (and not a scroll) -- in other words, the app is responsible
4290 // for deciding which is which. But once the decision is made, the OS tracks
4291 // the swipe until it has finished, and decides whether or not it succeeded.
4292 // A horizontal swipe has the same functionality as the Back and Forward
4293 // buttons.
4294 // This method is partly based on Apple sample code available at
4295 // developer.apple.com/library/mac/#releasenotes/Cocoa/AppKitOlderNotes.html
4296 // (under Fluid Swipe Tracking API).
4297 - (void)maybeTrackScrollEventAsSwipe:(NSEvent *)anEvent
4298 scrollOverflowX:(double)anOverflowX
4299 scrollOverflowY:(double)anOverflowY
4300 viewPortIsOverscrolled:(BOOL)aViewPortIsOverscrolled
4301 {
4302 if (!nsCocoaFeatures::OnLionOrLater()) {
4303 return;
4304 }
4306 // This method checks whether the AppleEnableSwipeNavigateWithScrolls global
4307 // preference is set. If it isn't, fluid swipe tracking is disabled, and a
4308 // horizontal two-finger gesture is always a scroll (even in Safari). This
4309 // preference can't (currently) be set from the Preferences UI -- only using
4310 // 'defaults write'.
4311 if (![NSEvent isSwipeTrackingFromScrollEventsEnabled]) {
4312 return;
4313 }
4315 // We should only track scroll events as swipe if the viewport is being
4316 // overscrolled.
4317 if (!aViewPortIsOverscrolled) {
4318 return;
4319 }
4321 // Verify that this is a scroll wheel event with proper phase to be tracked
4322 // by the OS.
4323 if ([anEvent type] != NSScrollWheel || [anEvent phase] == NSEventPhaseNone) {
4324 return;
4325 }
4327 // Only initiate tracking if the user has tried to scroll past the edge of
4328 // the current page (as indicated by 'anOverflowX' or 'anOverflowY' being
4329 // non-zero). Gecko only sets WidgetMouseScrollEvent.scrollOverflow when it's
4330 // processing NS_MOUSE_PIXEL_SCROLL events (not NS_MOUSE_SCROLL events).
4331 if (anOverflowX == 0.0 && anOverflowY == 0.0) {
4332 return;
4333 }
4335 CGFloat deltaX, deltaY;
4336 if ([anEvent hasPreciseScrollingDeltas]) {
4337 deltaX = [anEvent scrollingDeltaX];
4338 deltaY = [anEvent scrollingDeltaY];
4339 } else {
4340 return;
4341 }
4343 uint32_t vDirs = (uint32_t)nsIDOMSimpleGestureEvent::DIRECTION_DOWN |
4344 (uint32_t)nsIDOMSimpleGestureEvent::DIRECTION_UP;
4345 uint32_t direction = 0;
4347 // Only initiate horizontal tracking for events whose horizontal element is
4348 // at least eight times larger than its vertical element. This minimizes
4349 // performance problems with vertical scrolls (by minimizing the possibility
4350 // that they'll be misinterpreted as horizontal swipes), while still
4351 // tolerating a small vertical element to a true horizontal swipe. The number
4352 // '8' was arrived at by trial and error.
4353 if (anOverflowX != 0.0 && deltaX != 0.0 &&
4354 fabsf(deltaX) > fabsf(deltaY) * 8) {
4355 // Only initiate horizontal tracking for gestures that have just begun --
4356 // otherwise a scroll to one side of the page can have a swipe tacked on
4357 // to it.
4358 if ([anEvent phase] != NSEventPhaseBegan) {
4359 return;
4360 }
4362 if (deltaX < 0.0) {
4363 direction = (uint32_t)nsIDOMSimpleGestureEvent::DIRECTION_RIGHT;
4364 } else {
4365 direction = (uint32_t)nsIDOMSimpleGestureEvent::DIRECTION_LEFT;
4366 }
4367 }
4368 // Only initiate vertical tracking for events whose vertical element is
4369 // at least two times larger than its horizontal element. This minimizes
4370 // performance problems. The number '2' was arrived at by trial and error.
4371 else if (anOverflowY != 0.0 && deltaY != 0.0 &&
4372 fabsf(deltaY) > fabsf(deltaX) * 2) {
4373 if (deltaY < 0.0) {
4374 direction = (uint32_t)nsIDOMSimpleGestureEvent::DIRECTION_DOWN;
4375 } else {
4376 direction = (uint32_t)nsIDOMSimpleGestureEvent::DIRECTION_UP;
4377 }
4379 if ((mCurrentSwipeDir & vDirs) && (mCurrentSwipeDir != direction)) {
4380 // If a swipe is currently being tracked kill it -- it's been interrupted
4381 // by another gesture event.
4382 if (mCancelSwipeAnimation && *mCancelSwipeAnimation == NO) {
4383 *mCancelSwipeAnimation = YES;
4384 mCancelSwipeAnimation = nil;
4385 [self sendSwipeEndEvent:anEvent allowedDirections:0];
4386 }
4387 return;
4388 }
4389 } else {
4390 return;
4391 }
4393 // Track the direction we're going in.
4394 mCurrentSwipeDir = direction;
4396 uint32_t allowedDirections = 0;
4397 // We're ready to start the animation. Tell Gecko about it, and at the same
4398 // time ask it if it really wants to start an animation for this event.
4399 // This event also reports back the directions that we can swipe in.
4400 bool shouldStartSwipe = [self sendSwipeEvent:anEvent
4401 withKind:NS_SIMPLE_GESTURE_SWIPE_START
4402 allowedDirections:&allowedDirections
4403 direction:direction
4404 delta:0.0];
4406 if (!shouldStartSwipe) {
4407 return;
4408 }
4410 // If a swipe is currently being tracked kill it -- it's been interrupted
4411 // by another gesture event.
4412 if (mCancelSwipeAnimation && *mCancelSwipeAnimation == NO) {
4413 *mCancelSwipeAnimation = YES;
4414 mCancelSwipeAnimation = nil;
4415 }
4417 CGFloat min = 0.0;
4418 CGFloat max = 0.0;
4419 if (!(direction & vDirs)) {
4420 min = (allowedDirections & nsIDOMSimpleGestureEvent::DIRECTION_RIGHT) ?
4421 -1.0 : 0.0;
4422 max = (allowedDirections & nsIDOMSimpleGestureEvent::DIRECTION_LEFT) ?
4423 1.0 : 0.0;
4424 }
4426 __block BOOL animationCanceled = NO;
4427 __block BOOL geckoSwipeEventSent = NO;
4428 // At this point, anEvent is the first scroll wheel event in a two-finger
4429 // horizontal gesture that we've decided to treat as a swipe. When we call
4430 // [NSEvent trackSwipeEventWithOptions:...], the OS interprets all
4431 // subsequent scroll wheel events that are part of this gesture as a swipe,
4432 // and stops sending them to us. The OS calls the trackingHandler "block"
4433 // multiple times, asynchronously (sometimes after [NSEvent
4434 // maybeTrackScrollEventAsSwipe:...] has returned). The OS determines when
4435 // the gesture has finished, and whether or not it was "successful" -- this
4436 // information is passed to trackingHandler. We must be careful to only
4437 // call [NSEvent maybeTrackScrollEventAsSwipe:...] on a "real" swipe --
4438 // otherwise two-finger scrolling performance will suffer significantly.
4439 // Note that we use anEvent inside the block. This extends the lifetime of
4440 // the anEvent object because it's retained by the block, see bug 682445.
4441 // The block will release it when the block goes away at the end of the
4442 // animation, or when the animation is canceled.
4443 [anEvent trackSwipeEventWithOptions:NSEventSwipeTrackingLockDirection |
4444 NSEventSwipeTrackingClampGestureAmount
4445 dampenAmountThresholdMin:min
4446 max:max
4447 usingHandler:^(CGFloat gestureAmount,
4448 NSEventPhase phase,
4449 BOOL isComplete,
4450 BOOL *stop) {
4451 uint32_t allowedDirectionsCopy = allowedDirections;
4452 // Since this tracking handler can be called asynchronously, mGeckoChild
4453 // might have become NULL here (our child widget might have been
4454 // destroyed).
4455 // Checking for gestureAmount == 0.0 also works around bug 770626, which
4456 // happens when DispatchWindowEvent() triggers a modal dialog, which spins
4457 // the event loop and confuses the OS. This results in several re-entrant
4458 // calls to this handler.
4459 if (animationCanceled || !mGeckoChild || gestureAmount == 0.0) {
4460 *stop = YES;
4461 animationCanceled = YES;
4462 if (gestureAmount == 0.0 ||
4463 ((direction & vDirs) && (direction != mCurrentSwipeDir))) {
4464 if (mCancelSwipeAnimation)
4465 *mCancelSwipeAnimation = YES;
4466 mCancelSwipeAnimation = nil;
4467 [self sendSwipeEndEvent:anEvent
4468 allowedDirections:allowedDirectionsCopy];
4469 }
4470 mCurrentSwipeDir = 0;
4471 return;
4472 }
4474 // Update animation overlay to match gestureAmount.
4475 [self sendSwipeEvent:anEvent
4476 withKind:NS_SIMPLE_GESTURE_SWIPE_UPDATE
4477 allowedDirections:&allowedDirectionsCopy
4478 direction:0.0
4479 delta:gestureAmount];
4481 if (phase == NSEventPhaseEnded && !geckoSwipeEventSent) {
4482 // The result of the swipe is now known, so the main event can be sent.
4483 // The animation might continue even after this event was sent, so
4484 // don't tear down the animation overlay yet.
4486 uint32_t directionCopy = direction;
4488 // gestureAmount is documented to be '-1', '0' or '1' when isComplete
4489 // is TRUE, but the docs don't say anything about its value at other
4490 // times. However, tests show that, when phase == NSEventPhaseEnded,
4491 // gestureAmount is negative when it will be '-1' at isComplete, and
4492 // positive when it will be '1'. And phase is never equal to
4493 // NSEventPhaseEnded when gestureAmount will be '0' at isComplete.
4494 geckoSwipeEventSent = YES;
4495 [self sendSwipeEvent:anEvent
4496 withKind:NS_SIMPLE_GESTURE_SWIPE
4497 allowedDirections:&allowedDirectionsCopy
4498 direction:directionCopy
4499 delta:0.0];
4500 }
4502 if (isComplete) {
4503 [self sendSwipeEndEvent:anEvent allowedDirections:allowedDirectionsCopy];
4504 mCurrentSwipeDir = 0;
4505 mCancelSwipeAnimation = nil;
4506 }
4507 }];
4509 mCancelSwipeAnimation = &animationCanceled;
4510 }
4511 #endif // #ifdef __LP64__
4513 - (void)setUsingOMTCompositor:(BOOL)aUseOMTC
4514 {
4515 mUsingOMTCompositor = aUseOMTC;
4516 }
4519 // Returning NO from this method only disallows ordering on mousedown - in order
4520 // to prevent it for mouseup too, we need to call [NSApp preventWindowOrdering]
4521 // when handling the mousedown event.
4522 - (BOOL)shouldDelayWindowOrderingForEvent:(NSEvent*)aEvent
4523 {
4524 // Always using system-provided window ordering for normal windows.
4525 if (![[self window] isKindOfClass:[PopupWindow class]])
4526 return NO;
4528 // Don't reorder when we don't have a parent window, like when we're a
4529 // context menu or a tooltip.
4530 return ![[self window] parentWindow];
4531 }
4533 - (void)mouseDown:(NSEvent*)theEvent
4534 {
4535 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
4537 if ([self shouldDelayWindowOrderingForEvent:theEvent]) {
4538 [NSApp preventWindowOrdering];
4539 }
4541 // If we've already seen this event due to direct dispatch from menuForEvent:
4542 // just bail; if not, remember it.
4543 if (mLastMouseDownEvent == theEvent) {
4544 [mLastMouseDownEvent release];
4545 mLastMouseDownEvent = nil;
4546 return;
4547 }
4548 else {
4549 [mLastMouseDownEvent release];
4550 mLastMouseDownEvent = [theEvent retain];
4551 }
4553 [gLastDragMouseDownEvent release];
4554 gLastDragMouseDownEvent = [theEvent retain];
4556 // We need isClickThrough because at this point the window we're in might
4557 // already have become main, so the check for isMainWindow in
4558 // WindowAcceptsEvent isn't enough. It also has to check isClickThrough.
4559 BOOL isClickThrough = (theEvent == mClickThroughMouseDownEvent);
4560 [mClickThroughMouseDownEvent release];
4561 mClickThroughMouseDownEvent = nil;
4563 nsAutoRetainCocoaObject kungFuDeathGrip(self);
4565 if ([self maybeRollup:theEvent] ||
4566 !ChildViewMouseTracker::WindowAcceptsEvent([self window], theEvent, self, isClickThrough)) {
4567 // Remember blocking because that means we want to block mouseup as well.
4568 mBlockedLastMouseDown = YES;
4569 return;
4570 }
4572 #if USE_CLICK_HOLD_CONTEXTMENU
4573 // fire off timer to check for click-hold after two seconds. retains |theEvent|
4574 [self performSelector:@selector(clickHoldCallback:) withObject:theEvent afterDelay:2.0];
4575 #endif
4577 // in order to send gecko events we'll need a gecko widget
4578 if (!mGeckoChild)
4579 return;
4581 NSUInteger modifierFlags = [theEvent modifierFlags];
4583 WidgetMouseEvent geckoEvent(true, NS_MOUSE_BUTTON_DOWN, mGeckoChild,
4584 WidgetMouseEvent::eReal);
4585 [self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent];
4587 NSInteger clickCount = [theEvent clickCount];
4588 if (mBlockedLastMouseDown && clickCount > 1) {
4589 // Don't send a double click if the first click of the double click was
4590 // blocked.
4591 clickCount--;
4592 }
4593 geckoEvent.clickCount = clickCount;
4595 if (modifierFlags & NSControlKeyMask)
4596 geckoEvent.button = WidgetMouseEvent::eRightButton;
4597 else
4598 geckoEvent.button = WidgetMouseEvent::eLeftButton;
4600 // Create event for use by plugins.
4601 // This is going to our child view so we don't need to look up the destination
4602 // event type.
4603 NPCocoaEvent cocoaEvent;
4604 ChildViewMouseTracker::AttachPluginEvent(geckoEvent, self, theEvent,
4605 NPCocoaEventMouseDown,
4606 &cocoaEvent);
4607 // Don't lose possible changes made above to clickCount
4608 cocoaEvent.data.mouse.clickCount = clickCount;
4610 mGeckoChild->DispatchWindowEvent(geckoEvent);
4611 mBlockedLastMouseDown = NO;
4613 // XXX maybe call markedTextSelectionChanged:client: here?
4615 NS_OBJC_END_TRY_ABORT_BLOCK;
4616 }
4618 - (void)mouseUp:(NSEvent *)theEvent
4619 {
4620 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
4622 if (!mGeckoChild || mBlockedLastMouseDown)
4623 return;
4625 nsAutoRetainCocoaObject kungFuDeathGrip(self);
4627 NPCocoaEvent cocoaEvent;
4629 WidgetMouseEvent geckoEvent(true, NS_MOUSE_BUTTON_UP, mGeckoChild,
4630 WidgetMouseEvent::eReal);
4631 [self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent];
4632 if ([theEvent modifierFlags] & NSControlKeyMask)
4633 geckoEvent.button = WidgetMouseEvent::eRightButton;
4634 else
4635 geckoEvent.button = WidgetMouseEvent::eLeftButton;
4637 // Create event for use by plugins.
4638 // This is going to our child view so we don't need to look up the destination
4639 // event type.
4640 ChildViewMouseTracker::AttachPluginEvent(geckoEvent, self, theEvent,
4641 NPCocoaEventMouseUp,
4642 &cocoaEvent);
4644 // This might destroy our widget (and null out mGeckoChild).
4645 bool defaultPrevented = mGeckoChild->DispatchWindowEvent(geckoEvent);
4647 // Check to see if we are double-clicking in the titlebar.
4648 CGFloat locationInTitlebar = [[self window] frame].size.height - [theEvent locationInWindow].y;
4649 if (!defaultPrevented && [theEvent clickCount] == 2 &&
4650 [[self window] isMovableByWindowBackground] &&
4651 [self shouldMinimizeOnTitlebarDoubleClick] &&
4652 [[self window] isKindOfClass:[ToolbarWindow class]] &&
4653 (locationInTitlebar < [(ToolbarWindow*)[self window] titlebarHeight] ||
4654 locationInTitlebar < [(ToolbarWindow*)[self window] unifiedToolbarHeight])) {
4656 NSButton *minimizeButton = [[self window] standardWindowButton:NSWindowMiniaturizeButton];
4657 [minimizeButton performClick:self];
4658 }
4660 // If our mouse-up event's location is over some other object (as might
4661 // happen if it came at the end of a dragging operation), also send our
4662 // Gecko frame a mouse-exit event.
4663 if (mGeckoChild && mIsPluginView) {
4664 ChildViewMouseTracker::ReEvaluateMouseEnterState(theEvent, self);
4665 }
4667 NS_OBJC_END_TRY_ABORT_BLOCK;
4668 }
4670 - (void)sendMouseEnterOrExitEvent:(NSEvent*)aEvent
4671 enter:(BOOL)aEnter
4672 type:(WidgetMouseEvent::exitType)aType
4673 {
4674 if (!mGeckoChild)
4675 return;
4677 NSPoint windowEventLocation = nsCocoaUtils::EventLocationForWindow(aEvent, [self window]);
4678 NSPoint localEventLocation = [self convertPoint:windowEventLocation fromView:nil];
4680 uint32_t msg = aEnter ? NS_MOUSE_ENTER : NS_MOUSE_EXIT;
4681 WidgetMouseEvent event(true, msg, mGeckoChild, WidgetMouseEvent::eReal);
4682 event.refPoint = LayoutDeviceIntPoint::FromUntyped(
4683 mGeckoChild->CocoaPointsToDevPixels(localEventLocation));
4685 // Create event for use by plugins.
4686 // This is going to our child view so we don't need to look up the destination
4687 // event type.
4688 NPCocoaEvent cocoaEvent;
4689 ChildViewMouseTracker::AttachPluginEvent(event, self, aEvent,
4690 (msg == NS_MOUSE_ENTER) ?
4691 NPCocoaEventMouseEntered : NPCocoaEventMouseExited,
4692 &cocoaEvent);
4694 event.exit = aType;
4696 nsEventStatus status; // ignored
4697 mGeckoChild->DispatchEvent(&event, status);
4698 }
4700 - (void)updateWindowDraggableStateOnMouseMove:(NSEvent*)theEvent
4701 {
4702 if (!theEvent || !mGeckoChild) {
4703 return;
4704 }
4706 nsCocoaWindow* windowWidget = mGeckoChild->GetXULWindowWidget();
4707 if (!windowWidget) {
4708 return;
4709 }
4711 // We assume later on that sending a hit test event won't cause widget destruction.
4712 WidgetMouseEvent hitTestEvent(true, NS_MOUSE_MOZHITTEST, mGeckoChild,
4713 WidgetMouseEvent::eReal);
4714 [self convertCocoaMouseEvent:theEvent toGeckoEvent:&hitTestEvent];
4715 bool result = mGeckoChild->DispatchWindowEvent(hitTestEvent);
4717 [windowWidget->GetCocoaWindow() setMovableByWindowBackground:result];
4718 }
4720 - (void)handleMouseMoved:(NSEvent*)theEvent
4721 {
4722 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
4724 if (!mGeckoChild)
4725 return;
4727 WidgetMouseEvent geckoEvent(true, NS_MOUSE_MOVE, mGeckoChild,
4728 WidgetMouseEvent::eReal);
4729 [self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent];
4731 // Create event for use by plugins.
4732 // This is going to our child view so we don't need to look up the destination
4733 // event type.
4734 NPCocoaEvent cocoaEvent;
4735 ChildViewMouseTracker::AttachPluginEvent(geckoEvent, self, theEvent,
4736 NPCocoaEventMouseMoved,
4737 &cocoaEvent);
4738 mGeckoChild->DispatchWindowEvent(geckoEvent);
4740 NS_OBJC_END_TRY_ABORT_BLOCK;
4741 }
4743 - (void)mouseDragged:(NSEvent*)theEvent
4744 {
4745 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
4747 if (!mGeckoChild)
4748 return;
4750 gLastDragView = self;
4752 NPCocoaEvent cocoaEvent;
4754 WidgetMouseEvent geckoEvent(true, NS_MOUSE_MOVE, mGeckoChild,
4755 WidgetMouseEvent::eReal);
4756 [self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent];
4758 // create event for use by plugins
4759 ChildViewMouseTracker::AttachPluginEvent(geckoEvent, self, theEvent,
4760 NPCocoaEventMouseDragged,
4761 &cocoaEvent);
4763 mGeckoChild->DispatchWindowEvent(geckoEvent);
4765 // Note, sending the above event might have destroyed our widget since we didn't retain.
4766 // Fine so long as we don't access any local variables from here on.
4767 gLastDragView = nil;
4769 // XXX maybe call markedTextSelectionChanged:client: here?
4771 NS_OBJC_END_TRY_ABORT_BLOCK;
4772 }
4774 - (void)rightMouseDown:(NSEvent *)theEvent
4775 {
4776 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
4778 nsAutoRetainCocoaObject kungFuDeathGrip(self);
4780 [self maybeRollup:theEvent];
4781 if (!mGeckoChild)
4782 return;
4784 // The right mouse went down, fire off a right mouse down event to gecko
4785 WidgetMouseEvent geckoEvent(true, NS_MOUSE_BUTTON_DOWN, mGeckoChild,
4786 WidgetMouseEvent::eReal);
4787 [self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent];
4788 geckoEvent.button = WidgetMouseEvent::eRightButton;
4789 geckoEvent.clickCount = [theEvent clickCount];
4791 // create event for use by plugins
4792 NPCocoaEvent cocoaEvent;
4793 ChildViewMouseTracker::AttachPluginEvent(geckoEvent, self, theEvent,
4794 NPCocoaEventMouseDown,
4795 &cocoaEvent);
4797 mGeckoChild->DispatchWindowEvent(geckoEvent);
4798 if (!mGeckoChild)
4799 return;
4801 // Let the superclass do the context menu stuff.
4802 [super rightMouseDown:theEvent];
4804 NS_OBJC_END_TRY_ABORT_BLOCK;
4805 }
4807 - (void)rightMouseUp:(NSEvent *)theEvent
4808 {
4809 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
4811 if (!mGeckoChild)
4812 return;
4814 NPCocoaEvent cocoaEvent;
4816 WidgetMouseEvent geckoEvent(true, NS_MOUSE_BUTTON_UP, mGeckoChild,
4817 WidgetMouseEvent::eReal);
4818 [self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent];
4819 geckoEvent.button = WidgetMouseEvent::eRightButton;
4820 geckoEvent.clickCount = [theEvent clickCount];
4822 // create event for use by plugins
4823 ChildViewMouseTracker::AttachPluginEvent(geckoEvent, self, theEvent,
4824 NPCocoaEventMouseUp,
4825 &cocoaEvent);
4827 nsAutoRetainCocoaObject kungFuDeathGrip(self);
4828 mGeckoChild->DispatchWindowEvent(geckoEvent);
4830 // If our mouse-up event's location is over some other object (as might
4831 // happen if it came at the end of a dragging operation), also send our
4832 // Gecko frame a mouse-exit event.
4833 if (mGeckoChild && mIsPluginView) {
4834 ChildViewMouseTracker::ReEvaluateMouseEnterState(theEvent, self);
4835 }
4837 NS_OBJC_END_TRY_ABORT_BLOCK;
4838 }
4840 - (void)rightMouseDragged:(NSEvent*)theEvent
4841 {
4842 if (!mGeckoChild)
4843 return;
4845 WidgetMouseEvent geckoEvent(true, NS_MOUSE_MOVE, mGeckoChild,
4846 WidgetMouseEvent::eReal);
4847 [self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent];
4848 geckoEvent.button = WidgetMouseEvent::eRightButton;
4850 // create event for use by plugins
4851 NPCocoaEvent cocoaEvent;
4852 ChildViewMouseTracker::AttachPluginEvent(geckoEvent, self, theEvent,
4853 NPCocoaEventMouseDragged,
4854 &cocoaEvent);
4856 // send event into Gecko by going directly to the
4857 // the widget.
4858 mGeckoChild->DispatchWindowEvent(geckoEvent);
4859 }
4861 - (void)otherMouseDown:(NSEvent *)theEvent
4862 {
4863 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
4865 nsAutoRetainCocoaObject kungFuDeathGrip(self);
4867 if ([self maybeRollup:theEvent] ||
4868 !ChildViewMouseTracker::WindowAcceptsEvent([self window], theEvent, self))
4869 return;
4871 if (!mGeckoChild)
4872 return;
4874 WidgetMouseEvent geckoEvent(true, NS_MOUSE_BUTTON_DOWN, mGeckoChild,
4875 WidgetMouseEvent::eReal);
4876 [self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent];
4877 geckoEvent.button = WidgetMouseEvent::eMiddleButton;
4878 geckoEvent.clickCount = [theEvent clickCount];
4880 // create event for use by plugins
4881 NPCocoaEvent cocoaEvent;
4882 ChildViewMouseTracker::AttachPluginEvent(geckoEvent, self, theEvent,
4883 NPCocoaEventMouseDown,
4884 &cocoaEvent);
4886 mGeckoChild->DispatchWindowEvent(geckoEvent);
4888 NS_OBJC_END_TRY_ABORT_BLOCK;
4889 }
4891 - (void)otherMouseUp:(NSEvent *)theEvent
4892 {
4893 if (!mGeckoChild)
4894 return;
4896 WidgetMouseEvent geckoEvent(true, NS_MOUSE_BUTTON_UP, mGeckoChild,
4897 WidgetMouseEvent::eReal);
4898 [self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent];
4899 geckoEvent.button = WidgetMouseEvent::eMiddleButton;
4901 // create event for use by plugins
4902 NPCocoaEvent cocoaEvent;
4903 ChildViewMouseTracker::AttachPluginEvent(geckoEvent, self, theEvent,
4904 NPCocoaEventMouseUp,
4905 &cocoaEvent);
4907 nsAutoRetainCocoaObject kungFuDeathGrip(self);
4908 mGeckoChild->DispatchWindowEvent(geckoEvent);
4910 // If our mouse-up event's location is over some other object (as might
4911 // happen if it came at the end of a dragging operation), also send our
4912 // Gecko frame a mouse-exit event.
4913 if (mGeckoChild && mIsPluginView) {
4914 ChildViewMouseTracker::ReEvaluateMouseEnterState(theEvent, self);
4915 }
4916 }
4918 - (void)otherMouseDragged:(NSEvent*)theEvent
4919 {
4920 if (!mGeckoChild)
4921 return;
4923 WidgetMouseEvent geckoEvent(true, NS_MOUSE_MOVE, mGeckoChild,
4924 WidgetMouseEvent::eReal);
4925 [self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent];
4926 geckoEvent.button = WidgetMouseEvent::eMiddleButton;
4928 // create event for use by plugins
4929 NPCocoaEvent cocoaEvent;
4930 ChildViewMouseTracker::AttachPluginEvent(geckoEvent, self, theEvent,
4931 NPCocoaEventMouseDragged,
4932 &cocoaEvent);
4934 // send event into Gecko by going directly to the
4935 // the widget.
4936 mGeckoChild->DispatchWindowEvent(geckoEvent);
4937 }
4939 static int32_t RoundUp(double aDouble)
4940 {
4941 return aDouble < 0 ? static_cast<int32_t>(floor(aDouble)) :
4942 static_cast<int32_t>(ceil(aDouble));
4943 }
4945 - (void)sendWheelStartOrStop:(uint32_t)msg forEvent:(NSEvent *)theEvent
4946 {
4947 WidgetWheelEvent wheelEvent(true, msg, mGeckoChild);
4948 [self convertCocoaMouseWheelEvent:theEvent toGeckoEvent:&wheelEvent];
4949 mExpectingWheelStop = (msg == NS_WHEEL_START);
4950 mGeckoChild->DispatchWindowEvent(wheelEvent);
4951 }
4953 - (void)sendWheelCondition:(BOOL)condition first:(uint32_t)first second:(uint32_t)second forEvent:(NSEvent *)theEvent
4954 {
4955 if (mExpectingWheelStop == condition) {
4956 [self sendWheelStartOrStop:first forEvent:theEvent];
4957 }
4958 [self sendWheelStartOrStop:second forEvent:theEvent];
4959 }
4961 - (void)scrollWheel:(NSEvent*)theEvent
4962 {
4963 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
4965 nsAutoRetainCocoaObject kungFuDeathGrip(self);
4967 ChildViewMouseTracker::MouseScrolled(theEvent);
4969 if ([self maybeRollup:theEvent]) {
4970 return;
4971 }
4973 if (!mGeckoChild) {
4974 return;
4975 }
4977 if (nsCocoaFeatures::OnLionOrLater()) {
4978 NSEventPhase phase = [theEvent phase];
4979 // Fire NS_WHEEL_START/STOP events when 2 fingers touch/release the touchpad.
4980 if (phase & NSEventPhaseMayBegin) {
4981 [self sendWheelCondition:YES first:NS_WHEEL_STOP second:NS_WHEEL_START forEvent:theEvent];
4982 return;
4983 }
4985 if (phase & (NSEventPhaseEnded | NSEventPhaseCancelled)) {
4986 [self sendWheelCondition:NO first:NS_WHEEL_START second:NS_WHEEL_STOP forEvent:theEvent];
4987 return;
4988 }
4989 }
4991 WidgetWheelEvent wheelEvent(true, NS_WHEEL_WHEEL, mGeckoChild);
4992 [self convertCocoaMouseWheelEvent:theEvent toGeckoEvent:&wheelEvent];
4994 wheelEvent.lineOrPageDeltaX = RoundUp(-[theEvent deltaX]);
4995 wheelEvent.lineOrPageDeltaY = RoundUp(-[theEvent deltaY]);
4997 if (wheelEvent.deltaMode == nsIDOMWheelEvent::DOM_DELTA_PIXEL) {
4998 // Some scrolling devices supports pixel scrolling, e.g. a Macbook
4999 // touchpad or a Mighty Mouse. On those devices, [theEvent deviceDeltaX/Y]
5000 // contains the amount of pixels to scroll. Since Lion this has changed
5001 // to [theEvent scrollingDeltaX/Y].
5002 double scale = mGeckoChild->BackingScaleFactor();
5003 if ([theEvent respondsToSelector:@selector(scrollingDeltaX)]) {
5004 wheelEvent.deltaX = -[theEvent scrollingDeltaX] * scale;
5005 wheelEvent.deltaY = -[theEvent scrollingDeltaY] * scale;
5006 } else {
5007 wheelEvent.deltaX = -[theEvent deviceDeltaX] * scale;
5008 wheelEvent.deltaY = -[theEvent deviceDeltaY] * scale;
5009 }
5010 } else {
5011 wheelEvent.deltaX = -[theEvent deltaX];
5012 wheelEvent.deltaY = -[theEvent deltaY];
5013 }
5015 // TODO: We should not set deltaZ for now because we're not sure if we should
5016 // revert the sign.
5017 // wheelEvent.deltaZ = [theEvent deltaZ];
5019 if (!wheelEvent.deltaX && !wheelEvent.deltaY && !wheelEvent.deltaZ) {
5020 // No sense in firing off a Gecko event.
5021 return;
5022 }
5024 NPCocoaEvent cocoaEvent;
5025 ChildViewMouseTracker::AttachPluginEvent(wheelEvent, self, theEvent,
5026 NPCocoaEventScrollWheel,
5027 &cocoaEvent);
5029 mGeckoChild->DispatchWindowEvent(wheelEvent);
5030 if (!mGeckoChild) {
5031 return;
5032 }
5034 #ifdef __LP64__
5035 // overflowDeltaX and overflowDeltaY tell us when the user has tried to
5036 // scroll past the edge of a page (in those cases it's non-zero).
5037 if ((wheelEvent.deltaMode == nsIDOMWheelEvent::DOM_DELTA_PIXEL) &&
5038 (wheelEvent.deltaX != 0.0 || wheelEvent.deltaY != 0.0)) {
5039 [self maybeTrackScrollEventAsSwipe:theEvent
5040 scrollOverflowX:wheelEvent.overflowDeltaX
5041 scrollOverflowY:wheelEvent.overflowDeltaY
5042 viewPortIsOverscrolled:wheelEvent.mViewPortIsOverscrolled];
5043 }
5044 #endif // #ifdef __LP64__
5046 NS_OBJC_END_TRY_ABORT_BLOCK;
5047 }
5049 -(NSMenu*)menuForEvent:(NSEvent*)theEvent
5050 {
5051 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
5053 if (!mGeckoChild || [self isPluginView])
5054 return nil;
5056 nsAutoRetainCocoaObject kungFuDeathGrip(self);
5058 [self maybeRollup:theEvent];
5059 if (!mGeckoChild)
5060 return nil;
5062 // Cocoa doesn't always dispatch a mouseDown: for a control-click event,
5063 // depends on what we return from menuForEvent:. Gecko always expects one
5064 // and expects the mouse down event before the context menu event, so
5065 // get that event sent first if this is a left mouse click.
5066 if ([theEvent type] == NSLeftMouseDown) {
5067 [self mouseDown:theEvent];
5068 if (!mGeckoChild)
5069 return nil;
5070 }
5072 WidgetMouseEvent geckoEvent(true, NS_CONTEXTMENU, mGeckoChild,
5073 WidgetMouseEvent::eReal);
5074 [self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent];
5075 geckoEvent.button = WidgetMouseEvent::eRightButton;
5076 mGeckoChild->DispatchWindowEvent(geckoEvent);
5077 if (!mGeckoChild)
5078 return nil;
5080 [self maybeInitContextMenuTracking];
5082 // Go up our view chain to fetch the correct menu to return.
5083 return [self contextMenu];
5085 NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
5086 }
5088 - (NSMenu*)contextMenu
5089 {
5090 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
5092 NSView* superView = [self superview];
5093 if ([superView respondsToSelector:@selector(contextMenu)])
5094 return [(NSView<mozView>*)superView contextMenu];
5096 return nil;
5098 NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
5099 }
5101 - (void) convertCocoaMouseWheelEvent:(NSEvent*)aMouseEvent
5102 toGeckoEvent:(WidgetWheelEvent*)outWheelEvent
5103 {
5104 [self convertCocoaMouseEvent:aMouseEvent toGeckoEvent:outWheelEvent];
5105 outWheelEvent->deltaMode =
5106 Preferences::GetBool("mousewheel.enable_pixel_scrolling", true) ?
5107 nsIDOMWheelEvent::DOM_DELTA_PIXEL : nsIDOMWheelEvent::DOM_DELTA_LINE;
5109 // Calling deviceDeltaX or deviceDeltaY on theEvent will trigger a Cocoa
5110 // assertion and an Objective-C NSInternalInconsistencyException if the
5111 // underlying "Carbon" event doesn't contain pixel scrolling information.
5112 // For these events, carbonEventKind is kEventMouseWheelMoved instead of
5113 // kEventMouseScroll.
5114 if (outWheelEvent->deltaMode == nsIDOMWheelEvent::DOM_DELTA_PIXEL) {
5115 EventRef theCarbonEvent = [aMouseEvent _eventRef];
5116 UInt32 carbonEventKind = theCarbonEvent ? ::GetEventKind(theCarbonEvent) : 0;
5117 if (carbonEventKind != kEventMouseScroll) {
5118 outWheelEvent->deltaMode = nsIDOMWheelEvent::DOM_DELTA_LINE;
5119 }
5120 }
5121 outWheelEvent->isMomentum = nsCocoaUtils::IsMomentumScrollEvent(aMouseEvent);
5122 }
5124 - (void) convertCocoaMouseEvent:(NSEvent*)aMouseEvent
5125 toGeckoEvent:(WidgetInputEvent*)outGeckoEvent
5126 {
5127 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
5129 NS_ASSERTION(outGeckoEvent, "convertCocoaMouseEvent:toGeckoEvent: requires non-null aoutGeckoEvent");
5130 if (!outGeckoEvent)
5131 return;
5133 nsCocoaUtils::InitInputEvent(*outGeckoEvent, aMouseEvent);
5135 // convert point to view coordinate system
5136 NSPoint locationInWindow = nsCocoaUtils::EventLocationForWindow(aMouseEvent, [self window]);
5137 NSPoint localPoint = [self convertPoint:locationInWindow fromView:nil];
5139 outGeckoEvent->refPoint = LayoutDeviceIntPoint::FromUntyped(
5140 mGeckoChild->CocoaPointsToDevPixels(localPoint));
5142 WidgetMouseEventBase* mouseEvent = outGeckoEvent->AsMouseEventBase();
5143 mouseEvent->buttons = 0;
5144 NSUInteger mouseButtons = [NSEvent pressedMouseButtons];
5146 if (mouseButtons & 0x01) {
5147 mouseEvent->buttons |= WidgetMouseEvent::eLeftButtonFlag;
5148 }
5149 if (mouseButtons & 0x02) {
5150 mouseEvent->buttons |= WidgetMouseEvent::eRightButtonFlag;
5151 }
5152 if (mouseButtons & 0x04) {
5153 mouseEvent->buttons |= WidgetMouseEvent::eMiddleButtonFlag;
5154 }
5155 if (mouseButtons & 0x08) {
5156 mouseEvent->buttons |= WidgetMouseEvent::e4thButtonFlag;
5157 }
5158 if (mouseButtons & 0x10) {
5159 mouseEvent->buttons |= WidgetMouseEvent::e5thButtonFlag;
5160 }
5162 switch ([aMouseEvent type]) {
5163 case NSLeftMouseDown:
5164 case NSLeftMouseUp:
5165 case NSLeftMouseDragged:
5166 case NSRightMouseDown:
5167 case NSRightMouseUp:
5168 case NSRightMouseDragged:
5169 case NSOtherMouseDown:
5170 case NSOtherMouseUp:
5171 case NSOtherMouseDragged:
5172 if ([aMouseEvent subtype] == NSTabletPointEventSubtype) {
5173 mouseEvent->pressure = [aMouseEvent pressure];
5174 }
5175 break;
5176 }
5178 NS_OBJC_END_TRY_ABORT_BLOCK;
5179 }
5182 #pragma mark -
5183 // NSTextInput implementation
5185 - (void)insertText:(id)insertString
5186 {
5187 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
5189 NS_ENSURE_TRUE_VOID(mGeckoChild);
5191 nsAutoRetainCocoaObject kungFuDeathGrip(self);
5193 NSAttributedString* attrStr;
5194 if ([insertString isKindOfClass:[NSAttributedString class]]) {
5195 attrStr = static_cast<NSAttributedString*>(insertString);
5196 } else {
5197 attrStr =
5198 [[[NSAttributedString alloc] initWithString:insertString] autorelease];
5199 }
5201 mTextInputHandler->InsertText(attrStr);
5203 NS_OBJC_END_TRY_ABORT_BLOCK;
5204 }
5206 - (void)insertNewline:(id)sender
5207 {
5208 [self insertText:@"\n"];
5209 }
5211 - (void) doCommandBySelector:(SEL)aSelector
5212 {
5213 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
5215 if (!mGeckoChild || !mTextInputHandler) {
5216 return;
5217 }
5219 const char* sel = reinterpret_cast<const char*>(aSelector);
5220 if (!mTextInputHandler->DoCommandBySelector(sel)) {
5221 [super doCommandBySelector:aSelector];
5222 }
5224 NS_OBJC_END_TRY_ABORT_BLOCK;
5225 }
5227 - (void) setMarkedText:(id)aString selectedRange:(NSRange)selRange
5228 {
5229 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
5231 NS_ENSURE_TRUE_VOID(mTextInputHandler);
5233 nsAutoRetainCocoaObject kungFuDeathGrip(self);
5235 NSAttributedString* attrStr;
5236 if ([aString isKindOfClass:[NSAttributedString class]]) {
5237 attrStr = static_cast<NSAttributedString*>(aString);
5238 } else {
5239 attrStr = [[[NSAttributedString alloc] initWithString:aString] autorelease];
5240 }
5242 mTextInputHandler->SetMarkedText(attrStr, selRange);
5244 NS_OBJC_END_TRY_ABORT_BLOCK;
5245 }
5247 - (void) unmarkText
5248 {
5249 NS_ENSURE_TRUE(mTextInputHandler, );
5250 mTextInputHandler->CommitIMEComposition();
5251 }
5253 - (BOOL) hasMarkedText
5254 {
5255 NS_ENSURE_TRUE(mTextInputHandler, NO);
5256 return mTextInputHandler->HasMarkedText();
5257 }
5259 - (BOOL)shouldMinimizeOnTitlebarDoubleClick
5260 {
5261 NSString *MDAppleMiniaturizeOnDoubleClickKey =
5262 @"AppleMiniaturizeOnDoubleClick";
5263 NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
5264 bool shouldMinimize = [[userDefaults
5265 objectForKey:MDAppleMiniaturizeOnDoubleClickKey] boolValue];
5267 return shouldMinimize;
5268 }
5270 - (NSInteger) conversationIdentifier
5271 {
5272 NS_ENSURE_TRUE(mTextInputHandler, reinterpret_cast<NSInteger>(self));
5273 return mTextInputHandler->ConversationIdentifier();
5274 }
5276 - (NSAttributedString *) attributedSubstringFromRange:(NSRange)theRange
5277 {
5278 NS_ENSURE_TRUE(mTextInputHandler, nil);
5279 return mTextInputHandler->GetAttributedSubstringFromRange(theRange);
5280 }
5282 - (NSRange) markedRange
5283 {
5284 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
5286 NS_ENSURE_TRUE(mTextInputHandler, NSMakeRange(NSNotFound, 0));
5287 return mTextInputHandler->MarkedRange();
5289 NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NSMakeRange(0, 0));
5290 }
5292 - (NSRange) selectedRange
5293 {
5294 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
5296 NS_ENSURE_TRUE(mTextInputHandler, NSMakeRange(NSNotFound, 0));
5297 return mTextInputHandler->SelectedRange();
5299 NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NSMakeRange(0, 0));
5300 }
5302 - (NSRect) firstRectForCharacterRange:(NSRange)theRange
5303 {
5304 NSRect rect;
5305 NS_ENSURE_TRUE(mTextInputHandler, rect);
5306 return mTextInputHandler->FirstRectForCharacterRange(theRange);
5307 }
5309 - (NSUInteger)characterIndexForPoint:(NSPoint)thePoint
5310 {
5311 NS_ENSURE_TRUE(mTextInputHandler, 0);
5312 return mTextInputHandler->CharacterIndexForPoint(thePoint);
5313 }
5315 - (NSArray*) validAttributesForMarkedText
5316 {
5317 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
5319 NS_ENSURE_TRUE(mTextInputHandler, [NSArray array]);
5320 return mTextInputHandler->GetValidAttributesForMarkedText();
5322 NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
5323 }
5325 #pragma mark -
5326 // NSTextInputClient implementation
5328 - (void)insertText:(id)aString replacementRange:(NSRange)replacementRange
5329 {
5330 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
5332 NS_ENSURE_TRUE_VOID(mGeckoChild);
5334 nsAutoRetainCocoaObject kungFuDeathGrip(self);
5336 NSAttributedString* attrStr;
5337 if ([aString isKindOfClass:[NSAttributedString class]]) {
5338 attrStr = static_cast<NSAttributedString*>(aString);
5339 } else {
5340 attrStr = [[[NSAttributedString alloc] initWithString:aString] autorelease];
5341 }
5343 mTextInputHandler->InsertText(attrStr, &replacementRange);
5345 NS_OBJC_END_TRY_ABORT_BLOCK;
5346 }
5348 - (void)setMarkedText:(id)aString selectedRange:(NSRange)selectedRange
5349 replacementRange:(NSRange)replacementRange
5350 {
5351 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
5353 NS_ENSURE_TRUE_VOID(mTextInputHandler);
5355 nsAutoRetainCocoaObject kungFuDeathGrip(self);
5357 NSAttributedString* attrStr;
5358 if ([aString isKindOfClass:[NSAttributedString class]]) {
5359 attrStr = static_cast<NSAttributedString*>(aString);
5360 } else {
5361 attrStr = [[[NSAttributedString alloc] initWithString:aString] autorelease];
5362 }
5364 mTextInputHandler->SetMarkedText(attrStr, selectedRange, &replacementRange);
5366 NS_OBJC_END_TRY_ABORT_BLOCK;
5367 }
5369 - (NSAttributedString*)attributedSubstringForProposedRange:(NSRange)aRange
5370 actualRange:(NSRangePointer)actualRange
5371 {
5372 NS_ENSURE_TRUE(mTextInputHandler, nil);
5373 return mTextInputHandler->GetAttributedSubstringFromRange(aRange,
5374 actualRange);
5375 }
5377 - (NSRect)firstRectForCharacterRange:(NSRange)aRange
5378 actualRange:(NSRangePointer)actualRange
5379 {
5380 NS_ENSURE_TRUE(mTextInputHandler, NSMakeRect(0.0, 0.0, 0.0, 0.0));
5381 return mTextInputHandler->FirstRectForCharacterRange(aRange, actualRange);
5382 }
5384 - (NSInteger)windowLevel
5385 {
5386 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
5388 NS_ENSURE_TRUE(mTextInputHandler, [[self window] level]);
5389 return mTextInputHandler->GetWindowLevel();
5391 NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NSNormalWindowLevel);
5392 }
5394 #pragma mark -
5396 #ifdef __LP64__
5397 - (NSTextInputContext *)inputContext
5398 {
5399 if (mIsPluginView && mPluginEventModel == NPEventModelCocoa)
5400 return [[ComplexTextInputPanel sharedComplexTextInputPanel] inputContext];
5401 else
5402 return [super inputContext];
5403 }
5404 #endif
5406 // This is a private API that Cocoa uses.
5407 // Cocoa will call this after the menu system returns "NO" for "performKeyEquivalent:".
5408 // We want all they key events we can get so just return YES. In particular, this fixes
5409 // ctrl-tab - we don't get a "keyDown:" call for that without this.
5410 - (BOOL)_wantsKeyDownForEvent:(NSEvent*)event
5411 {
5412 return YES;
5413 }
5415 - (void)keyDown:(NSEvent*)theEvent
5416 {
5417 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
5419 #if !defined(RELEASE_BUILD) || defined(DEBUG)
5420 if (mGeckoChild && mTextInputHandler && mTextInputHandler->IsFocused()) {
5421 #ifdef MOZ_CRASHREPORTER
5422 NSWindow* window = [self window];
5423 NSString* info = [NSString stringWithFormat:@"\nview [%@], window [%@], key event [%@], window is key %i, is fullscreen %i, app is active %i",
5424 self, window, theEvent, [window isKeyWindow], ([window styleMask] & (1 << 14)) != 0,
5425 [NSApp isActive]];
5426 nsAutoCString additionalInfo([info UTF8String]);
5427 #endif
5428 if (mIsPluginView) {
5429 if (TextInputHandler::IsSecureEventInputEnabled()) {
5430 #define CRASH_MESSAGE "While a plugin has focus, we must not be in secure mode"
5431 #ifdef MOZ_CRASHREPORTER
5432 CrashReporter::AppendAppNotesToCrashReport(NS_LITERAL_CSTRING("\nBug 893973: ") +
5433 NS_LITERAL_CSTRING(CRASH_MESSAGE));
5434 CrashReporter::AppendAppNotesToCrashReport(additionalInfo);
5435 #endif
5436 MOZ_CRASH(CRASH_MESSAGE);
5437 #undef CRASH_MESSAGE
5438 }
5439 } else if (mGeckoChild->GetInputContext().IsPasswordEditor() &&
5440 !TextInputHandler::IsSecureEventInputEnabled()) {
5441 #define CRASH_MESSAGE "A password editor has focus, but not in secure input mode"
5442 #ifdef MOZ_CRASHREPORTER
5443 CrashReporter::AppendAppNotesToCrashReport(NS_LITERAL_CSTRING("\nBug 893973: ") +
5444 NS_LITERAL_CSTRING(CRASH_MESSAGE));
5445 CrashReporter::AppendAppNotesToCrashReport(additionalInfo);
5446 #endif
5447 MOZ_CRASH(CRASH_MESSAGE);
5448 #undef CRASH_MESSAGE
5449 } else if (!mGeckoChild->GetInputContext().IsPasswordEditor() &&
5450 TextInputHandler::IsSecureEventInputEnabled()) {
5451 #define CRASH_MESSAGE "A non-password editor has focus, but in secure input mode"
5452 #ifdef MOZ_CRASHREPORTER
5453 CrashReporter::AppendAppNotesToCrashReport(NS_LITERAL_CSTRING("\nBug 893973: ") +
5454 NS_LITERAL_CSTRING(CRASH_MESSAGE));
5455 CrashReporter::AppendAppNotesToCrashReport(additionalInfo);
5456 #endif
5457 MOZ_CRASH(CRASH_MESSAGE);
5458 #undef CRASH_MESSAGE
5459 }
5460 }
5461 #endif // #if !defined(RELEASE_BUILD) || defined(DEBUG)
5463 if (mGeckoChild && mTextInputHandler && mIsPluginView) {
5464 mTextInputHandler->HandleKeyDownEventForPlugin(theEvent);
5465 return;
5466 }
5468 nsAutoRetainCocoaObject kungFuDeathGrip(self);
5469 bool handled = false;
5470 if (mGeckoChild && mTextInputHandler) {
5471 handled = mTextInputHandler->HandleKeyDownEvent(theEvent);
5472 }
5474 // We always allow keyboard events to propagate to keyDown: but if they are not
5475 // handled we give special Application menu items a chance to act.
5476 if (!handled && sApplicationMenu) {
5477 [sApplicationMenu performKeyEquivalent:theEvent];
5478 }
5480 NS_OBJC_END_TRY_ABORT_BLOCK;
5481 }
5483 - (void)keyUp:(NSEvent*)theEvent
5484 {
5485 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
5487 NS_ENSURE_TRUE(mGeckoChild, );
5489 nsAutoRetainCocoaObject kungFuDeathGrip(self);
5491 if (mIsPluginView) {
5492 mTextInputHandler->HandleKeyUpEventForPlugin(theEvent);
5493 return;
5494 }
5496 mTextInputHandler->HandleKeyUpEvent(theEvent);
5498 NS_OBJC_END_TRY_ABORT_BLOCK;
5499 }
5501 - (void)flagsChanged:(NSEvent*)theEvent
5502 {
5503 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
5505 NS_ENSURE_TRUE(mGeckoChild, );
5507 nsAutoRetainCocoaObject kungFuDeathGrip(self);
5508 mTextInputHandler->HandleFlagsChanged(theEvent);
5510 NS_OBJC_END_TRY_ABORT_BLOCK;
5511 }
5513 - (BOOL) isFirstResponder
5514 {
5515 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
5517 NSResponder* resp = [[self window] firstResponder];
5518 return (resp == (NSResponder*)self);
5520 NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO);
5521 }
5523 - (BOOL)isDragInProgress
5524 {
5525 if (!mDragService)
5526 return NO;
5528 nsCOMPtr<nsIDragSession> dragSession;
5529 mDragService->GetCurrentSession(getter_AddRefs(dragSession));
5530 return dragSession != nullptr;
5531 }
5533 - (BOOL)inactiveWindowAcceptsMouseEvent:(NSEvent*)aEvent
5534 {
5535 // If we're being destroyed assume the default -- return YES.
5536 if (!mGeckoChild)
5537 return YES;
5539 WidgetMouseEvent geckoEvent(true, NS_MOUSE_ACTIVATE, mGeckoChild,
5540 WidgetMouseEvent::eReal);
5541 [self convertCocoaMouseEvent:aEvent toGeckoEvent:&geckoEvent];
5542 return !mGeckoChild->DispatchWindowEvent(geckoEvent);
5543 }
5545 // Returns NO if the plugin shouldn't be focused/unfocused.
5546 - (BOOL)updatePluginFocusStatus:(BOOL)getFocus
5547 {
5548 if (!mGeckoChild)
5549 return NO;
5551 if (mPluginEventModel == NPEventModelCocoa) {
5552 WidgetPluginEvent pluginEvent(true, NS_PLUGIN_FOCUS_EVENT, mGeckoChild);
5553 NPCocoaEvent cocoaEvent;
5554 nsCocoaUtils::InitNPCocoaEvent(&cocoaEvent);
5555 cocoaEvent.type = NPCocoaEventFocusChanged;
5556 cocoaEvent.data.focus.hasFocus = getFocus;
5557 nsCocoaUtils::InitPluginEvent(pluginEvent, cocoaEvent);
5558 mGeckoChild->DispatchWindowEvent(pluginEvent);
5560 if (getFocus)
5561 [self sendFocusEvent:NS_PLUGIN_FOCUS];
5562 }
5564 return YES;
5565 }
5567 // We must always call through to our superclass, even when mGeckoChild is
5568 // nil -- otherwise the keyboard focus can end up in the wrong NSView.
5569 - (BOOL)becomeFirstResponder
5570 {
5571 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
5573 if (mIsPluginView) {
5574 if (![self updatePluginFocusStatus:YES])
5575 return NO;
5576 }
5578 return [super becomeFirstResponder];
5580 NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(YES);
5581 }
5583 // We must always call through to our superclass, even when mGeckoChild is
5584 // nil -- otherwise the keyboard focus can end up in the wrong NSView.
5585 - (BOOL)resignFirstResponder
5586 {
5587 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
5589 if (mIsPluginView) {
5590 if (![self updatePluginFocusStatus:NO])
5591 return NO;
5592 }
5594 return [super resignFirstResponder];
5596 NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(YES);
5597 }
5599 - (void)viewsWindowDidBecomeKey
5600 {
5601 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
5603 if (!mGeckoChild)
5604 return;
5606 nsAutoRetainCocoaObject kungFuDeathGrip(self);
5608 // check to see if the window implements the mozWindow protocol. This
5609 // allows embedders to avoid re-entrant calls to -makeKeyAndOrderFront,
5610 // which can happen because these activate calls propagate out
5611 // to the embedder via nsIEmbeddingSiteWindow::SetFocus().
5612 BOOL isMozWindow = [[self window] respondsToSelector:@selector(setSuppressMakeKeyFront:)];
5613 if (isMozWindow)
5614 [[self window] setSuppressMakeKeyFront:YES];
5616 nsIWidgetListener* listener = mGeckoChild->GetWidgetListener();
5617 if (listener)
5618 listener->WindowActivated();
5620 if (isMozWindow)
5621 [[self window] setSuppressMakeKeyFront:NO];
5623 NS_OBJC_END_TRY_ABORT_BLOCK;
5624 }
5626 - (void)viewsWindowDidResignKey
5627 {
5628 if (!mGeckoChild)
5629 return;
5631 nsAutoRetainCocoaObject kungFuDeathGrip(self);
5633 nsIWidgetListener* listener = mGeckoChild->GetWidgetListener();
5634 if (listener)
5635 listener->WindowDeactivated();
5636 }
5638 // If the call to removeFromSuperview isn't delayed from nsChildView::
5639 // TearDownView(), the NSView hierarchy might get changed during calls to
5640 // [ChildView drawRect:], which leads to "beyond bounds" exceptions in
5641 // NSCFArray. For more info see bmo bug 373122. Apple's docs claim that
5642 // removeFromSuperviewWithoutNeedingDisplay "can be safely invoked during
5643 // display" (whatever "display" means). But it's _not_ true that it can be
5644 // safely invoked during calls to [NSView drawRect:]. We use
5645 // removeFromSuperview here because there's no longer any danger of being
5646 // "invoked during display", and because doing do clears up bmo bug 384343.
5647 - (void)delayedTearDown
5648 {
5649 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
5651 [self removeFromSuperview];
5652 [self release];
5654 NS_OBJC_END_TRY_ABORT_BLOCK;
5655 }
5657 #pragma mark -
5659 // drag'n'drop stuff
5660 #define kDragServiceContractID "@mozilla.org/widget/dragservice;1"
5662 - (NSDragOperation)dragOperationForSession:(nsIDragSession*)aDragSession
5663 {
5664 uint32_t dragAction;
5665 aDragSession->GetDragAction(&dragAction);
5666 if (nsIDragService::DRAGDROP_ACTION_LINK & dragAction)
5667 return NSDragOperationLink;
5668 if (nsIDragService::DRAGDROP_ACTION_COPY & dragAction)
5669 return NSDragOperationCopy;
5670 if (nsIDragService::DRAGDROP_ACTION_MOVE & dragAction)
5671 return NSDragOperationGeneric;
5672 return NSDragOperationNone;
5673 }
5675 // This is a utility function used by NSView drag event methods
5676 // to send events. It contains all of the logic needed for Gecko
5677 // dragging to work. Returns the appropriate cocoa drag operation code.
5678 - (NSDragOperation)doDragAction:(uint32_t)aMessage sender:(id)aSender
5679 {
5680 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
5682 if (!mGeckoChild)
5683 return NSDragOperationNone;
5685 PR_LOG(sCocoaLog, PR_LOG_ALWAYS, ("ChildView doDragAction: entered\n"));
5687 if (!mDragService) {
5688 CallGetService(kDragServiceContractID, &mDragService);
5689 NS_ASSERTION(mDragService, "Couldn't get a drag service - big problem!");
5690 if (!mDragService)
5691 return NSDragOperationNone;
5692 }
5694 if (aMessage == NS_DRAGDROP_ENTER)
5695 mDragService->StartDragSession();
5697 nsCOMPtr<nsIDragSession> dragSession;
5698 mDragService->GetCurrentSession(getter_AddRefs(dragSession));
5699 if (dragSession) {
5700 if (aMessage == NS_DRAGDROP_OVER) {
5701 // fire the drag event at the source. Just ignore whether it was
5702 // cancelled or not as there isn't actually a means to stop the drag
5703 mDragService->FireDragEventAtSource(NS_DRAGDROP_DRAG);
5704 dragSession->SetCanDrop(false);
5705 }
5706 else if (aMessage == NS_DRAGDROP_DROP) {
5707 // We make the assumption that the dragOver handlers have correctly set
5708 // the |canDrop| property of the Drag Session.
5709 bool canDrop = false;
5710 if (!NS_SUCCEEDED(dragSession->GetCanDrop(&canDrop)) || !canDrop) {
5711 [self doDragAction:NS_DRAGDROP_EXIT sender:aSender];
5713 nsCOMPtr<nsIDOMNode> sourceNode;
5714 dragSession->GetSourceNode(getter_AddRefs(sourceNode));
5715 if (!sourceNode) {
5716 mDragService->EndDragSession(false);
5717 }
5718 return NSDragOperationNone;
5719 }
5720 }
5722 unsigned int modifierFlags = [[NSApp currentEvent] modifierFlags];
5723 uint32_t action = nsIDragService::DRAGDROP_ACTION_MOVE;
5724 // force copy = option, alias = cmd-option, default is move
5725 if (modifierFlags & NSAlternateKeyMask) {
5726 if (modifierFlags & NSCommandKeyMask)
5727 action = nsIDragService::DRAGDROP_ACTION_LINK;
5728 else
5729 action = nsIDragService::DRAGDROP_ACTION_COPY;
5730 }
5731 dragSession->SetDragAction(action);
5732 }
5734 // set up gecko event
5735 WidgetDragEvent geckoEvent(true, aMessage, mGeckoChild);
5736 nsCocoaUtils::InitInputEvent(geckoEvent, [NSApp currentEvent]);
5738 // Use our own coordinates in the gecko event.
5739 // Convert event from gecko global coords to gecko view coords.
5740 NSPoint draggingLoc = [aSender draggingLocation];
5741 NSPoint localPoint = [self convertPoint:draggingLoc fromView:nil];
5743 geckoEvent.refPoint = LayoutDeviceIntPoint::FromUntyped(
5744 mGeckoChild->CocoaPointsToDevPixels(localPoint));
5746 nsAutoRetainCocoaObject kungFuDeathGrip(self);
5747 mGeckoChild->DispatchWindowEvent(geckoEvent);
5748 if (!mGeckoChild)
5749 return NSDragOperationNone;
5751 if (dragSession) {
5752 switch (aMessage) {
5753 case NS_DRAGDROP_ENTER:
5754 case NS_DRAGDROP_OVER:
5755 return [self dragOperationForSession:dragSession];
5756 case NS_DRAGDROP_EXIT:
5757 case NS_DRAGDROP_DROP: {
5758 nsCOMPtr<nsIDOMNode> sourceNode;
5759 dragSession->GetSourceNode(getter_AddRefs(sourceNode));
5760 if (!sourceNode) {
5761 // We're leaving a window while doing a drag that was
5762 // initiated in a different app. End the drag session,
5763 // since we're done with it for now (until the user
5764 // drags back into mozilla).
5765 mDragService->EndDragSession(false);
5766 }
5767 }
5768 }
5769 }
5771 return NSDragOperationGeneric;
5773 NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NSDragOperationNone);
5774 }
5776 - (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
5777 {
5778 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
5780 PR_LOG(sCocoaLog, PR_LOG_ALWAYS, ("ChildView draggingEntered: entered\n"));
5782 // there should never be a globalDragPboard when "draggingEntered:" is
5783 // called, but just in case we'll take care of it here.
5784 [globalDragPboard release];
5786 // Set the global drag pasteboard that will be used for this drag session.
5787 // This will be set back to nil when the drag session ends (mouse exits
5788 // the view or a drop happens within the view).
5789 globalDragPboard = [[sender draggingPasteboard] retain];
5791 return [self doDragAction:NS_DRAGDROP_ENTER sender:sender];
5793 NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NSDragOperationNone);
5794 }
5796 - (NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender
5797 {
5798 PR_LOG(sCocoaLog, PR_LOG_ALWAYS, ("ChildView draggingUpdated: entered\n"));
5800 return [self doDragAction:NS_DRAGDROP_OVER sender:sender];
5801 }
5803 - (void)draggingExited:(id <NSDraggingInfo>)sender
5804 {
5805 PR_LOG(sCocoaLog, PR_LOG_ALWAYS, ("ChildView draggingExited: entered\n"));
5807 nsAutoRetainCocoaObject kungFuDeathGrip(self);
5808 [self doDragAction:NS_DRAGDROP_EXIT sender:sender];
5809 NS_IF_RELEASE(mDragService);
5810 }
5812 - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
5813 {
5814 nsAutoRetainCocoaObject kungFuDeathGrip(self);
5815 BOOL handled = [self doDragAction:NS_DRAGDROP_DROP sender:sender] != NSDragOperationNone;
5816 NS_IF_RELEASE(mDragService);
5817 return handled;
5818 }
5820 // NSDraggingSource
5821 - (void)draggedImage:(NSImage *)anImage movedTo:(NSPoint)aPoint
5822 {
5823 // Get the drag service if it isn't already cached. The drag service
5824 // isn't cached when dragging over a different application.
5825 nsCOMPtr<nsIDragService> dragService = mDragService;
5826 if (!dragService) {
5827 dragService = do_GetService(kDragServiceContractID);
5828 }
5830 if (dragService) {
5831 NSPoint pnt = [NSEvent mouseLocation];
5832 FlipCocoaScreenCoordinate(pnt);
5833 dragService->DragMoved(NSToIntRound(pnt.x), NSToIntRound(pnt.y));
5834 }
5835 }
5837 // NSDraggingSource
5838 - (void)draggedImage:(NSImage *)anImage endedAt:(NSPoint)aPoint operation:(NSDragOperation)operation
5839 {
5840 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
5842 gDraggedTransferables = nullptr;
5844 NSEvent *currentEvent = [NSApp currentEvent];
5845 gUserCancelledDrag = ([currentEvent type] == NSKeyDown &&
5846 [currentEvent keyCode] == kVK_Escape);
5848 if (!mDragService) {
5849 CallGetService(kDragServiceContractID, &mDragService);
5850 NS_ASSERTION(mDragService, "Couldn't get a drag service - big problem!");
5851 }
5853 if (mDragService) {
5854 // set the dragend point from the current mouse location
5855 nsDragService* dragService = static_cast<nsDragService *>(mDragService);
5856 NSPoint pnt = [NSEvent mouseLocation];
5857 FlipCocoaScreenCoordinate(pnt);
5858 dragService->SetDragEndPoint(nsIntPoint(NSToIntRound(pnt.x), NSToIntRound(pnt.y)));
5860 // XXX: dropEffect should be updated per |operation|.
5861 // As things stand though, |operation| isn't well handled within "our"
5862 // events, that is, when the drop happens within the window: it is set
5863 // either to NSDragOperationGeneric or to NSDragOperationNone.
5864 // For that reason, it's not yet possible to override dropEffect per the
5865 // given OS value, and it's also unclear what's the correct dropEffect
5866 // value for NSDragOperationGeneric that is passed by other applications.
5867 // All that said, NSDragOperationNone is still reliable.
5868 if (operation == NSDragOperationNone) {
5869 nsCOMPtr<nsIDOMDataTransfer> dataTransfer;
5870 dragService->GetDataTransfer(getter_AddRefs(dataTransfer));
5871 if (dataTransfer)
5872 dataTransfer->SetDropEffectInt(nsIDragService::DRAGDROP_ACTION_NONE);
5873 }
5875 mDragService->EndDragSession(true);
5876 NS_RELEASE(mDragService);
5877 }
5879 [globalDragPboard release];
5880 globalDragPboard = nil;
5881 [gLastDragMouseDownEvent release];
5882 gLastDragMouseDownEvent = nil;
5884 NS_OBJC_END_TRY_ABORT_BLOCK;
5885 }
5887 // NSDraggingSource
5888 // this is just implemented so we comply with the NSDraggingSource informal protocol
5889 - (NSDragOperation)draggingSourceOperationMaskForLocal:(BOOL)isLocal
5890 {
5891 return UINT_MAX;
5892 }
5894 // This method is a callback typically invoked in response to a drag ending on the desktop
5895 // or a Findow folder window; the argument passed is a path to the drop location, to be used
5896 // in constructing a complete pathname for the file(s) we want to create as a result of
5897 // the drag.
5898 - (NSArray *)namesOfPromisedFilesDroppedAtDestination:(NSURL*)dropDestination
5899 {
5900 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
5902 nsresult rv;
5904 PR_LOG(sCocoaLog, PR_LOG_ALWAYS, ("ChildView namesOfPromisedFilesDroppedAtDestination: entering callback for promised files\n"));
5906 nsCOMPtr<nsIFile> targFile;
5907 NS_NewLocalFile(EmptyString(), true, getter_AddRefs(targFile));
5908 nsCOMPtr<nsILocalFileMac> macLocalFile = do_QueryInterface(targFile);
5909 if (!macLocalFile) {
5910 NS_ERROR("No Mac local file");
5911 return nil;
5912 }
5914 if (!NS_SUCCEEDED(macLocalFile->InitWithCFURL((CFURLRef)dropDestination))) {
5915 NS_ERROR("failed InitWithCFURL");
5916 return nil;
5917 }
5919 if (!gDraggedTransferables)
5920 return nil;
5922 uint32_t transferableCount;
5923 rv = gDraggedTransferables->Count(&transferableCount);
5924 if (NS_FAILED(rv))
5925 return nil;
5927 for (uint32_t i = 0; i < transferableCount; i++) {
5928 nsCOMPtr<nsISupports> genericItem;
5929 gDraggedTransferables->GetElementAt(i, getter_AddRefs(genericItem));
5930 nsCOMPtr<nsITransferable> item(do_QueryInterface(genericItem));
5931 if (!item) {
5932 NS_ERROR("no transferable");
5933 return nil;
5934 }
5936 item->SetTransferData(kFilePromiseDirectoryMime, macLocalFile, sizeof(nsIFile*));
5938 // now request the kFilePromiseMime data, which will invoke the data provider
5939 // If successful, the returned data is a reference to the resulting file.
5940 nsCOMPtr<nsISupports> fileDataPrimitive;
5941 uint32_t dataSize = 0;
5942 item->GetTransferData(kFilePromiseMime, getter_AddRefs(fileDataPrimitive), &dataSize);
5943 }
5945 NSPasteboard* generalPboard = [NSPasteboard pasteboardWithName:NSDragPboard];
5946 NSData* data = [generalPboard dataForType:@"application/x-moz-file-promise-dest-filename"];
5947 NSString* name = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
5948 NSArray* rslt = [NSArray arrayWithObject:name];
5950 [name release];
5952 return rslt;
5954 NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
5955 }
5957 #pragma mark -
5959 // Support for the "Services" menu. We currently only support sending strings
5960 // and HTML to system services.
5962 - (id)validRequestorForSendType:(NSString *)sendType
5963 returnType:(NSString *)returnType
5964 {
5965 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
5967 // sendType contains the type of data that the service would like this
5968 // application to send to it. sendType is nil if the service is not
5969 // requesting any data.
5970 //
5971 // returnType contains the type of data the the service would like to
5972 // return to this application (e.g., to overwrite the selection).
5973 // returnType is nil if the service will not return any data.
5974 //
5975 // The following condition thus triggers when the service expects a string
5976 // or HTML from us or no data at all AND when the service will either not
5977 // send back any data to us or will send a string or HTML back to us.
5979 #define IsSupportedType(typeStr) ([typeStr isEqual:NSStringPboardType] || [typeStr isEqual:NSHTMLPboardType])
5981 id result = nil;
5983 if ((!sendType || IsSupportedType(sendType)) &&
5984 (!returnType || IsSupportedType(returnType))) {
5985 if (mGeckoChild) {
5986 // Assume that this object will be able to handle this request.
5987 result = self;
5989 // Keep the ChildView alive during this operation.
5990 nsAutoRetainCocoaObject kungFuDeathGrip(self);
5992 // Determine if there is a selection (if sending to the service).
5993 if (sendType) {
5994 WidgetQueryContentEvent event(true, NS_QUERY_CONTENT_STATE,
5995 mGeckoChild);
5996 // This might destroy our widget (and null out mGeckoChild).
5997 mGeckoChild->DispatchWindowEvent(event);
5998 if (!mGeckoChild || !event.mSucceeded || !event.mReply.mHasSelection)
5999 result = nil;
6000 }
6002 // Determine if we can paste (if receiving data from the service).
6003 if (mGeckoChild && returnType) {
6004 WidgetContentCommandEvent command(true,
6005 NS_CONTENT_COMMAND_PASTE_TRANSFERABLE,
6006 mGeckoChild, true);
6007 // This might possibly destroy our widget (and null out mGeckoChild).
6008 mGeckoChild->DispatchWindowEvent(command);
6009 if (!mGeckoChild || !command.mSucceeded || !command.mIsEnabled)
6010 result = nil;
6011 }
6012 }
6013 }
6015 #undef IsSupportedType
6017 // Give the superclass a chance if this object will not handle this request.
6018 if (!result)
6019 result = [super validRequestorForSendType:sendType returnType:returnType];
6021 return result;
6023 NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
6024 }
6026 - (BOOL)writeSelectionToPasteboard:(NSPasteboard *)pboard
6027 types:(NSArray *)types
6028 {
6029 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
6031 nsAutoRetainCocoaObject kungFuDeathGrip(self);
6033 // Make sure that the service will accept strings or HTML.
6034 if ([types containsObject:NSStringPboardType] == NO &&
6035 [types containsObject:NSHTMLPboardType] == NO)
6036 return NO;
6038 // Bail out if there is no Gecko object.
6039 if (!mGeckoChild)
6040 return NO;
6042 // Obtain the current selection.
6043 WidgetQueryContentEvent event(true,
6044 NS_QUERY_SELECTION_AS_TRANSFERABLE,
6045 mGeckoChild);
6046 mGeckoChild->DispatchWindowEvent(event);
6047 if (!event.mSucceeded || !event.mReply.mTransferable)
6048 return NO;
6050 // Transform the transferable to an NSDictionary.
6051 NSDictionary* pasteboardOutputDict = nsClipboard::PasteboardDictFromTransferable(event.mReply.mTransferable);
6052 if (!pasteboardOutputDict)
6053 return NO;
6055 // Declare the pasteboard types.
6056 unsigned int typeCount = [pasteboardOutputDict count];
6057 NSMutableArray * types = [NSMutableArray arrayWithCapacity:typeCount];
6058 [types addObjectsFromArray:[pasteboardOutputDict allKeys]];
6059 [pboard declareTypes:types owner:nil];
6061 // Write the data to the pasteboard.
6062 for (unsigned int i = 0; i < typeCount; i++) {
6063 NSString* currentKey = [types objectAtIndex:i];
6064 id currentValue = [pasteboardOutputDict valueForKey:currentKey];
6066 if (currentKey == NSStringPboardType ||
6067 currentKey == kCorePboardType_url ||
6068 currentKey == kCorePboardType_urld ||
6069 currentKey == kCorePboardType_urln) {
6070 [pboard setString:currentValue forType:currentKey];
6071 } else if (currentKey == NSHTMLPboardType) {
6072 [pboard setString:(nsClipboard::WrapHtmlForSystemPasteboard(currentValue)) forType:currentKey];
6073 } else if (currentKey == NSTIFFPboardType) {
6074 [pboard setData:currentValue forType:currentKey];
6075 } else if (currentKey == NSFilesPromisePboardType) {
6076 [pboard setPropertyList:currentValue forType:currentKey];
6077 }
6078 }
6080 return YES;
6082 NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO);
6083 }
6085 // Called if the service wants us to replace the current selection.
6086 - (BOOL)readSelectionFromPasteboard:(NSPasteboard *)pboard
6087 {
6088 nsresult rv;
6089 nsCOMPtr<nsITransferable> trans = do_CreateInstance("@mozilla.org/widget/transferable;1", &rv);
6090 if (NS_FAILED(rv))
6091 return NO;
6092 trans->Init(nullptr);
6094 trans->AddDataFlavor(kUnicodeMime);
6095 trans->AddDataFlavor(kHTMLMime);
6097 rv = nsClipboard::TransferableFromPasteboard(trans, pboard);
6098 if (NS_FAILED(rv))
6099 return NO;
6101 NS_ENSURE_TRUE(mGeckoChild, false);
6103 WidgetContentCommandEvent command(true,
6104 NS_CONTENT_COMMAND_PASTE_TRANSFERABLE,
6105 mGeckoChild);
6106 command.mTransferable = trans;
6107 mGeckoChild->DispatchWindowEvent(command);
6109 return command.mSucceeded && command.mIsEnabled;
6110 }
6112 #pragma mark -
6114 #ifdef ACCESSIBILITY
6116 /* Every ChildView has a corresponding mozDocAccessible object that is doing all
6117 the heavy lifting. The topmost ChildView corresponds to a mozRootAccessible
6118 object.
6120 All ChildView needs to do is to route all accessibility calls (from the NSAccessibility APIs)
6121 down to its object, pretending that they are the same.
6122 */
6123 - (id<mozAccessible>)accessible
6124 {
6125 if (!mGeckoChild)
6126 return nil;
6128 id<mozAccessible> nativeAccessible = nil;
6130 nsAutoRetainCocoaObject kungFuDeathGrip(self);
6131 nsCOMPtr<nsIWidget> kungFuDeathGrip2(mGeckoChild);
6132 nsRefPtr<a11y::Accessible> accessible = mGeckoChild->GetDocumentAccessible();
6133 if (!accessible)
6134 return nil;
6136 accessible->GetNativeInterface((void**)&nativeAccessible);
6138 #ifdef DEBUG_hakan
6139 NSAssert(![nativeAccessible isExpired], @"native acc is expired!!!");
6140 #endif
6142 return nativeAccessible;
6143 }
6145 /* Implementation of formal mozAccessible formal protocol (enabling mozViews
6146 to talk to mozAccessible objects in the accessibility module). */
6148 - (BOOL)hasRepresentedView
6149 {
6150 return YES;
6151 }
6153 - (id)representedView
6154 {
6155 return self;
6156 }
6158 - (BOOL)isRoot
6159 {
6160 return [[self accessible] isRoot];
6161 }
6163 #ifdef DEBUG
6164 - (void)printHierarchy
6165 {
6166 [[self accessible] printHierarchy];
6167 }
6168 #endif
6170 #pragma mark -
6172 // general
6174 - (BOOL)accessibilityIsIgnored
6175 {
6176 if (!mozilla::a11y::ShouldA11yBeEnabled())
6177 return [super accessibilityIsIgnored];
6179 return [[self accessible] accessibilityIsIgnored];
6180 }
6182 - (id)accessibilityHitTest:(NSPoint)point
6183 {
6184 if (!mozilla::a11y::ShouldA11yBeEnabled())
6185 return [super accessibilityHitTest:point];
6187 return [[self accessible] accessibilityHitTest:point];
6188 }
6190 - (id)accessibilityFocusedUIElement
6191 {
6192 if (!mozilla::a11y::ShouldA11yBeEnabled())
6193 return [super accessibilityFocusedUIElement];
6195 return [[self accessible] accessibilityFocusedUIElement];
6196 }
6198 // actions
6200 - (NSArray*)accessibilityActionNames
6201 {
6202 if (!mozilla::a11y::ShouldA11yBeEnabled())
6203 return [super accessibilityActionNames];
6205 return [[self accessible] accessibilityActionNames];
6206 }
6208 - (NSString*)accessibilityActionDescription:(NSString*)action
6209 {
6210 if (!mozilla::a11y::ShouldA11yBeEnabled())
6211 return [super accessibilityActionDescription:action];
6213 return [[self accessible] accessibilityActionDescription:action];
6214 }
6216 - (void)accessibilityPerformAction:(NSString*)action
6217 {
6218 if (!mozilla::a11y::ShouldA11yBeEnabled())
6219 return [super accessibilityPerformAction:action];
6221 return [[self accessible] accessibilityPerformAction:action];
6222 }
6224 // attributes
6226 - (NSArray*)accessibilityAttributeNames
6227 {
6228 if (!mozilla::a11y::ShouldA11yBeEnabled())
6229 return [super accessibilityAttributeNames];
6231 return [[self accessible] accessibilityAttributeNames];
6232 }
6234 - (BOOL)accessibilityIsAttributeSettable:(NSString*)attribute
6235 {
6236 if (!mozilla::a11y::ShouldA11yBeEnabled())
6237 return [super accessibilityIsAttributeSettable:attribute];
6239 return [[self accessible] accessibilityIsAttributeSettable:attribute];
6240 }
6242 - (id)accessibilityAttributeValue:(NSString*)attribute
6243 {
6244 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
6246 if (!mozilla::a11y::ShouldA11yBeEnabled())
6247 return [super accessibilityAttributeValue:attribute];
6249 id<mozAccessible> accessible = [self accessible];
6251 // if we're the root (topmost) accessible, we need to return our native AXParent as we
6252 // traverse outside to the hierarchy of whoever embeds us. thus, fall back on NSView's
6253 // default implementation for this attribute.
6254 if ([attribute isEqualToString:NSAccessibilityParentAttribute] && [accessible isRoot]) {
6255 id parentAccessible = [super accessibilityAttributeValue:attribute];
6256 return parentAccessible;
6257 }
6259 return [accessible accessibilityAttributeValue:attribute];
6261 NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
6262 }
6264 #endif /* ACCESSIBILITY */
6266 @end
6268 #pragma mark -
6270 void
6271 ChildViewMouseTracker::OnDestroyView(ChildView* aView)
6272 {
6273 if (sLastMouseEventView == aView) {
6274 sLastMouseEventView = nil;
6275 [sLastMouseMoveEvent release];
6276 sLastMouseMoveEvent = nil;
6277 }
6278 }
6280 void
6281 ChildViewMouseTracker::OnDestroyWindow(NSWindow* aWindow)
6282 {
6283 if (sWindowUnderMouse == aWindow) {
6284 sWindowUnderMouse = nil;
6285 }
6286 }
6288 void
6289 ChildViewMouseTracker::MouseEnteredWindow(NSEvent* aEvent)
6290 {
6291 sWindowUnderMouse = [aEvent window];
6292 ReEvaluateMouseEnterState(aEvent);
6293 }
6295 void
6296 ChildViewMouseTracker::MouseExitedWindow(NSEvent* aEvent)
6297 {
6298 if (sWindowUnderMouse == [aEvent window]) {
6299 sWindowUnderMouse = nil;
6300 ReEvaluateMouseEnterState(aEvent);
6301 }
6302 }
6304 void
6305 ChildViewMouseTracker::ReEvaluateMouseEnterState(NSEvent* aEvent, ChildView* aOldView)
6306 {
6307 ChildView* oldView = aOldView ? aOldView : sLastMouseEventView;
6308 sLastMouseEventView = ViewForEvent(aEvent);
6309 if (sLastMouseEventView != oldView) {
6310 // Send enter and / or exit events.
6311 WidgetMouseEvent::exitType type =
6312 [sLastMouseEventView window] == [oldView window] ?
6313 WidgetMouseEvent::eChild : WidgetMouseEvent::eTopLevel;
6314 [oldView sendMouseEnterOrExitEvent:aEvent enter:NO type:type];
6315 // After the cursor exits the window set it to a visible regular arrow cursor.
6316 if (type == WidgetMouseEvent::eTopLevel) {
6317 [[nsCursorManager sharedInstance] setCursor:eCursor_standard];
6318 }
6319 [sLastMouseEventView sendMouseEnterOrExitEvent:aEvent enter:YES type:type];
6320 }
6321 }
6323 void
6324 ChildViewMouseTracker::ResendLastMouseMoveEvent()
6325 {
6326 if (sLastMouseMoveEvent) {
6327 MouseMoved(sLastMouseMoveEvent);
6328 }
6329 }
6331 void
6332 ChildViewMouseTracker::MouseMoved(NSEvent* aEvent)
6333 {
6334 MouseEnteredWindow(aEvent);
6335 [sLastMouseEventView handleMouseMoved:aEvent];
6336 if (sLastMouseMoveEvent != aEvent) {
6337 [sLastMouseMoveEvent release];
6338 sLastMouseMoveEvent = [aEvent retain];
6339 }
6340 }
6342 void
6343 ChildViewMouseTracker::MouseScrolled(NSEvent* aEvent)
6344 {
6345 if (!nsCocoaUtils::IsMomentumScrollEvent(aEvent)) {
6346 // Store the position so we can pin future momentum scroll events.
6347 sLastScrollEventScreenLocation = nsCocoaUtils::ScreenLocationForEvent(aEvent);
6348 }
6349 }
6351 ChildView*
6352 ChildViewMouseTracker::ViewForEvent(NSEvent* aEvent)
6353 {
6354 NSWindow* window = sWindowUnderMouse;
6355 if (!window)
6356 return nil;
6358 NSPoint windowEventLocation = nsCocoaUtils::EventLocationForWindow(aEvent, window);
6359 NSView* view = [[[window contentView] superview] hitTest:windowEventLocation];
6361 if (![view isKindOfClass:[ChildView class]])
6362 return nil;
6364 ChildView* childView = (ChildView*)view;
6365 // If childView is being destroyed return nil.
6366 if (![childView widget])
6367 return nil;
6368 return WindowAcceptsEvent(window, aEvent, childView) ? childView : nil;
6369 }
6371 void
6372 ChildViewMouseTracker::AttachPluginEvent(WidgetMouseEventBase& aMouseEvent,
6373 ChildView* aView,
6374 NSEvent* aNativeMouseEvent,
6375 int aPluginEventType,
6376 void* aPluginEventHolder)
6377 {
6378 if (![aView isPluginView] ||
6379 [aView pluginEventModel] != NPEventModelCocoa) {
6380 return;
6381 }
6383 NPCocoaEvent* cocoaEvent = (NPCocoaEvent*)aPluginEventHolder;
6384 nsCocoaUtils::InitNPCocoaEvent(cocoaEvent);
6385 NSPoint point = [aView convertPoint:[aNativeMouseEvent locationInWindow]
6386 fromView:nil];
6387 NPCocoaEventType type = (NPCocoaEventType)aPluginEventType;
6388 // XXX We should consider dropping this code, which causes data.mouse.pluginX
6389 // and data.mouse.pluginY to be set to '5' for mouse-entered and mouse-exited
6390 // events. But note that it's been in the tree since the Cocoa NPAPI event
6391 // model was first implemented four years ago, without any known complaints.
6392 //
6393 // Similar code first appeared (without explanation) in a very early version
6394 // ("fix 0.3") of the patch for bug 435041 ("Implement Cocoa NPAPI event
6395 // model for Mac OS X"). But there's no trace of it in the WebKit code that
6396 // was used as a model for much of that patch.
6397 #if (0)
6398 if (type == NPCocoaEventMouseEntered ||
6399 type == NPCocoaEventMouseExited) {
6400 point.x = point.y = 5;
6401 }
6402 #endif
6403 NSUInteger clickCount = 0;
6404 if (type != NPCocoaEventMouseEntered &&
6405 type != NPCocoaEventMouseExited &&
6406 type != NPCocoaEventScrollWheel) {
6407 clickCount = [aNativeMouseEvent clickCount];
6408 }
6409 cocoaEvent->type = type;
6410 cocoaEvent->data.mouse.modifierFlags = [aNativeMouseEvent modifierFlags];
6411 cocoaEvent->data.mouse.pluginX = point.x;
6412 cocoaEvent->data.mouse.pluginY = point.y;
6413 cocoaEvent->data.mouse.buttonNumber = [aNativeMouseEvent buttonNumber];
6414 cocoaEvent->data.mouse.clickCount = clickCount;
6415 cocoaEvent->data.mouse.deltaX = [aNativeMouseEvent deltaX];
6416 cocoaEvent->data.mouse.deltaY = [aNativeMouseEvent deltaY];
6417 cocoaEvent->data.mouse.deltaZ = [aNativeMouseEvent deltaZ];
6418 aMouseEvent.pluginEvent = cocoaEvent;
6419 }
6421 BOOL
6422 ChildViewMouseTracker::WindowAcceptsEvent(NSWindow* aWindow, NSEvent* aEvent,
6423 ChildView* aView, BOOL aIsClickThrough)
6424 {
6425 // Right mouse down events may get through to all windows, even to a top level
6426 // window with an open sheet.
6427 if (!aWindow || [aEvent type] == NSRightMouseDown)
6428 return YES;
6430 id delegate = [aWindow delegate];
6431 if (!delegate || ![delegate isKindOfClass:[WindowDelegate class]])
6432 return YES;
6434 nsIWidget *windowWidget = [(WindowDelegate *)delegate geckoWidget];
6435 if (!windowWidget)
6436 return YES;
6438 NSWindow* topLevelWindow = nil;
6440 switch (windowWidget->WindowType()) {
6441 case eWindowType_popup:
6442 // If this is a context menu, it won't have a parent. So we'll always
6443 // accept mouse move events on context menus even when none of our windows
6444 // is active, which is the right thing to do.
6445 // For panels, the parent window is the XUL window that owns the panel.
6446 return WindowAcceptsEvent([aWindow parentWindow], aEvent, aView, aIsClickThrough);
6448 case eWindowType_toplevel:
6449 case eWindowType_dialog:
6450 if ([aWindow attachedSheet])
6451 return NO;
6453 topLevelWindow = aWindow;
6454 break;
6455 case eWindowType_sheet: {
6456 nsIWidget* parentWidget = windowWidget->GetSheetWindowParent();
6457 if (!parentWidget)
6458 return YES;
6460 topLevelWindow = (NSWindow*)parentWidget->GetNativeData(NS_NATIVE_WINDOW);
6461 break;
6462 }
6464 default:
6465 return YES;
6466 }
6468 if (!topLevelWindow ||
6469 ([topLevelWindow isMainWindow] && !aIsClickThrough) ||
6470 [aEvent type] == NSOtherMouseDown ||
6471 (([aEvent modifierFlags] & NSCommandKeyMask) != 0 &&
6472 [aEvent type] != NSMouseMoved))
6473 return YES;
6475 // If we're here then we're dealing with a left click or mouse move on an
6476 // inactive window or something similar. Ask Gecko what to do.
6477 return [aView inactiveWindowAcceptsMouseEvent:aEvent];
6478 }
6480 #pragma mark -
6482 @interface NSView (MethodSwizzling)
6483 - (BOOL)nsChildView_NSView_mouseDownCanMoveWindow;
6484 @end
6486 @implementation NSView (MethodSwizzling)
6488 // All top-level browser windows belong to the ToolbarWindow class and have
6489 // NSTexturedBackgroundWindowMask turned on in their "style" (see particularly
6490 // [ToolbarWindow initWithContentRect:...] in nsCocoaWindow.mm). This style
6491 // normally means the window "may be moved by clicking and dragging anywhere
6492 // in the window background", but we've suppressed this by giving the
6493 // ChildView class a mouseDownCanMoveWindow method that always returns NO.
6494 // Normally a ToolbarWindow's contentView (not a ChildView) returns YES when
6495 // NSTexturedBackgroundWindowMask is turned on. But normally this makes no
6496 // difference. However, under some (probably very unusual) circumstances
6497 // (and only on Leopard) it *does* make a difference -- for example it
6498 // triggers bmo bugs 431902 and 476393. So here we make sure that a
6499 // ToolbarWindow's contentView always returns NO from the
6500 // mouseDownCanMoveWindow method.
6501 - (BOOL)nsChildView_NSView_mouseDownCanMoveWindow
6502 {
6503 NSWindow *ourWindow = [self window];
6504 NSView *contentView = [ourWindow contentView];
6505 if ([ourWindow isKindOfClass:[ToolbarWindow class]] && (self == contentView))
6506 return [ourWindow isMovableByWindowBackground];
6507 return [self nsChildView_NSView_mouseDownCanMoveWindow];
6508 }
6510 @end
6512 #ifdef __LP64__
6513 // When using blocks, at least on OS X 10.7, the OS sometimes calls
6514 // +[NSEvent removeMonitor:] more than once on a single event monitor, which
6515 // causes crashes. See bug 678607. We hook these methods to work around
6516 // the problem.
6517 @interface NSEvent (MethodSwizzling)
6518 + (id)nsChildView_NSEvent_addLocalMonitorForEventsMatchingMask:(unsigned long long)mask handler:(id)block;
6519 + (void)nsChildView_NSEvent_removeMonitor:(id)eventMonitor;
6520 @end
6522 // This is a local copy of the AppKit frameworks sEventObservers hashtable.
6523 // It only stores "local monitors". We use it to ensure that +[NSEvent
6524 // removeMonitor:] is never called more than once on the same local monitor.
6525 static NSHashTable *sLocalEventObservers = nil;
6527 @implementation NSEvent (MethodSwizzling)
6529 + (id)nsChildView_NSEvent_addLocalMonitorForEventsMatchingMask:(unsigned long long)mask handler:(id)block
6530 {
6531 if (!sLocalEventObservers) {
6532 sLocalEventObservers = [[NSHashTable hashTableWithOptions:
6533 NSHashTableStrongMemory | NSHashTableObjectPointerPersonality] retain];
6534 }
6535 id retval =
6536 [self nsChildView_NSEvent_addLocalMonitorForEventsMatchingMask:mask handler:block];
6537 if (sLocalEventObservers && retval && ![sLocalEventObservers containsObject:retval]) {
6538 [sLocalEventObservers addObject:retval];
6539 }
6540 return retval;
6541 }
6543 + (void)nsChildView_NSEvent_removeMonitor:(id)eventMonitor
6544 {
6545 if (sLocalEventObservers && [eventMonitor isKindOfClass: ::NSClassFromString(@"_NSLocalEventObserver")]) {
6546 if (![sLocalEventObservers containsObject:eventMonitor]) {
6547 return;
6548 }
6549 [sLocalEventObservers removeObject:eventMonitor];
6550 }
6551 [self nsChildView_NSEvent_removeMonitor:eventMonitor];
6552 }
6554 @end
6555 #endif // #ifdef __LP64__