2 * Copyright (C) 2004, 2005 Apple Computer, Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
13 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 #import "WebCoreAXObject.h"
29 #import "DOMInternal.h"
31 #import "EventNames.h"
33 #import "FrameLoader.h"
36 #import "HTMLAreaElement.h"
37 #import "HTMLCollection.h"
38 #import "HTMLFrameElementBase.h"
39 #import "HTMLInputElement.h"
40 #import "HTMLLabelElement.h"
41 #import "HTMLMapElement.h"
43 #import "HTMLSelectElement.h"
44 #import "HTMLTextAreaElement.h"
45 #import "HitTestRequest.h"
46 #import "HitTestResult.h"
47 #import "RenderImage.h"
48 #import "RenderListMarker.h"
49 #import "RenderMenuList.h"
50 #import "RenderTextControl.h"
51 #import "RenderTheme.h"
52 #import "RenderView.h"
53 #import "RenderWidget.h"
54 #import "SelectionController.h"
55 #import "TextIterator.h"
56 #import "WebCoreFrameBridge.h"
57 #import "WebCoreFrameView.h"
58 #import "WebCoreViewFactory.h"
59 #import "htmlediting.h"
61 #import "visible_units.h"
62 #include <mach-o/dyld.h>
64 using namespace WebCore;
65 using namespace EventNames;
66 using namespace HTMLNames;
68 // FIXME: This will eventually need to really localize.
69 #define UI_STRING(string, comment) ((NSString*)[NSString stringWithUTF8String:(string)])
71 @interface WebCoreAXObject (PrivateWebCoreAXObject)
72 // forward declarations as needed
73 - (WebCoreTextMarker*)textMarkerForIndex: (NSNumber*) index lastIndexOK: (BOOL)lastIndexOK;
74 - (id)doAXLineForTextMarker: (WebCoreTextMarker* ) textMarker;
77 @implementation WebCoreAXObject
79 -(id)initWithRenderer:(RenderObject*)renderer
82 m_renderer = renderer;
93 // Send unregisterUniqueIdForUIElement unconditionally because if it is
94 // ever accidently not done (via other bugs in our AX implementation) you
95 // end up with a crash like <rdar://problem/4273149>. It is safe and not
96 // expensive to send even if the object is not registered.
97 [[WebCoreViewFactory sharedFactory] unregisterUniqueIdForUIElement:self];
100 [self removeAXObjectID];
102 [self clearChildren];
116 -(void)setData:(id)data
126 -(HTMLAnchorElement*)anchorElement
128 // return already-known anchor for image areas
130 return m_areaElement;
132 // search up the render tree for a RenderObject with a DOM node. Defer to an earlier continuation, though.
133 RenderObject* currRenderer;
134 for (currRenderer = m_renderer; currRenderer && !currRenderer->element(); currRenderer = currRenderer->parent()) {
135 if (currRenderer->continuation())
136 return [currRenderer->document()->axObjectCache()->get(currRenderer->continuation()) anchorElement];
139 // bail of none found
143 // search up the DOM tree for an anchor element
144 // NOTE: this assumes that any non-image with an anchor is an HTMLAnchorElement
145 Node* elt = currRenderer->element();
146 for ( ; elt; elt = elt->parentNode()) {
147 if (elt->isLink() && elt->renderer() && !elt->renderer()->isImage())
148 return static_cast<HTMLAnchorElement*>(elt);
156 return m_renderer->isImage() && m_renderer->element() && m_renderer->element()->hasTagName(inputTag);
159 -(Element*)mouseButtonListener
161 // FIXME: Do the continuation search like anchorElement does
162 for (EventTargetNode* elt = static_cast<EventTargetNode*>(m_renderer->element()); elt; elt = static_cast<EventTargetNode*>(elt->parentNode())) {
163 if (elt->getHTMLEventListener(clickEvent) || elt->getHTMLEventListener(mousedownEvent) || elt->getHTMLEventListener(mouseupEvent))
164 return static_cast<Element*>(elt);
170 -(Element*)actionElement
172 if (m_renderer->element() && m_renderer->element()->hasTagName(inputTag)) {
173 HTMLInputElement* input = static_cast<HTMLInputElement*>(m_renderer->element());
174 if (!input->disabled() && (input->inputType() == HTMLInputElement::CHECKBOX ||
175 input->inputType() == HTMLInputElement::RADIO ||
176 input->isTextButton()))
180 if ([self isImageButton] || m_renderer->isMenuList())
181 return static_cast<Element*>(m_renderer->element());
183 Element* elt = [self anchorElement];
185 elt = [self mouseButtonListener];
190 -(WebCoreAXObject*)firstChild
192 if (!m_renderer || !m_renderer->firstChild())
195 return m_renderer->document()->axObjectCache()->get(m_renderer->firstChild());
198 -(WebCoreAXObject*)lastChild
200 if (!m_renderer || !m_renderer->lastChild())
203 return m_renderer->document()->axObjectCache()->get(m_renderer->lastChild());
206 -(WebCoreAXObject*)previousSibling
208 if (!m_renderer || !m_renderer->previousSibling())
211 return m_renderer->document()->axObjectCache()->get(m_renderer->previousSibling());
214 -(WebCoreAXObject*)nextSibling
216 if (!m_renderer || !m_renderer->nextSibling())
219 return m_renderer->document()->axObjectCache()->get(m_renderer->nextSibling());
222 -(WebCoreAXObject*)parentObject
225 return m_renderer->document()->axObjectCache()->get(m_renderer);
227 if (!m_renderer || !m_renderer->parent())
230 return m_renderer->document()->axObjectCache()->get(m_renderer->parent());
233 -(WebCoreAXObject*)parentObjectUnignored
235 WebCoreAXObject* obj = [self parentObject];
236 if ([obj accessibilityIsIgnored])
237 return [obj parentObjectUnignored];
242 -(void)addChildrenToArray:(NSMutableArray*)array
244 // nothing to add if there is no RenderObject
248 // try to add RenderWidget's children, but fall thru if there are none
249 if (m_renderer->isWidget()) {
250 RenderWidget* renderWidget = static_cast<RenderWidget*>(m_renderer);
251 Widget* widget = renderWidget->widget();
253 NSArray* childArr = [(widget->getOuterView()) accessibilityAttributeValue: NSAccessibilityChildrenAttribute];
254 [array addObjectsFromArray: childArr];
259 // add all unignored acc children
260 for (WebCoreAXObject* obj = [self firstChild]; obj; obj = [obj nextSibling]) {
261 if ([obj accessibilityIsIgnored])
262 [obj addChildrenToArray: array];
264 [array addObject: obj];
267 // for a RenderImage, add the <area> elements as individual accessibility objects
268 if (m_renderer->isImage() && !m_areaElement) {
269 HTMLMapElement* map = static_cast<RenderImage*>(m_renderer)->imageMap();
271 for (Node* current = map->firstChild(); current; current = current->traverseNextNode(map)) {
272 // add an <area> element for this child if it has a link
273 // NOTE: can't cache these because they all have the same renderer, which is the cache key, right?
274 // plus there may be little reason to since they are being added to the handy array
275 if (current->isLink()) {
276 WebCoreAXObject* obj = [[[WebCoreAXObject alloc] initWithRenderer: m_renderer] autorelease];
277 obj->m_areaElement = static_cast<HTMLAreaElement*>(current);
278 [array addObject: obj];
287 return m_renderer->isRenderView();
292 return m_areaElement || (!m_renderer->isImage() && m_renderer->element() && m_renderer->element()->isLink());
297 return m_renderer->isTextField() || m_renderer->isTextArea();
300 -(BOOL)isPasswordField
302 if (!m_renderer->element() || !m_renderer->element()->hasTagName(inputTag))
305 HTMLInputElement* input = static_cast<HTMLInputElement*>(m_renderer->element());
306 return input->inputType() == HTMLInputElement::PASSWORD;
311 // widgets are the replaced elements that we represent to AX as attachments
312 BOOL result = m_renderer->isWidget();
314 // assert that a widget is a replaced element that is not an image
315 ASSERT(!result || (m_renderer->isReplaced() && !m_renderer->isImage()));
319 -(NSView*)attachmentView
321 ASSERT(m_renderer->isReplaced() && m_renderer->isWidget() && !m_renderer->isImage());
323 RenderWidget* renderWidget = static_cast<RenderWidget*>(m_renderer);
324 Widget* widget = renderWidget->widget();
326 return widget->getView();
331 static int blockquoteLevel(RenderObject* renderer)
334 for (Node* node = renderer->element(); node; node = node->parent()) {
335 if (node->hasTagName(blockquoteTag))
342 static int headingLevel(RenderObject* renderer)
344 if (!renderer->isBlockFlow())
347 Node* node = renderer->element();
351 if (node->hasTagName(h1Tag))
354 if (node->hasTagName(h2Tag))
357 if (node->hasTagName(h3Tag))
360 if (node->hasTagName(h4Tag))
363 if (node->hasTagName(h5Tag))
366 if (node->hasTagName(h6Tag))
374 return headingLevel(m_renderer);
379 return [self headingLevel] != 0;
385 return NSAccessibilityUnknownRole;
389 if (m_renderer->element() && m_renderer->element()->isLink()) {
390 if (m_renderer->isImage())
391 return @"AXImageMap";
394 if (m_renderer->isListMarker())
395 return @"AXListMarker";
396 if (m_renderer->element() && m_renderer->element()->hasTagName(buttonTag))
397 return NSAccessibilityButtonRole;
398 if (m_renderer->isText())
399 return NSAccessibilityStaticTextRole;
400 if (m_renderer->isImage()) {
401 if ([self isImageButton])
402 return NSAccessibilityButtonRole;
403 return NSAccessibilityImageRole;
405 if ([self isWebArea])
408 if (m_renderer->isTextField())
409 return NSAccessibilityTextFieldRole;
411 if (m_renderer->isTextArea())
412 return NSAccessibilityTextAreaRole;
414 if (m_renderer->element() && m_renderer->element()->hasTagName(inputTag)) {
415 HTMLInputElement* input = static_cast<HTMLInputElement*>(m_renderer->element());
416 if (input->inputType() == HTMLInputElement::CHECKBOX)
417 return NSAccessibilityCheckBoxRole;
418 if (input->inputType() == HTMLInputElement::RADIO)
419 return NSAccessibilityRadioButtonRole;
420 if (input->isTextButton())
421 return NSAccessibilityButtonRole;
424 if (m_renderer->isMenuList())
425 return NSAccessibilityPopUpButtonRole;
427 if ([self isHeading])
430 if (m_renderer->isBlockFlow())
431 return NSAccessibilityGroupRole;
432 if ([self isAttachment])
433 return [[self attachmentView] accessibilityAttributeValue:NSAccessibilityRoleAttribute];
435 return NSAccessibilityUnknownRole;
440 if ([self isPasswordField])
441 return NSAccessibilitySecureTextFieldSubrole;
443 if ([self isAttachment])
444 return [[self attachmentView] accessibilityAttributeValue:NSAccessibilitySubroleAttribute];
449 -(NSString*)roleDescription
454 // attachments have the AXImage role, but a different subrole
455 if ([self isAttachment])
456 return [[self attachmentView] accessibilityAttributeValue:NSAccessibilityRoleDescriptionAttribute];
458 // FIXME 3517227: These need to be localized (UI_STRING here is a dummy macro)
459 // FIXME 3447564: It would be better to call some AppKit API to get these strings
460 // (which would be the best way to localize them)
462 NSString* role = [self role];
463 if ([role isEqualToString:NSAccessibilityButtonRole])
464 return NSAccessibilityRoleDescription(NSAccessibilityButtonRole, [self subrole]);
466 if ([role isEqualToString:NSAccessibilityPopUpButtonRole])
467 return NSAccessibilityRoleDescription(NSAccessibilityPopUpButtonRole, [self subrole]);
469 if ([role isEqualToString:NSAccessibilityStaticTextRole])
470 return NSAccessibilityRoleDescription(NSAccessibilityStaticTextRole, [self subrole]);
472 if ([role isEqualToString:NSAccessibilityImageRole])
473 return NSAccessibilityRoleDescription(NSAccessibilityImageRole, [self subrole]);
475 if ([role isEqualToString:NSAccessibilityGroupRole])
476 return NSAccessibilityRoleDescription(NSAccessibilityGroupRole, [self subrole]);
478 if ([role isEqualToString:NSAccessibilityCheckBoxRole])
479 return NSAccessibilityRoleDescription(NSAccessibilityCheckBoxRole, [self subrole]);
481 if ([role isEqualToString:NSAccessibilityRadioButtonRole])
482 return NSAccessibilityRoleDescription(NSAccessibilityRadioButtonRole, [self subrole]);
484 if ([role isEqualToString:NSAccessibilityTextFieldRole])
485 return NSAccessibilityRoleDescription(NSAccessibilityTextFieldRole, [self subrole]);
487 if ([role isEqualToString:NSAccessibilityTextAreaRole])
488 return NSAccessibilityRoleDescription(NSAccessibilityTextAreaRole, [self subrole]);
490 if ([role isEqualToString:@"AXWebArea"])
491 return UI_STRING("web area", "accessibility role description for web area");
493 if ([role isEqualToString:@"AXLink"])
494 return UI_STRING("link", "accessibility role description for link");
496 if ([role isEqualToString:@"AXListMarker"])
497 return UI_STRING("list marker", "accessibility role description for list marker");
499 if ([role isEqualToString:@"AXImageMap"])
500 return UI_STRING("image map", "accessibility role description for image map");
502 if ([role isEqualToString:@"AXHeading"])
503 return UI_STRING("heading", "accessibility role description for headings");
505 return NSAccessibilityRoleDescription(NSAccessibilityUnknownRole, nil);
514 const AtomicString& summary = static_cast<Element*>(m_areaElement)->getAttribute(summaryAttr);
515 if (!summary.isEmpty())
517 const AtomicString& title = static_cast<Element*>(m_areaElement)->getAttribute(titleAttr);
518 if (!title.isEmpty())
522 for (RenderObject* curr = m_renderer; curr; curr = curr->parent()) {
523 if (curr->element() && curr->element()->isHTMLElement()) {
524 const AtomicString& summary = static_cast<Element*>(curr->element())->getAttribute(summaryAttr);
525 if (!summary.isEmpty())
527 const AtomicString& title = static_cast<Element*>(curr->element())->getAttribute(titleAttr);
528 if (!title.isEmpty())
536 -(NSString*)textUnderElement
541 Node* e = m_renderer->element();
542 Document* d = m_renderer->document();
544 Frame* p = d->frame();
546 // catch stale WebCoreAXObject (see <rdar://problem/3960196>)
547 if (p->document() != d)
549 return plainText(rangeOfContents(e).get()).getNSString();
553 // return nil for anonymous text because it is non-trivial to get
554 // the actual text and, so far, that is not needed
560 if (!m_renderer || m_areaElement || [self isPasswordField])
563 if (m_renderer->isText())
564 return [self textUnderElement];
566 if (m_renderer->isMenuList())
567 return static_cast<RenderMenuList*>(m_renderer)->text();
569 if (m_renderer->isListMarker())
570 return static_cast<RenderListMarker*>(m_renderer)->text().getNSString();
572 if ([self isWebArea]) {
573 if (m_renderer->document()->frame())
576 // FIXME: should use startOfDocument and endOfDocument (or rangeForDocument?) here
577 VisiblePosition startVisiblePosition = m_renderer->positionForCoordinates (0, 0);
578 VisiblePosition endVisiblePosition = m_renderer->positionForCoordinates (LONG_MAX, LONG_MAX);
579 if (startVisiblePosition.isNull() || endVisiblePosition.isNull())
582 return plainText(makeRange(startVisiblePosition, endVisiblePosition).get()).getNSString();
585 if ([self isAttachment])
586 return [[self attachmentView] accessibilityAttributeValue:NSAccessibilityValueAttribute];
588 if ([self isHeading])
589 return [NSNumber numberWithInt:[self headingLevel]];
591 if ([self isTextControl])
592 return (NSString*)(static_cast<RenderTextControl*>(m_renderer)->text());
594 if (m_renderer->element() && m_renderer->element()->hasTagName(inputTag)) {
595 HTMLInputElement* input = static_cast<HTMLInputElement*>(m_renderer->element());
597 // Checkboxes return their state as an integer. 0 for off, 1 for on.
598 if (input->inputType() == HTMLInputElement::CHECKBOX ||
599 input->inputType() == HTMLInputElement::RADIO)
600 return [NSNumber numberWithInt:input->checked()];
603 // FIXME: We might need to implement a value here for more types
604 // FIXME: It would be better not to advertise a value at all for the types for which we don't implement one;
605 // this would require subclassing or making accessibilityAttributeNames do something other than return a
606 // single static array.
610 static HTMLLabelElement* labelForElement(Element* element)
612 RefPtr<NodeList> list = element->document()->getElementsByTagName("label");
613 unsigned len = list->length();
614 for (unsigned i = 0; i < len; i++) {
615 HTMLLabelElement* label = static_cast<HTMLLabelElement*>(list->item(i));
616 if (label->formElement() == element)
625 if (!m_renderer || m_areaElement || !m_renderer->element())
628 if (m_renderer->element()->hasTagName(buttonTag))
629 return [self textUnderElement];
631 if (m_renderer->element()->hasTagName(inputTag)) {
632 HTMLInputElement* input = static_cast<HTMLInputElement*>(m_renderer->element());
633 if (input->isTextButton())
634 return input->value();
636 HTMLLabelElement* label = labelForElement(input);
638 return label->innerText();
641 if (m_renderer->element()->isLink())
642 return [self textUnderElement];
644 if ([self isAttachment])
645 return [[self attachmentView] accessibilityAttributeValue:NSAccessibilityTitleAttribute];
650 - (NSString*)accessibilityDescription
652 if (!m_renderer || m_areaElement)
655 if (m_renderer->isImage()) {
656 if (m_renderer->element() && m_renderer->element()->isHTMLElement()) {
657 const AtomicString& alt = static_cast<Element*>(m_renderer->element())->getAttribute(altAttr);
662 } else if ([self isAttachment])
663 return [[self attachmentView] accessibilityAttributeValue:NSAccessibilityTitleAttribute];
665 if ([self isWebArea]) {
666 Node* owner = m_renderer->document()->ownerElement();
667 if (owner && (owner->hasTagName(frameTag) || owner->hasTagName(iframeTag))) {
668 HTMLFrameElementBase* frameElement = static_cast<HTMLFrameElementBase*>(owner);
669 return frameElement->name();
676 static IntRect boundingBoxRect(RenderObject* obj)
680 if (obj->isInlineContinuation())
681 obj = obj->element()->renderer();
682 Vector<IntRect> rects;
684 obj->absolutePosition(x, y);
685 obj->absoluteRects(rects, x, y);
686 const size_t n = rects.size();
687 for (size_t i = 0; i < n; ++i) {
688 IntRect r = rects[i];
690 if (obj->style()->hasAppearance())
691 theme()->adjustRepaintRect(obj, r);
701 IntRect rect = m_areaElement ? m_areaElement->getRect(m_renderer) : boundingBoxRect(m_renderer);
703 // The Cocoa accessibility API wants the lower-left corner.
704 NSPoint point = NSMakePoint(rect.x(), rect.bottom());
705 if (m_renderer && m_renderer->view() && m_renderer->view()->frameView()) {
706 NSView* view = m_renderer->view()->frameView()->getDocumentView();
707 point = [[view window] convertBaseToScreen: [view convertPoint: point toView:nil]];
710 return [NSValue valueWithPoint: point];
715 IntRect rect = m_areaElement ? m_areaElement->getRect(m_renderer) : boundingBoxRect(m_renderer);
716 return [NSValue valueWithSize: NSMakeSize(rect.width(), rect.height())];
719 // accessibilityShouldUseUniqueId is an AppKit method we override so that
720 // objects will be given a unique ID, and therefore allow AppKit to know when they
721 // become obsolete (e.g. when the user navigates to a new web page, making this one
722 // unrendered but not deallocated because it is in the back/forward cache).
723 // It is important to call NSAccessibilityUnregisterUniqueIdForUIElement in the
724 // appropriate place (e.g. dealloc) to remove these non-retained references from
725 // AppKit's id mapping tables. We do this in detach by calling unregisterUniqueIdForUIElement.
727 // Registering an object is also required for observing notifications. Only registered objects can be observed.
728 - (BOOL)accessibilityShouldUseUniqueId {
732 if ([self isWebArea])
735 if ([self isTextControl])
741 -(BOOL)accessibilityIsIgnored
743 // ignore invisible element
744 if (!m_renderer || m_renderer->style()->visibility() != VISIBLE)
747 // ignore popup menu items because AppKit does
748 for (RenderObject* parent = m_renderer->parent(); parent; parent = parent->parent()) {
749 if (parent->isMenuList())
753 // NOTE: BRs always have text boxes now, so the text box check here can be removed
754 if (m_renderer->isText())
755 return m_renderer->isBR() || !static_cast<RenderText*>(m_renderer)->firstTextBox();
757 // delegate to the attachment
758 if ([self isAttachment])
759 return [[self attachmentView] accessibilityIsIgnored];
761 if (m_areaElement || (m_renderer->element() && m_renderer->element()->isLink()))
764 // all controls are accessible
765 if (m_renderer->element() && m_renderer->element()->isControl())
768 if (m_renderer->isBlockFlow() && m_renderer->childrenInline())
769 return !static_cast<RenderBlock*>(m_renderer)->firstLineBox() && ![self mouseButtonListener];
771 // ignore images seemingly used as spacers
772 if (m_renderer->isImage()) {
773 // check for one-dimensional image
774 if (m_renderer->height() <= 1 || m_renderer->width() <= 1)
777 // check whether rendered image was stretched from one-dimensional file image
778 RenderImage* image = static_cast<RenderImage*>(m_renderer);
779 if (image->cachedImage()) {
780 IntSize imageSize = image->cachedImage()->imageSize();
781 return (imageSize.height() <= 1 || imageSize.width() <= 1);
787 return (!m_renderer->isListMarker() && ![self isWebArea]);
790 - (NSArray*)accessibilityAttributeNames
792 static NSArray* attributes = nil;
793 static NSArray* anchorAttrs = nil;
794 static NSArray* webAreaAttrs = nil;
795 static NSArray* textAttrs = nil;
796 NSMutableArray* tempArray;
797 if (attributes == nil) {
798 attributes = [[NSArray alloc] initWithObjects: NSAccessibilityRoleAttribute,
799 NSAccessibilitySubroleAttribute,
800 NSAccessibilityRoleDescriptionAttribute,
801 NSAccessibilityChildrenAttribute,
802 NSAccessibilityHelpAttribute,
803 NSAccessibilityParentAttribute,
804 NSAccessibilityPositionAttribute,
805 NSAccessibilitySizeAttribute,
806 NSAccessibilityTitleAttribute,
807 NSAccessibilityDescriptionAttribute,
808 NSAccessibilityValueAttribute,
809 NSAccessibilityFocusedAttribute,
810 NSAccessibilityEnabledAttribute,
811 NSAccessibilityWindowAttribute,
812 @"AXSelectedTextMarkerRange",
813 @"AXStartTextMarker",
818 if (anchorAttrs == nil) {
819 tempArray = [[NSMutableArray alloc] initWithArray:attributes];
820 [tempArray addObject: NSAccessibilityURLAttribute];
821 anchorAttrs = [[NSArray alloc] initWithArray:tempArray];
824 if (webAreaAttrs == nil) {
825 tempArray = [[NSMutableArray alloc] initWithArray:attributes];
826 [tempArray addObject: @"AXLinkUIElements"];
827 [tempArray addObject: @"AXLoaded"];
828 [tempArray addObject: @"AXLayoutCount"];
829 webAreaAttrs = [[NSArray alloc] initWithArray:tempArray];
832 if (textAttrs == nil) {
833 tempArray = [[NSMutableArray alloc] initWithArray:attributes];
834 [tempArray addObject: NSAccessibilityNumberOfCharactersAttribute];
835 [tempArray addObject: NSAccessibilitySelectedTextAttribute];
836 [tempArray addObject: NSAccessibilitySelectedTextRangeAttribute];
837 [tempArray addObject: NSAccessibilityVisibleCharacterRangeAttribute];
838 [tempArray addObject: NSAccessibilityInsertionPointLineNumberAttribute];
839 textAttrs = [[NSArray alloc] initWithArray:tempArray];
843 if (!m_renderer || [self isPasswordField])
846 if ([self isWebArea])
849 if ([self isTextControl])
858 - (NSArray*)accessibilityActionNames
860 static NSArray* actions = nil;
862 if ([self actionElement]) {
864 actions = [[NSArray alloc] initWithObjects: NSAccessibilityPressAction, nil];
871 - (NSString*)accessibilityActionDescription:(NSString*)action
873 // we have no custom actions
874 return NSAccessibilityActionDescription(action);
877 - (void)accessibilityPerformAction:(NSString*)action
879 if ([action isEqualToString:NSAccessibilityPressAction]) {
880 Element* actionElement = [self actionElement];
884 if (Frame* f = actionElement->document()->frame())
885 f->loader()->resetMultipleFormSubmissionProtection();
887 actionElement->accessKeyAction(true);
891 - (WebCoreTextMarkerRange*) textMarkerRangeFromMarkers: (WebCoreTextMarker*) textMarker1 andEndMarker:(WebCoreTextMarker*) textMarker2
893 return [[WebCoreViewFactory sharedFactory] textMarkerRangeWithStart:textMarker1 end:textMarker2];
896 - (WebCoreTextMarker*) textMarkerForVisiblePosition: (VisiblePosition)visiblePos
898 if (visiblePos.isNull())
901 return m_renderer->document()->axObjectCache()->textMarkerForVisiblePosition(visiblePos);
904 - (VisiblePosition) visiblePositionForTextMarker: (WebCoreTextMarker*)textMarker
906 return m_renderer->document()->axObjectCache()->visiblePositionForTextMarker(textMarker);
909 - (VisiblePosition) visiblePositionForStartOfTextMarkerRange: (WebCoreTextMarkerRange*)textMarkerRange
911 return [self visiblePositionForTextMarker:[[WebCoreViewFactory sharedFactory] startOfTextMarkerRange:textMarkerRange]];
914 - (VisiblePosition) visiblePositionForEndOfTextMarkerRange: (WebCoreTextMarkerRange*) textMarkerRange
916 return [self visiblePositionForTextMarker:[[WebCoreViewFactory sharedFactory] endOfTextMarkerRange:textMarkerRange]];
919 - (WebCoreTextMarkerRange*) textMarkerRangeFromVisiblePositions: (VisiblePosition) startPosition andEndPos: (VisiblePosition) endPosition
921 WebCoreTextMarker* startTextMarker = [self textMarkerForVisiblePosition: startPosition];
922 WebCoreTextMarker* endTextMarker = [self textMarkerForVisiblePosition: endPosition];
923 return [self textMarkerRangeFromMarkers: startTextMarker andEndMarker:endTextMarker];
926 - (WebCoreTextMarkerRange*)textMarkerRange
931 // construct VisiblePositions for start and end
932 Node* node = m_renderer->element();
933 VisiblePosition visiblePos1 = VisiblePosition(node, 0, VP_DEFAULT_AFFINITY);
934 VisiblePosition visiblePos2 = VisiblePosition(node, maxDeepOffset(node), VP_DEFAULT_AFFINITY);
936 // the VisiblePositions are equal for nodes like buttons, so adjust for that
937 if (visiblePos1 == visiblePos2) {
938 visiblePos2 = visiblePos2.next();
939 if (visiblePos2.isNull())
940 visiblePos2 = visiblePos1;
943 WebCoreTextMarker* startTextMarker = [self textMarkerForVisiblePosition: visiblePos1];
944 WebCoreTextMarker* endTextMarker = [self textMarkerForVisiblePosition: visiblePos2];
945 return [self textMarkerRangeFromMarkers: startTextMarker andEndMarker:endTextMarker];
948 - (Document*)topDocument
950 return m_renderer->document()->topDocument();
953 - (RenderObject*)topRenderer
955 return m_renderer->document()->topDocument()->renderer();
958 - (FrameView*)topView
960 return m_renderer->document()->topDocument()->renderer()->view()->frameView();
963 - (id)accessibilityAttributeValue:(NSString*)attributeName
968 if ([attributeName isEqualToString: NSAccessibilityRoleAttribute])
971 if ([attributeName isEqualToString: NSAccessibilitySubroleAttribute])
972 return [self subrole];
974 if ([attributeName isEqualToString: NSAccessibilityRoleDescriptionAttribute])
975 return [self roleDescription];
977 if ([attributeName isEqualToString: NSAccessibilityParentAttribute]) {
978 if (m_renderer->isRenderView() && m_renderer->view() && m_renderer->view()->frameView())
979 return m_renderer->view()->frameView()->getView();
980 return [self parentObjectUnignored];
983 if ([attributeName isEqualToString: NSAccessibilityChildrenAttribute]) {
985 m_children = [NSMutableArray arrayWithCapacity: 8];
987 [self addChildrenToArray: m_children];
992 if ([self isWebArea]) {
993 if ([attributeName isEqualToString: @"AXLinkUIElements"]) {
994 NSMutableArray* links = [NSMutableArray arrayWithCapacity: 32];
995 RefPtr<HTMLCollection> coll = m_renderer->document()->links();
996 Node* curr = coll->firstItem();
998 RenderObject* obj = curr->renderer();
1000 WebCoreAXObject* axobj = obj->document()->axObjectCache()->get(obj);
1001 ASSERT([[axobj role] isEqualToString:@"AXLink"]);
1002 if (![axobj accessibilityIsIgnored])
1003 [links addObject: axobj];
1005 curr = coll->nextItem();
1009 if ([attributeName isEqualToString: @"AXLoaded"])
1010 return [NSNumber numberWithBool: (!m_renderer->document()->tokenizer())];
1011 if ([attributeName isEqualToString: @"AXLayoutCount"])
1012 return [NSNumber numberWithInt: (static_cast<RenderView*>(m_renderer)->frameView()->layoutCount())];
1015 if ([self isTextControl]) {
1016 RenderTextControl* textControl = static_cast<RenderTextControl*>(m_renderer);
1017 if ([attributeName isEqualToString: NSAccessibilityNumberOfCharactersAttribute])
1018 return [NSNumber numberWithUnsignedInt: textControl->text().length()];
1019 if ([attributeName isEqualToString: NSAccessibilitySelectedTextAttribute]) {
1020 NSString* text = textControl->text();
1021 return [text substringWithRange: NSMakeRange(textControl->selectionStart(), textControl->selectionEnd() - textControl->selectionStart())];
1023 if ([attributeName isEqualToString: NSAccessibilitySelectedTextRangeAttribute])
1024 return [NSValue valueWithRange: NSMakeRange(textControl->selectionStart(), textControl->selectionEnd() - textControl->selectionStart())];
1025 // TODO: Get actual visible range. <rdar://problem/4712101>
1026 if ([attributeName isEqualToString: NSAccessibilityVisibleCharacterRangeAttribute])
1027 return [NSValue valueWithRange: NSMakeRange(0, textControl->text().length())];
1028 if ([attributeName isEqualToString: NSAccessibilityInsertionPointLineNumberAttribute]) {
1029 if (textControl->selectionStart() != textControl->selectionEnd())
1032 NSNumber* index = [NSNumber numberWithInt: textControl->selectionStart()];
1033 return [self doAXLineForTextMarker: [self textMarkerForIndex: index lastIndexOK: YES]];
1037 if ([self isAnchor] && [attributeName isEqualToString: NSAccessibilityURLAttribute]) {
1038 HTMLAnchorElement* anchor = [self anchorElement];
1040 DeprecatedString s = anchor->getAttribute(hrefAttr).deprecatedString();
1042 s = anchor->document()->completeURL(s);
1043 return s.getNSString();
1048 if ([attributeName isEqualToString: @"AXVisited"])
1049 return [NSNumber numberWithBool: m_renderer->style()->pseudoState() == PseudoVisited];
1051 if ([attributeName isEqualToString: NSAccessibilityTitleAttribute])
1052 return [self title];
1054 if ([attributeName isEqualToString: NSAccessibilityDescriptionAttribute])
1055 return [self accessibilityDescription];
1057 if ([attributeName isEqualToString: NSAccessibilityValueAttribute])
1058 return [self value];
1060 if ([attributeName isEqualToString: NSAccessibilityHelpAttribute])
1061 return [self helpText];
1063 if ([attributeName isEqualToString: NSAccessibilityFocusedAttribute])
1064 return [NSNumber numberWithBool: (m_renderer->element() && m_renderer->document()->focusNode() == m_renderer->element())];
1066 if ([attributeName isEqualToString: NSAccessibilityEnabledAttribute])
1067 return [NSNumber numberWithBool: m_renderer->element() ? m_renderer->element()->isEnabled() : YES];
1069 if ([attributeName isEqualToString: NSAccessibilitySizeAttribute])
1072 if ([attributeName isEqualToString: NSAccessibilityPositionAttribute])
1073 return [self position];
1075 if ([attributeName isEqualToString: NSAccessibilityWindowAttribute]) {
1076 if (m_renderer && m_renderer->view() && m_renderer->view()->frameView())
1077 return [m_renderer->view()->frameView()->getView() window];
1081 if ([attributeName isEqualToString: @"AXSelectedTextMarkerRange"]) {
1082 // get the selection from the document part
1083 // NOTE: BUG support nested WebAreas, like in <http://webcourses.niu.edu/>
1084 // (there is a web archive of this page attached to <rdar://problem/3888973>)
1085 // Trouble is we need to know which document view to ask.
1086 Selection selection = [self topView]->frame()->selectionController()->selection();
1087 if (selection.isNone()) {
1088 selection = m_renderer->document()->renderer()->view()->frameView()->frame()->selectionController()->selection();
1089 if (selection.isNone())
1093 return (id) [self textMarkerRangeFromVisiblePositions:selection.visibleStart() andEndPos:selection.visibleEnd()];
1096 if ([attributeName isEqualToString: @"AXStartTextMarker"])
1097 return (id) [self textMarkerForVisiblePosition: startOfDocument(m_renderer->document()->topDocument())];
1099 if ([attributeName isEqualToString: @"AXEndTextMarker"])
1100 return (id) [self textMarkerForVisiblePosition: endOfDocument(m_renderer->document()->topDocument())];
1105 - (NSArray* )accessibilityParameterizedAttributeNames
1107 static NSArray* paramAttrs = nil;
1108 static NSArray* textParamAttrs = nil;
1109 if (paramAttrs == nil) {
1110 paramAttrs = [[NSArray alloc] initWithObjects:
1111 @"AXUIElementForTextMarker",
1112 @"AXTextMarkerRangeForUIElement",
1113 @"AXLineForTextMarker",
1114 @"AXTextMarkerRangeForLine",
1115 @"AXStringForTextMarkerRange",
1116 @"AXTextMarkerForPosition",
1117 @"AXBoundsForTextMarkerRange",
1118 @"AXAttributedStringForTextMarkerRange",
1119 @"AXTextMarkerRangeForUnorderedTextMarkers",
1120 @"AXNextTextMarkerForTextMarker",
1121 @"AXPreviousTextMarkerForTextMarker",
1122 @"AXLeftWordTextMarkerRangeForTextMarker",
1123 @"AXRightWordTextMarkerRangeForTextMarker",
1124 @"AXLeftLineTextMarkerRangeForTextMarker",
1125 @"AXRightLineTextMarkerRangeForTextMarker",
1126 @"AXSentenceTextMarkerRangeForTextMarker",
1127 @"AXParagraphTextMarkerRangeForTextMarker",
1128 @"AXNextWordEndTextMarkerForTextMarker",
1129 @"AXPreviousWordStartTextMarkerForTextMarker",
1130 @"AXNextLineEndTextMarkerForTextMarker",
1131 @"AXPreviousLineStartTextMarkerForTextMarker",
1132 @"AXNextSentenceEndTextMarkerForTextMarker",
1133 @"AXPreviousSentenceStartTextMarkerForTextMarker",
1134 @"AXNextParagraphEndTextMarkerForTextMarker",
1135 @"AXPreviousParagraphStartTextMarkerForTextMarker",
1136 @"AXStyleTextMarkerRangeForTextMarker",
1137 @"AXLengthForTextMarkerRange",
1141 if (textParamAttrs == nil) {
1142 NSMutableArray* tempArray = [[NSMutableArray alloc] initWithArray:paramAttrs];
1143 [tempArray addObject: (NSString*)kAXLineForIndexParameterizedAttribute];
1144 [tempArray addObject: (NSString*)kAXRangeForLineParameterizedAttribute];
1145 [tempArray addObject: (NSString*)kAXStringForRangeParameterizedAttribute];
1146 [tempArray addObject: (NSString*)kAXRangeForPositionParameterizedAttribute];
1147 [tempArray addObject: (NSString*)kAXRangeForIndexParameterizedAttribute];
1148 [tempArray addObject: (NSString*)kAXBoundsForRangeParameterizedAttribute];
1149 [tempArray addObject: (NSString*)kAXRTFForRangeParameterizedAttribute];
1150 [tempArray addObject: (NSString*)kAXAttributedStringForRangeParameterizedAttribute];
1151 [tempArray addObject: (NSString*)kAXStyleRangeForIndexParameterizedAttribute];
1152 textParamAttrs = [[NSArray alloc] initWithArray:tempArray];
1153 [tempArray release];
1156 if ([self isPasswordField])
1157 return [NSArray array];
1162 if ([self isTextControl])
1163 return textParamAttrs;
1168 - (id)doAXUIElementForTextMarker: (WebCoreTextMarker* ) textMarker
1170 VisiblePosition visiblePos = [self visiblePositionForTextMarker:textMarker];
1171 if (visiblePos.isNull())
1174 RenderObject* obj = visiblePos.deepEquivalent().node()->renderer();
1178 return obj->document()->axObjectCache()->get(obj);
1181 - (id)doAXTextMarkerRangeForUIElement: (id) uiElement
1183 return (id)[uiElement textMarkerRange];
1186 - (id)doAXLineForTextMarker: (WebCoreTextMarker* ) textMarker
1188 unsigned int lineCount = 0;
1189 VisiblePosition savedVisiblePos;
1190 VisiblePosition visiblePos = [self visiblePositionForTextMarker:textMarker];
1191 if (visiblePos.isNull())
1194 // move up until we get to the top
1195 // NOTE: BUG This only takes us to the top of the rootEditableElement, not the top of the
1197 while (visiblePos.isNotNull() && !(inSameLine(visiblePos, savedVisiblePos))) {
1199 savedVisiblePos = visiblePos;
1200 visiblePos = previousLinePosition(visiblePos, 0);
1203 return [NSNumber numberWithUnsignedInt:(lineCount - 1)];
1206 - (id)doAXTextMarkerRangeForLine: (NSNumber*) lineNumber
1208 unsigned lineCount = [lineNumber unsignedIntValue];
1209 if (lineCount == 0 || !m_renderer)
1212 // iterate over the lines
1213 // NOTE: BUG this is wrong when lineNumber is lineCount+1, because nextLinePosition takes you to the
1214 // last offset of the last line
1215 VisiblePosition visiblePos = [self topRenderer]->positionForCoordinates (0, 0);
1216 VisiblePosition savedVisiblePos;
1217 while (--lineCount != 0) {
1218 savedVisiblePos = visiblePos;
1219 visiblePos = nextLinePosition(visiblePos, 0);
1220 if (visiblePos.isNull() || visiblePos == savedVisiblePos)
1224 // make a caret selection for the marker position, then extend it to the line
1225 // NOTE: ignores results of sel.modify because it returns false when
1226 // starting at an empty line. The resulting selection in that case
1227 // will be a caret at visiblePos.
1228 SelectionController selectionController;
1229 selectionController.setSelection(Selection(visiblePos));
1230 selectionController.modify(SelectionController::EXTEND, SelectionController::RIGHT, LineBoundary);
1232 // return a marker range for the selection start to end
1233 VisiblePosition startPosition = selectionController.selection().visibleStart();
1234 VisiblePosition endPosition = selectionController.selection().visibleEnd();
1235 return (id) [self textMarkerRangeFromVisiblePositions:startPosition andEndPos:endPosition];
1238 - (id)doAXStringForTextMarkerRange: (WebCoreTextMarkerRange*) textMarkerRange
1240 // extract the start and end VisiblePosition
1241 VisiblePosition startVisiblePosition = [self visiblePositionForStartOfTextMarkerRange: textMarkerRange];
1242 if (startVisiblePosition.isNull())
1245 VisiblePosition endVisiblePosition = [self visiblePositionForEndOfTextMarkerRange: textMarkerRange];
1246 if (endVisiblePosition.isNull())
1249 // get the visible text in the range
1250 return plainText(makeRange(startVisiblePosition, endVisiblePosition).get()).getNSString();
1253 - (id)doAXTextMarkerForPosition: (NSPoint) point
1255 // convert absolute point to view coordinates
1256 FrameView* docView = [self topView];
1257 NSView* view = docView->getDocumentView();
1258 RenderObject* renderer = [self topRenderer];
1259 Node* innerNode = NULL;
1262 // locate the node containing the point
1264 // ask the document layer to hitTest
1265 NSPoint windowCoord = [[view window] convertScreenToBase: point];
1266 ourpoint = [view convertPoint:windowCoord fromView:nil];
1268 HitTestRequest request(true, true);
1269 HitTestResult result = HitTestResult(IntPoint(ourpoint));
1270 renderer->layer()->hitTest(request, result);
1271 innerNode = result.innerNode();
1272 if (!innerNode || !innerNode->renderer())
1275 // done if hit something other than a widget
1276 renderer = innerNode->renderer();
1277 if (!renderer->isWidget())
1280 // descend into widget (FRAME, IFRAME, OBJECT...)
1281 Widget* widget = static_cast<RenderWidget*>(renderer)->widget();
1282 if (!widget || !widget->isFrameView())
1284 Frame* frame = static_cast<FrameView*>(widget)->frame();
1287 Document* document = frame->document();
1290 renderer = document->renderer();
1291 docView = static_cast<FrameView*>(widget);
1292 view = docView->getDocumentView();
1295 // get position within the node
1296 VisiblePosition pos = innerNode->renderer()->positionForCoordinates ((int)ourpoint.x, (int)ourpoint.y);
1297 return (id) [self textMarkerForVisiblePosition:pos];
1300 - (id)doAXBoundsForTextMarkerRange: (WebCoreTextMarkerRange*) textMarkerRange
1303 // extract the start and end VisiblePosition
1304 VisiblePosition startVisiblePosition = [self visiblePositionForStartOfTextMarkerRange: textMarkerRange];
1305 if (startVisiblePosition.isNull())
1308 VisiblePosition endVisiblePosition = [self visiblePositionForEndOfTextMarkerRange: textMarkerRange];
1309 if (endVisiblePosition.isNull())
1312 IntRect rect1 = startVisiblePosition.caretRect();
1313 IntRect rect2 = endVisiblePosition.caretRect();
1314 IntRect ourrect = rect1;
1315 ourrect.unite(rect2);
1317 // try to use the document view from the selection, so that nested WebAreas work,
1318 // but fall back to the top level doc if we do not find it easily
1319 FrameView* docView = NULL;
1320 RenderObject* renderer = startVisiblePosition.deepEquivalent().node()->renderer();
1322 Document* doc = renderer->document();
1324 docView = doc->view();
1327 docView = [self topView];
1328 NSView* view = docView->getView();
1330 // if the selection spans lines, the rectangle is to extend
1331 // across the width of the view
1332 if (rect1.bottom() != rect2.bottom()) {
1333 ourrect.setX(static_cast<int>([view frame].origin.x));
1334 ourrect.setWidth(static_cast<int>([view frame].size.width));
1337 // convert our rectangle to screen coordinates
1338 NSRect rect = ourrect;
1339 rect = NSOffsetRect(rect, -docView->contentsX(), -docView->contentsY());
1340 rect = [view convertRect:rect toView:nil];
1341 rect.origin = [[view window] convertBaseToScreen:rect.origin];
1343 // return the converted rect
1344 return [NSValue valueWithRect:rect];
1347 static CGColorRef CreateCGColorIfDifferent(NSColor* nsColor, CGColorRef existingColor)
1349 // get color information assuming NSDeviceRGBColorSpace
1350 NSColor* rgbColor = [nsColor colorUsingColorSpaceName:NSDeviceRGBColorSpace];
1351 if (rgbColor == nil)
1352 rgbColor = [NSColor blackColor];
1353 CGFloat components[4];
1354 [rgbColor getRed:&components[0] green:&components[1] blue:&components[2] alpha:&components[3]];
1356 // create a new CGColorRef to return
1357 CGColorSpaceRef cgColorSpace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
1358 CGColorRef cgColor = CGColorCreate(cgColorSpace, components);
1359 CGColorSpaceRelease(cgColorSpace);
1360 CFMakeCollectable(cgColor);
1362 // check for match with existing color
1363 if (existingColor && CGColorEqualToColor(cgColor, existingColor))
1369 static void AXAttributeStringSetColor(NSMutableAttributedString* attrString, NSString* attribute, NSColor* color, NSRange range)
1372 CGColorRef existingColor = (CGColorRef) [attrString attribute:attribute atIndex:range.location effectiveRange:nil];
1373 CGColorRef cgColor = CreateCGColorIfDifferent(color, existingColor);
1374 if (cgColor != NULL) {
1375 [attrString addAttribute:attribute value:(id)cgColor range:range];
1376 CGColorRelease(cgColor);
1379 [attrString removeAttribute:attribute range:range];
1382 static void AXAttributeStringSetNumber(NSMutableAttributedString* attrString, NSString* attribute, NSNumber* number, NSRange range)
1385 [attrString addAttribute:attribute value:number range:range];
1387 [attrString removeAttribute:attribute range:range];
1390 static void AXAttributeStringSetFont(NSMutableAttributedString* attrString, NSString* attribute, NSFont* font, NSRange range)
1395 dict = [NSDictionary dictionaryWithObjectsAndKeys:
1396 [font fontName] , NSAccessibilityFontNameKey,
1397 [font familyName] , NSAccessibilityFontFamilyKey,
1398 [font displayName] , NSAccessibilityVisibleNameKey,
1399 [NSNumber numberWithFloat:[font pointSize]] , NSAccessibilityFontSizeKey,
1402 [attrString addAttribute:attribute value:dict range:range];
1404 [attrString removeAttribute:attribute range:range];
1408 static void AXAttributeStringSetStyle(NSMutableAttributedString* attrString, RenderObject* renderer, NSRange range)
1410 RenderStyle* style = renderer->style();
1412 // set basic font info
1413 AXAttributeStringSetFont(attrString, NSAccessibilityFontTextAttribute, style->font().primaryFont()->getNSFont(), range);
1416 AXAttributeStringSetColor(attrString, NSAccessibilityForegroundColorTextAttribute, nsColor(style->color()), range);
1417 AXAttributeStringSetColor(attrString, NSAccessibilityBackgroundColorTextAttribute, nsColor(style->backgroundColor()), range);
1419 // set super/sub scripting
1420 EVerticalAlign alignment = style->verticalAlign();
1421 if (alignment == SUB)
1422 AXAttributeStringSetNumber(attrString, NSAccessibilitySuperscriptTextAttribute, [NSNumber numberWithInt:(-1)], range);
1423 else if (alignment == SUPER)
1424 AXAttributeStringSetNumber(attrString, NSAccessibilitySuperscriptTextAttribute, [NSNumber numberWithInt:1], range);
1426 [attrString removeAttribute:NSAccessibilitySuperscriptTextAttribute range:range];
1429 if (style->textShadow() != nil)
1430 AXAttributeStringSetNumber(attrString, NSAccessibilityShadowTextAttribute, [NSNumber numberWithBool:YES], range);
1432 [attrString removeAttribute:NSAccessibilityShadowTextAttribute range:range];
1434 // set underline and strikethrough
1435 int decor = style->textDecorationsInEffect();
1436 if ((decor & UNDERLINE) == 0) {
1437 [attrString removeAttribute:NSAccessibilityUnderlineTextAttribute range:range];
1438 [attrString removeAttribute:NSAccessibilityUnderlineColorTextAttribute range:range];
1441 if ((decor & LINE_THROUGH) == 0) {
1442 [attrString removeAttribute:NSAccessibilityStrikethroughTextAttribute range:range];
1443 [attrString removeAttribute:NSAccessibilityStrikethroughColorTextAttribute range:range];
1446 if ((decor & (UNDERLINE | LINE_THROUGH)) != 0) {
1447 // find colors using quirk mode approach (strict mode would use current
1448 // color for all but the root line box, which would use getTextDecorationColors)
1449 Color underline, overline, linethrough;
1450 renderer->getTextDecorationColors(decor, underline, overline, linethrough);
1452 if ((decor & UNDERLINE) != 0) {
1453 AXAttributeStringSetNumber(attrString, NSAccessibilityUnderlineTextAttribute, [NSNumber numberWithBool:YES], range);
1454 AXAttributeStringSetColor(attrString, NSAccessibilityUnderlineColorTextAttribute, nsColor(underline), range);
1457 if ((decor & LINE_THROUGH) != 0) {
1458 AXAttributeStringSetNumber(attrString, NSAccessibilityStrikethroughTextAttribute, [NSNumber numberWithBool:YES], range);
1459 AXAttributeStringSetColor(attrString, NSAccessibilityStrikethroughColorTextAttribute, nsColor(linethrough), range);
1464 static void AXAttributeStringSetHeadingLevel(NSMutableAttributedString* attrString, RenderObject* renderer, NSRange range)
1466 int parentHeadingLevel = headingLevel(renderer->parent());
1468 if (parentHeadingLevel)
1469 [attrString addAttribute:@"AXHeadingLevel" value:[NSNumber numberWithInt:parentHeadingLevel] range:range];
1471 [attrString removeAttribute:@"AXHeadingLevel" range:range];
1474 static void AXAttributeStringSetBlockquoteLevel(NSMutableAttributedString* attrString, RenderObject* renderer, NSRange range)
1476 int quoteLevel = blockquoteLevel(renderer);
1479 [attrString addAttribute:@"AXBlockQuoteLevel" value:[NSNumber numberWithInt:quoteLevel] range:range];
1481 [attrString removeAttribute:@"AXBlockQuoteLevel" range:range];
1484 static void AXAttributeStringSetElement(NSMutableAttributedString* attrString, NSString* attribute, id element, NSRange range)
1486 if (element != nil) {
1487 // make a serialiazable AX object
1488 AXUIElementRef axElement = [[WebCoreViewFactory sharedFactory] AXUIElementForElement:element];
1489 if (axElement != NULL) {
1490 [attrString addAttribute:attribute value:(id)axElement range:range];
1491 CFRelease(axElement);
1494 [attrString removeAttribute:attribute range:range];
1497 static WebCoreAXObject* AXLinkElementForNode (Node* node)
1499 RenderObject* obj = node->renderer();
1503 WebCoreAXObject* axObj = obj->document()->axObjectCache()->get(obj);
1504 HTMLAnchorElement* anchor = [axObj anchorElement];
1505 if (!anchor || !anchor->renderer())
1508 return anchor->renderer()->document()->axObjectCache()->get(anchor->renderer());
1511 static void AXAttributeStringSetSpelling(NSMutableAttributedString* attrString, Node* node, int offset, NSRange range)
1513 Vector<DocumentMarker> markers = node->renderer()->document()->markersForNode(node);
1514 Vector<DocumentMarker>::iterator markerIt = markers.begin();
1516 unsigned endOffset = (unsigned)offset + range.length;
1517 for ( ; markerIt != markers.end(); markerIt++) {
1518 DocumentMarker marker = *markerIt;
1520 if (marker.type != DocumentMarker::Spelling)
1523 if (marker.endOffset <= (unsigned)offset)
1526 if (marker.startOffset > endOffset)
1529 // add misspelling attribute for the intersection of the marker and the range
1530 int rStart = range.location + (marker.startOffset - offset);
1531 int rLength = MIN(marker.endOffset, endOffset) - marker.startOffset;
1532 NSRange spellRange = NSMakeRange(rStart, rLength);
1533 AXAttributeStringSetNumber(attrString, NSAccessibilityMisspelledTextAttribute, [NSNumber numberWithBool:YES], spellRange);
1535 if (marker.endOffset > endOffset + 1)
1540 static void AXAttributedStringAppendText(NSMutableAttributedString* attrString, Node* node, int offset, const UChar* chars, int length)
1542 // skip invisible text
1543 if (!node->renderer())
1546 // easier to calculate the range before appending the string
1547 NSRange attrStringRange = NSMakeRange([attrString length], length);
1549 // append the string from this node
1550 [[attrString mutableString] appendString:[NSString stringWithCharacters:chars length:length]];
1552 // add new attributes and remove irrelevant inherited ones
1553 // NOTE: color attributes are handled specially because -[NSMutableAttributedString addAttribute: value: range:] does not merge
1554 // identical colors. Workaround is to not replace an existing color attribute if it matches what we are adding. This also means
1555 // we can not just pre-remove all inherited attributes on the appended string, so we have to remove the irrelevant ones individually.
1557 // remove inherited attachment from prior AXAttributedStringAppendReplaced
1558 [attrString removeAttribute:NSAccessibilityAttachmentTextAttribute range:attrStringRange];
1560 // set new attributes
1561 AXAttributeStringSetStyle(attrString, node->renderer(), attrStringRange);
1562 AXAttributeStringSetHeadingLevel(attrString, node->renderer(), attrStringRange);
1563 AXAttributeStringSetBlockquoteLevel(attrString, node->renderer(), attrStringRange);
1564 AXAttributeStringSetElement(attrString, NSAccessibilityLinkTextAttribute, AXLinkElementForNode(node), attrStringRange);
1566 // do spelling last because it tends to break up the range
1567 AXAttributeStringSetSpelling(attrString, node, offset, attrStringRange);
1570 static void AXAttributedStringAppendReplaced (NSMutableAttributedString* attrString, Node* replacedNode)
1572 static const UniChar attachmentChar = NSAttachmentCharacter;
1574 // we should always be given a rendered node, but be safe
1575 if (!replacedNode || !replacedNode->renderer()) {
1576 ASSERT_NOT_REACHED();
1580 // we should always be given a replaced node, but be safe
1581 // replaced nodes are either attachments (widgets) or images
1582 if (!replacedNode->renderer()->isReplaced()) {
1583 ASSERT_NOT_REACHED();
1587 // create an AX object, but skip it if it is not supposed to be seen
1588 WebCoreAXObject* obj = replacedNode->renderer()->document()->axObjectCache()->get(replacedNode->renderer());
1589 if ([obj accessibilityIsIgnored])
1592 // easier to calculate the range before appending the string
1593 NSRange attrStringRange = NSMakeRange([attrString length], 1);
1595 // append the placeholder string
1596 [[attrString mutableString] appendString:[NSString stringWithCharacters:&attachmentChar length:1]];
1598 // remove all inherited attributes
1599 [attrString setAttributes:nil range:attrStringRange];
1601 // add the attachment attribute
1602 AXAttributeStringSetElement(attrString, NSAccessibilityAttachmentTextAttribute, obj, attrStringRange);
1605 - (id)doAXAttributedStringForTextMarkerRange: (WebCoreTextMarkerRange*) textMarkerRange
1607 // extract the start and end VisiblePosition
1608 VisiblePosition startVisiblePosition = [self visiblePositionForStartOfTextMarkerRange: textMarkerRange];
1609 if (startVisiblePosition.isNull())
1612 VisiblePosition endVisiblePosition = [self visiblePositionForEndOfTextMarkerRange: textMarkerRange];
1613 if (endVisiblePosition.isNull())
1616 // iterate over the range to build the AX attributed string
1617 NSMutableAttributedString* attrString = [[NSMutableAttributedString alloc] init];
1618 TextIterator it(makeRange(startVisiblePosition, endVisiblePosition).get());
1619 while (!it.atEnd()) {
1620 // locate the node and starting offset for this range
1622 Node* node = it.range()->startContainer(exception);
1623 ASSERT(node == it.range()->endContainer(exception));
1624 int offset = it.range()->startOffset(exception);
1626 // non-zero length means textual node, zero length means replaced node (AKA "attachments" in AX)
1627 if (it.length() != 0)
1628 AXAttributedStringAppendText(attrString, node, offset, it.characters(), it.length());
1630 AXAttributedStringAppendReplaced(attrString, node->childNode(offset));
1635 return [attrString autorelease];
1638 - (id)doAXTextMarkerRangeForUnorderedTextMarkers: (NSArray*) markers
1640 // get and validate the markers
1641 if ([markers count] < 2)
1644 WebCoreTextMarker* textMarker1 = (WebCoreTextMarker*) [markers objectAtIndex:0];
1645 WebCoreTextMarker* textMarker2 = (WebCoreTextMarker*) [markers objectAtIndex:1];
1646 if (![[WebCoreViewFactory sharedFactory] objectIsTextMarker:textMarker1] || ![[WebCoreViewFactory sharedFactory] objectIsTextMarker:textMarker2])
1649 // convert to VisiblePosition
1650 VisiblePosition visiblePos1 = [self visiblePositionForTextMarker:textMarker1];
1651 VisiblePosition visiblePos2 = [self visiblePositionForTextMarker:textMarker2];
1652 if (visiblePos1.isNull() || visiblePos2.isNull())
1655 // use the SelectionController class to do the ordering
1656 // NOTE: Perhaps we could add a SelectionController method to indicate direction, based on m_baseIsStart
1657 WebCoreTextMarker* startTextMarker;
1658 WebCoreTextMarker* endTextMarker;
1659 Selection selection(visiblePos1, visiblePos2);
1660 if (selection.base() == selection.start()) {
1661 startTextMarker = textMarker1;
1662 endTextMarker = textMarker2;
1664 startTextMarker = textMarker2;
1665 endTextMarker = textMarker1;
1668 // return a range based on the SelectionController verdict
1669 return (id) [self textMarkerRangeFromMarkers: startTextMarker andEndMarker:endTextMarker];
1672 - (id)doAXNextTextMarkerForTextMarker: (WebCoreTextMarker*) textMarker
1674 VisiblePosition visiblePos = [self visiblePositionForTextMarker:textMarker];
1675 VisiblePosition nextVisiblePos = visiblePos.next();
1676 if (nextVisiblePos.isNull())
1679 return (id) [self textMarkerForVisiblePosition:nextVisiblePos];
1682 - (id)doAXPreviousTextMarkerForTextMarker: (WebCoreTextMarker*) textMarker
1684 VisiblePosition visiblePos = [self visiblePositionForTextMarker:textMarker];
1685 VisiblePosition previousVisiblePos = visiblePos.previous();
1686 if (previousVisiblePos.isNull())
1689 return (id) [self textMarkerForVisiblePosition:previousVisiblePos];
1692 - (id)doAXLeftWordTextMarkerRangeForTextMarker: (WebCoreTextMarker*) textMarker
1694 VisiblePosition visiblePos = [self visiblePositionForTextMarker:textMarker];
1695 VisiblePosition startPosition = startOfWord(visiblePos, LeftWordIfOnBoundary);
1696 VisiblePosition endPosition = endOfWord(startPosition);
1698 return (id) [self textMarkerRangeFromVisiblePositions:startPosition andEndPos:endPosition];
1701 - (id)doAXRightWordTextMarkerRangeForTextMarker: (WebCoreTextMarker*) textMarker
1703 VisiblePosition visiblePos = [self visiblePositionForTextMarker:textMarker];
1704 VisiblePosition startPosition = startOfWord(visiblePos, RightWordIfOnBoundary);
1705 VisiblePosition endPosition = endOfWord(startPosition);
1707 return (id) [self textMarkerRangeFromVisiblePositions:startPosition andEndPos:endPosition];
1710 - (id)doAXLeftLineTextMarkerRangeForTextMarker: (WebCoreTextMarker*) textMarker
1712 VisiblePosition visiblePos = [self visiblePositionForTextMarker:textMarker];
1713 if (visiblePos.isNull())
1716 // make a caret selection for the position before marker position (to make sure
1717 // we move off of a line start)
1718 VisiblePosition prevVisiblePos = visiblePos.previous();
1719 if (prevVisiblePos.isNull())
1722 VisiblePosition startPosition = startOfLine(prevVisiblePos);
1723 VisiblePosition endPosition = endOfLine(prevVisiblePos);
1724 return (id) [self textMarkerRangeFromVisiblePositions:startPosition andEndPos:endPosition];
1727 - (id)doAXRightLineTextMarkerRangeForTextMarker: (WebCoreTextMarker*) textMarker
1729 VisiblePosition visiblePos = [self visiblePositionForTextMarker:textMarker];
1730 if (visiblePos.isNull())
1733 // make sure we move off of a line end
1734 VisiblePosition nextVisiblePos = visiblePos.next();
1735 if (nextVisiblePos.isNull())
1738 VisiblePosition startPosition = startOfLine(nextVisiblePos);
1739 VisiblePosition endPosition = endOfLine(nextVisiblePos);
1740 return (id) [self textMarkerRangeFromVisiblePositions:startPosition andEndPos:endPosition];
1743 - (id)doAXSentenceTextMarkerRangeForTextMarker: (WebCoreTextMarker*) textMarker
1745 // NOTE: BUG FO 2 IMPLEMENT (currently returns incorrect answer)
1746 // Related? <rdar://problem/3927736> Text selection broken in 8A336
1747 VisiblePosition visiblePos = [self visiblePositionForTextMarker:textMarker];
1748 VisiblePosition startPosition = startOfSentence(visiblePos);
1749 VisiblePosition endPosition = endOfSentence(startPosition);
1750 return (id) [self textMarkerRangeFromVisiblePositions:startPosition andEndPos:endPosition];
1753 - (id)doAXParagraphTextMarkerRangeForTextMarker: (WebCoreTextMarker*) textMarker
1755 VisiblePosition visiblePos = [self visiblePositionForTextMarker:textMarker];
1756 VisiblePosition startPosition = startOfParagraph(visiblePos);
1757 VisiblePosition endPosition = endOfParagraph(startPosition);
1758 return (id) [self textMarkerRangeFromVisiblePositions:startPosition andEndPos:endPosition];
1761 - (id)doAXNextWordEndTextMarkerForTextMarker: (WebCoreTextMarker*) textMarker
1763 VisiblePosition visiblePos = [self visiblePositionForTextMarker:textMarker];
1764 if (visiblePos.isNull())
1767 // make sure we move off of a word end
1768 visiblePos = visiblePos.next();
1769 if (visiblePos.isNull())
1772 VisiblePosition endPosition = endOfWord(visiblePos, LeftWordIfOnBoundary);
1773 return (id) [self textMarkerForVisiblePosition:endPosition];
1776 - (id)doAXPreviousWordStartTextMarkerForTextMarker: (WebCoreTextMarker*) textMarker
1778 VisiblePosition visiblePos = [self visiblePositionForTextMarker:textMarker];
1779 if (visiblePos.isNull())
1782 // make sure we move off of a word start
1783 visiblePos = visiblePos.previous();
1784 if (visiblePos.isNull())
1787 VisiblePosition startPosition = startOfWord(visiblePos, RightWordIfOnBoundary);
1788 return (id) [self textMarkerForVisiblePosition:startPosition];
1791 - (id)doAXNextLineEndTextMarkerForTextMarker: (WebCoreTextMarker*) textMarker
1793 VisiblePosition visiblePos = [self visiblePositionForTextMarker:textMarker];
1794 if (visiblePos.isNull())
1797 // to make sure we move off of a line end
1798 VisiblePosition nextVisiblePos = visiblePos.next();
1799 if (nextVisiblePos.isNull())
1802 VisiblePosition endPosition = endOfLine(nextVisiblePos);
1803 return (id) [self textMarkerForVisiblePosition: endPosition];
1806 - (id)doAXPreviousLineStartTextMarkerForTextMarker: (WebCoreTextMarker*) textMarker
1808 VisiblePosition visiblePos = [self visiblePositionForTextMarker:textMarker];
1809 if (visiblePos.isNull())
1812 // make sure we move off of a line start
1813 VisiblePosition prevVisiblePos = visiblePos.previous();
1814 if (prevVisiblePos.isNull())
1817 VisiblePosition startPosition = startOfLine(prevVisiblePos);
1818 return (id) [self textMarkerForVisiblePosition: startPosition];
1821 - (id)doAXNextSentenceEndTextMarkerForTextMarker: (WebCoreTextMarker*) textMarker
1823 // NOTE: BUG FO 2 IMPLEMENT (currently returns incorrect answer)
1824 // Related? <rdar://problem/3927736> Text selection broken in 8A336
1825 VisiblePosition visiblePos = [self visiblePositionForTextMarker:textMarker];
1826 if (visiblePos.isNull())
1829 // make sure we move off of a sentence end
1830 visiblePos = visiblePos.next();
1831 if (visiblePos.isNull())
1834 VisiblePosition endPosition = endOfSentence(visiblePos);
1835 return (id) [self textMarkerForVisiblePosition: endPosition];
1838 - (id)doAXPreviousSentenceStartTextMarkerForTextMarker: (WebCoreTextMarker*) textMarker
1840 // NOTE: BUG FO 2 IMPLEMENT (currently returns incorrect answer)
1841 // Related? <rdar://problem/3927736> Text selection broken in 8A336
1842 VisiblePosition visiblePos = [self visiblePositionForTextMarker:textMarker];
1843 if (visiblePos.isNull())
1846 // make sure we move off of a sentence start
1847 visiblePos = visiblePos.previous();
1848 if (visiblePos.isNull())
1851 VisiblePosition startPosition = startOfSentence(visiblePos);
1852 return (id) [self textMarkerForVisiblePosition: startPosition];
1855 - (id)doAXNextParagraphEndTextMarkerForTextMarker: (WebCoreTextMarker*) textMarker
1857 VisiblePosition visiblePos = [self visiblePositionForTextMarker:textMarker];
1858 if (visiblePos.isNull())
1861 // make sure we move off of a paragraph end
1862 visiblePos = visiblePos.next();
1863 if (visiblePos.isNull())
1866 VisiblePosition endPosition = endOfParagraph(visiblePos);
1867 return (id) [self textMarkerForVisiblePosition: endPosition];
1870 - (id)doAXPreviousParagraphStartTextMarkerForTextMarker: (WebCoreTextMarker*) textMarker
1872 VisiblePosition visiblePos = [self visiblePositionForTextMarker:textMarker];
1873 if (visiblePos.isNull())
1876 // make sure we move off of a paragraph start
1877 visiblePos = visiblePos.previous();
1878 if (visiblePos.isNull())
1881 VisiblePosition startPosition = startOfParagraph(visiblePos);
1882 return (id) [self textMarkerForVisiblePosition: startPosition];
1885 static VisiblePosition startOfStyleRange (const VisiblePosition visiblePos)
1887 RenderObject* renderer = visiblePos.deepEquivalent().node()->renderer();
1888 RenderObject* startRenderer = renderer;
1889 RenderStyle* style = renderer->style();
1891 // traverse backward by renderer to look for style change
1892 for (RenderObject* r = renderer->previousInPreOrder(); r; r = r->previousInPreOrder()) {
1893 // skip non-leaf nodes
1894 if (r->firstChild())
1897 // stop at style change
1898 if (r->style() != style)
1905 return VisiblePosition(startRenderer->node(), 0, VP_DEFAULT_AFFINITY);
1908 static VisiblePosition endOfStyleRange (const VisiblePosition visiblePos)
1910 RenderObject* renderer = visiblePos.deepEquivalent().node()->renderer();
1911 RenderObject* endRenderer = renderer;
1912 RenderStyle* style = renderer->style();
1914 // traverse forward by renderer to look for style change
1915 for (RenderObject* r = renderer->nextInPreOrder(); r; r = r->nextInPreOrder()) {
1916 // skip non-leaf nodes
1917 if (r->firstChild())
1920 // stop at style change
1921 if (r->style() != style)
1928 return VisiblePosition(endRenderer->node(), maxDeepOffset(endRenderer->node()), VP_DEFAULT_AFFINITY);
1931 - (id)doAXStyleTextMarkerRangeForTextMarker: (WebCoreTextMarker*) textMarker
1933 VisiblePosition visiblePos = [self visiblePositionForTextMarker:textMarker];
1934 if (visiblePos.isNull())
1937 VisiblePosition startPosition = startOfStyleRange(visiblePos);
1938 VisiblePosition endPosition = endOfStyleRange(visiblePos);
1939 return (id) [self textMarkerRangeFromVisiblePositions:startPosition andEndPos:endPosition];
1942 - (id)doAXLengthForTextMarkerRange: (WebCoreTextMarkerRange*) textMarkerRange
1944 // NOTE: BUG Multi-byte support
1945 CFStringRef string = (CFStringRef) [self doAXStringForTextMarkerRange: textMarkerRange];
1949 return [NSNumber numberWithInt:CFStringGetLength(string)];
1952 // NOTE: Consider providing this utility method as AX API
1953 - (WebCoreTextMarker*)textMarkerForIndex: (NSNumber*) index lastIndexOK: (BOOL)lastIndexOK
1955 ASSERT(m_renderer->isTextField() || m_renderer->isTextArea());
1956 RenderTextControl* textControl = static_cast<RenderTextControl*>(m_renderer);
1957 unsigned int indexValue = [index unsignedIntValue];
1959 // lastIndexOK specifies whether the position after the last character is acceptable
1960 if (indexValue >= textControl->text().length()) {
1961 if (!lastIndexOK || indexValue > textControl->text().length())
1964 VisiblePosition position = textControl->visiblePositionForIndex(indexValue);
1965 position.setAffinity(DOWNSTREAM);
1966 return [self textMarkerForVisiblePosition: position];
1969 // NOTE: Consider providing this utility method as AX API
1970 - (NSNumber*)indexForTextMarker: (WebCoreTextMarker*) marker
1972 ASSERT(m_renderer->isTextField() || m_renderer->isTextArea());
1973 RenderTextControl* textControl = static_cast<RenderTextControl*>(m_renderer);
1975 VisiblePosition position = [self visiblePositionForTextMarker: marker];
1976 Node* node = position.deepEquivalent().node();
1980 for (RenderObject* renderer = node->renderer(); renderer && renderer->element(); renderer = renderer->parent()) {
1981 if (renderer == textControl)
1982 return [NSNumber numberWithInt: textControl->indexForVisiblePosition(position)];
1988 // NOTE: Consider providing this utility method as AX API
1989 - (WebCoreTextMarkerRange*)textMarkerRangeForRange: (NSRange) range
1991 ASSERT(m_renderer->isTextField() || m_renderer->isTextArea());
1992 RenderTextControl* textControl = static_cast<RenderTextControl*>(m_renderer);
1993 if (range.location + range.length >= textControl->text().length())
1996 VisiblePosition startPosition = textControl->visiblePositionForIndex(range.location);
1997 startPosition.setAffinity(DOWNSTREAM);
1998 VisiblePosition endPosition = textControl->visiblePositionForIndex(range.location + range.length);
1999 return [self textMarkerRangeFromVisiblePositions:startPosition andEndPos:endPosition];
2002 // NOTE: Consider providing this utility method as AX API
2003 - (NSValue*)rangeForTextMarkerRange: (WebCoreTextMarkerRange*) textMarkerRange
2005 WebCoreTextMarker* textMarker1 = [[WebCoreViewFactory sharedFactory] startOfTextMarkerRange:textMarkerRange];
2006 WebCoreTextMarker* textMarker2 = [[WebCoreViewFactory sharedFactory] endOfTextMarkerRange:textMarkerRange];
2007 NSNumber* index1 = [self indexForTextMarker: textMarker1];
2008 NSNumber* index2 = [self indexForTextMarker: textMarker2];
2009 if (!index1 || !index2 || [index1 unsignedIntValue] > [index2 unsignedIntValue])
2012 return [NSValue valueWithRange: NSMakeRange([index1 unsignedIntValue], [index2 unsignedIntValue] - [index1 unsignedIntValue])];
2015 // Given an indexed character, the line number of the text associated with this accessibility
2016 // object that contains the character.
2017 - (id)doAXLineForIndex: (NSNumber*) index
2019 return [self doAXLineForTextMarker: [self textMarkerForIndex: index lastIndexOK: NO]];
2022 // Given a line number, the range of characters of the text associated with this accessibility
2023 // object that contains the line number.
2024 - (id)doAXRangeForLine: (NSNumber*) lineNumber
2026 ASSERT(m_renderer->isTextField() || m_renderer->isTextArea());
2027 RenderTextControl* textControl = static_cast<RenderTextControl*>(m_renderer);
2029 // iterate to the specified line
2030 VisiblePosition visiblePos = textControl->visiblePositionForIndex(0);
2031 VisiblePosition savedVisiblePos;
2032 for (unsigned lineCount = [lineNumber unsignedIntValue]; lineCount != 0; lineCount -= 1) {
2033 savedVisiblePos = visiblePos;
2034 visiblePos = nextLinePosition(visiblePos, 0);
2035 if (visiblePos.isNull() || visiblePos == savedVisiblePos)
2039 // make a caret selection for the marker position, then extend it to the line
2040 // NOTE: ignores results of selectionController.modify because it returns false when
2041 // starting at an empty line. The resulting selection in that case
2042 // will be a caret at visiblePos.
2043 SelectionController selectionController;
2044 selectionController.setSelection(Selection(visiblePos));
2045 selectionController.modify(SelectionController::EXTEND, SelectionController::LEFT, LineBoundary);
2046 selectionController.modify(SelectionController::EXTEND, SelectionController::RIGHT, LineBoundary);
2048 // calculate the indices for the selection start and end
2049 VisiblePosition startPosition = selectionController.selection().visibleStart();
2050 VisiblePosition endPosition = selectionController.selection().visibleEnd();
2051 int index1 = textControl->indexForVisiblePosition(startPosition);
2052 int index2 = textControl->indexForVisiblePosition(endPosition);
2054 // add one to the end index for a line break not caused by soft line wrap (to match AppKit)
2055 if (endPosition.affinity() == DOWNSTREAM && endPosition.next().isNotNull())
2058 // return nil rather than an zero-length range (to match AppKit)
2059 if (index1 == index2)
2062 return [NSValue valueWithRange: NSMakeRange(index1, index2 - index1)];
2065 // A substring of the text associated with this accessibility object that is
2066 // specified by the given character range.
2067 - (id)doAXStringForRange: (NSRange) range
2069 if (range.length == 0)
2072 ASSERT(m_renderer->isTextField() || m_renderer->isTextArea());
2073 RenderTextControl* textControl = static_cast<RenderTextControl*>(m_renderer);
2074 String text = textControl->text();
2075 if (range.location + range.length > text.length())
2078 return text.substring(range.location, range.length);
2081 // The composed character range in the text associated with this accessibility object that
2082 // is specified by the given screen coordinates. This parameterized attribute returns the
2083 // complete range of characters (including surrogate pairs of multi-byte glyphs) at the given
2084 // screen coordinates.
2085 // NOTE: This varies from AppKit when the point is below the last line. AppKit returns an
2086 // an error in that case. We return textControl->text().length(), 1. Does this matter?
2087 - (id)doAXRangeForPosition: (NSPoint) point
2089 NSNumber* index = [self indexForTextMarker: [self doAXTextMarkerForPosition: point]];
2093 return [NSValue valueWithRange: NSMakeRange([index unsignedIntValue], 1)];
2096 // The composed character range in the text associated with this accessibility object that
2097 // is specified by the given index value. This parameterized attribute returns the complete
2098 // range of characters (including surrogate pairs of multi-byte glyphs) at the given index.
2099 - (id)doAXRangeForIndex: (NSNumber*) number
2101 ASSERT(m_renderer->isTextField() || m_renderer->isTextArea());
2102 RenderTextControl* textControl = static_cast<RenderTextControl*>(m_renderer);
2103 String text = textControl->text();
2104 if (!text.length() || [number unsignedIntValue] > text.length() - 1)
2107 return [NSValue valueWithRange: NSMakeRange([number unsignedIntValue], 1)];
2110 // The bounding rectangle of the text associated with this accessibility object that is
2111 // specified by the given range. This is the bounding rectangle a sighted user would see
2112 // on the display screen, in pixels.
2113 - (id)doAXBoundsForRange: (NSRange) range
2115 return [self doAXBoundsForTextMarkerRange: [self textMarkerRangeForRange:range]];
2118 // The CFAttributedStringType representation of the text associated with this accessibility
2119 // object that is specified by the given range.
2120 - (id)doAXAttributedStringForRange: (NSRange) range
2122 return [self doAXAttributedStringForTextMarkerRange: [self textMarkerRangeForRange:range]];
2125 // The RTF representation of the text associated with this accessibility object that is
2126 // specified by the given range.
2127 - (id)doAXRTFForRange: (NSRange) range
2129 NSAttributedString* attrString = [self doAXAttributedStringForRange: range];
2130 return [attrString RTFFromRange: NSMakeRange(0, [attrString length]) documentAttributes: nil];
2133 // Given a character index, the range of text associated with this accessibility object
2134 // over which the style in effect at that character index applies.
2135 - (id)doAXStyleRangeForIndex: (NSNumber*) index
2137 WebCoreTextMarkerRange* textMarkerRange = [self doAXStyleTextMarkerRangeForTextMarker: [self textMarkerForIndex: index lastIndexOK: NO]];
2138 return [self rangeForTextMarkerRange: textMarkerRange];
2141 - (id)accessibilityAttributeValue:(NSString*)attribute forParameter:(id)parameter
2143 WebCoreTextMarker* textMarker = nil;
2144 WebCoreTextMarkerRange* textMarkerRange = nil;
2145 NSNumber* number = nil;
2146 NSArray* array = nil;
2147 WebCoreAXObject* uiElement = nil;
2148 NSPoint point = {0.0, 0.0};
2149 bool pointSet = false;
2150 NSRange range = {0, 0};
2151 bool rangeSet = false;
2153 // basic parameter validation
2154 if (!m_renderer || !attribute || !parameter)
2157 // common parameter type check/casting. Nil checks in handlers catch wrong type case.
2158 // NOTE: This assumes nil is not a valid parameter, because it is indistinguishable from
2159 // a parameter of the wrong type.
2160 if ([[WebCoreViewFactory sharedFactory] objectIsTextMarker:parameter])
2161 textMarker = (WebCoreTextMarker*) parameter;
2163 else if ([[WebCoreViewFactory sharedFactory] objectIsTextMarkerRange:parameter])
2164 textMarkerRange = (WebCoreTextMarkerRange*) parameter;
2166 else if ([parameter isKindOfClass:[WebCoreAXObject self]])
2167 uiElement = (WebCoreAXObject*) parameter;
2169 else if ([parameter isKindOfClass:[NSNumber self]])
2172 else if ([parameter isKindOfClass:[NSArray self]])
2175 else if ([parameter isKindOfClass:[NSValue self]] && strcmp([(NSValue*)parameter objCType], @encode(NSPoint)) == 0) {
2177 point = [(NSValue*)parameter pointValue];
2179 } else if ([parameter isKindOfClass:[NSValue self]] && strcmp([(NSValue*)parameter objCType], @encode(NSRange)) == 0) {
2181 range = [(NSValue*)parameter rangeValue];
2184 // got a parameter of a type we never use
2185 // NOTE: No ASSERT_NOT_REACHED because this can happen accidentally
2186 // while using accesstool (e.g.), forcing you to start over
2191 if ([attribute isEqualToString: @"AXUIElementForTextMarker"])
2192 return [self doAXUIElementForTextMarker: textMarker];
2194 if ([attribute isEqualToString: @"AXTextMarkerRangeForUIElement"])
2195 return [self doAXTextMarkerRangeForUIElement: uiElement];
2197 if ([attribute isEqualToString: @"AXLineForTextMarker"])
2198 return [self doAXLineForTextMarker: textMarker];
2200 if ([attribute isEqualToString: @"AXTextMarkerRangeForLine"])
2201 return [self doAXTextMarkerRangeForLine: number];
2203 if ([attribute isEqualToString: @"AXStringForTextMarkerRange"])
2204 return [self doAXStringForTextMarkerRange: textMarkerRange];
2206 if ([attribute isEqualToString: @"AXTextMarkerForPosition"])
2207 return pointSet ? [self doAXTextMarkerForPosition: point] : nil;
2209 if ([attribute isEqualToString: @"AXBoundsForTextMarkerRange"])
2210 return [self doAXBoundsForTextMarkerRange: textMarkerRange];
2212 if ([attribute isEqualToString: @"AXAttributedStringForTextMarkerRange"])
2213 return [self doAXAttributedStringForTextMarkerRange: textMarkerRange];
2215 if ([attribute isEqualToString: @"AXTextMarkerRangeForUnorderedTextMarkers"])
2216 return [self doAXTextMarkerRangeForUnorderedTextMarkers: array];
2218 if ([attribute isEqualToString: @"AXNextTextMarkerForTextMarker"])
2219 return [self doAXNextTextMarkerForTextMarker: textMarker];
2221 if ([attribute isEqualToString: @"AXPreviousTextMarkerForTextMarker"])
2222 return [self doAXPreviousTextMarkerForTextMarker: textMarker];
2224 if ([attribute isEqualToString: @"AXLeftWordTextMarkerRangeForTextMarker"])
2225 return [self doAXLeftWordTextMarkerRangeForTextMarker: textMarker];
2227 if ([attribute isEqualToString: @"AXRightWordTextMarkerRangeForTextMarker"])
2228 return [self doAXRightWordTextMarkerRangeForTextMarker: textMarker];
2230 if ([attribute isEqualToString: @"AXLeftLineTextMarkerRangeForTextMarker"])
2231 return [self doAXLeftLineTextMarkerRangeForTextMarker: textMarker];
2233 if ([attribute isEqualToString: @"AXRightLineTextMarkerRangeForTextMarker"])
2234 return [self doAXRightLineTextMarkerRangeForTextMarker: textMarker];
2236 if ([attribute isEqualToString: @"AXSentenceTextMarkerRangeForTextMarker"])
2237 return [self doAXSentenceTextMarkerRangeForTextMarker: textMarker];
2239 if ([attribute isEqualToString: @"AXParagraphTextMarkerRangeForTextMarker"])
2240 return [self doAXParagraphTextMarkerRangeForTextMarker: textMarker];
2242 if ([attribute isEqualToString: @"AXNextWordEndTextMarkerForTextMarker"])
2243 return [self doAXNextWordEndTextMarkerForTextMarker: textMarker];
2245 if ([attribute isEqualToString: @"AXPreviousWordStartTextMarkerForTextMarker"])
2246 return [self doAXPreviousWordStartTextMarkerForTextMarker: textMarker];
2248 if ([attribute isEqualToString: @"AXNextLineEndTextMarkerForTextMarker"])
2249 return [self doAXNextLineEndTextMarkerForTextMarker: textMarker];
2251 if ([attribute isEqualToString: @"AXPreviousLineStartTextMarkerForTextMarker"])
2252 return [self doAXPreviousLineStartTextMarkerForTextMarker: textMarker];
2254 if ([attribute isEqualToString: @"AXNextSentenceEndTextMarkerForTextMarker"])
2255 return [self doAXNextSentenceEndTextMarkerForTextMarker: textMarker];
2257 if ([attribute isEqualToString: @"AXPreviousSentenceStartTextMarkerForTextMarker"])
2258 return [self doAXPreviousSentenceStartTextMarkerForTextMarker: textMarker];
2260 if ([attribute isEqualToString: @"AXNextParagraphEndTextMarkerForTextMarker"])
2261 return [self doAXNextParagraphEndTextMarkerForTextMarker: textMarker];
2263 if ([attribute isEqualToString: @"AXPreviousParagraphStartTextMarkerForTextMarker"])
2264 return [self doAXPreviousParagraphStartTextMarkerForTextMarker: textMarker];
2266 if ([attribute isEqualToString: @"AXStyleTextMarkerRangeForTextMarker"])
2267 return [self doAXStyleTextMarkerRangeForTextMarker: textMarker];
2269 if ([attribute isEqualToString: @"AXLengthForTextMarkerRange"])
2270 return [self doAXLengthForTextMarkerRange: textMarkerRange];
2272 if ([self isTextControl]) {
2273 if ([attribute isEqualToString: (NSString*)kAXLineForIndexParameterizedAttribute])
2274 return [self doAXLineForIndex: number];
2276 if ([attribute isEqualToString: (NSString*)kAXRangeForLineParameterizedAttribute])
2277 return [self doAXRangeForLine: number];
2279 if ([attribute isEqualToString: (NSString*)kAXStringForRangeParameterizedAttribute])
2280 return rangeSet ? [self doAXStringForRange: range] : nil;
2282 if ([attribute isEqualToString: (NSString*)kAXRangeForPositionParameterizedAttribute])
2283 return pointSet ? [self doAXRangeForPosition: point] : nil;
2285 if ([attribute isEqualToString: (NSString*)kAXRangeForIndexParameterizedAttribute])
2286 return [self doAXRangeForIndex: number];
2288 if ([attribute isEqualToString: (NSString*)kAXBoundsForRangeParameterizedAttribute])
2289 return rangeSet ? [self doAXBoundsForRange: range] : nil;
2291 if ([attribute isEqualToString: (NSString*)kAXRTFForRangeParameterizedAttribute])
2292 return rangeSet ? [self doAXRTFForRange: range] : nil;
2294 if ([attribute isEqualToString: (NSString*)kAXAttributedStringForRangeParameterizedAttribute])
2295 return rangeSet ? [self doAXAttributedStringForRange: range] : nil;
2297 if ([attribute isEqualToString: (NSString*)kAXStyleRangeForIndexParameterizedAttribute])
2298 return [self doAXStyleRangeForIndex: number];
2304 - (id)accessibilityHitTest:(NSPoint)point
2307 return NSAccessibilityUnignoredAncestor(self);
2309 HitTestRequest request(true, true);
2310 HitTestResult result = HitTestResult(IntPoint(point));
2311 m_renderer->layer()->hitTest(request, result);
2312 if (!result.innerNode())
2313 return NSAccessibilityUnignoredAncestor(self);
2314 Node* node = result.innerNode()->shadowAncestorNode();
2315 RenderObject* obj = node->renderer();
2317 return NSAccessibilityUnignoredAncestor(self);
2319 return NSAccessibilityUnignoredAncestor(obj->document()->axObjectCache()->get(obj));
2322 - (RenderObject*)rendererForView:(NSView*)view
2324 // check for WebCore NSView that lets us find its widget
2325 Frame* frame = m_renderer->document()->frame();
2327 DOMElement* domElement = [Mac(frame)->bridge() elementForView:view];
2329 return [domElement _element]->renderer();
2332 // check for WebKit NSView that lets us find its bridge
2333 WebCoreFrameBridge* bridge = nil;
2334 if ([view conformsToProtocol:@protocol(WebCoreBridgeHolder)]) {
2335 NSView<WebCoreBridgeHolder>* bridgeHolder = (NSView<WebCoreBridgeHolder>*)view;
2336 bridge = [bridgeHolder webCoreBridge];
2339 FrameMac* frameMac = [bridge _frame];
2343 Document* document = frameMac->document();
2347 Node* node = document->ownerElement();
2351 return node->renderer();
2354 // _accessibilityParentForSubview is called by AppKit when moving up the tree
2355 // we override it so that we can return our WebCoreAXObject parent of an AppKit AX object
2356 - (id)_accessibilityParentForSubview:(NSView*)subview
2358 RenderObject* renderer = [self rendererForView:subview];
2362 WebCoreAXObject* obj = renderer->document()->axObjectCache()->get(renderer);
2363 return [obj parentObjectUnignored];
2366 - (id)accessibilityFocusedUIElement
2368 // NOTE: BUG support nested WebAreas
2369 Node* focusNode = m_renderer->document()->focusNode();
2370 if (!focusNode || !focusNode->renderer())
2373 WebCoreAXObject* obj = focusNode->renderer()->document()->axObjectCache()->get(focusNode->renderer());
2375 // the HTML element, for example, is focusable but has an AX object that is ignored
2376 if ([obj accessibilityIsIgnored])
2377 obj = [obj parentObjectUnignored];
2382 - (void)doSetAXSelectedTextMarkerRange: (WebCoreTextMarkerRange*)textMarkerRange
2384 // extract the start and end VisiblePosition
2385 VisiblePosition startVisiblePosition = [self visiblePositionForStartOfTextMarkerRange: textMarkerRange];
2386 if (startVisiblePosition.isNull())
2389 VisiblePosition endVisiblePosition = [self visiblePositionForEndOfTextMarkerRange: textMarkerRange];
2390 if (endVisiblePosition.isNull())
2393 // make selection and tell the document to use it
2394 // NOTE: BUG support nested WebAreas
2395 Selection newSelection = Selection(startVisiblePosition, endVisiblePosition);
2396 [self topDocument]->frame()->selectionController()->setSelection(newSelection);
2399 - (BOOL)canSetFocusAttribute
2401 // NOTE: It would be more accurate to ask the document whether setFocusNode() would
2402 // do anything. For example, it setFocusNode() will do nothing if the current focused
2403 // node will not relinquish the focus.
2404 if (!m_renderer->element() || !m_renderer->element()->isEnabled())
2407 NSString* role = [self role];
2408 if ([role isEqualToString:@"AXLink"] ||
2409 [role isEqualToString:NSAccessibilityTextFieldRole] ||
2410 [role isEqualToString:NSAccessibilityTextAreaRole] ||
2411 [role isEqualToString:NSAccessibilityButtonRole] ||
2412 [role isEqualToString:NSAccessibilityPopUpButtonRole] ||
2413 [role isEqualToString:NSAccessibilityCheckBoxRole] ||
2414 [role isEqualToString:NSAccessibilityRadioButtonRole])
2420 - (BOOL)canSetValueAttribute
2422 return [self isTextControl];
2425 - (BOOL)canSetTextRangeAttributes
2427 return [self isTextControl];
2430 - (BOOL)accessibilityIsAttributeSettable:(NSString*)attributeName
2432 if ([attributeName isEqualToString: @"AXSelectedTextMarkerRangeAttribute"])
2435 if ([attributeName isEqualToString: NSAccessibilityFocusedAttribute])
2436 return [self canSetFocusAttribute];
2438 if ([attributeName isEqualToString: NSAccessibilityValueAttribute])
2439 return [self canSetValueAttribute];
2441 if ([attributeName isEqualToString: NSAccessibilitySelectedTextAttribute] ||
2442 [attributeName isEqualToString: NSAccessibilitySelectedTextRangeAttribute] ||
2443 [attributeName isEqualToString: NSAccessibilityVisibleCharacterRangeAttribute])
2444 return [self canSetTextRangeAttributes];
2449 - (void)accessibilitySetValue:(id)value forAttribute:(NSString*)attributeName
2451 WebCoreTextMarkerRange* textMarkerRange = nil;
2452 NSNumber* number = nil;
2453 NSString* string = nil;
2454 NSRange range = {0, 0};
2456 // decode the parameter
2457 if ([[WebCoreViewFactory sharedFactory] objectIsTextMarkerRange:value])
2458 textMarkerRange = (WebCoreTextMarkerRange*) value;
2460 else if ([value isKindOfClass:[NSNumber self]])
2463 else if ([value isKindOfClass:[NSString self]])
2466 else if ([value isKindOfClass:[NSValue self]])
2467 range = [value rangeValue];
2469 // handle the command
2470 if ([attributeName isEqualToString: @"AXSelectedTextMarkerRange"]) {
2471 ASSERT(textMarkerRange);
2472 [self doSetAXSelectedTextMarkerRange:textMarkerRange];
2474 } else if ([attributeName isEqualToString: NSAccessibilityFocusedAttribute]) {
2476 if ([self canSetFocusAttribute] && number) {
2477 if ([number intValue] == 0)
2478 m_renderer->document()->setFocusNode(0);
2480 if (m_renderer->element()->isElementNode())
2481 static_cast<Element*>(m_renderer->element())->focus();
2483 m_renderer->document()->setFocusNode(m_renderer->element());
2486 } else if ([attributeName isEqualToString: NSAccessibilityValueAttribute]) {
2489 if (m_renderer->isTextField()) {
2490 HTMLInputElement* input = static_cast<HTMLInputElement*>(m_renderer->element());
2491 input->setValue(string);
2492 } else if (m_renderer->isTextArea()) {
2493 HTMLTextAreaElement* textArea = static_cast<HTMLTextAreaElement*>(m_renderer->element());
2494 textArea->setValue(string);
2496 } else if ([self isTextControl]) {
2497 RenderTextControl* textControl = static_cast<RenderTextControl*>(m_renderer);
2498 if ([attributeName isEqualToString: NSAccessibilitySelectedTextAttribute]) {
2499 // TODO: set selected text (ReplaceSelectionCommand). <rdar://problem/4712125>
2500 } else if ([attributeName isEqualToString: NSAccessibilitySelectedTextRangeAttribute]) {
2501 textControl->setSelectionRange(range.location, range.location + range.length);
2502 } else if ([attributeName isEqualToString: NSAccessibilityVisibleCharacterRangeAttribute]) {
2503 // TODO: make range visible (scrollRectToVisible). <rdar://problem/4712101>
2508 - (WebCoreAXObject*)observableObject
2510 for (RenderObject* renderer = m_renderer; renderer && renderer->element(); renderer = renderer->parent()) {
2511 if (renderer->isTextField() || renderer->isTextArea())
2512 return renderer->document()->axObjectCache()->get(renderer);
2518 - (void)childrenChanged
2520 [self clearChildren];
2522 if ([self accessibilityIsIgnored])
2523 [[self parentObject] childrenChanged];
2526 - (void)clearChildren
2528 [m_children release];
2537 -(void)setAXObjectID:(AXID) axObjectID
2542 - (void)removeAXObjectID
2547 m_renderer->document()->axObjectCache()->removeAXID(self);