OLD | NEW |
(Empty) | |
| 1 /** |
| 2 * @fileoverview Utilities for mixed-content in Web Platform Tests. |
| 3 * @author burnik@google.com (Kristijan Burnik) |
| 4 * Disclaimer: Some methods of other authors are annotated in the corresponding |
| 5 * method's JSDoc. |
| 6 */ |
| 7 |
| 8 /** |
| 9 * Normalizes the target port for use in a URL. For default ports, this is the |
| 10 * empty string (omitted port), otherwise it's a colon followed by the port |
| 11 * number. Ports 80, 443 and an empty string are regarded as default ports. |
| 12 * @param {number} targetPort The port to use |
| 13 * @return {string} The port portion for using as part of a URL. |
| 14 */ |
| 15 function getNormalizedPort(targetPort) { |
| 16 return ([80, 443, ""].indexOf(targetPort) >= 0) ? "" : ":" + targetPort; |
| 17 } |
| 18 |
| 19 /** |
| 20 * Creates a GUID. |
| 21 * See: https://en.wikipedia.org/wiki/Globally_unique_identifier |
| 22 * Original author: broofa (http://www.broofa.com/) |
| 23 * Sourced from: http://stackoverflow.com/a/2117523/4949715 |
| 24 * @return {string} A pseudo-random GUID. |
| 25 */ |
| 26 function guid() { |
| 27 return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { |
| 28 var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); |
| 29 return v.toString(16); |
| 30 }); |
| 31 } |
| 32 |
| 33 /** |
| 34 * Initiates a new XHR via GET. |
| 35 * @param {string} url The endpoint URL for the XHR. |
| 36 * @param {string} responseType Optional - how should the response be parsed. |
| 37 * Default is "json". |
| 38 * See: https://xhr.spec.whatwg.org/#dom-xmlhttprequest-responsetype |
| 39 * @return {Promise} A promise wrapping the success and error events. |
| 40 */ |
| 41 function xhrRequest(url, responseType) { |
| 42 return new Promise(function(resolve, reject) { |
| 43 var xhr = new XMLHttpRequest(); |
| 44 xhr.open('GET', url, true); |
| 45 xhr.responseType = responseType || "json"; |
| 46 |
| 47 xhr.addEventListener("error", function() { |
| 48 reject(Error("Network Error")); |
| 49 }); |
| 50 |
| 51 xhr.addEventListener("load", function() { |
| 52 if (xhr.status != 200) |
| 53 return reject(Error(xhr.statusText)); |
| 54 |
| 55 resolve(xhr.response); |
| 56 }); |
| 57 |
| 58 xhr.send(); |
| 59 }); |
| 60 } |
| 61 |
| 62 /** |
| 63 * Sets attributes on a given DOM element. |
| 64 * @param {DOMElement} element The element on which to set the attributes. |
| 65 * @param {object} An object with keys (serving as attribute names) and values. |
| 66 */ |
| 67 function setAttributes(el, attrs) { |
| 68 attrs = attrs || {} |
| 69 for (var attr in attrs) |
| 70 el.setAttribute(attr, attrs[attr]); |
| 71 } |
| 72 |
| 73 |
| 74 /** |
| 75 * Binds to success and error events of an object wrapping them into a promise |
| 76 * available through {@code element.eventPromise}. The success event |
| 77 * resolves and error event rejects. |
| 78 * @param {object} element An object supporting events on which to bind the |
| 79 * promise. |
| 80 * @param {string} resolveEventName [="load"] The event name to bind resolve to. |
| 81 * @param {string} rejectEventName [="error"] The event name to bind reject to. |
| 82 */ |
| 83 function bindEvents(element, resolveEventName, rejectEventName) { |
| 84 element.eventPromise = new Promise(function(resolve, reject) { |
| 85 element.addEventListener(resolveEventName || "load", resolve); |
| 86 element.addEventListener(rejectEventName || "error", |
| 87 function(e) { e.preventDefault(); reject(); } ); |
| 88 }); |
| 89 } |
| 90 |
| 91 /** |
| 92 * Creates a new DOM element. |
| 93 * @param {string} tagName The type of the DOM element. |
| 94 * @param {object} attrs A JSON with attributes to apply to the element. |
| 95 * @param {DOMElement} parent Optional - an existing DOM element to append to |
| 96 * If not provided, the returned element will remain orphaned. |
| 97 * @param {boolean} doBindEvents Optional - Whether to bind to load and error |
| 98 * events and provide the promise wrapping the events via the element's |
| 99 * {@code eventPromise} property. Default value evaluates to false. |
| 100 * @return {DOMElement} The newly created DOM element. |
| 101 */ |
| 102 function createElement(tagName, attrs, parent, doBindEvents) { |
| 103 var element = document.createElement(tagName); |
| 104 |
| 105 if (doBindEvents) |
| 106 bindEvents(element); |
| 107 |
| 108 // We set the attributes after binding to events to catch any |
| 109 // event-triggering attribute changes. E.g. form submission. |
| 110 // |
| 111 // But be careful with images: unlike other elements they will start the load |
| 112 // as soon as the attr is set, even if not in the document yet, and sometimes |
| 113 // complete it synchronously, so the append doesn't have the effect we want. |
| 114 // So for images, we want to set the attrs after appending, whereas for other |
| 115 // elements we want to do it before appending. |
| 116 var isImg = (tagName == "img"); |
| 117 if (!isImg) |
| 118 setAttributes(element, attrs); |
| 119 |
| 120 if (parent) |
| 121 parent.appendChild(element); |
| 122 |
| 123 if (isImg) |
| 124 setAttributes(element, attrs); |
| 125 |
| 126 return element; |
| 127 } |
| 128 |
| 129 function createRequestViaElement(tagName, attrs, parent) { |
| 130 return createElement(tagName, attrs, parent, true).eventPromise; |
| 131 } |
| 132 |
| 133 /** |
| 134 * Creates a new empty iframe and appends it to {@code document.body} . |
| 135 * @param {string} name The name and ID of the new iframe. |
| 136 * @param {boolean} doBindEvents Whether to bind load and error events. |
| 137 * @return {DOMElement} The newly created iframe. |
| 138 */ |
| 139 function createHelperIframe(name, doBindEvents) { |
| 140 return createElement("iframe", |
| 141 {"name": name, "id": name}, |
| 142 document.body, |
| 143 doBindEvents); |
| 144 } |
| 145 |
| 146 /** |
| 147 * Creates a new iframe, binds load and error events, sets the src attribute and |
| 148 * appends it to {@code document.body} . |
| 149 * @param {string} url The src for the iframe. |
| 150 * @return {Promise} The promise for success/error events. |
| 151 */ |
| 152 function requestViaIframe(url) { |
| 153 return createRequestViaElement("iframe", {"src": url}, document.body); |
| 154 } |
| 155 |
| 156 /** |
| 157 * Creates a new image, binds load and error events, sets the src attribute and |
| 158 * appends it to {@code document.body} . |
| 159 * @param {string} url The src for the image. |
| 160 * @return {Promise} The promise for success/error events. |
| 161 */ |
| 162 function requestViaImage(url) { |
| 163 return createRequestViaElement("img", {"src": url}, document.body); |
| 164 } |
| 165 |
| 166 /** |
| 167 * Initiates a new XHR GET request to provided URL. |
| 168 * @param {string} url The endpoint URL for the XHR. |
| 169 * @return {Promise} The promise for success/error events. |
| 170 */ |
| 171 function requestViaXhr(url) { |
| 172 return xhrRequest(url); |
| 173 } |
| 174 |
| 175 /** |
| 176 * Initiates a new GET request to provided URL via the Fetch API. |
| 177 * @param {string} url The endpoint URL for the Fetch. |
| 178 * @return {Promise} The promise for success/error events. |
| 179 */ |
| 180 function requestViaFetch(url) { |
| 181 return fetch(url); |
| 182 } |
| 183 |
| 184 /** |
| 185 * Creates a new Worker, binds message and error events wrapping them into. |
| 186 * {@code worker.eventPromise} and posts an empty string message to start |
| 187 * the worker. |
| 188 * @param {string} url The endpoint URL for the worker script. |
| 189 * @return {Promise} The promise for success/error events. |
| 190 */ |
| 191 function requestViaWorker(url) { |
| 192 var worker = new Worker(url); |
| 193 bindEvents(worker, "message", "error"); |
| 194 worker.postMessage(''); |
| 195 |
| 196 return worker.eventPromise; |
| 197 } |
| 198 |
| 199 /** |
| 200 * Sets the href attribute on a navigable DOM element and performs a navigation |
| 201 * by clicking it. To avoid navigating away from the current execution |
| 202 * context, a target attribute is set to point to a new helper iframe. |
| 203 * @param {DOMElement} navigableElement The navigable DOMElement |
| 204 * @param {string} url The href for the navigable element. |
| 205 * @return {Promise} The promise for success/error events. |
| 206 */ |
| 207 function requestViaNavigable(navigableElement, url) { |
| 208 var iframe = createHelperIframe(guid(), true); |
| 209 setAttributes(navigableElement, |
| 210 {"href": url, |
| 211 "target": iframe.name}); |
| 212 navigableElement.click(); |
| 213 |
| 214 return iframe.eventPromise; |
| 215 } |
| 216 |
| 217 /** |
| 218 * Creates a new anchor element, appends it to {@code document.body} and |
| 219 * performs the navigation. |
| 220 * @param {string} url The URL to navigate to. |
| 221 * @return {Promise} The promise for success/error events. |
| 222 */ |
| 223 function requestViaAnchor(url) { |
| 224 var a = createElement("a", {"innerHTML": "Link to resource"}, document.body); |
| 225 |
| 226 return requestViaNavigable(a, url); |
| 227 } |
| 228 |
| 229 /** |
| 230 * Creates a new area element, appends it to {@code document.body} and performs |
| 231 * the navigation. |
| 232 * @param {string} url The URL to navigate to. |
| 233 * @return {Promise} The promise for success/error events. |
| 234 */ |
| 235 function requestViaArea(url) { |
| 236 var area = createElement("area", {}, document.body); |
| 237 |
| 238 return requestViaNavigable(area, url); |
| 239 } |
| 240 |
| 241 /** |
| 242 * Creates a new script element, sets the src to url, and appends it to |
| 243 * {@code document.body}. |
| 244 * @param {string} url The src URL. |
| 245 * @return {Promise} The promise for success/error events. |
| 246 */ |
| 247 function requestViaScript(url) { |
| 248 return createRequestViaElement("script", {"src": url}, document.body); |
| 249 } |
| 250 |
| 251 /** |
| 252 * Creates a new form element, sets attributes, appends it to |
| 253 * {@code document.body} and submits the form. |
| 254 * @param {string} url The URL to submit to. |
| 255 * @return {Promise} The promise for success/error events. |
| 256 */ |
| 257 function requestViaForm(url) { |
| 258 var iframe = createHelperIframe(guid()); |
| 259 var form = createElement("form", |
| 260 {"action": url, |
| 261 "method": "POST", |
| 262 "target": iframe.name}, |
| 263 document.body); |
| 264 bindEvents(iframe); |
| 265 form.submit(); |
| 266 |
| 267 return iframe.eventPromise; |
| 268 } |
| 269 |
| 270 /** |
| 271 * Creates a new link element for a stylesheet, binds load and error events, |
| 272 * sets the href to url and appends it to {@code document.head}. |
| 273 * @param {string} url The URL for a stylesheet. |
| 274 * @return {Promise} The promise for success/error events. |
| 275 */ |
| 276 function requestViaLinkStylesheet(url) { |
| 277 return createRequestViaElement("link", |
| 278 {"rel": "stylesheet", "href": url}, |
| 279 document.head); |
| 280 } |
| 281 |
| 282 /** |
| 283 * Creates a new link element for a prefetch, binds load and error events, sets |
| 284 * the href to url and appends it to {@code document.head}. |
| 285 * @param {string} url The URL of a resource to prefetch. |
| 286 * @return {Promise} The promise for success/error events. |
| 287 */ |
| 288 function requestViaLinkPrefetch(url) { |
| 289 // TODO(kristijanburnik): Check if prefetch should support load and error |
| 290 // events. For now we assume it's not specified. |
| 291 // https://developer.mozilla.org/en-US/docs/Web/HTTP/Link_prefetching_FAQ |
| 292 return createRequestViaElement("link", |
| 293 {"rel": "prefetch", "href": url}, |
| 294 document.head); |
| 295 } |
| 296 |
| 297 /** |
| 298 * Creates a new media element with a child source element, binds loadeddata and |
| 299 * error events, sets attributes and appends to document.body. |
| 300 * @param {string} type The type of the media element (audio/video/picture). |
| 301 * @param {object} media_attrs The attributes for the media element. |
| 302 * @param {object} source_attrs The attributes for the child source element. |
| 303 * @return {DOMElement} The newly created media element. |
| 304 */ |
| 305 function createMediaElement(type, media_attrs, source_attrs) { |
| 306 var mediaElement = createElement(type, {}); |
| 307 var sourceElement = createElement("source", {}, mediaElement); |
| 308 |
| 309 mediaElement.eventPromise = new Promise(function(resolve, reject) { |
| 310 mediaElement.addEventListener("loadeddata", resolve); |
| 311 // Notice that the source element will raise the error. |
| 312 sourceElement.addEventListener("error", reject); |
| 313 }); |
| 314 |
| 315 setAttributes(mediaElement, media_attrs); |
| 316 setAttributes(sourceElement, source_attrs); |
| 317 document.body.appendChild(mediaElement); |
| 318 |
| 319 return mediaElement; |
| 320 } |
| 321 |
| 322 /** |
| 323 * Creates a new video element, binds loadeddata and error events, sets |
| 324 * attributes and source URL and appends to {@code document.body}. |
| 325 * @param {string} url The URL of the video. |
| 326 * @return {Promise} The promise for success/error events. |
| 327 */ |
| 328 function requestViaVideo(url) { |
| 329 return createMediaElement("video", |
| 330 {}, |
| 331 {type: "video/mp4", src: url}).eventPromise; |
| 332 } |
| 333 |
| 334 /** |
| 335 * Creates a new audio element, binds loadeddata and error events, sets |
| 336 * attributes and source URL and appends to {@code document.body}. |
| 337 * @param {string} url The URL of the audio. |
| 338 * @return {Promise} The promise for success/error events. |
| 339 */ |
| 340 function requestViaAudio(url) { |
| 341 return createMediaElement("audio", |
| 342 {}, |
| 343 {type: "audio/mpeg", src: url}).eventPromise; |
| 344 } |
| 345 |
| 346 /** |
| 347 * Creates a new picture element, binds loadeddata and error events, sets |
| 348 * attributes and source URL and appends to {@code document.body}. Also |
| 349 * creates new image element appending it to the picture |
| 350 * @param {string} url The URL of the image for the source and image elements. |
| 351 * @return {Promise} The promise for success/error events. |
| 352 */ |
| 353 function requestViaPicture(url) { |
| 354 var picture = createMediaElement("picture", {}, {"srcset": url, |
| 355 "type": "image/png"}); |
| 356 return createRequestViaElement("img", {"src": url}, picture); |
| 357 } |
| 358 |
| 359 /** |
| 360 * Creates a new object element, binds load and error events, sets the data to |
| 361 * url, and appends it to {@code document.body}. |
| 362 * @param {string} url The data URL. |
| 363 * @return {Promise} The promise for success/error events. |
| 364 */ |
| 365 function requestViaObject(url) { |
| 366 return createRequestViaElement("object", {"data": url}, document.body); |
| 367 } |
| 368 |
| 369 /** |
| 370 * Creates a new WebSocket pointing to {@code url} and sends a message string |
| 371 * "echo". The {@code message} and {@code error} events are triggering the |
| 372 * returned promise resolve/reject events. |
| 373 * @param {string} url The URL for WebSocket to connect to. |
| 374 * @return {Promise} The promise for success/error events. |
| 375 */ |
| 376 function requestViaWebSocket(url) { |
| 377 return new Promise(function(resolve, reject) { |
| 378 var websocket = new WebSocket(url); |
| 379 |
| 380 websocket.addEventListener("message", function(e) { |
| 381 resolve(JSON.parse(e.data)); |
| 382 }); |
| 383 |
| 384 websocket.addEventListener("open", function(e) { |
| 385 websocket.send("echo"); |
| 386 }); |
| 387 |
| 388 websocket.addEventListener("error", function(e) { |
| 389 reject(e) |
| 390 }); |
| 391 }); |
| 392 } |
| 393 |
| 394 // SanityChecker does nothing in release mode. See sanity-checker.js for debug |
| 395 // mode. |
| 396 function SanityChecker() {} |
| 397 SanityChecker.prototype.checkScenario = function() {}; |
| 398 SanityChecker.prototype.setFailTimeout = function(test, timeout) {}; |
OLD | NEW |