diff options
Diffstat (limited to 'widget/uikit/nsWindow.mm')
-rw-r--r-- | widget/uikit/nsWindow.mm | 420 |
1 files changed, 359 insertions, 61 deletions
diff --git a/widget/uikit/nsWindow.mm b/widget/uikit/nsWindow.mm index 0c1a38c27c..51b317ee61 100644 --- a/widget/uikit/nsWindow.mm +++ b/widget/uikit/nsWindow.mm @@ -4,6 +4,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #import <UIKit/UIEvent.h> +#import <UIKit/UIKit.h> #import <UIKit/UIGraphics.h> #import <UIKit/UIInterface.h> #import <UIKit/UIScreen.h> @@ -17,29 +18,41 @@ #include <algorithm> #include "nsWindow.h" -#include "nsScreenManager.h" #include "nsAppShell.h" +#ifdef ACCESSIBILITY +# include "nsAccessibilityService.h" +# include "mozilla/a11y/LocalAccessible.h" +#endif #include "nsWidgetsCID.h" #include "nsGfxCIID.h" +#include "gfxPlatform.h" #include "gfxQuartzSurface.h" #include "gfxUtils.h" #include "gfxImageSurface.h" #include "gfxContext.h" +#include "nsObjCExceptions.h" #include "nsRegion.h" -#include "Layers.h" #include "nsTArray.h" +#include "TextInputHandler.h" +#include "UIKitUtils.h" #include "mozilla/BasicEvents.h" #include "mozilla/ProfilerLabels.h" #include "mozilla/TouchEvents.h" #include "mozilla/Unused.h" #include "mozilla/dom/MouseEventBinding.h" +#include "mozilla/gfx/Logging.h" +#ifdef ACCESSIBILITY +# include "mozilla/a11y/MUIRootAccessibleProtocol.h" +#endif using namespace mozilla; -using namespace mozilla::dom; +using namespace mozilla::gfx; using namespace mozilla::layers; +using mozilla::dom::Touch; +using mozilla::widget::UIKitUtils; #define ALOG(args...) \ fprintf(stderr, args); \ @@ -62,18 +75,22 @@ static CGRect DevPixelsToUIKitPoints(const LayoutDeviceIntRect& aRect, // Used to retain a Cocoa object for the remainder of a method's execution. class nsAutoRetainUIKitObject { public: - nsAutoRetainUIKitObject(id anObject) { mObject = [anObject retain]; } + explicit nsAutoRetainUIKitObject(id anObject) { mObject = [anObject retain]; } ~nsAutoRetainUIKitObject() { [mObject release]; } private: id mObject; // [STRONG] }; -@interface ChildView : UIView { +#ifdef ACCESSIBILITY +@interface ChildView : UIView <UIKeyInput, MUIRootAccessibleProtocol> { +#else +@interface ChildView : UIView <UIKeyInput> { +#endif @public nsWindow* mGeckoChild; // weak ref BOOL mWaitingForPaint; - CFMutableDictionaryRef mTouches; + NSMapTable<UITouch*, NSNumber*>* mTouches; int mNextTouchID; } // sets up our view, attaching it to its owning gecko view @@ -93,10 +110,32 @@ class nsAutoRetainUIKitObject { touches:(NSSet*)aTouches widget:(nsWindow*)aWindow; // Event handling (UIResponder) -- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event; -- (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event; -- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event; -- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event; +- (void)touchesBegan:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event; +- (void)touchesCancelled:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event; +- (void)touchesEnded:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event; +- (void)touchesMoved:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event; + +- (void)activateWindow:(NSNotification*)notification; +- (void)deactivateWindow:(NSNotification*)notification; + +#ifdef ACCESSIBILITY +// MUIRootAccessible +- (BOOL)hasRepresentedView; +- (id)representedView; + +// MUIAccessible +- (BOOL)isAccessibilityElement; +- (NSString*)accessibilityLabel; +- (CGRect)accessibilityFrame; +- (NSString*)accessibilityValue; +- (uint64_t)accessibilityTraits; +- (NSInteger)accessibilityElementCount; +- (nullable id)accessibilityElementAtIndex:(NSInteger)index; +- (NSInteger)indexOfAccessibilityElement:(id)element; +- (NSArray* _Nullable)accessibilityElements; +- (UIAccessibilityContainerType)accessibilityContainerType; +#endif + @end @implementation ChildView @@ -120,15 +159,29 @@ class nsAutoRetainUIKitObject { tapRecognizer.numberOfTapsRequired = 1; [self addGestureRecognizer:tapRecognizer]; - mTouches = - CFDictionaryCreateMutable(kCFAllocatorDefault, 0, nullptr, nullptr); + mTouches = [[NSMapTable alloc] init]; mNextTouchID = 0; + + // This is managed with weak references by the notification center so that we + // do not need to call removeObserver. + // https://developer.apple.com/documentation/foundation/nsnotificationcenter/1415360-addobserver#discussion + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(activateWindow:) + name:UIWindowDidBecomeKeyNotification + object:nil]; + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(deactivateWindow:) + name:UIWindowDidResignKeyNotification + object:nil]; + return self; } - (void)widgetDestroyed { mGeckoChild = nullptr; - CFRelease(mTouches); + [mTouches release]; } - (void)delayedTearDown { @@ -136,6 +189,30 @@ class nsAutoRetainUIKitObject { [self release]; } +- (void)activateWindow:(NSNotification*)notification { + ALOG("[[ChildView[%p] activateWindow]", (void*)self); + + if (!mGeckoChild) { + return; + } + + if (nsIWidgetListener* listener = mGeckoChild->GetWidgetListener()) { + listener->WindowActivated(); + } +} + +- (void)deactivateWindow:(NSNotification*)notification { + ALOG("[[ChildView[%p] deactivateWindow]", (void*)self); + + if (!mGeckoChild) { + return; + } + + if (nsIWidgetListener* listener = mGeckoChild->GetWidgetListener()) { + listener->WindowDeactivated(); + } +} + - (void)sendMouseEvent:(EventMessage)aType point:(LayoutDeviceIntPoint)aPoint widget:(nsWindow*)aWindow { @@ -144,9 +221,8 @@ class nsAutoRetainUIKitObject { event.mRefPoint = aPoint; event.mClickCount = 1; - event.button = MouseButton::ePrimary; - event.mTime = PR_IntervalNow(); - event.inputSource = MouseEvent_Binding::MOZ_SOURCE_UNKNOWN; + event.mButton = MouseButton::ePrimary; + event.mInputSource = mozilla::dom::MouseEvent_Binding::MOZ_SOURCE_UNKNOWN; nsEventStatus status; aWindow->DispatchEvent(&event, status); @@ -169,20 +245,20 @@ class nsAutoRetainUIKitObject { WidgetTouchEvent event(true, aType, aWindow); // XXX: I think nativeEvent.timestamp * 1000 is probably usable here but // I don't care that much right now. - event.mTime = PR_IntervalNow(); event.mTouches.SetCapacity(aTouches.count); for (UITouch* touch in aTouches) { LayoutDeviceIntPoint loc = UIKitPointsToDevPixels( [touch locationInView:self], [self contentScaleFactor]); - LayoutDeviceIntPoint radius = - UIKitPointsToDevPixels([touch majorRadius], [touch majorRadius]); - void* value; - if (!CFDictionaryGetValueIfPresent(mTouches, touch, (const void**)&value)) { + LayoutDeviceIntPoint radius = UIKitPointsToDevPixels( + CGPointMake([touch majorRadius], [touch majorRadius]), + [self contentScaleFactor]); + NSNumber* value = [mTouches objectForKey:touch]; + if (value == nil) { // This shouldn't happen. NS_ASSERTION(false, "Got a touch that we didn't know about"); continue; } - int id = reinterpret_cast<int>(value); + int id = [value intValue]; RefPtr<Touch> t = new Touch(id, loc, radius, 0.0f, 1.0f); event.mRefPoint = loc; event.mTouches.AppendElement(t); @@ -190,12 +266,12 @@ class nsAutoRetainUIKitObject { aWindow->DispatchInputEvent(&event); } -- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event { +- (void)touchesBegan:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event { ALOG("[ChildView[%p] touchesBegan", self); if (!mGeckoChild) return; for (UITouch* touch : touches) { - CFDictionaryAddValue(mTouches, touch, (void*)mNextTouchID); + [mTouches setObject:[NSNumber numberWithInt:mNextTouchID] forKey:touch]; mNextTouchID++; } [self sendTouchEvent:eTouchStart @@ -203,31 +279,31 @@ class nsAutoRetainUIKitObject { widget:mGeckoChild]; } -- (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event { +- (void)touchesCancelled:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event { ALOG("[ChildView[%p] touchesCancelled", self); [self sendTouchEvent:eTouchCancel touches:touches widget:mGeckoChild]; for (UITouch* touch : touches) { - CFDictionaryRemoveValue(mTouches, touch); + [mTouches removeObjectForKey:touch]; } - if (CFDictionaryGetCount(mTouches) == 0) { + if (mTouches.count == 0) { mNextTouchID = 0; } } -- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event { +- (void)touchesEnded:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event { ALOG("[ChildView[%p] touchesEnded", self); if (!mGeckoChild) return; [self sendTouchEvent:eTouchEnd touches:touches widget:mGeckoChild]; for (UITouch* touch : touches) { - CFDictionaryRemoveValue(mTouches, touch); + [mTouches removeObjectForKey:touch]; } - if (CFDictionaryGetCount(mTouches) == 0) { + if (mTouches.count == 0) { mNextTouchID = 0; } } -- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event { +- (void)touchesMoved:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event { ALOG("[ChildView[%p] touchesMoved", self); if (!mGeckoChild) return; @@ -236,6 +312,17 @@ class nsAutoRetainUIKitObject { widget:mGeckoChild]; } +- (BOOL)canBecomeFirstResponder { + if (!mGeckoChild) { + return NO; + } + + if (mGeckoChild->IsVirtualKeyboardDisabled()) { + return NO; + } + return YES; +} + - (void)setNeedsDisplayInRect:(CGRect)aRect { if ([self isUsingMainThreadOpenGL]) { // Draw without calling drawRect. This prevent us from @@ -326,7 +413,8 @@ class nsAutoRetainUIKitObject { CGContextScaleCTM(aContext, 1.0 / scale, 1.0 / scale); CGSize viewSize = [self bounds].size; - gfx::IntSize backingSize(viewSize.width * scale, viewSize.height * scale); + gfx::IntSize backingSize(NSToIntRound(viewSize.width * scale), + NSToIntRound(viewSize.height * scale)); CGContextSaveGState(aContext); @@ -339,13 +427,12 @@ class nsAutoRetainUIKitObject { // Create Cairo objects. RefPtr<gfxQuartzSurface> targetSurface; - UniquePtrPtr<gfxContext> targetContext; + UniquePtr<gfxContext> targetContext; if (gfxPlatform::GetPlatform()->SupportsAzureContentForType( gfx::BackendType::CAIRO)) { // This is dead code unless you mess with prefs, but keep it around for // debugging. targetSurface = new gfxQuartzSurface(aContext, backingSize); - targetSurface->SetAllowUseAsSource(false); RefPtr<gfx::DrawTarget> dt = gfxPlatform::CreateDrawTargetForSurface(targetSurface, backingSize); if (!dt || !dt->IsValid()) { @@ -399,6 +486,183 @@ class nsAutoRetainUIKitObject { CGContextStrokeRect(aContext, aRect); #endif } + +// UIKeyInput + +- (void)insertText:(NSString*)text { + if (!mGeckoChild || mGeckoChild->Destroyed()) { + return; + } + widget::TextInputHandler* textInputHandler = + mGeckoChild->GetTextInputHandler(); + if (!textInputHandler) { + return; + } + textInputHandler->InsertText(text); +} + +- (void)deleteBackward { + if (!mGeckoChild || mGeckoChild->Destroyed()) { + return; + } + widget::TextInputHandler* textInputHandler = + mGeckoChild->GetTextInputHandler(); + if (!textInputHandler) { + return; + } + textInputHandler->HandleCommand(Command::DeleteCharBackward); +} + +- (BOOL)hasText { + if (!mGeckoChild || mGeckoChild->Destroyed()) { + return NO; + } + widget::InputContext context = mGeckoChild->GetInputContext(); + if (context.mIMEState.mEnabled == mozilla::widget::IMEEnabled::Disabled) { + return NO; + } + return YES; +} + +// UITextInputTraits + +- (UIKeyboardType)keyboardType { + if (!mGeckoChild || mGeckoChild->Destroyed()) { + return UIKeyboardTypeDefault; + } + return UIKitUtils::GetUIKeyboardType(mGeckoChild->GetInputContext()); +} + +- (UIReturnKeyType)returnKeyType { + if (!mGeckoChild || mGeckoChild->Destroyed()) { + return UIReturnKeyDefault; + } + return UIKitUtils::GetUIReturnKeyType(mGeckoChild->GetInputContext()); +} + +- (UITextAutocapitalizationType)autocapitalizationType { + if (!mGeckoChild || mGeckoChild->Destroyed()) { + return UITextAutocapitalizationTypeNone; + } + return UIKitUtils::GetUITextAutocapitalizationType( + mGeckoChild->GetInputContext()); +} + +- (BOOL)isSecureTextEntry { + if (!mGeckoChild || mGeckoChild->Destroyed()) { + return NO; + } + if (mGeckoChild->GetInputContext().IsPasswordEditor()) { + return YES; + } + return NO; +} + +#ifdef ACCESSIBILITY +// MUIRootAccessible + +- (id<MUIRootAccessibleProtocol>)accessible { + if (!mGeckoChild) return nil; + + id<MUIRootAccessibleProtocol> nativeAccessible = nil; + + // nsAutoRetainCocoaObject kungFuDeathGrip(self); + RefPtr<nsWindow> geckoChild(mGeckoChild); + RefPtr<a11y::LocalAccessible> accessible = geckoChild->GetRootAccessible(); + if (!accessible) return nil; + + accessible->GetNativeInterface((void**)&nativeAccessible); + + return nativeAccessible; +} + +- (BOOL)hasRepresentedView { + return YES; +} + +- (id)representedView { + return self; +} + +- (BOOL)isAccessibilityElement { + if (!mozilla::a11y::ShouldA11yBeEnabled()) { + return [super isAccessibilityElement]; + } + + return [[self accessible] isAccessibilityElement]; +} + +- (NSString*)accessibilityLabel { + if (!mozilla::a11y::ShouldA11yBeEnabled()) { + return [super accessibilityLabel]; + } + + return [[self accessible] accessibilityLabel]; +} + +- (CGRect)accessibilityFrame { + // Use the UIView implementation here. We rely on the position of this + // frame to place gecko bounds in the right offset. + return [super accessibilityFrame]; +} + +- (NSString*)accessibilityValue { + if (!mozilla::a11y::ShouldA11yBeEnabled()) { + return [super accessibilityValue]; + } + + return [[self accessible] accessibilityValue]; +} + +- (uint64_t)accessibilityTraits { + if (!mozilla::a11y::ShouldA11yBeEnabled()) { + return [super accessibilityTraits]; + } + + return [[self accessible] accessibilityTraits]; +} + +- (NSInteger)accessibilityElementCount { + if (!mozilla::a11y::ShouldA11yBeEnabled()) { + return [super accessibilityElementCount]; + } + + return [[self accessible] accessibilityElementCount]; +} + +- (nullable id)accessibilityElementAtIndex:(NSInteger)index { + if (!mozilla::a11y::ShouldA11yBeEnabled()) { + return [super accessibilityElementAtIndex:index]; + } + + return [[self accessible] accessibilityElementAtIndex:index]; +} + +- (NSInteger)indexOfAccessibilityElement:(id)element { + if (!mozilla::a11y::ShouldA11yBeEnabled()) { + return [super indexOfAccessibilityElement:element]; + } + + return [[self accessible] indexOfAccessibilityElement:element]; +} + +- (NSArray* _Nullable)accessibilityElements { + if (!mozilla::a11y::ShouldA11yBeEnabled()) { + return [super accessibilityElements]; + } + + return [[self accessible] accessibilityElements]; +} + +- (UIAccessibilityContainerType)accessibilityContainerType { + if (!mozilla::a11y::ShouldA11yBeEnabled()) { + return [super accessibilityContainerType]; + } + + return [[self accessible] accessibilityContainerType]; +} +#endif + @end nsWindow::nsWindow() @@ -442,20 +706,7 @@ nsresult nsWindow::Create(nsIWidget* aParent, nsNativeWidget aNativeParent, if (parent == nullptr && nativeParent) parent = nativeParent->mGeckoChild; if (parent && nativeParent == nullptr) nativeParent = parent->mNativeView; - // for toplevel windows, bounds are fixed to full screen size - if (parent == nullptr) { - if (nsAppShell::gWindow == nil) { - mBounds = UIKitScreenManager::GetBounds(); - } else { - CGRect cgRect = [nsAppShell::gWindow bounds]; - mBounds.x = cgRect.origin.x; - mBounds.y = cgRect.origin.y; - mBounds.width = cgRect.size.width; - mBounds.height = cgRect.size.height; - } - } else { - mBounds = aRect; - } + mBounds = aRect; ALOG("nsWindow[%p]::Create bounds: %d %d %d %d", (void*)this, mBounds.x, mBounds.y, mBounds.width, mBounds.height); @@ -487,6 +738,8 @@ nsresult nsWindow::Create(nsIWidget* aParent, nsNativeWidget aNativeParent, [nsAppShell::gTopLevelViews addObject:mNativeView]; } + mTextInputHandler = new widget::TextInputHandler(this); + return NS_OK; } @@ -498,6 +751,11 @@ void nsWindow::Destroy() { if (mParent) mParent->mChildren.RemoveElement(this); + if (mTextInputHandler) { + mTextInputHandler->OnDestroyed(); + } + mTextInputHandler = nullptr; + [mNativeView widgetDestroyed]; nsBaseWidget::Destroy(); @@ -507,8 +765,6 @@ void nsWindow::Destroy() { TearDownView(); nsBaseWidget::OnDestroy(); - - return NS_OK; } void nsWindow::Show(bool aState) { @@ -595,16 +851,12 @@ void nsWindow::SetSizeMode(nsSizeMode aMode) { void nsWindow::Invalidate(const LayoutDeviceIntRect& aRect) { if (!mNativeView || !mVisible) return; - MOZ_RELEASE_ASSERT( - GetLayerManager()->GetBackendType() != LayersBackend::LAYERS_WR, - "Shouldn't need to invalidate with accelerated OMTC layers!"); - [mNativeView setNeedsLayout]; [mNativeView setNeedsDisplayInRect:DevPixelsToUIKitPoints( mBounds, BackingScaleFactor())]; } -void nsWindow::SetFocus(Raise) { +void nsWindow::SetFocus(Raise, mozilla::dom::CallerType) { [[mNativeView window] makeKeyWindow]; [mNativeView becomeFirstResponder]; } @@ -649,10 +901,16 @@ void nsWindow::ReportSizeModeEvent(nsSizeMode aMode) { } void nsWindow::ReportSizeEvent() { + LayoutDeviceIntRect innerBounds = GetClientBounds(); + if (mWidgetListener) { - LayoutDeviceIntRect innerBounds = GetClientBounds(); mWidgetListener->WindowResized(this, innerBounds.width, innerBounds.height); } + + if (mAttachedWidgetListener) { + mAttachedWidgetListener->WindowResized(this, innerBounds.width, + innerBounds.height); + } } LayoutDeviceIntRect nsWindow::GetScreenBounds() { @@ -672,8 +930,8 @@ LayoutDeviceIntPoint nsWindow::WidgetToScreenOffset() { temp = [nsAppShell::gWindow convertPoint:temp toWindow:nil]; } - offset.x += temp.x; - offset.y += temp.y; + offset.x += static_cast<int32_t>(temp.x); + offset.y += static_cast<int32_t>(temp.y); return offset; } @@ -682,23 +940,63 @@ nsresult nsWindow::DispatchEvent(mozilla::WidgetGUIEvent* aEvent, nsEventStatus& aStatus) { aStatus = nsEventStatus_eIgnore; nsCOMPtr<nsIWidget> kungFuDeathGrip(aEvent->mWidget); + mozilla::Unused << kungFuDeathGrip; // Not used within this function - if (mWidgetListener) + if (mAttachedWidgetListener) { + aStatus = mAttachedWidgetListener->HandleEvent(aEvent, mUseAttachedEvents); + } else if (mWidgetListener) { aStatus = mWidgetListener->HandleEvent(aEvent, mUseAttachedEvents); + } return NS_OK; } void nsWindow::SetInputContext(const InputContext& aContext, const InputContextAction& aAction) { - // TODO: actually show VKB + NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; + + const bool changingEnabledState = + aContext.IsInputAttributeChanged(mInputContext); + mInputContext = aContext; + + if (IsVirtualKeyboardDisabled()) { + [mNativeView resignFirstResponder]; + return; + } + + [mNativeView becomeFirstResponder]; + + if (aAction.UserMightRequestOpenVKB() || changingEnabledState) { + // TODO(m_kato): + // It is unnecessary to call reloadInputViews with changingEnabledState if + // virtual keyboard is disappeared. + [mNativeView reloadInputViews]; + } + + NS_OBJC_END_TRY_IGNORE_BLOCK; } -mozilla::widget::InputContext nsWindow::GetInputContext() { +widget::InputContext nsWindow::GetInputContext() { + if (!mTextInputHandler) { + InputContext context; + context.mIMEState.mEnabled = IMEEnabled::Disabled; + context.mIMEState.mOpen = IMEState::OPEN_STATE_NOT_SUPPORTED; + return context; + } return mInputContext; } +widget::TextEventDispatcherListener* +nsWindow::GetNativeTextEventDispatcherListener() { + return mTextInputHandler; +} + +bool nsWindow::IsVirtualKeyboardDisabled() const { + return mInputContext.mIMEState.mEnabled == IMEEnabled::Disabled || + mInputContext.mHTMLInputMode.EqualsLiteral("none"); +} + void nsWindow::SetBackgroundColor(const nscolor& aColor) { mNativeView.backgroundColor = [UIColor colorWithRed:NS_GET_R(aColor) green:NS_GET_G(aColor) |