Bug 1649575 - Part 3: Introduce MOXTextMarkerDelegate. r=morgan
authorEitan Isaacson <eitan@monotonous.org>
Thu, 02 Jul 2020 21:33:07 +0000
changeset 538528 0441d1553b6bbf99d711300680eccd335e0b3545
parent 538527 65f8e705d7307b89e05e9f2e3ff199f5cc691148
child 538529 9bc12053de5e6b071d41c81d82859d1567c80c42
push id37564
push usernbeleuzu@mozilla.com
push dateFri, 03 Jul 2020 03:56:55 +0000
treeherdermozilla-central@b48777a21aab [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmorgan
bugs1649575
milestone80.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1649575 - Part 3: Introduce MOXTextMarkerDelegate. r=morgan The delegate is associated with accessible documents (either local or remote). I made a separate protocol for all the text marker stuff as it really is a seperate API. Differential Revision: https://phabricator.services.mozilla.com/D81759
accessible/mac/DocAccessibleWrap.h
accessible/mac/DocAccessibleWrap.mm
accessible/mac/MOXAccessibleBase.h
accessible/mac/MOXAccessibleBase.mm
accessible/mac/MOXAccessibleProtocol.h
accessible/mac/MOXTextMarkerDelegate.h
accessible/mac/MOXTextMarkerDelegate.mm
accessible/mac/Platform.mm
accessible/mac/moz.build
accessible/mac/mozAccessible.h
accessible/mac/mozAccessible.mm
--- a/accessible/mac/DocAccessibleWrap.h
+++ b/accessible/mac/DocAccessibleWrap.h
@@ -1,9 +1,11 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* clang-format off */
+/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* clang-format on */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_a11y_DocAccessibleWrap_h__
 #define mozilla_a11y_DocAccessibleWrap_h__
 
 #include "DocAccessible.h"
@@ -12,15 +14,18 @@ namespace mozilla {
 
 class PresShell;
 
 namespace a11y {
 
 class DocAccessibleWrap : public DocAccessible {
  public:
   DocAccessibleWrap(dom::Document* aDocument, PresShell* aPresShell);
+
+  virtual void Shutdown() override;
+
   virtual ~DocAccessibleWrap();
 };
 
 }  // namespace a11y
 }  // namespace mozilla
 
 #endif
--- a/accessible/mac/DocAccessibleWrap.mm
+++ b/accessible/mac/DocAccessibleWrap.mm
@@ -1,16 +1,22 @@
-/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "DocAccessibleWrap.h"
 
 #import "mozAccessible.h"
+#import "MOXTextMarkerDelegate.h"
 
 using namespace mozilla;
 using namespace mozilla::a11y;
 
 DocAccessibleWrap::DocAccessibleWrap(dom::Document* aDocument, PresShell* aPresShell)
     : DocAccessible(aDocument, aPresShell) {}
 
+void DocAccessibleWrap::Shutdown() {
+  [MOXTextMarkerDelegate destroyForDoc:this];
+  DocAccessible::Shutdown();
+}
+
 DocAccessibleWrap::~DocAccessibleWrap() {}
--- a/accessible/mac/MOXAccessibleBase.h
+++ b/accessible/mac/MOXAccessibleBase.h
@@ -90,16 +90,19 @@ inline id<mozAccessible> GetObjectOrRepr
 - (id)moxFocusedUIElement;
 
 // override
 - (void)moxPostNotification:(NSString*)notification;
 
 // override
 - (BOOL)moxBlockSelector:(SEL)selector;
 
+// override
+- (id<MOXTextMarkerSupport>)moxTextMarkerDelegate;
+
 #pragma mark -
 
 - (BOOL)isExpired;
 
 // makes ourselves "expired". after this point, we might be around if someone
 // has retained us (e.g., a third-party), but we really contain no information.
 - (void)expire;
 
--- a/accessible/mac/MOXAccessibleBase.mm
+++ b/accessible/mac/MOXAccessibleBase.mm
@@ -85,16 +85,21 @@ using namespace mozilla::a11y;
     // advertise the attribute name.
     for (NSString* attribute in getters) {
       SEL selector = NSSelectorFromString(getters[attribute]);
       if ([self isSelectorSupported:selector]) {
         [attributes addObject:attribute];
       }
     }
 
+    // If we have a delegate add all the text marker attributes.
+    if ([self moxTextMarkerDelegate]) {
+      [attributes addObjectsFromArray:[mac::TextAttributeGetters() allKeys]];
+    }
+
     // We store a hash table with types as keys, and atttribute lists as values.
     // This lets us cache the atttribute list of each subclass so we only
     // need to gather its MOXAccessible methods once.
     // XXX: Uncomment when accessibilityAttributeNames is removed from all subclasses.
     // attributesForEachClass[[self class]] = attributes;
   }
 
   return attributes;
@@ -110,16 +115,27 @@ using namespace mozilla::a11y;
 
   id value = nil;
   NSDictionary* getters = mac::AttributeGetters();
   if (getters[attribute]) {
     SEL selector = NSSelectorFromString(getters[attribute]);
     if ([self isSelectorSupported:selector]) {
       value = [self performSelector:selector];
     }
+  } else if (id textMarkerDelegate = [self moxTextMarkerDelegate]) {
+    // If we have a delegate, check if attribute is a text marker
+    // attribute and call the associated selector on the delegate
+    // if so.
+    NSDictionary* textMarkerGetters = mac::TextAttributeGetters();
+    if (textMarkerGetters[attribute]) {
+      SEL selector = NSSelectorFromString(textMarkerGetters[attribute]);
+      if ([textMarkerDelegate respondsToSelector:selector]) {
+        value = [textMarkerDelegate performSelector:selector];
+      }
+    }
   }
 
   if ([value isMozAccessible]) {
     // If this is a mozAccessible, get its represented view and filter it if
     // it should be ignored.
     value = GetObjectOrRepresentedView(value);
     return [value isAccessibilityElement] ? value : nil;
   }
@@ -238,16 +254,21 @@ using namespace mozilla::a11y;
   NSDictionary* attributes = mac::ParameterizedAttributeGetters();
   for (NSString* attribute in attributes) {
     SEL selector = NSSelectorFromString(attributes[attribute]);
     if ([self isSelectorSupported:selector]) {
       [attributeNames addObject:attribute];
     }
   }
 
+  // If we have a delegate add all the text marker attributes.
+  if ([self moxTextMarkerDelegate]) {
+    [attributeNames addObjectsFromArray:[mac::ParameterizedTextAttributeGetters() allKeys]];
+  }
+
   return attributeNames;
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
 }
 
 - (id)accessibilityAttributeValue:(NSString*)attribute forParameter:(id)parameter {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
 
@@ -256,16 +277,27 @@ using namespace mozilla::a11y;
   }
 
   NSDictionary* getters = mac::ParameterizedAttributeGetters();
   if (getters[attribute]) {
     SEL selector = NSSelectorFromString(getters[attribute]);
     if ([self isSelectorSupported:selector]) {
       return [self performSelector:selector withObject:parameter];
     }
+  } else if (id textMarkerDelegate = [self moxTextMarkerDelegate]) {
+    // If we have a delegate, check if attribute is a text marker
+    // attribute and call the associated selector on the delegate
+    // if so.
+    NSDictionary* textMarkerGetters = mac::ParameterizedTextAttributeGetters();
+    if (textMarkerGetters[attribute]) {
+      SEL selector = NSSelectorFromString(textMarkerGetters[attribute]);
+      if ([textMarkerDelegate respondsToSelector:selector]) {
+        return [textMarkerDelegate performSelector:selector withObject:parameter];
+      }
+    }
   }
 
   return nil;
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
 }
 
 - (id)accessibilityHitTest:(NSPoint)point {
@@ -315,16 +347,20 @@ using namespace mozilla::a11y;
 
   NSAccessibilityPostNotification(GetObjectOrRepresentedView(self), notification);
 }
 
 - (BOOL)moxBlockSelector:(SEL)selector {
   return NO;
 }
 
