diff --git a/lib/core/utils/respondable.js b/lib/core/utils/respondable.js index a2c2a0c276..db179a74b2 100644 --- a/lib/core/utils/respondable.js +++ b/lib/core/utils/respondable.js @@ -83,6 +83,7 @@ error: error, _respondable: true, _source: _getSource(), + _axeuuid: axe._uuid, _keepalive: keepalive }; @@ -213,7 +214,7 @@ 'message', function(e) { var data = parseMessage(e.data); - if (!data) { + if (!data || !data._axeuuid) { return; } @@ -221,13 +222,13 @@ /** * NOTE: messages from other contexts (frames) in response - * to a message should not contain a topic. We ignore these - * messages to prevent rogue postMessage handlers reflecting - * our messages. + * to a message should not contain the same axe._uuid. We + * ignore these messages to prevent rogue postMessage + * handlers reflecting our messages. * @see https://github.com/dequelabs/axe-core/issues/1754 */ var axeRespondables = axe._cache.get('axeRespondables') || {}; - if (axeRespondables[uuid] && data.topic && e.source !== window) { + if (axeRespondables[uuid] && data._axeuuid === axe._uuid) { return; } diff --git a/lib/core/utils/uuid.js b/lib/core/utils/uuid.js index af00cf4fbb..b08e3ec2cc 100644 --- a/lib/core/utils/uuid.js +++ b/lib/core/utils/uuid.js @@ -238,4 +238,7 @@ var uuid; uuid.parse = parse; uuid.unparse = unparse; uuid.BufferClass = BufferClass; + + // assign a unique id to this axe instance + axe._uuid = v1(); })(window); diff --git a/test/core/utils/respondable.js b/test/core/utils/respondable.js index 6843b48eaa..8df0380f84 100644 --- a/test/core/utils/respondable.js +++ b/test/core/utils/respondable.js @@ -119,10 +119,15 @@ describe('axe.utils.respondable', function() { _respondable: true, _source: 'axeAPI.2.0.0', message: 'Help us Obi-Wan', - uuid: mockUUID + uuid: mockUUID, + _axeuuid: 'otherAxe' }); event.source = window; + // this sets up the callback function but the post event from it + // is asynchronous so is will be ignored as the dispatch event + // that follows is synchronous and will trigger the callback + // (thats why there's no `done()` call) axe.utils.respondable(window, 'Death star', null, true, function(data) { success = true; assert.equal(data, 'Help us Obi-Wan'); @@ -140,7 +145,8 @@ describe('axe.utils.respondable', function() { _respondable: true, _source: 'axeAPI.x.y.z', message: 'Help us Obi-Wan', - uuid: mockUUID + uuid: mockUUID, + _axeuuid: 'otherAxe' }); event.source = window; @@ -163,7 +169,8 @@ describe('axe.utils.respondable', function() { _respondable: true, _source: 'axeAPI.2.0.0', message: 'Help us Obi-Wan', - uuid: mockUUID + uuid: mockUUID, + _axeuuid: 'otherAxe' }); event.source = window; @@ -187,7 +194,8 @@ describe('axe.utils.respondable', function() { _respondable: true, _source: 'axeAPI.2.0.0', message: 'Help us Obi-Wan', - uuid: mockUUID + uuid: mockUUID, + _axeuuid: 'otherAxe' }); event.source = window; @@ -206,7 +214,8 @@ describe('axe.utils.respondable', function() { event.initEvent('message', true, true); event.data = { _respondable: true, - uuid: mockUUID + uuid: mockUUID, + _axeuuid: 'otherAxe' }; event.source = window; @@ -225,7 +234,8 @@ describe('axe.utils.respondable', function() { event.data = JSON.stringify({ _respondable: true, - uuid: mockUUID + uuid: mockUUID, + _axeuuid: 'otherAxe' }) + 'joker tricks!'; event.source = window; @@ -241,9 +251,10 @@ describe('axe.utils.respondable', function() { var event = document.createEvent('Event'); // Define that the event name is 'build'. event.initEvent('message', true, true); - event.data = '{ "_respondable": true, "topic": "batman" }'; event.data = JSON.stringify({ - _respondable: true + _respondable: true, + topic: 'batman', + _axeuuid: 'otherAxe' }); event.source = window; @@ -259,10 +270,11 @@ describe('axe.utils.respondable', function() { var event = document.createEvent('Event'); // Define that the event name is 'build'. event.initEvent('message', true, true); - event.data = '{ "_respondable": true, "topic": "batman", "uuid": "12" }'; event.data = JSON.stringify({ _respondable: true, - uuid: 'not-' + mockUUID + topic: 'batman', + uuid: 'not-' + mockUUID, + _axeuuid: 'otherAxe' }); event.source = window; @@ -278,8 +290,48 @@ describe('axe.utils.respondable', function() { var event = document.createEvent('Event'); // Define that the event name is 'build'. event.initEvent('message', true, true); - event.data = '{ "uuid": "48", "topic": "batman" }'; event.data = JSON.stringify({ + uuid: mockUUID, + topic: 'batman', + _axeuuid: 'otherAxe' + }); + event.source = window; + + axe.utils.respondable(window, 'batman', 'nananana', true, function() { + success = false; + }); + document.dispatchEvent(event); + assert.isTrue(success); + }); + + it('should reject messages that do not have `_axeuuid`', function() { + var success = true; + var event = document.createEvent('Event'); + // Define that the event name is 'build'. + event.initEvent('message', true, true); + event.data = JSON.stringify({ + _respondable: true, + topic: 'batman', + uuid: mockUUID + }); + event.source = window; + + axe.utils.respondable(window, 'batman', 'nananana', true, function() { + success = false; + }); + document.dispatchEvent(event); + assert.isTrue(success); + }); + + it('should reject messages from the same axe instance (`_axeuuid`)', function() { + var success = true; + var event = document.createEvent('Event'); + // Define that the event name is 'build'. + event.initEvent('message', true, true); + event.data = JSON.stringify({ + _respondable: true, + topic: 'batman', + _axeuuid: axe._uuid, uuid: mockUUID }); event.source = window; @@ -304,7 +356,8 @@ describe('axe.utils.respondable', function() { message: 'The exhaust port is open!', trail: '... boom' }, - uuid: mockUUID + uuid: mockUUID, + _axeuuid: 'otherAxe' }); event.source = window; @@ -332,7 +385,8 @@ describe('axe.utils.respondable', function() { message: 'The exhaust port is open!', trail: '... boom' }, - uuid: mockUUID + uuid: mockUUID, + _axeuuid: 'otherAxe' }); event.source = window; @@ -363,6 +417,26 @@ describe('axe.utils.respondable', function() { }); describe('subscribe', function() { + var origAxeUUID = axe._uuid; + var counter = 0; + + before(function() { + // assign axe a new uuid every time it's requested to trick + // the code that each respondable was called from a different + // context + Object.defineProperty(axe, '_uuid', { + get: function() { + return ++counter; + } + }); + }); + + after(function() { + Object.defineProperty(axe, '_uuid', { + value: origAxeUUID + }); + }); + it('should be a function', function() { assert.isFunction(axe.utils.respondable.subscribe); }); diff --git a/test/mock/frames/responder.html b/test/mock/frames/responder.html index e382bae67c..cc4856b9d9 100644 --- a/test/mock/frames/responder.html +++ b/test/mock/frames/responder.html @@ -4,13 +4,13 @@