1/** 2 * Supports pseudo-"namespacing" for window-posted messages for a given test 3 * by generating and using a unique prefix that gets wrapped into message 4 * objects. This makes it more feasible to have multiple tests that use 5 * `window.postMessage` in a single test file. Basically, make it possible 6 * for the each test to listen for only the messages that are pertinent to it. 7 * 8 * 'Prefix' not an elegant term to use here but this models itself after 9 * PrefixedLocalStorage. 10 * 11 * PrefixedMessageTest: Instantiate in testharness.js tests to generate 12 * a new unique-ish prefix that can be used by other test support files 13 * PrefixedMessageResource: Instantiate in supporting test resource 14 * files to use/share a prefix generated by a test. 15 */ 16var PrefixedMessage = function () { 17 this.prefix = ''; 18 this.param = 'prefixedMessage'; // Param to use in querystrings 19}; 20 21/** 22 * Generate a URL that adds/replaces param with this object's prefix 23 * Use to link to test support files that make use of 24 * PrefixedMessageResource. 25 */ 26PrefixedMessage.prototype.url = function (uri) { 27 function updateUrlParameter (uri, key, value) { 28 var i = uri.indexOf('#'); 29 var hash = (i === -1) ? '' : uri.substr(i); 30 uri = (i === -1) ? uri : uri.substr(0, i); 31 var re = new RegExp(`([?&])${key}=.*?(&|$)`, 'i'); 32 var separator = uri.indexOf('?') !== -1 ? '&' : '?'; 33 uri = (uri.match(re)) ? uri.replace(re, `$1${key}=${value}$2`) : 34 `${uri}${separator}${key}=${value}`; 35 return uri + hash; 36 } 37 return updateUrlParameter(uri, this.param, this.prefix); 38}; 39 40/** 41 * Add an eventListener on `message` but only invoke the given callback 42 * for messages whose object contains this object's prefix. Remove the 43 * event listener once the anticipated message has been received. 44 */ 45PrefixedMessage.prototype.onMessage = function (fn) { 46 window.addEventListener('message', e => { 47 if (typeof e.data === 'object' && e.data.hasOwnProperty('prefix')) { 48 if (e.data.prefix === this.prefix) { 49 // Only invoke callback when `data` is an object containing 50 // a `prefix` key with this object's prefix value 51 // Note fn is invoked with "unwrapped" data first, then the event `e` 52 // (which contains the full, wrapped e.data should it be needed) 53 fn.call(this, e.data.data, e); 54 window.removeEventListener('message', fn); 55 } 56 } 57 }); 58}; 59 60/** 61 * Instantiate in a test file (e.g. during `setup`) to create a unique-ish 62 * prefix that can be shared by support files 63 */ 64var PrefixedMessageTest = function () { 65 PrefixedMessage.call(this); 66 this.prefix = `${document.location.pathname}-${Math.random()}-${Date.now()}-`; 67}; 68PrefixedMessageTest.prototype = Object.create(PrefixedMessage.prototype); 69PrefixedMessageTest.prototype.constructor = PrefixedMessageTest; 70 71/** 72 * Instantiate in a test support script to use a "prefix" generated by a 73 * PrefixedMessageTest in a controlling test file. It will look for 74 * the prefix in a URL param (see also PrefixedMessage#url) 75 */ 76var PrefixedMessageResource = function () { 77 PrefixedMessage.call(this); 78 // Check URL querystring for prefix to use 79 var regex = new RegExp(`[?&]${this.param}(=([^&#]*)|&|#|$)`), 80 results = regex.exec(document.location.href); 81 if (results && results[2]) { 82 this.prefix = results[2]; 83 } 84}; 85PrefixedMessageResource.prototype = Object.create(PrefixedMessage.prototype); 86PrefixedMessageResource.prototype.constructor = PrefixedMessageResource; 87 88/** 89 * This is how a test resource document can "send info" to its 90 * opener context. It will whatever message is being sent (`data`) in 91 * an object that injects the prefix. 92 */ 93PrefixedMessageResource.prototype.postToOpener = function (data) { 94 if (window.opener) { 95 window.opener.postMessage({ 96 prefix: this.prefix, 97 data: data 98 }, '*'); 99 } 100}; 101