feat: enable more granular a11y feature management (#48627)
* feat: enable more granular a11y feature management Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com> * Update docs/api/app.md Co-authored-by: John Kleinschmidt <jkleinsc@electronjs.org> Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com> --------- Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com> Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
This commit is contained in:
parent
c5894fb455
commit
92cbc042e8
4 changed files with 213 additions and 2 deletions
|
|
@ -1392,7 +1392,75 @@ details. Disabled by default.
|
||||||
This API must be called after the `ready` event is emitted.
|
This API must be called after the `ready` event is emitted.
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
> Rendering accessibility tree can significantly affect the performance of your app. It should not be enabled by default.
|
> Rendering accessibility tree can significantly affect the performance of your app. It should not be enabled by default. Calling this method will enable the following accessibility support features: `nativeAPIs`, `webContents`, `inlineTextBoxes`, and `extendedProperties`.
|
||||||
|
|
||||||
|
### `app.getAccessibilitySupportFeatures()` _macOS_ _Windows_
|
||||||
|
|
||||||
|
Returns `string[]` - Array of strings naming currently enabled accessibility support components. Possible values:
|
||||||
|
|
||||||
|
* `nativeAPIs` - Native OS accessibility APIs integration enabled.
|
||||||
|
* `webContents` - Web contents accessibility tree exposure enabled.
|
||||||
|
* `inlineTextBoxes` - Inline text boxes (character bounding boxes) enabled.
|
||||||
|
* `extendedProperties` - Extended accessibility properties enabled.
|
||||||
|
* `screenReader` - Screen reader specific mode enabled.
|
||||||
|
* `html` - HTML accessibility tree construction enabled.
|
||||||
|
* `labelImages` - Accessibility support for automatic image annotations.
|
||||||
|
* `pdfPrinting` - Accessibility support for PDF printing enabled.
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
|
||||||
|
* The array may be empty if no accessibility modes are active.
|
||||||
|
* Use `app.isAccessibilitySupportEnabled()` for the legacy boolean check;
|
||||||
|
prefer this method for granular diagnostics or telemetry.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const { app } = require('electron')
|
||||||
|
|
||||||
|
app.whenReady().then(() => {
|
||||||
|
if (app.getAccessibilitySupportFeatures().includes('screenReader')) {
|
||||||
|
// Change some app UI to better work with Screen Readers.
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### `app.setAccessibilitySupportFeatures(features)` _macOS_ _Windows_
|
||||||
|
|
||||||
|
* `features` string[] - An array of the accessibility features to enable.
|
||||||
|
|
||||||
|
Possible values are:
|
||||||
|
|
||||||
|
* `nativeAPIs` - Native OS accessibility APIs integration enabled.
|
||||||
|
* `webContents` - Web contents accessibility tree exposure enabled.
|
||||||
|
* `inlineTextBoxes` - Inline text boxes (character bounding boxes) enabled.
|
||||||
|
* `extendedProperties` - Extended accessibility properties enabled.
|
||||||
|
* `screenReader` - Screen reader specific mode enabled.
|
||||||
|
* `html` - HTML accessibility tree construction enabled.
|
||||||
|
* `labelImages` - Accessibility support for automatic image annotations.
|
||||||
|
* `pdfPrinting` - Accessibility support for PDF printing enabled.
|
||||||
|
|
||||||
|
To disable all supported features, pass an empty array `[]`.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const { app } = require('electron')
|
||||||
|
|
||||||
|
app.whenReady().then(() => {
|
||||||
|
// Enable a subset of features:
|
||||||
|
app.setAccessibilitySupportFeatures([
|
||||||
|
'screenReader',
|
||||||
|
'pdfPrinting',
|
||||||
|
'webContents'
|
||||||
|
])
|
||||||
|
|
||||||
|
// Other logic
|
||||||
|
|
||||||
|
// Some time later, disable all features:
|
||||||
|
app.setAccessibilitySupportFeatures([])
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
### `app.showAboutPanel()`
|
### `app.showAboutPanel()`
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1162,6 +1162,86 @@ bool App::IsAccessibilitySupportEnabled() {
|
||||||
return mode.has_mode(ui::kAXModeComplete.flags());
|
return mode.has_mode(ui::kAXModeComplete.flags());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
v8::Local<v8::Value> App::GetAccessibilitySupportFeatures() {
|
||||||
|
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
|
||||||
|
v8::EscapableHandleScope handle_scope(isolate);
|
||||||
|
auto* ax_state = content::BrowserAccessibilityState::GetInstance();
|
||||||
|
ui::AXMode mode = ax_state->GetAccessibilityMode();
|
||||||
|
|
||||||
|
std::vector<v8::Local<v8::Value>> features;
|
||||||
|
auto push = [&](const char* name) {
|
||||||
|
features.push_back(v8::String::NewFromUtf8(isolate, name).ToLocalChecked());
|
||||||
|
};
|
||||||
|
|
||||||
|
if (mode.has_mode(ui::AXMode::kNativeAPIs))
|
||||||
|
push("nativeAPIs");
|
||||||
|
if (mode.has_mode(ui::AXMode::kWebContents))
|
||||||
|
push("webContents");
|
||||||
|
if (mode.has_mode(ui::AXMode::kInlineTextBoxes))
|
||||||
|
push("inlineTextBoxes");
|
||||||
|
if (mode.has_mode(ui::AXMode::kExtendedProperties))
|
||||||
|
push("extendedProperties");
|
||||||
|
if (mode.has_mode(ui::AXMode::kHTML))
|
||||||
|
push("html");
|
||||||
|
if (mode.has_mode(ui::AXMode::kLabelImages))
|
||||||
|
push("labelImages");
|
||||||
|
if (mode.has_mode(ui::AXMode::kPDFPrinting))
|
||||||
|
push("pdfPrinting");
|
||||||
|
if (mode.has_mode(ui::AXMode::kScreenReader))
|
||||||
|
push("screenReader");
|
||||||
|
|
||||||
|
v8::Local<v8::Array> arr = v8::Array::New(isolate, features.size());
|
||||||
|
for (uint32_t i = 0; i < features.size(); ++i) {
|
||||||
|
arr->Set(isolate->GetCurrentContext(), i, features[i]).Check();
|
||||||
|
}
|
||||||
|
return handle_scope.Escape(arr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void App::SetAccessibilitySupportFeatures(
|
||||||
|
gin_helper::ErrorThrower thrower,
|
||||||
|
const std::vector<std::string>& features) {
|
||||||
|
if (!Browser::Get()->is_ready()) {
|
||||||
|
thrower.ThrowError(
|
||||||
|
"app.setAccessibilitySupportFeatures() can only be called after app "
|
||||||
|
"is ready");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ui::AXMode mode;
|
||||||
|
for (const auto& f : features) {
|
||||||
|
if (f == "nativeAPIs") {
|
||||||
|
mode.set_mode(ui::AXMode::kNativeAPIs, true);
|
||||||
|
} else if (f == "webContents") {
|
||||||
|
mode.set_mode(ui::AXMode::kWebContents, true);
|
||||||
|
} else if (f == "inlineTextBoxes") {
|
||||||
|
mode.set_mode(ui::AXMode::kInlineTextBoxes, true);
|
||||||
|
} else if (f == "extendedProperties") {
|
||||||
|
mode.set_mode(ui::AXMode::kExtendedProperties, true);
|
||||||
|
} else if (f == "screenReader") {
|
||||||
|
mode.set_mode(ui::AXMode::kScreenReader, true);
|
||||||
|
} else if (f == "html") {
|
||||||
|
mode.set_mode(ui::AXMode::kHTML, true);
|
||||||
|
} else if (f == "labelImages") {
|
||||||
|
mode.set_mode(ui::AXMode::kLabelImages, true);
|
||||||
|
} else if (f == "pdfPrinting") {
|
||||||
|
mode.set_mode(ui::AXMode::kPDFPrinting, true);
|
||||||
|
} else {
|
||||||
|
thrower.ThrowError("Unknown accessibility feature: " + f);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mode.is_mode_off()) {
|
||||||
|
scoped_accessibility_mode_.reset();
|
||||||
|
} else {
|
||||||
|
scoped_accessibility_mode_ =
|
||||||
|
content::BrowserAccessibilityState::GetInstance()
|
||||||
|
->CreateScopedModeForProcess(mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
Browser::Get()->OnAccessibilitySupportChanged();
|
||||||
|
}
|
||||||
|
|
||||||
void App::SetAccessibilitySupportEnabled(gin_helper::ErrorThrower thrower,
|
void App::SetAccessibilitySupportEnabled(gin_helper::ErrorThrower thrower,
|
||||||
bool enabled) {
|
bool enabled) {
|
||||||
if (!Browser::Get()->is_ready()) {
|
if (!Browser::Get()->is_ready()) {
|
||||||
|
|
@ -1820,6 +1900,10 @@ gin::ObjectTemplateBuilder App::GetObjectTemplateBuilder(v8::Isolate* isolate) {
|
||||||
.SetMethod("relaunch", &App::Relaunch)
|
.SetMethod("relaunch", &App::Relaunch)
|
||||||
.SetMethod("isAccessibilitySupportEnabled",
|
.SetMethod("isAccessibilitySupportEnabled",
|
||||||
&App::IsAccessibilitySupportEnabled)
|
&App::IsAccessibilitySupportEnabled)
|
||||||
|
.SetMethod("getAccessibilitySupportFeatures",
|
||||||
|
&App::GetAccessibilitySupportFeatures)
|
||||||
|
.SetMethod("setAccessibilitySupportFeatures",
|
||||||
|
&App::SetAccessibilitySupportFeatures)
|
||||||
.SetMethod("setAccessibilitySupportEnabled",
|
.SetMethod("setAccessibilitySupportEnabled",
|
||||||
&App::SetAccessibilitySupportEnabled)
|
&App::SetAccessibilitySupportEnabled)
|
||||||
.SetMethod("disableHardwareAcceleration",
|
.SetMethod("disableHardwareAcceleration",
|
||||||
|
|
|
||||||
|
|
@ -211,6 +211,10 @@ class App final : public ElectronBrowserClient::Delegate,
|
||||||
void DisableHardwareAcceleration(gin_helper::ErrorThrower thrower);
|
void DisableHardwareAcceleration(gin_helper::ErrorThrower thrower);
|
||||||
void DisableDomainBlockingFor3DAPIs(gin_helper::ErrorThrower thrower);
|
void DisableDomainBlockingFor3DAPIs(gin_helper::ErrorThrower thrower);
|
||||||
bool IsAccessibilitySupportEnabled();
|
bool IsAccessibilitySupportEnabled();
|
||||||
|
v8::Local<v8::Value> GetAccessibilitySupportFeatures();
|
||||||
|
void SetAccessibilitySupportFeatures(
|
||||||
|
gin_helper::ErrorThrower thrower,
|
||||||
|
const std::vector<std::string>& features);
|
||||||
void SetAccessibilitySupportEnabled(gin_helper::ErrorThrower thrower,
|
void SetAccessibilitySupportEnabled(gin_helper::ErrorThrower thrower,
|
||||||
bool enabled);
|
bool enabled);
|
||||||
v8::Local<v8::Value> GetLoginItemSettings(gin::Arguments* args);
|
v8::Local<v8::Value> GetLoginItemSettings(gin::Arguments* args);
|
||||||
|
|
|
||||||
|
|
@ -1019,7 +1019,7 @@ describe('app module', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
ifdescribe(process.platform !== 'linux')('accessibilitySupportEnabled property', () => {
|
ifdescribe(process.platform !== 'linux')('accessibility support functionality', () => {
|
||||||
it('is mutable', () => {
|
it('is mutable', () => {
|
||||||
const values = [false, true, false];
|
const values = [false, true, false];
|
||||||
const setters: Array<(arg: boolean) => void> = [
|
const setters: Array<(arg: boolean) => void> = [
|
||||||
|
|
@ -1037,6 +1037,61 @@ describe('app module', () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('getAccessibilitySupportFeatures returns an array with accessibility properties', () => {
|
||||||
|
const values = [
|
||||||
|
'nativeAPIs',
|
||||||
|
'webContents',
|
||||||
|
'inlineTextBoxes',
|
||||||
|
'extendedProperties',
|
||||||
|
'screenReader',
|
||||||
|
'html',
|
||||||
|
'labelImages',
|
||||||
|
'pdfPrinting'
|
||||||
|
];
|
||||||
|
|
||||||
|
app.setAccessibilitySupportEnabled(false);
|
||||||
|
|
||||||
|
const disabled = app.getAccessibilitySupportFeatures();
|
||||||
|
expect(disabled).to.be.an('array');
|
||||||
|
expect(disabled.includes('complete')).to.equal(false);
|
||||||
|
|
||||||
|
app.setAccessibilitySupportEnabled(true);
|
||||||
|
const enabled = app.getAccessibilitySupportFeatures();
|
||||||
|
expect(enabled).to.be.an('array').with.length.greaterThan(0);
|
||||||
|
|
||||||
|
const boolEnabled = app.isAccessibilitySupportEnabled();
|
||||||
|
if (boolEnabled) {
|
||||||
|
expect(enabled.some(f => values.includes(f))).to.equal(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('setAccessibilitySupportFeatures can enable a subset of features', () => {
|
||||||
|
app.setAccessibilitySupportEnabled(false);
|
||||||
|
expect(app.isAccessibilitySupportEnabled()).to.equal(false);
|
||||||
|
expect(app.getAccessibilitySupportFeatures()).to.be.an('array').that.is.empty();
|
||||||
|
|
||||||
|
const subsetA = ['webContents', 'html'];
|
||||||
|
app.setAccessibilitySupportFeatures(subsetA);
|
||||||
|
const afterSubsetA = app.getAccessibilitySupportFeatures();
|
||||||
|
expect(afterSubsetA).to.deep.equal(subsetA);
|
||||||
|
|
||||||
|
const subsetB = [
|
||||||
|
'nativeAPIs',
|
||||||
|
'webContents',
|
||||||
|
'inlineTextBoxes',
|
||||||
|
'extendedProperties'
|
||||||
|
];
|
||||||
|
app.setAccessibilitySupportFeatures(subsetB);
|
||||||
|
const afterSubsetB = app.getAccessibilitySupportFeatures();
|
||||||
|
expect(afterSubsetB).to.deep.equal(subsetB);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws when an unknown accessibility feature is requested', () => {
|
||||||
|
expect(() => {
|
||||||
|
app.setAccessibilitySupportFeatures(['unknownFeature']);
|
||||||
|
}).to.throw('Unknown accessibility feature: unknownFeature');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
ifdescribe(process.platform === 'win32')('setJumpList(categories)', () => {
|
ifdescribe(process.platform === 'win32')('setJumpList(categories)', () => {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue