UNPKG

axe-core

Version:

Accessibility engine for automated Web UI testing

108 lines (83 loc) 5.72 kB
# Frame Messenger Axe frameMessenger can be used to configure how axe-core communicates information between frames. By default, axe-core uses `window.postMessage()`. Since other scripts on the page may also use `window.postMessage`, axe-core's use of it can sometimes disrupt page functionality. This can be avoided by providing `axe.frameMessenger()` a way to communicate to frames that does not use `window.postMessage`. Tools like browser extensions and testing environments often have different channels through which information can be communicated. `axe.frameMessenger` must be set up in **every frame** axe-core is included. ```js axe.frameMessenger({ // Called to initialize message handling open(topicHandler) { // Map data from the bridge to topicHandler function subscriber(frameWin, data, response) { // Data deserializations / validation / etc. here topicHandler(data, response); } // Start listening for "axe-core" events const unsubscribe = bridge.subscribe('axe-core', subscriber); // Tell axe how to close the connection if it needs to return unsubscribe; }, // Called when axe needs to send a message to another frame async post(frameWindow, data, replyHandler) { // Send a message to another frame for "axe-core" const replies = bridge.send(frameWindow, 'axe-core', data); // Async handling replies as they come back for await (let data of replies) { replyHandler(data); } } }); ``` ## axe.frameMessenger({ open }) `open` is a function that should setup the communication channel with iframes. It is passed a `topicHandler` function, which must be called when a message is received from another frame. The `topicHandler` function takes two arguments: the `data` object and a callback function that is called when the subscribed listener completes. The `data` object is exclusively passed data that can be serialized with `JSON.stringify()`, which depending on the system may need to be used. The `open` function can `return` an optional `close` function. Axe-core will only ever have one frameMessenger open at a time. The `close` function is called when another frameMessenger is registered. ## axe.frameMessenger({ post }) `post` is a function that dictates how axe-core communicates with frames. It is passed three arguments: `frameWindow`, which is the frame's `contentWindow`, the `data` object, and a `replyHandler` that must be called when responses are received. To inform axe-core that no message was sent, return `false`. This informs axe-core not to await for the ping to time out. Currently, axe-core will only require `replyHandler` to be called once, so promises can also be used here. This may change in the future, so it is preferable to make it possible for `replyHandler` to be called multiple times. Some axe-core [plugins](plugins.md) may rely on this feature. A second frameMessenger feature available to plugins, but not used in axe-core by default is to reply to a reply. This works by passing `replyHandler` a `responder` callback as a second argument. This requires a different setup, in which callbacks are stored based on their `channelId` property. ```js // store handlers based on channelId const channels = {}; axe.frameMessenger({ post(frameWindow, data, replyHandler) { // Store the handler so it can be called later channels[data.channelId] = replyHandler; // Send a message to the frame bridge.send(frameWindow, data); }, open(topicHandler) { function subscriber(frameWin, data) { const { channelId, message, keepalive } = data; // Create a callback to invoke on a reply. const responder = createResponder(frameWin, channelId); // If there is a topic, pass it to the axe supplied topic-handler if (data.topic) { topicHandler(data, responder); // If there is a replyHandler stored, invoke it } else if (channels[channelId]) { const replyHandler = channels[channelId]; replyHandler(message, keepalive, responder); // Clean up replyHandler, as no further messages are expected if (!keepalive) delete channels[channelId]; } } // Start listening for "axe-core" events const unsubscribe = bridge.subscribe('axe-core', subscriber); // Tell axe how to close the connection if it needs to return unsubscribe; } }); // Return a function to be called when a reply is received function createResponder(frameWin, channelId) { return function responder(message, keepalive, replyHandler) { // Store the new reply handler, possibly replacing a previous one // to avoid receiving a message twice. channels[channelId] = replyHandler; // Send a message to the frame bridge.send(frameWin, { channelId, message, keepalive }); }; } ``` ## Error handling & Timeouts If for some reason the frameMessenger fails to open, post, or close you should not throw an error. Axe-core will handle missing results by reporting on them in the `frame-tested` rule. It should not be possible for the `topicHandler` and `replyHandler` callbacks to throw an error. If this happens, please file an issue. Axe-core has a timeout mechanism built in, which pings frames to see if they respond before instructing them to run. There is no retry behavior in axe-core, which assumes that whatever channel is used is stable. If this isn't the case, this will need to be built into frameMessenger. The `message` passed to responder may be an `Error`. If axe-core passes an `Error`, this should be propagated "as is". If this is not possible because the message needs to be serialized, a new `Error` object must be constructed as part of deserialization.