• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2013 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5part of engine;
6
7/// Singleton for accessing accessibility announcements from the platform.
8final AccessibilityAnnouncements accessibilityAnnouncements =
9    AccessibilityAnnouncements.instance;
10
11/// Attaches accessibility announcements coming from the 'flutter/accessibility'
12/// channel as temporary elements to the DOM.
13class AccessibilityAnnouncements {
14  /// Initializes the [AccessibilityAnnouncements] singleton if it is not
15  /// already initialized.
16  static AccessibilityAnnouncements get instance {
17    return _instance ??= AccessibilityAnnouncements._();
18  }
19
20  static AccessibilityAnnouncements _instance;
21
22  AccessibilityAnnouncements._() {
23    registerHotRestartListener(() {
24      _removeElementTimer?.cancel();
25    });
26  }
27
28  /// Timer that times when the accessibility element should be removed from the
29  /// DOM.
30  ///
31  /// The element is added to the DOM temporarily for announcing the
32  /// message to the assistive technology.
33  Timer _removeElementTimer;
34
35  /// The duration the accessibility announcements stay on the DOM.
36  ///
37  /// It is removed after this time expired.
38  Duration durationA11yMessageIsOnDom = const Duration(seconds: 5);
39
40  /// Element which is used to communicate the message from the
41  /// 'flutter/accessibility' to the assistive technologies.
42  ///
43  /// This element gets attached to the DOM temporarily. It gets removed
44  /// after a duration. See [durationA11yMessageIsOnDom].
45  ///
46  /// This element has aria-live attribute.
47  ///
48  /// It also has id 'accessibility-element' for testing purposes.
49  html.HtmlElement _element;
50
51  html.HtmlElement get _domElement => _element ??= _createElement();
52
53  /// Decodes the message coming from the 'flutter/accessibility' channel.
54  void handleMessage(ByteData data) {
55    final Map<dynamic, dynamic> inputMap =
56        const StandardMessageCodec().decodeMessage(data);
57    final Map<dynamic, dynamic> dataMap = inputMap['data'];
58    final String message = dataMap['message'];
59    if (message != null && message.isNotEmpty) {
60      _initLiveRegion(message);
61      _removeElementTimer = Timer(durationA11yMessageIsOnDom, () {
62        _element.remove();
63      });
64    }
65  }
66
67  void _initLiveRegion(String message) {
68    _domElement.setAttribute('aria-live', 'polite');
69    _domElement.text = message;
70    html.document.body.append(_domElement);
71  }
72
73  html.LabelElement _createElement() {
74    final html.LabelElement liveRegion = html.LabelElement();
75    liveRegion.setAttribute('id', 'accessibility-element');
76    liveRegion.style
77      ..position = 'fixed'
78      ..overflow = 'hidden'
79      ..transform = 'translate(-99999px, -99999px)'
80      ..width = '1px'
81      ..height = '1px';
82    return liveRegion;
83  }
84}
85