office-gobmx/vcl/osx/a11ytextwrapper.mm
Patrick Luby 0735a4306d Related tdf#158914: fix memory leaks by calling (auto)release selectors
Found the following memory leaks using Xcode's Instruments application:

1. Posting an NSAccessibilityUIElementDestroyedNotification
   notification causes [ AquaA11yWrapper isAccessibilityElement ]
   to be called on the object so mark the object as disposed
   before posting the destroyed notification and test for disposed
   in all of the standard NSAccessibility selectors to prevent
   any calls to likely disposed C++ accessibility objects.

2. In [ AquaA11yWrapper accessibilityHitTest: ],
   [ AquaA11yFactory wrapperForAccessibleContext: ] already retains
   the returned object so retaining it until the next call to this
   selector can lead to a memory leak when dragging selected cells
   in Calc to a new location. So autorelease the object so that
   transient objects stay alive but not past the next clearing of
   the autorelease pool.

3. [ AquaA11ySelectionWrapper selectedChildrenAttributeForElement: ] is
   expected to return an autoreleased object.

4. [ AquaA11yFactory wrapperForAccessible: ] is not a getter. It
   expects the caller to release the returned object.

5. CreateNSString() is not a getter. It expects the caller to
   release the returned string.

Change-Id: I824740d7e3851b0c3e31e2c009860aa822c94222
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/168034
Reviewed-by: Patrick Luby <guibomacdev@gmail.com>
Reviewed-by: Noel Grandin <noel.grandin@collabora.co.uk>
Tested-by: Jenkins
2024-05-26 09:52:41 +02:00