+- (id<MOXTextMarkerSupport>)moxTextMarkerDelegate {
+  return nil;
+}
+
 #pragma mark -
 
 - (BOOL)isExpired {
   return mIsExpired;
 }
 
 - (void)expire {
   MOZ_ASSERT(!mIsExpired, "expire called an expired mozAccessible!");
--- a/accessible/mac/MOXAccessibleProtocol.h
+++ b/accessible/mac/MOXAccessibleProtocol.h
@@ -1,13 +1,15 @@
 /* -*- Mode: Objective-C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+@protocol MOXTextMarkerSupport;
+
 // This protocol's primary use is for abstracting the NSAccessibility informal protocol
 // into a formal internal API. Conforming classes get to choose a subset of the optional
 // methods to implement. Those methods will be mapped to NSAccessibility attributes or actions.
 // A conforming class can implement moxBlockSelector to control which of its implemented
 // methods should be exposed to NSAccessibility.
 
 @protocol MOXAccessible
 
@@ -24,16 +26,19 @@
 
 // Return YES if selector should be considered not supported.
 // Used in implementations such as:
 // - accessibilityAttributeNames
 // - accessibilityActionNames
 // - accessibilityIsAttributeSettable
 - (BOOL)moxBlockSelector:(SEL _Nonnull)selector;
 
+// Return text delegate if it exists.
+- (id<MOXTextMarkerSupport> _Nullable)moxTextMarkerDelegate;
+
 @optional
 
 #pragma mark - AttributeGetters
 
 // AXChildren
 - (NSArray* _Nullable)moxChildren;
 
 // AXParent
@@ -292,8 +297,33 @@
 
 // AXStyleRangeForIndex
 - (NSValue* _Nullable)moxStyleRangeForIndex:(NSNumber* _Nonnull)index;
 
 // AttributedStringForRange
 - (NSAttributedString* _Nullable)moxAttributedStringForRange:(NSValue* _Nonnull)range;
 
 @end
+
+// This protocol maps text marker and text marker range parameters to
+// methods. It is implemented by a delegate of a MOXAccessible.
+@protocol MOXTextMarkerSupport
+
+#pragma mark - TextAttributeGetters
+
+// AXStartTextMarker
+- (id _Nullable)moxStartTextMarker;
+
+// AXEndTextMarker
+- (id _Nullable)moxEndTextMarker;
+
+#pragma mark - ParameterizedTextAttributeGetters
+
+// AXLengthForTextMarkerRange
+- (NSNumber* _Nullable)moxLengthForTextMarkerRange:(id _Nonnull)textMarkerRange;
+
+// AXStringForTextMarkerRange
+- (NSString* _Nullable)moxStringForTextMarkerRange:(id _Nonnull)textMarkerRange;
+
+// AXTextMarkerRangeForUnorderedTextMarkers
+- (id _Nullable)moxTextMarkerRangeForUnorderedTextMarkers:(NSArray* _Nonnull)textMarkers;
+
+@end
new file mode 100644
--- /dev/null
+++ b/accessible/mac/MOXTextMarkerDelegate.h
@@ -0,0 +1,37 @@
+/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#import <Cocoa/Cocoa.h>
+
+#import "MOXAccessibleProtocol.h"
+
+#include "AccessibleOrProxy.h"
+
+@interface MOXTextMarkerDelegate : NSObject <MOXTextMarkerSupport> {
+  mozilla::a11y::AccessibleOrProxy mGeckoDocAccessible;
+}
+
++ (id)getOrCreateForDoc:(mozilla::a11y::AccessibleOrProxy)aDoc;
+
++ (void)destroyForDoc:(mozilla::a11y::AccessibleOrProxy)aDoc;
+
+- (id)initWithDoc:(mozilla::a11y::AccessibleOrProxy)aDoc;
+
+// override
+- (id)moxStartTextMarker;
+
+// override
+- (id)moxEndTextMarker;
+
+// override
+- (NSNumber*)moxLengthForTextMarkerRange:(id)textMarkerRange;
+
+// override
+- (NSString*)moxStringForTextMarkerRange:(id)textMarkerRange;
+
+// override
+- (id)moxTextMarkerRangeForUnorderedTextMarkers:(NSArray*)textMarkers;
+
+@end
new file mode 100644
--- /dev/null
+++ b/accessible/mac/MOXTextMarkerDelegate.mm
@@ -0,0 +1,68 @@
+/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#import <Cocoa/Cocoa.h>
+
+#import "MOXTextMarkerDelegate.h"
+
+using namespace mozilla::a11y;
+
+static nsDataHashtable<nsUint64HashKey, MOXTextMarkerDelegate*> sDelegates;
+
+@implementation MOXTextMarkerDelegate
+
++ (id)getOrCreateForDoc:(mozilla::a11y::AccessibleOrProxy)aDoc {
+  MOZ_ASSERT(!aDoc.IsNull());
+
+  MOXTextMarkerDelegate* delegate = sDelegates.Get(aDoc.Bits());
+  if (!delegate) {
+    delegate = [[MOXTextMarkerDelegate alloc] initWithDoc:aDoc];
+    sDelegates.Put(aDoc.Bits(), delegate);
+    [delegate retain];
+  }
+
+  return delegate;
+}
+
++ (void)destroyForDoc:(mozilla::a11y::AccessibleOrProxy)aDoc {
+  MOZ_ASSERT(!aDoc.IsNull());
+
+  MOXTextMarkerDelegate* delegate = sDelegates.Get(aDoc.Bits());
+  if (delegate) {
+    sDelegates.Remove(aDoc.Bits());
+    [delegate release];
+  }
+}
+
+- (id)initWithDoc:(AccessibleOrProxy)aDoc {
+  MOZ_ASSERT(!aDoc.IsNull(), "Cannot init MOXTextDelegate with null");
+  if ((self = [super init])) {
+    mGeckoDocAccessible = aDoc;
+  }
+
+  return self;
+}
+
+- (id)moxStartTextMarker {
+  return nil;
+}
+
+- (id)moxEndTextMarker {
+  return nil;
+}
+
+- (NSString*)moxStringForTextMarkerRange:(id)textMarkerRange {
+  return nil;
+}
+
+- (NSNumber*)moxLengthForTextMarkerRange:(id)textMarkerRange {
+  return nil;
+}
+
+- (id)moxTextMarkerRangeForUnorderedTextMarkers:(NSArray*)textMarkers {
+  return nil;
+}
+
+@end
--- a/accessible/mac/Platform.mm
+++ b/accessible/mac/Platform.mm
@@ -1,16 +1,18 @@
 /* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #import <Cocoa/Cocoa.h>
 
+#import "MOXTextMarkerDelegate.h"
+
 #include "Platform.h"
 #include "ProxyAccessible.h"
 #include "AccessibleOrProxy.h"
 #include "DocAccessibleParent.h"
 #include "mozTableAccessible.h"
 
 #include "nsAppShell.h"
 
@@ -58,16 +60,20 @@ void ProxyCreated(ProxyAccessible* aProx
   aProxy->SetWrapper(reinterpret_cast<uintptr_t>(mozWrapper));
 }
 
 void ProxyDestroyed(ProxyAccessible* aProxy) {
   mozAccessible* wrapper = GetNativeFromGeckoAccessible(aProxy);
   [wrapper expire];
   [wrapper release];
   aProxy->SetWrapper(0);
+
+  if (aProxy->IsDoc()) {
+    [MOXTextMarkerDelegate destroyForDoc:aProxy];
+  }
 }
 
 void ProxyEvent(ProxyAccessible* aProxy, uint32_t aEventType) {
   // ignore everything but focus-changed, value-changed, caret,
   // selection, and document load complete events for now.
   NSLog(@"Event type is %u", aEventType);
   if (aEventType != nsIAccessibleEvent::EVENT_FOCUS &&
       aEventType != nsIAccessibleEvent::EVENT_VALUE_CHANGE &&
--- a/accessible/mac/moz.build
+++ b/accessible/mac/moz.build
@@ -14,16 +14,17 @@ EXPORTS.mozilla.a11y += [
 ]
 
 UNIFIED_SOURCES += [
     'AccessibleWrap.mm',
     'DocAccessibleWrap.mm',
     'MacUtils.mm',
     'MOXAccessibleBase.mm',
     'MOXMathAccessibles.mm',
+    'MOXTextMarkerDelegate.mm',
     'MOXWebAreaAccessible.mm',
     'mozAccessible.mm',
     'mozActionElements.mm',
     'mozHTMLAccessible.mm',
     'mozRootAccessible.mm',
     'mozSelectableElements.mm',
     'mozTableAccessible.mm',
     'mozTextAccessible.mm',
--- a/accessible/mac/mozAccessible.h
+++ b/accessible/mac/mozAccessible.h
@@ -117,16 +117,18 @@ inline mozAccessible* GetNativeFromGecko
 - (BOOL)moxBlockSelector:(SEL)selector;
 
 // override
 - (id)moxHitTest:(NSPoint)point;
 
 // override
 - (id)moxFocusedUIElement;
 
+- (id<MOXTextMarkerSupport>)moxTextMarkerDelegate;
+
 // Attribute getters
 
 // override
 - (id<mozAccessible>)moxParent;
 
 // override
 - (NSArray*)moxChildren;
 
--- a/accessible/mac/mozAccessible.mm
+++ b/accessible/mac/mozAccessible.mm
@@ -235,16 +235,26 @@ static const uint64_t kCacheInitialized 
   if ([focusedChild isAccessibilityElement]) {
     return focusedChild;
   }
 
   // return ourself if we can't get a native focused child.
   return self;
 }
 
+- (id<MOXTextMarkerSupport>)moxTextMarkerDelegate {
+  MOZ_ASSERT(!mGeckoAccessible.IsNull());
+
+  if (mGeckoAccessible.IsAccessible()) {
+    return [MOXTextMarkerDelegate getOrCreateForDoc:mGeckoAccessible.AsAccessible()->Document()];
+  }
+
+  return [MOXTextMarkerDelegate getOrCreateForDoc:mGeckoAccessible.AsProxy()->Document()];
+}
+
 - (id)moxHitTest:(NSPoint)point {
   MOZ_ASSERT(!mGeckoAccessible.IsNull());
 
   // Convert the given screen-global point in the cocoa coordinate system (with
   // origin in the bottom-left corner of the screen) into point in the Gecko
   // coordinate system (with origin in a top-left screen point).
   NSScreen* mainView = [[NSScreen screens] objectAtIndex:0];
   NSPoint tmpPoint = NSMakePoint(point.x, [mainView frame].size.height - point.y);