accessibles.js (4252B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 "use strict"; 5 6 const { 7 AUDIT, 8 FETCH_CHILDREN, 9 HIGHLIGHT, 10 RESET, 11 SELECT, 12 } = require("resource://devtools/client/accessibility/constants.js"); 13 14 /** 15 * Initial state definition 16 */ 17 function getInitialState() { 18 return new Map(); 19 } 20 21 /** 22 * Maintain a cache of received accessibles responses from the backend. 23 */ 24 function accessibles(state = getInitialState(), action) { 25 switch (action.type) { 26 case FETCH_CHILDREN: 27 return onReceiveChildren(state, action); 28 case HIGHLIGHT: 29 case SELECT: 30 return onReceiveAncestry(state, action); 31 case AUDIT: 32 return onAudit(state, action); 33 case RESET: 34 return getInitialState(); 35 default: 36 return state; 37 } 38 } 39 40 function getActorID(accessible) { 41 return accessible.actorID || accessible._form?.actor; 42 } 43 44 /** 45 * If accessible is cached recursively remove all its children and remove itself 46 * from cache. 47 * 48 * @param {Map} cache Previous state maintaining a cache of previously 49 * fetched accessibles. 50 * @param {object} accessible Accessible object to remove from cache. 51 */ 52 function cleanupChild(cache, accessible) { 53 const actorID = getActorID(accessible); 54 const cached = cache.get(actorID); 55 if (!cached) { 56 return; 57 } 58 59 for (const child of cached.children) { 60 cleanupChild(cache, child); 61 } 62 63 cache.delete(actorID); 64 } 65 66 /** 67 * Determine if accessible in cache is stale. Accessible object is stale if its 68 * cached children array has the size other than the value of its childCount 69 * property that updates on accessible actor event. 70 * 71 * @param {Map} cache Previous state maintaining a cache of previously 72 * fetched accessibles. 73 * @param {object} accessible Accessible object to test for staleness. 74 */ 75 function staleChildren(cache, accessible) { 76 const cached = cache.get(getActorID(accessible)); 77 if (!cached) { 78 return false; 79 } 80 81 return cached.children.length !== accessible.childCount; 82 } 83 84 function updateChildrenCache(cache, accessible, children) { 85 const actorID = getActorID(accessible); 86 87 if (cache.has(actorID)) { 88 const cached = cache.get(actorID); 89 for (const child of cached.children) { 90 // If exhisting children cache includes an accessible that is not present 91 // any more or if child accessible is stale remove it and all its children 92 // from cache. 93 if (!children.includes(child) || staleChildren(cache, child)) { 94 cleanupChild(cache, child); 95 } 96 } 97 cached.children = children; 98 cache.set(actorID, cached); 99 } else { 100 cache.set(actorID, { children }); 101 } 102 103 return cache; 104 } 105 106 function updateAncestry(cache, ancestry) { 107 ancestry.forEach(({ accessible, children }) => 108 updateChildrenCache(cache, accessible, children) 109 ); 110 111 return cache; 112 } 113 114 /** 115 * Handles fetching of accessible children. 116 * 117 * @param {Map} cache Previous state maintaining a cache of previously 118 * fetched accessibles. 119 * @param {object} action Redux action object. 120 * @return {object} updated state 121 */ 122 function onReceiveChildren(cache, action) { 123 const { error, accessible, response: children } = action; 124 if (!error) { 125 return updateChildrenCache(new Map(cache), accessible, children); 126 } 127 128 if (!accessible.isDestroyed()) { 129 console.warn(`Error fetching children: `, error); 130 return cache; 131 } 132 133 const newCache = new Map(cache); 134 cleanupChild(newCache, accessible); 135 return newCache; 136 } 137 138 function onReceiveAncestry(cache, action) { 139 const { error, response: ancestry } = action; 140 if (error) { 141 console.warn(`Error fetching ancestry: `, error); 142 return cache; 143 } 144 145 return updateAncestry(new Map(cache), ancestry); 146 } 147 148 function onAudit(cache, action) { 149 const { error, response: ancestries } = action; 150 if (error) { 151 console.warn(`Error performing an audit: `, error); 152 return cache; 153 } 154 155 const newCache = new Map(cache); 156 ancestries.forEach(ancestry => updateAncestry(newCache, ancestry)); 157 158 return newCache; 159 } 160 161 exports.accessibles = accessibles;