305 lines
13 KiB
Text

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This file is part of the LibreOffice project.
*
* 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/.
*
* This file incorporates work covered by the following license notice:
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed
* with this work for additional information regarding copyright
* ownership. The ASF licenses this file to you under the Apache
* License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
*/
#include <osx/salinst.h>
#include <quartz/utils.h>
#include "a11ytextwrapper.h"
#include "a11ytextattributeswrapper.h"
#include "a11yutil.h"
#include <com/sun/star/accessibility/AccessibleTextType.hpp>
#include <com/sun/star/awt/Rectangle.hpp>
#include <com/sun/star/lang/IllegalArgumentException.hpp>
#include <com/sun/star/lang/IndexOutOfBoundsException.hpp>
using namespace ::com::sun::star::accessibility;
using namespace ::com::sun::star::awt;
using namespace ::com::sun::star::lang;
using namespace ::com::sun::star::uno;
// Wrapper for XAccessibleText, XAccessibleEditableText and XAccessibleMultiLineText
@implementation AquaA11yTextWrapper : NSObject
+(id)valueAttributeForElement:(AquaA11yWrapper *)wrapper {
// Related tdf#158914: explicitly call autorelease selector
// CreateNSString() is not a getter. It expects the caller to
// release the returned string.
return [ CreateNSString ( [ wrapper accessibleText ] -> getText() ) autorelease ];
}
+(void)setValueAttributeForElement:(AquaA11yWrapper *)wrapper to:(id)value
{
// TODO
(void)wrapper;
(void)value;
}
+(id)numberOfCharactersAttributeForElement:(AquaA11yWrapper *)wrapper {
return [ NSNumber numberWithLong: [ wrapper accessibleText ] -> getCharacterCount() ];
}
+(id)selectedTextAttributeForElement:(AquaA11yWrapper *)wrapper {
// Related tdf#158914: explicitly call autorelease selector
// CreateNSString() is not a getter. It expects the caller to
// release the returned string.
return [ CreateNSString ( [ wrapper accessibleText ] -> getSelectedText() ) autorelease ];
}
+(void)setSelectedTextAttributeForElement:(AquaA11yWrapper *)wrapper to:(id)value {
if ( [ wrapper accessibleEditableText ] ) {
NSAutoreleasePool * pool = [ [ NSAutoreleasePool alloc ] init ];
OUString newText = GetOUString ( static_cast<NSString *>(value) );
NSRange selectedTextRange = [ [ AquaA11yTextWrapper selectedTextRangeAttributeForElement: wrapper ] rangeValue ];
try {
[ wrapper accessibleEditableText ] -> replaceText ( selectedTextRange.location, selectedTextRange.location + selectedTextRange.length, newText );
} catch ( const Exception & ) {
// empty
}
[ pool release ];
}
}
+(id)selectedTextRangeAttributeForElement:(AquaA11yWrapper *)wrapper {
sal_Int32 start = [ wrapper accessibleText ] -> getSelectionStart();
sal_Int32 end = [ wrapper accessibleText ] -> getSelectionEnd();
if ( start != end ) {
return [ NSValue valueWithRange: NSMakeRange ( start, end - start ) ]; // true selection
} else {
sal_Int32 caretPos = [ wrapper accessibleText ] -> getCaretPosition();
if ( caretPos < 0 || caretPos > [ wrapper accessibleText ] -> getCharacterCount() ) {
return nil;
}
return [ NSValue valueWithRange: NSMakeRange ( caretPos, 0 ) ]; // insertion point
}
}
+(void)setSelectedTextRangeAttributeForElement:(AquaA11yWrapper *)wrapper to:(id)value {
NSRange range = [ value rangeValue ];
try {
[ wrapper accessibleText ] -> setSelection ( range.location, range.location + range.length );
} catch ( const Exception & ) {
// empty
}
}
+(id)visibleCharacterRangeAttributeForElement:(AquaA11yWrapper *)wrapper {
// the OOo a11y API returns only the visible portion...
return [ NSValue valueWithRange: NSMakeRange ( 0, [ wrapper accessibleText ] -> getCharacterCount() ) ];
}
+(void)setVisibleCharacterRangeAttributeForElement:(AquaA11yWrapper *)wrapper to:(id)value
{
// do nothing
(void)wrapper;
(void)value;
}
+(id)sharedTextUIElementsAttributeForElement:(AquaA11yWrapper *)wrapper
{
return [NSArray arrayWithObject:wrapper];
}
+(id)sharedCharacterRangeAttributeForElement:(AquaA11yWrapper *)wrapper
{
return [ NSValue valueWithRange: NSMakeRange ( 0, [wrapper accessibleText]->getCharacterCount() ) ];
}
+(void)addAttributeNamesTo:(NSMutableArray *)attributeNames {
[ attributeNames addObjectsFromArray: [ AquaA11yTextWrapper specialAttributeNames ] ];
}
+(NSArray *)specialAttributeNames {
return [ NSArray arrayWithObjects:
NSAccessibilityValueAttribute,
NSAccessibilityNumberOfCharactersAttribute,
NSAccessibilitySelectedTextAttribute,
NSAccessibilitySelectedTextRangeAttribute,
NSAccessibilityVisibleCharacterRangeAttribute,
NSAccessibilitySharedTextUIElementsAttribute,
NSAccessibilitySharedCharacterRangeAttribute,
nil ];
}
+(void)addParameterizedAttributeNamesTo:(NSMutableArray *)attributeNames {
[ attributeNames addObjectsFromArray: [ AquaA11yTextWrapper specialParameterizedAttributeNames ] ];
}
+(NSArray *)specialParameterizedAttributeNames {
return [ NSArray arrayWithObjects:
NSAccessibilityStringForRangeParameterizedAttribute,
NSAccessibilityAttributedStringForRangeParameterizedAttribute,
NSAccessibilityRangeForIndexParameterizedAttribute,
NSAccessibilityRangeForPositionParameterizedAttribute,
NSAccessibilityBoundsForRangeParameterizedAttribute,
NSAccessibilityStyleRangeForIndexParameterizedAttribute,
NSAccessibilityRTFForRangeParameterizedAttribute,
NSAccessibilityLineForIndexParameterizedAttribute,
NSAccessibilityRangeForLineParameterizedAttribute,
nil ];
}
+(id)lineForIndexAttributeForElement:(AquaA11yWrapper *)wrapper forParameter:(id)index {
NSNumber * lineNumber = nil;
try {
sal_Int32 line = [ wrapper accessibleMultiLineText ] -> getLineNumberAtIndex ( static_cast<sal_Int32>([ index intValue ]) );
lineNumber = [ NSNumber numberWithInt: line ];
} catch ( IndexOutOfBoundsException & ) {
// empty
}
return lineNumber;
}
+(id)rangeForLineAttributeForElement:(AquaA11yWrapper *)wrapper forParameter:(id)line {
NSValue * range = nil;
try {
TextSegment textSegment = [ wrapper accessibleMultiLineText ] -> getTextAtLineNumber ( [ line intValue ] );
range = [ NSValue valueWithRange: NSMakeRange ( textSegment.SegmentStart, textSegment.SegmentEnd - textSegment.SegmentStart ) ];
} catch ( IndexOutOfBoundsException & ) {
// empty
}
return range;
}
+(id)stringForRangeAttributeForElement:(AquaA11yWrapper *)wrapper forParameter:(id)range {
int loc = [ range rangeValue ].location;
int len = [ range rangeValue ].length;
NSMutableString * textRange = [ NSMutableString string ];
try {
// Related tdf#158914: explicitly call autorelease selector
// CreateNSString() is not a getter. It expects the caller to
// release the returned string.
[ textRange appendString: [ CreateNSString ( [ wrapper accessibleText ] -> getTextRange ( loc, loc + len ) ) autorelease ] ];
} catch ( IndexOutOfBoundsException & ) {
// empty
}
return textRange;
}
+(id)attributedStringForRangeAttributeForElement:(AquaA11yWrapper *)wrapper forParameter:(id)range {
return [ AquaA11yTextAttributesWrapper createAttributedStringForElement: wrapper inOrigRange: range ];
}
+(id)rangeForIndexAttributeForElement:(AquaA11yWrapper *)wrapper forParameter:(id)index {
NSValue * range = nil;
try {
TextSegment textSegment = [ wrapper accessibleText ] -> getTextBeforeIndex ( [ index intValue ], AccessibleTextType::GLYPH );
range = [ NSValue valueWithRange: NSMakeRange ( textSegment.SegmentStart, textSegment.SegmentEnd - textSegment.SegmentStart ) ];
} catch ( IndexOutOfBoundsException & ) {
// empty
} catch ( IllegalArgumentException & ) {
// empty
}
return range;
}
+(id)rangeForPositionAttributeForElement:(AquaA11yWrapper *)wrapper forParameter:(id)point {
NSValue * value = nil;
css::awt::Point aPoint( [ AquaA11yUtil nsPointToVclPoint: point ]);
const css::awt::Point screenPos = [ wrapper accessibleComponent ] -> getLocationOnScreen();
aPoint.X -= screenPos.X;
aPoint.Y -= screenPos.Y;
sal_Int32 index = [ wrapper accessibleText ] -> getIndexAtPoint( aPoint );
if ( index > -1 ) {
value = [ AquaA11yTextWrapper rangeForIndexAttributeForElement: wrapper forParameter: [ NSNumber numberWithLong: index ] ];
}
return value;
}
+(id)boundsForRangeAttributeForElement:(AquaA11yWrapper *)wrapper forParameter:(id)range {
NSValue * rect = nil;
try {
// TODO: this is ugly!!!
// the UNP-API can only return the bounds for a single character, not for a range
int loc = [ range rangeValue ].location;
int len = [ range rangeValue ].length;
int minx = 0x7fffffff, miny = 0x7fffffff, maxx = 0, maxy = 0;
for ( int i = 0; i < len; i++ ) {
Rectangle vclRect = [ wrapper accessibleText ] -> getCharacterBounds ( loc + i );
if ( vclRect.X < minx ) {
minx = vclRect.X;
}
if ( vclRect.Y < miny ) {
miny = vclRect.Y;
}
if ( vclRect.Width + vclRect.X > maxx ) {
maxx = vclRect.Width + vclRect.X;
}
if ( vclRect.Height + vclRect.Y > maxy ) {
maxy = vclRect.Height + vclRect.Y;
}
}
if ( [ wrapper accessibleComponent ] ) {
// get location on screen (must be added since get CharacterBounds returns values relative to parent)
css::awt::Point screenPos = [ wrapper accessibleComponent ] -> getLocationOnScreen();
css::awt::Point pos ( minx + screenPos.X, miny + screenPos.Y );
css::awt::Point size ( maxx - minx, maxy - miny );
NSValue * nsPos = [ AquaA11yUtil vclPointToNSPoint: pos ];
rect = [ NSValue valueWithRect: NSMakeRect ( [ nsPos pointValue ].x, [ nsPos pointValue ].y - size.Y, size.X, size.Y ) ];
//printf("Range: %s --- Rect: %s\n", [ NSStringFromRange ( [ range rangeValue ] ) UTF8String ], [ NSStringFromRect ( [ rect rectValue ] ) UTF8String ]);
}
} catch ( IndexOutOfBoundsException & ) {
// empty
}
return rect;
}
+(id)styleRangeForIndexAttributeForElement:(AquaA11yWrapper *)wrapper forParameter:(id)index {
NSValue * range = nil;
try {
TextSegment textSegment = [ wrapper accessibleText ] -> getTextAtIndex ( [ index intValue ], AccessibleTextType::ATTRIBUTE_RUN );
range = [ NSValue valueWithRange: NSMakeRange ( textSegment.SegmentStart, textSegment.SegmentEnd - textSegment.SegmentStart ) ];
} catch ( IndexOutOfBoundsException & ) {
// empty
} catch ( IllegalArgumentException & ) {
// empty
}
return range;
}
+(id)rTFForRangeAttributeForElement:(AquaA11yWrapper *)wrapper forParameter:(id)range {
NSData * rtfData = nil;
NSAttributedString * attrString = static_cast<NSAttributedString *>([ AquaA11yTextWrapper attributedStringForRangeAttributeForElement: wrapper forParameter: range ]);
if ( attrString != nil ) {
@try {
rtfData = [ attrString RTFFromRange: [ range rangeValue ] documentAttributes: @{NSDocumentTypeDocumentAttribute : NSRTFTextDocumentType} ];
} @catch ( NSException *) {
// empty
}
}
return rtfData;
}
+(BOOL)isAttributeSettable:(NSString *)attribute forElement:(AquaA11yWrapper *)wrapper {
bool isSettable = false;
if ( [ attribute isEqualToString: NSAccessibilityValueAttribute ]
|| [ attribute isEqualToString: NSAccessibilitySelectedTextAttribute ]
|| [ attribute isEqualToString: NSAccessibilitySelectedTextRangeAttribute ]
|| [ attribute isEqualToString: NSAccessibilityVisibleCharacterRangeAttribute ] ) {
if ( ! [ [ wrapper accessibilityAttributeValue: NSAccessibilityRoleAttribute ] isEqualToString: NSAccessibilityStaticTextRole ] ) {
isSettable = true;
}
}
return isSettable;
}
@end
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */