/* Base/WebInspector.js */ /* * Copyright (C) 2013 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ var WI = {}; // Namespace var WebKitAdditions = {}; /* Base/Multimap.js */ /* * Copyright (C) 2018-2020 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ class Multimap { constructor(items = []) { this._map = new Map; for (let [key, value] of items) this.add(key, value); } // Public get size() { return this._map.size; } has(key, value) { let valueSet = this._map.get(key); if (!valueSet) return false; return value === undefined || valueSet.has(value); } get(key) { return this._map.get(key); } add(key, value) { let valueSet = this._map.get(key); if (!valueSet) { valueSet = new Set; this._map.set(key, valueSet); } valueSet.add(value); return this; } delete(key, value) { // Allow an entire key to be removed by not passing a value. if (arguments.length === 1) return this._map.delete(key); let valueSet = this._map.get(key); if (!valueSet) return false; let deleted = valueSet.delete(value); if (!valueSet.size) this._map.delete(key); return deleted; } take(key, value) { // Allow an entire key to be removed by not passing a value. if (arguments.length === 1) return this._map.take(key); let valueSet = this._map.get(key); if (!valueSet) return undefined; let result = valueSet.take(value); if (!valueSet.size) this._map.delete(key); return result; } clear() { this._map.clear(); } keys() { return this._map.keys(); } *values() { for (let valueSet of this._map.values()) { for (let value of valueSet) yield value; } } sets() { return this._map.entries(); } *[Symbol.iterator]() { for (let [key, valueSet] of this._map) { for (let value of valueSet) yield [key, value]; } } copy() { return new Multimap(this.toJSON()); } toJSON() { return Array.from(this); } } /* Base/Object.js */ /* * Copyright (C) 2008, 2013 Apple Inc. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ WI.Object = class WebInspectorObject { constructor() { this._listeners = null; } // Static static addEventListener(eventType, listener, thisObject) { console.assert(typeof eventType === "string", this, eventType, listener, thisObject); console.assert(typeof listener === "function", this, eventType, listener, thisObject); console.assert(typeof thisObject === "object" || window.InspectorTest || window.ProtocolTest, this, eventType, listener, thisObject); thisObject ??= this; let data = { listener, thisObjectWeakRef: new WeakRef(thisObject), }; WI.Object._listenerThisObjectFinalizationRegistry.register(thisObject, {eventTargetWeakRef: new WeakRef(this), eventType, data}, data); this._listeners ??= new Multimap; this._listeners.add(eventType, data); console.assert(Array.from(this._listeners.get(eventType)).filter((item) => item.listener === listener && item.thisObjectWeakRef.deref() === thisObject).length === 1, this, eventType, listener, thisObject); return listener; } static singleFireEventListener(eventType, listener, thisObject) { let eventTargetWeakRef = new WeakRef(this); return this.addEventListener(eventType, function wrappedCallback() { eventTargetWeakRef.deref()?.removeEventListener(eventType, wrappedCallback, this); listener.apply(this, arguments); }, thisObject); } static awaitEvent(eventType, thisObject) { return new Promise((resolve, reject) => { this.singleFireEventListener(eventType, resolve, thisObject); }); } static removeEventListener(eventType, listener, thisObject) { console.assert(this._listeners, this, eventType, listener, thisObject); console.assert(typeof eventType === "string", this, eventType, listener, thisObject); console.assert(typeof listener === "function", this, eventType, listener, thisObject); console.assert(typeof thisObject === "object" || window.InspectorTest || window.ProtocolTest, this, eventType, listener, thisObject); if (!this._listeners) return; thisObject ??= this; let listenersForEventType = this._listeners.get(eventType); console.assert(listenersForEventType, this, eventType, listener, thisObject); if (!listenersForEventType) return; let didDelete = false; for (let data of listenersForEventType) { let unwrapped = data.thisObjectWeakRef.deref(); if (!unwrapped || unwrapped !== thisObject || data.listener !== listener) continue; if (this._listeners.delete(eventType, data)) didDelete = true; WI.Object._listenerThisObjectFinalizationRegistry.unregister(data); } console.assert(didDelete, this, eventType, listener, thisObject); } // Public addEventListener() { return WI.Object.addEventListener.apply(this, arguments); } singleFireEventListener() { return WI.Object.singleFireEventListener.apply(this, arguments); } awaitEvent() { return WI.Object.awaitEvent.apply(this, arguments); } removeEventListener() { return WI.Object.removeEventListener.apply(this, arguments); } dispatchEventToListeners(eventType, eventData) { let event = new WI.Event(this, eventType, eventData); function dispatch(object) { if (!object || event._stoppedPropagation) return; let listeners = object._listeners; if (!listeners || !object.hasOwnProperty("_listeners") || !listeners.size) return; let listenersForEventType = listeners.get(eventType); if (!listenersForEventType) return; // Copy the set of listeners so we don't have to worry about mutating while iterating. for (let data of Array.from(listenersForEventType)) { let unwrapped = data.thisObjectWeakRef.deref(); if (!unwrapped) continue; data.listener.call(unwrapped, event); if (event._stoppedPropagation) break; } } // Dispatch to listeners of this specific object. dispatch(this); // Allow propagation again so listeners on the constructor always have a crack at the event. event._stoppedPropagation = false; // Dispatch to listeners on all constructors up the prototype chain, including the immediate constructor. let constructor = this.constructor; while (constructor) { dispatch(constructor); if (!constructor.prototype.__proto__) break; constructor = constructor.prototype.__proto__.constructor; } return event.defaultPrevented; } // Test static hasEventListeners(eventType) { console.assert(window.InspectorTest || window.ProtocolTest); return this._listeners?.has(eventType); } static activelyListeningObjectsWithPrototype(proto) { console.assert(window.InspectorTest || window.ProtocolTest); let results = new Set; if (this._listeners) { for (let data of this._listeners.values()) { let unwrapped = data.thisObjectWeakRef.deref(); if (unwrapped instanceof proto) results.add(unwrapped); } } return results; } hasEventListeners() { return WI.Object.hasEventListeners.apply(this, arguments); } activelyListeningObjectsWithPrototype() { return WI.Object.activelyListeningObjectsWithPrototype.apply(this, arguments); } }; WI.Object._listenerThisObjectFinalizationRegistry = new FinalizationRegistry((heldValue) => { heldValue.eventTargetWeakRef.deref()?._listeners.delete(heldValue.eventType, heldValue.data); }); WI.Event = class Event { constructor(target, type, data) { this.target = target; this.type = type; this.data = data; this.defaultPrevented = false; this._stoppedPropagation = false; } stopPropagation() { this._stoppedPropagation = true; } preventDefault() { this.defaultPrevented = true; } }; WI.notifications = new WI.Object; WI.Notification = { GlobalModifierKeysDidChange: "global-modifiers-did-change", PageArchiveStarted: "page-archive-started", PageArchiveEnded: "page-archive-ended", ExtraDomainsActivated: "extra-domains-activated", // COMPATIBILITY (iOS 14.0): Inspector.activateExtraDomains was removed in favor of a declared debuggable type VisibilityStateDidChange: "visibility-state-did-change", TransitionPageTarget: "transition-page-target", }; /* Base/Utilities.js */ /* * Copyright (C) 2013 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ var emDash = "\u2014"; var enDash = "\u2013"; var figureDash = "\u2012"; var ellipsis = "\u2026"; var zeroWidthSpace = "\u200b"; var multiplicationSign = "\u00d7"; function xor(a, b) { if (a) return b ? false : a; return b || false; } function nullish(value) { return value === null || value === undefined; } Object.defineProperty(Object, "shallowCopy", { value(object) { // Make a new object and copy all the key/values. The values are not copied. var copy = {}; var keys = Object.keys(object); for (var i = 0; i < keys.length; ++i) copy[keys[i]] = object[keys[i]]; return copy; } }); Object.defineProperty(Object, "shallowEqual", { value(a, b) { // Checks if two objects have the same top-level properties. if (!(a instanceof Object) || !(b instanceof Object)) return false; if (a === b) return true; if (Array.shallowEqual(a, b)) return true; if (a.constructor !== b.constructor) return false; let aKeys = Object.keys(a); let bKeys = Object.keys(b); if (aKeys.length !== bKeys.length) return false; for (let aKey of aKeys) { if (!(aKey in b)) return false; let aValue = a[aKey]; let bValue = b[aKey]; if (aValue !== bValue && !Array.shallowEqual(aValue, bValue)) return false; } return true; } }); Object.defineProperty(Object, "filter", { value(object, callback) { let filtered = {}; for (let key in object) { if (callback(key, object[key])) filtered[key] = object[key]; } return filtered; } }); Object.defineProperty(Object.prototype, "valueForCaseInsensitiveKey", { value(key) { if (this.hasOwnProperty(key)) return this[key]; var lowerCaseKey = key.toLowerCase(); for (var currentKey in this) { if (currentKey.toLowerCase() === lowerCaseKey) return this[currentKey]; } return undefined; } }); Object.defineProperty(Map, "fromObject", { value(object) { let map = new Map; for (let key in object) map.set(key, object[key]); return map; } }); Object.defineProperty(Map.prototype, "take", { value(key) { let deletedValue = this.get(key); this.delete(key); return deletedValue; } }); Object.defineProperty(Map.prototype, "getOrInitialize", { value(key, initialValue) { console.assert(initialValue !== undefined, "getOrInitialize should not be used with undefined."); let value = this.get(key); if (value) return value; if (typeof initialValue === "function") initialValue = initialValue(); console.assert(initialValue !== undefined, "getOrInitialize should not be used with undefined."); this.set(key, initialValue); return initialValue; } }); Object.defineProperty(WeakMap.prototype, "getOrInitialize", { value(key, initialValue) { console.assert(initialValue !== undefined, "getOrInitialize should not be used with undefined."); let value = this.get(key); if (value) return value; if (typeof initialValue === "function") initialValue = initialValue(); console.assert(initialValue !== undefined, "getOrInitialize should not be used with undefined."); this.set(key, initialValue); return initialValue; } }); Object.defineProperty(Set.prototype, "find", { value(predicate) { for (let item of this) { if (predicate(item, this)) return item; } return undefined; }, }); Object.defineProperty(Set.prototype, "filter", { value(callback, thisArg) { let filtered = new Set; for (let item of this) { if (callback.call(thisArg, item, item, this)) filtered.add(item); } return filtered; }, }); Object.defineProperty(Set.prototype, "some", { value(predicate, thisArg) { for (let item of this) { if (predicate.call(thisArg, item, item, this)) return true; } return false; }, }); Object.defineProperty(Set.prototype, "addAll", { value(iterable) { for (let item of iterable) this.add(item); }, }); Object.defineProperty(Set.prototype, "take", { value(key) { if (this.has(key)) { this.delete(key); return key; } return undefined; } }); Object.defineProperty(Set.prototype, "equals", { value(other) { return this.size === other.size && this.isSubsetOf(other); } }); Object.defineProperty(Set.prototype, "difference", { value(other) { if (other === this) return new Set; let result = new Set; for (let item of this) { if (!other.has(item)) result.add(item); } return result; } }); Object.defineProperty(Set.prototype, "firstValue", { get() { return this.values().next().value; } }); Object.defineProperty(Set.prototype, "lastValue", { get() { return Array.from(this.values()).lastValue; } }); Object.defineProperty(Set.prototype, "intersects", { value(other) { if (!this.size || !other.size) return false; for (let item of this) { if (other.has(item)) return true; } return false; } }); Object.defineProperty(Set.prototype, "isSubsetOf", { value(other) { for (let item of this) { if (!other.has(item)) return false; } return true; } }); Object.defineProperty(Node.prototype, "traverseNextNode", { value(stayWithin) { var node = this.firstChild; if (node) return node; if (stayWithin && this === stayWithin) return null; node = this.nextSibling; if (node) return node; node = this; while (node && !node.nextSibling && (!stayWithin || !node.parentNode || node.parentNode !== stayWithin)) node = node.parentNode; if (!node) return null; return node.nextSibling; } }); Object.defineProperty(Node.prototype, "traversePreviousNode", { value(stayWithin) { if (stayWithin && this === stayWithin) return null; var node = this.previousSibling; while (node && node.lastChild) node = node.lastChild; if (node) return node; return this.parentNode; } }); Object.defineProperty(Node.prototype, "rangeOfWord", { value(offset, stopCharacters, stayWithinNode, direction) { var startNode; var startOffset = 0; var endNode; var endOffset = 0; if (!stayWithinNode) stayWithinNode = this; if (!direction || direction === "backward" || direction === "both") { var node = this; while (node) { if (node === stayWithinNode) { if (!startNode) startNode = stayWithinNode; break; } if (node.nodeType === Node.TEXT_NODE) { let start = node === this ? (offset - 1) : (node.nodeValue.length - 1); for (var i = start; i >= 0; --i) { if (stopCharacters.indexOf(node.nodeValue[i]) !== -1) { startNode = node; startOffset = i + 1; break; } } } if (startNode) break; node = node.traversePreviousNode(stayWithinNode); } if (!startNode) { startNode = stayWithinNode; startOffset = 0; } } else { startNode = this; startOffset = offset; } if (!direction || direction === "forward" || direction === "both") { node = this; while (node) { if (node === stayWithinNode) { if (!endNode) endNode = stayWithinNode; break; } if (node.nodeType === Node.TEXT_NODE) { let start = node === this ? offset : 0; for (var i = start; i < node.nodeValue.length; ++i) { if (stopCharacters.indexOf(node.nodeValue[i]) !== -1) { endNode = node; endOffset = i; break; } } } if (endNode) break; node = node.traverseNextNode(stayWithinNode); } if (!endNode) { endNode = stayWithinNode; endOffset = stayWithinNode.nodeType === Node.TEXT_NODE ? stayWithinNode.nodeValue.length : stayWithinNode.childNodes.length; } } else { endNode = this; endOffset = offset; } var result = this.ownerDocument.createRange(); result.setStart(startNode, startOffset); result.setEnd(endNode, endOffset); return result; } }); Object.defineProperty(Element.prototype, "realOffsetWidth", { get() { return this.getBoundingClientRect().width; } }); Object.defineProperty(Element.prototype, "realOffsetHeight", { get() { return this.getBoundingClientRect().height; } }); Object.defineProperty(Element.prototype, "totalOffsetLeft", { get() { return this.getBoundingClientRect().left; } }); Object.defineProperty(Element.prototype, "totalOffsetRight", { get() { return this.getBoundingClientRect().right; } }); Object.defineProperty(Element.prototype, "totalOffsetTop", { get() { return this.getBoundingClientRect().top; } }); Object.defineProperty(Element.prototype, "totalOffsetBottom", { get() { return this.getBoundingClientRect().bottom; } }); Object.defineProperty(Element.prototype, "removeChildren", { value() { // This has been tested to be the fastest removal method. if (this.firstChild) this.textContent = ""; } }); Object.defineProperty(Element.prototype, "isInsertionCaretInside", { value() { var selection = window.getSelection(); if (!selection.rangeCount || !selection.isCollapsed) return false; var selectionRange = selection.getRangeAt(0); return selectionRange.startContainer === this || this.contains(selectionRange.startContainer); } }); Object.defineProperty(Element.prototype, "createChild", { value(elementName, className) { var element = this.ownerDocument.createElement(elementName); if (className) element.className = className; this.appendChild(element); return element; } }); Object.defineProperty(Element.prototype, "isScrolledToBottom", { value() { // This code works only for 0-width border return this.scrollTop + this.clientHeight === this.scrollHeight; } }); Object.defineProperty(Element.prototype, "recalculateStyles", { value() { this.ownerDocument.defaultView.getComputedStyle(this); } }); Object.defineProperty(Element.prototype, "getComputedCSSPropertyNumberValue", { value(property) { let result = undefined; result ??= this.computedStyleMap?.().get(property)?.value; result ??= window.getComputedStyle(this).getPropertyCSSValue(property)?.getFloatValue(CSSPrimitiveValue.CSS_PX); return result; }, }); Object.defineProperty(DocumentFragment.prototype, "createChild", { value: Element.prototype.createChild }); (function() { const fontSymbol = Symbol("font"); Object.defineProperty(HTMLInputElement.prototype, "autosize", { value(extra = 0) { extra += 6; // UserAgent styles add 1px padding and 2px border. if (this.type === "number") extra += 13; // Number input inner spin button width. extra += 2; // Add extra pixels for the cursor. WI.ImageUtilities.scratchCanvasContext2D((context) => { this[fontSymbol] ||= window.getComputedStyle(this).font; context.font = this[fontSymbol]; let textMetrics = context.measureText(this.value || this.placeholder); this.style.setProperty("width", (textMetrics.width + extra) + "px"); }); }, }); })(); Object.defineProperty(HTMLCollection.prototype, "indexOf", { value(element) { let length = this.length; for (let i = 0; i < length; ++i) { if (this[i] === element) return i; } return -1; } }); Object.defineProperty(Event.prototype, "stop", { value() { this.stopImmediatePropagation(); this.preventDefault(); } }); Object.defineProperty(KeyboardEvent.prototype, "commandOrControlKey", { get() { return WI.Platform.name === "mac" ? this.metaKey : this.ctrlKey; } }); Object.defineProperty(MouseEvent.prototype, "commandOrControlKey", { get() { return WI.Platform.name === "mac" ? this.metaKey : this.ctrlKey; } }); Object.defineProperty(Array, "isTypedArray", { value(array) { if (!array) return false; let constructor = array.constructor; return constructor === Int8Array || constructor === Int16Array || constructor === Int32Array || constructor === Uint8Array || constructor === Uint8ClampedArray || constructor === Uint16Array || constructor === Uint32Array || constructor === Float32Array || constructor === Float64Array; } }); Object.defineProperty(Array, "shallowEqual", { value(a, b) { function isArrayLike(x) { return Array.isArray(x) || Array.isTypedArray(x); } if (!isArrayLike(a) || !isArrayLike(b)) return false; if (a === b) return true; let length = a.length; if (length !== b.length) return false; for (let i = 0; i < length; ++i) { if (a[i] === b[i]) continue; if (!Object.shallowEqual(a[i], b[i])) return false; } return true; } }); Object.defineProperty(Array, "diffArrays", { value(initialArray, currentArray, onEach, comparator) { "use strict"; function defaultComparator(initial, current) { return initial === current; } comparator = comparator || defaultComparator; // Find the shortest prefix of matching items in both arrays. // // initialArray = ["a", "b", "b", "c"] // currentArray = ["c", "b", "b", "a"] // findShortestEdit() // [1, 1] // function findShortestEdit() { let deletionCount = initialArray.length; let additionCount = currentArray.length; let editCount = deletionCount + additionCount; for (let i = 0; i < initialArray.length; ++i) { if (i > editCount) { // Break since any possible edits at this point are going to be longer than the one already found. break; } for (let j = 0; j < currentArray.length; ++j) { let newEditCount = i + j; if (newEditCount > editCount) { // Break since any possible edits at this point are going to be longer than the one already found. break; } if (comparator(initialArray[i], currentArray[j])) { // A candidate for the shortest edit found. if (newEditCount < editCount) { editCount = newEditCount; deletionCount = i; additionCount = j; } break; } } } return [deletionCount, additionCount]; } function commonPrefixLength(listA, listB) { let shorterListLength = Math.min(listA.length, listB.length); let i = 0; while (i < shorterListLength) { if (!comparator(listA[i], listB[i])) break; ++i; } return i; } function fireOnEach(count, diffAction, array) { for (let i = 0; i < count; ++i) onEach(array[i], diffAction); } while (initialArray.length || currentArray.length) { // Remove common prefix. let prefixLength = commonPrefixLength(initialArray, currentArray); if (prefixLength) { fireOnEach(prefixLength, 0, currentArray); initialArray = initialArray.slice(prefixLength); currentArray = currentArray.slice(prefixLength); } if (!initialArray.length && !currentArray.length) break; let [deletionCount, additionCount] = findShortestEdit(); fireOnEach(deletionCount, -1, initialArray); fireOnEach(additionCount, 1, currentArray); initialArray = initialArray.slice(deletionCount); currentArray = currentArray.slice(additionCount); } } }); Object.defineProperty(Array.prototype, "firstValue", { get() { return this[0]; } }); Object.defineProperty(Array.prototype, "lastValue", { get() { if (!this.length) return undefined; return this[this.length - 1]; } }); Object.defineProperty(Array.prototype, "adjacencies", { value: function*() { for (let i = 1; i < this.length; ++i) yield [this[i - 1], this[i]]; } }); Object.defineProperty(Array.prototype, "remove", { value(value) { for (let i = 0; i < this.length; ++i) { if (this[i] === value) { this.splice(i, 1); return true; } } return false; } }); Object.defineProperty(Array.prototype, "removeAll", { value(value) { for (let i = this.length - 1; i >= 0; --i) { if (this[i] === value) this.splice(i, 1); } } }); Object.defineProperty(Array.prototype, "toggleIncludes", { value(value, force) { let exists = this.includes(value); if (force !== undefined && exists === !!force) return; if (exists) this.remove(value); else this.push(value); } }); Object.defineProperty(Array.prototype, "insertAtIndex", { value(value, index) { this.splice(index, 0, value); } }); Object.defineProperty(Array.prototype, "pushAll", { value(iterable) { for (let item of iterable) this.push(item); }, }); Object.defineProperty(Array.prototype, "partition", { value(callback) { let positive = []; let negative = []; for (let i = 0; i < this.length; ++i) { let value = this[i]; if (callback(value)) positive.push(value); else negative.push(value); } return [positive, negative]; } }); Object.defineProperty(String.prototype, "isLowerCase", { value() { return /^[a-z]+$/.test(this); } }); Object.defineProperty(String.prototype, "isUpperCase", { value() { return /^[A-Z]+$/.test(this); } }); Object.defineProperty(String.prototype, "isJSON", { value(predicate) { try { let json = JSON.parse(this); return !predicate || predicate(json); } catch { } return false; } }); Object.defineProperty(String.prototype, "truncateStart", { value(maxLength) { "use strict"; if (this.length <= maxLength) return this; return ellipsis + this.substr(this.length - maxLength + 1); } }); Object.defineProperty(String.prototype, "truncateMiddle", { value(maxLength) { "use strict"; if (this.length <= maxLength) return this; var leftHalf = maxLength >> 1; var rightHalf = maxLength - leftHalf - 1; return this.substr(0, leftHalf) + ellipsis + this.substr(this.length - rightHalf, rightHalf); } }); Object.defineProperty(String.prototype, "truncateEnd", { value(maxLength) { "use strict"; if (this.length <= maxLength) return this; return this.substr(0, maxLength - 1) + ellipsis; } }); Object.defineProperty(String.prototype, "truncate", { value(maxLength) { "use strict"; if (this.length <= maxLength) return this; let clipped = this.slice(0, maxLength); let indexOfLastWhitespace = clipped.search(/\s\S*$/); if (indexOfLastWhitespace > Math.floor(maxLength / 2)) clipped = clipped.slice(0, indexOfLastWhitespace - 1); return clipped + ellipsis; } }); Object.defineProperty(String.prototype, "collapseWhitespace", { value() { return this.replace(/[\s\xA0]+/g, " "); } }); Object.defineProperty(String.prototype, "removeWhitespace", { value() { return this.replace(/[\s\xA0]+/g, ""); } }); Object.defineProperty(String.prototype, "escapeCharacters", { value(charactersToEscape) { if (!charactersToEscape) return this.valueOf(); let charactersToEscapeSet = new Set(charactersToEscape); let foundCharacter = false; for (let c of this) { if (!charactersToEscapeSet.has(c)) continue; foundCharacter = true; break; } if (!foundCharacter) return this.valueOf(); let result = ""; for (let c of this) { if (charactersToEscapeSet.has(c)) result += "\\"; result += c; } return result.valueOf(); } }); Object.defineProperty(String.prototype, "escapeForRegExp", { value() { return this.escapeCharacters("^[]{}()\\.$*+?|"); } }); Object.defineProperty(String.prototype, "capitalize", { value() { return this.charAt(0).toUpperCase() + this.slice(1); } }); Object.defineProperty(String.prototype, "extendedLocaleCompare", { value(other) { return this.localeCompare(other, undefined, {numeric: true}); } }); Object.defineProperty(String, "tokenizeFormatString", { value(format) { var tokens = []; var substitutionIndex = 0; function addStringToken(str) { tokens.push({type: "string", value: str}); } function addSpecifierToken(specifier, precision, substitutionIndex) { tokens.push({type: "specifier", specifier, precision, substitutionIndex}); } var index = 0; for (var precentIndex = format.indexOf("%", index); precentIndex !== -1; precentIndex = format.indexOf("%", index)) { addStringToken(format.substring(index, precentIndex)); index = precentIndex + 1; if (format[index] === "%") { addStringToken("%"); ++index; continue; } if (!isNaN(format[index])) { // The first character is a number, it might be a substitution index. var number = parseInt(format.substring(index), 10); while (!isNaN(format[index])) ++index; // If the number is greater than zero and ends with a "$", // then this is a substitution index. if (number > 0 && format[index] === "$") { substitutionIndex = (number - 1); ++index; } } const defaultPrecision = 6; let precision = defaultPrecision; if (format[index] === ".") { // This is a precision specifier. If no digit follows the ".", // then use the default precision of six digits (ISO C99 specification). ++index; precision = parseInt(format.substring(index), 10); if (isNaN(precision)) precision = defaultPrecision; while (!isNaN(format[index])) ++index; } addSpecifierToken(format[index], precision, substitutionIndex); ++substitutionIndex; ++index; } addStringToken(format.substring(index)); return tokens; } }); Object.defineProperty(String.prototype, "lineCount", { get() { "use strict"; let lineCount = 1; let index = 0; while (true) { index = this.indexOf("\n", index); if (index === -1) return lineCount; index += "\n".length; lineCount++; } } }); Object.defineProperty(String.prototype, "lastLine", { get() { "use strict"; let index = this.lastIndexOf("\n"); if (index === -1) return this; return this.slice(index + "\n".length); } }); Object.defineProperty(String.prototype, "hash", { get() { // Matches the wtf/Hasher.h (SuperFastHash) algorithm. // Arbitrary start value to avoid mapping all 0's to all 0's. const stringHashingStartValue = 0x9e3779b9; var result = stringHashingStartValue; var pendingCharacter = null; for (var i = 0; i < this.length; ++i) { var currentCharacter = this[i].charCodeAt(0); if (pendingCharacter === null) { pendingCharacter = currentCharacter; continue; } result += pendingCharacter; result = (result << 16) ^ ((currentCharacter << 11) ^ result); result += result >> 11; pendingCharacter = null; } // Handle the last character in odd length strings. if (pendingCharacter !== null) { result += pendingCharacter; result ^= result << 11; result += result >> 17; } // Force "avalanching" of final 31 bits. result ^= result << 3; result += result >> 5; result ^= result << 2; result += result >> 15; result ^= result << 10; // Prevent 0 and negative results. return (0xffffffff + result + 1).toString(36); } }); Object.defineProperty(String, "standardFormatters", { value: { d: function(substitution) { return parseInt(substitution).toLocaleString(); }, f: function(substitution, token) { let value = parseFloat(substitution); if (isNaN(value)) return NaN; let options = { minimumFractionDigits: token.precision, maximumFractionDigits: token.precision, useGrouping: false }; return value.toLocaleString(undefined, options); }, s: function(substitution) { return substitution; } } }); Object.defineProperty(String, "format", { value(format, substitutions, formatters, initialValue, append) { if (!format || !substitutions || !substitutions.length) return {formattedResult: append(initialValue, format), unusedSubstitutions: substitutions}; function prettyFunctionName() { return "String.format(\"" + format + "\", \"" + Array.from(substitutions).join("\", \"") + "\")"; } function warn(msg) { console.warn(prettyFunctionName() + ": " + msg); } function error(msg) { console.error(prettyFunctionName() + ": " + msg); } var result = initialValue; var tokens = String.tokenizeFormatString(format); var usedSubstitutionIndexes = {}; let ignoredUnknownSpecifierCount = 0; for (var i = 0; i < tokens.length; ++i) { var token = tokens[i]; if (token.type === "string") { result = append(result, token.value); continue; } if (token.type !== "specifier") { error("Unknown token type \"" + token.type + "\" found."); continue; } let substitutionIndex = token.substitutionIndex - ignoredUnknownSpecifierCount; if (substitutionIndex >= substitutions.length) { // If there are not enough substitutions for the current substitutionIndex // just output the format specifier literally and move on. error("not enough substitution arguments. Had " + substitutions.length + " but needed " + (substitutionIndex + 1) + ", so substitution was skipped."); result = append(result, "%" + (token.precision > -1 ? token.precision : "") + token.specifier); continue; } if (!(token.specifier in formatters)) { warn(`Unsupported format specifier "%${token.specifier}" will be ignored.`); result = append(result, "%" + token.specifier); ++ignoredUnknownSpecifierCount; continue; } usedSubstitutionIndexes[substitutionIndex] = true; result = append(result, formatters[token.specifier](substitutions[substitutionIndex], token)); } var unusedSubstitutions = []; for (var i = 0; i < substitutions.length; ++i) { if (i in usedSubstitutionIndexes) continue; unusedSubstitutions.push(substitutions[i]); } return {formattedResult: result, unusedSubstitutions}; } }); Object.defineProperty(String.prototype, "format", { value() { return String.format(this, arguments, String.standardFormatters, "", function(a, b) { return a + b; }).formattedResult; } }); Object.defineProperty(String.prototype, "insertWordBreakCharacters", { value() { // Add zero width spaces after characters that are good to break after. // Otherwise a string with no spaces will not break and overflow its container. // This is mainly used on URL strings, so the characters are tailored for URLs. return this.replace(/([\/;:\)\]\}&?])/g, "$1\u200b"); } }); Object.defineProperty(String.prototype, "removeWordBreakCharacters", { value() { // Undoes what insertWordBreakCharacters did. return this.replace(/\u200b/g, ""); } }); Object.defineProperty(String.prototype, "levenshteinDistance", { value(s) { var m = this.length; var n = s.length; var d = new Array(m + 1); for (var i = 0; i <= m; ++i) { d[i] = new Array(n + 1); d[i][0] = i; } for (var j = 0; j <= n; ++j) d[0][j] = j; for (var j = 1; j <= n; ++j) { for (var i = 1; i <= m; ++i) { if (this[i - 1] === s[j - 1]) d[i][j] = d[i - 1][j - 1]; else { var deletion = d[i - 1][j] + 1; var insertion = d[i][j - 1] + 1; var substitution = d[i - 1][j - 1] + 1; d[i][j] = Math.min(deletion, insertion, substitution); } } } return d[m][n]; } }); Object.defineProperty(String.prototype, "toCamelCase", { value() { return this.toLowerCase().replace(/[^\w]+(\w)/g, (match, group) => group.toUpperCase()); } }); Object.defineProperty(String.prototype, "hasMatchingEscapedQuotes", { value() { return /^\"(?:[^\"\\]|\\.)*\"$/.test(this) || /^\'(?:[^\'\\]|\\.)*\'$/.test(this); } }); Object.defineProperty(Math, "roundTo", { value(num, step) { return Math.round(num / step) * step; } }); // https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Matrix_math_for_the_web#Multiplying_a_matrix_and_a_point Object.defineProperty(Math, "multiplyMatrixByVector", { value(matrix, vector) { let height = matrix.length; let width = matrix[0].length; console.assert(width === vector.length); let result = Array(width).fill(0); for (let i = 0; i < width; ++i) { for (let rowIndex = 0; rowIndex < height; ++rowIndex) result[i] += vector[rowIndex] * matrix[i][rowIndex]; } return result; } }); Object.defineProperty(Number, "constrain", { value(num, min, max) { if (isNaN(num) || max < min) return min; if (num < min) num = min; else if (num > max) num = max; return num; } }); Object.defineProperty(Number, "percentageString", { value(fraction, precision = 1) { return fraction.toLocaleString(undefined, {minimumFractionDigits: precision, style: "percent"}); } }); Object.defineProperty(Number, "secondsToMillisecondsString", { value(seconds, higherResolution) { let ms = seconds * 1000; if (higherResolution) return WI.UIString("%.2fms").format(ms); return WI.UIString("%.1fms").format(ms); } }); Object.defineProperty(Number, "secondsToString", { value(seconds, higherResolution) { const epsilon = 0.0001; let ms = seconds * 1000; if (ms < epsilon) return WI.UIString("%.0fms").format(0); if (Math.abs(ms) < (10 + epsilon)) { if (higherResolution) return WI.UIString("%.3fms").format(ms); return WI.UIString("%.2fms").format(ms); } if (Math.abs(ms) < (100 + epsilon)) { if (higherResolution) return WI.UIString("%.2fms").format(ms); return WI.UIString("%.1fms").format(ms); } if (Math.abs(ms) < (1000 + epsilon)) { if (higherResolution) return WI.UIString("%.1fms").format(ms); return WI.UIString("%.0fms").format(ms); } // Do not go over seconds when in high resolution mode. if (higherResolution || Math.abs(seconds) < 60) return WI.UIString("%.2fs").format(seconds); let minutes = seconds / 60; if (Math.abs(minutes) < 60) return WI.UIString("%.1fmin").format(minutes); let hours = minutes / 60; if (Math.abs(hours) < 24) return WI.UIString("%.1fhrs").format(hours); let days = hours / 24; return WI.UIString("%.1f days").format(days); } }); Object.defineProperty(Number, "bytesToString", { value(bytes, higherResolution, bytesThreshold) { higherResolution ??= true; bytesThreshold ??= 1000; if (Math.abs(bytes) < bytesThreshold) return WI.UIString("%.0f B").format(bytes); let kilobytes = bytes / 1000; if (Math.abs(kilobytes) < 1000) { if (higherResolution || Math.abs(kilobytes) < 10) return WI.UIString("%.2f KB").format(kilobytes); return WI.UIString("%.1f KB").format(kilobytes); } let megabytes = kilobytes / 1000; if (Math.abs(megabytes) < 1000) { if (higherResolution || Math.abs(megabytes) < 10) return WI.UIString("%.2f MB").format(megabytes); return WI.UIString("%.1f MB").format(megabytes); } let gigabytes = megabytes / 1000; if (higherResolution || Math.abs(gigabytes) < 10) return WI.UIString("%.2f GB").format(gigabytes); return WI.UIString("%.1f GB").format(gigabytes); } }); Object.defineProperty(Number, "abbreviate", { value(num) { if (num < 1000) return num.toLocaleString(); if (num < 1_000_000) return WI.UIString("%.1fK").format(Math.round(num / 100) / 10); if (num < 1_000_000_000) return WI.UIString("%.1fM").format(Math.round(num / 100_000) / 10); return WI.UIString("%.1fB").format(Math.round(num / 100_000_000) / 10); } }); Object.defineProperty(Number, "zeroPad", { value(num, length) { let string = num.toLocaleString(); return string.padStart(length, "0"); }, }); Object.defineProperty(Number, "countDigits", { value(num) { if (num === 0) return 1; num = Math.abs(num); return Math.floor(Math.log(num) * Math.LOG10E) + 1; } }); Object.defineProperty(Number.prototype, "maxDecimals", { value(decimals) { let power = 10 ** decimals; return Math.round(this * power) / power; } }); Object.defineProperty(Uint32Array, "isLittleEndian", { value() { if ("_isLittleEndian" in this) return this._isLittleEndian; var buffer = new ArrayBuffer(4); var longData = new Uint32Array(buffer); var data = new Uint8Array(buffer); longData[0] = 0x0a0b0c0d; this._isLittleEndian = data[0] === 0x0d && data[1] === 0x0c && data[2] === 0x0b && data[3] === 0x0a; return this._isLittleEndian; } }); function isEmptyObject(object) { for (var property in object) return false; return true; } function isEnterKey(event) { // Check if this is an IME event. return event.keyCode !== 229 && event.keyIdentifier === "Enter"; } function resolveDotsInPath(path) { if (!path) return path; if (path.indexOf("./") === -1) return path; console.assert(path.charAt(0) === "/"); var result = []; var components = path.split("/"); for (var i = 0; i < components.length; ++i) { var component = components[i]; // Skip over "./". if (component === ".") continue; // Rewind one component for "../". if (component === "..") { if (result.length === 1) continue; result.pop(); continue; } result.push(component); } return result.join("/"); } function parseMIMEType(fullMimeType) { if (!fullMimeType) return {type: fullMimeType, boundary: null, encoding: null}; var typeParts = fullMimeType.split(/\s*;\s*/); console.assert(typeParts.length >= 1); var type = typeParts[0]; var boundary = null; var encoding = null; for (var i = 1; i < typeParts.length; ++i) { var subparts = typeParts[i].split(/\s*=\s*/); if (subparts.length !== 2) continue; if (subparts[0].toLowerCase() === "boundary") boundary = subparts[1]; else if (subparts[0].toLowerCase() === "charset") encoding = subparts[1].replace("^\"|\"$", ""); // Trim quotes. } return {type, boundary: boundary || null, encoding: encoding || null}; } function simpleGlobStringToRegExp(globString, regExpFlags) { // Only supports "*" globs. if (!globString) return null; // Escape everything from String.prototype.escapeForRegExp except "*". var regexString = globString.escapeCharacters("^[]{}()\\.$+?|"); // Unescape all doubly escaped backslashes in front of escaped asterisks. // So "\\*" will become "\*" again, undoing escapeCharacters escaping of "\". // This makes "\*" match a literal "*" instead of using the "*" for globbing. regexString = regexString.replace(/\\\\\*/g, "\\*"); // The following regex doesn't match an asterisk that has a backslash in front. // It also catches consecutive asterisks so they collapse down when replaced. var unescapedAsteriskRegex = /(^|[^\\])\*+/g; if (unescapedAsteriskRegex.test(globString)) { // Replace all unescaped asterisks with ".*". regexString = regexString.replace(unescapedAsteriskRegex, "$1.*"); // Match edge boundaries when there is an asterisk to better meet the expectations // of the user. When someone types "*.js" they don't expect "foo.json" to match. They // would only expect that if they type "*.js*". We use \b (instead of ^ and $) to allow // matches inside paths or URLs, so "ba*.js" will match "foo/bar.js" but not "boo/bbar.js". // When there isn't an asterisk the regexString is just a substring search. regexString = "\\b" + regexString + "\\b"; } return new RegExp(regexString, regExpFlags); } Object.defineProperty(Array.prototype, "min", { value(comparator) { return this[this.minIndex(comparator)]; }, }); Object.defineProperty(Array.prototype, "minIndex", { value(comparator) { function defaultComparator(a, b) { return a - b; } comparator = comparator || defaultComparator; let minIndex = -1; for (let i = 0; i < this.length; ++i) { if (minIndex === -1 || comparator(this[minIndex], this[i]) > 0) minIndex = i; } return minIndex; }, }); Object.defineProperty(Array.prototype, "lowerBound", { // Return index of the leftmost element that is equal or greater // than the specimen object. If there's no such element (i.e. all // elements are smaller than the specimen) returns array.length. // The function works for sorted array. value(object, comparator) { function defaultComparator(a, b) { return a - b; } comparator = comparator || defaultComparator; var l = 0; var r = this.length; while (l < r) { var m = (l + r) >> 1; if (comparator(object, this[m]) > 0) l = m + 1; else r = m; } return r; } }); Object.defineProperty(Array.prototype, "upperBound", { // Return index of the leftmost element that is greater // than the specimen object. If there's no such element (i.e. all // elements are smaller than the specimen) returns array.length. // The function works for sorted array. value(object, comparator) { function defaultComparator(a, b) { return a - b; } comparator = comparator || defaultComparator; var l = 0; var r = this.length; while (l < r) { var m = (l + r) >> 1; if (comparator(object, this[m]) >= 0) l = m + 1; else r = m; } return r; } }); Object.defineProperty(Array.prototype, "binaryIndexOf", { value(value, comparator) { function defaultComparator(a, b) { return a - b; } comparator = comparator || defaultComparator; var index = this.lowerBound(value, comparator); return index < this.length && comparator(value, this[index]) === 0 ? index : -1; } }); Object.defineProperty(Promise, "chain", { async value(callbacks, initialValue) { let results = []; for (let i = 0; i < callbacks.length; ++i) results.push(await callbacks[i](results.lastValue || initialValue || null, i)); return results; } }); Object.defineProperty(Promise, "delay", { value(delay) { return new Promise((resolve) => setTimeout(resolve, delay || 0)); } }); function appendWebInspectorSourceURL(string) { if (string.includes("//# sourceURL")) return string; return "\n//# sourceURL=__WebInspectorInternal__\n" + string; } function appendWebInspectorConsoleEvaluationSourceURL(string) { if (string.includes("//# sourceURL")) return string; return "\n//# sourceURL=__WebInspectorConsoleEvaluation__\n" + string; } function isWebInspectorBootstrapScript(url) { return url === WI.NetworkManager.bootstrapScriptURL; } function isWebInspectorInternalScript(url) { return url === "__WebInspectorInternal__"; } function isWebInspectorConsoleEvaluationScript(url) { return url === "__WebInspectorConsoleEvaluation__"; } function isWebKitInjectedScript(url) { return url && url.startsWith("__InjectedScript_") && url.endsWith(".js"); } function isWebKitInternalScript(url) { if (isWebInspectorConsoleEvaluationScript(url)) return false; if (isWebKitInjectedScript(url)) return true; return url && url.startsWith("__Web") && url.endsWith("__"); } function isFunctionStringNativeCode(str) { return str.endsWith("{\n [native code]\n}"); } function whitespaceRatio(content, start, end) { let whitespaceScore = 0; let size = end - start; for (let i = start; i < end; i++) { let char = content[i]; if (char === " ") whitespaceScore++; else if (char === "\t") whitespaceScore += 4; else if (char === "\n") whitespaceScore += 8; } let ratio = whitespaceScore / size; return ratio; } function isTextLikelyMinified(content) { const autoFormatMaxCharactersToCheck = 2500; const autoFormatWhitespaceRatio = 0.2; if (content.length <= autoFormatMaxCharactersToCheck) { let ratio = whitespaceRatio(content, 0, content.length); return ratio < autoFormatWhitespaceRatio; } let startRatio = whitespaceRatio(content, 0, autoFormatMaxCharactersToCheck); if (startRatio < autoFormatWhitespaceRatio) return true; let endRatio = whitespaceRatio(content, content.length - autoFormatMaxCharactersToCheck, content.length); if (endRatio < autoFormatWhitespaceRatio) return true; return false; } function doubleQuotedString(str) { return JSON.stringify(str); } function insertionIndexForObjectInListSortedByFunction(object, list, comparator, insertionIndexAfter) { if (insertionIndexAfter) { return list.upperBound(object, comparator); } else { return list.lowerBound(object, comparator); } } function insertObjectIntoSortedArray(object, array, comparator) { array.splice(insertionIndexForObjectInListSortedByFunction(object, array, comparator), 0, object); } async function retryUntil(predicate, {delay, retries} = {}) { retries ??= 100; delay ??= 100; for (let i = 0; i < retries; ++i) { let result = predicate(); if (result) return result; await Promise.delay(delay); } console.assert(false, "retryUntil exceeded the maximum number of retries.", predicate, retries); return null; } WI.setReentrantCheck = function(object, key) { key = "__checkReentrant_" + key; object[key] = (object[key] || 0) + 1; return object[key] === 1; }; WI.clearReentrantCheck = function(object, key) { key = "__checkReentrant_" + key; object[key] = (object[key] || 0) - 1; return object[key] === 0; }; /* Models/CallingContextTree.js */ /* * Copyright (C) 2016 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ WI.CallingContextTree = class CallingContextTree { constructor(type) { this._type = type || WI.CallingContextTree.Type.TopDown; this.reset(); } // Public get type() { return this._type; } get totalNumberOfSamples() { return this._totalNumberOfSamples; } reset() { this._root = new WI.CallingContextTreeNode(-1, -1, -1, "", null); this._totalNumberOfSamples = 0; } totalDurationInTimeRange(startTime, endTime) { return this._root.filteredTimestampsAndDuration(startTime, endTime).duration; } updateTreeWithStackTrace({timestamp, stackFrames}, duration) { this._totalNumberOfSamples++; let node = this._root; node.addTimestampAndExpressionLocation(timestamp, duration, null); switch (this._type) { case WI.CallingContextTree.Type.TopDown: for (let i = stackFrames.length; i--; ) { let stackFrame = stackFrames[i]; node = node.findOrMakeChild(stackFrame); node.addTimestampAndExpressionLocation(timestamp, duration, stackFrame.expressionLocation || null, i === 0); } break; case WI.CallingContextTree.Type.BottomUp: for (let i = 0; i < stackFrames.length; ++i) { let stackFrame = stackFrames[i]; node = node.findOrMakeChild(stackFrame); node.addTimestampAndExpressionLocation(timestamp, duration, stackFrame.expressionLocation || null, i === 0); } break; case WI.CallingContextTree.Type.TopFunctionsTopDown: for (let i = stackFrames.length; i--; ) { node = this._root; for (let j = i + 1; j--; ) { let stackFrame = stackFrames[j]; node = node.findOrMakeChild(stackFrame); node.addTimestampAndExpressionLocation(timestamp, duration, stackFrame.expressionLocation || null, j === 0); } } break; case WI.CallingContextTree.Type.TopFunctionsBottomUp: for (let i = 0; i < stackFrames.length; i++) { node = this._root; for (let j = i; j < stackFrames.length; j++) { let stackFrame = stackFrames[j]; node = node.findOrMakeChild(stackFrame); node.addTimestampAndExpressionLocation(timestamp, duration, stackFrame.expressionLocation || null, j === 0); } } break; default: console.assert(false, "This should not be reached."); break; } } toCPUProfilePayload(startTime, endTime) { let cpuProfile = {}; let roots = []; let numSamplesInTimeRange = this._root.filteredTimestampsAndDuration(startTime, endTime).timestamps.length; this._root.forEachChild((child) => { if (child.hasStackTraceInTimeRange(startTime, endTime)) roots.push(child.toCPUProfileNode(numSamplesInTimeRange, startTime, endTime)); }); cpuProfile.rootNodes = roots; return cpuProfile; } forEachChild(callback) { this._root.forEachChild(callback); } forEachNode(callback) { this._root.forEachNode(callback); } // Testing. static __test_makeTreeFromProtocolMessageObject(messageObject) { let tree = new WI.CallingContextTree; let stackTraces = messageObject.params.samples.stackTraces; for (let i = 0; i < stackTraces.length; i++) tree.updateTreeWithStackTrace(stackTraces[i]); return tree; } __test_matchesStackTrace(stackTrace) { // StackTrace should have top frame first in the array and bottom frame last. // We don't look for a match that traces down the tree from the root; instead, // we match by looking at all the leafs, and matching while walking up the tree // towards the root. If we successfully make the walk, we've got a match that // suffices for a particular test. A successful match doesn't mean we actually // walk all the way up to the root; it just means we didn't fail while walking // in the direction of the root. let leaves = this.__test_buildLeafLinkedLists(); outer: for (let node of leaves) { for (let stackNode of stackTrace) { for (let propertyName of Object.getOwnPropertyNames(stackNode)) { if (stackNode[propertyName] !== node[propertyName]) continue outer; } node = node.parent; } return true; } return false; } __test_buildLeafLinkedLists() { let result = []; let parent = null; this._root.__test_buildLeafLinkedLists(parent, result); return result; } }; WI.CallingContextTree.Type = { TopDown: Symbol("TopDown"), BottomUp: Symbol("BottomUp"), TopFunctionsTopDown: Symbol("TopFunctionsTopDown"), TopFunctionsBottomUp: Symbol("TopFunctionsBottomUp"), }; /* Models/CallingContextTreeNode.js */ /* * Copyright (C) 2016 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ WI.CallingContextTreeNode = class CallingContextTreeNode { constructor(sourceID, line, column, name, url, hash) { this._children = {}; this._sourceID = sourceID; this._line = line; this._column = column; this._name = name; this._url = url; this._uid = WI.CallingContextTreeNode.__uid++; this._timestamps = []; this._durations = []; this._leafTimestamps = []; this._leafDurations = []; this._expressionLocations = {}; // Keys are "line:column" strings. Values are arrays of timestamps in sorted order. this._hash = hash || WI.CallingContextTreeNode._hash(this); } // Static and Private static _hash(stackFrame) { return stackFrame.name + ":" + stackFrame.sourceID + ":" + stackFrame.line + ":" + stackFrame.column; } // Public get sourceID() { return this._sourceID; } get line() { return this._line; } get column() { return this._column; } get name() { return this._name; } get uid() { return this._uid; } get url() { return this._url; } get hash() { return this._hash; } hasChildrenInTimeRange(startTime, endTime) { for (let propertyName of Object.getOwnPropertyNames(this._children)) { let child = this._children[propertyName]; if (child.hasStackTraceInTimeRange(startTime, endTime)) return true; } return false; } hasStackTraceInTimeRange(startTime, endTime) { console.assert(startTime <= endTime); if (startTime > endTime) return false; let timestamps = this._timestamps; let length = timestamps.length; if (!length) return false; let index = timestamps.lowerBound(startTime); if (index === length) return false; console.assert(startTime <= timestamps[index]); let hasTimestampInRange = timestamps[index] <= endTime; return hasTimestampInRange; } filteredTimestampsAndDuration(startTime, endTime) { let lowerIndex = this._timestamps.lowerBound(startTime); let upperIndex = this._timestamps.upperBound(endTime); let totalDuration = 0; for (let i = lowerIndex; i < upperIndex; ++i) totalDuration += this._durations[i]; return { timestamps: this._timestamps.slice(lowerIndex, upperIndex), duration: totalDuration, }; } filteredLeafTimestampsAndDuration(startTime, endTime) { let lowerIndex = this._leafTimestamps.lowerBound(startTime); let upperIndex = this._leafTimestamps.upperBound(endTime); let totalDuration = 0; for (let i = lowerIndex; i < upperIndex; ++i) totalDuration += this._leafDurations[i]; return { leafTimestamps: this._leafTimestamps.slice(lowerIndex, upperIndex), leafDuration: totalDuration, }; } hasChildren() { return !isEmptyObject(this._children); } findOrMakeChild(stackFrame) { let hash = WI.CallingContextTreeNode._hash(stackFrame); let node = this._children[hash]; if (node) return node; node = new WI.CallingContextTreeNode(stackFrame.sourceID, stackFrame.line, stackFrame.column, stackFrame.name, stackFrame.url, hash); this._children[hash] = node; return node; } addTimestampAndExpressionLocation(timestamp, duration, expressionLocation, leaf) { console.assert(!this._timestamps.length || this._timestamps.lastValue <= timestamp, "Expected timestamps to be added in sorted, increasing, order."); this._timestamps.push(timestamp); this._durations.push(duration); if (leaf) { this._leafTimestamps.push(timestamp); this._leafDurations.push(duration); } if (!expressionLocation) return; let {line, column} = expressionLocation; let hashCons = line + ":" + column; let timestamps = this._expressionLocations[hashCons]; if (!timestamps) { timestamps = []; this._expressionLocations[hashCons] = timestamps; } console.assert(!timestamps.length || timestamps.lastValue <= timestamp, "Expected timestamps to be added in sorted, increasing, order."); timestamps.push(timestamp); } forEachChild(callback) { for (let propertyName of Object.getOwnPropertyNames(this._children)) callback(this._children[propertyName]); } forEachNode(callback) { callback(this); this.forEachChild(function(child) { child.forEachNode(callback); }); } equals(other) { return this._hash === other.hash; } toCPUProfileNode(numSamples, startTime, endTime) { let children = []; this.forEachChild((child) => { if (child.hasStackTraceInTimeRange(startTime, endTime)) children.push(child.toCPUProfileNode(numSamples, startTime, endTime)); }); let cpuProfileNode = { id: this._uid, functionName: this._name, url: this._url, lineNumber: this._line, columnNumber: this._column, children: children }; let timestamps = []; let frameStartTime = Number.MAX_VALUE; let frameEndTime = Number.MIN_VALUE; for (let i = 0; i < this._timestamps.length; i++) { let timestamp = this._timestamps[i]; if (startTime <= timestamp && timestamp <= endTime) { timestamps.push(timestamp); frameStartTime = Math.min(frameStartTime, timestamp); frameEndTime = Math.max(frameEndTime, timestamp); } } cpuProfileNode.callInfo = { callCount: timestamps.length, // Totally not callCount, but oh well, this makes life easier because of field names. startTime: frameStartTime, endTime: frameEndTime, totalTime: (timestamps.length / numSamples) * (endTime - startTime) }; return cpuProfileNode; } // Testing. __test_buildLeafLinkedLists(parent, result) { let linkedListNode = { name: this._name, url: this._url, parent: parent }; if (this.hasChildren()) { this.forEachChild((child) => { child.__test_buildLeafLinkedLists(linkedListNode, result); }); } else { // We're a leaf. result.push(linkedListNode); } } }; WI.CallingContextTreeNode.__uid = 0; /* Models/WrappedPromise.js */ /* * Copyright (C) 2015, 2016 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ WI.WrappedPromise = class WrappedPromise { constructor(work) { this._settled = false; this._promise = new Promise((resolve, reject) => { this._resolveCallback = resolve; this._rejectCallback = reject; // Allow work to resolve or reject the promise by shimming our // internal callbacks. This ensures that this._settled gets set properly. if (work && typeof work === "function") return work(this.resolve.bind(this), this.reject.bind(this)); }); } // Public get settled() { return this._settled; } get promise() { return this._promise; } resolve(value) { if (this._settled) throw new Error("Promise is already settled, cannot call resolve()."); this._settled = true; this._resolveCallback(value); } reject(value) { if (this._settled) throw new Error("Promise is already settled, cannot call reject()."); this._settled = true; this._rejectCallback(value); } }; /* Test/TestSuite.js */ /* * Copyright (C) 2015 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ TestSuite = class TestSuite { constructor(harness, name) { if (!(harness instanceof TestHarness)) throw new Error("Must pass the test's harness as the first argument."); if (typeof name !== "string" || !name.trim().length) throw new Error("Tried to create TestSuite without string suite name."); this.name = name; this._harness = harness; this.testcases = []; this.runCount = 0; this.failCount = 0; } // Use this if the test file only has one suite, and no handling // of the value returned by runTestCases() is needed. runTestCasesAndFinish() { throw new Error("Must be implemented by subclasses."); } runTestCases() { throw new Error("Must be implemented by subclasses."); } get passCount() { return this.runCount - (this.failCount - this.skipCount); } get skipCount() { if (this.failCount) return this.testcases.length - this.runCount; else return 0; } addTestCase(testcase) { if (!testcase || !(testcase instanceof Object)) throw new Error("Tried to add non-object test case."); if (typeof testcase.name !== "string" || !testcase.name.trim().length) throw new Error("Tried to add test case without a name."); if (typeof testcase.test !== "function") throw new Error("Tried to add test case without `test` function."); if (testcase.setup && typeof testcase.setup !== "function") throw new Error("Tried to add test case with invalid `setup` parameter (must be a function)."); if (testcase.teardown && typeof testcase.teardown !== "function") throw new Error("Tried to add test case with invalid `teardown` parameter (must be a function)."); this.testcases.push(testcase); } // Protected logThrownObject(e) { let message = e; let stack = "(unknown)"; if (e instanceof Error) { message = e.message; if (e.stack) stack = e.stack; } if (typeof message !== "string") message = JSON.stringify(message); let sanitizedStack = this._harness.sanitizeStack(stack); let result = `!! EXCEPTION: ${message}`; if (stack) result += `\nStack Trace: ${sanitizedStack}`; this._harness.log(result); } }; AsyncTestSuite = class AsyncTestSuite extends TestSuite { runTestCasesAndFinish() { let finish = () => { this._harness.completeTest(); }; this.runTestCases() .then(finish) .catch(finish); } runTestCases() { if (!this.testcases.length) throw new Error("Tried to call runTestCases() for suite with no test cases"); if (this._startedRunning) throw new Error("Tried to call runTestCases() more than once."); this._startedRunning = true; this._harness.log(""); this._harness.log(`== Running test suite: ${this.name}`); // Avoid adding newlines if nothing was logged. let priorLogCount = this._harness.logCount; return Promise.resolve().then(() => Promise.chain(this.testcases.map((testcase, i) => () => new Promise(async (resolve, reject) => { if (i > 0 && priorLogCount < this._harness.logCount) this._harness.log(""); priorLogCount = this._harness.logCount; let hasTimeout = testcase.timeout !== -1; let timeoutId = undefined; if (hasTimeout) { let delay = testcase.timeout || 10000; timeoutId = setTimeout(() => { if (!timeoutId) return; timeoutId = undefined; this.failCount++; this._harness.log(`!! TIMEOUT: took longer than ${delay}ms`); resolve(); }, delay); } try { if (testcase.setup) { this._harness.log("-- Running test setup."); priorLogCount++; if (testcase.setup[Symbol.toStringTag] === "AsyncFunction") await testcase.setup(); else await new Promise(testcase.setup); } this.runCount++; this._harness.log(`-- Running test case: ${testcase.name}`); priorLogCount++; if (testcase.test[Symbol.toStringTag] === "AsyncFunction") await testcase.test(); else await new Promise(testcase.test); if (testcase.teardown) { this._harness.log("-- Running test teardown."); priorLogCount++; if (testcase.teardown[Symbol.toStringTag] === "AsyncFunction") await testcase.teardown(); else await new Promise(testcase.teardown); } } catch (e) { this.failCount++; this.logThrownObject(e); } if (!hasTimeout || timeoutId) { clearTimeout(timeoutId); timeoutId = undefined; resolve(); } }))) // Clear result value. .then(() => {})); } }; SyncTestSuite = class SyncTestSuite extends TestSuite { addTestCase(testcase) { if ([testcase.setup, testcase.teardown, testcase.test].some((fn) => fn && fn[Symbol.toStringTag] === "AsyncFunction")) throw new Error("Tried to pass a test case with an async `setup`, `test`, or `teardown` function, but this is a synchronous test suite."); super.addTestCase(testcase); } runTestCasesAndFinish() { this.runTestCases(); this._harness.completeTest(); } runTestCases() { if (!this.testcases.length) throw new Error("Tried to call runTestCases() for suite with no test cases"); if (this._startedRunning) throw new Error("Tried to call runTestCases() more than once."); this._startedRunning = true; this._harness.log(""); this._harness.log(`== Running test suite: ${this.name}`); let priorLogCount = this._harness.logCount; for (let i = 0; i < this.testcases.length; i++) { let testcase = this.testcases[i]; if (i > 0 && priorLogCount < this._harness.logCount) this._harness.log(""); priorLogCount = this._harness.logCount; try { // Run the setup action, if one was provided. if (testcase.setup) { this._harness.log("-- Running test setup."); priorLogCount++; let setupResult = testcase.setup(); if (setupResult === false) { this._harness.log("!! SETUP FAILED"); this.failCount++; continue; } } this.runCount++; this._harness.log(`-- Running test case: ${testcase.name}`); priorLogCount++; let testResult = testcase.test(); if (testResult === false) { this.failCount++; continue; } // Run the teardown action, if one was provided. if (testcase.teardown) { this._harness.log("-- Running test teardown."); priorLogCount++; let teardownResult = testcase.teardown(); if (teardownResult === false) { this._harness.log("!! TEARDOWN FAILED"); this.failCount++; continue; } } } catch (e) { this.failCount++; this.logThrownObject(e); } } return true; } }; /* Test/TestHarness.js */ /* * Copyright (C) 2015-2017 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ TestHarness = class TestHarness extends WI.Object { constructor() { super(); this._logCount = 0; this._failureObjects = new Map; this._failureObjectIdentifier = 1; // Options that are set per-test for debugging purposes. this.forceDebugLogging = false; // Options that are set per-test to ensure deterministic output. this.suppressStackTraces = false; } completeTest() { throw new Error("Must be implemented by subclasses."); } addResult() { throw new Error("Must be implemented by subclasses."); } debugLog() { throw new Error("Must be implemented by subclasses."); } // If 'callback' is a function, it will be with the arguments // callback(error, result, wasThrown). Otherwise, a promise is // returned that resolves with 'result' or rejects with 'error'. // The options object accepts the following keys and values: // 'remoteObjectOnly': if true, do not unwrap the result payload to a // primitive value even if possible. Useful if testing WI.RemoteObject directly. evaluateInPage(string, callback, options={}) { throw new Error("Must be implemented by subclasses."); } debug() { throw new Error("Must be implemented by subclasses."); } createAsyncSuite(name) { return new AsyncTestSuite(this, name); } createSyncSuite(name) { return new SyncTestSuite(this, name); } get logCount() { return this._logCount; } log(message) { ++this._logCount; if (this.forceDebugLogging) this.debugLog(message); else this.addResult(message); } newline() { this.log(""); } json(object, filter) { this.log(JSON.stringify(object, filter || null, 2)); } assert(condition, message) { if (condition) return; let stringifiedMessage = TestHarness.messageAsString(message); this.log("ASSERT: " + stringifiedMessage); } expectThat(actual, message) { this._expect(TestHarness.ExpectationType.True, !!actual, message, actual); } expectTrue(actual, message) { this._expect(TestHarness.ExpectationType.True, !!actual, message, actual); } expectFalse(actual, message) { this._expect(TestHarness.ExpectationType.False, !actual, message, actual); } expectEmpty(actual, message) { if (Array.isArray(actual) || typeof actual === "string") { this._expect(TestHarness.ExpectationType.Empty, !actual.length, message, actual); return; } if (actual instanceof Set || actual instanceof Map) { this._expect(TestHarness.ExpectationType.Empty, !actual.size, message, actual); return; } if (typeof actual === "object") { this._expect(TestHarness.ExpectationType.Empty, isEmptyObject(actual), message, actual); return; } this.fail("expectEmpty should not be called with a non-object:\n Actual: " + this._expectationValueAsString(actual)); } expectNotEmpty(actual, message) { if (Array.isArray(actual) || typeof actual === "string") { this._expect(TestHarness.ExpectationType.NotEmpty, !!actual.length, message, actual); return; } if (actual instanceof Set || actual instanceof Map) { this._expect(TestHarness.ExpectationType.NotEmpty, !!actual.size, message, actual); return; } if (typeof actual === "object") { this._expect(TestHarness.ExpectationType.NotEmpty, !isEmptyObject(actual), message, actual); return; } this.fail("expectNotEmpty should not be called with a non-object:\n Actual: " + this._expectationValueAsString(actual)); } expectNull(actual, message) { this._expect(TestHarness.ExpectationType.Null, actual === null, message, actual, null); } expectNotNull(actual, message) { this._expect(TestHarness.ExpectationType.NotNull, actual !== null, message, actual); } expectEqual(actual, expected, message) { this._expect(TestHarness.ExpectationType.Equal, expected === actual, message, actual, expected); } expectNotEqual(actual, expected, message) { this._expect(TestHarness.ExpectationType.NotEqual, expected !== actual, message, actual, expected); } expectShallowEqual(actual, expected, message) { this._expect(TestHarness.ExpectationType.ShallowEqual, Object.shallowEqual(actual, expected), message, actual, expected); } expectNotShallowEqual(actual, expected, message) { this._expect(TestHarness.ExpectationType.NotShallowEqual, !Object.shallowEqual(actual, expected), message, actual, expected); } expectEqualWithAccuracy(actual, expected, accuracy, message) { console.assert(typeof expected === "number"); console.assert(typeof actual === "number"); this._expect(TestHarness.ExpectationType.EqualWithAccuracy, Math.abs(expected - actual) <= accuracy, message, actual, expected, accuracy); } expectLessThan(actual, expected, message) { this._expect(TestHarness.ExpectationType.LessThan, actual < expected, message, actual, expected); } expectLessThanOrEqual(actual, expected, message) { this._expect(TestHarness.ExpectationType.LessThanOrEqual, actual <= expected, message, actual, expected); } expectGreaterThan(actual, expected, message) { this._expect(TestHarness.ExpectationType.GreaterThan, actual > expected, message, actual, expected); } expectGreaterThanOrEqual(actual, expected, message) { this._expect(TestHarness.ExpectationType.GreaterThanOrEqual, actual >= expected, message, actual, expected); } pass(message) { let stringifiedMessage = TestHarness.messageAsString(message); this.log("PASS: " + stringifiedMessage); } fail(message) { let stringifiedMessage = TestHarness.messageAsString(message); this.log("FAIL: " + stringifiedMessage); } passOrFail(condition, message) { if (condition) this.pass(message); else this.fail(message); } // Use this to expect an exception. To further examine the exception, // chain onto the result with .then() and add your own test assertions. // The returned promise is rejected if an exception was not thrown. expectException(work) { if (typeof work !== "function") throw new Error("Invalid argument to catchException: work must be a function."); let expectAndDumpError = (e, resolvedValue) => { this.expectNotNull(e, "Should produce an exception."); if (!e) { this.expectEqual(resolvedValue, undefined, "Exception-producing work should not return a value"); return; } if (e instanceof Error || !(e instanceof Object)) this.log(e.toString()); else { try { this.json(e); } catch { this.log(e.constructor.name); } } } let error = null; let result = null; try { result = work(); } catch (caughtError) { error = caughtError; } finally { // If 'work' returns a promise, it will settle (resolve or reject) by itself. // Invert the promise's settled state to match the expectation of the caller. if (result instanceof Promise) { return result.then((resolvedValue) => { expectAndDumpError(null, resolvedValue); return Promise.reject(resolvedValue); }, (e) => { // Don't chain the .catch as it will log the value we just rejected. expectAndDumpError(e); return Promise.resolve(e); }); } // If a promise is not produced, turn the exception into a resolved promise, and a // resolved value into a rejected value (since an exception was expected). expectAndDumpError(error); return error ? Promise.resolve(error) : Promise.reject(result); } } // Protected static messageAsString(message) { if (message instanceof Element) return message.textContent; return typeof message !== "string" ? JSON.stringify(message) : message; } static sanitizeURL(url) { if (!url) return "(unknown)"; let lastPathSeparator = Math.max(url.lastIndexOf("/"), url.lastIndexOf("\\")); let location = lastPathSeparator > 0 ? url.substr(lastPathSeparator + 1) : url; if (!location.length) location = "(unknown)"; // Clean up the location so it is bracketed or in parenthesis. if (url.indexOf("[native code]") !== -1) location = "[native code]"; return location; } static sanitizeStackFrame(frame, i) { // Most frames are of the form "functionName@file:///foo/bar/File.js:345". // But, some frames do not have a functionName. Get rid of the file path. let nameAndURLSeparator = frame.indexOf("@"); let frameName = nameAndURLSeparator > 0 ? frame.substr(0, nameAndURLSeparator) : "(anonymous)"; let lastPathSeparator = Math.max(frame.lastIndexOf("/"), frame.lastIndexOf("\\")); let frameLocation = lastPathSeparator > 0 ? frame.substr(lastPathSeparator + 1) : frame; if (!frameLocation.length) frameLocation = "unknown"; // Clean up the location so it is bracketed or in parenthesis. if (frame.indexOf("[native code]") !== -1) frameLocation = "[native code]"; else frameLocation = "(" + frameLocation + ")"; return `#${i}: ${frameName} ${frameLocation}`; } sanitizeStack(stack) { if (this.suppressStackTraces) return "(suppressed)"; if (!stack || typeof stack !== "string") return "(unknown)"; return stack.split("\n").map(TestHarness.sanitizeStackFrame).join("\n"); } // Private _expect(type, condition, message, ...values) { console.assert(values.length > 0, "Should have an 'actual' value."); if (!message || !condition) { values = values.map(this._expectationValueAsString.bind(this)); message = message || this._expectationMessageFormat(type).format(...values); } if (condition) { this.pass(message); return; } message += "\n Expected: " + this._expectedValueFormat(type).format(...values.slice(1)); message += "\n Actual: " + values[0]; this.fail(message); } _expectationValueAsString(value) { let instanceIdentifier = (object) => { let id = this._failureObjects.get(object); if (!id) { id = this._failureObjectIdentifier++; this._failureObjects.set(object, id); } return "#" + id; }; const maximumValueStringLength = 200; const defaultValueString = String(new Object); // [object Object] // Special case for numbers, since JSON.stringify converts Infinity and NaN to null. if (typeof value === "number") return value; try { let valueString = JSON.stringify(value); if (valueString.length <= maximumValueStringLength) return valueString; } catch { } try { let valueString = String(value); if (valueString === defaultValueString && value.constructor && value.constructor.name !== "Object") return value.constructor.name + " instance " + instanceIdentifier(value); return valueString; } catch { return defaultValueString; } } _expectationMessageFormat(type) { switch (type) { case TestHarness.ExpectationType.True: return "expectThat(%s)"; case TestHarness.ExpectationType.False: return "expectFalse(%s)"; case TestHarness.ExpectationType.Empty: return "expectEmpty(%s)"; case TestHarness.ExpectationType.NotEmpty: return "expectNotEmpty(%s)"; case TestHarness.ExpectationType.Null: return "expectNull(%s)"; case TestHarness.ExpectationType.NotNull: return "expectNotNull(%s)"; case TestHarness.ExpectationType.Equal: return "expectEqual(%s, %s)"; case TestHarness.ExpectationType.NotEqual: return "expectNotEqual(%s, %s)"; case TestHarness.ExpectationType.ShallowEqual: return "expectShallowEqual(%s, %s)"; case TestHarness.ExpectationType.NotShallowEqual: return "expectNotShallowEqual(%s, %s)"; case TestHarness.ExpectationType.EqualWithAccuracy: return "expectEqualWithAccuracy(%s, %s, %s)"; case TestHarness.ExpectationType.LessThan: return "expectLessThan(%s, %s)"; case TestHarness.ExpectationType.LessThanOrEqual: return "expectLessThanOrEqual(%s, %s)"; case TestHarness.ExpectationType.GreaterThan: return "expectGreaterThan(%s, %s)"; case TestHarness.ExpectationType.GreaterThanOrEqual: return "expectGreaterThanOrEqual(%s, %s)"; default: console.error("Unknown TestHarness.ExpectationType type: " + type); return null; } } _expectedValueFormat(type) { switch (type) { case TestHarness.ExpectationType.True: return "truthy"; case TestHarness.ExpectationType.False: return "falsey"; case TestHarness.ExpectationType.Empty: return "empty"; case TestHarness.ExpectationType.NotEmpty: return "not empty"; case TestHarness.ExpectationType.NotNull: return "not null"; case TestHarness.ExpectationType.NotEqual: case TestHarness.ExpectationType.NotShallowEqual: return "not %s"; case TestHarness.ExpectationType.EqualWithAccuracy: return "%s +/- %s"; case TestHarness.ExpectationType.LessThan: return "less than %s"; case TestHarness.ExpectationType.LessThanOrEqual: return "less than or equal to %s"; case TestHarness.ExpectationType.GreaterThan: return "greater than %s"; case TestHarness.ExpectationType.GreaterThanOrEqual: return "greater than or equal to %s"; default: return "%s"; } } }; TestHarness.ExpectationType = { True: Symbol("expect-true"), False: Symbol("expect-false"), Empty: Symbol("expect-empty"), NotEmpty: Symbol("expect-not-empty"), Null: Symbol("expect-null"), NotNull: Symbol("expect-not-null"), Equal: Symbol("expect-equal"), NotEqual: Symbol("expect-not-equal"), ShallowEqual: Symbol("expect-shallow-equal"), NotShallowEqual: Symbol("expect-not-shallow-equal"), EqualWithAccuracy: Symbol("expect-equal-with-accuracy"), LessThan: Symbol("expect-less-than"), LessThanOrEqual: Symbol("expect-less-than-or-equal"), GreaterThan: Symbol("expect-greater-than"), GreaterThanOrEqual: Symbol("expect-greater-than-or-equal"), }; /* Test/ProtocolTestHarness.js */ /* * Copyright (C) 2015 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ ProtocolTestHarness = class ProtocolTestHarness extends TestHarness { // TestHarness Overrides completeTest() { if (this.dumpActivityToSystemConsole) InspectorFrontendHost.unbufferedLog("completeTest()"); this.evaluateInPage("TestPage.closeTest();"); } addResult(message) { let stringifiedMessage = TestHarness.messageAsString(message); if (this.dumpActivityToSystemConsole) InspectorFrontendHost.unbufferedLog(stringifiedMessage); // Unfortunately, every string argument must be escaped because tests are not consistent // with respect to escaping with single or double quotes. Some exceptions use single quotes. this.evaluateInPage(`TestPage.log(unescape("${escape(stringifiedMessage)}"));`); } debugLog(message) { let stringifiedMessage = TestHarness.messageAsString(message); if (this.dumpActivityToSystemConsole) InspectorFrontendHost.unbufferedLog(stringifiedMessage); this.evaluateInPage(`TestPage.debugLog(unescape("${escape(stringifiedMessage)}"));`); } evaluateInPage(expression, callback) { let args = { method: "Runtime.evaluate", params: {expression} }; if (typeof callback === "function") InspectorProtocol.sendCommand(args, callback); else return InspectorProtocol.awaitCommand(args); } debug() { this.dumpActivityToSystemConsole = true; this.dumpInspectorProtocolMessages = true; } }; /* Test/TestUtilities.js */ /* * Copyright (C) 2017 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ // // This can be used to get a promise for any function that takes a callback. // // For example: // // object.getValues(arg1, arg2, (callbackArg1, callbackArg2) => { // ... // }); // // Can be promisified like so: // // promisify((cb) => { object.getValues(arg1, arg2, cb); }).then([callbackArg1, callbackArg2]) { // ... // }); // // Or more naturally with await: // // let [callbackArg1, callbackArg2] = await promisify((cb) => { object.getValues(arg1, arg2, cb); }); // function promisify(func) { return new Promise((resolve, reject) => { try { func((...args) => { resolve(args); }); } catch (e) { reject(e); } }); } function sanitizeURL(url) { return url.replace(/^.*?LayoutTests\//, ""); } /* Test/InspectorProtocol.js */ /* * Copyright (C) 2012 Samsung Electronics. All rights reserved. * Copyright (C) 2014, 2015 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ InspectorProtocol = {}; InspectorProtocol._dispatchTable = []; InspectorProtocol._placeholderRequestIds = []; InspectorProtocol._requestId = -1; InspectorProtocol.eventHandler = {}; InspectorProtocol.sendCommand = function(methodOrObject, params, handler) { // Allow new-style arguments object, as in awaitCommand. let method = methodOrObject; if (typeof methodOrObject === "object") ({method, params, handler} = methodOrObject); else if (!params) params = {}; this._dispatchTable[++this._requestId] = handler; let messageObject = {method, params, id: this._requestId}; this._sendMessage(messageObject); return this._requestId; }; InspectorProtocol.awaitCommand = function(args) { let {method, params} = args; let messageObject = {method, params, id: ++this._requestId}; return this.awaitMessage(messageObject); }; InspectorProtocol.awaitMessage = function(messageObject) { // Send a raw message to the backend. Mostly used to test the backend's error handling. return new Promise((resolve, reject) => { let requestId = messageObject.id; // If the caller did not provide an id, then make one up so that the response // can be used to settle a promise. if (typeof requestId !== "number") { requestId = ++this._requestId; this._placeholderRequestIds.push(requestId); } this._dispatchTable[requestId] = {resolve, reject}; this._sendMessage(messageObject); }); }; InspectorProtocol.awaitEvent = function(args) { let event = args.event; if (typeof event !== "string") throw new Error("Event must be a string."); return new Promise((resolve, reject) => { InspectorProtocol.eventHandler[event] = function(message) { InspectorProtocol.eventHandler[event] = undefined; resolve(message); }; }); }; InspectorProtocol._sendMessage = function(messageObject) { let messageString = typeof messageObject !== "string" ? JSON.stringify(messageObject) : messageObject; if (ProtocolTest.dumpInspectorProtocolMessages) InspectorFrontendHost.unbufferedLog(`frontend: ${messageString}`); InspectorFrontendHost.sendMessageToBackend(messageString); }; InspectorProtocol.addEventListener = function(eventTypeOrObject, listener) { let event = eventTypeOrObject; if (typeof eventTypeOrObject === "object") ({event, listener} = eventTypeOrObject); if (typeof event !== "string") throw new Error("Event name must be a string."); if (typeof listener !== "function") throw new Error("Event listener must be callable."); // Convert to an array of listeners. let listeners = InspectorProtocol.eventHandler[event]; if (!listeners) listeners = InspectorProtocol.eventHandler[event] = []; else if (typeof listeners === "function") listeners = InspectorProtocol.eventHandler[event] = [listeners]; // Prevent registering multiple times. if (listeners.includes(listener)) throw new Error("Cannot register the same listener more than once."); listeners.push(listener); return listener; }; InspectorProtocol.removeEventListener = function(eventTypeOrObject, listener) { let event = eventTypeOrObject; if (typeof eventTypeOrObject === "object") ({event, listener} = eventTypeOrObject); if (typeof event !== "string") throw new Error("Event name must be a string."); if (typeof listener !== "function") throw new Error("Event listener must be callable."); // Convert to an array of listeners. let listeners = InspectorProtocol.eventHandler[event]; if (!listeners) return; listeners.removeAll(listener); }; InspectorProtocol.checkForError = function(responseObject) { if (responseObject.error) { ProtocolTest.log("PROTOCOL ERROR: " + JSON.stringify(responseObject.error)); ProtocolTest.completeTest(); throw "PROTOCOL ERROR"; } }; InspectorProtocol.dispatchMessageFromBackend = function(messageObject) { // This matches the debug dumping in InspectorBackend, which is bypassed // by InspectorProtocol. Return messages should be dumped by InspectorBackend. if (ProtocolTest.dumpInspectorProtocolMessages) InspectorFrontendHost.unbufferedLog("backend: " + JSON.stringify(messageObject)); // If the message has an id, then it is a reply to a command. let messageId = messageObject.id; // If the id is 'null', then it may be an error response. if (messageId === null) messageId = InspectorProtocol._placeholderRequestIds.shift(); // If we could figure out a requestId, then dispatch the message. if (typeof messageId === "number") { let handler = InspectorProtocol._dispatchTable[messageId]; if (!handler) return; if (typeof handler === "function") handler(messageObject); else if (typeof handler === "object") { let {resolve, reject} = handler; if ("error" in messageObject) reject(messageObject.error); else resolve(messageObject.result); } } else { // Otherwise, it is an event. let eventName = messageObject["method"]; let handler = InspectorProtocol.eventHandler[eventName]; if (!handler) return; if (typeof handler === "function") handler(messageObject); else if (handler instanceof Array) { handler.map((listener) => { listener.call(null, messageObject); }); } else if (typeof handler === "object") { let {resolve, reject} = handler; if ("error" in messageObject) reject(messageObject.error); else resolve(messageObject.result); } } }; /* Test/TestStub.js */ /* * Copyright (C) 2012 Samsung Electronics. All rights reserved. * Copyright (C) 2014, 2015 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ InspectorFrontendAPI = {}; InspectorFrontendAPI.dispatch = function () { }; InspectorFrontendAPI.dispatchMessageAsync = InspectorProtocol.dispatchMessageFromBackend; window.ProtocolTest = new ProtocolTestHarness(); document.addEventListener("DOMContentLoaded", (event) => { InspectorFrontendHost.loaded(); }); window.addEventListener("message", (event) => { try { eval(event.data); } catch (e) { alert(e.stack); ProtocolTest.completeTest(); throw e; } });