1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #import <UIKit/UIEvent.h>
7 #import <UIKit/UIKit.h>
8 #import <UIKit/UIGraphics.h>
9 #import <UIKit/UIInterface.h>
10 #import <UIKit/UIScreen.h>
11 #import <UIKit/UITapGestureRecognizer.h>
12 #import <UIKit/UITouch.h>
13 #import <UIKit/UIView.h>
14 #import <UIKit/UIViewController.h>
15 #import <UIKit/UIWindow.h>
16 #import <QuartzCore/QuartzCore.h>
21 #include "nsAppShell.h"
22 #include "nsIAppWindow.h"
23 #include "nsIWindowWatcher.h"
25 # include "nsAccessibilityService.h"
26 # include "mozilla/a11y/LocalAccessible.h"
29 #include "nsWidgetsCID.h"
30 #include "nsGfxCIID.h"
32 #include "gfxPlatform.h"
33 #include "gfxQuartzSurface.h"
35 #include "gfxImageSurface.h"
36 #include "gfxContext.h"
37 #include "nsObjCExceptions.h"
38 #include "nsQueryObject.h"
41 #include "TextInputHandler.h"
42 #include "UIKitUtils.h"
44 #include "mozilla/BasicEvents.h"
45 #include "mozilla/EventForwards.h"
46 #include "mozilla/ProfilerLabels.h"
47 #include "mozilla/TouchEvents.h"
48 #include "mozilla/Unused.h"
49 #include "mozilla/dom/MouseEventBinding.h"
50 #include "mozilla/gfx/Logging.h"
51 #include "mozilla/widget/GeckoViewSupport.h"
53 # include "mozilla/a11y/MUIRootAccessibleProtocol.h"
56 using namespace mozilla;
57 using namespace mozilla::gfx;
58 using namespace mozilla::layers;
59 using namespace mozilla::widget;
60 using mozilla::dom::Touch;
61 using mozilla::widget::UIKitUtils;
63 #define ALOG(args...) \
64 fprintf(stderr, args); \
67 static LayoutDeviceIntPoint UIKitPointsToDevPixels(CGPoint aPoint,
68 CGFloat aBackingScale) {
69 return LayoutDeviceIntPoint(NSToIntRound(aPoint.x * aBackingScale),
70 NSToIntRound(aPoint.y * aBackingScale));
73 static CGRect DevPixelsToUIKitPoints(const LayoutDeviceIntRect& aRect,
74 CGFloat aBackingScale) {
75 return CGRectMake((CGFloat)aRect.x / aBackingScale,
76 (CGFloat)aRect.y / aBackingScale,
77 (CGFloat)aRect.width / aBackingScale,
78 (CGFloat)aRect.height / aBackingScale);
81 // Used to retain a Cocoa object for the remainder of a method's execution.
82 class nsAutoRetainUIKitObject {
84 explicit nsAutoRetainUIKitObject(id anObject) { mObject = [anObject retain]; }
85 ~nsAutoRetainUIKitObject() { [mObject release]; }
88 id mObject; // [STRONG]
92 @interface ChildView : UIView <UIKeyInput, MUIRootAccessibleProtocol> {
94 @interface ChildView : UIView <UIKeyInput> {
97 nsWindow* mGeckoChild; // weak ref
98 BOOL mWaitingForPaint;
99 NSMapTable<UITouch*, NSNumber*>* mTouches;
102 // sets up our view, attaching it to its owning gecko view
103 - (id)initWithFrame:(CGRect)inFrame geckoChild:(nsWindow*)inChild;
104 // Our Gecko child was Destroy()ed
105 - (void)widgetDestroyed;
106 // Tear down this ChildView
107 - (void)delayedTearDown;
108 - (void)sendMouseEvent:(EventMessage)aType
109 point:(LayoutDeviceIntPoint)aPoint
110 widget:(nsWindow*)aWindow;
111 - (void)handleTap:(UITapGestureRecognizer*)sender;
112 - (BOOL)isUsingMainThreadOpenGL;
113 - (void)drawUsingOpenGL;
114 - (void)drawUsingOpenGLCallback;
115 - (void)sendTouchEvent:(EventMessage)aType
116 touches:(NSSet*)aTouches
117 widget:(nsWindow*)aWindow;
118 // Event handling (UIResponder)
119 - (void)touchesBegan:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event;
120 - (void)touchesCancelled:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event;
121 - (void)touchesEnded:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event;
122 - (void)touchesMoved:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event;
124 - (void)activateWindow:(NSNotification*)notification;
125 - (void)deactivateWindow:(NSNotification*)notification;
129 - (BOOL)hasRepresentedView;
130 - (id)representedView;
133 - (BOOL)isAccessibilityElement;
134 - (NSString*)accessibilityLabel;
135 - (CGRect)accessibilityFrame;
136 - (NSString*)accessibilityValue;
137 - (uint64_t)accessibilityTraits;
138 - (NSInteger)accessibilityElementCount;
139 - (nullable id)accessibilityElementAtIndex:(NSInteger)index;
140 - (NSInteger)indexOfAccessibilityElement:(id)element;
141 - (NSArray* _Nullable)accessibilityElements;
142 - (UIAccessibilityContainerType)accessibilityContainerType;
147 @implementation ChildView
148 + (Class)layerClass {
149 return [CAEAGLLayer class];
152 - (id)initWithFrame:(CGRect)inFrame geckoChild:(nsWindow*)inChild {
153 self.multipleTouchEnabled = YES;
154 if ((self = [super initWithFrame:inFrame])) {
155 mGeckoChild = inChild;
157 ALOG("[ChildView[%p] initWithFrame:] (mGeckoChild = %p)", (void*)self,
162 UITapGestureRecognizer* tapRecognizer =
163 [[UITapGestureRecognizer alloc] initWithTarget:self
164 action:@selector(handleTap:)];
165 tapRecognizer.numberOfTapsRequired = 1;
166 [self addGestureRecognizer:tapRecognizer];
168 mTouches = [[NSMapTable alloc] init];
171 // This is managed with weak references by the notification center so that we
172 // do not need to call removeObserver.
173 // https://developer.apple.com/documentation/foundation/nsnotificationcenter/1415360-addobserver#discussion
174 [[NSNotificationCenter defaultCenter]
176 selector:@selector(activateWindow:)
177 name:UIWindowDidBecomeKeyNotification
179 [[NSNotificationCenter defaultCenter]
181 selector:@selector(deactivateWindow:)
182 name:UIWindowDidResignKeyNotification
188 - (void)widgetDestroyed {
189 mGeckoChild = nullptr;
193 - (void)delayedTearDown {
194 [self removeFromSuperview];
198 - (void)activateWindow:(NSNotification*)notification {
199 ALOG("[[ChildView[%p] activateWindow]", (void*)self);
205 if (nsIWidgetListener* listener = mGeckoChild->GetWidgetListener()) {
206 listener->WindowActivated();
210 - (void)deactivateWindow:(NSNotification*)notification {
211 ALOG("[[ChildView[%p] deactivateWindow]", (void*)self);
217 if (nsIWidgetListener* listener = mGeckoChild->GetWidgetListener()) {
218 listener->WindowDeactivated();
222 - (void)sendMouseEvent:(EventMessage)aType
223 point:(LayoutDeviceIntPoint)aPoint
224 widget:(nsWindow*)aWindow {
225 MOZ_DIAGNOSTIC_ASSERT(
226 aType != eContextMenu,
227 "eContextMenu event may need to be dispatched as WidgetPointerEvent");
228 WidgetMouseEvent event(true, aType, aWindow, WidgetMouseEvent::eReal);
230 event.mRefPoint = aPoint;
231 event.mClickCount = 1;
232 event.mButton = MouseButton::ePrimary;
233 event.mInputSource = mozilla::dom::MouseEvent_Binding::MOZ_SOURCE_UNKNOWN;
235 nsEventStatus status;
236 aWindow->DispatchEvent(&event, status);
239 - (void)handleTap:(UITapGestureRecognizer*)sender {
240 if (sender.state == UIGestureRecognizerStateEnded) {
241 ALOG("[ChildView[%p] handleTap]", self);
242 LayoutDeviceIntPoint lp = UIKitPointsToDevPixels(
243 [sender locationInView:self], [self contentScaleFactor]);
244 [self sendMouseEvent:eMouseMove point:lp widget:mGeckoChild];
245 [self sendMouseEvent:eMouseDown point:lp widget:mGeckoChild];
246 [self sendMouseEvent:eMouseUp point:lp widget:mGeckoChild];
250 - (void)sendTouchEvent:(EventMessage)aType
251 touches:(NSSet*)aTouches
252 widget:(nsWindow*)aWindow {
253 WidgetTouchEvent event(true, aType, aWindow);
254 // XXX: I think nativeEvent.timestamp * 1000 is probably usable here but
255 // I don't care that much right now.
256 event.mTouches.SetCapacity(aTouches.count);
257 for (UITouch* touch in aTouches) {
258 LayoutDeviceIntPoint loc = UIKitPointsToDevPixels(
259 [touch locationInView:self], [self contentScaleFactor]);
260 LayoutDeviceIntPoint radius = UIKitPointsToDevPixels(
261 CGPointMake([touch majorRadius], [touch majorRadius]),
262 [self contentScaleFactor]);
263 NSNumber* value = [mTouches objectForKey:touch];
265 // This shouldn't happen.
266 NS_ASSERTION(false, "Got a touch that we didn't know about");
269 int id = [value intValue];
270 RefPtr<Touch> t = new Touch(id, loc, radius, 0.0f, 1.0f);
271 event.mRefPoint = loc;
272 event.mTouches.AppendElement(t);
274 aWindow->DispatchInputEvent(&event);
277 - (void)touchesBegan:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event {
278 ALOG("[ChildView[%p] touchesBegan", self);
279 if (!mGeckoChild) return;
281 for (UITouch* touch : touches) {
282 [mTouches setObject:[NSNumber numberWithInt:mNextTouchID] forKey:touch];
285 [self sendTouchEvent:eTouchStart
286 touches:[event allTouches]
290 - (void)touchesCancelled:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event {
291 ALOG("[ChildView[%p] touchesCancelled", self);
292 [self sendTouchEvent:eTouchCancel touches:touches widget:mGeckoChild];
293 for (UITouch* touch : touches) {
294 [mTouches removeObjectForKey:touch];
296 if (mTouches.count == 0) {
301 - (void)touchesEnded:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event {
302 ALOG("[ChildView[%p] touchesEnded", self);
303 if (!mGeckoChild) return;
305 [self sendTouchEvent:eTouchEnd touches:touches widget:mGeckoChild];
306 for (UITouch* touch : touches) {
307 [mTouches removeObjectForKey:touch];
309 if (mTouches.count == 0) {
314 - (void)touchesMoved:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event {
315 ALOG("[ChildView[%p] touchesMoved", self);
316 if (!mGeckoChild) return;
318 [self sendTouchEvent:eTouchMove
319 touches:[event allTouches]
323 - (BOOL)canBecomeFirstResponder {
328 if (mGeckoChild->IsVirtualKeyboardDisabled()) {
334 - (void)setNeedsDisplayInRect:(CGRect)aRect {
335 if ([self isUsingMainThreadOpenGL]) {
336 // Draw without calling drawRect. This prevent us from
337 // needing to access the normal window buffer surface unnecessarily, so we
338 // waste less time synchronizing the two surfaces.
339 if (!mWaitingForPaint) {
340 mWaitingForPaint = YES;
341 // Use NSRunLoopCommonModes instead of the default NSDefaultRunLoopMode
342 // so that the timer also fires while a native menu is open.
343 [self performSelector:@selector(drawUsingOpenGLCallback)
346 inModes:[NSArray arrayWithObject:NSRunLoopCommonModes]];
351 - (BOOL)isUsingMainThreadOpenGL {
352 if (!mGeckoChild || ![self window]) return NO;
357 - (void)drawUsingOpenGL {
358 ALOG("drawUsingOpenGL");
359 AUTO_PROFILER_LABEL("ChildView::drawUsingOpenGL", OTHER);
361 if (!mGeckoChild->IsVisible()) return;
363 mWaitingForPaint = NO;
365 LayoutDeviceIntRect geckoBounds = mGeckoChild->GetBounds();
366 LayoutDeviceIntRegion region(geckoBounds);
368 mGeckoChild->PaintWindow(region);
371 // Called asynchronously after setNeedsDisplay in order to avoid entering the
372 // normal drawing machinery.
373 - (void)drawUsingOpenGLCallback {
374 if (mWaitingForPaint) {
375 [self drawUsingOpenGL];
379 // The display system has told us that a portion of our view is dirty. Tell
381 - (void)drawRect:(CGRect)aRect {
382 CGContextRef cgContext = UIGraphicsGetCurrentContext();
383 [self drawRect:aRect inContext:cgContext];
386 - (void)drawRect:(CGRect)aRect inContext:(CGContextRef)aContext {
388 LayoutDeviceIntRect geckoBounds = mGeckoChild->GetBounds();
391 "---- Update[%p][%p] [%f %f %f %f] cgc: %p\n gecko bounds: [%d %d "
393 self, mGeckoChild, aRect.origin.x, aRect.origin.y, aRect.size.width,
394 aRect.size.height, aContext, geckoBounds.x, geckoBounds.y,
395 geckoBounds.width, geckoBounds.height);
397 CGAffineTransform xform = CGContextGetCTM(aContext);
398 fprintf(stderr, " xform in: [%f %f %f %f %f %f]\n", xform.a, xform.b,
399 xform.c, xform.d, xform.tx, xform.ty);
403 // For Gecko-initiated repaints in OpenGL mode, drawUsingOpenGL is
404 // directly called from a delayed perform callback - without going through
406 // Paints that come through here are triggered by something that Cocoa
407 // controls, for example by window resizing or window focus changes.
409 // Do GL composition and return.
410 [self drawUsingOpenGL];
413 AUTO_PROFILER_LABEL("ChildView::drawRect", OTHER);
415 // The CGContext that drawRect supplies us with comes with a transform that
416 // scales one user space unit to one Cocoa point, which can consist of
417 // multiple dev pixels. But Gecko expects its supplied context to be scaled
418 // to device pixels, so we need to reverse the scaling.
419 double scale = mGeckoChild->BackingScaleFactor();
420 CGContextSaveGState(aContext);
421 CGContextScaleCTM(aContext, 1.0 / scale, 1.0 / scale);
423 CGSize viewSize = [self bounds].size;
424 gfx::IntSize backingSize(NSToIntRound(viewSize.width * scale),
425 NSToIntRound(viewSize.height * scale));
427 CGContextSaveGState(aContext);
429 LayoutDeviceIntRegion region =
430 LayoutDeviceIntRect(NSToIntRound(aRect.origin.x * scale),
431 NSToIntRound(aRect.origin.y * scale),
432 NSToIntRound(aRect.size.width * scale),
433 NSToIntRound(aRect.size.height * scale));
435 // Create Cairo objects.
436 RefPtr<gfxQuartzSurface> targetSurface;
438 UniquePtr<gfxContext> targetContext;
439 if (gfxPlatform::GetPlatform()->SupportsAzureContentForType(
440 gfx::BackendType::CAIRO)) {
441 // This is dead code unless you mess with prefs, but keep it around for
443 targetSurface = new gfxQuartzSurface(aContext, backingSize);
444 RefPtr<gfx::DrawTarget> dt =
445 gfxPlatform::CreateDrawTargetForSurface(targetSurface, backingSize);
446 if (!dt || !dt->IsValid()) {
447 gfxDevCrash(mozilla::gfx::LogReason::InvalidContext)
448 << "Window context problem 2 " << backingSize;
451 targetContext = gfxContext::CreateOrNull(dt);
453 MOZ_ASSERT_UNREACHABLE("COREGRAPHICS is the only supported backend");
455 MOZ_ASSERT(targetContext); // already checked for valid draw targets above
457 // Set up the clip region.
458 targetContext->NewPath();
459 for (auto iter = region.RectIter(); !iter.Done(); iter.Next()) {
460 const LayoutDeviceIntRect& r = iter.Get();
461 targetContext->Rectangle(gfxRect(r.x, r.y, r.width, r.height));
463 targetContext->Clip();
465 // nsAutoRetainCocoaObject kungFuDeathGrip(self);
466 bool painted = false;
467 targetContext = nullptr;
468 targetSurface = nullptr;
470 CGContextRestoreGState(aContext);
472 // Undo the scale transform so that from now on the context is in
473 // CocoaPoints again.
474 CGContextRestoreGState(aContext);
475 if (!painted && [self isOpaque]) {
476 // Gecko refused to draw, but we've claimed to be opaque, so we have to
477 // draw something--fill with white.
478 CGContextSetRGBFillColor(aContext, 1, 1, 1, 1);
479 CGContextFillRect(aContext, aRect);
483 fprintf(stderr, "---- update done ----\n");
486 CGContextSetRGBStrokeColor (aContext,
487 ((((unsigned long)self) & 0xff)) / 255.0,
488 ((((unsigned long)self) & 0xff00) >> 8) / 255.0,
489 ((((unsigned long)self) & 0xff0000) >> 16) / 255.0,
492 CGContextSetRGBStrokeColor(aContext, 1, 0, 0, 0.8);
493 CGContextSetLineWidth(aContext, 4.0);
494 CGContextStrokeRect(aContext, aRect);
500 - (void)insertText:(NSString*)text {
501 if (!mGeckoChild || mGeckoChild->Destroyed()) {
504 widget::TextInputHandler* textInputHandler =
505 mGeckoChild->GetTextInputHandler();
506 if (!textInputHandler) {
509 textInputHandler->InsertText(text);
512 - (void)deleteBackward {
513 if (!mGeckoChild || mGeckoChild->Destroyed()) {
516 widget::TextInputHandler* textInputHandler =
517 mGeckoChild->GetTextInputHandler();
518 if (!textInputHandler) {
521 textInputHandler->HandleCommand(Command::DeleteCharBackward);
525 if (!mGeckoChild || mGeckoChild->Destroyed()) {
528 widget::InputContext context = mGeckoChild->GetInputContext();
529 if (context.mIMEState.mEnabled == IMEEnabled::Disabled) {
537 - (UIKeyboardType)keyboardType {
538 if (!mGeckoChild || mGeckoChild->Destroyed()) {
539 return UIKeyboardTypeDefault;
541 return UIKitUtils::GetUIKeyboardType(mGeckoChild->GetInputContext());
544 - (UIReturnKeyType)returnKeyType {
545 if (!mGeckoChild || mGeckoChild->Destroyed()) {
546 return UIReturnKeyDefault;
548 return UIKitUtils::GetUIReturnKeyType(mGeckoChild->GetInputContext());
551 - (UITextAutocapitalizationType)autocapitalizationType {
552 if (!mGeckoChild || mGeckoChild->Destroyed()) {
553 return UITextAutocapitalizationTypeNone;
555 return UIKitUtils::GetUITextAutocapitalizationType(
556 mGeckoChild->GetInputContext());
559 - (UITextAutocorrectionType)autocorrectionType {
560 if (!mGeckoChild || mGeckoChild->Destroyed()) {
561 return UITextAutocorrectionTypeDefault;
564 return UIKitUtils::GetUITextAutocorrectionType(
565 mGeckoChild->GetInputContext());
568 - (BOOL)isSecureTextEntry {
569 if (!mGeckoChild || mGeckoChild->Destroyed()) {
572 if (mGeckoChild->GetInputContext().IsPasswordEditor()) {
581 - (id<MUIRootAccessibleProtocol>)accessible {
582 if (!mGeckoChild) return nil;
584 id<MUIRootAccessibleProtocol> nativeAccessible = nil;
586 // nsAutoRetainCocoaObject kungFuDeathGrip(self);
587 RefPtr<nsWindow> geckoChild(mGeckoChild);
588 RefPtr<a11y::LocalAccessible> accessible = geckoChild->GetRootAccessible();
589 if (!accessible) return nil;
591 accessible->GetNativeInterface((void**)&nativeAccessible);
593 return nativeAccessible;
596 - (BOOL)hasRepresentedView {
600 - (id)representedView {
604 - (BOOL)isAccessibilityElement {
605 if (!mozilla::a11y::ShouldA11yBeEnabled()) {
606 return [super isAccessibilityElement];
609 return [[self accessible] isAccessibilityElement];
612 - (NSString*)accessibilityLabel {
613 if (!mozilla::a11y::ShouldA11yBeEnabled()) {
614 return [super accessibilityLabel];
617 return [[self accessible] accessibilityLabel];
620 - (CGRect)accessibilityFrame {
621 // Use the UIView implementation here. We rely on the position of this
622 // frame to place gecko bounds in the right offset.
623 return [super accessibilityFrame];
626 - (NSString*)accessibilityValue {
627 if (!mozilla::a11y::ShouldA11yBeEnabled()) {
628 return [super accessibilityValue];
631 return [[self accessible] accessibilityValue];
634 - (uint64_t)accessibilityTraits {
635 if (!mozilla::a11y::ShouldA11yBeEnabled()) {
636 return [super accessibilityTraits];
639 return [[self accessible] accessibilityTraits];
642 - (NSInteger)accessibilityElementCount {
643 if (!mozilla::a11y::ShouldA11yBeEnabled()) {
644 return [super accessibilityElementCount];
647 return [[self accessible] accessibilityElementCount];
650 - (nullable id)accessibilityElementAtIndex:(NSInteger)index {
651 if (!mozilla::a11y::ShouldA11yBeEnabled()) {
652 return [super accessibilityElementAtIndex:index];
655 return [[self accessible] accessibilityElementAtIndex:index];
658 - (NSInteger)indexOfAccessibilityElement:(id)element {
659 if (!mozilla::a11y::ShouldA11yBeEnabled()) {
660 return [super indexOfAccessibilityElement:element];
663 return [[self accessible] indexOfAccessibilityElement:element];
666 - (NSArray* _Nullable)accessibilityElements {
667 if (!mozilla::a11y::ShouldA11yBeEnabled()) {
668 return [super accessibilityElements];
671 return [[self accessible] accessibilityElements];
674 - (UIAccessibilityContainerType)accessibilityContainerType {
675 if (!mozilla::a11y::ShouldA11yBeEnabled()) {
676 return [super accessibilityContainerType];
679 return [[self accessible] accessibilityContainerType];
685 NS_IMPL_ISUPPORTS_INHERITED(nsWindow, nsBaseWidget, nsWindow);
688 : mNativeView(nullptr),
690 mSizeMode(nsSizeMode_Normal),
693 nsWindow::~nsWindow() {
694 [mNativeView widgetDestroyed]; // Safe if mNativeView is nil.
695 TearDownView(); // Safe if called twice.
698 void nsWindow::TearDownView() {
699 if (!mNativeView) return;
701 [mNativeView performSelectorOnMainThread:@selector(delayedTearDown)
703 waitUntilDone:false];
707 bool nsWindow::IsTopLevel() {
708 return mWindowType == WindowType::TopLevel ||
709 mWindowType == WindowType::Dialog ||
710 mWindowType == WindowType::Invisible;
717 nsresult nsWindow::Create(nsIWidget* aParent, const LayoutDeviceIntRect& aRect,
718 widget::InitData* aInitData) {
719 ALOG("nsWindow[%p]::Create %p [%d %d %d %d]", (void*)this, (void*)aParent,
720 aRect.x, aRect.y, aRect.width, aRect.height);
721 nsWindow* parent = (nsWindow*)aParent;
725 ALOG("nsWindow[%p]::Create bounds: %d %d %d %d", (void*)this, mBounds.x,
726 mBounds.y, mBounds.width, mBounds.height);
728 // Set defaults which can be overriden from aInitData in BaseCreate
729 mWindowType = WindowType::TopLevel;
730 mBorderStyle = BorderStyle::Default;
732 nsBaseWidget::BaseCreate(aParent, aInitData);
734 NS_ASSERTION(IsTopLevel() || parent,
735 "non top level window doesn't have a parent!");
737 mNativeView = [[ChildView alloc]
738 initWithFrame:DevPixelsToUIKitPoints(mBounds, BackingScaleFactor())
740 mNativeView.hidden = YES;
743 parent->mChildren.AppendElement(this);
747 if (parent && parent->mNativeView) {
748 [parent->mNativeView addSubview:mNativeView];
749 } else if (nsAppShell::gWindow) {
750 [nsAppShell::gWindow.rootViewController.view addSubview:mNativeView];
752 [nsAppShell::gTopLevelViews addObject:mNativeView];
755 mTextInputHandler = new widget::TextInputHandler(this);
760 void nsWindow::Destroy() {
761 for (uint32_t i = 0; i < mChildren.Length(); ++i) {
762 // why do we still have children?
763 mChildren[i]->ClearParent();
766 if (mParent) mParent->mChildren.RemoveElement(this);
768 if (mTextInputHandler) {
769 mTextInputHandler->OnDestroyed();
771 mTextInputHandler = nullptr;
773 [mNativeView widgetDestroyed];
775 nsCOMPtr<nsIWidget> kungFuDeathGrip(this);
777 nsBaseWidget::Destroy();
779 // ReportDestroyEvent();
783 nsBaseWidget::OnDestroy();
786 void nsWindow::Show(bool aState) {
787 if (aState != mVisible) {
788 mNativeView.hidden = aState ? NO : YES;
790 UIView* parentView = mParent
791 ? mParent->mNativeView
792 : nsAppShell::gWindow.rootViewController.view;
793 [parentView bringSubviewToFront:mNativeView];
794 [mNativeView setNeedsDisplay];
800 void nsWindow::Move(double aX, double aY) {
801 if (!mNativeView || (mBounds.x == aX && mBounds.y == aY)) return;
804 // The point we have is in Gecko coordinates (origin top-left). Convert
805 // it to Cocoa ones (origin bottom-left).
809 mNativeView.frame = DevPixelsToUIKitPoints(mBounds, BackingScaleFactor());
811 if (mVisible) [mNativeView setNeedsDisplay];
816 void nsWindow::Resize(double aX, double aY, double aWidth, double aHeight,
818 BOOL isMoving = (mBounds.x != aX || mBounds.y != aY);
819 BOOL isResizing = (mBounds.width != aWidth || mBounds.height != aHeight);
820 if (!mNativeView || (!isMoving && !isResizing)) return;
827 mBounds.width = aWidth;
828 mBounds.height = aHeight;
831 [mNativeView setFrame:DevPixelsToUIKitPoints(mBounds, BackingScaleFactor())];
833 if (mVisible && aRepaint) [mNativeView setNeedsDisplay];
835 if (isMoving) ReportMoveEvent();
837 if (isResizing) ReportSizeEvent();
840 void nsWindow::Resize(double aWidth, double aHeight, bool aRepaint) {
841 if (!mNativeView || (mBounds.width == aWidth && mBounds.height == aHeight))
844 mBounds.width = aWidth;
845 mBounds.height = aHeight;
847 [mNativeView setFrame:DevPixelsToUIKitPoints(mBounds, BackingScaleFactor())];
849 if (mVisible && aRepaint) [mNativeView setNeedsDisplay];
854 void nsWindow::SetSizeMode(nsSizeMode aMode) {
855 if (aMode == static_cast<int32_t>(mSizeMode)) {
859 mSizeMode = static_cast<nsSizeMode>(aMode);
860 if (aMode == nsSizeMode_Maximized || aMode == nsSizeMode_Fullscreen) {
861 // Resize to fill screen
862 nsBaseWidget::InfallibleMakeFullScreen(true);
864 ReportSizeModeEvent(aMode);
867 void nsWindow::Invalidate(const LayoutDeviceIntRect& aRect) {
868 if (!mNativeView || !mVisible) return;
870 [mNativeView setNeedsLayout];
871 [mNativeView setNeedsDisplayInRect:DevPixelsToUIKitPoints(
872 mBounds, BackingScaleFactor())];
875 void nsWindow::SetFocus(Raise, mozilla::dom::CallerType) {
876 [[mNativeView window] makeKeyWindow];
877 [mNativeView becomeFirstResponder];
880 void nsWindow::WillPaintWindow() {
881 if (mWidgetListener) {
882 mWidgetListener->WillPaintWindow(this);
886 bool nsWindow::PaintWindow(LayoutDeviceIntRegion aRegion) {
887 if (!mWidgetListener) return false;
889 bool returnValue = false;
890 returnValue = mWidgetListener->PaintWindow(this, aRegion);
892 if (mWidgetListener) {
893 mWidgetListener->DidPaintWindow();
899 void nsWindow::ReportMoveEvent() { NotifyWindowMoved(mBounds.x, mBounds.y); }
901 void nsWindow::ReportSizeModeEvent(nsSizeMode aMode) {
902 if (mWidgetListener) {
906 case nsSizeMode_Maximized:
907 theMode = nsSizeMode_Maximized;
909 case nsSizeMode_Fullscreen:
910 theMode = nsSizeMode_Fullscreen;
915 mWidgetListener->SizeModeChanged(theMode);
919 void nsWindow::ReportSizeEvent() {
920 LayoutDeviceIntRect innerBounds = GetClientBounds();
922 if (mWidgetListener) {
923 mWidgetListener->WindowResized(this, innerBounds.width, innerBounds.height);
926 if (mAttachedWidgetListener) {
927 mAttachedWidgetListener->WindowResized(this, innerBounds.width,
932 LayoutDeviceIntRect nsWindow::GetScreenBounds() {
933 return LayoutDeviceIntRect(WidgetToScreenOffset(), mBounds.Size());
936 LayoutDeviceIntPoint nsWindow::WidgetToScreenOffset() {
937 LayoutDeviceIntPoint offset(0, 0);
939 offset = mParent->WidgetToScreenOffset();
942 CGPoint temp = [mNativeView convertPoint:temp toView:nil];
944 if (!mParent && nsAppShell::gWindow) {
945 // convert to screen coords
946 temp = [nsAppShell::gWindow convertPoint:temp toWindow:nil];
949 offset.x += static_cast<int32_t>(temp.x);
950 offset.y += static_cast<int32_t>(temp.y);
955 nsresult nsWindow::DispatchEvent(mozilla::WidgetGUIEvent* aEvent,
956 nsEventStatus& aStatus) {
957 aStatus = nsEventStatus_eIgnore;
958 nsCOMPtr<nsIWidget> kungFuDeathGrip(aEvent->mWidget);
959 mozilla::Unused << kungFuDeathGrip; // Not used within this function
961 if (mAttachedWidgetListener) {
962 aStatus = mAttachedWidgetListener->HandleEvent(aEvent, mUseAttachedEvents);
963 } else if (mWidgetListener) {
964 aStatus = mWidgetListener->HandleEvent(aEvent, mUseAttachedEvents);
970 void nsWindow::SetInputContext(const InputContext& aContext,
971 const InputContextAction& aAction) {
972 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
974 const bool changingEnabledState =
975 aContext.IsInputAttributeChanged(mInputContext);
977 mInputContext = aContext;
979 if (IsVirtualKeyboardDisabled()) {
980 [mNativeView resignFirstResponder];
984 [mNativeView becomeFirstResponder];
986 if (aAction.UserMightRequestOpenVKB() || changingEnabledState) {
988 // It is unnecessary to call reloadInputViews with changingEnabledState if
989 // virtual keyboard is disappeared.
990 [mNativeView reloadInputViews];
993 NS_OBJC_END_TRY_IGNORE_BLOCK;
996 widget::InputContext nsWindow::GetInputContext() {
997 if (!mTextInputHandler) {
998 InputContext context;
999 context.mIMEState.mEnabled = IMEEnabled::Disabled;
1000 context.mIMEState.mOpen = IMEState::OPEN_STATE_NOT_SUPPORTED;
1003 return mInputContext;
1006 widget::TextEventDispatcherListener*
1007 nsWindow::GetNativeTextEventDispatcherListener() {
1008 return mTextInputHandler;
1011 bool nsWindow::IsVirtualKeyboardDisabled() const {
1012 return mInputContext.mIMEState.mEnabled == IMEEnabled::Disabled ||
1013 mInputContext.mHTMLInputMode.EqualsLiteral("none");
1016 void nsWindow::SetBackgroundColor(const nscolor& aColor) {
1017 mNativeView.backgroundColor = [UIColor colorWithRed:NS_GET_R(aColor)
1018 green:NS_GET_G(aColor)
1019 blue:NS_GET_B(aColor)
1020 alpha:NS_GET_A(aColor)];
1023 void* nsWindow::GetNativeData(uint32_t aDataType) {
1024 void* retVal = nullptr;
1026 switch (aDataType) {
1027 case NS_NATIVE_WIDGET:
1028 retVal = (void*)mNativeView;
1031 case NS_NATIVE_WINDOW:
1032 retVal = [mNativeView window];
1035 case NS_NATIVE_GRAPHIC:
1036 NS_ERROR("Requesting NS_NATIVE_GRAPHIC on a UIKit child view!");
1039 case NS_NATIVE_OFFSETX:
1043 case NS_NATIVE_OFFSETY:
1047 case NS_RAW_NATIVE_IME_CONTEXT:
1048 retVal = GetPseudoIMEContext();
1052 retVal = NS_ONLY_ONE_NATIVE_IME_CONTEXT;
1059 CGFloat nsWindow::BackingScaleFactor() {
1061 return [mNativeView contentScaleFactor];
1063 return [UIScreen mainScreen].scale;
1066 int32_t nsWindow::RoundsWidgetCoordinatesTo() {
1067 if (BackingScaleFactor() == 2.0) {
1073 EventDispatcher* nsWindow::GetEventDispatcher() const {
1075 return mIOSView->mEventDispatcher;
1080 already_AddRefed<nsIWidget> nsIWidget::CreateTopLevelWindow() {
1081 nsCOMPtr<nsIWidget> window = new nsWindow();
1082 return window.forget();
1085 already_AddRefed<nsIWidget> nsIWidget::CreateChildWindow() {
1086 nsCOMPtr<nsIWidget> window = new nsWindow();
1087 return window.forget();
1091 already_AddRefed<nsWindow> nsWindow::From(nsPIDOMWindowOuter* aDOMWindow) {
1092 nsCOMPtr<nsIWidget> widget = WidgetUtils::DOMWindowToWidget(aDOMWindow);
1093 return From(widget);
1097 already_AddRefed<nsWindow> nsWindow::From(nsIWidget* aWidget) {
1098 RefPtr<nsWindow> window = do_QueryObject(aWidget);
1099 return window.forget();
1102 NS_IMPL_ISUPPORTS(IOSView, nsIGeckoViewEventDispatcher, nsIGeckoViewView)
1104 IOSView::~IOSView() { [mInitData release]; }
1106 nsresult IOSView::GetInitData(JSContext* aCx,
1107 JS::MutableHandle<JS::Value> aOut) {
1108 return NS_ERROR_NOT_IMPLEMENTED;
1111 @interface GeckoViewWindowImpl : NSObject <GeckoViewWindow> {
1113 RefPtr<nsWindow> mWindow;
1114 nsCOMPtr<nsPIDOMWindowOuter> mOuterWindow;
1119 @implementation GeckoViewWindowImpl
1121 return mWindow ? (UIView*)mWindow->GetNativeData(NS_NATIVE_WIDGET) : nil;
1126 if (IOSView* iosView = mWindow->GetIOSView()) {
1127 iosView->mEventDispatcher->Detach();
1133 mOuterWindow->ForceClose();
1134 mOuterWindow = nullptr;
1139 id<GeckoViewWindow> GeckoViewOpenWindow(NSString* aId,
1140 id<SwiftEventDispatcher> aDispatcher,
1141 id aInitData, bool aPrivateMode) {
1142 MOZ_ASSERT(NS_IsMainThread());
1144 AUTO_PROFILER_LABEL("GeckoViewOpenWindows", OTHER);
1146 nsCOMPtr<nsIWindowWatcher> ww = do_GetService(NS_WINDOWWATCHER_CONTRACTID);
1147 MOZ_RELEASE_ASSERT(ww);
1150 nsresult rv = Preferences::GetCString("toolkit.defaultChromeURI", url);
1151 if (NS_FAILED(rv)) {
1152 url = "chrome://geckoview/content/geckoview.xhtml"_ns;
1155 // Prepare an nsIGeckoViewView to pass as argument to the window.
1156 RefPtr<IOSView> iosView = new IOSView();
1157 iosView->mEventDispatcher->Attach(aDispatcher);
1158 iosView->mInitData = [aInitData retain];
1160 nsAutoCString chromeFlags("chrome,dialog=0,remote,resizable,scrollbars");
1162 chromeFlags += ",private";
1165 nsCOMPtr<mozIDOMWindowProxy> domWindow;
1168 nsDependentCString([aId UTF8String],
1169 [aId lengthOfBytesUsingEncoding:NSUTF8StringEncoding]),
1170 chromeFlags, iosView, getter_AddRefs(domWindow));
1171 MOZ_RELEASE_ASSERT(domWindow);
1173 nsCOMPtr<nsPIDOMWindowOuter> pdomWindow = nsPIDOMWindowOuter::From(domWindow);
1174 const RefPtr<nsWindow> window = nsWindow::From(pdomWindow);
1177 window->SetIOSView(iosView.forget());
1179 if (nsIWidgetListener* widgetListener = window->GetWidgetListener()) {
1180 nsCOMPtr<nsIAppWindow> appWindow(widgetListener->GetAppWindow());
1182 // Our window is not intrinsically sized, so tell AppWindow to
1183 // not set a size for us.
1184 appWindow->SetIntrinsicallySized(false);
1188 GeckoViewWindowImpl* gvWindow = [[GeckoViewWindowImpl alloc] init];
1189 gvWindow->mOuterWindow = pdomWindow;
1190 gvWindow->mWindow = window;
1191 return [gvWindow autorelease];