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