michael@0: /* -*- Mode: objc; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "mozilla/ArrayUtils.h" michael@0: michael@0: #ifdef MOZ_LOGGING michael@0: #define FORCE_PR_LOG michael@0: #endif michael@0: #include "prlog.h" michael@0: michael@0: #include michael@0: #include michael@0: michael@0: #include "nsChildView.h" michael@0: #include "nsCocoaWindow.h" michael@0: michael@0: #include "mozilla/MiscEvents.h" michael@0: #include "mozilla/MouseEvents.h" michael@0: #include "mozilla/TextEvents.h" michael@0: #include "mozilla/TouchEvents.h" michael@0: michael@0: #include "nsObjCExceptions.h" michael@0: #include "nsCOMPtr.h" michael@0: #include "nsToolkit.h" michael@0: #include "nsCRT.h" michael@0: michael@0: #include "nsFontMetrics.h" michael@0: #include "nsIRollupListener.h" michael@0: #include "nsViewManager.h" michael@0: #include "nsIInterfaceRequestor.h" michael@0: #include "nsIFile.h" michael@0: #include "nsILocalFileMac.h" michael@0: #include "nsGfxCIID.h" michael@0: #include "nsIDOMSimpleGestureEvent.h" michael@0: #include "nsNPAPIPluginInstance.h" michael@0: #include "nsThemeConstants.h" michael@0: #include "nsIWidgetListener.h" michael@0: #include "nsIPresShell.h" michael@0: michael@0: #include "nsDragService.h" michael@0: #include "nsClipboard.h" michael@0: #include "nsCursorManager.h" michael@0: #include "nsWindowMap.h" michael@0: #include "nsCocoaFeatures.h" michael@0: #include "nsCocoaUtils.h" michael@0: #include "nsMenuUtilsX.h" michael@0: #include "nsMenuBarX.h" michael@0: #ifdef __LP64__ michael@0: #include "ComplexTextInputPanel.h" michael@0: #endif michael@0: #include "NativeKeyBindings.h" michael@0: michael@0: #include "gfxContext.h" michael@0: #include "gfxQuartzSurface.h" michael@0: #include "gfxUtils.h" michael@0: #include "nsRegion.h" michael@0: #include "Layers.h" michael@0: #include "ClientLayerManager.h" michael@0: #include "mozilla/layers/LayerManagerComposite.h" michael@0: #include "GLTextureImage.h" michael@0: #include "GLContextProvider.h" michael@0: #include "GLContextCGL.h" michael@0: #include "GLUploadHelpers.h" michael@0: #include "ScopedGLHelpers.h" michael@0: #include "mozilla/layers/GLManager.h" michael@0: #include "mozilla/layers/CompositorOGL.h" michael@0: #include "mozilla/layers/BasicCompositor.h" michael@0: #include "gfxUtils.h" michael@0: #include "mozilla/gfx/2D.h" michael@0: #include "mozilla/gfx/BorrowedContext.h" michael@0: #ifdef ACCESSIBILITY michael@0: #include "nsAccessibilityService.h" michael@0: #include "mozilla/a11y/Platform.h" michael@0: #endif michael@0: #ifdef MOZ_CRASHREPORTER michael@0: #include "nsExceptionHandler.h" michael@0: #endif michael@0: michael@0: #include "mozilla/Preferences.h" michael@0: michael@0: #include michael@0: michael@0: #include michael@0: michael@0: #include "GeckoProfiler.h" michael@0: michael@0: #include "nsIDOMWheelEvent.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::layers; michael@0: using namespace mozilla::gl; michael@0: using namespace mozilla::widget; michael@0: michael@0: #undef DEBUG_UPDATE michael@0: #undef INVALIDATE_DEBUGGING // flash areas as they are invalidated michael@0: michael@0: // Don't put more than this many rects in the dirty region, just fluff michael@0: // out to the bounding-box if there are more michael@0: #define MAX_RECTS_IN_REGION 100 michael@0: michael@0: #ifdef PR_LOGGING michael@0: PRLogModuleInfo* sCocoaLog = nullptr; michael@0: #endif michael@0: michael@0: extern "C" { michael@0: CG_EXTERN void CGContextResetCTM(CGContextRef); michael@0: CG_EXTERN void CGContextSetCTM(CGContextRef, CGAffineTransform); michael@0: CG_EXTERN void CGContextResetClip(CGContextRef); michael@0: michael@0: typedef CFTypeRef CGSRegionObj; michael@0: CGError CGSNewRegionWithRect(const CGRect *rect, CGSRegionObj *outRegion); michael@0: } michael@0: michael@0: // defined in nsMenuBarX.mm michael@0: extern NSMenu* sApplicationMenu; // Application menu shared by all menubars michael@0: michael@0: bool gChildViewMethodsSwizzled = false; michael@0: michael@0: extern nsISupportsArray *gDraggedTransferables; michael@0: michael@0: ChildView* ChildViewMouseTracker::sLastMouseEventView = nil; michael@0: NSEvent* ChildViewMouseTracker::sLastMouseMoveEvent = nil; michael@0: NSWindow* ChildViewMouseTracker::sWindowUnderMouse = nil; michael@0: NSPoint ChildViewMouseTracker::sLastScrollEventScreenLocation = NSZeroPoint; michael@0: michael@0: #ifdef INVALIDATE_DEBUGGING michael@0: static void blinkRect(Rect* r); michael@0: static void blinkRgn(RgnHandle rgn); michael@0: #endif michael@0: michael@0: bool gUserCancelledDrag = false; michael@0: michael@0: uint32_t nsChildView::sLastInputEventCount = 0; michael@0: michael@0: @interface ChildView(Private) michael@0: michael@0: // sets up our view, attaching it to its owning gecko view michael@0: - (id)initWithFrame:(NSRect)inFrame geckoChild:(nsChildView*)inChild; michael@0: - (void)forceRefreshOpenGL; michael@0: michael@0: // set up a gecko mouse event based on a cocoa mouse event michael@0: - (void) convertCocoaMouseWheelEvent:(NSEvent*)aMouseEvent michael@0: toGeckoEvent:(WidgetWheelEvent*)outWheelEvent; michael@0: - (void) convertCocoaMouseEvent:(NSEvent*)aMouseEvent michael@0: toGeckoEvent:(WidgetInputEvent*)outGeckoEvent; michael@0: michael@0: - (NSMenu*)contextMenu; michael@0: michael@0: - (void)setIsPluginView:(BOOL)aIsPlugin; michael@0: - (void)setPluginEventModel:(NPEventModel)eventModel; michael@0: - (void)setPluginDrawingModel:(NPDrawingModel)drawingModel; michael@0: - (NPDrawingModel)pluginDrawingModel; michael@0: michael@0: - (BOOL)isRectObscuredBySubview:(NSRect)inRect; michael@0: michael@0: - (void)processPendingRedraws; michael@0: michael@0: - (void)drawRect:(NSRect)aRect inContext:(CGContextRef)aContext; michael@0: - (nsIntRegion)nativeDirtyRegionWithBoundingRect:(NSRect)aRect; michael@0: - (BOOL)isUsingMainThreadOpenGL; michael@0: - (BOOL)isUsingOpenGL; michael@0: - (void)drawUsingOpenGL; michael@0: - (void)drawUsingOpenGLCallback; michael@0: michael@0: - (BOOL)hasRoundedBottomCorners; michael@0: - (CGFloat)cornerRadius; michael@0: - (void)clearCorners; michael@0: michael@0: // Overlay drawing functions for traditional CGContext drawing michael@0: - (void)drawTitleString; michael@0: - (void)drawTitlebarHighlight; michael@0: - (void)maskTopCornersInContext:(CGContextRef)aContext; michael@0: michael@0: // Called using performSelector:withObject:afterDelay:0 to release michael@0: // aWidgetArray (and its contents) the next time through the run loop. michael@0: - (void)releaseWidgets:(NSArray*)aWidgetArray; michael@0: michael@0: #if USE_CLICK_HOLD_CONTEXTMENU michael@0: // called on a timer two seconds after a mouse down to see if we should display michael@0: // a context menu (click-hold) michael@0: - (void)clickHoldCallback:(id)inEvent; michael@0: #endif michael@0: michael@0: #ifdef ACCESSIBILITY michael@0: - (id)accessible; michael@0: #endif michael@0: michael@0: - (BOOL)inactiveWindowAcceptsMouseEvent:(NSEvent*)aEvent; michael@0: michael@0: @end michael@0: michael@0: @interface NSView(NSThemeFrameCornerRadius) michael@0: - (float)roundedCornerRadius; michael@0: @end michael@0: michael@0: // Starting with 10.7 the bottom corners of all windows are rounded. michael@0: // Unfortunately, the standard rounding that OS X applies to OpenGL views michael@0: // does not use anti-aliasing and looks very crude. Since we want a smooth, michael@0: // anti-aliased curve, we'll draw it ourselves. michael@0: // Additionally, we need to turn off the OS-supplied rounding because it michael@0: // eats into our corner's curve. We do that by overriding an NSSurface method. michael@0: @interface NSSurface @end michael@0: michael@0: @implementation NSSurface(DontCutOffCorners) michael@0: - (CGSRegionObj)_createRoundedBottomRegionForRect:(CGRect)rect michael@0: { michael@0: // Create a normal rect region without rounded bottom corners. michael@0: CGSRegionObj region; michael@0: CGSNewRegionWithRect(&rect, ®ion); michael@0: return region; michael@0: } michael@0: @end michael@0: michael@0: #pragma mark - michael@0: michael@0: /* Convenience routine to go from a Gecko rect to Cocoa NSRect. michael@0: * michael@0: * Gecko rects (nsRect) contain an origin (x,y) in a coordinate michael@0: * system with (0,0) in the top-left of the screen. Cocoa rects michael@0: * (NSRect) contain an origin (x,y) in a coordinate system with michael@0: * (0,0) in the bottom-left of the screen. Both nsRect and NSRect michael@0: * contain width/height info, with no difference in their use. michael@0: * If a Cocoa rect is from a flipped view, there is no need to michael@0: * convert coordinate systems. michael@0: */ michael@0: #ifndef __LP64__ michael@0: static inline void michael@0: ConvertGeckoRectToMacRect(const nsIntRect& aRect, Rect& outMacRect) michael@0: { michael@0: outMacRect.left = aRect.x; michael@0: outMacRect.top = aRect.y; michael@0: outMacRect.right = aRect.x + aRect.width; michael@0: outMacRect.bottom = aRect.y + aRect.height; michael@0: } michael@0: #endif michael@0: michael@0: // Flips a screen coordinate from a point in the cocoa coordinate system (bottom-left rect) to a point michael@0: // that is a "flipped" cocoa coordinate system (starts in the top-left). michael@0: static inline void michael@0: FlipCocoaScreenCoordinate(NSPoint &inPoint) michael@0: { michael@0: inPoint.y = nsCocoaUtils::FlippedScreenY(inPoint.y); michael@0: } michael@0: michael@0: void EnsureLogInitialized() michael@0: { michael@0: #ifdef PR_LOGGING michael@0: if (!sCocoaLog) { michael@0: sCocoaLog = PR_NewLogModule("nsCocoaWidgets"); michael@0: } michael@0: #endif // PR_LOGGING michael@0: } michael@0: michael@0: namespace { michael@0: michael@0: // Manages a texture which can resize dynamically, binds to the michael@0: // LOCAL_GL_TEXTURE_RECTANGLE_ARB texture target and is automatically backed michael@0: // by a power-of-two size GL texture. The latter two features are used for michael@0: // compatibility with older Mac hardware which we block GL layers on. michael@0: // RectTextureImages are used both for accelerated GL layers drawing and for michael@0: // OMTC BasicLayers drawing. michael@0: class RectTextureImage { michael@0: public: michael@0: RectTextureImage(GLContext* aGLContext) michael@0: : mGLContext(aGLContext) michael@0: , mTexture(0) michael@0: , mInUpdate(false) michael@0: {} michael@0: michael@0: virtual ~RectTextureImage(); michael@0: michael@0: TemporaryRef michael@0: BeginUpdate(const nsIntSize& aNewSize, michael@0: const nsIntRegion& aDirtyRegion = nsIntRegion()); michael@0: void EndUpdate(bool aKeepSurface = false); michael@0: michael@0: void UpdateIfNeeded(const nsIntSize& aNewSize, michael@0: const nsIntRegion& aDirtyRegion, michael@0: void (^aCallback)(gfx::DrawTarget*, const nsIntRegion&)) michael@0: { michael@0: RefPtr drawTarget = BeginUpdate(aNewSize, aDirtyRegion); michael@0: if (drawTarget) { michael@0: aCallback(drawTarget, GetUpdateRegion()); michael@0: EndUpdate(); michael@0: } michael@0: } michael@0: michael@0: void UpdateFromCGContext(const nsIntSize& aNewSize, michael@0: const nsIntRegion& aDirtyRegion, michael@0: CGContextRef aCGContext); michael@0: michael@0: void UpdateFromDrawTarget(const nsIntSize& aNewSize, michael@0: const nsIntRegion& aDirtyRegion, michael@0: gfx::DrawTarget* aFromDrawTarget); michael@0: michael@0: nsIntRegion GetUpdateRegion() { michael@0: MOZ_ASSERT(mInUpdate, "update region only valid during update"); michael@0: return mUpdateRegion; michael@0: } michael@0: michael@0: void Draw(mozilla::layers::GLManager* aManager, michael@0: const nsIntPoint& aLocation, michael@0: const gfx3DMatrix& aTransform = gfx3DMatrix()); michael@0: michael@0: static nsIntSize TextureSizeForSize(const nsIntSize& aSize); michael@0: michael@0: protected: michael@0: michael@0: RefPtr mUpdateDrawTarget; michael@0: GLContext* mGLContext; michael@0: nsIntRegion mUpdateRegion; michael@0: nsIntSize mUsedSize; michael@0: nsIntSize mBufferSize; michael@0: nsIntSize mTextureSize; michael@0: GLuint mTexture; michael@0: bool mInUpdate; michael@0: }; michael@0: michael@0: // Used for OpenGL drawing from the compositor thread for OMTC BasicLayers. michael@0: // We need to use OpenGL for this because there seems to be no other robust michael@0: // way of drawing from a secondary thread without locking, which would cause michael@0: // deadlocks in our setup. See bug 882523. michael@0: class GLPresenter : public GLManager michael@0: { michael@0: public: michael@0: static GLPresenter* CreateForWindow(nsIWidget* aWindow) michael@0: { michael@0: nsRefPtr context = gl::GLContextProvider::CreateForWindow(aWindow); michael@0: return context ? new GLPresenter(context) : nullptr; michael@0: } michael@0: michael@0: GLPresenter(GLContext* aContext); michael@0: virtual ~GLPresenter(); michael@0: michael@0: virtual GLContext* gl() const MOZ_OVERRIDE { return mGLContext; } michael@0: virtual ShaderProgramOGL* GetProgram(GLenum aTarget, gfx::SurfaceFormat aFormat) MOZ_OVERRIDE michael@0: { michael@0: MOZ_ASSERT(aTarget == LOCAL_GL_TEXTURE_RECTANGLE_ARB); michael@0: MOZ_ASSERT(aFormat == gfx::SurfaceFormat::R8G8B8A8); michael@0: return mRGBARectProgram; michael@0: } michael@0: virtual const gfx::Matrix4x4& GetProjMatrix() const MOZ_OVERRIDE michael@0: { michael@0: return mProjMatrix; michael@0: } michael@0: virtual void BindAndDrawQuad(ShaderProgramOGL *aProg) MOZ_OVERRIDE; michael@0: michael@0: void BeginFrame(nsIntSize aRenderSize); michael@0: void EndFrame(); michael@0: michael@0: NSOpenGLContext* GetNSOpenGLContext() michael@0: { michael@0: return GLContextCGL::Cast(mGLContext)->GetNSOpenGLContext(); michael@0: } michael@0: michael@0: protected: michael@0: nsRefPtr mGLContext; michael@0: nsAutoPtr mRGBARectProgram; michael@0: gfx::Matrix4x4 mProjMatrix; michael@0: GLuint mQuadVBO; michael@0: }; michael@0: michael@0: } // unnamed namespace michael@0: michael@0: #pragma mark - michael@0: michael@0: nsChildView::nsChildView() : nsBaseWidget() michael@0: , mView(nullptr) michael@0: , mParentView(nullptr) michael@0: , mParentWidget(nullptr) michael@0: , mViewTearDownLock("ChildViewTearDown") michael@0: , mEffectsLock("WidgetEffects") michael@0: , mShowsResizeIndicator(false) michael@0: , mHasRoundedBottomCorners(false) michael@0: , mIsCoveringTitlebar(false) michael@0: , mIsFullscreen(false) michael@0: , mTitlebarCGContext(nullptr) michael@0: , mBackingScaleFactor(0.0) michael@0: , mVisible(false) michael@0: , mDrawing(false) michael@0: , mPluginDrawing(false) michael@0: , mIsDispatchPaint(false) michael@0: , mPluginInstanceOwner(nullptr) michael@0: { michael@0: EnsureLogInitialized(); michael@0: michael@0: memset(&mPluginCGContext, 0, sizeof(mPluginCGContext)); michael@0: } michael@0: michael@0: nsChildView::~nsChildView() michael@0: { michael@0: ReleaseTitlebarCGContext(); michael@0: michael@0: // Notify the children that we're gone. childView->ResetParent() can change michael@0: // our list of children while it's being iterated, so the way we iterate the michael@0: // list must allow for this. michael@0: for (nsIWidget* kid = mLastChild; kid;) { michael@0: nsChildView* childView = static_cast(kid); michael@0: kid = kid->GetPrevSibling(); michael@0: childView->ResetParent(); michael@0: } michael@0: michael@0: NS_WARN_IF_FALSE(mOnDestroyCalled, "nsChildView object destroyed without calling Destroy()"); michael@0: michael@0: DestroyCompositor(); michael@0: michael@0: // An nsChildView object that was in use can be destroyed without Destroy() michael@0: // ever being called on it. So we also need to do a quick, safe cleanup michael@0: // here (it's too late to just call Destroy(), which can cause crashes). michael@0: // It's particularly important to make sure widgetDestroyed is called on our michael@0: // mView -- this method NULLs mView's mGeckoChild, and NULL checks on michael@0: // mGeckoChild are used throughout the ChildView class to tell if it's safe michael@0: // to use a ChildView object. michael@0: [mView widgetDestroyed]; // Safe if mView is nil. michael@0: mParentWidget = nil; michael@0: TearDownView(); // Safe if called twice. michael@0: } michael@0: michael@0: void michael@0: nsChildView::ReleaseTitlebarCGContext() michael@0: { michael@0: if (mTitlebarCGContext) { michael@0: CGContextRelease(mTitlebarCGContext); michael@0: mTitlebarCGContext = nullptr; michael@0: } michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS_INHERITED(nsChildView, nsBaseWidget, nsIPluginWidget) michael@0: michael@0: nsresult nsChildView::Create(nsIWidget *aParent, michael@0: nsNativeWidget aNativeParent, michael@0: const nsIntRect &aRect, michael@0: nsDeviceContext *aContext, michael@0: nsWidgetInitData *aInitData) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; michael@0: michael@0: // Because the hidden window is created outside of an event loop, michael@0: // we need to provide an autorelease pool to avoid leaking cocoa objects michael@0: // (see bug 559075). michael@0: nsAutoreleasePool localPool; michael@0: michael@0: // See NSView (MethodSwizzling) below. michael@0: if (!gChildViewMethodsSwizzled) { michael@0: nsToolkit::SwizzleMethods([NSView class], @selector(mouseDownCanMoveWindow), michael@0: @selector(nsChildView_NSView_mouseDownCanMoveWindow)); michael@0: #ifdef __LP64__ michael@0: if (nsCocoaFeatures::OnLionOrLater()) { michael@0: nsToolkit::SwizzleMethods([NSEvent class], @selector(addLocalMonitorForEventsMatchingMask:handler:), michael@0: @selector(nsChildView_NSEvent_addLocalMonitorForEventsMatchingMask:handler:), michael@0: true); michael@0: nsToolkit::SwizzleMethods([NSEvent class], @selector(removeMonitor:), michael@0: @selector(nsChildView_NSEvent_removeMonitor:), true); michael@0: } michael@0: #else michael@0: TextInputHandler::SwizzleMethods(); michael@0: #endif michael@0: gChildViewMethodsSwizzled = true; michael@0: } michael@0: michael@0: mBounds = aRect; michael@0: michael@0: // Ensure that the toolkit is created. michael@0: nsToolkit::GetToolkit(); michael@0: michael@0: BaseCreate(aParent, aRect, aContext, aInitData); michael@0: michael@0: // inherit things from the parent view and create our parallel michael@0: // NSView in the Cocoa display system michael@0: mParentView = nil; michael@0: if (aParent) { michael@0: // inherit the top-level window. NS_NATIVE_WIDGET is always a NSView michael@0: // regardless of if we're asking a window or a view (for compatibility michael@0: // with windows). michael@0: mParentView = (NSView*)aParent->GetNativeData(NS_NATIVE_WIDGET); michael@0: mParentWidget = aParent; michael@0: } else { michael@0: // This is the normal case. When we're the root widget of the view hiararchy, michael@0: // aNativeParent will be the contentView of our window, since that's what michael@0: // nsCocoaWindow returns when asked for an NS_NATIVE_VIEW. michael@0: mParentView = reinterpret_cast*>(aNativeParent); michael@0: } michael@0: michael@0: // create our parallel NSView and hook it up to our parent. Recall michael@0: // that NS_NATIVE_WIDGET is the NSView. michael@0: CGFloat scaleFactor = nsCocoaUtils::GetBackingScaleFactor(mParentView); michael@0: NSRect r = nsCocoaUtils::DevPixelsToCocoaPoints(mBounds, scaleFactor); michael@0: mView = [(NSView*)CreateCocoaView(r) retain]; michael@0: if (!mView) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: [(ChildView*)mView setIsPluginView:(mWindowType == eWindowType_plugin)]; michael@0: michael@0: // If this view was created in a Gecko view hierarchy, the initial state michael@0: // is hidden. If the view is attached only to a native NSView but has michael@0: // no Gecko parent (as in embedding), the initial state is visible. michael@0: if (mParentWidget) michael@0: [mView setHidden:YES]; michael@0: else michael@0: mVisible = true; michael@0: michael@0: // Hook it up in the NSView hierarchy. michael@0: if (mParentView) { michael@0: [mParentView addSubview:mView]; michael@0: } michael@0: michael@0: // if this is a ChildView, make sure that our per-window data michael@0: // is set up michael@0: if ([mView isKindOfClass:[ChildView class]]) michael@0: [[WindowDataMap sharedWindowDataMap] ensureDataForWindow:[mView window]]; michael@0: michael@0: NS_ASSERTION(!mTextInputHandler, "mTextInputHandler has already existed"); michael@0: mTextInputHandler = new TextInputHandler(this, mView); michael@0: michael@0: return NS_OK; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; michael@0: } michael@0: michael@0: // Creates the appropriate child view. Override to create something other than michael@0: // our |ChildView| object. Autoreleases, so caller must retain. michael@0: NSView* michael@0: nsChildView::CreateCocoaView(NSRect inFrame) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; michael@0: michael@0: return [[[ChildView alloc] initWithFrame:inFrame geckoChild:this] autorelease]; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NIL; michael@0: } michael@0: michael@0: void nsChildView::TearDownView() michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: if (!mView) michael@0: return; michael@0: michael@0: NSWindow* win = [mView window]; michael@0: NSResponder* responder = [win firstResponder]; michael@0: michael@0: // We're being unhooked from the view hierarchy, don't leave our view michael@0: // or a child view as the window first responder. michael@0: if (responder && [responder isKindOfClass:[NSView class]] && michael@0: [(NSView*)responder isDescendantOf:mView]) { michael@0: [win makeFirstResponder:[mView superview]]; michael@0: } michael@0: michael@0: // If mView is win's contentView, win (mView's NSWindow) "owns" mView -- michael@0: // win has retained mView, and will detach it from the view hierarchy and michael@0: // release it when necessary (when win is itself destroyed (in a call to michael@0: // [win dealloc])). So all we need to do here is call [mView release] (to michael@0: // match the call to [mView retain] in nsChildView::StandardCreate()). michael@0: // Also calling [mView removeFromSuperviewWithoutNeedingDisplay] causes michael@0: // mView to be released again and dealloced, while remaining win's michael@0: // contentView. So if we do that here, win will (for a short while) have michael@0: // an invalid contentView (for the consequences see bmo bugs 381087 and michael@0: // 374260). michael@0: if ([mView isEqual:[win contentView]]) { michael@0: [mView release]; michael@0: } else { michael@0: // Stop NSView hierarchy being changed during [ChildView drawRect:] michael@0: [mView performSelectorOnMainThread:@selector(delayedTearDown) withObject:nil waitUntilDone:false]; michael@0: } michael@0: mView = nil; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: nsCocoaWindow* michael@0: nsChildView::GetXULWindowWidget() michael@0: { michael@0: id windowDelegate = [[mView window] delegate]; michael@0: if (windowDelegate && [windowDelegate isKindOfClass:[WindowDelegate class]]) { michael@0: return [(WindowDelegate *)windowDelegate geckoWidget]; michael@0: } michael@0: return nullptr; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsChildView::Destroy() michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; michael@0: michael@0: // Make sure that no composition is in progress while disconnecting michael@0: // ourselves from the view. michael@0: MutexAutoLock lock(mViewTearDownLock); michael@0: michael@0: if (mOnDestroyCalled) michael@0: return NS_OK; michael@0: mOnDestroyCalled = true; michael@0: michael@0: [mView widgetDestroyed]; michael@0: michael@0: nsBaseWidget::Destroy(); michael@0: michael@0: NotifyWindowDestroyed(); michael@0: mParentWidget = nil; michael@0: michael@0: TearDownView(); michael@0: michael@0: nsBaseWidget::OnDestroy(); michael@0: michael@0: return NS_OK; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; michael@0: } michael@0: michael@0: #pragma mark - michael@0: michael@0: #if 0 michael@0: static void PrintViewHierarchy(NSView *view) michael@0: { michael@0: while (view) { michael@0: NSLog(@" view is %x, frame %@", view, NSStringFromRect([view frame])); michael@0: view = [view superview]; michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: // Return native data according to aDataType michael@0: void* nsChildView::GetNativeData(uint32_t aDataType) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSNULL; michael@0: michael@0: void* retVal = nullptr; michael@0: michael@0: switch (aDataType) michael@0: { michael@0: case NS_NATIVE_WIDGET: michael@0: case NS_NATIVE_DISPLAY: michael@0: retVal = (void*)mView; michael@0: break; michael@0: michael@0: case NS_NATIVE_WINDOW: michael@0: retVal = [mView window]; michael@0: break; michael@0: michael@0: case NS_NATIVE_GRAPHIC: michael@0: NS_ERROR("Requesting NS_NATIVE_GRAPHIC on a Mac OS X child view!"); michael@0: retVal = nullptr; michael@0: break; michael@0: michael@0: case NS_NATIVE_OFFSETX: michael@0: retVal = 0; michael@0: break; michael@0: michael@0: case NS_NATIVE_OFFSETY: michael@0: retVal = 0; michael@0: break; michael@0: michael@0: case NS_NATIVE_PLUGIN_PORT: michael@0: case NS_NATIVE_PLUGIN_PORT_CG: michael@0: { michael@0: // The NP_CGContext pointer should always be NULL in the Cocoa event model. michael@0: if ([(ChildView*)mView pluginEventModel] == NPEventModelCocoa) michael@0: return nullptr; michael@0: michael@0: UpdatePluginPort(); michael@0: retVal = (void*)&mPluginCGContext; michael@0: break; michael@0: } michael@0: } michael@0: michael@0: return retVal; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NSNULL; michael@0: } michael@0: michael@0: #pragma mark - michael@0: michael@0: nsTransparencyMode nsChildView::GetTransparencyMode() michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; michael@0: michael@0: nsCocoaWindow* windowWidget = GetXULWindowWidget(); michael@0: return windowWidget ? windowWidget->GetTransparencyMode() : eTransparencyOpaque; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(eTransparencyOpaque); michael@0: } michael@0: michael@0: // This is called by nsContainerFrame on the root widget for all window types michael@0: // except popup windows (when nsCocoaWindow::SetTransparencyMode is used instead). michael@0: void nsChildView::SetTransparencyMode(nsTransparencyMode aMode) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: nsCocoaWindow* windowWidget = GetXULWindowWidget(); michael@0: if (windowWidget) { michael@0: windowWidget->SetTransparencyMode(aMode); michael@0: } michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: bool nsChildView::IsVisible() const michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; michael@0: michael@0: if (!mVisible) { michael@0: return mVisible; michael@0: } michael@0: michael@0: // mVisible does not accurately reflect the state of a hidden tabbed view michael@0: // so verify that the view has a window as well michael@0: // then check native widget hierarchy visibility michael@0: return ([mView window] != nil) && !NSIsEmptyRect([mView visibleRect]); michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false); michael@0: } michael@0: michael@0: void nsChildView::HidePlugin() michael@0: { michael@0: NS_ASSERTION(mWindowType == eWindowType_plugin, michael@0: "HidePlugin called on non-plugin view"); michael@0: } michael@0: michael@0: void nsChildView::UpdatePluginPort() michael@0: { michael@0: NS_ASSERTION(mWindowType == eWindowType_plugin, michael@0: "UpdatePluginPort called on non-plugin view"); michael@0: michael@0: // [NSGraphicsContext currentContext] is supposed to "return the michael@0: // current graphics context of the current thread." But sometimes michael@0: // (when called while mView isn't focused for drawing) it returns a michael@0: // graphics context for the wrong window. [window graphicsContext] michael@0: // (which "provides the graphics context associated with the window michael@0: // for the current thread") seems always to return the "right" michael@0: // graphics context. See bug 500130. michael@0: mPluginCGContext.context = NULL; michael@0: mPluginCGContext.window = NULL; michael@0: } michael@0: michael@0: static void HideChildPluginViews(NSView* aView) michael@0: { michael@0: NSArray* subviews = [aView subviews]; michael@0: michael@0: for (unsigned int i = 0; i < [subviews count]; ++i) { michael@0: NSView* view = [subviews objectAtIndex: i]; michael@0: michael@0: if (![view isKindOfClass:[ChildView class]]) michael@0: continue; michael@0: michael@0: ChildView* childview = static_cast(view); michael@0: if ([childview isPluginView]) { michael@0: nsChildView* widget = static_cast([childview widget]); michael@0: if (widget) { michael@0: widget->HidePlugin(); michael@0: } michael@0: } else { michael@0: HideChildPluginViews(view); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Some NSView methods (e.g. setFrame and setHidden) invalidate the view's michael@0: // bounds in our window. However, we don't want these invalidations because michael@0: // they are unnecessary and because they actually slow us down since we michael@0: // block on the compositor inside drawRect. michael@0: // When we actually need something invalidated, there will be an explicit call michael@0: // to Invalidate from Gecko, so turning these automatic invalidations off michael@0: // won't hurt us in the non-OMTC case. michael@0: // The invalidations inside these NSView methods happen via a call to the michael@0: // private method -[NSWindow _setNeedsDisplayInRect:]. Our BaseWindow michael@0: // implementation of that method is augmented to let us ignore those calls michael@0: // using -[BaseWindow disable/enableSetNeedsDisplay]. michael@0: static void michael@0: ManipulateViewWithoutNeedingDisplay(NSView* aView, void (^aCallback)()) michael@0: { michael@0: BaseWindow* win = nil; michael@0: if ([[aView window] isKindOfClass:[BaseWindow class]]) { michael@0: win = (BaseWindow*)[aView window]; michael@0: } michael@0: [win disableSetNeedsDisplay]; michael@0: aCallback(); michael@0: [win enableSetNeedsDisplay]; michael@0: } michael@0: michael@0: // Hide or show this component michael@0: NS_IMETHODIMP nsChildView::Show(bool aState) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; michael@0: michael@0: if (aState != mVisible) { michael@0: // Provide an autorelease pool because this gets called during startup michael@0: // on the "hidden window", resulting in cocoa object leakage if there's michael@0: // no pool in place. michael@0: nsAutoreleasePool localPool; michael@0: michael@0: ManipulateViewWithoutNeedingDisplay(mView, ^{ michael@0: [mView setHidden:!aState]; michael@0: }); michael@0: michael@0: mVisible = aState; michael@0: if (!mVisible && IsPluginView()) michael@0: HidePlugin(); michael@0: } michael@0: return NS_OK; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; michael@0: } michael@0: michael@0: // Change the parent of this widget michael@0: NS_IMETHODIMP michael@0: nsChildView::SetParent(nsIWidget* aNewParent) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; michael@0: michael@0: if (mOnDestroyCalled) michael@0: return NS_OK; michael@0: michael@0: nsCOMPtr kungFuDeathGrip(this); michael@0: michael@0: if (mParentWidget) { michael@0: mParentWidget->RemoveChild(this); michael@0: } michael@0: michael@0: if (aNewParent) { michael@0: ReparentNativeWidget(aNewParent); michael@0: } else { michael@0: [mView removeFromSuperview]; michael@0: mParentView = nil; michael@0: } michael@0: michael@0: mParentWidget = aNewParent; michael@0: michael@0: if (mParentWidget) { michael@0: mParentWidget->AddChild(this); michael@0: } michael@0: michael@0: return NS_OK; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsChildView::ReparentNativeWidget(nsIWidget* aNewParent) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; michael@0: michael@0: NS_PRECONDITION(aNewParent, ""); michael@0: michael@0: if (mOnDestroyCalled) michael@0: return NS_OK; michael@0: michael@0: NSView* newParentView = michael@0: (NSView*)aNewParent->GetNativeData(NS_NATIVE_WIDGET); michael@0: NS_ENSURE_TRUE(newParentView, NS_ERROR_FAILURE); michael@0: michael@0: // we hold a ref to mView, so this is safe michael@0: [mView removeFromSuperview]; michael@0: mParentView = newParentView; michael@0: [mParentView addSubview:mView]; michael@0: return NS_OK; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; michael@0: } michael@0: michael@0: void nsChildView::ResetParent() michael@0: { michael@0: if (!mOnDestroyCalled) { michael@0: if (mParentWidget) michael@0: mParentWidget->RemoveChild(this); michael@0: if (mView) michael@0: [mView removeFromSuperview]; michael@0: } michael@0: mParentWidget = nullptr; michael@0: } michael@0: michael@0: nsIWidget* michael@0: nsChildView::GetParent() michael@0: { michael@0: return mParentWidget; michael@0: } michael@0: michael@0: float michael@0: nsChildView::GetDPI() michael@0: { michael@0: NSWindow* window = [mView window]; michael@0: if (window && [window isKindOfClass:[BaseWindow class]]) { michael@0: return [(BaseWindow*)window getDPI]; michael@0: } michael@0: michael@0: return 96.0; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsChildView::Enable(bool aState) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool nsChildView::IsEnabled() const michael@0: { michael@0: return true; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsChildView::SetFocus(bool aRaise) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; michael@0: michael@0: NSWindow* window = [mView window]; michael@0: if (window) michael@0: [window makeFirstResponder:mView]; michael@0: return NS_OK; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; michael@0: } michael@0: michael@0: // Override to set the cursor on the mac michael@0: NS_IMETHODIMP nsChildView::SetCursor(nsCursor aCursor) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; michael@0: michael@0: if ([mView isDragInProgress]) michael@0: return NS_OK; // Don't change the cursor during dragging. michael@0: michael@0: nsBaseWidget::SetCursor(aCursor); michael@0: return [[nsCursorManager sharedInstance] setCursor:aCursor]; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; michael@0: } michael@0: michael@0: // implement to fix "hidden virtual function" warning michael@0: NS_IMETHODIMP nsChildView::SetCursor(imgIContainer* aCursor, michael@0: uint32_t aHotspotX, uint32_t aHotspotY) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; michael@0: michael@0: nsBaseWidget::SetCursor(aCursor, aHotspotX, aHotspotY); michael@0: return [[nsCursorManager sharedInstance] setCursorWithImage:aCursor hotSpotX:aHotspotX hotSpotY:aHotspotY scaleFactor:BackingScaleFactor()]; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; michael@0: } michael@0: michael@0: #pragma mark - michael@0: michael@0: // Get this component dimension michael@0: NS_IMETHODIMP nsChildView::GetBounds(nsIntRect &aRect) michael@0: { michael@0: if (!mView) { michael@0: aRect = mBounds; michael@0: } else { michael@0: aRect = CocoaPointsToDevPixels([mView frame]); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsChildView::GetClientBounds(nsIntRect &aRect) michael@0: { michael@0: GetBounds(aRect); michael@0: if (!mParentWidget) { michael@0: // For top level widgets we want the position on screen, not the position michael@0: // of this view inside the window. michael@0: MOZ_ASSERT(mWindowType != eWindowType_plugin, "plugin widgets should have parents"); michael@0: aRect.MoveTo(WidgetToScreenOffset()); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsChildView::GetScreenBounds(nsIntRect &aRect) michael@0: { michael@0: GetBounds(aRect); michael@0: aRect.MoveTo(WidgetToScreenOffset()); michael@0: return NS_OK; michael@0: } michael@0: michael@0: double michael@0: nsChildView::GetDefaultScaleInternal() michael@0: { michael@0: return BackingScaleFactor(); michael@0: } michael@0: michael@0: CGFloat michael@0: nsChildView::BackingScaleFactor() michael@0: { michael@0: if (mBackingScaleFactor > 0.0) { michael@0: return mBackingScaleFactor; michael@0: } michael@0: if (!mView) { michael@0: return 1.0; michael@0: } michael@0: mBackingScaleFactor = nsCocoaUtils::GetBackingScaleFactor(mView); michael@0: return mBackingScaleFactor; michael@0: } michael@0: michael@0: void michael@0: nsChildView::BackingScaleFactorChanged() michael@0: { michael@0: CGFloat newScale = nsCocoaUtils::GetBackingScaleFactor(mView); michael@0: michael@0: // ignore notification if it hasn't really changed (or maybe we have michael@0: // disabled HiDPI mode via prefs) michael@0: if (mBackingScaleFactor == newScale) { michael@0: return; michael@0: } michael@0: michael@0: mBackingScaleFactor = newScale; michael@0: michael@0: if (mWidgetListener && !mWidgetListener->GetXULWindow()) { michael@0: nsIPresShell* presShell = mWidgetListener->GetPresShell(); michael@0: if (presShell) { michael@0: presShell->BackingScaleFactorChanged(); michael@0: } michael@0: } michael@0: michael@0: if (IsPluginView()) { michael@0: nsEventStatus status = nsEventStatus_eIgnore; michael@0: WidgetGUIEvent guiEvent(true, NS_PLUGIN_RESOLUTION_CHANGED, this); michael@0: guiEvent.time = PR_IntervalNow(); michael@0: DispatchEvent(&guiEvent, status); michael@0: } michael@0: } michael@0: michael@0: int32_t michael@0: nsChildView::RoundsWidgetCoordinatesTo() michael@0: { michael@0: if (BackingScaleFactor() == 2.0) { michael@0: return 2; michael@0: } michael@0: return 1; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsChildView::ConstrainPosition(bool aAllowSlop, michael@0: int32_t *aX, int32_t *aY) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Move this component, aX and aY are in the parent widget coordinate system michael@0: NS_IMETHODIMP nsChildView::Move(double aX, double aY) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; michael@0: michael@0: int32_t x = NSToIntRound(aX); michael@0: int32_t y = NSToIntRound(aY); michael@0: michael@0: if (!mView || (mBounds.x == x && mBounds.y == y)) michael@0: return NS_OK; michael@0: michael@0: mBounds.x = x; michael@0: mBounds.y = y; michael@0: michael@0: ManipulateViewWithoutNeedingDisplay(mView, ^{ michael@0: [mView setFrame:DevPixelsToCocoaPoints(mBounds)]; michael@0: }); michael@0: michael@0: NotifyRollupGeometryChange(); michael@0: ReportMoveEvent(); michael@0: michael@0: return NS_OK; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsChildView::Resize(double aWidth, double aHeight, bool aRepaint) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; michael@0: michael@0: int32_t width = NSToIntRound(aWidth); michael@0: int32_t height = NSToIntRound(aHeight); michael@0: michael@0: if (!mView || (mBounds.width == width && mBounds.height == height)) michael@0: return NS_OK; michael@0: michael@0: mBounds.width = width; michael@0: mBounds.height = height; michael@0: michael@0: ManipulateViewWithoutNeedingDisplay(mView, ^{ michael@0: [mView setFrame:DevPixelsToCocoaPoints(mBounds)]; michael@0: }); michael@0: michael@0: if (mVisible && aRepaint) michael@0: [mView setNeedsDisplay:YES]; michael@0: michael@0: NotifyRollupGeometryChange(); michael@0: ReportSizeEvent(); michael@0: michael@0: return NS_OK; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsChildView::Resize(double aX, double aY, michael@0: double aWidth, double aHeight, bool aRepaint) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; michael@0: michael@0: int32_t x = NSToIntRound(aX); michael@0: int32_t y = NSToIntRound(aY); michael@0: int32_t width = NSToIntRound(aWidth); michael@0: int32_t height = NSToIntRound(aHeight); michael@0: michael@0: BOOL isMoving = (mBounds.x != x || mBounds.y != y); michael@0: BOOL isResizing = (mBounds.width != width || mBounds.height != height); michael@0: if (!mView || (!isMoving && !isResizing)) michael@0: return NS_OK; michael@0: michael@0: if (isMoving) { michael@0: mBounds.x = x; michael@0: mBounds.y = y; michael@0: } michael@0: if (isResizing) { michael@0: mBounds.width = width; michael@0: mBounds.height = height; michael@0: } michael@0: michael@0: ManipulateViewWithoutNeedingDisplay(mView, ^{ michael@0: [mView setFrame:DevPixelsToCocoaPoints(mBounds)]; michael@0: }); michael@0: michael@0: if (mVisible && aRepaint) michael@0: [mView setNeedsDisplay:YES]; michael@0: michael@0: NotifyRollupGeometryChange(); michael@0: if (isMoving) { michael@0: ReportMoveEvent(); michael@0: if (mOnDestroyCalled) michael@0: return NS_OK; michael@0: } michael@0: if (isResizing) michael@0: ReportSizeEvent(); michael@0: michael@0: return NS_OK; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; michael@0: } michael@0: michael@0: static const int32_t resizeIndicatorWidth = 15; michael@0: static const int32_t resizeIndicatorHeight = 15; michael@0: bool nsChildView::ShowsResizeIndicator(nsIntRect* aResizerRect) michael@0: { michael@0: NSView *topLevelView = mView, *superView = nil; michael@0: while ((superView = [topLevelView superview])) michael@0: topLevelView = superView; michael@0: michael@0: if (![[topLevelView window] showsResizeIndicator] || michael@0: !([[topLevelView window] styleMask] & NSResizableWindowMask)) michael@0: return false; michael@0: michael@0: if (aResizerRect) { michael@0: NSSize bounds = [topLevelView bounds].size; michael@0: NSPoint corner = NSMakePoint(bounds.width, [topLevelView isFlipped] ? bounds.height : 0); michael@0: corner = [topLevelView convertPoint:corner toView:mView]; michael@0: aResizerRect->SetRect(NSToIntRound(corner.x) - resizeIndicatorWidth, michael@0: NSToIntRound(corner.y) - resizeIndicatorHeight, michael@0: resizeIndicatorWidth, resizeIndicatorHeight); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: // In QuickDraw mode the coordinate system used here should be that of the michael@0: // browser window's content region (defined as everything but the 22-pixel michael@0: // high titlebar). But in CoreGraphics mode the coordinate system should be michael@0: // that of the browser window as a whole (including its titlebar). Both michael@0: // coordinate systems have a top-left origin. See bmo bug 474491. michael@0: // michael@0: // There's a bug in this method's code -- it currently uses the QuickDraw michael@0: // coordinate system for both the QuickDraw and CoreGraphics drawing modes. michael@0: // This bug is fixed by the patch for bug 474491. But the Flash plugin (both michael@0: // version 10.0.12.36 from Adobe and version 9.0 r151 from Apple) has Mozilla- michael@0: // specific code to work around this bug, which breaks when we fix it (see bmo michael@0: // bug 477077). So we'll need to coordinate releasing a fix for this bug with michael@0: // Adobe and other major plugin vendors that support the CoreGraphics mode. michael@0: // michael@0: // outClipRect and outOrigin are in display pixels, not device pixels. michael@0: NS_IMETHODIMP nsChildView::GetPluginClipRect(nsIntRect& outClipRect, nsIntPoint& outOrigin, bool& outWidgetVisible) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; michael@0: michael@0: NS_ASSERTION(mWindowType == eWindowType_plugin, michael@0: "GetPluginClipRect must only be called on a plugin widget"); michael@0: if (mWindowType != eWindowType_plugin) return NS_ERROR_FAILURE; michael@0: michael@0: NSWindow* window = [mView window]; michael@0: if (!window) return NS_ERROR_FAILURE; michael@0: michael@0: NSPoint viewOrigin = [mView convertPoint:NSZeroPoint toView:nil]; michael@0: NSRect frame = [[window contentView] frame]; michael@0: viewOrigin.y = frame.size.height - viewOrigin.y; michael@0: michael@0: // set up the clipping region for plugins. michael@0: NSRect visibleBounds = [mView visibleRect]; michael@0: NSPoint clipOrigin = [mView convertPoint:visibleBounds.origin toView:nil]; michael@0: michael@0: // Convert from cocoa to QuickDraw coordinates michael@0: clipOrigin.y = frame.size.height - clipOrigin.y; michael@0: michael@0: outClipRect.x = NSToIntRound(clipOrigin.x); michael@0: outClipRect.y = NSToIntRound(clipOrigin.y); michael@0: michael@0: // need to convert view's origin to window coordinates. michael@0: // then, encode as "SetOrigin" ready values. michael@0: outOrigin.x = -NSToIntRound(viewOrigin.x); michael@0: outOrigin.y = -NSToIntRound(viewOrigin.y); michael@0: michael@0: if (IsVisible() && [mView window] != nil) { michael@0: outClipRect.width = NSToIntRound(visibleBounds.origin.x + visibleBounds.size.width) - NSToIntRound(visibleBounds.origin.x); michael@0: outClipRect.height = NSToIntRound(visibleBounds.origin.y + visibleBounds.size.height) - NSToIntRound(visibleBounds.origin.y); michael@0: michael@0: if (mClipRects) { michael@0: nsIntRect clipBounds; michael@0: for (uint32_t i = 0; i < mClipRectCount; ++i) { michael@0: NSRect cocoaPoints = DevPixelsToCocoaPoints(mClipRects[i]); michael@0: clipBounds.UnionRect(clipBounds, nsIntRect(NSToIntRound(cocoaPoints.origin.x), michael@0: NSToIntRound(cocoaPoints.origin.y), michael@0: NSToIntRound(cocoaPoints.size.width), michael@0: NSToIntRound(cocoaPoints.size.height))); michael@0: } michael@0: outClipRect.IntersectRect(outClipRect, clipBounds - outOrigin); michael@0: } michael@0: michael@0: // XXXroc should this be !outClipRect.IsEmpty()? michael@0: outWidgetVisible = true; michael@0: } michael@0: else { michael@0: outClipRect.width = 0; michael@0: outClipRect.height = 0; michael@0: outWidgetVisible = false; michael@0: } michael@0: michael@0: return NS_OK; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsChildView::StartDrawPlugin() michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; michael@0: michael@0: NS_ASSERTION(mWindowType == eWindowType_plugin, michael@0: "StartDrawPlugin must only be called on a plugin widget"); michael@0: if (mWindowType != eWindowType_plugin) return NS_ERROR_FAILURE; michael@0: michael@0: // This code is necessary for CoreGraphics in 32-bit builds. michael@0: // See comments below about why. In 64-bit CoreGraphics mode we will not keep michael@0: // this region up to date, plugins should not depend on it. michael@0: #ifndef __LP64__ michael@0: NSWindow* window = [mView window]; michael@0: if (!window) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: // In QuickDraw drawing mode we used to prevent reentrant handling of any michael@0: // plugin event. But in CoreGraphics drawing mode only do this if the current michael@0: // plugin event isn't an update/paint event. This allows popupcontextmenu() michael@0: // to work properly from a plugin that supports the Cocoa event model, michael@0: // without regressing bug 409615. See bug 435041. (StartDrawPlugin() and michael@0: // EndDrawPlugin() wrap every call to nsIPluginInstance::HandleEvent() -- michael@0: // not just calls that "draw" or paint.) michael@0: if (mIsDispatchPaint && mPluginDrawing) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: // It appears that the WindowRef from which we get the plugin port undergoes the michael@0: // traditional BeginUpdate/EndUpdate cycle, which, if you recall, sets the visible michael@0: // region to the intersection of the visible region and the update region. Since michael@0: // we don't know here if we're being drawn inside a BeginUpdate/EndUpdate pair michael@0: // (which seem to occur in [NSWindow display]), and we don't want to have the burden michael@0: // of correctly doing Carbon invalidates of the plugin rect, we manually set the michael@0: // visible region to be the entire port every time. It is necessary to set up our michael@0: // window's port even for CoreGraphics plugins, because they may still use Carbon michael@0: // internally (see bug #420527 for details). michael@0: // michael@0: // Don't use this code if any of the QuickDraw APIs it currently requires are michael@0: // missing (as they probably will be on OS X 10.8 and up). michael@0: if (::NewRgn && ::GetQDGlobalsThePort && ::GetGWorld && ::SetGWorld && michael@0: ::IsPortOffscreen && ::GetMainDevice && ::SetOrigin && ::RectRgn && michael@0: ::SetPortVisibleRegion && ::SetPortClipRegion && ::DisposeRgn) { michael@0: RgnHandle pluginRegion = ::NewRgn(); michael@0: if (pluginRegion) { michael@0: CGrafPtr port = ::GetWindowPort(WindowRef([window windowRef])); michael@0: bool portChanged = (port != CGrafPtr(::GetQDGlobalsThePort())); michael@0: CGrafPtr oldPort; michael@0: GDHandle oldDevice; michael@0: michael@0: if (portChanged) { michael@0: ::GetGWorld(&oldPort, &oldDevice); michael@0: ::SetGWorld(port, ::IsPortOffscreen(port) ? nullptr : ::GetMainDevice()); michael@0: } michael@0: michael@0: ::SetOrigin(0, 0); michael@0: michael@0: nsIntRect clipRect; // this is in native window coordinates michael@0: nsIntPoint origin; michael@0: bool visible; michael@0: GetPluginClipRect(clipRect, origin, visible); michael@0: michael@0: // XXX if we're not visible, set an empty clip region? michael@0: Rect pluginRect; michael@0: ConvertGeckoRectToMacRect(clipRect, pluginRect); michael@0: michael@0: ::RectRgn(pluginRegion, &pluginRect); michael@0: ::SetPortVisibleRegion(port, pluginRegion); michael@0: ::SetPortClipRegion(port, pluginRegion); michael@0: michael@0: // now set up the origin for the plugin michael@0: ::SetOrigin(origin.x, origin.y); michael@0: michael@0: ::DisposeRgn(pluginRegion); michael@0: michael@0: if (portChanged) { michael@0: ::SetGWorld(oldPort, oldDevice); michael@0: } michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: mPluginDrawing = true; michael@0: return NS_OK; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsChildView::EndDrawPlugin() michael@0: { michael@0: NS_ASSERTION(mWindowType == eWindowType_plugin, michael@0: "EndDrawPlugin must only be called on a plugin widget"); michael@0: if (mWindowType != eWindowType_plugin) return NS_ERROR_FAILURE; michael@0: michael@0: mPluginDrawing = false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsChildView::SetPluginInstanceOwner(nsIPluginInstanceOwner* aInstanceOwner) michael@0: { michael@0: mPluginInstanceOwner = aInstanceOwner; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsChildView::SetPluginEventModel(int inEventModel) michael@0: { michael@0: [(ChildView*)mView setPluginEventModel:(NPEventModel)inEventModel]; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsChildView::GetPluginEventModel(int* outEventModel) michael@0: { michael@0: *outEventModel = [(ChildView*)mView pluginEventModel]; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsChildView::SetPluginDrawingModel(int inDrawingModel) michael@0: { michael@0: [(ChildView*)mView setPluginDrawingModel:(NPDrawingModel)inDrawingModel]; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsChildView::StartComplexTextInputForCurrentEvent() michael@0: { michael@0: return mTextInputHandler->StartComplexTextInputForCurrentEvent(); michael@0: } michael@0: michael@0: nsresult nsChildView::SynthesizeNativeKeyEvent(int32_t aNativeKeyboardLayout, michael@0: int32_t aNativeKeyCode, michael@0: uint32_t aModifierFlags, michael@0: const nsAString& aCharacters, michael@0: const nsAString& aUnmodifiedCharacters) michael@0: { michael@0: return mTextInputHandler->SynthesizeNativeKeyEvent(aNativeKeyboardLayout, michael@0: aNativeKeyCode, michael@0: aModifierFlags, michael@0: aCharacters, michael@0: aUnmodifiedCharacters); michael@0: } michael@0: michael@0: nsresult nsChildView::SynthesizeNativeMouseEvent(nsIntPoint aPoint, michael@0: uint32_t aNativeMessage, michael@0: uint32_t aModifierFlags) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; michael@0: michael@0: NSPoint pt = michael@0: nsCocoaUtils::DevPixelsToCocoaPoints(aPoint, BackingScaleFactor()); michael@0: michael@0: // Move the mouse cursor to the requested position and reconnect it to the mouse. michael@0: CGWarpMouseCursorPosition(NSPointToCGPoint(pt)); michael@0: CGAssociateMouseAndMouseCursorPosition(true); michael@0: michael@0: // aPoint is given with the origin on the top left, but convertScreenToBase michael@0: // expects a point in a coordinate system that has its origin on the bottom left. michael@0: NSPoint screenPoint = NSMakePoint(pt.x, nsCocoaUtils::FlippedScreenY(pt.y)); michael@0: NSPoint windowPoint = [[mView window] convertScreenToBase:screenPoint]; michael@0: michael@0: NSEvent* event = [NSEvent mouseEventWithType:(NSEventType)aNativeMessage michael@0: location:windowPoint michael@0: modifierFlags:aModifierFlags michael@0: timestamp:[NSDate timeIntervalSinceReferenceDate] michael@0: windowNumber:[[mView window] windowNumber] michael@0: context:nil michael@0: eventNumber:0 michael@0: clickCount:1 michael@0: pressure:0.0]; michael@0: michael@0: if (!event) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: if ([[mView window] isKindOfClass:[BaseWindow class]]) { michael@0: // Tracking area events don't end up in their tracking areas when sent michael@0: // through [NSApp sendEvent:], so pass them directly to the right methods. michael@0: BaseWindow* window = (BaseWindow*)[mView window]; michael@0: if (aNativeMessage == NSMouseEntered) { michael@0: [window mouseEntered:event]; michael@0: return NS_OK; michael@0: } michael@0: if (aNativeMessage == NSMouseExited) { michael@0: [window mouseExited:event]; michael@0: return NS_OK; michael@0: } michael@0: if (aNativeMessage == NSMouseMoved) { michael@0: [window mouseMoved:event]; michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: [NSApp sendEvent:event]; michael@0: return NS_OK; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; michael@0: } michael@0: michael@0: // First argument has to be an NSMenu representing the application's top-level michael@0: // menu bar. The returned item is *not* retained. michael@0: static NSMenuItem* NativeMenuItemWithLocation(NSMenu* menubar, NSString* locationString) michael@0: { michael@0: NSArray* indexes = [locationString componentsSeparatedByString:@"|"]; michael@0: unsigned int indexCount = [indexes count]; michael@0: if (indexCount == 0) michael@0: return nil; michael@0: michael@0: NSMenu* currentSubmenu = [NSApp mainMenu]; michael@0: for (unsigned int i = 0; i < indexCount; i++) { michael@0: int targetIndex; michael@0: // We remove the application menu from consideration for the top-level menu michael@0: if (i == 0) michael@0: targetIndex = [[indexes objectAtIndex:i] intValue] + 1; michael@0: else michael@0: targetIndex = [[indexes objectAtIndex:i] intValue]; michael@0: int itemCount = [currentSubmenu numberOfItems]; michael@0: if (targetIndex < itemCount) { michael@0: NSMenuItem* menuItem = [currentSubmenu itemAtIndex:targetIndex]; michael@0: // if this is the last index just return the menu item michael@0: if (i == (indexCount - 1)) michael@0: return menuItem; michael@0: // if this is not the last index find the submenu and keep going michael@0: if ([menuItem hasSubmenu]) michael@0: currentSubmenu = [menuItem submenu]; michael@0: else michael@0: return nil; michael@0: } michael@0: } michael@0: michael@0: return nil; michael@0: } michael@0: michael@0: // Used for testing native menu system structure and event handling. michael@0: NS_IMETHODIMP nsChildView::ActivateNativeMenuItemAt(const nsAString& indexString) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; michael@0: michael@0: NSString* locationString = [NSString stringWithCharacters:reinterpret_cast(indexString.BeginReading()) michael@0: length:indexString.Length()]; michael@0: NSMenuItem* item = NativeMenuItemWithLocation([NSApp mainMenu], locationString); michael@0: // We can't perform an action on an item with a submenu, that will raise michael@0: // an obj-c exception. michael@0: if (item && ![item hasSubmenu]) { michael@0: NSMenu* parent = [item menu]; michael@0: if (parent) { michael@0: // NSLog(@"Performing action for native menu item titled: %@\n", michael@0: // [[currentSubmenu itemAtIndex:targetIndex] title]); michael@0: [parent performActionForItemAtIndex:[parent indexOfItem:item]]; michael@0: return NS_OK; michael@0: } michael@0: } michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; michael@0: } michael@0: michael@0: // Used for testing native menu system structure and event handling. michael@0: NS_IMETHODIMP nsChildView::ForceUpdateNativeMenuAt(const nsAString& indexString) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; michael@0: michael@0: nsCocoaWindow *widget = GetXULWindowWidget(); michael@0: if (widget) { michael@0: nsMenuBarX* mb = widget->GetMenuBar(); michael@0: if (mb) { michael@0: if (indexString.IsEmpty()) michael@0: mb->ForceNativeMenuReload(); michael@0: else michael@0: mb->ForceUpdateNativeMenuAt(indexString); michael@0: } michael@0: } michael@0: return NS_OK; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; michael@0: } michael@0: michael@0: #pragma mark - michael@0: michael@0: #ifdef INVALIDATE_DEBUGGING michael@0: michael@0: static Boolean KeyDown(const UInt8 theKey) michael@0: { michael@0: KeyMap map; michael@0: GetKeys(map); michael@0: return ((*((UInt8 *)map + (theKey >> 3)) >> (theKey & 7)) & 1) != 0; michael@0: } michael@0: michael@0: static Boolean caps_lock() michael@0: { michael@0: return KeyDown(0x39); michael@0: } michael@0: michael@0: static void blinkRect(Rect* r) michael@0: { michael@0: StRegionFromPool oldClip; michael@0: if (oldClip != NULL) michael@0: ::GetClip(oldClip); michael@0: michael@0: ::ClipRect(r); michael@0: ::InvertRect(r); michael@0: UInt32 end = ::TickCount() + 5; michael@0: while (::TickCount() < end) ; michael@0: ::InvertRect(r); michael@0: michael@0: if (oldClip != NULL) michael@0: ::SetClip(oldClip); michael@0: } michael@0: michael@0: static void blinkRgn(RgnHandle rgn) michael@0: { michael@0: StRegionFromPool oldClip; michael@0: if (oldClip != NULL) michael@0: ::GetClip(oldClip); michael@0: michael@0: ::SetClip(rgn); michael@0: ::InvertRgn(rgn); michael@0: UInt32 end = ::TickCount() + 5; michael@0: while (::TickCount() < end) ; michael@0: ::InvertRgn(rgn); michael@0: michael@0: if (oldClip != NULL) michael@0: ::SetClip(oldClip); michael@0: } michael@0: michael@0: #endif michael@0: michael@0: // Invalidate this component's visible area michael@0: NS_IMETHODIMP nsChildView::Invalidate(const nsIntRect &aRect) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; michael@0: michael@0: if (!mView || !mVisible) michael@0: return NS_OK; michael@0: michael@0: NS_ASSERTION(GetLayerManager()->GetBackendType() != LayersBackend::LAYERS_CLIENT, michael@0: "Shouldn't need to invalidate with accelerated OMTC layers!"); michael@0: michael@0: if ([NSView focusView]) { michael@0: // if a view is focussed (i.e. being drawn), then postpone the invalidate so that we michael@0: // don't lose it. michael@0: [mView setNeedsPendingDisplayInRect:DevPixelsToCocoaPoints(aRect)]; michael@0: } michael@0: else { michael@0: [mView setNeedsDisplayInRect:DevPixelsToCocoaPoints(aRect)]; michael@0: } michael@0: michael@0: return NS_OK; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; michael@0: } michael@0: michael@0: bool michael@0: nsChildView::ComputeShouldAccelerate(bool aDefault) michael@0: { michael@0: // Don't use OpenGL for transparent windows or for popup windows. michael@0: if (!mView || ![[mView window] isOpaque] || michael@0: [[mView window] isKindOfClass:[PopupWindow class]]) michael@0: return false; michael@0: michael@0: return nsBaseWidget::ComputeShouldAccelerate(aDefault); michael@0: } michael@0: michael@0: bool michael@0: nsChildView::ShouldUseOffMainThreadCompositing() michael@0: { michael@0: // Don't use OMTC for transparent windows or for popup windows. michael@0: if (!mView || ![[mView window] isOpaque] || michael@0: [[mView window] isKindOfClass:[PopupWindow class]]) michael@0: return false; michael@0: michael@0: return nsBaseWidget::ShouldUseOffMainThreadCompositing(); michael@0: } michael@0: michael@0: inline uint16_t COLOR8TOCOLOR16(uint8_t color8) michael@0: { michael@0: // return (color8 == 0xFF ? 0xFFFF : (color8 << 8)); michael@0: return (color8 << 8) | color8; /* (color8 * 257) == (color8 * 0x0101) */ michael@0: } michael@0: michael@0: #pragma mark - michael@0: michael@0: nsresult nsChildView::ConfigureChildren(const nsTArray& aConfigurations) michael@0: { michael@0: for (uint32_t i = 0; i < aConfigurations.Length(); ++i) { michael@0: const Configuration& config = aConfigurations[i]; michael@0: nsChildView* child = static_cast(config.mChild); michael@0: #ifdef DEBUG michael@0: nsWindowType kidType = child->WindowType(); michael@0: #endif michael@0: NS_ASSERTION(kidType == eWindowType_plugin, michael@0: "Configured widget is not a plugin type"); michael@0: NS_ASSERTION(child->GetParent() == this, michael@0: "Configured widget is not a child of the right widget"); michael@0: michael@0: // nsIWidget::Show() doesn't get called on plugin widgets unless we call michael@0: // it from here. See bug 592563. michael@0: child->Show(!config.mClipRegion.IsEmpty()); michael@0: michael@0: child->Resize( michael@0: config.mBounds.x, config.mBounds.y, michael@0: config.mBounds.width, config.mBounds.height, michael@0: false); michael@0: michael@0: // Store the clip region here in case GetPluginClipRect needs it. michael@0: child->StoreWindowClipRegion(config.mClipRegion); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Invokes callback and ProcessEvent methods on Event Listener object michael@0: NS_IMETHODIMP nsChildView::DispatchEvent(WidgetGUIEvent* event, michael@0: nsEventStatus& aStatus) michael@0: { michael@0: #ifdef DEBUG michael@0: debug_DumpEvent(stdout, event->widget, event, nsAutoCString("something"), 0); michael@0: #endif michael@0: michael@0: NS_ASSERTION(!(mTextInputHandler && mTextInputHandler->IsIMEComposing() && michael@0: event->HasKeyEventMessage()), michael@0: "Any key events should not be fired during IME composing"); michael@0: michael@0: if (event->mFlags.mIsSynthesizedForTests) { michael@0: WidgetKeyboardEvent* keyEvent = event->AsKeyboardEvent(); michael@0: if (keyEvent) { michael@0: nsresult rv = mTextInputHandler->AttachNativeKeyEvent(*keyEvent); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: } michael@0: michael@0: aStatus = nsEventStatus_eIgnore; michael@0: michael@0: nsIWidgetListener* listener = mWidgetListener; michael@0: michael@0: // If the listener is NULL, check if the parent is a popup. If it is, then michael@0: // this child is the popup content view attached to a popup. Get the michael@0: // listener from the parent popup instead. michael@0: nsCOMPtr kungFuDeathGrip = do_QueryInterface(mParentWidget ? mParentWidget : this); michael@0: if (!listener && mParentWidget) { michael@0: if (mParentWidget->WindowType() == eWindowType_popup) { michael@0: // Check just in case event->widget isn't this widget michael@0: if (event->widget) michael@0: listener = event->widget->GetWidgetListener(); michael@0: if (!listener) { michael@0: event->widget = mParentWidget; michael@0: listener = mParentWidget->GetWidgetListener(); michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (listener) michael@0: aStatus = listener->HandleEvent(event, mUseAttachedEvents); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool nsChildView::DispatchWindowEvent(WidgetGUIEvent& event) michael@0: { michael@0: nsEventStatus status; michael@0: DispatchEvent(&event, status); michael@0: return ConvertStatus(status); michael@0: } michael@0: michael@0: nsIWidget* michael@0: nsChildView::GetWidgetForListenerEvents() michael@0: { michael@0: // If there is no listener, use the parent popup's listener if that exists. michael@0: if (!mWidgetListener && mParentWidget && michael@0: mParentWidget->WindowType() == eWindowType_popup) { michael@0: return mParentWidget; michael@0: } michael@0: michael@0: return this; michael@0: } michael@0: michael@0: void nsChildView::WillPaintWindow() michael@0: { michael@0: nsCOMPtr widget = GetWidgetForListenerEvents(); michael@0: michael@0: nsIWidgetListener* listener = widget->GetWidgetListener(); michael@0: if (listener) { michael@0: listener->WillPaintWindow(widget); michael@0: } michael@0: } michael@0: michael@0: bool nsChildView::PaintWindow(nsIntRegion aRegion) michael@0: { michael@0: nsCOMPtr widget = GetWidgetForListenerEvents(); michael@0: michael@0: nsIWidgetListener* listener = widget->GetWidgetListener(); michael@0: if (!listener) michael@0: return false; michael@0: michael@0: bool returnValue = false; michael@0: bool oldDispatchPaint = mIsDispatchPaint; michael@0: mIsDispatchPaint = true; michael@0: returnValue = listener->PaintWindow(widget, aRegion); michael@0: michael@0: listener = widget->GetWidgetListener(); michael@0: if (listener) { michael@0: listener->DidPaintWindow(); michael@0: } michael@0: michael@0: mIsDispatchPaint = oldDispatchPaint; michael@0: return returnValue; michael@0: } michael@0: michael@0: #pragma mark - michael@0: michael@0: void nsChildView::ReportMoveEvent() michael@0: { michael@0: NotifyWindowMoved(mBounds.x, mBounds.y); michael@0: } michael@0: michael@0: void nsChildView::ReportSizeEvent() michael@0: { michael@0: if (mWidgetListener) michael@0: mWidgetListener->WindowResized(this, mBounds.width, mBounds.height); michael@0: } michael@0: michael@0: #pragma mark - michael@0: michael@0: // Return the offset between this child view and the screen. michael@0: // @return -- widget origin in device-pixel coords michael@0: nsIntPoint nsChildView::WidgetToScreenOffset() michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; michael@0: michael@0: NSPoint origin = NSMakePoint(0, 0); michael@0: michael@0: // 1. First translate view origin point into window coords. michael@0: // The returned point is in bottom-left coordinates. michael@0: origin = [mView convertPoint:origin toView:nil]; michael@0: michael@0: // 2. We turn the window-coord rect's origin into screen (still bottom-left) coords. michael@0: origin = [[mView window] convertBaseToScreen:origin]; michael@0: michael@0: // 3. Since we're dealing in bottom-left coords, we need to make it top-left coords michael@0: // before we pass it back to Gecko. michael@0: FlipCocoaScreenCoordinate(origin); michael@0: michael@0: // convert to device pixels michael@0: return CocoaPointsToDevPixels(origin); michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(nsIntPoint(0,0)); michael@0: } michael@0: michael@0: NS_IMETHODIMP nsChildView::CaptureRollupEvents(nsIRollupListener * aListener, michael@0: bool aDoCapture) michael@0: { michael@0: // this never gets called, only top-level windows can be rollup widgets michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsChildView::SetTitle(const nsAString& title) michael@0: { michael@0: // child views don't have titles michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsChildView::GetAttention(int32_t aCycleCount) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; michael@0: michael@0: [NSApp requestUserAttention:NSInformationalRequest]; michael@0: return NS_OK; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; michael@0: } michael@0: michael@0: /* static */ michael@0: bool nsChildView::DoHasPendingInputEvent() michael@0: { michael@0: return sLastInputEventCount != GetCurrentInputEventCount(); michael@0: } michael@0: michael@0: /* static */ michael@0: uint32_t nsChildView::GetCurrentInputEventCount() michael@0: { michael@0: // Can't use kCGAnyInputEventType because that updates too rarely for us (and michael@0: // always in increments of 30+!) and because apparently it's sort of broken michael@0: // on Tiger. So just go ahead and query the counters we care about. michael@0: static const CGEventType eventTypes[] = { michael@0: kCGEventLeftMouseDown, michael@0: kCGEventLeftMouseUp, michael@0: kCGEventRightMouseDown, michael@0: kCGEventRightMouseUp, michael@0: kCGEventMouseMoved, michael@0: kCGEventLeftMouseDragged, michael@0: kCGEventRightMouseDragged, michael@0: kCGEventKeyDown, michael@0: kCGEventKeyUp, michael@0: kCGEventScrollWheel, michael@0: kCGEventTabletPointer, michael@0: kCGEventOtherMouseDown, michael@0: kCGEventOtherMouseUp, michael@0: kCGEventOtherMouseDragged michael@0: }; michael@0: michael@0: uint32_t eventCount = 0; michael@0: for (uint32_t i = 0; i < ArrayLength(eventTypes); ++i) { michael@0: eventCount += michael@0: CGEventSourceCounterForEventType(kCGEventSourceStateCombinedSessionState, michael@0: eventTypes[i]); michael@0: } michael@0: return eventCount; michael@0: } michael@0: michael@0: /* static */ michael@0: void nsChildView::UpdateCurrentInputEventCount() michael@0: { michael@0: sLastInputEventCount = GetCurrentInputEventCount(); michael@0: } michael@0: michael@0: bool nsChildView::HasPendingInputEvent() michael@0: { michael@0: return DoHasPendingInputEvent(); michael@0: } michael@0: michael@0: #pragma mark - michael@0: michael@0: NS_IMETHODIMP michael@0: nsChildView::NotifyIME(const IMENotification& aIMENotification) michael@0: { michael@0: switch (aIMENotification.mMessage) { michael@0: case REQUEST_TO_COMMIT_COMPOSITION: michael@0: NS_ENSURE_TRUE(mTextInputHandler, NS_ERROR_NOT_AVAILABLE); michael@0: mTextInputHandler->CommitIMEComposition(); michael@0: return NS_OK; michael@0: case REQUEST_TO_CANCEL_COMPOSITION: michael@0: NS_ENSURE_TRUE(mTextInputHandler, NS_ERROR_NOT_AVAILABLE); michael@0: mTextInputHandler->CancelIMEComposition(); michael@0: return NS_OK; michael@0: case NOTIFY_IME_OF_FOCUS: michael@0: if (mInputContext.IsPasswordEditor()) { michael@0: TextInputHandler::EnableSecureEventInput(); michael@0: } michael@0: michael@0: NS_ENSURE_TRUE(mTextInputHandler, NS_ERROR_NOT_AVAILABLE); michael@0: mTextInputHandler->OnFocusChangeInGecko(true); michael@0: return NS_OK; michael@0: case NOTIFY_IME_OF_BLUR: michael@0: // When we're going to be deactive, we must disable the secure event input michael@0: // mode, see the Carbon Event Manager Reference. michael@0: TextInputHandler::EnsureSecureEventInputDisabled(); michael@0: michael@0: NS_ENSURE_TRUE(mTextInputHandler, NS_ERROR_NOT_AVAILABLE); michael@0: mTextInputHandler->OnFocusChangeInGecko(false); michael@0: return NS_OK; michael@0: case NOTIFY_IME_OF_SELECTION_CHANGE: michael@0: NS_ENSURE_TRUE(mTextInputHandler, NS_ERROR_NOT_AVAILABLE); michael@0: mTextInputHandler->OnSelectionChange(); michael@0: default: michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: } michael@0: michael@0: NS_IMETHODIMP_(void) michael@0: nsChildView::SetInputContext(const InputContext& aContext, michael@0: const InputContextAction& aAction) michael@0: { michael@0: NS_ENSURE_TRUE_VOID(mTextInputHandler); michael@0: michael@0: if (mTextInputHandler->IsFocused()) { michael@0: if (aContext.IsPasswordEditor()) { michael@0: TextInputHandler::EnableSecureEventInput(); michael@0: } else { michael@0: TextInputHandler::EnsureSecureEventInputDisabled(); michael@0: } michael@0: } michael@0: michael@0: mInputContext = aContext; michael@0: switch (aContext.mIMEState.mEnabled) { michael@0: case IMEState::ENABLED: michael@0: case IMEState::PLUGIN: michael@0: mTextInputHandler->SetASCIICapableOnly(false); michael@0: mTextInputHandler->EnableIME(true); michael@0: if (mInputContext.mIMEState.mOpen != IMEState::DONT_CHANGE_OPEN_STATE) { michael@0: mTextInputHandler->SetIMEOpenState( michael@0: mInputContext.mIMEState.mOpen == IMEState::OPEN); michael@0: } michael@0: break; michael@0: case IMEState::DISABLED: michael@0: mTextInputHandler->SetASCIICapableOnly(false); michael@0: mTextInputHandler->EnableIME(false); michael@0: break; michael@0: case IMEState::PASSWORD: michael@0: mTextInputHandler->SetASCIICapableOnly(true); michael@0: mTextInputHandler->EnableIME(false); michael@0: break; michael@0: default: michael@0: NS_ERROR("not implemented!"); michael@0: } michael@0: } michael@0: michael@0: NS_IMETHODIMP_(InputContext) michael@0: nsChildView::GetInputContext() michael@0: { michael@0: switch (mInputContext.mIMEState.mEnabled) { michael@0: case IMEState::ENABLED: michael@0: case IMEState::PLUGIN: michael@0: if (mTextInputHandler) { michael@0: mInputContext.mIMEState.mOpen = michael@0: mTextInputHandler->IsIMEOpened() ? IMEState::OPEN : IMEState::CLOSED; michael@0: break; michael@0: } michael@0: // If mTextInputHandler is null, set CLOSED instead... michael@0: default: michael@0: mInputContext.mIMEState.mOpen = IMEState::CLOSED; michael@0: break; michael@0: } michael@0: mInputContext.mNativeIMEContext = [mView inputContext]; michael@0: // If input context isn't available on this widget, we should set |this| michael@0: // instead of nullptr since nullptr means that the platform has only one michael@0: // context per process. michael@0: if (!mInputContext.mNativeIMEContext) { michael@0: mInputContext.mNativeIMEContext = this; michael@0: } michael@0: return mInputContext; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsChildView::AttachNativeKeyEvent(mozilla::WidgetKeyboardEvent& aEvent) michael@0: { michael@0: NS_ENSURE_TRUE(mTextInputHandler, NS_ERROR_NOT_AVAILABLE); michael@0: return mTextInputHandler->AttachNativeKeyEvent(aEvent); michael@0: } michael@0: michael@0: NS_IMETHODIMP_(bool) michael@0: nsChildView::ExecuteNativeKeyBinding(NativeKeyBindingsType aType, michael@0: const WidgetKeyboardEvent& aEvent, michael@0: DoCommandCallback aCallback, michael@0: void* aCallbackData) michael@0: { michael@0: NativeKeyBindings* keyBindings = NativeKeyBindings::GetInstance(aType); michael@0: return keyBindings->Execute(aEvent, aCallback, aCallbackData); michael@0: } michael@0: michael@0: nsIMEUpdatePreference michael@0: nsChildView::GetIMEUpdatePreference() michael@0: { michael@0: return nsIMEUpdatePreference(nsIMEUpdatePreference::NOTIFY_SELECTION_CHANGE); michael@0: } michael@0: michael@0: NS_IMETHODIMP nsChildView::GetToggledKeyState(uint32_t aKeyCode, michael@0: bool* aLEDState) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; michael@0: michael@0: NS_ENSURE_ARG_POINTER(aLEDState); michael@0: uint32_t key; michael@0: switch (aKeyCode) { michael@0: case NS_VK_CAPS_LOCK: michael@0: key = alphaLock; michael@0: break; michael@0: case NS_VK_NUM_LOCK: michael@0: key = kEventKeyModifierNumLockMask; michael@0: break; michael@0: // Mac doesn't support SCROLL_LOCK state. michael@0: default: michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: uint32_t modifierFlags = ::GetCurrentKeyModifiers(); michael@0: *aLEDState = (modifierFlags & key) != 0; michael@0: return NS_OK; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; michael@0: } michael@0: michael@0: NSView* nsChildView::GetEditorView() michael@0: { michael@0: NSView* editorView = mView; michael@0: // We need to get editor's view. E.g., when the focus is in the bookmark michael@0: // dialog, the view is element of the dialog. At this time, the key michael@0: // events are processed the parent window's view that has native focus. michael@0: WidgetQueryContentEvent textContent(true, NS_QUERY_TEXT_CONTENT, this); michael@0: textContent.InitForQueryTextContent(0, 0); michael@0: DispatchWindowEvent(textContent); michael@0: if (textContent.mSucceeded && textContent.mReply.mFocusedWidget) { michael@0: NSView* view = static_cast*>( michael@0: textContent.mReply.mFocusedWidget->GetNativeData(NS_NATIVE_WIDGET)); michael@0: if (view) michael@0: editorView = view; michael@0: } michael@0: return editorView; michael@0: } michael@0: michael@0: #pragma mark - michael@0: michael@0: void michael@0: nsChildView::CreateCompositor() michael@0: { michael@0: nsBaseWidget::CreateCompositor(); michael@0: if (mCompositorChild) { michael@0: [(ChildView *)mView setUsingOMTCompositor:true]; michael@0: } michael@0: } michael@0: michael@0: gfxASurface* michael@0: nsChildView::GetThebesSurface() michael@0: { michael@0: if (!mTempThebesSurface) { michael@0: mTempThebesSurface = new gfxQuartzSurface(gfxSize(1, 1), gfxImageFormat::ARGB32); michael@0: } michael@0: michael@0: return mTempThebesSurface; michael@0: } michael@0: michael@0: void michael@0: nsChildView::NotifyDirtyRegion(const nsIntRegion& aDirtyRegion) michael@0: { michael@0: if ([(ChildView*)mView isCoveringTitlebar]) { michael@0: // We store the dirty region so that we know what to repaint in the titlebar. michael@0: mDirtyTitlebarRegion.Or(mDirtyTitlebarRegion, aDirtyRegion); michael@0: } michael@0: } michael@0: michael@0: nsIntRect michael@0: nsChildView::RectContainingTitlebarControls() michael@0: { michael@0: // Start with a thin strip at the top of the window for the highlight line. michael@0: NSRect rect = NSMakeRect(0, 0, [mView bounds].size.width, michael@0: [(ChildView*)mView cornerRadius]); michael@0: michael@0: // Add the rects of the titlebar controls. michael@0: for (id view in [(BaseWindow*)[mView window] titlebarControls]) { michael@0: rect = NSUnionRect(rect, [mView convertRect:[view bounds] fromView:view]); michael@0: } michael@0: return CocoaPointsToDevPixels(rect); michael@0: } michael@0: michael@0: void michael@0: nsChildView::PrepareWindowEffects() michael@0: { michael@0: MutexAutoLock lock(mEffectsLock); michael@0: mShowsResizeIndicator = ShowsResizeIndicator(&mResizeIndicatorRect); michael@0: mHasRoundedBottomCorners = [(ChildView*)mView hasRoundedBottomCorners]; michael@0: CGFloat cornerRadius = [(ChildView*)mView cornerRadius]; michael@0: mDevPixelCornerRadius = cornerRadius * BackingScaleFactor(); michael@0: mIsCoveringTitlebar = [(ChildView*)mView isCoveringTitlebar]; michael@0: mIsFullscreen = ([[mView window] styleMask] & NSFullScreenWindowMask) != 0; michael@0: if (mIsCoveringTitlebar) { michael@0: mTitlebarRect = RectContainingTitlebarControls(); michael@0: UpdateTitlebarCGContext(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsChildView::CleanupWindowEffects() michael@0: { michael@0: mResizerImage = nullptr; michael@0: mCornerMaskImage = nullptr; michael@0: mTitlebarImage = nullptr; michael@0: } michael@0: michael@0: bool michael@0: nsChildView::PreRender(LayerManagerComposite* aManager) michael@0: { michael@0: nsAutoPtr manager(GLManager::CreateGLManager(aManager)); michael@0: if (!manager) { michael@0: return true; michael@0: } michael@0: michael@0: // The lock makes sure that we don't attempt to tear down the view while michael@0: // compositing. That would make us unable to call postRender on it when the michael@0: // composition is done, thus keeping the GL context locked forever. michael@0: mViewTearDownLock.Lock(); michael@0: michael@0: NSOpenGLContext *glContext = GLContextCGL::Cast(manager->gl())->GetNSOpenGLContext(); michael@0: michael@0: if (![(ChildView*)mView preRender:glContext]) { michael@0: mViewTearDownLock.Unlock(); michael@0: return false; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: nsChildView::PostRender(LayerManagerComposite* aManager) michael@0: { michael@0: nsAutoPtr manager(GLManager::CreateGLManager(aManager)); michael@0: if (!manager) { michael@0: return; michael@0: } michael@0: NSOpenGLContext *glContext = GLContextCGL::Cast(manager->gl())->GetNSOpenGLContext(); michael@0: [(ChildView*)mView postRender:glContext]; michael@0: mViewTearDownLock.Unlock(); michael@0: } michael@0: michael@0: void michael@0: nsChildView::DrawWindowOverlay(LayerManagerComposite* aManager, nsIntRect aRect) michael@0: { michael@0: nsAutoPtr manager(GLManager::CreateGLManager(aManager)); michael@0: if (manager) { michael@0: DrawWindowOverlay(manager, aRect); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsChildView::DrawWindowOverlay(GLManager* aManager, nsIntRect aRect) michael@0: { michael@0: GLContext* gl = aManager->gl(); michael@0: ScopedGLState scopedScissorTestState(gl, LOCAL_GL_SCISSOR_TEST, false); michael@0: michael@0: MaybeDrawTitlebar(aManager, aRect); michael@0: MaybeDrawResizeIndicator(aManager, aRect); michael@0: MaybeDrawRoundedCorners(aManager, aRect); michael@0: } michael@0: michael@0: static void michael@0: ClearRegion(gfx::DrawTarget *aDT, nsIntRegion aRegion) michael@0: { michael@0: gfxUtils::ClipToRegion(aDT, aRegion); michael@0: aDT->ClearRect(gfx::Rect(0, 0, aDT->GetSize().width, aDT->GetSize().height)); michael@0: aDT->PopClip(); michael@0: } michael@0: michael@0: static void michael@0: DrawResizer(CGContextRef aCtx) michael@0: { michael@0: CGContextSetShouldAntialias(aCtx, false); michael@0: CGPoint points[6]; michael@0: points[0] = CGPointMake(13.0f, 4.0f); michael@0: points[1] = CGPointMake(3.0f, 14.0f); michael@0: points[2] = CGPointMake(13.0f, 8.0f); michael@0: points[3] = CGPointMake(7.0f, 14.0f); michael@0: points[4] = CGPointMake(13.0f, 12.0f); michael@0: points[5] = CGPointMake(11.0f, 14.0f); michael@0: CGContextSetRGBStrokeColor(aCtx, 0.00f, 0.00f, 0.00f, 0.15f); michael@0: CGContextStrokeLineSegments(aCtx, points, 6); michael@0: michael@0: points[0] = CGPointMake(13.0f, 5.0f); michael@0: points[1] = CGPointMake(4.0f, 14.0f); michael@0: points[2] = CGPointMake(13.0f, 9.0f); michael@0: points[3] = CGPointMake(8.0f, 14.0f); michael@0: points[4] = CGPointMake(13.0f, 13.0f); michael@0: points[5] = CGPointMake(12.0f, 14.0f); michael@0: CGContextSetRGBStrokeColor(aCtx, 0.13f, 0.13f, 0.13f, 0.54f); michael@0: CGContextStrokeLineSegments(aCtx, points, 6); michael@0: michael@0: points[0] = CGPointMake(13.0f, 6.0f); michael@0: points[1] = CGPointMake(5.0f, 14.0f); michael@0: points[2] = CGPointMake(13.0f, 10.0f); michael@0: points[3] = CGPointMake(9.0f, 14.0f); michael@0: points[5] = CGPointMake(13.0f, 13.9f); michael@0: points[4] = CGPointMake(13.0f, 14.0f); michael@0: CGContextSetRGBStrokeColor(aCtx, 0.84f, 0.84f, 0.84f, 0.55f); michael@0: CGContextStrokeLineSegments(aCtx, points, 6); michael@0: } michael@0: michael@0: void michael@0: nsChildView::MaybeDrawResizeIndicator(GLManager* aManager, const nsIntRect& aRect) michael@0: { michael@0: MutexAutoLock lock(mEffectsLock); michael@0: if (!mShowsResizeIndicator) { michael@0: return; michael@0: } michael@0: michael@0: if (!mResizerImage) { michael@0: mResizerImage = new RectTextureImage(aManager->gl()); michael@0: } michael@0: michael@0: nsIntSize size = mResizeIndicatorRect.Size(); michael@0: mResizerImage->UpdateIfNeeded(size, nsIntRegion(), ^(gfx::DrawTarget* drawTarget, const nsIntRegion& updateRegion) { michael@0: ClearRegion(drawTarget, updateRegion); michael@0: gfx::BorrowedCGContext borrow(drawTarget); michael@0: DrawResizer(borrow.cg); michael@0: borrow.Finish(); michael@0: }); michael@0: michael@0: mResizerImage->Draw(aManager, mResizeIndicatorRect.TopLeft()); michael@0: } michael@0: michael@0: // Draw the highlight line at the top of the titlebar. michael@0: // This function draws into the current NSGraphicsContext and assumes flippedness. michael@0: static void michael@0: DrawTitlebarHighlight(NSSize aWindowSize, CGFloat aRadius, CGFloat aDevicePixelWidth) michael@0: { michael@0: [NSGraphicsContext saveGraphicsState]; michael@0: michael@0: // Set up the clip path. We start with the outer rectangle and cut out a michael@0: // slightly smaller inner rectangle with rounded corners. michael@0: // The outer corners of the resulting path will be square, but they will be michael@0: // masked away in a later step. michael@0: NSBezierPath* path = [NSBezierPath bezierPath]; michael@0: [path setWindingRule:NSEvenOddWindingRule]; michael@0: NSRect pathRect = NSMakeRect(0, 0, aWindowSize.width, aRadius + 2); michael@0: [path appendBezierPathWithRect:pathRect]; michael@0: pathRect = NSInsetRect(pathRect, aDevicePixelWidth, aDevicePixelWidth); michael@0: CGFloat innerRadius = aRadius - aDevicePixelWidth; michael@0: [path appendBezierPathWithRoundedRect:pathRect xRadius:innerRadius yRadius:innerRadius]; michael@0: [path addClip]; michael@0: michael@0: // Now we fill the path with a subtle highlight gradient. michael@0: // We don't use NSGradient because it's 5x to 15x slower than the manual fill, michael@0: // as indicated by the performance test in bug 880620. michael@0: for (CGFloat y = 0; y < aRadius; y += aDevicePixelWidth) { michael@0: CGFloat t = y / aRadius; michael@0: [[NSColor colorWithDeviceWhite:1.0 alpha:0.4 * (1.0 - t)] set]; michael@0: NSRectFill(NSMakeRect(0, y, aWindowSize.width, aDevicePixelWidth)); michael@0: } michael@0: michael@0: [NSGraphicsContext restoreGraphicsState]; michael@0: } michael@0: michael@0: static CGContextRef michael@0: CreateCGContext(const nsIntSize& aSize) michael@0: { michael@0: CGColorSpaceRef cs = CGColorSpaceCreateDeviceRGB(); michael@0: CGContextRef ctx = michael@0: CGBitmapContextCreate(NULL, michael@0: aSize.width, michael@0: aSize.height, michael@0: 8 /* bitsPerComponent */, michael@0: aSize.width * 4, michael@0: cs, michael@0: kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst); michael@0: CGColorSpaceRelease(cs); michael@0: michael@0: CGContextTranslateCTM(ctx, 0, aSize.height); michael@0: CGContextScaleCTM(ctx, 1, -1); michael@0: CGContextSetInterpolationQuality(ctx, kCGInterpolationLow); michael@0: michael@0: return ctx; michael@0: } michael@0: michael@0: // When this method is entered, mEffectsLock is already being held. michael@0: void michael@0: nsChildView::UpdateTitlebarCGContext() michael@0: { michael@0: nsIntRegion dirtyTitlebarRegion; michael@0: dirtyTitlebarRegion.And(mDirtyTitlebarRegion, mTitlebarRect); michael@0: mDirtyTitlebarRegion.SetEmpty(); michael@0: michael@0: nsIntSize texSize = RectTextureImage::TextureSizeForSize(mTitlebarRect.Size()); michael@0: if (!mTitlebarCGContext || michael@0: CGBitmapContextGetWidth(mTitlebarCGContext) != size_t(texSize.width) || michael@0: CGBitmapContextGetHeight(mTitlebarCGContext) != size_t(texSize.height)) { michael@0: dirtyTitlebarRegion = mTitlebarRect; michael@0: michael@0: ReleaseTitlebarCGContext(); michael@0: michael@0: mTitlebarCGContext = CreateCGContext(texSize); michael@0: } michael@0: michael@0: if (dirtyTitlebarRegion.IsEmpty()) michael@0: return; michael@0: michael@0: CGContextRef ctx = mTitlebarCGContext; michael@0: michael@0: CGContextSaveGState(ctx); michael@0: michael@0: std::vector rects; michael@0: nsIntRegionRectIterator iter(dirtyTitlebarRegion); michael@0: for (;;) { michael@0: const nsIntRect* r = iter.Next(); michael@0: if (!r) michael@0: break; michael@0: rects.push_back(CGRectMake(r->x, r->y, r->width, r->height)); michael@0: } michael@0: CGContextClipToRects(ctx, rects.data(), rects.size()); michael@0: michael@0: CGContextClearRect(ctx, CGRectMake(0, 0, texSize.width, texSize.height)); michael@0: michael@0: double scale = BackingScaleFactor(); michael@0: CGContextScaleCTM(ctx, scale, scale); michael@0: NSGraphicsContext* oldContext = [NSGraphicsContext currentContext]; michael@0: michael@0: CGContextSaveGState(ctx); michael@0: michael@0: BaseWindow* window = (BaseWindow*)[mView window]; michael@0: NSView* frameView = [[window contentView] superview]; michael@0: if (![frameView isFlipped]) { michael@0: CGContextTranslateCTM(ctx, 0, [frameView bounds].size.height); michael@0: CGContextScaleCTM(ctx, 1, -1); michael@0: } michael@0: NSGraphicsContext* context = [NSGraphicsContext graphicsContextWithGraphicsPort:ctx flipped:[frameView isFlipped]]; michael@0: [NSGraphicsContext setCurrentContext:context]; michael@0: michael@0: // Draw the title string. michael@0: if ([window wantsTitleDrawn] && [frameView respondsToSelector:@selector(_drawTitleBar:)]) { michael@0: [frameView _drawTitleBar:[frameView bounds]]; michael@0: } michael@0: michael@0: // Draw the titlebar controls into the titlebar image. michael@0: for (id view in [window titlebarControls]) { michael@0: NSRect viewFrame = [view frame]; michael@0: nsIntRect viewRect = CocoaPointsToDevPixels([mView convertRect:viewFrame fromView:frameView]); michael@0: if (!dirtyTitlebarRegion.Intersects(viewRect)) { michael@0: continue; michael@0: } michael@0: // All of the titlebar controls we're interested in are subclasses of michael@0: // NSButton. michael@0: if (![view isKindOfClass:[NSButton class]]) { michael@0: continue; michael@0: } michael@0: NSButton *button = (NSButton *) view; michael@0: id cellObject = [button cell]; michael@0: if (![cellObject isKindOfClass:[NSCell class]]) { michael@0: continue; michael@0: } michael@0: NSCell *cell = (NSCell *) cellObject; michael@0: michael@0: CGContextSaveGState(ctx); michael@0: CGContextTranslateCTM(ctx, viewFrame.origin.x, viewFrame.origin.y); michael@0: michael@0: if ([context isFlipped] != [view isFlipped]) { michael@0: CGContextTranslateCTM(ctx, 0, viewFrame.size.height); michael@0: CGContextScaleCTM(ctx, 1, -1); michael@0: } michael@0: michael@0: [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:ctx flipped:[view isFlipped]]]; michael@0: michael@0: [cell drawWithFrame:[button bounds] inView:button]; michael@0: michael@0: [NSGraphicsContext setCurrentContext:context]; michael@0: CGContextRestoreGState(ctx); michael@0: } michael@0: michael@0: CGContextRestoreGState(ctx); michael@0: michael@0: DrawTitlebarHighlight([frameView bounds].size, [(ChildView*)mView cornerRadius], michael@0: DevPixelsToCocoaPoints(1)); michael@0: michael@0: [NSGraphicsContext setCurrentContext:oldContext]; michael@0: michael@0: CGContextRestoreGState(ctx); michael@0: michael@0: mUpdatedTitlebarRegion.Or(mUpdatedTitlebarRegion, dirtyTitlebarRegion); michael@0: } michael@0: michael@0: // This method draws an overlay in the top of the window which contains the michael@0: // titlebar controls (e.g. close, min, zoom, fullscreen) and the titlebar michael@0: // highlight effect. michael@0: // This is necessary because the real titlebar controls are covered by our michael@0: // OpenGL context. Note that in terms of the NSView hierarchy, our ChildView michael@0: // is actually below the titlebar controls - that's why hovering and clicking michael@0: // them works as expected - but their visual representation is only drawn into michael@0: // the normal window buffer, and the window buffer surface lies below the michael@0: // GLContext surface. In order to make the titlebar controls visible, we have michael@0: // to redraw them inside the OpenGL context surface. michael@0: void michael@0: nsChildView::MaybeDrawTitlebar(GLManager* aManager, const nsIntRect& aRect) michael@0: { michael@0: MutexAutoLock lock(mEffectsLock); michael@0: if (!mIsCoveringTitlebar || mIsFullscreen) { michael@0: return; michael@0: } michael@0: michael@0: nsIntRegion updatedTitlebarRegion; michael@0: updatedTitlebarRegion.And(mUpdatedTitlebarRegion, mTitlebarRect); michael@0: mUpdatedTitlebarRegion.SetEmpty(); michael@0: michael@0: if (!mTitlebarImage) { michael@0: mTitlebarImage = new RectTextureImage(aManager->gl()); michael@0: } michael@0: michael@0: mTitlebarImage->UpdateFromCGContext(mTitlebarRect.Size(), michael@0: updatedTitlebarRegion, michael@0: mTitlebarCGContext); michael@0: michael@0: mTitlebarImage->Draw(aManager, mTitlebarRect.TopLeft()); michael@0: } michael@0: michael@0: static void michael@0: DrawTopLeftCornerMask(CGContextRef aCtx, int aRadius) michael@0: { michael@0: CGContextSetRGBFillColor(aCtx, 1.0, 1.0, 1.0, 1.0); michael@0: CGContextFillEllipseInRect(aCtx, CGRectMake(0, 0, aRadius * 2, aRadius * 2)); michael@0: } michael@0: michael@0: void michael@0: nsChildView::MaybeDrawRoundedCorners(GLManager* aManager, const nsIntRect& aRect) michael@0: { michael@0: MutexAutoLock lock(mEffectsLock); michael@0: michael@0: if (!mCornerMaskImage) { michael@0: mCornerMaskImage = new RectTextureImage(aManager->gl()); michael@0: } michael@0: michael@0: nsIntSize size(mDevPixelCornerRadius, mDevPixelCornerRadius); michael@0: mCornerMaskImage->UpdateIfNeeded(size, nsIntRegion(), ^(gfx::DrawTarget* drawTarget, const nsIntRegion& updateRegion) { michael@0: ClearRegion(drawTarget, updateRegion); michael@0: RefPtr builder = drawTarget->CreatePathBuilder(); michael@0: builder->Arc(gfx::Point(mDevPixelCornerRadius, mDevPixelCornerRadius), mDevPixelCornerRadius, 0, 2.0f * M_PI); michael@0: RefPtr path = builder->Finish(); michael@0: drawTarget->Fill(path, michael@0: gfx::ColorPattern(gfx::Color(1.0, 1.0, 1.0, 1.0)), michael@0: gfx::DrawOptions(1.0f, gfx::CompositionOp::OP_SOURCE)); michael@0: }); michael@0: michael@0: // Use operator destination in: multiply all 4 channels with source alpha. michael@0: aManager->gl()->fBlendFuncSeparate(LOCAL_GL_ZERO, LOCAL_GL_SRC_ALPHA, michael@0: LOCAL_GL_ZERO, LOCAL_GL_SRC_ALPHA); michael@0: michael@0: gfx3DMatrix flipX = gfx3DMatrix::ScalingMatrix(-1, 1, 1); michael@0: gfx3DMatrix flipY = gfx3DMatrix::ScalingMatrix(1, -1, 1); michael@0: michael@0: if (mIsCoveringTitlebar && !mIsFullscreen) { michael@0: // Mask the top corners. michael@0: mCornerMaskImage->Draw(aManager, aRect.TopLeft()); michael@0: mCornerMaskImage->Draw(aManager, aRect.TopRight(), flipX); michael@0: } michael@0: michael@0: if (mHasRoundedBottomCorners && !mIsFullscreen) { michael@0: // Mask the bottom corners. michael@0: mCornerMaskImage->Draw(aManager, aRect.BottomLeft(), flipY); michael@0: mCornerMaskImage->Draw(aManager, aRect.BottomRight(), flipY * flipX); michael@0: } michael@0: michael@0: // Reset blend mode. michael@0: aManager->gl()->fBlendFuncSeparate(LOCAL_GL_ONE, LOCAL_GL_ONE_MINUS_SRC_ALPHA, michael@0: LOCAL_GL_ONE, LOCAL_GL_ONE); michael@0: } michael@0: michael@0: static int32_t michael@0: FindTitlebarBottom(const nsTArray& aThemeGeometries, michael@0: int32_t aWindowWidth) michael@0: { michael@0: int32_t titlebarBottom = 0; michael@0: for (uint32_t i = 0; i < aThemeGeometries.Length(); ++i) { michael@0: const nsIWidget::ThemeGeometry& g = aThemeGeometries[i]; michael@0: if ((g.mWidgetType == NS_THEME_WINDOW_TITLEBAR) && michael@0: g.mRect.X() <= 0 && michael@0: g.mRect.XMost() >= aWindowWidth && michael@0: g.mRect.Y() <= 0) { michael@0: titlebarBottom = std::max(titlebarBottom, g.mRect.YMost()); michael@0: } michael@0: } michael@0: return titlebarBottom; michael@0: } michael@0: michael@0: static int32_t michael@0: FindUnifiedToolbarBottom(const nsTArray& aThemeGeometries, michael@0: int32_t aWindowWidth, int32_t aTitlebarBottom) michael@0: { michael@0: int32_t unifiedToolbarBottom = aTitlebarBottom; michael@0: for (uint32_t i = 0; i < aThemeGeometries.Length(); ++i) { michael@0: const nsIWidget::ThemeGeometry& g = aThemeGeometries[i]; michael@0: if ((g.mWidgetType == NS_THEME_MOZ_MAC_UNIFIED_TOOLBAR || michael@0: g.mWidgetType == NS_THEME_TOOLBAR) && michael@0: g.mRect.X() <= 0 && michael@0: g.mRect.XMost() >= aWindowWidth && michael@0: g.mRect.Y() <= aTitlebarBottom) { michael@0: unifiedToolbarBottom = std::max(unifiedToolbarBottom, g.mRect.YMost()); michael@0: } michael@0: } michael@0: return unifiedToolbarBottom; michael@0: } michael@0: michael@0: static nsIntRect michael@0: FindFirstRectOfType(const nsTArray& aThemeGeometries, michael@0: uint8_t aWidgetType) michael@0: { michael@0: for (uint32_t i = 0; i < aThemeGeometries.Length(); ++i) { michael@0: const nsIWidget::ThemeGeometry& g = aThemeGeometries[i]; michael@0: if (g.mWidgetType == aWidgetType) { michael@0: return g.mRect; michael@0: } michael@0: } michael@0: return nsIntRect(); michael@0: } michael@0: michael@0: void michael@0: nsChildView::UpdateThemeGeometries(const nsTArray& aThemeGeometries) michael@0: { michael@0: if (![mView window] || ![[mView window] isKindOfClass:[ToolbarWindow class]]) michael@0: return; michael@0: michael@0: // Update unified toolbar height. michael@0: int32_t windowWidth = mBounds.width; michael@0: int32_t titlebarBottom = FindTitlebarBottom(aThemeGeometries, windowWidth); michael@0: int32_t unifiedToolbarBottom = michael@0: FindUnifiedToolbarBottom(aThemeGeometries, windowWidth, titlebarBottom); michael@0: michael@0: ToolbarWindow* win = (ToolbarWindow*)[mView window]; michael@0: bool drawsContentsIntoWindowFrame = [win drawsContentsIntoWindowFrame]; michael@0: int32_t titlebarHeight = CocoaPointsToDevPixels([win titlebarHeight]); michael@0: int32_t contentOffset = drawsContentsIntoWindowFrame ? titlebarHeight : 0; michael@0: int32_t devUnifiedHeight = titlebarHeight + unifiedToolbarBottom - contentOffset; michael@0: [win setUnifiedToolbarHeight:DevPixelsToCocoaPoints(devUnifiedHeight)]; michael@0: michael@0: // Update titlebar control offsets. michael@0: nsIntRect windowButtonRect = FindFirstRectOfType(aThemeGeometries, NS_THEME_WINDOW_BUTTON_BOX); michael@0: [win placeWindowButtons:[mView convertRect:DevPixelsToCocoaPoints(windowButtonRect) toView:nil]]; michael@0: nsIntRect fullScreenButtonRect = FindFirstRectOfType(aThemeGeometries, NS_THEME_MOZ_MAC_FULLSCREEN_BUTTON); michael@0: [win placeFullScreenButton:[mView convertRect:DevPixelsToCocoaPoints(fullScreenButtonRect) toView:nil]]; michael@0: } michael@0: michael@0: TemporaryRef michael@0: nsChildView::StartRemoteDrawing() michael@0: { michael@0: if (!mGLPresenter) { michael@0: mGLPresenter = GLPresenter::CreateForWindow(this); michael@0: michael@0: if (!mGLPresenter) { michael@0: return nullptr; michael@0: } michael@0: } michael@0: michael@0: nsIntRegion dirtyRegion = mBounds; michael@0: nsIntSize renderSize = mBounds.Size(); michael@0: michael@0: if (!mBasicCompositorImage) { michael@0: mBasicCompositorImage = new RectTextureImage(mGLPresenter->gl()); michael@0: } michael@0: michael@0: RefPtr drawTarget = michael@0: mBasicCompositorImage->BeginUpdate(renderSize, dirtyRegion); michael@0: michael@0: if (!drawTarget) { michael@0: // Composite unchanged textures. michael@0: DoRemoteComposition(mBounds); michael@0: return nullptr; michael@0: } michael@0: michael@0: return drawTarget; michael@0: } michael@0: michael@0: void michael@0: nsChildView::EndRemoteDrawing() michael@0: { michael@0: mBasicCompositorImage->EndUpdate(true); michael@0: DoRemoteComposition(mBounds); michael@0: } michael@0: michael@0: void michael@0: nsChildView::CleanupRemoteDrawing() michael@0: { michael@0: mBasicCompositorImage = nullptr; michael@0: mCornerMaskImage = nullptr; michael@0: mResizerImage = nullptr; michael@0: mTitlebarImage = nullptr; michael@0: mGLPresenter = nullptr; michael@0: } michael@0: michael@0: void michael@0: nsChildView::DoRemoteComposition(const nsIntRect& aRenderRect) michael@0: { michael@0: if (![(ChildView*)mView preRender:mGLPresenter->GetNSOpenGLContext()]) { michael@0: return; michael@0: } michael@0: mGLPresenter->BeginFrame(aRenderRect.Size()); michael@0: michael@0: // Draw the result from the basic compositor. michael@0: mBasicCompositorImage->Draw(mGLPresenter, nsIntPoint(0, 0)); michael@0: michael@0: // DrawWindowOverlay doesn't do anything for non-GL, so it didn't paint michael@0: // anything during the basic compositor transaction. Draw the overlay now. michael@0: DrawWindowOverlay(mGLPresenter, aRenderRect); michael@0: michael@0: mGLPresenter->EndFrame(); michael@0: michael@0: [(ChildView*)mView postRender:mGLPresenter->GetNSOpenGLContext()]; michael@0: } michael@0: michael@0: #ifdef ACCESSIBILITY michael@0: already_AddRefed michael@0: nsChildView::GetDocumentAccessible() michael@0: { michael@0: if (!mozilla::a11y::ShouldA11yBeEnabled()) michael@0: return nullptr; michael@0: michael@0: if (mAccessible) { michael@0: nsRefPtr ret; michael@0: CallQueryReferent(mAccessible.get(), michael@0: static_cast(getter_AddRefs(ret))); michael@0: return ret.forget(); michael@0: } michael@0: michael@0: // need to fetch the accessible anew, because it has gone away. michael@0: // cache the accessible in our weak ptr michael@0: nsRefPtr acc = GetRootAccessible(); michael@0: mAccessible = do_GetWeakReference(static_cast(acc.get())); michael@0: michael@0: return acc.forget(); michael@0: } michael@0: #endif michael@0: michael@0: // RectTextureImage implementation michael@0: michael@0: RectTextureImage::~RectTextureImage() michael@0: { michael@0: if (mTexture) { michael@0: mGLContext->MakeCurrent(); michael@0: mGLContext->fDeleteTextures(1, &mTexture); michael@0: mTexture = 0; michael@0: } michael@0: } michael@0: michael@0: nsIntSize michael@0: RectTextureImage::TextureSizeForSize(const nsIntSize& aSize) michael@0: { michael@0: return nsIntSize(gfx::NextPowerOfTwo(aSize.width), michael@0: gfx::NextPowerOfTwo(aSize.height)); michael@0: } michael@0: michael@0: TemporaryRef michael@0: RectTextureImage::BeginUpdate(const nsIntSize& aNewSize, michael@0: const nsIntRegion& aDirtyRegion) michael@0: { michael@0: MOZ_ASSERT(!mInUpdate, "Beginning update during update!"); michael@0: mUpdateRegion = aDirtyRegion; michael@0: if (aNewSize != mUsedSize) { michael@0: mUsedSize = aNewSize; michael@0: mUpdateRegion = nsIntRect(nsIntPoint(0, 0), aNewSize); michael@0: } michael@0: michael@0: if (mUpdateRegion.IsEmpty()) { michael@0: return nullptr; michael@0: } michael@0: michael@0: nsIntSize neededBufferSize = TextureSizeForSize(mUsedSize); michael@0: if (!mUpdateDrawTarget || mBufferSize != neededBufferSize) { michael@0: gfx::IntSize size(neededBufferSize.width, neededBufferSize.height); michael@0: mUpdateDrawTarget = michael@0: gfx::Factory::CreateDrawTarget(gfx::BackendType::COREGRAPHICS, size, michael@0: gfx::SurfaceFormat::B8G8R8A8); michael@0: mBufferSize = neededBufferSize; michael@0: } michael@0: michael@0: mInUpdate = true; michael@0: michael@0: RefPtr drawTarget = mUpdateDrawTarget; michael@0: return drawTarget; michael@0: } michael@0: michael@0: #define NSFoundationVersionWithProperStrideSupportForSubtextureUpload NSFoundationVersionNumber10_6_3 michael@0: michael@0: static bool michael@0: CanUploadSubtextures() michael@0: { michael@0: return NSFoundationVersionNumber >= NSFoundationVersionWithProperStrideSupportForSubtextureUpload; michael@0: } michael@0: michael@0: void michael@0: RectTextureImage::EndUpdate(bool aKeepSurface) michael@0: { michael@0: MOZ_ASSERT(mInUpdate, "Ending update while not in update"); michael@0: michael@0: bool overwriteTexture = false; michael@0: nsIntRegion updateRegion = mUpdateRegion; michael@0: if (!mTexture || (mTextureSize != mBufferSize)) { michael@0: overwriteTexture = true; michael@0: mTextureSize = mBufferSize; michael@0: } michael@0: michael@0: if (overwriteTexture || !CanUploadSubtextures()) { michael@0: updateRegion = nsIntRect(nsIntPoint(0, 0), mTextureSize); michael@0: } michael@0: michael@0: RefPtr snapshot = mUpdateDrawTarget->Snapshot(); michael@0: RefPtr dataSnapshot = snapshot->GetDataSurface(); michael@0: michael@0: UploadSurfaceToTexture(mGLContext, michael@0: dataSnapshot, michael@0: updateRegion, michael@0: mTexture, michael@0: overwriteTexture, michael@0: updateRegion.GetBounds().TopLeft(), michael@0: false, michael@0: LOCAL_GL_TEXTURE0, michael@0: LOCAL_GL_TEXTURE_RECTANGLE_ARB); michael@0: michael@0: if (!aKeepSurface) { michael@0: mUpdateDrawTarget = nullptr; michael@0: } michael@0: michael@0: mInUpdate = false; michael@0: } michael@0: michael@0: void michael@0: RectTextureImage::UpdateFromCGContext(const nsIntSize& aNewSize, michael@0: const nsIntRegion& aDirtyRegion, michael@0: CGContextRef aCGContext) michael@0: { michael@0: gfx::IntSize size = gfx::IntSize(CGBitmapContextGetWidth(aCGContext), michael@0: CGBitmapContextGetHeight(aCGContext)); michael@0: mBufferSize.SizeTo(size.width, size.height); michael@0: RefPtr dt = BeginUpdate(aNewSize, aDirtyRegion); michael@0: if (dt) { michael@0: gfx::Rect rect(0, 0, size.width, size.height); michael@0: gfxUtils::ClipToRegion(dt, GetUpdateRegion()); michael@0: RefPtr sourceSurface = michael@0: dt->CreateSourceSurfaceFromData(static_cast(CGBitmapContextGetData(aCGContext)), michael@0: size, michael@0: CGBitmapContextGetBytesPerRow(aCGContext), michael@0: gfx::SurfaceFormat::B8G8R8A8); michael@0: dt->DrawSurface(sourceSurface, rect, rect, michael@0: gfx::DrawSurfaceOptions(), michael@0: gfx::DrawOptions(1.0, gfx::CompositionOp::OP_SOURCE)); michael@0: dt->PopClip(); michael@0: EndUpdate(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: RectTextureImage::UpdateFromDrawTarget(const nsIntSize& aNewSize, michael@0: const nsIntRegion& aDirtyRegion, michael@0: gfx::DrawTarget* aFromDrawTarget) michael@0: { michael@0: mUpdateDrawTarget = aFromDrawTarget; michael@0: mBufferSize.SizeTo(aFromDrawTarget->GetSize().width, aFromDrawTarget->GetSize().height); michael@0: RefPtr drawTarget = BeginUpdate(aNewSize, aDirtyRegion); michael@0: if (drawTarget) { michael@0: if (drawTarget != aFromDrawTarget) { michael@0: RefPtr source = aFromDrawTarget->Snapshot(); michael@0: gfx::Rect rect(0, 0, aFromDrawTarget->GetSize().width, aFromDrawTarget->GetSize().height); michael@0: gfxUtils::ClipToRegion(drawTarget, GetUpdateRegion()); michael@0: drawTarget->DrawSurface(source, rect, rect, michael@0: gfx::DrawSurfaceOptions(), michael@0: gfx::DrawOptions(1.0, gfx::CompositionOp::OP_SOURCE)); michael@0: drawTarget->PopClip(); michael@0: } michael@0: EndUpdate(); michael@0: } michael@0: mUpdateDrawTarget = nullptr; michael@0: } michael@0: michael@0: void michael@0: RectTextureImage::Draw(GLManager* aManager, michael@0: const nsIntPoint& aLocation, michael@0: const gfx3DMatrix& aTransform) michael@0: { michael@0: ShaderProgramOGL* program = aManager->GetProgram(LOCAL_GL_TEXTURE_RECTANGLE_ARB, michael@0: gfx::SurfaceFormat::R8G8B8A8); michael@0: michael@0: aManager->gl()->fBindTexture(LOCAL_GL_TEXTURE_RECTANGLE_ARB, mTexture); michael@0: michael@0: program->Activate(); michael@0: program->SetProjectionMatrix(aManager->GetProjMatrix()); michael@0: program->SetLayerQuadRect(nsIntRect(nsIntPoint(0, 0), mUsedSize)); michael@0: gfx::Matrix4x4 transform; michael@0: gfx::ToMatrix4x4(aTransform, transform); michael@0: program->SetLayerTransform(transform * gfx::Matrix4x4().Translate(aLocation.x, aLocation.y, 0)); michael@0: program->SetTextureTransform(gfx::Matrix4x4()); michael@0: program->SetRenderOffset(nsIntPoint(0, 0)); michael@0: program->SetTexCoordMultiplier(mUsedSize.width, mUsedSize.height); michael@0: program->SetTextureUnit(0); michael@0: michael@0: aManager->BindAndDrawQuad(program); michael@0: michael@0: aManager->gl()->fBindTexture(LOCAL_GL_TEXTURE_RECTANGLE_ARB, 0); michael@0: } michael@0: michael@0: // GLPresenter implementation michael@0: michael@0: GLPresenter::GLPresenter(GLContext* aContext) michael@0: : mGLContext(aContext) michael@0: { michael@0: mGLContext->MakeCurrent(); michael@0: ShaderConfigOGL config; michael@0: config.SetTextureTarget(LOCAL_GL_TEXTURE_RECTANGLE_ARB); michael@0: mRGBARectProgram = new ShaderProgramOGL(mGLContext, michael@0: ProgramProfileOGL::GetProfileFor(config)); michael@0: michael@0: // Create mQuadVBO. michael@0: mGLContext->fGenBuffers(1, &mQuadVBO); michael@0: mGLContext->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, mQuadVBO); michael@0: michael@0: GLfloat vertices[] = { michael@0: /* First quad vertices */ michael@0: 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, michael@0: /* Then quad texcoords */ michael@0: 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, michael@0: }; michael@0: mGLContext->fBufferData(LOCAL_GL_ARRAY_BUFFER, sizeof(vertices), vertices, LOCAL_GL_STATIC_DRAW); michael@0: mGLContext->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, 0); michael@0: } michael@0: michael@0: GLPresenter::~GLPresenter() michael@0: { michael@0: if (mQuadVBO) { michael@0: mGLContext->MakeCurrent(); michael@0: mGLContext->fDeleteBuffers(1, &mQuadVBO); michael@0: mQuadVBO = 0; michael@0: } michael@0: } michael@0: michael@0: void michael@0: GLPresenter::BindAndDrawQuad(ShaderProgramOGL *aProgram) michael@0: { michael@0: mGLContext->MakeCurrent(); michael@0: michael@0: GLuint vertAttribIndex = aProgram->AttribLocation(ShaderProgramOGL::VertexCoordAttrib); michael@0: GLuint texCoordAttribIndex = aProgram->AttribLocation(ShaderProgramOGL::TexCoordAttrib); michael@0: michael@0: mGLContext->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, mQuadVBO); michael@0: mGLContext->fVertexAttribPointer(vertAttribIndex, 2, michael@0: LOCAL_GL_FLOAT, LOCAL_GL_FALSE, 0, michael@0: (GLvoid*)0); michael@0: mGLContext->fEnableVertexAttribArray(vertAttribIndex); michael@0: mGLContext->fVertexAttribPointer(texCoordAttribIndex, 2, michael@0: LOCAL_GL_FLOAT, LOCAL_GL_FALSE, 0, michael@0: (GLvoid*) (sizeof(float)*4*2)); michael@0: mGLContext->fEnableVertexAttribArray(texCoordAttribIndex); michael@0: mGLContext->fDrawArrays(LOCAL_GL_TRIANGLE_STRIP, 0, 4); michael@0: mGLContext->fDisableVertexAttribArray(vertAttribIndex); michael@0: mGLContext->fDisableVertexAttribArray(texCoordAttribIndex); michael@0: } michael@0: michael@0: void michael@0: GLPresenter::BeginFrame(nsIntSize aRenderSize) michael@0: { michael@0: mGLContext->MakeCurrent(); michael@0: michael@0: mGLContext->fViewport(0, 0, aRenderSize.width, aRenderSize.height); michael@0: michael@0: // Matrix to transform (0, 0, width, height) to viewport space (-1.0, 1.0, michael@0: // 2, 2) and flip the contents. michael@0: gfx::Matrix viewMatrix; michael@0: viewMatrix.Translate(-1.0, 1.0); michael@0: viewMatrix.Scale(2.0f / float(aRenderSize.width), 2.0f / float(aRenderSize.height)); michael@0: viewMatrix.Scale(1.0f, -1.0f); michael@0: michael@0: gfx::Matrix4x4 matrix3d = gfx::Matrix4x4::From2D(viewMatrix); michael@0: matrix3d._33 = 0.0f; michael@0: michael@0: // set the projection matrix for the next time the program is activated michael@0: mProjMatrix = matrix3d; michael@0: michael@0: // Default blend function implements "OVER" michael@0: mGLContext->fBlendFuncSeparate(LOCAL_GL_ONE, LOCAL_GL_ONE_MINUS_SRC_ALPHA, michael@0: LOCAL_GL_ONE, LOCAL_GL_ONE); michael@0: mGLContext->fEnable(LOCAL_GL_BLEND); michael@0: michael@0: mGLContext->fClearColor(0.0, 0.0, 0.0, 0.0); michael@0: mGLContext->fClear(LOCAL_GL_COLOR_BUFFER_BIT | LOCAL_GL_DEPTH_BUFFER_BIT); michael@0: michael@0: mGLContext->fEnable(LOCAL_GL_TEXTURE_RECTANGLE_ARB); michael@0: } michael@0: michael@0: void michael@0: GLPresenter::EndFrame() michael@0: { michael@0: mGLContext->SwapBuffers(); michael@0: mGLContext->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, 0); michael@0: } michael@0: michael@0: #pragma mark - michael@0: michael@0: @implementation ChildView michael@0: michael@0: // globalDragPboard is non-null during native drag sessions that did not originate michael@0: // in our native NSView (it is set in |draggingEntered:|). It is unset when the michael@0: // drag session ends for this view, either with the mouse exiting or when a drop michael@0: // occurs in this view. michael@0: NSPasteboard* globalDragPboard = nil; michael@0: michael@0: // gLastDragView and gLastDragMouseDownEvent are used to communicate information michael@0: // to the drag service during drag invocation (starting a drag in from the view). michael@0: // gLastDragView is only non-null while mouseDragged is on the call stack. michael@0: NSView* gLastDragView = nil; michael@0: NSEvent* gLastDragMouseDownEvent = nil; michael@0: michael@0: + (void)initialize michael@0: { michael@0: static BOOL initialized = NO; michael@0: michael@0: if (!initialized) { michael@0: // Inform the OS about the types of services (from the "Services" menu) michael@0: // that we can handle. michael@0: michael@0: NSArray *sendTypes = [[NSArray alloc] initWithObjects:NSStringPboardType,NSHTMLPboardType,nil]; michael@0: NSArray *returnTypes = [[NSArray alloc] initWithObjects:NSStringPboardType,NSHTMLPboardType,nil]; michael@0: michael@0: [NSApp registerServicesMenuSendTypes:sendTypes returnTypes:returnTypes]; michael@0: michael@0: [sendTypes release]; michael@0: [returnTypes release]; michael@0: michael@0: initialized = YES; michael@0: } michael@0: } michael@0: michael@0: + (void)registerViewForDraggedTypes:(NSView*)aView michael@0: { michael@0: [aView registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, michael@0: NSStringPboardType, michael@0: NSHTMLPboardType, michael@0: NSURLPboardType, michael@0: NSFilesPromisePboardType, michael@0: kWildcardPboardType, michael@0: kCorePboardType_url, michael@0: kCorePboardType_urld, michael@0: kCorePboardType_urln, michael@0: nil]]; michael@0: } michael@0: michael@0: // initWithFrame:geckoChild: michael@0: - (id)initWithFrame:(NSRect)inFrame geckoChild:(nsChildView*)inChild michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; michael@0: michael@0: if ((self = [super initWithFrame:inFrame])) { michael@0: mGeckoChild = inChild; michael@0: mIsPluginView = NO; michael@0: #ifndef NP_NO_CARBON michael@0: // We don't support the Carbon event model but it's still the default michael@0: // model for i386 per NPAPI. michael@0: mPluginEventModel = NPEventModelCarbon; michael@0: #else michael@0: mPluginEventModel = NPEventModelCocoa; michael@0: #endif michael@0: #ifndef NP_NO_QUICKDRAW michael@0: // We don't support the Quickdraw drawing model any more but it's still michael@0: // the default model for i386 per NPAPI. michael@0: mPluginDrawingModel = NPDrawingModelQuickDraw; michael@0: #else michael@0: mPluginDrawingModel = NPDrawingModelCoreGraphics; michael@0: #endif michael@0: mPendingDisplay = NO; michael@0: mBlockedLastMouseDown = NO; michael@0: mExpectingWheelStop = NO; michael@0: michael@0: mLastMouseDownEvent = nil; michael@0: mClickThroughMouseDownEvent = nil; michael@0: mDragService = nullptr; michael@0: michael@0: mGestureState = eGestureState_None; michael@0: mCumulativeMagnification = 0.0; michael@0: mCumulativeRotation = 0.0; michael@0: michael@0: // We can't call forceRefreshOpenGL here because, in order to work around michael@0: // the bug, it seems we need to have a draw already happening. Therefore, michael@0: // we call it in drawRect:inContext:, when we know that a draw is in michael@0: // progress. michael@0: mDidForceRefreshOpenGL = NO; michael@0: michael@0: [self setFocusRingType:NSFocusRingTypeNone]; michael@0: michael@0: #ifdef __LP64__ michael@0: mCancelSwipeAnimation = nil; michael@0: mCurrentSwipeDir = 0; michael@0: #endif michael@0: michael@0: mTopLeftCornerMask = NULL; michael@0: } michael@0: michael@0: // register for things we'll take from other applications michael@0: [ChildView registerViewForDraggedTypes:self]; michael@0: michael@0: [[NSNotificationCenter defaultCenter] addObserver:self michael@0: selector:@selector(windowBecameMain:) michael@0: name:NSWindowDidBecomeMainNotification michael@0: object:nil]; michael@0: [[NSNotificationCenter defaultCenter] addObserver:self michael@0: selector:@selector(windowResignedMain:) michael@0: name:NSWindowDidResignMainNotification michael@0: object:nil]; michael@0: [[NSNotificationCenter defaultCenter] addObserver:self michael@0: selector:@selector(systemMetricsChanged) michael@0: name:NSControlTintDidChangeNotification michael@0: object:nil]; michael@0: [[NSNotificationCenter defaultCenter] addObserver:self michael@0: selector:@selector(systemMetricsChanged) michael@0: name:NSSystemColorsDidChangeNotification michael@0: object:nil]; michael@0: // TODO: replace the string with the constant once we build with the 10.7 SDK michael@0: [[NSNotificationCenter defaultCenter] addObserver:self michael@0: selector:@selector(scrollbarSystemMetricChanged) michael@0: name:@"NSPreferredScrollerStyleDidChangeNotification" michael@0: object:nil]; michael@0: [[NSDistributedNotificationCenter defaultCenter] addObserver:self michael@0: selector:@selector(systemMetricsChanged) michael@0: name:@"AppleAquaScrollBarVariantChanged" michael@0: object:nil michael@0: suspensionBehavior:NSNotificationSuspensionBehaviorDeliverImmediately]; michael@0: [[NSNotificationCenter defaultCenter] addObserver:self michael@0: selector:@selector(_surfaceNeedsUpdate:) michael@0: name:NSViewGlobalFrameDidChangeNotification michael@0: object:self]; michael@0: michael@0: return self; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NIL; michael@0: } michael@0: michael@0: - (void)installTextInputHandler:(TextInputHandler*)aHandler michael@0: { michael@0: mTextInputHandler = aHandler; michael@0: } michael@0: michael@0: - (void)uninstallTextInputHandler michael@0: { michael@0: mTextInputHandler = nullptr; michael@0: } michael@0: michael@0: // Work around bug 603134. michael@0: // OS X has a bug that causes new OpenGL windows to only paint once or twice, michael@0: // then stop painting altogether. By clearing the drawable from the GL context, michael@0: // and then resetting the view to ourselves, we convince OS X to start updating michael@0: // again. michael@0: // This can cause a flash in new windows - bug 631339 - but it's very hard to michael@0: // fix that while maintaining this workaround. michael@0: - (void)forceRefreshOpenGL michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: [mGLContext clearDrawable]; michael@0: [self updateGLContext]; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: - (void)setGLContext:(NSOpenGLContext *)aGLContext michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: mGLContext = aGLContext; michael@0: [mGLContext retain]; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: - (bool)preRender:(NSOpenGLContext *)aGLContext michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; michael@0: michael@0: if (![self window] || michael@0: ([[self window] isKindOfClass:[BaseWindow class]] && michael@0: ![(BaseWindow*)[self window] isVisibleOrBeingShown])) { michael@0: // Before the window is shown, our GL context's front FBO is not michael@0: // framebuffer complete, so we refuse to render. michael@0: return false; michael@0: } michael@0: michael@0: if (!mGLContext) { michael@0: [self setGLContext:aGLContext]; michael@0: [self updateGLContext]; michael@0: } michael@0: michael@0: CGLLockContext((CGLContextObj)[aGLContext CGLContextObj]); michael@0: michael@0: return true; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false); michael@0: } michael@0: michael@0: - (void)postRender:(NSOpenGLContext *)aGLContext michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: CGLUnlockContext((CGLContextObj)[aGLContext CGLContextObj]); michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: - (void)dealloc michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: [mGLContext release]; michael@0: [mPendingDirtyRects release]; michael@0: [mLastMouseDownEvent release]; michael@0: [mClickThroughMouseDownEvent release]; michael@0: CGImageRelease(mTopLeftCornerMask); michael@0: ChildViewMouseTracker::OnDestroyView(self); michael@0: michael@0: [[NSNotificationCenter defaultCenter] removeObserver:self]; michael@0: [[NSDistributedNotificationCenter defaultCenter] removeObserver:self]; michael@0: michael@0: [super dealloc]; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: - (void)updatePluginTopLevelWindowStatus:(BOOL)hasMain michael@0: { michael@0: if (!mGeckoChild) michael@0: return; michael@0: michael@0: WidgetPluginEvent pluginEvent(true, NS_PLUGIN_FOCUS_EVENT, mGeckoChild); michael@0: NPCocoaEvent cocoaEvent; michael@0: nsCocoaUtils::InitNPCocoaEvent(&cocoaEvent); michael@0: cocoaEvent.type = NPCocoaEventWindowFocusChanged; michael@0: cocoaEvent.data.focus.hasFocus = hasMain; michael@0: nsCocoaUtils::InitPluginEvent(pluginEvent, cocoaEvent); michael@0: mGeckoChild->DispatchWindowEvent(pluginEvent); michael@0: } michael@0: michael@0: - (void)windowBecameMain:(NSNotification*)inNotification michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: if (mIsPluginView && mPluginEventModel == NPEventModelCocoa) { michael@0: if ((NSWindow*)[inNotification object] == [self window]) { michael@0: [self updatePluginTopLevelWindowStatus:YES]; michael@0: } michael@0: } michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: - (void)windowResignedMain:(NSNotification*)inNotification michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: if (mIsPluginView && mPluginEventModel == NPEventModelCocoa) { michael@0: if ((NSWindow*)[inNotification object] == [self window]) { michael@0: [self updatePluginTopLevelWindowStatus:NO]; michael@0: } michael@0: } michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: - (void)widgetDestroyed michael@0: { michael@0: if (mTextInputHandler) { michael@0: mTextInputHandler->OnDestroyWidget(mGeckoChild); michael@0: mTextInputHandler = nullptr; michael@0: } michael@0: mGeckoChild = nullptr; michael@0: michael@0: // Just in case we're destroyed abruptly and missed the draggingExited michael@0: // or performDragOperation message. michael@0: NS_IF_RELEASE(mDragService); michael@0: } michael@0: michael@0: // mozView method, return our gecko child view widget. Note this does not AddRef. michael@0: - (nsIWidget*) widget michael@0: { michael@0: return static_cast(mGeckoChild); michael@0: } michael@0: michael@0: - (void)systemMetricsChanged michael@0: { michael@0: if (mGeckoChild) michael@0: mGeckoChild->NotifyThemeChanged(); michael@0: } michael@0: michael@0: - (void)scrollbarSystemMetricChanged michael@0: { michael@0: [self systemMetricsChanged]; michael@0: michael@0: if (mGeckoChild) { michael@0: nsIWidgetListener* listener = mGeckoChild->GetWidgetListener(); michael@0: if (listener) { michael@0: listener->GetPresShell()->ReconstructFrames(); michael@0: } michael@0: } michael@0: } michael@0: michael@0: - (void)setNeedsPendingDisplay michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: mPendingFullDisplay = YES; michael@0: if (!mPendingDisplay) { michael@0: [self performSelector:@selector(processPendingRedraws) withObject:nil afterDelay:0]; michael@0: mPendingDisplay = YES; michael@0: } michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: - (void)setNeedsPendingDisplayInRect:(NSRect)invalidRect michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: if (!mPendingDirtyRects) michael@0: mPendingDirtyRects = [[NSMutableArray alloc] initWithCapacity:1]; michael@0: [mPendingDirtyRects addObject:[NSValue valueWithRect:invalidRect]]; michael@0: if (!mPendingDisplay) { michael@0: [self performSelector:@selector(processPendingRedraws) withObject:nil afterDelay:0]; michael@0: mPendingDisplay = YES; michael@0: } michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: // Clears the queue of any pending invalides michael@0: - (void)processPendingRedraws michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: if (mPendingFullDisplay) { michael@0: [self setNeedsDisplay:YES]; michael@0: } michael@0: else if (mPendingDirtyRects) { michael@0: unsigned int count = [mPendingDirtyRects count]; michael@0: for (unsigned int i = 0; i < count; ++i) { michael@0: [self setNeedsDisplayInRect:[[mPendingDirtyRects objectAtIndex:i] rectValue]]; michael@0: } michael@0: } michael@0: mPendingFullDisplay = NO; michael@0: mPendingDisplay = NO; michael@0: [mPendingDirtyRects release]; michael@0: mPendingDirtyRects = nil; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: - (void)setNeedsDisplayInRect:(NSRect)aRect michael@0: { michael@0: if (![self isUsingOpenGL]) { michael@0: [super setNeedsDisplayInRect:aRect]; michael@0: return; michael@0: } michael@0: michael@0: if ([[self window] isVisible] && [self isUsingMainThreadOpenGL]) { michael@0: // Draw without calling drawRect. This prevent us from michael@0: // needing to access the normal window buffer surface unnecessarily, so we michael@0: // waste less time synchronizing the two surfaces. (These synchronizations michael@0: // show up in a profiler as CGSDeviceLock / _CGSLockWindow / michael@0: // _CGSSynchronizeWindowBackingStore.) It also means that Cocoa doesn't michael@0: // have any potentially expensive invalid rect management for us. michael@0: if (!mWaitingForPaint) { michael@0: mWaitingForPaint = YES; michael@0: // Use NSRunLoopCommonModes instead of the default NSDefaultRunLoopMode michael@0: // so that the timer also fires while a native menu is open. michael@0: [self performSelector:@selector(drawUsingOpenGLCallback) michael@0: withObject:nil michael@0: afterDelay:0 michael@0: inModes:[NSArray arrayWithObject:NSRunLoopCommonModes]]; michael@0: } michael@0: } michael@0: } michael@0: michael@0: - (NSString*)description michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; michael@0: michael@0: return [NSString stringWithFormat:@"ChildView %p, gecko child %p, frame %@", self, mGeckoChild, NSStringFromRect([self frame])]; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NIL; michael@0: } michael@0: michael@0: // Make the origin of this view the topLeft corner (gecko origin) rather michael@0: // than the bottomLeft corner (standard cocoa origin). michael@0: - (BOOL)isFlipped michael@0: { michael@0: return YES; michael@0: } michael@0: michael@0: - (BOOL)isOpaque michael@0: { michael@0: return [[self window] isOpaque] && !mIsPluginView; michael@0: } michael@0: michael@0: -(void)setIsPluginView:(BOOL)aIsPlugin michael@0: { michael@0: mIsPluginView = aIsPlugin; michael@0: } michael@0: michael@0: -(BOOL)isPluginView michael@0: { michael@0: return mIsPluginView; michael@0: } michael@0: michael@0: - (NSView *)hitTest:(NSPoint)aPoint michael@0: { michael@0: NSView* target = [super hitTest:aPoint]; michael@0: if ((target == self) && [self isPluginView] && mGeckoChild) { michael@0: nsAutoRetainCocoaObject kungFuDeathGrip(self); michael@0: michael@0: NSPoint cocoaLoc = [[self superview] convertPoint:aPoint toView:self]; michael@0: LayoutDeviceIntPoint widgetLoc = LayoutDeviceIntPoint::FromUntyped( michael@0: mGeckoChild->CocoaPointsToDevPixels(cocoaLoc)); michael@0: michael@0: WidgetQueryContentEvent hitTest(true, NS_QUERY_DOM_WIDGET_HITTEST, michael@0: mGeckoChild); michael@0: hitTest.InitForQueryDOMWidgetHittest(widgetLoc); michael@0: // This might destroy our widget. michael@0: mGeckoChild->DispatchWindowEvent(hitTest); michael@0: if (!mGeckoChild) { michael@0: return nil; michael@0: } michael@0: if (hitTest.mSucceeded && !hitTest.mReply.mWidgetIsHit) { michael@0: return nil; michael@0: } michael@0: } michael@0: return target; michael@0: } michael@0: michael@0: // Are we processing an NSLeftMouseDown event that will fail to click through? michael@0: // If so, we shouldn't focus or unfocus a plugin. michael@0: - (BOOL)isInFailingLeftClickThrough michael@0: { michael@0: if (!mGeckoChild) michael@0: return NO; michael@0: michael@0: if (!mClickThroughMouseDownEvent || michael@0: [mClickThroughMouseDownEvent type] != NSLeftMouseDown) michael@0: return NO; michael@0: michael@0: BOOL retval = michael@0: !ChildViewMouseTracker::WindowAcceptsEvent([self window], michael@0: mClickThroughMouseDownEvent, michael@0: self, true); michael@0: michael@0: // If we return YES here, this will result in us not being focused, michael@0: // which will stop us receiving mClickThroughMouseDownEvent in michael@0: // [ChildView mouseDown:]. So we need to release and null-out michael@0: // mClickThroughMouseDownEvent here. michael@0: if (retval) { michael@0: [mClickThroughMouseDownEvent release]; michael@0: mClickThroughMouseDownEvent = nil; michael@0: } michael@0: michael@0: return retval; michael@0: } michael@0: michael@0: - (void)setPluginEventModel:(NPEventModel)eventModel michael@0: { michael@0: mPluginEventModel = eventModel; michael@0: } michael@0: michael@0: - (void)setPluginDrawingModel:(NPDrawingModel)drawingModel michael@0: { michael@0: mPluginDrawingModel = drawingModel; michael@0: } michael@0: michael@0: - (NPEventModel)pluginEventModel michael@0: { michael@0: return mPluginEventModel; michael@0: } michael@0: michael@0: - (NPDrawingModel)pluginDrawingModel michael@0: { michael@0: return mPluginDrawingModel; michael@0: } michael@0: michael@0: - (void)sendFocusEvent:(uint32_t)eventType michael@0: { michael@0: if (!mGeckoChild) michael@0: return; michael@0: michael@0: nsEventStatus status = nsEventStatus_eIgnore; michael@0: WidgetGUIEvent focusGuiEvent(true, eventType, mGeckoChild); michael@0: focusGuiEvent.time = PR_IntervalNow(); michael@0: mGeckoChild->DispatchEvent(&focusGuiEvent, status); michael@0: } michael@0: michael@0: // We accept key and mouse events, so don't keep passing them up the chain. Allow michael@0: // this to be a 'focused' widget for event dispatch. michael@0: - (BOOL)acceptsFirstResponder michael@0: { michael@0: return YES; michael@0: } michael@0: michael@0: // Accept mouse down events on background windows michael@0: - (BOOL)acceptsFirstMouse:(NSEvent*)aEvent michael@0: { michael@0: if (![[self window] isKindOfClass:[PopupWindow class]]) { michael@0: // We rely on this function to tell us that the mousedown was on a michael@0: // background window. Inside mouseDown we can't tell whether we were michael@0: // inactive because at that point we've already been made active. michael@0: // Unfortunately, acceptsFirstMouse is called for PopupWindows even when michael@0: // their parent window is active, so ignore this on them for now. michael@0: mClickThroughMouseDownEvent = [aEvent retain]; michael@0: } michael@0: return YES; michael@0: } michael@0: michael@0: - (void)viewWillMoveToWindow:(NSWindow *)newWindow michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: if (!newWindow) michael@0: HideChildPluginViews(self); michael@0: michael@0: [super viewWillMoveToWindow:newWindow]; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: - (void)viewDidMoveToWindow michael@0: { michael@0: if (mPluginEventModel == NPEventModelCocoa && michael@0: [self window] && [self isPluginView] && mGeckoChild) { michael@0: mGeckoChild->UpdatePluginPort(); michael@0: } michael@0: michael@0: [super viewDidMoveToWindow]; michael@0: } michael@0: michael@0: - (void)scrollRect:(NSRect)aRect by:(NSSize)offset michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: // Update any pending dirty rects to reflect the new scroll position michael@0: if (mPendingDirtyRects) { michael@0: unsigned int count = [mPendingDirtyRects count]; michael@0: for (unsigned int i = 0; i < count; ++i) { michael@0: NSRect oldRect = [[mPendingDirtyRects objectAtIndex:i] rectValue]; michael@0: NSRect newRect = NSOffsetRect(oldRect, offset.width, offset.height); michael@0: [mPendingDirtyRects replaceObjectAtIndex:i michael@0: withObject:[NSValue valueWithRect:newRect]]; michael@0: } michael@0: } michael@0: [super scrollRect:aRect by:offset]; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: - (BOOL)mouseDownCanMoveWindow michael@0: { michael@0: return [[self window] isMovableByWindowBackground]; michael@0: } michael@0: michael@0: -(void)updateGLContext michael@0: { michael@0: if (mGLContext) { michael@0: CGLLockContext((CGLContextObj)[mGLContext CGLContextObj]); michael@0: [mGLContext setView:self]; michael@0: [mGLContext update]; michael@0: CGLUnlockContext((CGLContextObj)[mGLContext CGLContextObj]); michael@0: } michael@0: } michael@0: michael@0: - (void)_surfaceNeedsUpdate:(NSNotification*)notification michael@0: { michael@0: [self updateGLContext]; michael@0: } michael@0: michael@0: - (BOOL)wantsBestResolutionOpenGLSurface michael@0: { michael@0: return nsCocoaUtils::HiDPIEnabled() ? YES : NO; michael@0: } michael@0: michael@0: - (void)viewDidChangeBackingProperties michael@0: { michael@0: [super viewDidChangeBackingProperties]; michael@0: if (mGeckoChild) { michael@0: // actually, it could be the color space that's changed, michael@0: // but we can't tell the difference here except by retrieving michael@0: // the backing scale factor and comparing to the old value michael@0: mGeckoChild->BackingScaleFactorChanged(); michael@0: } michael@0: } michael@0: michael@0: - (BOOL)isCoveringTitlebar michael@0: { michael@0: return [[self window] isKindOfClass:[BaseWindow class]] && michael@0: [(BaseWindow*)[self window] mainChildView] == self && michael@0: [(BaseWindow*)[self window] drawsContentsIntoWindowFrame]; michael@0: } michael@0: michael@0: - (nsIntRegion)nativeDirtyRegionWithBoundingRect:(NSRect)aRect michael@0: { michael@0: nsIntRect boundingRect = mGeckoChild->CocoaPointsToDevPixels(aRect); michael@0: const NSRect *rects; michael@0: NSInteger count; michael@0: [self getRectsBeingDrawn:&rects count:&count]; michael@0: michael@0: if (count > MAX_RECTS_IN_REGION) { michael@0: return boundingRect; michael@0: } michael@0: michael@0: nsIntRegion region; michael@0: for (NSInteger i = 0; i < count; ++i) { michael@0: region.Or(region, mGeckoChild->CocoaPointsToDevPixels(rects[i])); michael@0: } michael@0: region.And(region, boundingRect); michael@0: return region; michael@0: } michael@0: michael@0: // The display system has told us that a portion of our view is dirty. Tell michael@0: // gecko to paint it michael@0: - (void)drawRect:(NSRect)aRect michael@0: { michael@0: CGContextRef cgContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort]; michael@0: [self drawRect:aRect inContext:cgContext]; michael@0: michael@0: // If we're a transparent window and our contents have changed, we need michael@0: // to make sure the shadow is updated to the new contents. michael@0: if ([[self window] isKindOfClass:[BaseWindow class]]) { michael@0: [(BaseWindow*)[self window] deferredInvalidateShadow]; michael@0: } michael@0: } michael@0: michael@0: - (void)drawRect:(NSRect)aRect inContext:(CGContextRef)aContext michael@0: { michael@0: if (!mGeckoChild || !mGeckoChild->IsVisible()) michael@0: return; michael@0: michael@0: // Don't ever draw plugin views explicitly; they'll be drawn as part of their parent widget. michael@0: if (mIsPluginView) michael@0: return; michael@0: michael@0: #ifdef DEBUG_UPDATE michael@0: nsIntRect geckoBounds; michael@0: mGeckoChild->GetBounds(geckoBounds); michael@0: michael@0: fprintf (stderr, "---- Update[%p][%p] [%f %f %f %f] cgc: %p\n gecko bounds: [%d %d %d %d]\n", michael@0: self, mGeckoChild, michael@0: aRect.origin.x, aRect.origin.y, aRect.size.width, aRect.size.height, aContext, michael@0: geckoBounds.x, geckoBounds.y, geckoBounds.width, geckoBounds.height); michael@0: michael@0: CGAffineTransform xform = CGContextGetCTM(aContext); michael@0: fprintf (stderr, " xform in: [%f %f %f %f %f %f]\n", xform.a, xform.b, xform.c, xform.d, xform.tx, xform.ty); michael@0: #endif michael@0: michael@0: if ([self isUsingOpenGL]) { michael@0: // For Gecko-initiated repaints in OpenGL mode, drawUsingOpenGL is michael@0: // directly called from a delayed perform callback - without going through michael@0: // drawRect. michael@0: // Paints that come through here are triggered by something that Cocoa michael@0: // controls, for example by window resizing or window focus changes. michael@0: michael@0: // Since this view is usually declared as opaque, the window's pixel michael@0: // buffer may now contain garbage which we need to prevent from reaching michael@0: // the screen. The only place where garbage can show is in the window michael@0: // corners - the rest of the window is covered by opaque content in our michael@0: // OpenGL surface. michael@0: // So we need to clear the pixel buffer contents in the corners. michael@0: [self clearCorners]; michael@0: michael@0: // Do GL composition and return. michael@0: [self drawUsingOpenGL]; michael@0: return; michael@0: } michael@0: michael@0: PROFILER_LABEL("widget", "ChildView::drawRect"); michael@0: michael@0: // The CGContext that drawRect supplies us with comes with a transform that michael@0: // scales one user space unit to one Cocoa point, which can consist of michael@0: // multiple dev pixels. But Gecko expects its supplied context to be scaled michael@0: // to device pixels, so we need to reverse the scaling. michael@0: double scale = mGeckoChild->BackingScaleFactor(); michael@0: CGContextSaveGState(aContext); michael@0: CGContextScaleCTM(aContext, 1.0 / scale, 1.0 / scale); michael@0: michael@0: NSSize viewSize = [self bounds].size; michael@0: nsIntSize backingSize(viewSize.width * scale, viewSize.height * scale); michael@0: michael@0: CGContextSaveGState(aContext); michael@0: michael@0: nsIntRegion region = [self nativeDirtyRegionWithBoundingRect:aRect]; michael@0: michael@0: // Create Cairo objects. michael@0: nsRefPtr targetSurface = michael@0: new gfxQuartzSurface(aContext, backingSize); michael@0: targetSurface->SetAllowUseAsSource(false); michael@0: michael@0: nsRefPtr targetContext; michael@0: if (gfxPlatform::GetPlatform()->SupportsAzureContentForType(gfx::BackendType::CAIRO)) { michael@0: RefPtr dt = michael@0: gfxPlatform::GetPlatform()->CreateDrawTargetForSurface(targetSurface, michael@0: gfx::IntSize(backingSize.width, michael@0: backingSize.height)); michael@0: dt->AddUserData(&gfxContext::sDontUseAsSourceKey, dt, nullptr); michael@0: targetContext = new gfxContext(dt); michael@0: } else if (gfxPlatform::GetPlatform()->SupportsAzureContentForType(gfx::BackendType::COREGRAPHICS)) { michael@0: RefPtr dt = michael@0: gfx::Factory::CreateDrawTargetForCairoCGContext(aContext, michael@0: gfx::IntSize(backingSize.width, michael@0: backingSize.height)); michael@0: dt->AddUserData(&gfxContext::sDontUseAsSourceKey, dt, nullptr); michael@0: targetContext = new gfxContext(dt); michael@0: } else { michael@0: targetContext = new gfxContext(targetSurface); michael@0: } michael@0: michael@0: // Set up the clip region. michael@0: nsIntRegionRectIterator iter(region); michael@0: targetContext->NewPath(); michael@0: for (;;) { michael@0: const nsIntRect* r = iter.Next(); michael@0: if (!r) michael@0: break; michael@0: targetContext->Rectangle(gfxRect(r->x, r->y, r->width, r->height)); michael@0: } michael@0: targetContext->Clip(); michael@0: michael@0: nsAutoRetainCocoaObject kungFuDeathGrip(self); michael@0: bool painted = false; michael@0: if (mGeckoChild->GetLayerManager()->GetBackendType() == LayersBackend::LAYERS_BASIC) { michael@0: nsBaseWidget::AutoLayerManagerSetup michael@0: setupLayerManager(mGeckoChild, targetContext, BufferMode::BUFFER_NONE); michael@0: painted = mGeckoChild->PaintWindow(region); michael@0: } else if (mGeckoChild->GetLayerManager()->GetBackendType() == LayersBackend::LAYERS_CLIENT) { michael@0: // We only need this so that we actually get DidPaintWindow fired michael@0: painted = mGeckoChild->PaintWindow(region); michael@0: } michael@0: michael@0: targetContext = nullptr; michael@0: targetSurface = nullptr; michael@0: michael@0: CGContextRestoreGState(aContext); michael@0: michael@0: // Undo the scale transform so that from now on the context is in michael@0: // CocoaPoints again. michael@0: CGContextRestoreGState(aContext); michael@0: michael@0: if (!painted && [self isOpaque]) { michael@0: // Gecko refused to draw, but we've claimed to be opaque, so we have to michael@0: // draw something--fill with white. michael@0: CGContextSetRGBFillColor(aContext, 1, 1, 1, 1); michael@0: CGContextFillRect(aContext, NSRectToCGRect(aRect)); michael@0: } michael@0: michael@0: if ([self isCoveringTitlebar]) { michael@0: [self drawTitleString]; michael@0: [self drawTitlebarHighlight]; michael@0: [self maskTopCornersInContext:aContext]; michael@0: } michael@0: michael@0: #ifdef DEBUG_UPDATE michael@0: fprintf (stderr, "---- update done ----\n"); michael@0: michael@0: #if 0 michael@0: CGContextSetRGBStrokeColor (aContext, michael@0: ((((unsigned long)self) & 0xff)) / 255.0, michael@0: ((((unsigned long)self) & 0xff00) >> 8) / 255.0, michael@0: ((((unsigned long)self) & 0xff0000) >> 16) / 255.0, michael@0: 0.5); michael@0: #endif michael@0: CGContextSetRGBStrokeColor(aContext, 1, 0, 0, 0.8); michael@0: CGContextSetLineWidth(aContext, 4.0); michael@0: CGContextStrokeRect(aContext, NSRectToCGRect(aRect)); michael@0: #endif michael@0: } michael@0: michael@0: - (BOOL)isUsingMainThreadOpenGL michael@0: { michael@0: if (!mGeckoChild || ![self window]) michael@0: return NO; michael@0: michael@0: return mGeckoChild->GetLayerManager(nullptr)->GetBackendType() == mozilla::layers::LayersBackend::LAYERS_OPENGL; michael@0: } michael@0: michael@0: - (BOOL)isUsingOpenGL michael@0: { michael@0: if (!mGeckoChild || ![self window]) michael@0: return NO; michael@0: michael@0: return mGLContext || mUsingOMTCompositor || [self isUsingMainThreadOpenGL]; michael@0: } michael@0: michael@0: - (void)drawUsingOpenGL michael@0: { michael@0: PROFILER_LABEL("widget", "ChildView::drawUsingOpenGL"); michael@0: michael@0: if (![self isUsingOpenGL] || !mGeckoChild->IsVisible()) michael@0: return; michael@0: michael@0: mWaitingForPaint = NO; michael@0: michael@0: nsIntRect geckoBounds; michael@0: mGeckoChild->GetBounds(geckoBounds); michael@0: nsIntRegion region(geckoBounds); michael@0: michael@0: mGeckoChild->PaintWindow(region); michael@0: michael@0: // Force OpenGL to refresh the very first time we draw. This works around a michael@0: // Mac OS X bug that stops windows updating on OS X when we use OpenGL. michael@0: if (!mDidForceRefreshOpenGL) { michael@0: [self performSelector:@selector(forceRefreshOpenGL) withObject:nil afterDelay:0]; michael@0: mDidForceRefreshOpenGL = YES; michael@0: } michael@0: } michael@0: michael@0: // Called asynchronously after setNeedsDisplay in order to avoid entering the michael@0: // normal drawing machinery. michael@0: - (void)drawUsingOpenGLCallback michael@0: { michael@0: if (mWaitingForPaint) { michael@0: [self drawUsingOpenGL]; michael@0: } michael@0: } michael@0: michael@0: - (BOOL)hasRoundedBottomCorners michael@0: { michael@0: return [[self window] respondsToSelector:@selector(bottomCornerRounded)] && michael@0: [[self window] bottomCornerRounded]; michael@0: } michael@0: michael@0: - (CGFloat)cornerRadius michael@0: { michael@0: NSView* frameView = [[[self window] contentView] superview]; michael@0: if (!frameView || ![frameView respondsToSelector:@selector(roundedCornerRadius)]) michael@0: return 4.0f; michael@0: return [frameView roundedCornerRadius]; michael@0: } michael@0: michael@0: // Accelerated windows have two NSSurfaces: michael@0: // (1) The window's pixel buffer in the back and michael@0: // (2) the OpenGL view in the front. michael@0: // These two surfaces are composited by the window manager. Drawing into the michael@0: // CGContext which is provided by drawRect ends up in (1). michael@0: // When our window has rounded corners, the OpenGL view has transparent pixels michael@0: // in the corners. In these places the contents of the window's pixel buffer michael@0: // can show through. So we need to make sure that the pixel buffer is michael@0: // transparent in the corners so that no garbage reaches the screen. michael@0: // The contents of the pixel buffer in the rest of the window don't matter michael@0: // because they're covered by opaque pixels of the OpenGL context. michael@0: // Making the corners transparent works even though our window is michael@0: // declared "opaque" (in the NSWindow's isOpaque method). michael@0: - (void)clearCorners michael@0: { michael@0: CGFloat radius = [self cornerRadius]; michael@0: CGFloat w = [self bounds].size.width, h = [self bounds].size.height; michael@0: [[NSColor clearColor] set]; michael@0: michael@0: if ([self isCoveringTitlebar]) { michael@0: NSRectFill(NSMakeRect(0, 0, radius, radius)); michael@0: NSRectFill(NSMakeRect(w - radius, 0, radius, radius)); michael@0: } michael@0: michael@0: if ([self hasRoundedBottomCorners]) { michael@0: NSRectFill(NSMakeRect(0, h - radius, radius, radius)); michael@0: NSRectFill(NSMakeRect(w - radius, h - radius, radius, radius)); michael@0: } michael@0: } michael@0: michael@0: // This is the analog of nsChildView::MaybeDrawRoundedCorners for CGContexts. michael@0: // We only need to mask the top corners here because Cocoa does the masking michael@0: // for the window's bottom corners automatically (starting with 10.7). michael@0: - (void)maskTopCornersInContext:(CGContextRef)aContext michael@0: { michael@0: CGFloat radius = [self cornerRadius]; michael@0: int32_t devPixelCornerRadius = mGeckoChild->CocoaPointsToDevPixels(radius); michael@0: michael@0: // First make sure that mTopLeftCornerMask is set up. michael@0: if (!mTopLeftCornerMask || michael@0: int32_t(CGImageGetWidth(mTopLeftCornerMask)) != devPixelCornerRadius) { michael@0: CGImageRelease(mTopLeftCornerMask); michael@0: CGColorSpaceRef rgb = CGColorSpaceCreateDeviceRGB(); michael@0: CGContextRef imgCtx = CGBitmapContextCreate(NULL, michael@0: devPixelCornerRadius, michael@0: devPixelCornerRadius, michael@0: 8, devPixelCornerRadius * 4, michael@0: rgb, kCGImageAlphaPremultipliedFirst); michael@0: CGColorSpaceRelease(rgb); michael@0: DrawTopLeftCornerMask(imgCtx, devPixelCornerRadius); michael@0: mTopLeftCornerMask = CGBitmapContextCreateImage(imgCtx); michael@0: CGContextRelease(imgCtx); michael@0: } michael@0: michael@0: // kCGBlendModeDestinationIn is the secret sauce which allows us to erase michael@0: // already painted pixels. It's defined as R = D * Sa: multiply all channels michael@0: // of the destination pixel with the alpha of the source pixel. In our case, michael@0: // the source is mTopLeftCornerMask. michael@0: CGContextSaveGState(aContext); michael@0: CGContextSetBlendMode(aContext, kCGBlendModeDestinationIn); michael@0: michael@0: CGRect destRect = CGRectMake(0, 0, radius, radius); michael@0: michael@0: // Erase the top left corner... michael@0: CGContextDrawImage(aContext, destRect, mTopLeftCornerMask); michael@0: michael@0: // ... and the top right corner. michael@0: CGContextTranslateCTM(aContext, [self bounds].size.width, 0); michael@0: CGContextScaleCTM(aContext, -1, 1); michael@0: CGContextDrawImage(aContext, destRect, mTopLeftCornerMask); michael@0: michael@0: CGContextRestoreGState(aContext); michael@0: } michael@0: michael@0: - (void)drawTitleString michael@0: { michael@0: BaseWindow* window = (BaseWindow*)[self window]; michael@0: if (![window wantsTitleDrawn]) { michael@0: return; michael@0: } michael@0: michael@0: NSView* frameView = [[window contentView] superview]; michael@0: if (![frameView respondsToSelector:@selector(_drawTitleBar:)]) { michael@0: return; michael@0: } michael@0: michael@0: NSGraphicsContext* oldContext = [NSGraphicsContext currentContext]; michael@0: CGContextRef ctx = (CGContextRef)[oldContext graphicsPort]; michael@0: CGContextSaveGState(ctx); michael@0: if ([oldContext isFlipped] != [frameView isFlipped]) { michael@0: CGContextTranslateCTM(ctx, 0, [self bounds].size.height); michael@0: CGContextScaleCTM(ctx, 1, -1); michael@0: } michael@0: [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:ctx flipped:[frameView isFlipped]]]; michael@0: [frameView _drawTitleBar:[frameView bounds]]; michael@0: CGContextRestoreGState(ctx); michael@0: [NSGraphicsContext setCurrentContext:oldContext]; michael@0: } michael@0: michael@0: - (void)drawTitlebarHighlight michael@0: { michael@0: DrawTitlebarHighlight([self bounds].size, [self cornerRadius], michael@0: mGeckoChild->DevPixelsToCocoaPoints(1)); michael@0: } michael@0: michael@0: - (void)releaseWidgets:(NSArray*)aWidgetArray michael@0: { michael@0: if (!aWidgetArray) { michael@0: return; michael@0: } michael@0: NSInteger count = [aWidgetArray count]; michael@0: for (NSInteger i = 0; i < count; ++i) { michael@0: NSNumber* pointer = (NSNumber*) [aWidgetArray objectAtIndex:i]; michael@0: nsIWidget* widget = (nsIWidget*) [pointer unsignedIntegerValue]; michael@0: NS_RELEASE(widget); michael@0: } michael@0: } michael@0: michael@0: - (void)viewWillDraw michael@0: { michael@0: if (mGeckoChild) { michael@0: // The OS normally *will* draw our NSWindow, no matter what we do here. michael@0: // But Gecko can delete our parent widget(s) (along with mGeckoChild) michael@0: // while processing a paint request, which closes our NSWindow and michael@0: // makes the OS throw an NSInternalInconsistencyException assertion when michael@0: // it tries to draw it. Sometimes the OS also aborts the browser process. michael@0: // So we need to retain our parent(s) here and not release it/them until michael@0: // the next time through the main thread's run loop. When we do this we michael@0: // also need to retain and release mGeckoChild, which holds a strong michael@0: // reference to us (otherwise we might have been deleted by the time michael@0: // releaseWidgets: is called on us). See bug 550392. michael@0: nsIWidget* parent = mGeckoChild->GetParent(); michael@0: if (parent) { michael@0: NSMutableArray* widgetArray = [NSMutableArray arrayWithCapacity:3]; michael@0: while (parent) { michael@0: NS_ADDREF(parent); michael@0: [widgetArray addObject:[NSNumber numberWithUnsignedInteger:(NSUInteger)parent]]; michael@0: parent = parent->GetParent(); michael@0: } michael@0: NS_ADDREF(mGeckoChild); michael@0: [widgetArray addObject:[NSNumber numberWithUnsignedInteger:(NSUInteger)mGeckoChild]]; michael@0: [self performSelector:@selector(releaseWidgets:) michael@0: withObject:widgetArray michael@0: afterDelay:0]; michael@0: } michael@0: michael@0: if ([self isUsingOpenGL]) { michael@0: // When our view covers the titlebar, we need to repaint the titlebar michael@0: // texture buffer when, for example, the window buttons are hovered. michael@0: // So we notify our nsChildView about any areas needing repainting. michael@0: mGeckoChild->NotifyDirtyRegion([self nativeDirtyRegionWithBoundingRect:[self bounds]]); michael@0: michael@0: if (mGeckoChild->GetLayerManager()->GetBackendType() == LayersBackend::LAYERS_CLIENT) { michael@0: ClientLayerManager *manager = static_cast(mGeckoChild->GetLayerManager()); michael@0: manager->AsShadowForwarder()->WindowOverlayChanged(); michael@0: } michael@0: } michael@0: michael@0: mGeckoChild->WillPaintWindow(); michael@0: } michael@0: [super viewWillDraw]; michael@0: } michael@0: michael@0: #if USE_CLICK_HOLD_CONTEXTMENU michael@0: // michael@0: // -clickHoldCallback: michael@0: // michael@0: // called from a timer two seconds after a mouse down to see if we should display michael@0: // a context menu (click-hold). |anEvent| is the original mouseDown event. If we're michael@0: // still in that mouseDown by this time, put up the context menu, otherwise just michael@0: // fuhgeddaboutit. |anEvent| has been retained by the OS until after this callback michael@0: // fires so we're ok there. michael@0: // michael@0: // This code currently messes in a bunch of edge cases (bugs 234751, 232964, 232314) michael@0: // so removing it until we get it straightened out. michael@0: // michael@0: - (void)clickHoldCallback:(id)theEvent; michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: if( theEvent == [NSApp currentEvent] ) { michael@0: // we're still in the middle of the same mousedown event here, activate michael@0: // click-hold context menu by triggering the right mouseDown action. michael@0: NSEvent* clickHoldEvent = [NSEvent mouseEventWithType:NSRightMouseDown michael@0: location:[theEvent locationInWindow] michael@0: modifierFlags:[theEvent modifierFlags] michael@0: timestamp:[theEvent timestamp] michael@0: windowNumber:[theEvent windowNumber] michael@0: context:[theEvent context] michael@0: eventNumber:[theEvent eventNumber] michael@0: clickCount:[theEvent clickCount] michael@0: pressure:[theEvent pressure]]; michael@0: [self rightMouseDown:clickHoldEvent]; michael@0: } michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: #endif michael@0: michael@0: // If we've just created a non-native context menu, we need to mark it as michael@0: // such and let the OS (and other programs) know when it opens and closes michael@0: // (this is how the OS knows to close other programs' context menus when michael@0: // ours open). We send the initial notification here, but others are sent michael@0: // in nsCocoaWindow::Show(). michael@0: - (void)maybeInitContextMenuTracking michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: #ifdef MOZ_USE_NATIVE_POPUP_WINDOWS michael@0: return; michael@0: #endif /* MOZ_USE_NATIVE_POPUP_WINDOWS */ michael@0: michael@0: nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener(); michael@0: NS_ENSURE_TRUE_VOID(rollupListener); michael@0: nsCOMPtr widget = rollupListener->GetRollupWidget(); michael@0: NS_ENSURE_TRUE_VOID(widget); michael@0: michael@0: NSWindow *popupWindow = (NSWindow*)widget->GetNativeData(NS_NATIVE_WINDOW); michael@0: if (!popupWindow || ![popupWindow isKindOfClass:[PopupWindow class]]) michael@0: return; michael@0: michael@0: [[NSDistributedNotificationCenter defaultCenter] michael@0: postNotificationName:@"com.apple.HIToolbox.beginMenuTrackingNotification" michael@0: object:@"org.mozilla.gecko.PopupWindow"]; michael@0: [(PopupWindow*)popupWindow setIsContextMenu:YES]; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: // Returns true if the event should no longer be processed, false otherwise. michael@0: // This does not return whether or not anything was rolled up. michael@0: - (BOOL)maybeRollup:(NSEvent*)theEvent michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; michael@0: michael@0: BOOL consumeEvent = NO; michael@0: michael@0: nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener(); michael@0: NS_ENSURE_TRUE(rollupListener, false); michael@0: nsCOMPtr rollupWidget = rollupListener->GetRollupWidget(); michael@0: if (rollupWidget) { michael@0: NSWindow* currentPopup = static_cast(rollupWidget->GetNativeData(NS_NATIVE_WINDOW)); michael@0: if (!nsCocoaUtils::IsEventOverWindow(theEvent, currentPopup)) { michael@0: // event is not over the rollup window, default is to roll up michael@0: bool shouldRollup = true; michael@0: michael@0: // check to see if scroll events should roll up the popup michael@0: if ([theEvent type] == NSScrollWheel) { michael@0: shouldRollup = rollupListener->ShouldRollupOnMouseWheelEvent(); michael@0: // consume scroll events that aren't over the popup michael@0: // unless the popup is an arrow panel michael@0: consumeEvent = rollupListener->ShouldConsumeOnMouseWheelEvent(); michael@0: } michael@0: michael@0: // if we're dealing with menus, we probably have submenus and michael@0: // we don't want to rollup if the click is in a parent menu of michael@0: // the current submenu michael@0: uint32_t popupsToRollup = UINT32_MAX; michael@0: nsAutoTArray widgetChain; michael@0: uint32_t sameTypeCount = rollupListener->GetSubmenuWidgetChain(&widgetChain); michael@0: for (uint32_t i = 0; i < widgetChain.Length(); i++) { michael@0: nsIWidget* widget = widgetChain[i]; michael@0: NSWindow* currWindow = (NSWindow*)widget->GetNativeData(NS_NATIVE_WINDOW); michael@0: if (nsCocoaUtils::IsEventOverWindow(theEvent, currWindow)) { michael@0: // don't roll up if the mouse event occurred within a menu of the michael@0: // same type. If the mouse event occurred in a menu higher than michael@0: // that, roll up, but pass the number of popups to Rollup so michael@0: // that only those of the same type close up. michael@0: if (i < sameTypeCount) { michael@0: shouldRollup = false; michael@0: } michael@0: else { michael@0: popupsToRollup = sameTypeCount; michael@0: } michael@0: break; michael@0: } michael@0: } michael@0: michael@0: if (shouldRollup) { michael@0: if ([theEvent type] == NSLeftMouseDown) { michael@0: NSPoint point = [NSEvent mouseLocation]; michael@0: FlipCocoaScreenCoordinate(point); michael@0: nsIntPoint pos(point.x, point.y); michael@0: consumeEvent = (BOOL)rollupListener->Rollup(popupsToRollup, &pos, nullptr); michael@0: } michael@0: else { michael@0: consumeEvent = (BOOL)rollupListener->Rollup(popupsToRollup, nullptr, nullptr); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: return consumeEvent; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO); michael@0: } michael@0: michael@0: /* michael@0: * In OS X Mountain Lion and above, smart zoom gestures are implemented in michael@0: * smartMagnifyWithEvent. In OS X Lion, they are implemented in michael@0: * magnifyWithEvent. See inline comments for more info. michael@0: * michael@0: * The prototypes swipeWithEvent, beginGestureWithEvent, magnifyWithEvent, michael@0: * smartMagnifyWithEvent, rotateWithEvent, and endGestureWithEvent were michael@0: * obtained from the following links: michael@0: * https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ApplicationKit/Classes/NSResponder_Class/Reference/Reference.html michael@0: * https://developer.apple.com/library/mac/#releasenotes/Cocoa/AppKit.html michael@0: */ michael@0: michael@0: - (void)swipeWithEvent:(NSEvent *)anEvent michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: if (!anEvent || !mGeckoChild) michael@0: return; michael@0: michael@0: nsAutoRetainCocoaObject kungFuDeathGrip(self); michael@0: michael@0: float deltaX = [anEvent deltaX]; // left=1.0, right=-1.0 michael@0: float deltaY = [anEvent deltaY]; // up=1.0, down=-1.0 michael@0: michael@0: // Setup the "swipe" event. michael@0: WidgetSimpleGestureEvent geckoEvent(true, NS_SIMPLE_GESTURE_SWIPE, michael@0: mGeckoChild); michael@0: [self convertCocoaMouseEvent:anEvent toGeckoEvent:&geckoEvent]; michael@0: michael@0: // Record the left/right direction. michael@0: if (deltaX > 0.0) michael@0: geckoEvent.direction |= nsIDOMSimpleGestureEvent::DIRECTION_LEFT; michael@0: else if (deltaX < 0.0) michael@0: geckoEvent.direction |= nsIDOMSimpleGestureEvent::DIRECTION_RIGHT; michael@0: michael@0: // Record the up/down direction. michael@0: if (deltaY > 0.0) michael@0: geckoEvent.direction |= nsIDOMSimpleGestureEvent::DIRECTION_UP; michael@0: else if (deltaY < 0.0) michael@0: geckoEvent.direction |= nsIDOMSimpleGestureEvent::DIRECTION_DOWN; michael@0: michael@0: // Send the event. michael@0: mGeckoChild->DispatchWindowEvent(geckoEvent); michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: - (void)beginGestureWithEvent:(NSEvent *)anEvent michael@0: { michael@0: if (!anEvent) michael@0: return; michael@0: michael@0: mGestureState = eGestureState_StartGesture; michael@0: mCumulativeMagnification = 0; michael@0: mCumulativeRotation = 0.0; michael@0: } michael@0: michael@0: - (void)magnifyWithEvent:(NSEvent *)anEvent michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: if (!anEvent || !mGeckoChild) michael@0: return; michael@0: michael@0: /* michael@0: * In OS X 10.7.* (Lion), smart zoom events come through magnifyWithEvent, michael@0: * instead of smartMagnifyWithEvent. See bug 863841. michael@0: */ michael@0: if ([ChildView isLionSmartMagnifyEvent: anEvent]) { michael@0: [self smartMagnifyWithEvent: anEvent]; michael@0: return; michael@0: } michael@0: michael@0: nsAutoRetainCocoaObject kungFuDeathGrip(self); michael@0: michael@0: float deltaZ = [anEvent deltaZ]; michael@0: michael@0: uint32_t msg; michael@0: switch (mGestureState) { michael@0: case eGestureState_StartGesture: michael@0: msg = NS_SIMPLE_GESTURE_MAGNIFY_START; michael@0: mGestureState = eGestureState_MagnifyGesture; michael@0: break; michael@0: michael@0: case eGestureState_MagnifyGesture: michael@0: msg = NS_SIMPLE_GESTURE_MAGNIFY_UPDATE; michael@0: break; michael@0: michael@0: case eGestureState_None: michael@0: case eGestureState_RotateGesture: michael@0: default: michael@0: return; michael@0: } michael@0: michael@0: // Setup the event. michael@0: WidgetSimpleGestureEvent geckoEvent(true, msg, mGeckoChild); michael@0: geckoEvent.delta = deltaZ; michael@0: [self convertCocoaMouseEvent:anEvent toGeckoEvent:&geckoEvent]; michael@0: michael@0: // Send the event. michael@0: mGeckoChild->DispatchWindowEvent(geckoEvent); michael@0: michael@0: // Keep track of the cumulative magnification for the final "magnify" event. michael@0: mCumulativeMagnification += deltaZ; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: - (void)smartMagnifyWithEvent:(NSEvent *)anEvent michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: if (!anEvent || !mGeckoChild) { michael@0: return; michael@0: } michael@0: michael@0: nsAutoRetainCocoaObject kungFuDeathGrip(self); michael@0: michael@0: // Setup the "double tap" event. michael@0: WidgetSimpleGestureEvent geckoEvent(true, NS_SIMPLE_GESTURE_TAP, michael@0: mGeckoChild); michael@0: [self convertCocoaMouseEvent:anEvent toGeckoEvent:&geckoEvent]; michael@0: geckoEvent.clickCount = 1; michael@0: michael@0: // Send the event. michael@0: mGeckoChild->DispatchWindowEvent(geckoEvent); michael@0: michael@0: // Clear the gesture state michael@0: mGestureState = eGestureState_None; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: - (void)rotateWithEvent:(NSEvent *)anEvent michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: if (!anEvent || !mGeckoChild) michael@0: return; michael@0: michael@0: nsAutoRetainCocoaObject kungFuDeathGrip(self); michael@0: michael@0: float rotation = [anEvent rotation]; michael@0: michael@0: uint32_t msg; michael@0: switch (mGestureState) { michael@0: case eGestureState_StartGesture: michael@0: msg = NS_SIMPLE_GESTURE_ROTATE_START; michael@0: mGestureState = eGestureState_RotateGesture; michael@0: break; michael@0: michael@0: case eGestureState_RotateGesture: michael@0: msg = NS_SIMPLE_GESTURE_ROTATE_UPDATE; michael@0: break; michael@0: michael@0: case eGestureState_None: michael@0: case eGestureState_MagnifyGesture: michael@0: default: michael@0: return; michael@0: } michael@0: michael@0: // Setup the event. michael@0: WidgetSimpleGestureEvent geckoEvent(true, msg, mGeckoChild); michael@0: [self convertCocoaMouseEvent:anEvent toGeckoEvent:&geckoEvent]; michael@0: geckoEvent.delta = -rotation; michael@0: if (rotation > 0.0) { michael@0: geckoEvent.direction = nsIDOMSimpleGestureEvent::ROTATION_COUNTERCLOCKWISE; michael@0: } else { michael@0: geckoEvent.direction = nsIDOMSimpleGestureEvent::ROTATION_CLOCKWISE; michael@0: } michael@0: michael@0: // Send the event. michael@0: mGeckoChild->DispatchWindowEvent(geckoEvent); michael@0: michael@0: // Keep track of the cumulative rotation for the final "rotate" event. michael@0: mCumulativeRotation += rotation; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: - (void)endGestureWithEvent:(NSEvent *)anEvent michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: if (!anEvent || !mGeckoChild) { michael@0: // Clear the gestures state if we cannot send an event. michael@0: mGestureState = eGestureState_None; michael@0: mCumulativeMagnification = 0.0; michael@0: mCumulativeRotation = 0.0; michael@0: return; michael@0: } michael@0: michael@0: nsAutoRetainCocoaObject kungFuDeathGrip(self); michael@0: michael@0: switch (mGestureState) { michael@0: case eGestureState_MagnifyGesture: michael@0: { michael@0: // Setup the "magnify" event. michael@0: WidgetSimpleGestureEvent geckoEvent(true, NS_SIMPLE_GESTURE_MAGNIFY, michael@0: mGeckoChild); michael@0: geckoEvent.delta = mCumulativeMagnification; michael@0: [self convertCocoaMouseEvent:anEvent toGeckoEvent:&geckoEvent]; michael@0: michael@0: // Send the event. michael@0: mGeckoChild->DispatchWindowEvent(geckoEvent); michael@0: } michael@0: break; michael@0: michael@0: case eGestureState_RotateGesture: michael@0: { michael@0: // Setup the "rotate" event. michael@0: WidgetSimpleGestureEvent geckoEvent(true, NS_SIMPLE_GESTURE_ROTATE, michael@0: mGeckoChild); michael@0: [self convertCocoaMouseEvent:anEvent toGeckoEvent:&geckoEvent]; michael@0: geckoEvent.delta = -mCumulativeRotation; michael@0: if (mCumulativeRotation > 0.0) { michael@0: geckoEvent.direction = nsIDOMSimpleGestureEvent::ROTATION_COUNTERCLOCKWISE; michael@0: } else { michael@0: geckoEvent.direction = nsIDOMSimpleGestureEvent::ROTATION_CLOCKWISE; michael@0: } michael@0: michael@0: // Send the event. michael@0: mGeckoChild->DispatchWindowEvent(geckoEvent); michael@0: } michael@0: break; michael@0: michael@0: case eGestureState_None: michael@0: case eGestureState_StartGesture: michael@0: default: michael@0: break; michael@0: } michael@0: michael@0: // Clear the gestures state. michael@0: mGestureState = eGestureState_None; michael@0: mCumulativeMagnification = 0.0; michael@0: mCumulativeRotation = 0.0; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: + (BOOL)isLionSmartMagnifyEvent:(NSEvent*)anEvent michael@0: { michael@0: /* michael@0: * On Lion, smart zoom events have type NSEventTypeGesture, subtype 0x16, michael@0: * whereas pinch zoom events have type NSEventTypeMagnify. So, use that to michael@0: * discriminate between the two. Smart zoom gestures do not call michael@0: * beginGestureWithEvent or endGestureWithEvent, so mGestureState is not michael@0: * changed. Documentation couldn't be found for the meaning of the subtype michael@0: * 0x16, but it will probably never change. See bug 863841. michael@0: */ michael@0: return nsCocoaFeatures::OnLionOrLater() && michael@0: !nsCocoaFeatures::OnMountainLionOrLater() && michael@0: [anEvent type] == NSEventTypeGesture && michael@0: [anEvent subtype] == 0x16; michael@0: } michael@0: michael@0: #ifdef __LP64__ michael@0: - (bool)sendSwipeEvent:(NSEvent*)aEvent michael@0: withKind:(uint32_t)aMsg michael@0: allowedDirections:(uint32_t*)aAllowedDirections michael@0: direction:(uint32_t)aDirection michael@0: delta:(double)aDelta michael@0: { michael@0: if (!mGeckoChild) michael@0: return false; michael@0: michael@0: WidgetSimpleGestureEvent geckoEvent(true, aMsg, mGeckoChild); michael@0: geckoEvent.direction = aDirection; michael@0: geckoEvent.delta = aDelta; michael@0: geckoEvent.allowedDirections = *aAllowedDirections; michael@0: [self convertCocoaMouseEvent:aEvent toGeckoEvent:&geckoEvent]; michael@0: bool eventCancelled = mGeckoChild->DispatchWindowEvent(geckoEvent); michael@0: *aAllowedDirections = geckoEvent.allowedDirections; michael@0: return eventCancelled; // event cancelled == swipe should start michael@0: } michael@0: michael@0: - (void)sendSwipeEndEvent:(NSEvent *)anEvent michael@0: allowedDirections:(uint32_t)aAllowedDirections michael@0: { michael@0: // Tear down animation overlay by sending a swipe end event. michael@0: uint32_t allowedDirectionsCopy = aAllowedDirections; michael@0: [self sendSwipeEvent:anEvent michael@0: withKind:NS_SIMPLE_GESTURE_SWIPE_END michael@0: allowedDirections:&allowedDirectionsCopy michael@0: direction:0 michael@0: delta:0.0]; michael@0: } michael@0: michael@0: // Support fluid swipe tracking on OS X 10.7 and higher. We must be careful michael@0: // to only invoke this support on a two-finger gesture that really michael@0: // is a swipe (and not a scroll) -- in other words, the app is responsible michael@0: // for deciding which is which. But once the decision is made, the OS tracks michael@0: // the swipe until it has finished, and decides whether or not it succeeded. michael@0: // A horizontal swipe has the same functionality as the Back and Forward michael@0: // buttons. michael@0: // This method is partly based on Apple sample code available at michael@0: // developer.apple.com/library/mac/#releasenotes/Cocoa/AppKitOlderNotes.html michael@0: // (under Fluid Swipe Tracking API). michael@0: - (void)maybeTrackScrollEventAsSwipe:(NSEvent *)anEvent michael@0: scrollOverflowX:(double)anOverflowX michael@0: scrollOverflowY:(double)anOverflowY michael@0: viewPortIsOverscrolled:(BOOL)aViewPortIsOverscrolled michael@0: { michael@0: if (!nsCocoaFeatures::OnLionOrLater()) { michael@0: return; michael@0: } michael@0: michael@0: // This method checks whether the AppleEnableSwipeNavigateWithScrolls global michael@0: // preference is set. If it isn't, fluid swipe tracking is disabled, and a michael@0: // horizontal two-finger gesture is always a scroll (even in Safari). This michael@0: // preference can't (currently) be set from the Preferences UI -- only using michael@0: // 'defaults write'. michael@0: if (![NSEvent isSwipeTrackingFromScrollEventsEnabled]) { michael@0: return; michael@0: } michael@0: michael@0: // We should only track scroll events as swipe if the viewport is being michael@0: // overscrolled. michael@0: if (!aViewPortIsOverscrolled) { michael@0: return; michael@0: } michael@0: michael@0: // Verify that this is a scroll wheel event with proper phase to be tracked michael@0: // by the OS. michael@0: if ([anEvent type] != NSScrollWheel || [anEvent phase] == NSEventPhaseNone) { michael@0: return; michael@0: } michael@0: michael@0: // Only initiate tracking if the user has tried to scroll past the edge of michael@0: // the current page (as indicated by 'anOverflowX' or 'anOverflowY' being michael@0: // non-zero). Gecko only sets WidgetMouseScrollEvent.scrollOverflow when it's michael@0: // processing NS_MOUSE_PIXEL_SCROLL events (not NS_MOUSE_SCROLL events). michael@0: if (anOverflowX == 0.0 && anOverflowY == 0.0) { michael@0: return; michael@0: } michael@0: michael@0: CGFloat deltaX, deltaY; michael@0: if ([anEvent hasPreciseScrollingDeltas]) { michael@0: deltaX = [anEvent scrollingDeltaX]; michael@0: deltaY = [anEvent scrollingDeltaY]; michael@0: } else { michael@0: return; michael@0: } michael@0: michael@0: uint32_t vDirs = (uint32_t)nsIDOMSimpleGestureEvent::DIRECTION_DOWN | michael@0: (uint32_t)nsIDOMSimpleGestureEvent::DIRECTION_UP; michael@0: uint32_t direction = 0; michael@0: michael@0: // Only initiate horizontal tracking for events whose horizontal element is michael@0: // at least eight times larger than its vertical element. This minimizes michael@0: // performance problems with vertical scrolls (by minimizing the possibility michael@0: // that they'll be misinterpreted as horizontal swipes), while still michael@0: // tolerating a small vertical element to a true horizontal swipe. The number michael@0: // '8' was arrived at by trial and error. michael@0: if (anOverflowX != 0.0 && deltaX != 0.0 && michael@0: fabsf(deltaX) > fabsf(deltaY) * 8) { michael@0: // Only initiate horizontal tracking for gestures that have just begun -- michael@0: // otherwise a scroll to one side of the page can have a swipe tacked on michael@0: // to it. michael@0: if ([anEvent phase] != NSEventPhaseBegan) { michael@0: return; michael@0: } michael@0: michael@0: if (deltaX < 0.0) { michael@0: direction = (uint32_t)nsIDOMSimpleGestureEvent::DIRECTION_RIGHT; michael@0: } else { michael@0: direction = (uint32_t)nsIDOMSimpleGestureEvent::DIRECTION_LEFT; michael@0: } michael@0: } michael@0: // Only initiate vertical tracking for events whose vertical element is michael@0: // at least two times larger than its horizontal element. This minimizes michael@0: // performance problems. The number '2' was arrived at by trial and error. michael@0: else if (anOverflowY != 0.0 && deltaY != 0.0 && michael@0: fabsf(deltaY) > fabsf(deltaX) * 2) { michael@0: if (deltaY < 0.0) { michael@0: direction = (uint32_t)nsIDOMSimpleGestureEvent::DIRECTION_DOWN; michael@0: } else { michael@0: direction = (uint32_t)nsIDOMSimpleGestureEvent::DIRECTION_UP; michael@0: } michael@0: michael@0: if ((mCurrentSwipeDir & vDirs) && (mCurrentSwipeDir != direction)) { michael@0: // If a swipe is currently being tracked kill it -- it's been interrupted michael@0: // by another gesture event. michael@0: if (mCancelSwipeAnimation && *mCancelSwipeAnimation == NO) { michael@0: *mCancelSwipeAnimation = YES; michael@0: mCancelSwipeAnimation = nil; michael@0: [self sendSwipeEndEvent:anEvent allowedDirections:0]; michael@0: } michael@0: return; michael@0: } michael@0: } else { michael@0: return; michael@0: } michael@0: michael@0: // Track the direction we're going in. michael@0: mCurrentSwipeDir = direction; michael@0: michael@0: uint32_t allowedDirections = 0; michael@0: // We're ready to start the animation. Tell Gecko about it, and at the same michael@0: // time ask it if it really wants to start an animation for this event. michael@0: // This event also reports back the directions that we can swipe in. michael@0: bool shouldStartSwipe = [self sendSwipeEvent:anEvent michael@0: withKind:NS_SIMPLE_GESTURE_SWIPE_START michael@0: allowedDirections:&allowedDirections michael@0: direction:direction michael@0: delta:0.0]; michael@0: michael@0: if (!shouldStartSwipe) { michael@0: return; michael@0: } michael@0: michael@0: // If a swipe is currently being tracked kill it -- it's been interrupted michael@0: // by another gesture event. michael@0: if (mCancelSwipeAnimation && *mCancelSwipeAnimation == NO) { michael@0: *mCancelSwipeAnimation = YES; michael@0: mCancelSwipeAnimation = nil; michael@0: } michael@0: michael@0: CGFloat min = 0.0; michael@0: CGFloat max = 0.0; michael@0: if (!(direction & vDirs)) { michael@0: min = (allowedDirections & nsIDOMSimpleGestureEvent::DIRECTION_RIGHT) ? michael@0: -1.0 : 0.0; michael@0: max = (allowedDirections & nsIDOMSimpleGestureEvent::DIRECTION_LEFT) ? michael@0: 1.0 : 0.0; michael@0: } michael@0: michael@0: __block BOOL animationCanceled = NO; michael@0: __block BOOL geckoSwipeEventSent = NO; michael@0: // At this point, anEvent is the first scroll wheel event in a two-finger michael@0: // horizontal gesture that we've decided to treat as a swipe. When we call michael@0: // [NSEvent trackSwipeEventWithOptions:...], the OS interprets all michael@0: // subsequent scroll wheel events that are part of this gesture as a swipe, michael@0: // and stops sending them to us. The OS calls the trackingHandler "block" michael@0: // multiple times, asynchronously (sometimes after [NSEvent michael@0: // maybeTrackScrollEventAsSwipe:...] has returned). The OS determines when michael@0: // the gesture has finished, and whether or not it was "successful" -- this michael@0: // information is passed to trackingHandler. We must be careful to only michael@0: // call [NSEvent maybeTrackScrollEventAsSwipe:...] on a "real" swipe -- michael@0: // otherwise two-finger scrolling performance will suffer significantly. michael@0: // Note that we use anEvent inside the block. This extends the lifetime of michael@0: // the anEvent object because it's retained by the block, see bug 682445. michael@0: // The block will release it when the block goes away at the end of the michael@0: // animation, or when the animation is canceled. michael@0: [anEvent trackSwipeEventWithOptions:NSEventSwipeTrackingLockDirection | michael@0: NSEventSwipeTrackingClampGestureAmount michael@0: dampenAmountThresholdMin:min michael@0: max:max michael@0: usingHandler:^(CGFloat gestureAmount, michael@0: NSEventPhase phase, michael@0: BOOL isComplete, michael@0: BOOL *stop) { michael@0: uint32_t allowedDirectionsCopy = allowedDirections; michael@0: // Since this tracking handler can be called asynchronously, mGeckoChild michael@0: // might have become NULL here (our child widget might have been michael@0: // destroyed). michael@0: // Checking for gestureAmount == 0.0 also works around bug 770626, which michael@0: // happens when DispatchWindowEvent() triggers a modal dialog, which spins michael@0: // the event loop and confuses the OS. This results in several re-entrant michael@0: // calls to this handler. michael@0: if (animationCanceled || !mGeckoChild || gestureAmount == 0.0) { michael@0: *stop = YES; michael@0: animationCanceled = YES; michael@0: if (gestureAmount == 0.0 || michael@0: ((direction & vDirs) && (direction != mCurrentSwipeDir))) { michael@0: if (mCancelSwipeAnimation) michael@0: *mCancelSwipeAnimation = YES; michael@0: mCancelSwipeAnimation = nil; michael@0: [self sendSwipeEndEvent:anEvent michael@0: allowedDirections:allowedDirectionsCopy]; michael@0: } michael@0: mCurrentSwipeDir = 0; michael@0: return; michael@0: } michael@0: michael@0: // Update animation overlay to match gestureAmount. michael@0: [self sendSwipeEvent:anEvent michael@0: withKind:NS_SIMPLE_GESTURE_SWIPE_UPDATE michael@0: allowedDirections:&allowedDirectionsCopy michael@0: direction:0.0 michael@0: delta:gestureAmount]; michael@0: michael@0: if (phase == NSEventPhaseEnded && !geckoSwipeEventSent) { michael@0: // The result of the swipe is now known, so the main event can be sent. michael@0: // The animation might continue even after this event was sent, so michael@0: // don't tear down the animation overlay yet. michael@0: michael@0: uint32_t directionCopy = direction; michael@0: michael@0: // gestureAmount is documented to be '-1', '0' or '1' when isComplete michael@0: // is TRUE, but the docs don't say anything about its value at other michael@0: // times. However, tests show that, when phase == NSEventPhaseEnded, michael@0: // gestureAmount is negative when it will be '-1' at isComplete, and michael@0: // positive when it will be '1'. And phase is never equal to michael@0: // NSEventPhaseEnded when gestureAmount will be '0' at isComplete. michael@0: geckoSwipeEventSent = YES; michael@0: [self sendSwipeEvent:anEvent michael@0: withKind:NS_SIMPLE_GESTURE_SWIPE michael@0: allowedDirections:&allowedDirectionsCopy michael@0: direction:directionCopy michael@0: delta:0.0]; michael@0: } michael@0: michael@0: if (isComplete) { michael@0: [self sendSwipeEndEvent:anEvent allowedDirections:allowedDirectionsCopy]; michael@0: mCurrentSwipeDir = 0; michael@0: mCancelSwipeAnimation = nil; michael@0: } michael@0: }]; michael@0: michael@0: mCancelSwipeAnimation = &animationCanceled; michael@0: } michael@0: #endif // #ifdef __LP64__ michael@0: michael@0: - (void)setUsingOMTCompositor:(BOOL)aUseOMTC michael@0: { michael@0: mUsingOMTCompositor = aUseOMTC; michael@0: } michael@0: michael@0: michael@0: // Returning NO from this method only disallows ordering on mousedown - in order michael@0: // to prevent it for mouseup too, we need to call [NSApp preventWindowOrdering] michael@0: // when handling the mousedown event. michael@0: - (BOOL)shouldDelayWindowOrderingForEvent:(NSEvent*)aEvent michael@0: { michael@0: // Always using system-provided window ordering for normal windows. michael@0: if (![[self window] isKindOfClass:[PopupWindow class]]) michael@0: return NO; michael@0: michael@0: // Don't reorder when we don't have a parent window, like when we're a michael@0: // context menu or a tooltip. michael@0: return ![[self window] parentWindow]; michael@0: } michael@0: michael@0: - (void)mouseDown:(NSEvent*)theEvent michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: if ([self shouldDelayWindowOrderingForEvent:theEvent]) { michael@0: [NSApp preventWindowOrdering]; michael@0: } michael@0: michael@0: // If we've already seen this event due to direct dispatch from menuForEvent: michael@0: // just bail; if not, remember it. michael@0: if (mLastMouseDownEvent == theEvent) { michael@0: [mLastMouseDownEvent release]; michael@0: mLastMouseDownEvent = nil; michael@0: return; michael@0: } michael@0: else { michael@0: [mLastMouseDownEvent release]; michael@0: mLastMouseDownEvent = [theEvent retain]; michael@0: } michael@0: michael@0: [gLastDragMouseDownEvent release]; michael@0: gLastDragMouseDownEvent = [theEvent retain]; michael@0: michael@0: // We need isClickThrough because at this point the window we're in might michael@0: // already have become main, so the check for isMainWindow in michael@0: // WindowAcceptsEvent isn't enough. It also has to check isClickThrough. michael@0: BOOL isClickThrough = (theEvent == mClickThroughMouseDownEvent); michael@0: [mClickThroughMouseDownEvent release]; michael@0: mClickThroughMouseDownEvent = nil; michael@0: michael@0: nsAutoRetainCocoaObject kungFuDeathGrip(self); michael@0: michael@0: if ([self maybeRollup:theEvent] || michael@0: !ChildViewMouseTracker::WindowAcceptsEvent([self window], theEvent, self, isClickThrough)) { michael@0: // Remember blocking because that means we want to block mouseup as well. michael@0: mBlockedLastMouseDown = YES; michael@0: return; michael@0: } michael@0: michael@0: #if USE_CLICK_HOLD_CONTEXTMENU michael@0: // fire off timer to check for click-hold after two seconds. retains |theEvent| michael@0: [self performSelector:@selector(clickHoldCallback:) withObject:theEvent afterDelay:2.0]; michael@0: #endif michael@0: michael@0: // in order to send gecko events we'll need a gecko widget michael@0: if (!mGeckoChild) michael@0: return; michael@0: michael@0: NSUInteger modifierFlags = [theEvent modifierFlags]; michael@0: michael@0: WidgetMouseEvent geckoEvent(true, NS_MOUSE_BUTTON_DOWN, mGeckoChild, michael@0: WidgetMouseEvent::eReal); michael@0: [self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent]; michael@0: michael@0: NSInteger clickCount = [theEvent clickCount]; michael@0: if (mBlockedLastMouseDown && clickCount > 1) { michael@0: // Don't send a double click if the first click of the double click was michael@0: // blocked. michael@0: clickCount--; michael@0: } michael@0: geckoEvent.clickCount = clickCount; michael@0: michael@0: if (modifierFlags & NSControlKeyMask) michael@0: geckoEvent.button = WidgetMouseEvent::eRightButton; michael@0: else michael@0: geckoEvent.button = WidgetMouseEvent::eLeftButton; michael@0: michael@0: // Create event for use by plugins. michael@0: // This is going to our child view so we don't need to look up the destination michael@0: // event type. michael@0: NPCocoaEvent cocoaEvent; michael@0: ChildViewMouseTracker::AttachPluginEvent(geckoEvent, self, theEvent, michael@0: NPCocoaEventMouseDown, michael@0: &cocoaEvent); michael@0: // Don't lose possible changes made above to clickCount michael@0: cocoaEvent.data.mouse.clickCount = clickCount; michael@0: michael@0: mGeckoChild->DispatchWindowEvent(geckoEvent); michael@0: mBlockedLastMouseDown = NO; michael@0: michael@0: // XXX maybe call markedTextSelectionChanged:client: here? michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: - (void)mouseUp:(NSEvent *)theEvent michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: if (!mGeckoChild || mBlockedLastMouseDown) michael@0: return; michael@0: michael@0: nsAutoRetainCocoaObject kungFuDeathGrip(self); michael@0: michael@0: NPCocoaEvent cocoaEvent; michael@0: michael@0: WidgetMouseEvent geckoEvent(true, NS_MOUSE_BUTTON_UP, mGeckoChild, michael@0: WidgetMouseEvent::eReal); michael@0: [self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent]; michael@0: if ([theEvent modifierFlags] & NSControlKeyMask) michael@0: geckoEvent.button = WidgetMouseEvent::eRightButton; michael@0: else michael@0: geckoEvent.button = WidgetMouseEvent::eLeftButton; michael@0: michael@0: // Create event for use by plugins. michael@0: // This is going to our child view so we don't need to look up the destination michael@0: // event type. michael@0: ChildViewMouseTracker::AttachPluginEvent(geckoEvent, self, theEvent, michael@0: NPCocoaEventMouseUp, michael@0: &cocoaEvent); michael@0: michael@0: // This might destroy our widget (and null out mGeckoChild). michael@0: bool defaultPrevented = mGeckoChild->DispatchWindowEvent(geckoEvent); michael@0: michael@0: // Check to see if we are double-clicking in the titlebar. michael@0: CGFloat locationInTitlebar = [[self window] frame].size.height - [theEvent locationInWindow].y; michael@0: if (!defaultPrevented && [theEvent clickCount] == 2 && michael@0: [[self window] isMovableByWindowBackground] && michael@0: [self shouldMinimizeOnTitlebarDoubleClick] && michael@0: [[self window] isKindOfClass:[ToolbarWindow class]] && michael@0: (locationInTitlebar < [(ToolbarWindow*)[self window] titlebarHeight] || michael@0: locationInTitlebar < [(ToolbarWindow*)[self window] unifiedToolbarHeight])) { michael@0: michael@0: NSButton *minimizeButton = [[self window] standardWindowButton:NSWindowMiniaturizeButton]; michael@0: [minimizeButton performClick:self]; michael@0: } michael@0: michael@0: // If our mouse-up event's location is over some other object (as might michael@0: // happen if it came at the end of a dragging operation), also send our michael@0: // Gecko frame a mouse-exit event. michael@0: if (mGeckoChild && mIsPluginView) { michael@0: ChildViewMouseTracker::ReEvaluateMouseEnterState(theEvent, self); michael@0: } michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: - (void)sendMouseEnterOrExitEvent:(NSEvent*)aEvent michael@0: enter:(BOOL)aEnter michael@0: type:(WidgetMouseEvent::exitType)aType michael@0: { michael@0: if (!mGeckoChild) michael@0: return; michael@0: michael@0: NSPoint windowEventLocation = nsCocoaUtils::EventLocationForWindow(aEvent, [self window]); michael@0: NSPoint localEventLocation = [self convertPoint:windowEventLocation fromView:nil]; michael@0: michael@0: uint32_t msg = aEnter ? NS_MOUSE_ENTER : NS_MOUSE_EXIT; michael@0: WidgetMouseEvent event(true, msg, mGeckoChild, WidgetMouseEvent::eReal); michael@0: event.refPoint = LayoutDeviceIntPoint::FromUntyped( michael@0: mGeckoChild->CocoaPointsToDevPixels(localEventLocation)); michael@0: michael@0: // Create event for use by plugins. michael@0: // This is going to our child view so we don't need to look up the destination michael@0: // event type. michael@0: NPCocoaEvent cocoaEvent; michael@0: ChildViewMouseTracker::AttachPluginEvent(event, self, aEvent, michael@0: (msg == NS_MOUSE_ENTER) ? michael@0: NPCocoaEventMouseEntered : NPCocoaEventMouseExited, michael@0: &cocoaEvent); michael@0: michael@0: event.exit = aType; michael@0: michael@0: nsEventStatus status; // ignored michael@0: mGeckoChild->DispatchEvent(&event, status); michael@0: } michael@0: michael@0: - (void)updateWindowDraggableStateOnMouseMove:(NSEvent*)theEvent michael@0: { michael@0: if (!theEvent || !mGeckoChild) { michael@0: return; michael@0: } michael@0: michael@0: nsCocoaWindow* windowWidget = mGeckoChild->GetXULWindowWidget(); michael@0: if (!windowWidget) { michael@0: return; michael@0: } michael@0: michael@0: // We assume later on that sending a hit test event won't cause widget destruction. michael@0: WidgetMouseEvent hitTestEvent(true, NS_MOUSE_MOZHITTEST, mGeckoChild, michael@0: WidgetMouseEvent::eReal); michael@0: [self convertCocoaMouseEvent:theEvent toGeckoEvent:&hitTestEvent]; michael@0: bool result = mGeckoChild->DispatchWindowEvent(hitTestEvent); michael@0: michael@0: [windowWidget->GetCocoaWindow() setMovableByWindowBackground:result]; michael@0: } michael@0: michael@0: - (void)handleMouseMoved:(NSEvent*)theEvent michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: if (!mGeckoChild) michael@0: return; michael@0: michael@0: WidgetMouseEvent geckoEvent(true, NS_MOUSE_MOVE, mGeckoChild, michael@0: WidgetMouseEvent::eReal); michael@0: [self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent]; michael@0: michael@0: // Create event for use by plugins. michael@0: // This is going to our child view so we don't need to look up the destination michael@0: // event type. michael@0: NPCocoaEvent cocoaEvent; michael@0: ChildViewMouseTracker::AttachPluginEvent(geckoEvent, self, theEvent, michael@0: NPCocoaEventMouseMoved, michael@0: &cocoaEvent); michael@0: mGeckoChild->DispatchWindowEvent(geckoEvent); michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: - (void)mouseDragged:(NSEvent*)theEvent michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: if (!mGeckoChild) michael@0: return; michael@0: michael@0: gLastDragView = self; michael@0: michael@0: NPCocoaEvent cocoaEvent; michael@0: michael@0: WidgetMouseEvent geckoEvent(true, NS_MOUSE_MOVE, mGeckoChild, michael@0: WidgetMouseEvent::eReal); michael@0: [self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent]; michael@0: michael@0: // create event for use by plugins michael@0: ChildViewMouseTracker::AttachPluginEvent(geckoEvent, self, theEvent, michael@0: NPCocoaEventMouseDragged, michael@0: &cocoaEvent); michael@0: michael@0: mGeckoChild->DispatchWindowEvent(geckoEvent); michael@0: michael@0: // Note, sending the above event might have destroyed our widget since we didn't retain. michael@0: // Fine so long as we don't access any local variables from here on. michael@0: gLastDragView = nil; michael@0: michael@0: // XXX maybe call markedTextSelectionChanged:client: here? michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: - (void)rightMouseDown:(NSEvent *)theEvent michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: nsAutoRetainCocoaObject kungFuDeathGrip(self); michael@0: michael@0: [self maybeRollup:theEvent]; michael@0: if (!mGeckoChild) michael@0: return; michael@0: michael@0: // The right mouse went down, fire off a right mouse down event to gecko michael@0: WidgetMouseEvent geckoEvent(true, NS_MOUSE_BUTTON_DOWN, mGeckoChild, michael@0: WidgetMouseEvent::eReal); michael@0: [self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent]; michael@0: geckoEvent.button = WidgetMouseEvent::eRightButton; michael@0: geckoEvent.clickCount = [theEvent clickCount]; michael@0: michael@0: // create event for use by plugins michael@0: NPCocoaEvent cocoaEvent; michael@0: ChildViewMouseTracker::AttachPluginEvent(geckoEvent, self, theEvent, michael@0: NPCocoaEventMouseDown, michael@0: &cocoaEvent); michael@0: michael@0: mGeckoChild->DispatchWindowEvent(geckoEvent); michael@0: if (!mGeckoChild) michael@0: return; michael@0: michael@0: // Let the superclass do the context menu stuff. michael@0: [super rightMouseDown:theEvent]; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: - (void)rightMouseUp:(NSEvent *)theEvent michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: if (!mGeckoChild) michael@0: return; michael@0: michael@0: NPCocoaEvent cocoaEvent; michael@0: michael@0: WidgetMouseEvent geckoEvent(true, NS_MOUSE_BUTTON_UP, mGeckoChild, michael@0: WidgetMouseEvent::eReal); michael@0: [self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent]; michael@0: geckoEvent.button = WidgetMouseEvent::eRightButton; michael@0: geckoEvent.clickCount = [theEvent clickCount]; michael@0: michael@0: // create event for use by plugins michael@0: ChildViewMouseTracker::AttachPluginEvent(geckoEvent, self, theEvent, michael@0: NPCocoaEventMouseUp, michael@0: &cocoaEvent); michael@0: michael@0: nsAutoRetainCocoaObject kungFuDeathGrip(self); michael@0: mGeckoChild->DispatchWindowEvent(geckoEvent); michael@0: michael@0: // If our mouse-up event's location is over some other object (as might michael@0: // happen if it came at the end of a dragging operation), also send our michael@0: // Gecko frame a mouse-exit event. michael@0: if (mGeckoChild && mIsPluginView) { michael@0: ChildViewMouseTracker::ReEvaluateMouseEnterState(theEvent, self); michael@0: } michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: - (void)rightMouseDragged:(NSEvent*)theEvent michael@0: { michael@0: if (!mGeckoChild) michael@0: return; michael@0: michael@0: WidgetMouseEvent geckoEvent(true, NS_MOUSE_MOVE, mGeckoChild, michael@0: WidgetMouseEvent::eReal); michael@0: [self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent]; michael@0: geckoEvent.button = WidgetMouseEvent::eRightButton; michael@0: michael@0: // create event for use by plugins michael@0: NPCocoaEvent cocoaEvent; michael@0: ChildViewMouseTracker::AttachPluginEvent(geckoEvent, self, theEvent, michael@0: NPCocoaEventMouseDragged, michael@0: &cocoaEvent); michael@0: michael@0: // send event into Gecko by going directly to the michael@0: // the widget. michael@0: mGeckoChild->DispatchWindowEvent(geckoEvent); michael@0: } michael@0: michael@0: - (void)otherMouseDown:(NSEvent *)theEvent michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: nsAutoRetainCocoaObject kungFuDeathGrip(self); michael@0: michael@0: if ([self maybeRollup:theEvent] || michael@0: !ChildViewMouseTracker::WindowAcceptsEvent([self window], theEvent, self)) michael@0: return; michael@0: michael@0: if (!mGeckoChild) michael@0: return; michael@0: michael@0: WidgetMouseEvent geckoEvent(true, NS_MOUSE_BUTTON_DOWN, mGeckoChild, michael@0: WidgetMouseEvent::eReal); michael@0: [self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent]; michael@0: geckoEvent.button = WidgetMouseEvent::eMiddleButton; michael@0: geckoEvent.clickCount = [theEvent clickCount]; michael@0: michael@0: // create event for use by plugins michael@0: NPCocoaEvent cocoaEvent; michael@0: ChildViewMouseTracker::AttachPluginEvent(geckoEvent, self, theEvent, michael@0: NPCocoaEventMouseDown, michael@0: &cocoaEvent); michael@0: michael@0: mGeckoChild->DispatchWindowEvent(geckoEvent); michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: - (void)otherMouseUp:(NSEvent *)theEvent michael@0: { michael@0: if (!mGeckoChild) michael@0: return; michael@0: michael@0: WidgetMouseEvent geckoEvent(true, NS_MOUSE_BUTTON_UP, mGeckoChild, michael@0: WidgetMouseEvent::eReal); michael@0: [self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent]; michael@0: geckoEvent.button = WidgetMouseEvent::eMiddleButton; michael@0: michael@0: // create event for use by plugins michael@0: NPCocoaEvent cocoaEvent; michael@0: ChildViewMouseTracker::AttachPluginEvent(geckoEvent, self, theEvent, michael@0: NPCocoaEventMouseUp, michael@0: &cocoaEvent); michael@0: michael@0: nsAutoRetainCocoaObject kungFuDeathGrip(self); michael@0: mGeckoChild->DispatchWindowEvent(geckoEvent); michael@0: michael@0: // If our mouse-up event's location is over some other object (as might michael@0: // happen if it came at the end of a dragging operation), also send our michael@0: // Gecko frame a mouse-exit event. michael@0: if (mGeckoChild && mIsPluginView) { michael@0: ChildViewMouseTracker::ReEvaluateMouseEnterState(theEvent, self); michael@0: } michael@0: } michael@0: michael@0: - (void)otherMouseDragged:(NSEvent*)theEvent michael@0: { michael@0: if (!mGeckoChild) michael@0: return; michael@0: michael@0: WidgetMouseEvent geckoEvent(true, NS_MOUSE_MOVE, mGeckoChild, michael@0: WidgetMouseEvent::eReal); michael@0: [self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent]; michael@0: geckoEvent.button = WidgetMouseEvent::eMiddleButton; michael@0: michael@0: // create event for use by plugins michael@0: NPCocoaEvent cocoaEvent; michael@0: ChildViewMouseTracker::AttachPluginEvent(geckoEvent, self, theEvent, michael@0: NPCocoaEventMouseDragged, michael@0: &cocoaEvent); michael@0: michael@0: // send event into Gecko by going directly to the michael@0: // the widget. michael@0: mGeckoChild->DispatchWindowEvent(geckoEvent); michael@0: } michael@0: michael@0: static int32_t RoundUp(double aDouble) michael@0: { michael@0: return aDouble < 0 ? static_cast(floor(aDouble)) : michael@0: static_cast(ceil(aDouble)); michael@0: } michael@0: michael@0: - (void)sendWheelStartOrStop:(uint32_t)msg forEvent:(NSEvent *)theEvent michael@0: { michael@0: WidgetWheelEvent wheelEvent(true, msg, mGeckoChild); michael@0: [self convertCocoaMouseWheelEvent:theEvent toGeckoEvent:&wheelEvent]; michael@0: mExpectingWheelStop = (msg == NS_WHEEL_START); michael@0: mGeckoChild->DispatchWindowEvent(wheelEvent); michael@0: } michael@0: michael@0: - (void)sendWheelCondition:(BOOL)condition first:(uint32_t)first second:(uint32_t)second forEvent:(NSEvent *)theEvent michael@0: { michael@0: if (mExpectingWheelStop == condition) { michael@0: [self sendWheelStartOrStop:first forEvent:theEvent]; michael@0: } michael@0: [self sendWheelStartOrStop:second forEvent:theEvent]; michael@0: } michael@0: michael@0: - (void)scrollWheel:(NSEvent*)theEvent michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: nsAutoRetainCocoaObject kungFuDeathGrip(self); michael@0: michael@0: ChildViewMouseTracker::MouseScrolled(theEvent); michael@0: michael@0: if ([self maybeRollup:theEvent]) { michael@0: return; michael@0: } michael@0: michael@0: if (!mGeckoChild) { michael@0: return; michael@0: } michael@0: michael@0: if (nsCocoaFeatures::OnLionOrLater()) { michael@0: NSEventPhase phase = [theEvent phase]; michael@0: // Fire NS_WHEEL_START/STOP events when 2 fingers touch/release the touchpad. michael@0: if (phase & NSEventPhaseMayBegin) { michael@0: [self sendWheelCondition:YES first:NS_WHEEL_STOP second:NS_WHEEL_START forEvent:theEvent]; michael@0: return; michael@0: } michael@0: michael@0: if (phase & (NSEventPhaseEnded | NSEventPhaseCancelled)) { michael@0: [self sendWheelCondition:NO first:NS_WHEEL_START second:NS_WHEEL_STOP forEvent:theEvent]; michael@0: return; michael@0: } michael@0: } michael@0: michael@0: WidgetWheelEvent wheelEvent(true, NS_WHEEL_WHEEL, mGeckoChild); michael@0: [self convertCocoaMouseWheelEvent:theEvent toGeckoEvent:&wheelEvent]; michael@0: michael@0: wheelEvent.lineOrPageDeltaX = RoundUp(-[theEvent deltaX]); michael@0: wheelEvent.lineOrPageDeltaY = RoundUp(-[theEvent deltaY]); michael@0: michael@0: if (wheelEvent.deltaMode == nsIDOMWheelEvent::DOM_DELTA_PIXEL) { michael@0: // Some scrolling devices supports pixel scrolling, e.g. a Macbook michael@0: // touchpad or a Mighty Mouse. On those devices, [theEvent deviceDeltaX/Y] michael@0: // contains the amount of pixels to scroll. Since Lion this has changed michael@0: // to [theEvent scrollingDeltaX/Y]. michael@0: double scale = mGeckoChild->BackingScaleFactor(); michael@0: if ([theEvent respondsToSelector:@selector(scrollingDeltaX)]) { michael@0: wheelEvent.deltaX = -[theEvent scrollingDeltaX] * scale; michael@0: wheelEvent.deltaY = -[theEvent scrollingDeltaY] * scale; michael@0: } else { michael@0: wheelEvent.deltaX = -[theEvent deviceDeltaX] * scale; michael@0: wheelEvent.deltaY = -[theEvent deviceDeltaY] * scale; michael@0: } michael@0: } else { michael@0: wheelEvent.deltaX = -[theEvent deltaX]; michael@0: wheelEvent.deltaY = -[theEvent deltaY]; michael@0: } michael@0: michael@0: // TODO: We should not set deltaZ for now because we're not sure if we should michael@0: // revert the sign. michael@0: // wheelEvent.deltaZ = [theEvent deltaZ]; michael@0: michael@0: if (!wheelEvent.deltaX && !wheelEvent.deltaY && !wheelEvent.deltaZ) { michael@0: // No sense in firing off a Gecko event. michael@0: return; michael@0: } michael@0: michael@0: NPCocoaEvent cocoaEvent; michael@0: ChildViewMouseTracker::AttachPluginEvent(wheelEvent, self, theEvent, michael@0: NPCocoaEventScrollWheel, michael@0: &cocoaEvent); michael@0: michael@0: mGeckoChild->DispatchWindowEvent(wheelEvent); michael@0: if (!mGeckoChild) { michael@0: return; michael@0: } michael@0: michael@0: #ifdef __LP64__ michael@0: // overflowDeltaX and overflowDeltaY tell us when the user has tried to michael@0: // scroll past the edge of a page (in those cases it's non-zero). michael@0: if ((wheelEvent.deltaMode == nsIDOMWheelEvent::DOM_DELTA_PIXEL) && michael@0: (wheelEvent.deltaX != 0.0 || wheelEvent.deltaY != 0.0)) { michael@0: [self maybeTrackScrollEventAsSwipe:theEvent michael@0: scrollOverflowX:wheelEvent.overflowDeltaX michael@0: scrollOverflowY:wheelEvent.overflowDeltaY michael@0: viewPortIsOverscrolled:wheelEvent.mViewPortIsOverscrolled]; michael@0: } michael@0: #endif // #ifdef __LP64__ michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: -(NSMenu*)menuForEvent:(NSEvent*)theEvent michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; michael@0: michael@0: if (!mGeckoChild || [self isPluginView]) michael@0: return nil; michael@0: michael@0: nsAutoRetainCocoaObject kungFuDeathGrip(self); michael@0: michael@0: [self maybeRollup:theEvent]; michael@0: if (!mGeckoChild) michael@0: return nil; michael@0: michael@0: // Cocoa doesn't always dispatch a mouseDown: for a control-click event, michael@0: // depends on what we return from menuForEvent:. Gecko always expects one michael@0: // and expects the mouse down event before the context menu event, so michael@0: // get that event sent first if this is a left mouse click. michael@0: if ([theEvent type] == NSLeftMouseDown) { michael@0: [self mouseDown:theEvent]; michael@0: if (!mGeckoChild) michael@0: return nil; michael@0: } michael@0: michael@0: WidgetMouseEvent geckoEvent(true, NS_CONTEXTMENU, mGeckoChild, michael@0: WidgetMouseEvent::eReal); michael@0: [self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent]; michael@0: geckoEvent.button = WidgetMouseEvent::eRightButton; michael@0: mGeckoChild->DispatchWindowEvent(geckoEvent); michael@0: if (!mGeckoChild) michael@0: return nil; michael@0: michael@0: [self maybeInitContextMenuTracking]; michael@0: michael@0: // Go up our view chain to fetch the correct menu to return. michael@0: return [self contextMenu]; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NIL; michael@0: } michael@0: michael@0: - (NSMenu*)contextMenu michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; michael@0: michael@0: NSView* superView = [self superview]; michael@0: if ([superView respondsToSelector:@selector(contextMenu)]) michael@0: return [(NSView*)superView contextMenu]; michael@0: michael@0: return nil; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NIL; michael@0: } michael@0: michael@0: - (void) convertCocoaMouseWheelEvent:(NSEvent*)aMouseEvent michael@0: toGeckoEvent:(WidgetWheelEvent*)outWheelEvent michael@0: { michael@0: [self convertCocoaMouseEvent:aMouseEvent toGeckoEvent:outWheelEvent]; michael@0: outWheelEvent->deltaMode = michael@0: Preferences::GetBool("mousewheel.enable_pixel_scrolling", true) ? michael@0: nsIDOMWheelEvent::DOM_DELTA_PIXEL : nsIDOMWheelEvent::DOM_DELTA_LINE; michael@0: michael@0: // Calling deviceDeltaX or deviceDeltaY on theEvent will trigger a Cocoa michael@0: // assertion and an Objective-C NSInternalInconsistencyException if the michael@0: // underlying "Carbon" event doesn't contain pixel scrolling information. michael@0: // For these events, carbonEventKind is kEventMouseWheelMoved instead of michael@0: // kEventMouseScroll. michael@0: if (outWheelEvent->deltaMode == nsIDOMWheelEvent::DOM_DELTA_PIXEL) { michael@0: EventRef theCarbonEvent = [aMouseEvent _eventRef]; michael@0: UInt32 carbonEventKind = theCarbonEvent ? ::GetEventKind(theCarbonEvent) : 0; michael@0: if (carbonEventKind != kEventMouseScroll) { michael@0: outWheelEvent->deltaMode = nsIDOMWheelEvent::DOM_DELTA_LINE; michael@0: } michael@0: } michael@0: outWheelEvent->isMomentum = nsCocoaUtils::IsMomentumScrollEvent(aMouseEvent); michael@0: } michael@0: michael@0: - (void) convertCocoaMouseEvent:(NSEvent*)aMouseEvent michael@0: toGeckoEvent:(WidgetInputEvent*)outGeckoEvent michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: NS_ASSERTION(outGeckoEvent, "convertCocoaMouseEvent:toGeckoEvent: requires non-null aoutGeckoEvent"); michael@0: if (!outGeckoEvent) michael@0: return; michael@0: michael@0: nsCocoaUtils::InitInputEvent(*outGeckoEvent, aMouseEvent); michael@0: michael@0: // convert point to view coordinate system michael@0: NSPoint locationInWindow = nsCocoaUtils::EventLocationForWindow(aMouseEvent, [self window]); michael@0: NSPoint localPoint = [self convertPoint:locationInWindow fromView:nil]; michael@0: michael@0: outGeckoEvent->refPoint = LayoutDeviceIntPoint::FromUntyped( michael@0: mGeckoChild->CocoaPointsToDevPixels(localPoint)); michael@0: michael@0: WidgetMouseEventBase* mouseEvent = outGeckoEvent->AsMouseEventBase(); michael@0: mouseEvent->buttons = 0; michael@0: NSUInteger mouseButtons = [NSEvent pressedMouseButtons]; michael@0: michael@0: if (mouseButtons & 0x01) { michael@0: mouseEvent->buttons |= WidgetMouseEvent::eLeftButtonFlag; michael@0: } michael@0: if (mouseButtons & 0x02) { michael@0: mouseEvent->buttons |= WidgetMouseEvent::eRightButtonFlag; michael@0: } michael@0: if (mouseButtons & 0x04) { michael@0: mouseEvent->buttons |= WidgetMouseEvent::eMiddleButtonFlag; michael@0: } michael@0: if (mouseButtons & 0x08) { michael@0: mouseEvent->buttons |= WidgetMouseEvent::e4thButtonFlag; michael@0: } michael@0: if (mouseButtons & 0x10) { michael@0: mouseEvent->buttons |= WidgetMouseEvent::e5thButtonFlag; michael@0: } michael@0: michael@0: switch ([aMouseEvent type]) { michael@0: case NSLeftMouseDown: michael@0: case NSLeftMouseUp: michael@0: case NSLeftMouseDragged: michael@0: case NSRightMouseDown: michael@0: case NSRightMouseUp: michael@0: case NSRightMouseDragged: michael@0: case NSOtherMouseDown: michael@0: case NSOtherMouseUp: michael@0: case NSOtherMouseDragged: michael@0: if ([aMouseEvent subtype] == NSTabletPointEventSubtype) { michael@0: mouseEvent->pressure = [aMouseEvent pressure]; michael@0: } michael@0: break; michael@0: } michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: michael@0: #pragma mark - michael@0: // NSTextInput implementation michael@0: michael@0: - (void)insertText:(id)insertString michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: NS_ENSURE_TRUE_VOID(mGeckoChild); michael@0: michael@0: nsAutoRetainCocoaObject kungFuDeathGrip(self); michael@0: michael@0: NSAttributedString* attrStr; michael@0: if ([insertString isKindOfClass:[NSAttributedString class]]) { michael@0: attrStr = static_cast(insertString); michael@0: } else { michael@0: attrStr = michael@0: [[[NSAttributedString alloc] initWithString:insertString] autorelease]; michael@0: } michael@0: michael@0: mTextInputHandler->InsertText(attrStr); michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: - (void)insertNewline:(id)sender michael@0: { michael@0: [self insertText:@"\n"]; michael@0: } michael@0: michael@0: - (void) doCommandBySelector:(SEL)aSelector michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: if (!mGeckoChild || !mTextInputHandler) { michael@0: return; michael@0: } michael@0: michael@0: const char* sel = reinterpret_cast(aSelector); michael@0: if (!mTextInputHandler->DoCommandBySelector(sel)) { michael@0: [super doCommandBySelector:aSelector]; michael@0: } michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: - (void) setMarkedText:(id)aString selectedRange:(NSRange)selRange michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: NS_ENSURE_TRUE_VOID(mTextInputHandler); michael@0: michael@0: nsAutoRetainCocoaObject kungFuDeathGrip(self); michael@0: michael@0: NSAttributedString* attrStr; michael@0: if ([aString isKindOfClass:[NSAttributedString class]]) { michael@0: attrStr = static_cast(aString); michael@0: } else { michael@0: attrStr = [[[NSAttributedString alloc] initWithString:aString] autorelease]; michael@0: } michael@0: michael@0: mTextInputHandler->SetMarkedText(attrStr, selRange); michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: - (void) unmarkText michael@0: { michael@0: NS_ENSURE_TRUE(mTextInputHandler, ); michael@0: mTextInputHandler->CommitIMEComposition(); michael@0: } michael@0: michael@0: - (BOOL) hasMarkedText michael@0: { michael@0: NS_ENSURE_TRUE(mTextInputHandler, NO); michael@0: return mTextInputHandler->HasMarkedText(); michael@0: } michael@0: michael@0: - (BOOL)shouldMinimizeOnTitlebarDoubleClick michael@0: { michael@0: NSString *MDAppleMiniaturizeOnDoubleClickKey = michael@0: @"AppleMiniaturizeOnDoubleClick"; michael@0: NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; michael@0: bool shouldMinimize = [[userDefaults michael@0: objectForKey:MDAppleMiniaturizeOnDoubleClickKey] boolValue]; michael@0: michael@0: return shouldMinimize; michael@0: } michael@0: michael@0: - (NSInteger) conversationIdentifier michael@0: { michael@0: NS_ENSURE_TRUE(mTextInputHandler, reinterpret_cast(self)); michael@0: return mTextInputHandler->ConversationIdentifier(); michael@0: } michael@0: michael@0: - (NSAttributedString *) attributedSubstringFromRange:(NSRange)theRange michael@0: { michael@0: NS_ENSURE_TRUE(mTextInputHandler, nil); michael@0: return mTextInputHandler->GetAttributedSubstringFromRange(theRange); michael@0: } michael@0: michael@0: - (NSRange) markedRange michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; michael@0: michael@0: NS_ENSURE_TRUE(mTextInputHandler, NSMakeRange(NSNotFound, 0)); michael@0: return mTextInputHandler->MarkedRange(); michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NSMakeRange(0, 0)); michael@0: } michael@0: michael@0: - (NSRange) selectedRange michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; michael@0: michael@0: NS_ENSURE_TRUE(mTextInputHandler, NSMakeRange(NSNotFound, 0)); michael@0: return mTextInputHandler->SelectedRange(); michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NSMakeRange(0, 0)); michael@0: } michael@0: michael@0: - (NSRect) firstRectForCharacterRange:(NSRange)theRange michael@0: { michael@0: NSRect rect; michael@0: NS_ENSURE_TRUE(mTextInputHandler, rect); michael@0: return mTextInputHandler->FirstRectForCharacterRange(theRange); michael@0: } michael@0: michael@0: - (NSUInteger)characterIndexForPoint:(NSPoint)thePoint michael@0: { michael@0: NS_ENSURE_TRUE(mTextInputHandler, 0); michael@0: return mTextInputHandler->CharacterIndexForPoint(thePoint); michael@0: } michael@0: michael@0: - (NSArray*) validAttributesForMarkedText michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; michael@0: michael@0: NS_ENSURE_TRUE(mTextInputHandler, [NSArray array]); michael@0: return mTextInputHandler->GetValidAttributesForMarkedText(); michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NIL; michael@0: } michael@0: michael@0: #pragma mark - michael@0: // NSTextInputClient implementation michael@0: michael@0: - (void)insertText:(id)aString replacementRange:(NSRange)replacementRange michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: NS_ENSURE_TRUE_VOID(mGeckoChild); michael@0: michael@0: nsAutoRetainCocoaObject kungFuDeathGrip(self); michael@0: michael@0: NSAttributedString* attrStr; michael@0: if ([aString isKindOfClass:[NSAttributedString class]]) { michael@0: attrStr = static_cast(aString); michael@0: } else { michael@0: attrStr = [[[NSAttributedString alloc] initWithString:aString] autorelease]; michael@0: } michael@0: michael@0: mTextInputHandler->InsertText(attrStr, &replacementRange); michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: - (void)setMarkedText:(id)aString selectedRange:(NSRange)selectedRange michael@0: replacementRange:(NSRange)replacementRange michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: NS_ENSURE_TRUE_VOID(mTextInputHandler); michael@0: michael@0: nsAutoRetainCocoaObject kungFuDeathGrip(self); michael@0: michael@0: NSAttributedString* attrStr; michael@0: if ([aString isKindOfClass:[NSAttributedString class]]) { michael@0: attrStr = static_cast(aString); michael@0: } else { michael@0: attrStr = [[[NSAttributedString alloc] initWithString:aString] autorelease]; michael@0: } michael@0: michael@0: mTextInputHandler->SetMarkedText(attrStr, selectedRange, &replacementRange); michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: - (NSAttributedString*)attributedSubstringForProposedRange:(NSRange)aRange michael@0: actualRange:(NSRangePointer)actualRange michael@0: { michael@0: NS_ENSURE_TRUE(mTextInputHandler, nil); michael@0: return mTextInputHandler->GetAttributedSubstringFromRange(aRange, michael@0: actualRange); michael@0: } michael@0: michael@0: - (NSRect)firstRectForCharacterRange:(NSRange)aRange michael@0: actualRange:(NSRangePointer)actualRange michael@0: { michael@0: NS_ENSURE_TRUE(mTextInputHandler, NSMakeRect(0.0, 0.0, 0.0, 0.0)); michael@0: return mTextInputHandler->FirstRectForCharacterRange(aRange, actualRange); michael@0: } michael@0: michael@0: - (NSInteger)windowLevel michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; michael@0: michael@0: NS_ENSURE_TRUE(mTextInputHandler, [[self window] level]); michael@0: return mTextInputHandler->GetWindowLevel(); michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NSNormalWindowLevel); michael@0: } michael@0: michael@0: #pragma mark - michael@0: michael@0: #ifdef __LP64__ michael@0: - (NSTextInputContext *)inputContext michael@0: { michael@0: if (mIsPluginView && mPluginEventModel == NPEventModelCocoa) michael@0: return [[ComplexTextInputPanel sharedComplexTextInputPanel] inputContext]; michael@0: else michael@0: return [super inputContext]; michael@0: } michael@0: #endif michael@0: michael@0: // This is a private API that Cocoa uses. michael@0: // Cocoa will call this after the menu system returns "NO" for "performKeyEquivalent:". michael@0: // We want all they key events we can get so just return YES. In particular, this fixes michael@0: // ctrl-tab - we don't get a "keyDown:" call for that without this. michael@0: - (BOOL)_wantsKeyDownForEvent:(NSEvent*)event michael@0: { michael@0: return YES; michael@0: } michael@0: michael@0: - (void)keyDown:(NSEvent*)theEvent michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: #if !defined(RELEASE_BUILD) || defined(DEBUG) michael@0: if (mGeckoChild && mTextInputHandler && mTextInputHandler->IsFocused()) { michael@0: #ifdef MOZ_CRASHREPORTER michael@0: NSWindow* window = [self window]; michael@0: NSString* info = [NSString stringWithFormat:@"\nview [%@], window [%@], key event [%@], window is key %i, is fullscreen %i, app is active %i", michael@0: self, window, theEvent, [window isKeyWindow], ([window styleMask] & (1 << 14)) != 0, michael@0: [NSApp isActive]]; michael@0: nsAutoCString additionalInfo([info UTF8String]); michael@0: #endif michael@0: if (mIsPluginView) { michael@0: if (TextInputHandler::IsSecureEventInputEnabled()) { michael@0: #define CRASH_MESSAGE "While a plugin has focus, we must not be in secure mode" michael@0: #ifdef MOZ_CRASHREPORTER michael@0: CrashReporter::AppendAppNotesToCrashReport(NS_LITERAL_CSTRING("\nBug 893973: ") + michael@0: NS_LITERAL_CSTRING(CRASH_MESSAGE)); michael@0: CrashReporter::AppendAppNotesToCrashReport(additionalInfo); michael@0: #endif michael@0: MOZ_CRASH(CRASH_MESSAGE); michael@0: #undef CRASH_MESSAGE michael@0: } michael@0: } else if (mGeckoChild->GetInputContext().IsPasswordEditor() && michael@0: !TextInputHandler::IsSecureEventInputEnabled()) { michael@0: #define CRASH_MESSAGE "A password editor has focus, but not in secure input mode" michael@0: #ifdef MOZ_CRASHREPORTER michael@0: CrashReporter::AppendAppNotesToCrashReport(NS_LITERAL_CSTRING("\nBug 893973: ") + michael@0: NS_LITERAL_CSTRING(CRASH_MESSAGE)); michael@0: CrashReporter::AppendAppNotesToCrashReport(additionalInfo); michael@0: #endif michael@0: MOZ_CRASH(CRASH_MESSAGE); michael@0: #undef CRASH_MESSAGE michael@0: } else if (!mGeckoChild->GetInputContext().IsPasswordEditor() && michael@0: TextInputHandler::IsSecureEventInputEnabled()) { michael@0: #define CRASH_MESSAGE "A non-password editor has focus, but in secure input mode" michael@0: #ifdef MOZ_CRASHREPORTER michael@0: CrashReporter::AppendAppNotesToCrashReport(NS_LITERAL_CSTRING("\nBug 893973: ") + michael@0: NS_LITERAL_CSTRING(CRASH_MESSAGE)); michael@0: CrashReporter::AppendAppNotesToCrashReport(additionalInfo); michael@0: #endif michael@0: MOZ_CRASH(CRASH_MESSAGE); michael@0: #undef CRASH_MESSAGE michael@0: } michael@0: } michael@0: #endif // #if !defined(RELEASE_BUILD) || defined(DEBUG) michael@0: michael@0: if (mGeckoChild && mTextInputHandler && mIsPluginView) { michael@0: mTextInputHandler->HandleKeyDownEventForPlugin(theEvent); michael@0: return; michael@0: } michael@0: michael@0: nsAutoRetainCocoaObject kungFuDeathGrip(self); michael@0: bool handled = false; michael@0: if (mGeckoChild && mTextInputHandler) { michael@0: handled = mTextInputHandler->HandleKeyDownEvent(theEvent); michael@0: } michael@0: michael@0: // We always allow keyboard events to propagate to keyDown: but if they are not michael@0: // handled we give special Application menu items a chance to act. michael@0: if (!handled && sApplicationMenu) { michael@0: [sApplicationMenu performKeyEquivalent:theEvent]; michael@0: } michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: - (void)keyUp:(NSEvent*)theEvent michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: NS_ENSURE_TRUE(mGeckoChild, ); michael@0: michael@0: nsAutoRetainCocoaObject kungFuDeathGrip(self); michael@0: michael@0: if (mIsPluginView) { michael@0: mTextInputHandler->HandleKeyUpEventForPlugin(theEvent); michael@0: return; michael@0: } michael@0: michael@0: mTextInputHandler->HandleKeyUpEvent(theEvent); michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: - (void)flagsChanged:(NSEvent*)theEvent michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: NS_ENSURE_TRUE(mGeckoChild, ); michael@0: michael@0: nsAutoRetainCocoaObject kungFuDeathGrip(self); michael@0: mTextInputHandler->HandleFlagsChanged(theEvent); michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: - (BOOL) isFirstResponder michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; michael@0: michael@0: NSResponder* resp = [[self window] firstResponder]; michael@0: return (resp == (NSResponder*)self); michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO); michael@0: } michael@0: michael@0: - (BOOL)isDragInProgress michael@0: { michael@0: if (!mDragService) michael@0: return NO; michael@0: michael@0: nsCOMPtr dragSession; michael@0: mDragService->GetCurrentSession(getter_AddRefs(dragSession)); michael@0: return dragSession != nullptr; michael@0: } michael@0: michael@0: - (BOOL)inactiveWindowAcceptsMouseEvent:(NSEvent*)aEvent michael@0: { michael@0: // If we're being destroyed assume the default -- return YES. michael@0: if (!mGeckoChild) michael@0: return YES; michael@0: michael@0: WidgetMouseEvent geckoEvent(true, NS_MOUSE_ACTIVATE, mGeckoChild, michael@0: WidgetMouseEvent::eReal); michael@0: [self convertCocoaMouseEvent:aEvent toGeckoEvent:&geckoEvent]; michael@0: return !mGeckoChild->DispatchWindowEvent(geckoEvent); michael@0: } michael@0: michael@0: // Returns NO if the plugin shouldn't be focused/unfocused. michael@0: - (BOOL)updatePluginFocusStatus:(BOOL)getFocus michael@0: { michael@0: if (!mGeckoChild) michael@0: return NO; michael@0: michael@0: if (mPluginEventModel == NPEventModelCocoa) { michael@0: WidgetPluginEvent pluginEvent(true, NS_PLUGIN_FOCUS_EVENT, mGeckoChild); michael@0: NPCocoaEvent cocoaEvent; michael@0: nsCocoaUtils::InitNPCocoaEvent(&cocoaEvent); michael@0: cocoaEvent.type = NPCocoaEventFocusChanged; michael@0: cocoaEvent.data.focus.hasFocus = getFocus; michael@0: nsCocoaUtils::InitPluginEvent(pluginEvent, cocoaEvent); michael@0: mGeckoChild->DispatchWindowEvent(pluginEvent); michael@0: michael@0: if (getFocus) michael@0: [self sendFocusEvent:NS_PLUGIN_FOCUS]; michael@0: } michael@0: michael@0: return YES; michael@0: } michael@0: michael@0: // We must always call through to our superclass, even when mGeckoChild is michael@0: // nil -- otherwise the keyboard focus can end up in the wrong NSView. michael@0: - (BOOL)becomeFirstResponder michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; michael@0: michael@0: if (mIsPluginView) { michael@0: if (![self updatePluginFocusStatus:YES]) michael@0: return NO; michael@0: } michael@0: michael@0: return [super becomeFirstResponder]; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(YES); michael@0: } michael@0: michael@0: // We must always call through to our superclass, even when mGeckoChild is michael@0: // nil -- otherwise the keyboard focus can end up in the wrong NSView. michael@0: - (BOOL)resignFirstResponder michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; michael@0: michael@0: if (mIsPluginView) { michael@0: if (![self updatePluginFocusStatus:NO]) michael@0: return NO; michael@0: } michael@0: michael@0: return [super resignFirstResponder]; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(YES); michael@0: } michael@0: michael@0: - (void)viewsWindowDidBecomeKey michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: if (!mGeckoChild) michael@0: return; michael@0: michael@0: nsAutoRetainCocoaObject kungFuDeathGrip(self); michael@0: michael@0: // check to see if the window implements the mozWindow protocol. This michael@0: // allows embedders to avoid re-entrant calls to -makeKeyAndOrderFront, michael@0: // which can happen because these activate calls propagate out michael@0: // to the embedder via nsIEmbeddingSiteWindow::SetFocus(). michael@0: BOOL isMozWindow = [[self window] respondsToSelector:@selector(setSuppressMakeKeyFront:)]; michael@0: if (isMozWindow) michael@0: [[self window] setSuppressMakeKeyFront:YES]; michael@0: michael@0: nsIWidgetListener* listener = mGeckoChild->GetWidgetListener(); michael@0: if (listener) michael@0: listener->WindowActivated(); michael@0: michael@0: if (isMozWindow) michael@0: [[self window] setSuppressMakeKeyFront:NO]; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: - (void)viewsWindowDidResignKey michael@0: { michael@0: if (!mGeckoChild) michael@0: return; michael@0: michael@0: nsAutoRetainCocoaObject kungFuDeathGrip(self); michael@0: michael@0: nsIWidgetListener* listener = mGeckoChild->GetWidgetListener(); michael@0: if (listener) michael@0: listener->WindowDeactivated(); michael@0: } michael@0: michael@0: // If the call to removeFromSuperview isn't delayed from nsChildView:: michael@0: // TearDownView(), the NSView hierarchy might get changed during calls to michael@0: // [ChildView drawRect:], which leads to "beyond bounds" exceptions in michael@0: // NSCFArray. For more info see bmo bug 373122. Apple's docs claim that michael@0: // removeFromSuperviewWithoutNeedingDisplay "can be safely invoked during michael@0: // display" (whatever "display" means). But it's _not_ true that it can be michael@0: // safely invoked during calls to [NSView drawRect:]. We use michael@0: // removeFromSuperview here because there's no longer any danger of being michael@0: // "invoked during display", and because doing do clears up bmo bug 384343. michael@0: - (void)delayedTearDown michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: [self removeFromSuperview]; michael@0: [self release]; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: #pragma mark - michael@0: michael@0: // drag'n'drop stuff michael@0: #define kDragServiceContractID "@mozilla.org/widget/dragservice;1" michael@0: michael@0: - (NSDragOperation)dragOperationForSession:(nsIDragSession*)aDragSession michael@0: { michael@0: uint32_t dragAction; michael@0: aDragSession->GetDragAction(&dragAction); michael@0: if (nsIDragService::DRAGDROP_ACTION_LINK & dragAction) michael@0: return NSDragOperationLink; michael@0: if (nsIDragService::DRAGDROP_ACTION_COPY & dragAction) michael@0: return NSDragOperationCopy; michael@0: if (nsIDragService::DRAGDROP_ACTION_MOVE & dragAction) michael@0: return NSDragOperationGeneric; michael@0: return NSDragOperationNone; michael@0: } michael@0: michael@0: // This is a utility function used by NSView drag event methods michael@0: // to send events. It contains all of the logic needed for Gecko michael@0: // dragging to work. Returns the appropriate cocoa drag operation code. michael@0: - (NSDragOperation)doDragAction:(uint32_t)aMessage sender:(id)aSender michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; michael@0: michael@0: if (!mGeckoChild) michael@0: return NSDragOperationNone; michael@0: michael@0: PR_LOG(sCocoaLog, PR_LOG_ALWAYS, ("ChildView doDragAction: entered\n")); michael@0: michael@0: if (!mDragService) { michael@0: CallGetService(kDragServiceContractID, &mDragService); michael@0: NS_ASSERTION(mDragService, "Couldn't get a drag service - big problem!"); michael@0: if (!mDragService) michael@0: return NSDragOperationNone; michael@0: } michael@0: michael@0: if (aMessage == NS_DRAGDROP_ENTER) michael@0: mDragService->StartDragSession(); michael@0: michael@0: nsCOMPtr dragSession; michael@0: mDragService->GetCurrentSession(getter_AddRefs(dragSession)); michael@0: if (dragSession) { michael@0: if (aMessage == NS_DRAGDROP_OVER) { michael@0: // fire the drag event at the source. Just ignore whether it was michael@0: // cancelled or not as there isn't actually a means to stop the drag michael@0: mDragService->FireDragEventAtSource(NS_DRAGDROP_DRAG); michael@0: dragSession->SetCanDrop(false); michael@0: } michael@0: else if (aMessage == NS_DRAGDROP_DROP) { michael@0: // We make the assumption that the dragOver handlers have correctly set michael@0: // the |canDrop| property of the Drag Session. michael@0: bool canDrop = false; michael@0: if (!NS_SUCCEEDED(dragSession->GetCanDrop(&canDrop)) || !canDrop) { michael@0: [self doDragAction:NS_DRAGDROP_EXIT sender:aSender]; michael@0: michael@0: nsCOMPtr sourceNode; michael@0: dragSession->GetSourceNode(getter_AddRefs(sourceNode)); michael@0: if (!sourceNode) { michael@0: mDragService->EndDragSession(false); michael@0: } michael@0: return NSDragOperationNone; michael@0: } michael@0: } michael@0: michael@0: unsigned int modifierFlags = [[NSApp currentEvent] modifierFlags]; michael@0: uint32_t action = nsIDragService::DRAGDROP_ACTION_MOVE; michael@0: // force copy = option, alias = cmd-option, default is move michael@0: if (modifierFlags & NSAlternateKeyMask) { michael@0: if (modifierFlags & NSCommandKeyMask) michael@0: action = nsIDragService::DRAGDROP_ACTION_LINK; michael@0: else michael@0: action = nsIDragService::DRAGDROP_ACTION_COPY; michael@0: } michael@0: dragSession->SetDragAction(action); michael@0: } michael@0: michael@0: // set up gecko event michael@0: WidgetDragEvent geckoEvent(true, aMessage, mGeckoChild); michael@0: nsCocoaUtils::InitInputEvent(geckoEvent, [NSApp currentEvent]); michael@0: michael@0: // Use our own coordinates in the gecko event. michael@0: // Convert event from gecko global coords to gecko view coords. michael@0: NSPoint draggingLoc = [aSender draggingLocation]; michael@0: NSPoint localPoint = [self convertPoint:draggingLoc fromView:nil]; michael@0: michael@0: geckoEvent.refPoint = LayoutDeviceIntPoint::FromUntyped( michael@0: mGeckoChild->CocoaPointsToDevPixels(localPoint)); michael@0: michael@0: nsAutoRetainCocoaObject kungFuDeathGrip(self); michael@0: mGeckoChild->DispatchWindowEvent(geckoEvent); michael@0: if (!mGeckoChild) michael@0: return NSDragOperationNone; michael@0: michael@0: if (dragSession) { michael@0: switch (aMessage) { michael@0: case NS_DRAGDROP_ENTER: michael@0: case NS_DRAGDROP_OVER: michael@0: return [self dragOperationForSession:dragSession]; michael@0: case NS_DRAGDROP_EXIT: michael@0: case NS_DRAGDROP_DROP: { michael@0: nsCOMPtr sourceNode; michael@0: dragSession->GetSourceNode(getter_AddRefs(sourceNode)); michael@0: if (!sourceNode) { michael@0: // We're leaving a window while doing a drag that was michael@0: // initiated in a different app. End the drag session, michael@0: // since we're done with it for now (until the user michael@0: // drags back into mozilla). michael@0: mDragService->EndDragSession(false); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: return NSDragOperationGeneric; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NSDragOperationNone); michael@0: } michael@0: michael@0: - (NSDragOperation)draggingEntered:(id )sender michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; michael@0: michael@0: PR_LOG(sCocoaLog, PR_LOG_ALWAYS, ("ChildView draggingEntered: entered\n")); michael@0: michael@0: // there should never be a globalDragPboard when "draggingEntered:" is michael@0: // called, but just in case we'll take care of it here. michael@0: [globalDragPboard release]; michael@0: michael@0: // Set the global drag pasteboard that will be used for this drag session. michael@0: // This will be set back to nil when the drag session ends (mouse exits michael@0: // the view or a drop happens within the view). michael@0: globalDragPboard = [[sender draggingPasteboard] retain]; michael@0: michael@0: return [self doDragAction:NS_DRAGDROP_ENTER sender:sender]; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NSDragOperationNone); michael@0: } michael@0: michael@0: - (NSDragOperation)draggingUpdated:(id )sender michael@0: { michael@0: PR_LOG(sCocoaLog, PR_LOG_ALWAYS, ("ChildView draggingUpdated: entered\n")); michael@0: michael@0: return [self doDragAction:NS_DRAGDROP_OVER sender:sender]; michael@0: } michael@0: michael@0: - (void)draggingExited:(id )sender michael@0: { michael@0: PR_LOG(sCocoaLog, PR_LOG_ALWAYS, ("ChildView draggingExited: entered\n")); michael@0: michael@0: nsAutoRetainCocoaObject kungFuDeathGrip(self); michael@0: [self doDragAction:NS_DRAGDROP_EXIT sender:sender]; michael@0: NS_IF_RELEASE(mDragService); michael@0: } michael@0: michael@0: - (BOOL)performDragOperation:(id )sender michael@0: { michael@0: nsAutoRetainCocoaObject kungFuDeathGrip(self); michael@0: BOOL handled = [self doDragAction:NS_DRAGDROP_DROP sender:sender] != NSDragOperationNone; michael@0: NS_IF_RELEASE(mDragService); michael@0: return handled; michael@0: } michael@0: michael@0: // NSDraggingSource michael@0: - (void)draggedImage:(NSImage *)anImage movedTo:(NSPoint)aPoint michael@0: { michael@0: // Get the drag service if it isn't already cached. The drag service michael@0: // isn't cached when dragging over a different application. michael@0: nsCOMPtr dragService = mDragService; michael@0: if (!dragService) { michael@0: dragService = do_GetService(kDragServiceContractID); michael@0: } michael@0: michael@0: if (dragService) { michael@0: NSPoint pnt = [NSEvent mouseLocation]; michael@0: FlipCocoaScreenCoordinate(pnt); michael@0: dragService->DragMoved(NSToIntRound(pnt.x), NSToIntRound(pnt.y)); michael@0: } michael@0: } michael@0: michael@0: // NSDraggingSource michael@0: - (void)draggedImage:(NSImage *)anImage endedAt:(NSPoint)aPoint operation:(NSDragOperation)operation michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: gDraggedTransferables = nullptr; michael@0: michael@0: NSEvent *currentEvent = [NSApp currentEvent]; michael@0: gUserCancelledDrag = ([currentEvent type] == NSKeyDown && michael@0: [currentEvent keyCode] == kVK_Escape); michael@0: michael@0: if (!mDragService) { michael@0: CallGetService(kDragServiceContractID, &mDragService); michael@0: NS_ASSERTION(mDragService, "Couldn't get a drag service - big problem!"); michael@0: } michael@0: michael@0: if (mDragService) { michael@0: // set the dragend point from the current mouse location michael@0: nsDragService* dragService = static_cast(mDragService); michael@0: NSPoint pnt = [NSEvent mouseLocation]; michael@0: FlipCocoaScreenCoordinate(pnt); michael@0: dragService->SetDragEndPoint(nsIntPoint(NSToIntRound(pnt.x), NSToIntRound(pnt.y))); michael@0: michael@0: // XXX: dropEffect should be updated per |operation|. michael@0: // As things stand though, |operation| isn't well handled within "our" michael@0: // events, that is, when the drop happens within the window: it is set michael@0: // either to NSDragOperationGeneric or to NSDragOperationNone. michael@0: // For that reason, it's not yet possible to override dropEffect per the michael@0: // given OS value, and it's also unclear what's the correct dropEffect michael@0: // value for NSDragOperationGeneric that is passed by other applications. michael@0: // All that said, NSDragOperationNone is still reliable. michael@0: if (operation == NSDragOperationNone) { michael@0: nsCOMPtr dataTransfer; michael@0: dragService->GetDataTransfer(getter_AddRefs(dataTransfer)); michael@0: if (dataTransfer) michael@0: dataTransfer->SetDropEffectInt(nsIDragService::DRAGDROP_ACTION_NONE); michael@0: } michael@0: michael@0: mDragService->EndDragSession(true); michael@0: NS_RELEASE(mDragService); michael@0: } michael@0: michael@0: [globalDragPboard release]; michael@0: globalDragPboard = nil; michael@0: [gLastDragMouseDownEvent release]; michael@0: gLastDragMouseDownEvent = nil; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: // NSDraggingSource michael@0: // this is just implemented so we comply with the NSDraggingSource informal protocol michael@0: - (NSDragOperation)draggingSourceOperationMaskForLocal:(BOOL)isLocal michael@0: { michael@0: return UINT_MAX; michael@0: } michael@0: michael@0: // This method is a callback typically invoked in response to a drag ending on the desktop michael@0: // or a Findow folder window; the argument passed is a path to the drop location, to be used michael@0: // in constructing a complete pathname for the file(s) we want to create as a result of michael@0: // the drag. michael@0: - (NSArray *)namesOfPromisedFilesDroppedAtDestination:(NSURL*)dropDestination michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; michael@0: michael@0: nsresult rv; michael@0: michael@0: PR_LOG(sCocoaLog, PR_LOG_ALWAYS, ("ChildView namesOfPromisedFilesDroppedAtDestination: entering callback for promised files\n")); michael@0: michael@0: nsCOMPtr targFile; michael@0: NS_NewLocalFile(EmptyString(), true, getter_AddRefs(targFile)); michael@0: nsCOMPtr macLocalFile = do_QueryInterface(targFile); michael@0: if (!macLocalFile) { michael@0: NS_ERROR("No Mac local file"); michael@0: return nil; michael@0: } michael@0: michael@0: if (!NS_SUCCEEDED(macLocalFile->InitWithCFURL((CFURLRef)dropDestination))) { michael@0: NS_ERROR("failed InitWithCFURL"); michael@0: return nil; michael@0: } michael@0: michael@0: if (!gDraggedTransferables) michael@0: return nil; michael@0: michael@0: uint32_t transferableCount; michael@0: rv = gDraggedTransferables->Count(&transferableCount); michael@0: if (NS_FAILED(rv)) michael@0: return nil; michael@0: michael@0: for (uint32_t i = 0; i < transferableCount; i++) { michael@0: nsCOMPtr genericItem; michael@0: gDraggedTransferables->GetElementAt(i, getter_AddRefs(genericItem)); michael@0: nsCOMPtr item(do_QueryInterface(genericItem)); michael@0: if (!item) { michael@0: NS_ERROR("no transferable"); michael@0: return nil; michael@0: } michael@0: michael@0: item->SetTransferData(kFilePromiseDirectoryMime, macLocalFile, sizeof(nsIFile*)); michael@0: michael@0: // now request the kFilePromiseMime data, which will invoke the data provider michael@0: // If successful, the returned data is a reference to the resulting file. michael@0: nsCOMPtr fileDataPrimitive; michael@0: uint32_t dataSize = 0; michael@0: item->GetTransferData(kFilePromiseMime, getter_AddRefs(fileDataPrimitive), &dataSize); michael@0: } michael@0: michael@0: NSPasteboard* generalPboard = [NSPasteboard pasteboardWithName:NSDragPboard]; michael@0: NSData* data = [generalPboard dataForType:@"application/x-moz-file-promise-dest-filename"]; michael@0: NSString* name = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; michael@0: NSArray* rslt = [NSArray arrayWithObject:name]; michael@0: michael@0: [name release]; michael@0: michael@0: return rslt; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NIL; michael@0: } michael@0: michael@0: #pragma mark - michael@0: michael@0: // Support for the "Services" menu. We currently only support sending strings michael@0: // and HTML to system services. michael@0: michael@0: - (id)validRequestorForSendType:(NSString *)sendType michael@0: returnType:(NSString *)returnType michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; michael@0: michael@0: // sendType contains the type of data that the service would like this michael@0: // application to send to it. sendType is nil if the service is not michael@0: // requesting any data. michael@0: // michael@0: // returnType contains the type of data the the service would like to michael@0: // return to this application (e.g., to overwrite the selection). michael@0: // returnType is nil if the service will not return any data. michael@0: // michael@0: // The following condition thus triggers when the service expects a string michael@0: // or HTML from us or no data at all AND when the service will either not michael@0: // send back any data to us or will send a string or HTML back to us. michael@0: michael@0: #define IsSupportedType(typeStr) ([typeStr isEqual:NSStringPboardType] || [typeStr isEqual:NSHTMLPboardType]) michael@0: michael@0: id result = nil; michael@0: michael@0: if ((!sendType || IsSupportedType(sendType)) && michael@0: (!returnType || IsSupportedType(returnType))) { michael@0: if (mGeckoChild) { michael@0: // Assume that this object will be able to handle this request. michael@0: result = self; michael@0: michael@0: // Keep the ChildView alive during this operation. michael@0: nsAutoRetainCocoaObject kungFuDeathGrip(self); michael@0: michael@0: // Determine if there is a selection (if sending to the service). michael@0: if (sendType) { michael@0: WidgetQueryContentEvent event(true, NS_QUERY_CONTENT_STATE, michael@0: mGeckoChild); michael@0: // This might destroy our widget (and null out mGeckoChild). michael@0: mGeckoChild->DispatchWindowEvent(event); michael@0: if (!mGeckoChild || !event.mSucceeded || !event.mReply.mHasSelection) michael@0: result = nil; michael@0: } michael@0: michael@0: // Determine if we can paste (if receiving data from the service). michael@0: if (mGeckoChild && returnType) { michael@0: WidgetContentCommandEvent command(true, michael@0: NS_CONTENT_COMMAND_PASTE_TRANSFERABLE, michael@0: mGeckoChild, true); michael@0: // This might possibly destroy our widget (and null out mGeckoChild). michael@0: mGeckoChild->DispatchWindowEvent(command); michael@0: if (!mGeckoChild || !command.mSucceeded || !command.mIsEnabled) michael@0: result = nil; michael@0: } michael@0: } michael@0: } michael@0: michael@0: #undef IsSupportedType michael@0: michael@0: // Give the superclass a chance if this object will not handle this request. michael@0: if (!result) michael@0: result = [super validRequestorForSendType:sendType returnType:returnType]; michael@0: michael@0: return result; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NIL; michael@0: } michael@0: michael@0: - (BOOL)writeSelectionToPasteboard:(NSPasteboard *)pboard michael@0: types:(NSArray *)types michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; michael@0: michael@0: nsAutoRetainCocoaObject kungFuDeathGrip(self); michael@0: michael@0: // Make sure that the service will accept strings or HTML. michael@0: if ([types containsObject:NSStringPboardType] == NO && michael@0: [types containsObject:NSHTMLPboardType] == NO) michael@0: return NO; michael@0: michael@0: // Bail out if there is no Gecko object. michael@0: if (!mGeckoChild) michael@0: return NO; michael@0: michael@0: // Obtain the current selection. michael@0: WidgetQueryContentEvent event(true, michael@0: NS_QUERY_SELECTION_AS_TRANSFERABLE, michael@0: mGeckoChild); michael@0: mGeckoChild->DispatchWindowEvent(event); michael@0: if (!event.mSucceeded || !event.mReply.mTransferable) michael@0: return NO; michael@0: michael@0: // Transform the transferable to an NSDictionary. michael@0: NSDictionary* pasteboardOutputDict = nsClipboard::PasteboardDictFromTransferable(event.mReply.mTransferable); michael@0: if (!pasteboardOutputDict) michael@0: return NO; michael@0: michael@0: // Declare the pasteboard types. michael@0: unsigned int typeCount = [pasteboardOutputDict count]; michael@0: NSMutableArray * types = [NSMutableArray arrayWithCapacity:typeCount]; michael@0: [types addObjectsFromArray:[pasteboardOutputDict allKeys]]; michael@0: [pboard declareTypes:types owner:nil]; michael@0: michael@0: // Write the data to the pasteboard. michael@0: for (unsigned int i = 0; i < typeCount; i++) { michael@0: NSString* currentKey = [types objectAtIndex:i]; michael@0: id currentValue = [pasteboardOutputDict valueForKey:currentKey]; michael@0: michael@0: if (currentKey == NSStringPboardType || michael@0: currentKey == kCorePboardType_url || michael@0: currentKey == kCorePboardType_urld || michael@0: currentKey == kCorePboardType_urln) { michael@0: [pboard setString:currentValue forType:currentKey]; michael@0: } else if (currentKey == NSHTMLPboardType) { michael@0: [pboard setString:(nsClipboard::WrapHtmlForSystemPasteboard(currentValue)) forType:currentKey]; michael@0: } else if (currentKey == NSTIFFPboardType) { michael@0: [pboard setData:currentValue forType:currentKey]; michael@0: } else if (currentKey == NSFilesPromisePboardType) { michael@0: [pboard setPropertyList:currentValue forType:currentKey]; michael@0: } michael@0: } michael@0: michael@0: return YES; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO); michael@0: } michael@0: michael@0: // Called if the service wants us to replace the current selection. michael@0: - (BOOL)readSelectionFromPasteboard:(NSPasteboard *)pboard michael@0: { michael@0: nsresult rv; michael@0: nsCOMPtr trans = do_CreateInstance("@mozilla.org/widget/transferable;1", &rv); michael@0: if (NS_FAILED(rv)) michael@0: return NO; michael@0: trans->Init(nullptr); michael@0: michael@0: trans->AddDataFlavor(kUnicodeMime); michael@0: trans->AddDataFlavor(kHTMLMime); michael@0: michael@0: rv = nsClipboard::TransferableFromPasteboard(trans, pboard); michael@0: if (NS_FAILED(rv)) michael@0: return NO; michael@0: michael@0: NS_ENSURE_TRUE(mGeckoChild, false); michael@0: michael@0: WidgetContentCommandEvent command(true, michael@0: NS_CONTENT_COMMAND_PASTE_TRANSFERABLE, michael@0: mGeckoChild); michael@0: command.mTransferable = trans; michael@0: mGeckoChild->DispatchWindowEvent(command); michael@0: michael@0: return command.mSucceeded && command.mIsEnabled; michael@0: } michael@0: michael@0: #pragma mark - michael@0: michael@0: #ifdef ACCESSIBILITY michael@0: michael@0: /* Every ChildView has a corresponding mozDocAccessible object that is doing all michael@0: the heavy lifting. The topmost ChildView corresponds to a mozRootAccessible michael@0: object. michael@0: michael@0: All ChildView needs to do is to route all accessibility calls (from the NSAccessibility APIs) michael@0: down to its object, pretending that they are the same. michael@0: */ michael@0: - (id)accessible michael@0: { michael@0: if (!mGeckoChild) michael@0: return nil; michael@0: michael@0: id nativeAccessible = nil; michael@0: michael@0: nsAutoRetainCocoaObject kungFuDeathGrip(self); michael@0: nsCOMPtr kungFuDeathGrip2(mGeckoChild); michael@0: nsRefPtr accessible = mGeckoChild->GetDocumentAccessible(); michael@0: if (!accessible) michael@0: return nil; michael@0: michael@0: accessible->GetNativeInterface((void**)&nativeAccessible); michael@0: michael@0: #ifdef DEBUG_hakan michael@0: NSAssert(![nativeAccessible isExpired], @"native acc is expired!!!"); michael@0: #endif michael@0: michael@0: return nativeAccessible; michael@0: } michael@0: michael@0: /* Implementation of formal mozAccessible formal protocol (enabling mozViews michael@0: to talk to mozAccessible objects in the accessibility module). */ michael@0: michael@0: - (BOOL)hasRepresentedView michael@0: { michael@0: return YES; michael@0: } michael@0: michael@0: - (id)representedView michael@0: { michael@0: return self; michael@0: } michael@0: michael@0: - (BOOL)isRoot michael@0: { michael@0: return [[self accessible] isRoot]; michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: - (void)printHierarchy michael@0: { michael@0: [[self accessible] printHierarchy]; michael@0: } michael@0: #endif michael@0: michael@0: #pragma mark - michael@0: michael@0: // general michael@0: michael@0: - (BOOL)accessibilityIsIgnored michael@0: { michael@0: if (!mozilla::a11y::ShouldA11yBeEnabled()) michael@0: return [super accessibilityIsIgnored]; michael@0: michael@0: return [[self accessible] accessibilityIsIgnored]; michael@0: } michael@0: michael@0: - (id)accessibilityHitTest:(NSPoint)point michael@0: { michael@0: if (!mozilla::a11y::ShouldA11yBeEnabled()) michael@0: return [super accessibilityHitTest:point]; michael@0: michael@0: return [[self accessible] accessibilityHitTest:point]; michael@0: } michael@0: michael@0: - (id)accessibilityFocusedUIElement michael@0: { michael@0: if (!mozilla::a11y::ShouldA11yBeEnabled()) michael@0: return [super accessibilityFocusedUIElement]; michael@0: michael@0: return [[self accessible] accessibilityFocusedUIElement]; michael@0: } michael@0: michael@0: // actions michael@0: michael@0: - (NSArray*)accessibilityActionNames michael@0: { michael@0: if (!mozilla::a11y::ShouldA11yBeEnabled()) michael@0: return [super accessibilityActionNames]; michael@0: michael@0: return [[self accessible] accessibilityActionNames]; michael@0: } michael@0: michael@0: - (NSString*)accessibilityActionDescription:(NSString*)action michael@0: { michael@0: if (!mozilla::a11y::ShouldA11yBeEnabled()) michael@0: return [super accessibilityActionDescription:action]; michael@0: michael@0: return [[self accessible] accessibilityActionDescription:action]; michael@0: } michael@0: michael@0: - (void)accessibilityPerformAction:(NSString*)action michael@0: { michael@0: if (!mozilla::a11y::ShouldA11yBeEnabled()) michael@0: return [super accessibilityPerformAction:action]; michael@0: michael@0: return [[self accessible] accessibilityPerformAction:action]; michael@0: } michael@0: michael@0: // attributes michael@0: michael@0: - (NSArray*)accessibilityAttributeNames michael@0: { michael@0: if (!mozilla::a11y::ShouldA11yBeEnabled()) michael@0: return [super accessibilityAttributeNames]; michael@0: michael@0: return [[self accessible] accessibilityAttributeNames]; michael@0: } michael@0: michael@0: - (BOOL)accessibilityIsAttributeSettable:(NSString*)attribute michael@0: { michael@0: if (!mozilla::a11y::ShouldA11yBeEnabled()) michael@0: return [super accessibilityIsAttributeSettable:attribute]; michael@0: michael@0: return [[self accessible] accessibilityIsAttributeSettable:attribute]; michael@0: } michael@0: michael@0: - (id)accessibilityAttributeValue:(NSString*)attribute michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; michael@0: michael@0: if (!mozilla::a11y::ShouldA11yBeEnabled()) michael@0: return [super accessibilityAttributeValue:attribute]; michael@0: michael@0: id accessible = [self accessible]; michael@0: michael@0: // if we're the root (topmost) accessible, we need to return our native AXParent as we michael@0: // traverse outside to the hierarchy of whoever embeds us. thus, fall back on NSView's michael@0: // default implementation for this attribute. michael@0: if ([attribute isEqualToString:NSAccessibilityParentAttribute] && [accessible isRoot]) { michael@0: id parentAccessible = [super accessibilityAttributeValue:attribute]; michael@0: return parentAccessible; michael@0: } michael@0: michael@0: return [accessible accessibilityAttributeValue:attribute]; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NIL; michael@0: } michael@0: michael@0: #endif /* ACCESSIBILITY */ michael@0: michael@0: @end michael@0: michael@0: #pragma mark - michael@0: michael@0: void michael@0: ChildViewMouseTracker::OnDestroyView(ChildView* aView) michael@0: { michael@0: if (sLastMouseEventView == aView) { michael@0: sLastMouseEventView = nil; michael@0: [sLastMouseMoveEvent release]; michael@0: sLastMouseMoveEvent = nil; michael@0: } michael@0: } michael@0: michael@0: void michael@0: ChildViewMouseTracker::OnDestroyWindow(NSWindow* aWindow) michael@0: { michael@0: if (sWindowUnderMouse == aWindow) { michael@0: sWindowUnderMouse = nil; michael@0: } michael@0: } michael@0: michael@0: void michael@0: ChildViewMouseTracker::MouseEnteredWindow(NSEvent* aEvent) michael@0: { michael@0: sWindowUnderMouse = [aEvent window]; michael@0: ReEvaluateMouseEnterState(aEvent); michael@0: } michael@0: michael@0: void michael@0: ChildViewMouseTracker::MouseExitedWindow(NSEvent* aEvent) michael@0: { michael@0: if (sWindowUnderMouse == [aEvent window]) { michael@0: sWindowUnderMouse = nil; michael@0: ReEvaluateMouseEnterState(aEvent); michael@0: } michael@0: } michael@0: michael@0: void michael@0: ChildViewMouseTracker::ReEvaluateMouseEnterState(NSEvent* aEvent, ChildView* aOldView) michael@0: { michael@0: ChildView* oldView = aOldView ? aOldView : sLastMouseEventView; michael@0: sLastMouseEventView = ViewForEvent(aEvent); michael@0: if (sLastMouseEventView != oldView) { michael@0: // Send enter and / or exit events. michael@0: WidgetMouseEvent::exitType type = michael@0: [sLastMouseEventView window] == [oldView window] ? michael@0: WidgetMouseEvent::eChild : WidgetMouseEvent::eTopLevel; michael@0: [oldView sendMouseEnterOrExitEvent:aEvent enter:NO type:type]; michael@0: // After the cursor exits the window set it to a visible regular arrow cursor. michael@0: if (type == WidgetMouseEvent::eTopLevel) { michael@0: [[nsCursorManager sharedInstance] setCursor:eCursor_standard]; michael@0: } michael@0: [sLastMouseEventView sendMouseEnterOrExitEvent:aEvent enter:YES type:type]; michael@0: } michael@0: } michael@0: michael@0: void michael@0: ChildViewMouseTracker::ResendLastMouseMoveEvent() michael@0: { michael@0: if (sLastMouseMoveEvent) { michael@0: MouseMoved(sLastMouseMoveEvent); michael@0: } michael@0: } michael@0: michael@0: void michael@0: ChildViewMouseTracker::MouseMoved(NSEvent* aEvent) michael@0: { michael@0: MouseEnteredWindow(aEvent); michael@0: [sLastMouseEventView handleMouseMoved:aEvent]; michael@0: if (sLastMouseMoveEvent != aEvent) { michael@0: [sLastMouseMoveEvent release]; michael@0: sLastMouseMoveEvent = [aEvent retain]; michael@0: } michael@0: } michael@0: michael@0: void michael@0: ChildViewMouseTracker::MouseScrolled(NSEvent* aEvent) michael@0: { michael@0: if (!nsCocoaUtils::IsMomentumScrollEvent(aEvent)) { michael@0: // Store the position so we can pin future momentum scroll events. michael@0: sLastScrollEventScreenLocation = nsCocoaUtils::ScreenLocationForEvent(aEvent); michael@0: } michael@0: } michael@0: michael@0: ChildView* michael@0: ChildViewMouseTracker::ViewForEvent(NSEvent* aEvent) michael@0: { michael@0: NSWindow* window = sWindowUnderMouse; michael@0: if (!window) michael@0: return nil; michael@0: michael@0: NSPoint windowEventLocation = nsCocoaUtils::EventLocationForWindow(aEvent, window); michael@0: NSView* view = [[[window contentView] superview] hitTest:windowEventLocation]; michael@0: michael@0: if (![view isKindOfClass:[ChildView class]]) michael@0: return nil; michael@0: michael@0: ChildView* childView = (ChildView*)view; michael@0: // If childView is being destroyed return nil. michael@0: if (![childView widget]) michael@0: return nil; michael@0: return WindowAcceptsEvent(window, aEvent, childView) ? childView : nil; michael@0: } michael@0: michael@0: void michael@0: ChildViewMouseTracker::AttachPluginEvent(WidgetMouseEventBase& aMouseEvent, michael@0: ChildView* aView, michael@0: NSEvent* aNativeMouseEvent, michael@0: int aPluginEventType, michael@0: void* aPluginEventHolder) michael@0: { michael@0: if (![aView isPluginView] || michael@0: [aView pluginEventModel] != NPEventModelCocoa) { michael@0: return; michael@0: } michael@0: michael@0: NPCocoaEvent* cocoaEvent = (NPCocoaEvent*)aPluginEventHolder; michael@0: nsCocoaUtils::InitNPCocoaEvent(cocoaEvent); michael@0: NSPoint point = [aView convertPoint:[aNativeMouseEvent locationInWindow] michael@0: fromView:nil]; michael@0: NPCocoaEventType type = (NPCocoaEventType)aPluginEventType; michael@0: // XXX We should consider dropping this code, which causes data.mouse.pluginX michael@0: // and data.mouse.pluginY to be set to '5' for mouse-entered and mouse-exited michael@0: // events. But note that it's been in the tree since the Cocoa NPAPI event michael@0: // model was first implemented four years ago, without any known complaints. michael@0: // michael@0: // Similar code first appeared (without explanation) in a very early version michael@0: // ("fix 0.3") of the patch for bug 435041 ("Implement Cocoa NPAPI event michael@0: // model for Mac OS X"). But there's no trace of it in the WebKit code that michael@0: // was used as a model for much of that patch. michael@0: #if (0) michael@0: if (type == NPCocoaEventMouseEntered || michael@0: type == NPCocoaEventMouseExited) { michael@0: point.x = point.y = 5; michael@0: } michael@0: #endif michael@0: NSUInteger clickCount = 0; michael@0: if (type != NPCocoaEventMouseEntered && michael@0: type != NPCocoaEventMouseExited && michael@0: type != NPCocoaEventScrollWheel) { michael@0: clickCount = [aNativeMouseEvent clickCount]; michael@0: } michael@0: cocoaEvent->type = type; michael@0: cocoaEvent->data.mouse.modifierFlags = [aNativeMouseEvent modifierFlags]; michael@0: cocoaEvent->data.mouse.pluginX = point.x; michael@0: cocoaEvent->data.mouse.pluginY = point.y; michael@0: cocoaEvent->data.mouse.buttonNumber = [aNativeMouseEvent buttonNumber]; michael@0: cocoaEvent->data.mouse.clickCount = clickCount; michael@0: cocoaEvent->data.mouse.deltaX = [aNativeMouseEvent deltaX]; michael@0: cocoaEvent->data.mouse.deltaY = [aNativeMouseEvent deltaY]; michael@0: cocoaEvent->data.mouse.deltaZ = [aNativeMouseEvent deltaZ]; michael@0: aMouseEvent.pluginEvent = cocoaEvent; michael@0: } michael@0: michael@0: BOOL michael@0: ChildViewMouseTracker::WindowAcceptsEvent(NSWindow* aWindow, NSEvent* aEvent, michael@0: ChildView* aView, BOOL aIsClickThrough) michael@0: { michael@0: // Right mouse down events may get through to all windows, even to a top level michael@0: // window with an open sheet. michael@0: if (!aWindow || [aEvent type] == NSRightMouseDown) michael@0: return YES; michael@0: michael@0: id delegate = [aWindow delegate]; michael@0: if (!delegate || ![delegate isKindOfClass:[WindowDelegate class]]) michael@0: return YES; michael@0: michael@0: nsIWidget *windowWidget = [(WindowDelegate *)delegate geckoWidget]; michael@0: if (!windowWidget) michael@0: return YES; michael@0: michael@0: NSWindow* topLevelWindow = nil; michael@0: michael@0: switch (windowWidget->WindowType()) { michael@0: case eWindowType_popup: michael@0: // If this is a context menu, it won't have a parent. So we'll always michael@0: // accept mouse move events on context menus even when none of our windows michael@0: // is active, which is the right thing to do. michael@0: // For panels, the parent window is the XUL window that owns the panel. michael@0: return WindowAcceptsEvent([aWindow parentWindow], aEvent, aView, aIsClickThrough); michael@0: michael@0: case eWindowType_toplevel: michael@0: case eWindowType_dialog: michael@0: if ([aWindow attachedSheet]) michael@0: return NO; michael@0: michael@0: topLevelWindow = aWindow; michael@0: break; michael@0: case eWindowType_sheet: { michael@0: nsIWidget* parentWidget = windowWidget->GetSheetWindowParent(); michael@0: if (!parentWidget) michael@0: return YES; michael@0: michael@0: topLevelWindow = (NSWindow*)parentWidget->GetNativeData(NS_NATIVE_WINDOW); michael@0: break; michael@0: } michael@0: michael@0: default: michael@0: return YES; michael@0: } michael@0: michael@0: if (!topLevelWindow || michael@0: ([topLevelWindow isMainWindow] && !aIsClickThrough) || michael@0: [aEvent type] == NSOtherMouseDown || michael@0: (([aEvent modifierFlags] & NSCommandKeyMask) != 0 && michael@0: [aEvent type] != NSMouseMoved)) michael@0: return YES; michael@0: michael@0: // If we're here then we're dealing with a left click or mouse move on an michael@0: // inactive window or something similar. Ask Gecko what to do. michael@0: return [aView inactiveWindowAcceptsMouseEvent:aEvent]; michael@0: } michael@0: michael@0: #pragma mark - michael@0: michael@0: @interface NSView (MethodSwizzling) michael@0: - (BOOL)nsChildView_NSView_mouseDownCanMoveWindow; michael@0: @end michael@0: michael@0: @implementation NSView (MethodSwizzling) michael@0: michael@0: // All top-level browser windows belong to the ToolbarWindow class and have michael@0: // NSTexturedBackgroundWindowMask turned on in their "style" (see particularly michael@0: // [ToolbarWindow initWithContentRect:...] in nsCocoaWindow.mm). This style michael@0: // normally means the window "may be moved by clicking and dragging anywhere michael@0: // in the window background", but we've suppressed this by giving the michael@0: // ChildView class a mouseDownCanMoveWindow method that always returns NO. michael@0: // Normally a ToolbarWindow's contentView (not a ChildView) returns YES when michael@0: // NSTexturedBackgroundWindowMask is turned on. But normally this makes no michael@0: // difference. However, under some (probably very unusual) circumstances michael@0: // (and only on Leopard) it *does* make a difference -- for example it michael@0: // triggers bmo bugs 431902 and 476393. So here we make sure that a michael@0: // ToolbarWindow's contentView always returns NO from the michael@0: // mouseDownCanMoveWindow method. michael@0: - (BOOL)nsChildView_NSView_mouseDownCanMoveWindow michael@0: { michael@0: NSWindow *ourWindow = [self window]; michael@0: NSView *contentView = [ourWindow contentView]; michael@0: if ([ourWindow isKindOfClass:[ToolbarWindow class]] && (self == contentView)) michael@0: return [ourWindow isMovableByWindowBackground]; michael@0: return [self nsChildView_NSView_mouseDownCanMoveWindow]; michael@0: } michael@0: michael@0: @end michael@0: michael@0: #ifdef __LP64__ michael@0: // When using blocks, at least on OS X 10.7, the OS sometimes calls michael@0: // +[NSEvent removeMonitor:] more than once on a single event monitor, which michael@0: // causes crashes. See bug 678607. We hook these methods to work around michael@0: // the problem. michael@0: @interface NSEvent (MethodSwizzling) michael@0: + (id)nsChildView_NSEvent_addLocalMonitorForEventsMatchingMask:(unsigned long long)mask handler:(id)block; michael@0: + (void)nsChildView_NSEvent_removeMonitor:(id)eventMonitor; michael@0: @end michael@0: michael@0: // This is a local copy of the AppKit frameworks sEventObservers hashtable. michael@0: // It only stores "local monitors". We use it to ensure that +[NSEvent michael@0: // removeMonitor:] is never called more than once on the same local monitor. michael@0: static NSHashTable *sLocalEventObservers = nil; michael@0: michael@0: @implementation NSEvent (MethodSwizzling) michael@0: michael@0: + (id)nsChildView_NSEvent_addLocalMonitorForEventsMatchingMask:(unsigned long long)mask handler:(id)block michael@0: { michael@0: if (!sLocalEventObservers) { michael@0: sLocalEventObservers = [[NSHashTable hashTableWithOptions: michael@0: NSHashTableStrongMemory | NSHashTableObjectPointerPersonality] retain]; michael@0: } michael@0: id retval = michael@0: [self nsChildView_NSEvent_addLocalMonitorForEventsMatchingMask:mask handler:block]; michael@0: if (sLocalEventObservers && retval && ![sLocalEventObservers containsObject:retval]) { michael@0: [sLocalEventObservers addObject:retval]; michael@0: } michael@0: return retval; michael@0: } michael@0: michael@0: + (void)nsChildView_NSEvent_removeMonitor:(id)eventMonitor michael@0: { michael@0: if (sLocalEventObservers && [eventMonitor isKindOfClass: ::NSClassFromString(@"_NSLocalEventObserver")]) { michael@0: if (![sLocalEventObservers containsObject:eventMonitor]) { michael@0: return; michael@0: } michael@0: [sLocalEventObservers removeObject:eventMonitor]; michael@0: } michael@0: [self nsChildView_NSEvent_removeMonitor:eventMonitor]; michael@0: } michael@0: michael@0: @end michael@0: #endif // #ifdef __LP64__