Bug 1931425 - Limit how often moz-label's #setStyles runs r=reusable-components-revie...
[gecko.git] / widget / uikit / nsWindow.mm
bloba62653dc16501cab814a6ab5c6036fc38961cb02
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>
18 #include <algorithm>
20 #include "nsWindow.h"
21 #include "nsAppShell.h"
22 #include "nsIAppWindow.h"
23 #include "nsIWindowWatcher.h"
24 #ifdef ACCESSIBILITY
25 #  include "nsAccessibilityService.h"
26 #  include "mozilla/a11y/LocalAccessible.h"
27 #endif
29 #include "nsWidgetsCID.h"
30 #include "nsGfxCIID.h"
32 #include "gfxPlatform.h"
33 #include "gfxQuartzSurface.h"
34 #include "gfxUtils.h"
35 #include "gfxImageSurface.h"
36 #include "gfxContext.h"
37 #include "nsObjCExceptions.h"
38 #include "nsQueryObject.h"
39 #include "nsRegion.h"
40 #include "nsTArray.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"
52 #ifdef ACCESSIBILITY
53 #  include "mozilla/a11y/MUIRootAccessibleProtocol.h"
54 #endif
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); \
65   fprintf(stderr, "\n")
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 {
83  public:
84   explicit nsAutoRetainUIKitObject(id anObject) { mObject = [anObject retain]; }
85   ~nsAutoRetainUIKitObject() { [mObject release]; }
87  private:
88   id mObject;  // [STRONG]
91 #ifdef ACCESSIBILITY
92 @interface ChildView : UIView <UIKeyInput, MUIRootAccessibleProtocol> {
93 #else
94 @interface ChildView : UIView <UIKeyInput> {
95 #endif
96  @public
97   nsWindow* mGeckoChild;  // weak ref
98   BOOL mWaitingForPaint;
99   NSMapTable<UITouch*, NSNumber*>* mTouches;
100   int mNextTouchID;
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;
127 #ifdef ACCESSIBILITY
128 // MUIRootAccessible
129 - (BOOL)hasRepresentedView;
130 - (id)representedView;
132 // MUIAccessible
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;
143 #endif
145 @end
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;
156   }
157   ALOG("[ChildView[%p] initWithFrame:] (mGeckoChild = %p)", (void*)self,
158        (void*)mGeckoChild);
159   self.opaque = YES;
160   self.alpha = 1.0;
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];
169   mNextTouchID = 0;
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]
175       addObserver:self
176          selector:@selector(activateWindow:)
177              name:UIWindowDidBecomeKeyNotification
178            object:nil];
179   [[NSNotificationCenter defaultCenter]
180       addObserver:self
181          selector:@selector(deactivateWindow:)
182              name:UIWindowDidResignKeyNotification
183            object:nil];
185   return self;
188 - (void)widgetDestroyed {
189   mGeckoChild = nullptr;
190   [mTouches release];
193 - (void)delayedTearDown {
194   [self removeFromSuperview];
195   [self release];
198 - (void)activateWindow:(NSNotification*)notification {
199   ALOG("[[ChildView[%p] activateWindow]", (void*)self);
201   if (!mGeckoChild) {
202     return;
203   }
205   if (nsIWidgetListener* listener = mGeckoChild->GetWidgetListener()) {
206     listener->WindowActivated();
207   }
210 - (void)deactivateWindow:(NSNotification*)notification {
211   ALOG("[[ChildView[%p] deactivateWindow]", (void*)self);
213   if (!mGeckoChild) {
214     return;
215   }
217   if (nsIWidgetListener* listener = mGeckoChild->GetWidgetListener()) {
218     listener->WindowDeactivated();
219   }
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];
247   }
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];
264     if (value == nil) {
265       // This shouldn't happen.
266       NS_ASSERTION(false, "Got a touch that we didn't know about");
267       continue;
268     }
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);
273   }
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];
283     mNextTouchID++;
284   }
285   [self sendTouchEvent:eTouchStart
286                touches:[event allTouches]
287                 widget:mGeckoChild];
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];
295   }
296   if (mTouches.count == 0) {
297     mNextTouchID = 0;
298   }
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];
308   }
309   if (mTouches.count == 0) {
310     mNextTouchID = 0;
311   }
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]
320                 widget:mGeckoChild];
323 - (BOOL)canBecomeFirstResponder {
324   if (!mGeckoChild) {
325     return NO;
326   }
328   if (mGeckoChild->IsVirtualKeyboardDisabled()) {
329     return NO;
330   }
331   return YES;
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)
344                  withObject:nil
345                  afterDelay:0
346                     inModes:[NSArray arrayWithObject:NSRunLoopCommonModes]];
347     }
348   }
351 - (BOOL)isUsingMainThreadOpenGL {
352   if (!mGeckoChild || ![self window]) return NO;
354   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];
376   }
379 // The display system has told us that a portion of our view is dirty. Tell
380 // gecko to paint it
381 - (void)drawRect:(CGRect)aRect {
382   CGContextRef cgContext = UIGraphicsGetCurrentContext();
383   [self drawRect:aRect inContext:cgContext];
386 - (void)drawRect:(CGRect)aRect inContext:(CGContextRef)aContext {
387 #ifdef DEBUG_UPDATE
388   LayoutDeviceIntRect geckoBounds = mGeckoChild->GetBounds();
390   fprintf(stderr,
391           "---- Update[%p][%p] [%f %f %f %f] cgc: %p\n  gecko bounds: [%d %d "
392           "%d %d]\n",
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);
400 #endif
402   if (true) {
403     // For Gecko-initiated repaints in OpenGL mode, drawUsingOpenGL is
404     // directly called from a delayed perform callback - without going through
405     // drawRect.
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];
411     return;
412   }
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
442     // debugging.
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;
449       return;
450     }
451     targetContext = gfxContext::CreateOrNull(dt);
452   } else {
453     MOZ_ASSERT_UNREACHABLE("COREGRAPHICS is the only supported backend");
454   }
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));
462   }
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);
480   }
482 #ifdef DEBUG_UPDATE
483   fprintf(stderr, "---- update done ----\n");
485 #  if 0
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,
490                             0.5);
491 #  endif
492   CGContextSetRGBStrokeColor(aContext, 1, 0, 0, 0.8);
493   CGContextSetLineWidth(aContext, 4.0);
494   CGContextStrokeRect(aContext, aRect);
495 #endif
498 // UIKeyInput
500 - (void)insertText:(NSString*)text {
501   if (!mGeckoChild || mGeckoChild->Destroyed()) {
502     return;
503   }
504   widget::TextInputHandler* textInputHandler =
505       mGeckoChild->GetTextInputHandler();
506   if (!textInputHandler) {
507     return;
508   }
509   textInputHandler->InsertText(text);
512 - (void)deleteBackward {
513   if (!mGeckoChild || mGeckoChild->Destroyed()) {
514     return;
515   }
516   widget::TextInputHandler* textInputHandler =
517       mGeckoChild->GetTextInputHandler();
518   if (!textInputHandler) {
519     return;
520   }
521   textInputHandler->HandleCommand(Command::DeleteCharBackward);
524 - (BOOL)hasText {
525   if (!mGeckoChild || mGeckoChild->Destroyed()) {
526     return NO;
527   }
528   widget::InputContext context = mGeckoChild->GetInputContext();
529   if (context.mIMEState.mEnabled == IMEEnabled::Disabled) {
530     return NO;
531   }
532   return YES;
535 // UITextInputTraits
537 - (UIKeyboardType)keyboardType {
538   if (!mGeckoChild || mGeckoChild->Destroyed()) {
539     return UIKeyboardTypeDefault;
540   }
541   return UIKitUtils::GetUIKeyboardType(mGeckoChild->GetInputContext());
544 - (UIReturnKeyType)returnKeyType {
545   if (!mGeckoChild || mGeckoChild->Destroyed()) {
546     return UIReturnKeyDefault;
547   }
548   return UIKitUtils::GetUIReturnKeyType(mGeckoChild->GetInputContext());
551 - (UITextAutocapitalizationType)autocapitalizationType {
552   if (!mGeckoChild || mGeckoChild->Destroyed()) {
553     return UITextAutocapitalizationTypeNone;
554   }
555   return UIKitUtils::GetUITextAutocapitalizationType(
556       mGeckoChild->GetInputContext());
559 - (UITextAutocorrectionType)autocorrectionType {
560   if (!mGeckoChild || mGeckoChild->Destroyed()) {
561     return UITextAutocorrectionTypeDefault;
562   }
564   return UIKitUtils::GetUITextAutocorrectionType(
565       mGeckoChild->GetInputContext());
568 - (BOOL)isSecureTextEntry {
569   if (!mGeckoChild || mGeckoChild->Destroyed()) {
570     return NO;
571   }
572   if (mGeckoChild->GetInputContext().IsPasswordEditor()) {
573     return YES;
574   }
575   return NO;
578 #ifdef ACCESSIBILITY
579 // MUIRootAccessible
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 {
597   return YES;
600 - (id)representedView {
601   return self;
604 - (BOOL)isAccessibilityElement {
605   if (!mozilla::a11y::ShouldA11yBeEnabled()) {
606     return [super isAccessibilityElement];
607   }
609   return [[self accessible] isAccessibilityElement];
612 - (NSString*)accessibilityLabel {
613   if (!mozilla::a11y::ShouldA11yBeEnabled()) {
614     return [super accessibilityLabel];
615   }
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];
629   }
631   return [[self accessible] accessibilityValue];
634 - (uint64_t)accessibilityTraits {
635   if (!mozilla::a11y::ShouldA11yBeEnabled()) {
636     return [super accessibilityTraits];
637   }
639   return [[self accessible] accessibilityTraits];
642 - (NSInteger)accessibilityElementCount {
643   if (!mozilla::a11y::ShouldA11yBeEnabled()) {
644     return [super accessibilityElementCount];
645   }
647   return [[self accessible] accessibilityElementCount];
650 - (nullable id)accessibilityElementAtIndex:(NSInteger)index {
651   if (!mozilla::a11y::ShouldA11yBeEnabled()) {
652     return [super accessibilityElementAtIndex:index];
653   }
655   return [[self accessible] accessibilityElementAtIndex:index];
658 - (NSInteger)indexOfAccessibilityElement:(id)element {
659   if (!mozilla::a11y::ShouldA11yBeEnabled()) {
660     return [super indexOfAccessibilityElement:element];
661   }
663   return [[self accessible] indexOfAccessibilityElement:element];
666 - (NSArray* _Nullable)accessibilityElements {
667   if (!mozilla::a11y::ShouldA11yBeEnabled()) {
668     return [super accessibilityElements];
669   }
671   return [[self accessible] accessibilityElements];
674 - (UIAccessibilityContainerType)accessibilityContainerType {
675   if (!mozilla::a11y::ShouldA11yBeEnabled()) {
676     return [super accessibilityContainerType];
677   }
679   return [[self accessible] accessibilityContainerType];
681 #endif
683 @end
685 NS_IMPL_ISUPPORTS_INHERITED(nsWindow, nsBaseWidget, nsWindow);
687 nsWindow::nsWindow()
688     : mNativeView(nullptr),
689       mVisible(false),
690       mSizeMode(nsSizeMode_Normal),
691       mParent(nullptr) {}
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)
702                                 withObject:nil
703                              waitUntilDone:false];
704   mNativeView = nil;
707 bool nsWindow::IsTopLevel() {
708   return mWindowType == WindowType::TopLevel ||
709          mWindowType == WindowType::Dialog ||
710          mWindowType == WindowType::Invisible;
714 // nsIWidget
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;
723   mBounds = aRect;
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())
739          geckoChild:this];
740   mNativeView.hidden = YES;
742   if (parent) {
743     parent->mChildren.AppendElement(this);
744     mParent = parent;
745   }
747   if (parent && parent->mNativeView) {
748     [parent->mNativeView addSubview:mNativeView];
749   } else if (nsAppShell::gWindow) {
750     [nsAppShell::gWindow.rootViewController.view addSubview:mNativeView];
751   } else {
752     [nsAppShell::gTopLevelViews addObject:mNativeView];
753   }
755   mTextInputHandler = new widget::TextInputHandler(this);
757   return NS_OK;
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();
764   }
766   if (mParent) mParent->mChildren.RemoveElement(this);
768   if (mTextInputHandler) {
769     mTextInputHandler->OnDestroyed();
770   }
771   mTextInputHandler = nullptr;
773   [mNativeView widgetDestroyed];
775   nsCOMPtr<nsIWidget> kungFuDeathGrip(this);
777   nsBaseWidget::Destroy();
779   // ReportDestroyEvent();
781   TearDownView();
783   nsBaseWidget::OnDestroy();
786 void nsWindow::Show(bool aState) {
787   if (aState != mVisible) {
788     mNativeView.hidden = aState ? NO : YES;
789     if (aState) {
790       UIView* parentView = mParent
791                                ? mParent->mNativeView
792                                : nsAppShell::gWindow.rootViewController.view;
793       [parentView bringSubviewToFront:mNativeView];
794       [mNativeView setNeedsDisplay];
795     }
796     mVisible = aState;
797   }
800 void nsWindow::Move(double aX, double aY) {
801   if (!mNativeView || (mBounds.x == aX && mBounds.y == aY)) return;
803   // XXX: handle this
804   // The point we have is in Gecko coordinates (origin top-left). Convert
805   // it to Cocoa ones (origin bottom-left).
806   mBounds.x = aX;
807   mBounds.y = aY;
809   mNativeView.frame = DevPixelsToUIKitPoints(mBounds, BackingScaleFactor());
811   if (mVisible) [mNativeView setNeedsDisplay];
813   ReportMoveEvent();
816 void nsWindow::Resize(double aX, double aY, double aWidth, double aHeight,
817                       bool aRepaint) {
818   BOOL isMoving = (mBounds.x != aX || mBounds.y != aY);
819   BOOL isResizing = (mBounds.width != aWidth || mBounds.height != aHeight);
820   if (!mNativeView || (!isMoving && !isResizing)) return;
822   if (isMoving) {
823     mBounds.x = aX;
824     mBounds.y = aY;
825   }
826   if (isResizing) {
827     mBounds.width = aWidth;
828     mBounds.height = aHeight;
829   }
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))
842     return;
844   mBounds.width = aWidth;
845   mBounds.height = aHeight;
847   [mNativeView setFrame:DevPixelsToUIKitPoints(mBounds, BackingScaleFactor())];
849   if (mVisible && aRepaint) [mNativeView setNeedsDisplay];
851   ReportSizeEvent();
854 void nsWindow::SetSizeMode(nsSizeMode aMode) {
855   if (aMode == static_cast<int32_t>(mSizeMode)) {
856     return;
857   }
859   mSizeMode = static_cast<nsSizeMode>(aMode);
860   if (aMode == nsSizeMode_Maximized || aMode == nsSizeMode_Fullscreen) {
861     // Resize to fill screen
862     nsBaseWidget::InfallibleMakeFullScreen(true);
863   }
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);
883   }
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();
894   }
896   return returnValue;
899 void nsWindow::ReportMoveEvent() { NotifyWindowMoved(mBounds.x, mBounds.y); }
901 void nsWindow::ReportSizeModeEvent(nsSizeMode aMode) {
902   if (mWidgetListener) {
903     // This is terrible.
904     nsSizeMode theMode;
905     switch (aMode) {
906       case nsSizeMode_Maximized:
907         theMode = nsSizeMode_Maximized;
908         break;
909       case nsSizeMode_Fullscreen:
910         theMode = nsSizeMode_Fullscreen;
911         break;
912       default:
913         return;
914     }
915     mWidgetListener->SizeModeChanged(theMode);
916   }
919 void nsWindow::ReportSizeEvent() {
920   LayoutDeviceIntRect innerBounds = GetClientBounds();
922   if (mWidgetListener) {
923     mWidgetListener->WindowResized(this, innerBounds.width, innerBounds.height);
924   }
926   if (mAttachedWidgetListener) {
927     mAttachedWidgetListener->WindowResized(this, innerBounds.width,
928                                            innerBounds.height);
929   }
932 LayoutDeviceIntRect nsWindow::GetScreenBounds() {
933   return LayoutDeviceIntRect(WidgetToScreenOffset(), mBounds.Size());
936 LayoutDeviceIntPoint nsWindow::WidgetToScreenOffset() {
937   LayoutDeviceIntPoint offset(0, 0);
938   if (mParent) {
939     offset = mParent->WidgetToScreenOffset();
940   }
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];
947   }
949   offset.x += static_cast<int32_t>(temp.x);
950   offset.y += static_cast<int32_t>(temp.y);
952   return offset;
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);
965   }
967   return NS_OK;
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];
981     return;
982   }
984   [mNativeView becomeFirstResponder];
986   if (aAction.UserMightRequestOpenVKB() || changingEnabledState) {
987     // TODO(m_kato):
988     // It is unnecessary to call reloadInputViews with changingEnabledState if
989     // virtual keyboard is disappeared.
990     [mNativeView reloadInputViews];
991   }
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;
1001     return context;
1002   }
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;
1029       break;
1031     case NS_NATIVE_WINDOW:
1032       retVal = [mNativeView window];
1033       break;
1035     case NS_NATIVE_GRAPHIC:
1036       NS_ERROR("Requesting NS_NATIVE_GRAPHIC on a UIKit child view!");
1037       break;
1039     case NS_NATIVE_OFFSETX:
1040       retVal = 0;
1041       break;
1043     case NS_NATIVE_OFFSETY:
1044       retVal = 0;
1045       break;
1047     case NS_RAW_NATIVE_IME_CONTEXT:
1048       retVal = GetPseudoIMEContext();
1049       if (retVal) {
1050         break;
1051       }
1052       retVal = NS_ONLY_ONE_NATIVE_IME_CONTEXT;
1053       break;
1054   }
1056   return retVal;
1059 CGFloat nsWindow::BackingScaleFactor() {
1060   if (mNativeView) {
1061     return [mNativeView contentScaleFactor];
1062   }
1063   return [UIScreen mainScreen].scale;
1066 int32_t nsWindow::RoundsWidgetCoordinatesTo() {
1067   if (BackingScaleFactor() == 2.0) {
1068     return 2;
1069   }
1070   return 1;
1073 EventDispatcher* nsWindow::GetEventDispatcher() const {
1074   if (mIOSView) {
1075     return mIOSView->mEventDispatcher;
1076   }
1077   return nullptr;
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();
1090 /* static */
1091 already_AddRefed<nsWindow> nsWindow::From(nsPIDOMWindowOuter* aDOMWindow) {
1092   nsCOMPtr<nsIWidget> widget = WidgetUtils::DOMWindowToWidget(aDOMWindow);
1093   return From(widget);
1096 /* static */
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> {
1112  @public
1113   RefPtr<nsWindow> mWindow;
1114   nsCOMPtr<nsPIDOMWindowOuter> mOuterWindow;
1117 @end
1119 @implementation GeckoViewWindowImpl
1120 - (UIView*)view {
1121   return mWindow ? (UIView*)mWindow->GetNativeData(NS_NATIVE_WIDGET) : nil;
1124 - (void)close {
1125   if (mWindow) {
1126     if (IOSView* iosView = mWindow->GetIOSView()) {
1127       iosView->mEventDispatcher->Detach();
1128     }
1129     mWindow = nullptr;
1130   }
1132   if (mOuterWindow) {
1133     mOuterWindow->ForceClose();
1134     mOuterWindow = nullptr;
1135   }
1137 @end
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);
1149   nsAutoCString url;
1150   nsresult rv = Preferences::GetCString("toolkit.defaultChromeURI", url);
1151   if (NS_FAILED(rv)) {
1152     url = "chrome://geckoview/content/geckoview.xhtml"_ns;
1153   }
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");
1161   if (aPrivateMode) {
1162     chromeFlags += ",private";
1163   }
1165   nsCOMPtr<mozIDOMWindowProxy> domWindow;
1166   ww->OpenWindow(
1167       nullptr, url,
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);
1175   MOZ_ASSERT(window);
1177   window->SetIOSView(iosView.forget());
1179   if (nsIWidgetListener* widgetListener = window->GetWidgetListener()) {
1180     nsCOMPtr<nsIAppWindow> appWindow(widgetListener->GetAppWindow());
1181     if (appWindow) {
1182       // Our window is not intrinsically sized, so tell AppWindow to
1183       // not set a size for us.
1184       appWindow->SetIntrinsicallySized(false);
1185     }
1186   }
1188   GeckoViewWindowImpl* gvWindow = [[GeckoViewWindowImpl alloc] init];
1189   gvWindow->mOuterWindow = pdomWindow;
1190   gvWindow->mWindow = window;
1191   return [gvWindow autorelease];