/* * Copyright (C) 2007-2021 Apple Inc. All rights reserved. * Copyright (C) 2008 Matt Lilek. All rights reserved. * Copyright (C) 2008-2009 Anthony Ricaud * Copyright (C) 2009-2010 Joseph Pecoraro. All rights reserved. * Copyright (C) 2009-2011 Google Inc. All rights reserved. * Copyright (C) 2009 280 North Inc. All Rights Reserved. * Copyright (C) 2010 Nikita Vasilyev. All rights reserved. * Copyright (C) 2011 Brian Grinstead All rights reserved. * Copyright (C) 2013 Matt Holden * Copyright (C) 2013 Samsung Electronics. All rights reserved. * Copyright (C) 2013 Seokju Kwon (seokju.kwon@gmail.com) * Copyright (C) 2013 Adobe Systems Inc. All rights reserved. * Copyright (C) 2013-2015 University of Washington. All rights reserved. * Copyright (C) 2014-2015 Saam Barati * Copyright (C) 2014 Antoine Quint * Copyright (C) 2015 Tobias Reiss * Copyright (C) 2015-2017 Devin Rousso . All rights reserved. * Copyright (C) 2017 The Chromium Authors * Copyright (C) 2017-2018 Sony Interactive Entertainment Inc. * * 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. */ /* 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/Platform.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. */ WI.Platform = { name: InspectorFrontendHost.platform, version: { name: InspectorFrontendHost.platformVersionName, } }; /* Base/Debouncer.js */ /* * Copyright (C) 2019 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. */ // Debouncer wraps a function and continues to delay its invocation as long as // clients continue to delay its firing. The most recent delay call overrides // previous calls. Delays may be timeouts, animation frames, or microtasks. // // Example: // // let debouncer = new Debouncer(() => { this.refresh() }); // element.addEventListener("keydown", (event) => { debouncer.delayForTime(100); }); // // Will ensure `refresh` will not happen until no keyevent has happened in 100ms: // // 0ms 100ms 200ms 300ms 400ms // time: |-------------|-------------|-------------|-------------| // delay: ^ ^ ^ ^ ^ ^ ^ ^ // refreshes: * (1) // // When the wrapped function is actually called, it will be given the most recent set of arguments. class Debouncer { constructor(callback) { console.assert(typeof callback === "function"); this._callback = callback; this._lastArguments = []; this._timeoutIdentifier = undefined; this._animationFrameIdentifier = undefined; this._promiseIdentifier = undefined; } // Public force() { this._lastArguments = arguments; this._execute(); } delayForTime(time, ...args) { console.assert(time >= 0); this.cancel(); this._lastArguments = args; this._timeoutIdentifier = setTimeout(() => { this._execute(); }, time); } delayForFrame() { this.cancel(); this._lastArguments = arguments; this._animationFrameIdentifier = requestAnimationFrame(() => { this._execute(); }); } delayForMicrotask() { this.cancel(); this._lastArguments = arguments; let promiseIdentifier = Symbol("next-microtask"); this._promiseIdentifier = promiseIdentifier; queueMicrotask(() => { if (this._promiseIdentifier === promiseIdentifier) this._execute(); }); } cancel() { this._lastArguments = []; if (this._timeoutIdentifier) { clearTimeout(this._timeoutIdentifier); this._timeoutIdentifier = undefined; } if (this._animationFrameIdentifier) { cancelAnimationFrame(this._animationFrameIdentifier); this._animationFrameIdentifier = undefined; } if (this._promiseIdentifier) this._promiseIdentifier = undefined; } // Private _execute() { let args = this._lastArguments; this.cancel(); this._callback.apply(undefined, args); } } /* Base/DebuggableType.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 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.DebuggableType = { ITML: "itml", JavaScript: "javascript", Page: "page", ServiceWorker: "service-worker", WebPage: "web-page", }; WI.DebuggableType.fromString = function(type) { switch (type) { case "itml": return WI.DebuggableType.ITML; case "javascript": return WI.DebuggableType.JavaScript; case "page": return WI.DebuggableType.Page; case "service-worker": return WI.DebuggableType.ServiceWorker; case "web-page": return WI.DebuggableType.WebPage; } console.assert(false, "Unknown debuggable type", type); return null; }; WI.DebuggableType.supportedTargetTypes = function(debuggableType) { let targetTypes = new Set; switch (debuggableType) { case WI.DebuggableType.ITML: targetTypes.add(WI.TargetType.ITML); break; case WI.DebuggableType.JavaScript: targetTypes.add(WI.TargetType.JavaScript); break; case WI.DebuggableType.Page: targetTypes.add(WI.TargetType.Page); targetTypes.add(WI.TargetType.Worker); break; case WI.DebuggableType.ServiceWorker: targetTypes.add(WI.TargetType.ServiceWorker); break; case WI.DebuggableType.WebPage: targetTypes.add(WI.TargetType.Page); targetTypes.add(WI.TargetType.WebPage); targetTypes.add(WI.TargetType.Worker); break; } console.assert(targetTypes.size, "Unknown debuggable type", debuggableType); return targetTypes; }; /* Base/IterableWeakSet.js */ /* * Copyright (C) 2022 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 IterableWeakSet { constructor(items = []) { this._wrappers = new Set; this._wrapperForItem = new WeakMap; for (let item of items) this.add(item); } // Public get size() { let size = 0; for (let wrapper of this._wrappers) { if (wrapper.deref()) ++size; } return size; } has(item) { let result = this._wrapperForItem.has(item); console.assert(Array.from(this._wrappers).some((wrapper) => wrapper.deref() === item) === result, this, item); return result; } add(item) { console.assert(typeof item === "object", item); console.assert(item !== null, item); if (this.has(item)) return; let wrapper = new WeakRef(item); this._wrappers.add(wrapper); this._wrapperForItem.set(item, wrapper); this._finalizationRegistry.register(item, {weakThis: new WeakRef(this), wrapper}, wrapper); } delete(item) { return !!this.take(item); } take(item) { let wrapper = this._wrapperForItem.get(item); if (!wrapper) return undefined; let itemDeleted = this._wrapperForItem.delete(item); console.assert(itemDeleted, this, item); let wrapperDeleted = this._wrappers.delete(wrapper); console.assert(wrapperDeleted, this, item); this._finalizationRegistry.unregister(wrapper); console.assert(wrapper.deref() === item, this, item); return item; } clear() { for (let wrapper of this._wrappers) { this._wrapperForItem.delete(wrapper); this._finalizationRegistry.unregister(wrapper); } this._wrappers.clear(); } keys() { return this.values(); } *values() { for (let wrapper of this._wrappers) { let item = wrapper.deref(); console.assert(!item === !this._wrapperForItem.has(item), this, item); if (item) yield item; } } [Symbol.iterator]() { return this.values(); } copy() { return new IterableWeakSet(this.toJSON()); } toJSON() { return Array.from(this); } // Private get _finalizationRegistry() { return IterableWeakSet._finalizationRegistry ??= new FinalizationRegistry(function(heldValue) { heldValue.weakThis.deref()?._wrappers.delete(heldValue.wrapper); }); } } /* 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/ReferencePage.js */ /* * Copyright (C) 2022 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.ReferencePage = class ReferencePage { constructor(page, {topic} = {}) { console.assert(page instanceof WI.ReferencePage || typeof page === "string", page); console.assert(!(page instanceof WI.ReferencePage && page._page instanceof WI.ReferencePage), page); console.assert(!(page instanceof WI.ReferencePage && page.topic), page); console.assert(!topic || typeof topic === "string", topic); if (page instanceof WI.ReferencePage) page = page.page; this._page = page; this._topic = topic || ""; } // Public get page() { return this._page; } get topic() { return this._topic; } createLinkElement() { let url = "https://webkit.org/web-inspector/" + this._page + "/"; if (this._topic) url += "#" + this._topic; let wrapper = document.createElement("span"); wrapper.className = "reference-page-link-container"; let link = wrapper.appendChild(document.createElement("a")); link.className = "reference-page-link"; link.href = link.title = url; link.textContent = "?"; link.addEventListener("click", (event) => { event.preventDefault(); event.stopPropagation(); WI.openURL(link.href, {alwaysOpenExternally: true}); }); return wrapper; } }; WI.ReferencePage.AuditTab = new WI.ReferencePage("audit-tab"); WI.ReferencePage.AuditTab.AuditResults = new WI.ReferencePage(WI.ReferencePage.AuditTab, {topic: "audit-results"}); WI.ReferencePage.AuditTab.CreatingAudits = new WI.ReferencePage(WI.ReferencePage.AuditTab, {topic: "creating-audits"}); WI.ReferencePage.AuditTab.EditingAudits = new WI.ReferencePage(WI.ReferencePage.AuditTab, {topic: "editing-audits"}); WI.ReferencePage.AuditTab.RunningAudits = new WI.ReferencePage(WI.ReferencePage.AuditTab, {topic: "running-audits"}); WI.ReferencePage.DOMBreakpoints = new WI.ReferencePage("dom-breakpoints"); WI.ReferencePage.DOMBreakpoints.Configuration = new WI.ReferencePage(WI.ReferencePage.DOMBreakpoints, {topic: "configuration"}); WI.ReferencePage.DeviceSettings = new WI.ReferencePage("device-settings"); WI.ReferencePage.DeviceSettings.Configuration = new WI.ReferencePage(WI.ReferencePage.DeviceSettings, {topic: "configuration"}); WI.ReferencePage.ElementsTab = new WI.ReferencePage("elements-tab"); WI.ReferencePage.ElementsTab.DOMTree = new WI.ReferencePage(WI.ReferencePage.ElementsTab, {topic: "dom-tree"}); WI.ReferencePage.EventBreakpoints = new WI.ReferencePage("event-breakpoints"); WI.ReferencePage.EventBreakpoints.Configuration = new WI.ReferencePage(WI.ReferencePage.EventBreakpoints, {topic: "configuration"}); WI.ReferencePage.JavaScriptBreakpoints = new WI.ReferencePage("javascript-breakpoints"); WI.ReferencePage.JavaScriptBreakpoints.Configuration = new WI.ReferencePage(WI.ReferencePage.JavaScriptBreakpoints, {topic: "configuration"}); WI.ReferencePage.LayersTab = new WI.ReferencePage("layers-tab"); WI.ReferencePage.LocalOverrides = new WI.ReferencePage("local-overrides"); WI.ReferencePage.LocalOverrides.ConfiguringLocalOverrides = new WI.ReferencePage(WI.ReferencePage.LocalOverrides, {topic: "configuring-local-overrides"}); WI.ReferencePage.NetworkTab = new WI.ReferencePage("network-tab"); WI.ReferencePage.NetworkTab.CookiesPane = new WI.ReferencePage(WI.ReferencePage.NetworkTab, {topic: "cookies-pane"}); WI.ReferencePage.NetworkTab.HeadersPane = new WI.ReferencePage(WI.ReferencePage.NetworkTab, {topic: "headers-pane"}); WI.ReferencePage.NetworkTab.PreviewPane = new WI.ReferencePage(WI.ReferencePage.NetworkTab, {topic: "preview-pane"}); WI.ReferencePage.NetworkTab.SecurityPane = new WI.ReferencePage(WI.ReferencePage.NetworkTab, {topic: "security-pane"}); WI.ReferencePage.NetworkTab.SizesPane = new WI.ReferencePage(WI.ReferencePage.NetworkTab, {topic: "sizes-pane"}); WI.ReferencePage.NetworkTab.TimingPane = new WI.ReferencePage(WI.ReferencePage.NetworkTab, {topic: "timing-pane"}); WI.ReferencePage.SymbolicBreakpoints = new WI.ReferencePage("symbolic-breakpoints"); WI.ReferencePage.SymbolicBreakpoints.Configuration = new WI.ReferencePage(WI.ReferencePage.SymbolicBreakpoints, {topic: "configuration"}); WI.ReferencePage.TimelinesTab = new WI.ReferencePage("timelines-tab"); WI.ReferencePage.TimelinesTab.CPUTimeline = new WI.ReferencePage(WI.ReferencePage.TimelinesTab, {topic: "cpu-timeline"}); WI.ReferencePage.TimelinesTab.EventsView = new WI.ReferencePage(WI.ReferencePage.TimelinesTab, {topic: "events-view"}); WI.ReferencePage.TimelinesTab.FramesView = new WI.ReferencePage(WI.ReferencePage.TimelinesTab, {topic: "frames-view"}); WI.ReferencePage.TimelinesTab.JavaScriptAllocationsTimeline = new WI.ReferencePage(WI.ReferencePage.TimelinesTab, {topic: "javascript-allocations-timeline"}); WI.ReferencePage.TimelinesTab.JavaScriptAndEventsTimeline = new WI.ReferencePage(WI.ReferencePage.TimelinesTab, {topic: "javascript-events-timeline"}); WI.ReferencePage.TimelinesTab.LayoutAndRenderingTimeline = new WI.ReferencePage(WI.ReferencePage.TimelinesTab, {topic: "layout-rendering-timeline"}); WI.ReferencePage.TimelinesTab.MediaAndAnimationsTimeline = new WI.ReferencePage(WI.ReferencePage.TimelinesTab, {topic: "media-animations-timeline"}); WI.ReferencePage.TimelinesTab.MemoryTimeline = new WI.ReferencePage(WI.ReferencePage.TimelinesTab, {topic: "memory-timeline"}); WI.ReferencePage.TimelinesTab.NetworkRequestsTimeline = new WI.ReferencePage(WI.ReferencePage.TimelinesTab, {topic: "network-timeline"}); WI.ReferencePage.TimelinesTab.ScreenshotsTimeline = new WI.ReferencePage(WI.ReferencePage.TimelinesTab, {topic: "screenshots-timeline"}); WI.ReferencePage.URLBreakpoints = new WI.ReferencePage("url-breakpoints"); WI.ReferencePage.URLBreakpoints.Configuration = new WI.ReferencePage(WI.ReferencePage.URLBreakpoints, {topic: "configuration"}); /* Base/TargetType.js */ /* * Copyright (C) 2019 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.TargetType = { ITML: "itml", JavaScript: "javascript", Page: "page", ServiceWorker: "service-worker", WebPage: "web-page", Worker: "worker", }; WI.TargetType.all = Object.values(WI.TargetType); /* Base/Throttler.js */ /* * Copyright (C) 2019 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. */ // Throttler wraps a function and ensures it doesn't get called more than once every `delay` ms. // The first fire will always trigger synchronous, and subsequent calls that need to be throttled // will happen later asynchronously. // // Example: // // let throttler = new Throttler(250, () => { this.refresh() }); // element.addEventListener("keydown", (event) => { throttler.fire(); }); // // Will ensure `refresh` happens no more than once every 250ms no matter how often `fire` is called: // // 0ms 250ms 500ms // time: |---------------------------|---------------------------| // fire: ^ ^ ^ ^ ^ ^ ^ ^ // refreshes: * (1) * (2) * (3) // // When the wrapped function is actually called, it will be given the most recent set of arguments. class Throttler { constructor(callback, delay) { console.assert(typeof callback === "function"); console.assert(delay >= 0); this._callback = callback; this._delay = delay; this._lastArguments = []; this._timeoutIdentifier = undefined; this._lastFireTime = -this._delay; } // Public force() { this._lastArguments = arguments; this._execute(); } fire() { this._lastArguments = arguments; let remaining = this._delay - (Date.now() - this._lastFireTime); if (remaining <= 0) { this._execute(); return; } if (this._timeoutIdentifier) return; this._timeoutIdentifier = setTimeout(() => { this._execute(); }, remaining); } cancel() { this._lastArguments = []; if (this._timeoutIdentifier) { clearTimeout(this._timeoutIdentifier); this._timeoutIdentifier = undefined; } } // Private _execute() { this._lastFireTime = Date.now(); let args = this._lastArguments; this.cancel(); this._callback.apply(undefined, args); } } /* 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/FrontendTestHarness.js */ /* * Copyright (C) 2013-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 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. */ FrontendTestHarness = class FrontendTestHarness extends TestHarness { constructor() { super(); this._results = []; this._testPageHasLoaded = false; // Options that are set per-test for debugging purposes. this.dumpActivityToSystemConsole = false; } // TestHarness Overrides completeTest() { if (this.dumpActivityToSystemConsole) InspectorFrontendHost.unbufferedLog("completeTest()"); // Wait for results to be resent before requesting completeTest(). Otherwise, messages will be // queued after pending dispatches run to zero and the test page will quit before processing them. if (this._testPageIsReloading) { this._completeTestAfterReload = true; return; } InspectorBackend.runAfterPendingDispatches(this.evaluateInPage.bind(this, "TestPage.completeTest()")); } addResult(message) { let stringifiedMessage = TestHarness.messageAsString(message); // Save the stringified message, since message may be a DOM element that won't survive reload. this._results.push(stringifiedMessage); if (this.dumpActivityToSystemConsole) InspectorFrontendHost.unbufferedLog(stringifiedMessage); if (!this._testPageIsReloading) this.evaluateInPage(`TestPage.addResult(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, options = {}) { let remoteObjectOnly = !!options.remoteObjectOnly; let target = WI.assumingMainTarget(); // If we load this page outside of the inspector, or hit an early error when loading // the test frontend, then defer evaluating the commands (indefinitely in the former case). if (this._originalConsole && (!target || !target.hasDomain("Runtime"))) { this._originalConsole["error"]("Tried to evaluate in test page, but connection not yet established:", expression); return; } // Return primitive values directly, otherwise return a WI.RemoteObject instance. function translateResult(result) { let remoteObject = WI.RemoteObject.fromPayload(result); return (!remoteObjectOnly && remoteObject.hasValue()) ? remoteObject.value : remoteObject; } let response = target.RuntimeAgent.evaluate.invoke({expression, objectGroup: "test", includeCommandLineAPI: false}); if (callback && typeof callback === "function") { response = response.then(({result, wasThrown}) => callback(null, translateResult(result), wasThrown)); response = response.catch((error) => callback(error, null, false)); } else { // Turn a thrown Error result into a promise rejection. return response.then(({result, wasThrown}) => { result = translateResult(result); if (result && wasThrown) return Promise.reject(new Error(result.description)); return Promise.resolve(result); }); } } debug() { this.dumpActivityToSystemConsole = true; InspectorBackend.dumpInspectorProtocolMessages = true; } // Frontend test-specific methods. expectNoError(error) { if (error) { InspectorTest.log("PROTOCOL ERROR: " + error); InspectorTest.completeTest(); throw "PROTOCOL ERROR"; } } deferOutputUntilTestPageIsReloaded() { console.assert(!this._testPageIsReloading); this._testPageIsReloading = true; } testPageDidLoad() { if (this.dumpActivityToSystemConsole) InspectorFrontendHost.unbufferedLog("testPageDidLoad()"); this._testPageIsReloading = false; if (this._testPageHasLoaded) this._resendResults(); else this._testPageHasLoaded = true; this.dispatchEventToListeners(FrontendTestHarness.Event.TestPageDidLoad); if (this._completeTestAfterReload) this.completeTest(); } reloadPage(options = {}) { console.assert(!this._testPageIsReloading); console.assert(!this._testPageReloadedOnce); this._testPageIsReloading = true; let {ignoreCache, revalidateAllResources} = options; ignoreCache = !!ignoreCache; revalidateAllResources = !!revalidateAllResources; let target = WI.assumingMainTarget(); return target.PageAgent.reload.invoke({ignoreCache, revalidateAllResources}) .then(() => { this._testPageReloadedOnce = true; return Promise.resolve(null); }); } redirectRequestAnimationFrame() { console.assert(!this._originalRequestAnimationFrame); if (this._originalRequestAnimationFrame) return; this._originalRequestAnimationFrame = window.requestAnimationFrame; this._requestAnimationFrameCallbacks = new Map; this._nextRequestIdentifier = 1; window.requestAnimationFrame = (callback) => { let requestIdentifier = this._nextRequestIdentifier++; this._requestAnimationFrameCallbacks.set(requestIdentifier, callback); if (this._requestAnimationFrameTimer) return requestIdentifier; let dispatchCallbacks = () => { let callbacks = this._requestAnimationFrameCallbacks; this._requestAnimationFrameCallbacks = new Map; this._requestAnimationFrameTimer = undefined; let timestamp = window.performance.now(); for (let callback of callbacks.values()) callback(timestamp); }; this._requestAnimationFrameTimer = setTimeout(dispatchCallbacks, 0); return requestIdentifier; }; window.cancelAnimationFrame = (requestIdentifier) => { if (!this._requestAnimationFrameCallbacks.delete(requestIdentifier)) return; if (!this._requestAnimationFrameCallbacks.size) { clearTimeout(this._requestAnimationFrameTimer); this._requestAnimationFrameTimer = undefined; } }; } redirectConsoleToTestOutput() { // We can't use arrow functions here because of 'arguments'. It might // be okay once rest parameters work. let self = this; function createProxyConsoleHandler(type) { return function() { self.addResult(`${type}: ` + Array.from(arguments).join(" ")); }; } function createProxyConsoleTraceHandler(){ return function() { try { throw new Exception(); } catch (e) { // Skip the first frame which is added by this function. let frames = e.stack.split("\n").slice(1); let sanitizedFrames = frames.map(TestHarness.sanitizeStackFrame); self.addResult("TRACE: " + Array.from(arguments).join(" ")); self.addResult(sanitizedFrames.join("\n")); } }; } let redirectedMethods = {}; for (let key in window.console) redirectedMethods[key] = window.console[key]; for (let type of ["log", "error", "info", "warn"]) redirectedMethods[type] = createProxyConsoleHandler(type.toUpperCase()); redirectedMethods["trace"] = createProxyConsoleTraceHandler(); this._originalConsole = window.console; window.console = redirectedMethods; } reportUnhandledRejection(error) { let message = error.message; let stack = error.stack; let result = `Unhandled promise rejection in inspector page: ${message}\n`; if (stack) { let sanitizedStack = this.sanitizeStack(stack); result += `\nStack Trace: ${sanitizedStack}\n`; } // If the connection to the test page is not set up, then just dump to console and give up. // Errors encountered this early can be debugged by loading Test.html in a normal browser page. if (this._originalConsole && !this._testPageHasLoaded) this._originalConsole["error"](result); this.addResult(result); this.completeTest(); // Stop default handler so we can empty InspectorBackend's message queue. return true; } reportUncaughtExceptionFromEvent(message, url, lineNumber, columnNumber) { // An exception thrown from a timer callback does not report a URL. if (url === "undefined") url = "global"; return this.reportUncaughtException({message, url, lineNumber, columnNumber}); } reportUncaughtException({message, url, lineNumber, columnNumber, stack, code}) { let result; let sanitizedURL = TestHarness.sanitizeURL(url); let sanitizedStack = this.sanitizeStack(stack); if (url || lineNumber || columnNumber) result = `Uncaught exception in Inspector page: ${message} [${sanitizedURL}:${lineNumber}:${columnNumber}]\n`; else result = `Uncaught exception in Inspector page: ${message}\n`; if (stack) result += `\nStack Trace:\n${sanitizedStack}\n`; if (code) result += `\nEvaluated Code:\n${code}`; // If the connection to the test page is not set up, then just dump to console and give up. // Errors encountered this early can be debugged by loading Test.html in a normal browser page. if (this._originalConsole && !this._testPageHasLoaded) this._originalConsole["error"](result); this.addResult(result); this.completeTest(); // Stop default handler so we can empty InspectorBackend's message queue. return true; } // Private _resendResults() { console.assert(this._testPageHasLoaded); if (this.dumpActivityToSystemConsole) InspectorFrontendHost.unbufferedLog("_resendResults()"); this.evaluateInPage("TestPage.clearOutput()"); for (let result of this._results) this.evaluateInPage(`TestPage.addResult(unescape("${escape(result)}"))`); } }; FrontendTestHarness.Event = { TestPageDidLoad: "frontend-test-test-page-did-load" }; /* 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/Test.js */ /* * Copyright (C) 2013-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 THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT * HOLDER 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.loaded = function() { // Register observers for events from the InspectorBackend. // The initialization order should match the same in Main.js. InspectorBackend.registerAnimationDispatcher(WI.AnimationObserver); InspectorBackend.registerApplicationCacheDispatcher(WI.ApplicationCacheObserver); InspectorBackend.registerBrowserDispatcher(WI.BrowserObserver); InspectorBackend.registerCPUProfilerDispatcher(WI.CPUProfilerObserver); InspectorBackend.registerCSSDispatcher(WI.CSSObserver); InspectorBackend.registerCanvasDispatcher(WI.CanvasObserver); InspectorBackend.registerConsoleDispatcher(WI.ConsoleObserver); InspectorBackend.registerDOMDispatcher(WI.DOMObserver); InspectorBackend.registerDOMStorageDispatcher(WI.DOMStorageObserver); InspectorBackend.registerDatabaseDispatcher(WI.DatabaseObserver); InspectorBackend.registerDebuggerDispatcher(WI.DebuggerObserver); InspectorBackend.registerHeapDispatcher(WI.HeapObserver); InspectorBackend.registerInspectorDispatcher(WI.InspectorObserver); InspectorBackend.registerLayerTreeDispatcher(WI.LayerTreeObserver); InspectorBackend.registerMemoryDispatcher(WI.MemoryObserver); InspectorBackend.registerNetworkDispatcher(WI.NetworkObserver); InspectorBackend.registerPageDispatcher(WI.PageObserver); InspectorBackend.registerRuntimeDispatcher(WI.RuntimeObserver); InspectorBackend.registerScriptProfilerDispatcher(WI.ScriptProfilerObserver); InspectorBackend.registerTargetDispatcher(WI.TargetObserver); InspectorBackend.registerTimelineDispatcher(WI.TimelineObserver); InspectorBackend.registerWorkerDispatcher(WI.WorkerObserver); // Instantiate controllers used by tests. WI.managers = [ WI.browserManager = new WI.BrowserManager, WI.targetManager = new WI.TargetManager, WI.networkManager = new WI.NetworkManager, WI.domStorageManager = new WI.DOMStorageManager, WI.databaseManager = new WI.DatabaseManager, WI.indexedDBManager = new WI.IndexedDBManager, WI.domManager = new WI.DOMManager, WI.cssManager = new WI.CSSManager, WI.consoleManager = new WI.ConsoleManager, WI.runtimeManager = new WI.RuntimeManager, WI.heapManager = new WI.HeapManager, WI.memoryManager = new WI.MemoryManager, WI.applicationCacheManager = new WI.ApplicationCacheManager, WI.timelineManager = new WI.TimelineManager, WI.auditManager = new WI.AuditManager, WI.debuggerManager = new WI.DebuggerManager, WI.layerTreeManager = new WI.LayerTreeManager, WI.workerManager = new WI.WorkerManager, WI.domDebuggerManager = new WI.DOMDebuggerManager, WI.canvasManager = new WI.CanvasManager, WI.animationManager = new WI.AnimationManager, ]; // Register for events. document.addEventListener("DOMContentLoaded", WI.contentLoaded); WI.browserManager.enable(); // Targets. WI.backendTarget = null; WI._backendTargetAvailablePromise = new WI.WrappedPromise; WI.pageTarget = null; WI._pageTargetAvailablePromise = new WI.WrappedPromise; if (InspectorBackend.hasDomain("Target")) WI.targetManager.createMultiplexingBackendTarget(); else WI.targetManager.createDirectBackendTarget(); }; WI.contentLoaded = function() { // Things that would normally get called by the UI, that we still want to do in tests. WI.animationManager.enable(); WI.applicationCacheManager.enable(); WI.canvasManager.enable(); WI.databaseManager.enable(); WI.domStorageManager.enable(); WI.heapManager.enable(); WI.indexedDBManager.enable(); WI.memoryManager.enable(); WI.timelineManager.enable(); // Signal that the frontend is now ready to receive messages. WI._backendTargetAvailablePromise.promise.then(() => { InspectorFrontendAPI.loadCompleted(); }); // Tell the InspectorFrontendHost we loaded, which causes the window to display // and pending InspectorFrontendAPI commands to be sent. InspectorFrontendHost.loaded(); }; WI.performOneTimeFrontendInitializationsUsingTarget = function(target) { if (!WI.__didPerformConsoleInitialization && target.hasDomain("Console")) { WI.__didPerformConsoleInitialization = true; WI.consoleManager.initializeLogChannels(target); } // FIXME: This slows down test debug logging considerably. if (!WI.__didPerformCSSInitialization && target.hasDomain("CSS")) { WI.__didPerformCSSInitialization = true; WI.cssManager.initializeCSSPropertyNameCompletions(target); } }; WI.initializeTarget = function(target) { }; WI.targetsAvailable = function() { return WI._pageTargetAvailablePromise.settled; }; WI.whenTargetsAvailable = function() { return WI._pageTargetAvailablePromise.promise; }; Object.defineProperty(WI, "mainTarget", { get() { return WI.pageTarget || WI.backendTarget; } }); Object.defineProperty(WI, "targets", { get() { return WI.targetManager.targets; } }); WI.assumingMainTarget = () => WI.mainTarget; WI.isDebugUIEnabled = () => false; WI.isEngineeringBuild = false; WI.engineeringSettingsAllowed = () => WI.isEngineeringBuild; WI.unlocalizedString = (string) => string; WI.UIString = (string, key, comment) => string; WI.indentString = () => " "; WI.LayoutDirection = { System: "system", LTR: "ltr", RTL: "rtl", }; WI.resolvedLayoutDirection = () => { return InspectorFrontendHost.userInterfaceLayoutDirection(); }; // Add stubs that are called by the frontend API. WI.updateDockedState = () => {}; WI.updateDockingAvailability = () => {}; WI.updateVisibilityState = () => {}; WI.updateFindString = () => {}; // FIXME: Web Inspector: replace all uses of `window.*Agent` with a target-specific call (function() { function makeAgentGetter(domainName) { Object.defineProperty(window, domainName + "Agent", { get() { return WI.mainTarget._agents[domainName]; }, }); } makeAgentGetter("Animation"); makeAgentGetter("Audit"); makeAgentGetter("ApplicationCache"); makeAgentGetter("CPUProfiler"); makeAgentGetter("CSS"); makeAgentGetter("Canvas"); makeAgentGetter("Console"); makeAgentGetter("DOM"); makeAgentGetter("DOMDebugger"); makeAgentGetter("DOMStorage"); makeAgentGetter("Database"); makeAgentGetter("Debugger"); makeAgentGetter("Heap"); makeAgentGetter("IndexedDB"); makeAgentGetter("Inspector"); makeAgentGetter("LayerTree"); makeAgentGetter("Memory"); makeAgentGetter("Network"); makeAgentGetter("Page"); makeAgentGetter("Recording"); makeAgentGetter("Runtime"); makeAgentGetter("ScriptProfiler"); makeAgentGetter("ServiceWorker"); makeAgentGetter("Target"); makeAgentGetter("Timeline"); makeAgentGetter("Worker"); })(); window.InspectorTest = new FrontendTestHarness(); InspectorTest.redirectConsoleToTestOutput(); WI.reportInternalError = (e) => { console.error(e); }; window.reportUnhandledRejection = InspectorTest.reportUnhandledRejection.bind(InspectorTest); window.onerror = InspectorTest.reportUncaughtExceptionFromEvent.bind(InspectorTest); /* Controllers/AppControllerBase.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 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.NotImplementedError = class NotImplementedError extends Error { constructor(message = "This method is not implemented.") { super(message); } static subclassMustOverride() { return new WI.NotImplementedError("This method must be overridden by a subclass."); } }; WI.AppControllerBase = class AppControllerBase { constructor() { this._initialized = false; this._extensionController = new WI.WebInspectorExtensionController; } // Public get debuggableType() { throw WI.NotImplementedError.subclassMustOverride(); } get extensionController() { return this._extensionController; } // Since various members of the app controller depend on the global singleton to exist, // some initialization needs to happen after the app controller has been constructed. initialize() { if (this._initialized) throw new Error("App controller is already initialized."); this._initialized = true; // FIXME: eventually all code within WI.loaded should be distributed elsewhere. WI.loaded(); } isWebDebuggable() { return this.debuggableType === WI.DebuggableType.Page || this.debuggableType === WI.DebuggableType.WebPage; } }; /* Test/TestAppController.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 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.TestAppController = class TestAppController extends WI.AppControllerBase { constructor() { super(); this._debuggableType = WI.DebuggableType.fromString(InspectorFrontendHost.debuggableInfo.debuggableType); console.assert(this._debuggableType); } // Public get debuggableType() { return this._debuggableType; } }; /* 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\//, ""); } /* Base/BlobUtilities.js */ /* * Copyright (C) 2019 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.BlobUtilities = class BlobUtilities { static blobForContent(content, base64Encoded, mimeType) { if (base64Encoded) return BlobUtilities.decodeBase64ToBlob(content, mimeType); return BlobUtilities.textToBlob(content, mimeType); } static decodeBase64ToBlob(base64Data, mimeType) { mimeType = mimeType || ""; const sliceSize = 1024; let byteCharacters = atob(base64Data); let bytesLength = byteCharacters.length; let slicesCount = Math.ceil(bytesLength / sliceSize); let byteArrays = new Array(slicesCount); for (let sliceIndex = 0; sliceIndex < slicesCount; ++sliceIndex) { let begin = sliceIndex * sliceSize; let end = Math.min(begin + sliceSize, bytesLength); let bytes = new Array(end - begin); for (let offset = begin, i = 0; offset < end; ++i, ++offset) bytes[i] = byteCharacters[offset].charCodeAt(0); byteArrays[sliceIndex] = new Uint8Array(bytes); } return new Blob(byteArrays, {type: mimeType}); } static textToBlob(text, mimeType) { return new Blob([text], {type: mimeType}); } static blobAsText(blob, callback) { console.assert(blob instanceof Blob); let fileReader = new FileReader; fileReader.addEventListener("loadend", () => { callback(fileReader.result); }); fileReader.readAsText(blob); } }; /* Base/DOMUtilities.js */ /* * Copyright (C) 2011 Google Inc. All rights reserved. * Copyright (C) 2007, 2008, 2013 Apple Inc. All rights reserved. * Copyright (C) 2008 Matt Lilek * Copyright (C) 2009 Joseph Pecoraro * * 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. * 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * 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. */ WI.roleSelectorForNode = function(node) { // This is proposed syntax for CSS 4 computed role selector :role(foo) and subject to change. // See http://lists.w3.org/Archives/Public/www-style/2013Jul/0104.html var title = ""; var role = node.computedRole(); if (role) title = ":role(" + role + ")"; return title; }; WI.linkifyAccessibilityNodeReference = function(node) { if (!node) return null; // Same as linkifyNodeReference except the link text has the classnames removed... // ...for list brevity, and both text and title have roleSelectorForNode appended. var link = WI.linkifyNodeReference(node); var tagIdSelector = link.title; var classSelectorIndex = tagIdSelector.indexOf("."); if (classSelectorIndex > -1) tagIdSelector = tagIdSelector.substring(0, classSelectorIndex); var roleSelector = WI.roleSelectorForNode(node); link.textContent = tagIdSelector + roleSelector; link.title += roleSelector; return link; }; WI.linkifyStyleable = function(styleable) { console.assert(styleable instanceof WI.DOMStyleable, styleable); let displayName = styleable.displayName; let link = document.createElement("span"); link.append(displayName); return WI.linkifyNodeReferenceElement(styleable.node, link, {displayName}); }; WI.linkifyNodeReference = function(node, options = {}) { let displayName = node.displayName; if (!isNaN(options.maxLength)) displayName = displayName.truncate(options.maxLength); let link = document.createElement("span"); link.append(displayName); return WI.linkifyNodeReferenceElement(node, link, {...options, displayName}); }; WI.linkifyNodeReferenceElement = function(node, element, options = {}) { element.setAttribute("role", "link"); element.title = options.displayName || node.displayName; let nodeType = node.nodeType(); if (!options.ignoreClick && (nodeType !== Node.DOCUMENT_NODE || node.parentNode) && nodeType !== Node.TEXT_NODE) element.classList.add("node-link"); WI.bindInteractionsForNodeToElement(node, element, options); return element; }; WI.bindInteractionsForNodeToElement = function(node, element, options = {}) { if (!options.ignoreClick) { element.addEventListener("click", (event) => { WI.domManager.inspectElement(node.id, { initiatorHint: WI.TabBrowser.TabNavigationInitiator.LinkClick, }); }); } element.addEventListener("mouseover", (event) => { node.highlight(); }); element.addEventListener("mouseout", (event) => { WI.domManager.hideDOMNodeHighlight(); }); element.addEventListener("contextmenu", (event) => { let contextMenu = WI.ContextMenu.createFromEvent(event); WI.appendContextMenuItemsForDOMNode(contextMenu, node, options); }); }; function createSVGElement(tagName) { return document.createElementNS("http://www.w3.org/2000/svg", tagName); } WI.cssPath = function(node, options = {}) { console.assert(node instanceof WI.DOMNode, "Expected a DOMNode."); if (node.nodeType() !== Node.ELEMENT_NODE) return ""; let suffix = ""; if (node.isPseudoElement()) { suffix = "::" + node.pseudoType(); node = node.parentNode; } let components = []; while (node) { let component = WI.cssPathComponent(node, options); if (!component) break; components.push(component); if (component.done) break; node = node.parentNode; } components.reverse(); return components.map((x) => x.value).join(" > ") + suffix; }; WI.cssPathComponent = function(node, options = {}) { console.assert(node instanceof WI.DOMNode, "Expected a DOMNode."); console.assert(!node.isPseudoElement()); if (node.nodeType() !== Node.ELEMENT_NODE) return null; let nodeName = node.nodeNameInCorrectCase(); // Root node does not have siblings. if (!node.parentNode || node.parentNode.nodeType() === Node.DOCUMENT_NODE) return {value: nodeName, done: true}; if (options.full) { function getUniqueAttributes(domNode) { let uniqueAttributes = new Map; for (let attribute of domNode.attributes()) { let values = [attribute.value]; if (attribute.name === "id" || attribute.name === "class") values = attribute.value.split(/\s+/); uniqueAttributes.set(attribute.name, new Set(values)); } return uniqueAttributes; } let nodeIndex = 0; let needsNthChild = false; let uniqueAttributes = getUniqueAttributes(node); node.parentNode.children.forEach((child, i) => { if (child.nodeType() !== Node.ELEMENT_NODE) return; if (child === node) { nodeIndex = i; return; } if (needsNthChild || child.nodeNameInCorrectCase() !== nodeName) return; let childUniqueAttributes = getUniqueAttributes(child); let subsetCount = 0; for (let [name, values] of uniqueAttributes) { let childValues = childUniqueAttributes.get(name); if (childValues && values.size <= childValues.size && values.isSubsetOf(childValues)) ++subsetCount; } if (subsetCount === uniqueAttributes.size) needsNthChild = true; }); function selectorForAttribute(values, prefix = "", shouldCSSEscape = false) { if (!values || !values.size) return ""; values = Array.from(values); values = values.filter((value) => value && value.length); if (!values.length) return ""; values = values.map((value) => shouldCSSEscape ? CSS.escape(value) : value.escapeCharacters("\"")); return prefix + values.join(prefix); } let selector = nodeName; selector += selectorForAttribute(uniqueAttributes.get("id"), "#", true); selector += selectorForAttribute(uniqueAttributes.get("class"), ".", true); for (let [attribute, values] of uniqueAttributes) { if (attribute !== "id" && attribute !== "class") selector += `[${attribute}="${selectorForAttribute(values)}"]`; } if (needsNthChild) selector += `:nth-child(${nodeIndex + 1})`; return {value: selector, done: false}; } let lowerNodeName = node.nodeName().toLowerCase(); // html, head, and body are unique nodes. if (lowerNodeName === "body" || lowerNodeName === "head" || lowerNodeName === "html") return {value: nodeName, done: true}; // #id is unique. let id = node.getAttribute("id"); if (id) return {value: node.escapedIdSelector, done: true}; // Find uniqueness among siblings. // - look for a unique className // - look for a unique tagName // - fallback to nth-child() function classNames(node) { let classAttribute = node.getAttribute("class"); return classAttribute ? classAttribute.trim().split(/\s+/) : []; } let nthChildIndex = -1; let hasUniqueTagName = true; let uniqueClasses = new Set(classNames(node)); let siblings = node.parentNode.children; let elementIndex = 0; for (let sibling of siblings) { if (sibling.nodeType() !== Node.ELEMENT_NODE) continue; elementIndex++; if (sibling === node) { nthChildIndex = elementIndex; continue; } if (sibling.nodeNameInCorrectCase() === nodeName) hasUniqueTagName = false; if (uniqueClasses.size) { let siblingClassNames = classNames(sibling); for (let className of siblingClassNames) uniqueClasses.delete(className); } } let selector = nodeName; if (lowerNodeName === "input" && node.getAttribute("type") && !uniqueClasses.size) selector += `[type="${node.getAttribute("type")}"]`; if (!hasUniqueTagName) { if (uniqueClasses.size) selector += node.escapedClassSelector; else selector += `:nth-child(${nthChildIndex})`; } return {value: selector, done: false}; }; WI.xpath = function(node) { console.assert(node instanceof WI.DOMNode, "Expected a DOMNode."); if (node.nodeType() === Node.DOCUMENT_NODE) return "/"; let components = []; while (node) { let component = WI.xpathComponent(node); if (!component) break; components.push(component); if (component.done) break; node = node.parentNode; } components.reverse(); let prefix = components.length && components[0].done ? "" : "/"; return prefix + components.map((x) => x.value).join("/"); }; WI.xpathComponent = function(node) { console.assert(node instanceof WI.DOMNode, "Expected a DOMNode."); let index = WI.xpathIndex(node); if (index === -1) return null; let value; switch (node.nodeType()) { case Node.DOCUMENT_NODE: return {value: "", done: true}; case Node.ELEMENT_NODE: var id = node.getAttribute("id"); if (id) return {value: `//*[@id="${id}"]`, done: true}; value = node.localName(); break; case Node.ATTRIBUTE_NODE: value = `@${node.nodeName()}`; break; case Node.TEXT_NODE: case Node.CDATA_SECTION_NODE: value = "text()"; break; case Node.COMMENT_NODE: value = "comment()"; break; case Node.PROCESSING_INSTRUCTION_NODE: value = "processing-instruction()"; break; default: value = ""; break; } if (index > 0) value += `[${index}]`; return {value, done: false}; }; WI.xpathIndex = function(node) { // Root node. if (!node.parentNode) return 0; // No siblings. let siblings = node.parentNode.children; if (siblings.length <= 1) return 0; // Find uniqueness among siblings. // - look for a unique localName // - fallback to index function isSimiliarNode(a, b) { if (a === b) return true; let aType = a.nodeType(); let bType = b.nodeType(); if (aType === Node.ELEMENT_NODE && bType === Node.ELEMENT_NODE) return a.localName() === b.localName(); // XPath CDATA and text() are the same. if (aType === Node.CDATA_SECTION_NODE) return aType === Node.TEXT_NODE; if (bType === Node.CDATA_SECTION_NODE) return bType === Node.TEXT_NODE; return aType === bType; } let unique = true; let xPathIndex = -1; let xPathIndexCounter = 1; // XPath indices start at 1. for (let sibling of siblings) { if (!isSimiliarNode(node, sibling)) continue; if (node === sibling) { xPathIndex = xPathIndexCounter; if (!unique) return xPathIndex; } else { unique = false; if (xPathIndex !== -1) return xPathIndex; } xPathIndexCounter++; } if (unique) return 0; console.assert(xPathIndex > 0, "Should have found the node."); return xPathIndex; }; /* Base/FileUtilities.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 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.FileUtilities = class FileUtilities { static screenshotString() { let date = new Date; let values = [ date.getFullYear(), Number.zeroPad(date.getMonth() + 1, 2), Number.zeroPad(date.getDate(), 2), Number.zeroPad(date.getHours(), 2), Number.zeroPad(date.getMinutes(), 2), Number.zeroPad(date.getSeconds(), 2), ]; return WI.UIString("Screen Shot %s-%s-%s at %s.%s.%s").format(...values); } static sanitizeFilename(filename) { return filename.replace(/:+/g, "-"); } static inspectorURLForFilename(filename) { return "web-inspector:///" + encodeURIComponent(FileUtilities.sanitizeFilename(filename)); } static longestCommonPrefix(files, {directory} = {}) { let longestCommonPrefix = files[0].getPath(); for (let i = 1; i < files.length; ++i) { let path = files[i].getPath(); for (let j = 0; j < longestCommonPrefix.length; ++j) { if (longestCommonPrefix[j] !== path[j]) { longestCommonPrefix = longestCommonPrefix.substring(0, j); break; } } } if ((directory || files.length > 1) && !longestCommonPrefix.endsWith("/")) { let lastSlashIndex = longestCommonPrefix.lastIndexOf("/"); console.assert(lastSlashIndex); if (lastSlashIndex) longestCommonPrefix = longestCommonPrefix.substring(0, lastSlashIndex); longestCommonPrefix += "/"; } return longestCommonPrefix; } static canSave(saveMode) { console.assert(Object.values(WI.FileUtilities.SaveMode).includes(saveMode), saveMode); return InspectorFrontendHost.canSave(saveMode); } static async save(saveMode, fileVariants, forceSaveAs) { console.assert(WI.FileUtilities.canSave(saveMode), saveMode); console.assert(fileVariants); if (!fileVariants) { InspectorFrontendHost.beep(); return; } let isFileVariantsMode = saveMode === WI.FileUtilities.SaveMode.FileVariants; if (isFileVariantsMode) forceSaveAs = true; if (typeof fileVariants.customSaveHandler === "function") { fileVariants.customSaveHandler(forceSaveAs); return; } if (!isFileVariantsMode && !Array.isArray(fileVariants)) fileVariants = [fileVariants]; console.assert(Array.isArray(fileVariants), fileVariants); if (!Array.isArray(fileVariants)) { InspectorFrontendHost.beep(); return; } let promises = fileVariants.map((fileVariant) => { let content = fileVariant.content; console.assert(content, fileVariant); if (!content) return null; let displayType = fileVariant.displayType || ""; console.assert(!isFileVariantsMode || fileVariant.displayType, fileVariant); if (!fileVariant.displayType && isFileVariantsMode) return null; let suggestedName = fileVariant.suggestedName; if (!suggestedName) { let url = fileVariant.url || ""; suggestedName = parseURL(url).lastPathComponent; if (!suggestedName) { suggestedName = WI.UIString("Untitled"); let dataURLTypeMatch = /^data:([^;]+)/.exec(url); if (dataURLTypeMatch) { let fileExtension = WI.fileExtensionForMIMEType(dataURLTypeMatch[1]); if (fileExtension) suggestedName += "." + fileExtension; } } } let url = WI.FileUtilities.inspectorURLForFilename(suggestedName); if (typeof content === "string") { return Promise.resolve({ displayType, url, content, base64Encoded: !!fileVariant.base64Encoded, }); } let wrappedPromise = new WI.WrappedPromise; let fileReader = new FileReader; fileReader.addEventListener("loadend", () => { wrappedPromise.resolve({ displayType, url, content: parseDataURL(fileReader.result).data, base64Encoded: true, }); }); fileReader.readAsDataURL(content); return wrappedPromise.promise; }); if (promises.includes(null)) { InspectorFrontendHost.beep(); return; } let saveDatas = await Promise.all(promises); console.assert(isFileVariantsMode || saveDatas.length === 1, saveDatas); console.assert(!isFileVariantsMode || new Set(saveDatas.map((saveData) => saveData.displayType)).size === saveDatas.length, saveDatas); console.assert(!isFileVariantsMode || new Set(saveDatas.map((saveData) => WI.urlWithoutExtension(saveData.url))).size === 1, saveDatas); InspectorFrontendHost.save(saveDatas, !!forceSaveAs); } static import(callback, {multiple, directory} = {}) { let inputElement = document.createElement("input"); inputElement.type = "file"; inputElement.value = null; inputElement.multiple = !!multiple || !!directory; inputElement.webkitdirectory = !!directory; inputElement.addEventListener("change", (event) => { callback(inputElement.files); }); inputElement.click(); // Cache the last used import element so that it doesn't get GCd while the native file // picker is shown, which would prevent the "change" event listener from firing. FileUtilities.importInputElement = inputElement; } static importText(callback, options = {}) { FileUtilities.import((files) => { FileUtilities.readText(files, callback); }, options); } static importJSON(callback, options = {}) { FileUtilities.import((files) => { FileUtilities.readJSON(files, callback); }, options); } static importData(callback, options = {}) { FileUtilities.import((files) => { FileUtilities.readData(files, callback); }, options); } static async readText(fileOrList, callback) { await FileUtilities._read(fileOrList, async (file, result) => { await new Promise((resolve, reject) => { let reader = new FileReader; reader.addEventListener("loadend", (event) => { result.text = reader.result; resolve(event); }); reader.addEventListener("error", reject); reader.readAsText(file); }); }, callback); } static async readJSON(fileOrList, callback) { await WI.FileUtilities.readText(fileOrList, async (result) => { if (result.text && !result.error) { try { result.json = JSON.parse(result.text); } catch (e) { result.error = e; } } await callback(result); }); } static async readData(fileOrList, callback) { await FileUtilities._read(fileOrList, async (file, result) => { await new Promise((resolve, reject) => { let reader = new FileReader; reader.addEventListener("loadend", (event) => { let {mimeType, base64, data} = parseDataURL(reader.result); // In case no mime type was determined, try to derive one from the file extension. if (!mimeType || mimeType === "text/plain") { let extension = WI.fileExtensionForFilename(result.filename); if (extension) mimeType = WI.mimeTypeForFileExtension(extension); } result.mimeType = mimeType; result.base64Encoded = base64; result.content = data; resolve(event); }); reader.addEventListener("error", reject); reader.readAsDataURL(file); }); }, callback); } // Private static async _read(fileOrList, operation, callback) { console.assert(fileOrList instanceof File || fileOrList instanceof FileList); let files = []; if (fileOrList instanceof File) files.push(fileOrList); else if (fileOrList instanceof FileList) files = Array.from(fileOrList); for (let file of files) { let result = { filename: file.name, }; try { await operation(file, result); } catch (e) { result.error = e; } await callback(result); } } }; // Keep in sync with `InspectorFrontendClient::SaveMode` and `InspectorFrontendHost::SaveMode`. WI.FileUtilities.SaveMode = { SingleFile: "single-file", FileVariants: "file-variants", }; /* Base/HTTPUtilities.js */ /* * Copyright (C) 2019 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.HTTPUtilities = class HTTPUtilities { static statusTextForStatusCode(code) { console.assert(typeof code === "number"); switch (code) { case 0: return "OK"; case 100: return "Continue"; case 101: return "Switching Protocols"; case 200: return "OK"; case 201: return "Created"; case 202: return "Accepted"; case 203: return "Non-Authoritative Information"; case 204: return "No Content"; case 205: return "Reset Content"; case 206: return "Partial Content"; case 207: return "Multi-Status"; case 300: return "Multiple Choices"; case 301: return "Moved Permanently"; case 302: return "Found"; case 303: return "See Other"; case 304: return "Not Modified"; case 305: return "Use Proxy"; case 307: return "Temporary Redirect"; case 308: return "Permanent Redirect"; case 400: return "Bad Request"; case 401: return "Unauthorized"; case 402: return "Payment Required"; case 403: return "Forbidden"; case 404: return "Not Found"; case 405: return "Method Not Allowed"; case 406: return "Not Acceptable"; case 407: return "Proxy Authentication Required"; case 408: return "Request Time-out"; case 409: return "Conflict"; case 410: return "Gone"; case 411: return "Length Required"; case 412: return "Precondition Failed"; case 413: return "Request Entity Too Large"; case 414: return "Request-URI Too Large"; case 415: return "Unsupported Media Type"; case 416: return "Requested range not satisfiable"; case 417: return "Expectation Failed"; case 500: return "Internal Server Error"; case 501: return "Not Implemented"; case 502: return "Bad Gateway"; case 503: return "Service Unavailable"; case 504: return "Gateway Time-out"; case 505: return "HTTP Version not supported"; } if (code < 200) return "Continue"; if (code < 300) return "OK"; if (code < 400) return "Multiple Choices"; if (code < 500) return "Bad Request"; return "Internal Server Error"; } }; WI.HTTPUtilities.RequestMethod = { CONNECT: "CONNECT", DELETE: "DELETE", GET: "GET", HEAD: "HEAD", OPTIONS: "OPTIONS", PATCH: "PATCH", POST: "POST", PUT: "PUT", TRACE: "TRACE", }; WI.HTTPUtilities.RequestMethodsWithBody = new Set([ WI.HTTPUtilities.RequestMethod.DELETE, WI.HTTPUtilities.RequestMethod.PATCH, WI.HTTPUtilities.RequestMethod.POST, WI.HTTPUtilities.RequestMethod.PUT, ]); /* Base/ImageUtilities.js */ /* * Copyright (C) 2013, 2015 Apple Inc. All rights reserved. * Copyright (C) 2017 Devin Rousso . 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.ImageUtilities = class ImageUtilities { static useSVGSymbol(url, className, title) { const svgNamespace = "http://www.w3.org/2000/svg"; const xlinkNamespace = "http://www.w3.org/1999/xlink"; let svgElement = document.createElementNS(svgNamespace, "svg"); svgElement.style.width = "100%"; svgElement.style.height = "100%"; // URL must contain a fragment reference to a graphical element, like a symbol. If none is given // append #root which all of our SVGs have on the top level element. if (!url.includes("#")) url += "#root"; let useElement = document.createElementNS(svgNamespace, "use"); useElement.setAttributeNS(xlinkNamespace, "xlink:href", url); svgElement.appendChild(useElement); let wrapper = document.createElement("div"); wrapper.appendChild(svgElement); if (className) wrapper.className = className; if (title) wrapper.title = title; return wrapper; } static promisifyLoad(src) { return new Promise((resolve, reject) => { let image = new Image; let resolveWithImage = () => { resolve(image); }; image.addEventListener("load", resolveWithImage); image.addEventListener("error", resolveWithImage); image.src = src; }); } static scratchCanvasContext2D(callback) { if (!WI.ImageUtilities._scratchContext2D) WI.ImageUtilities._scratchContext2D = document.createElement("canvas").getContext("2d"); let context = WI.ImageUtilities._scratchContext2D; context.clearRect(0, 0, context.canvas.width, context.canvas.height); context.save(); callback(context); context.restore(); } static imageFromImageBitmap(data) { console.assert(data instanceof ImageBitmap); let image = null; WI.ImageUtilities.scratchCanvasContext2D((context) => { context.canvas.width = data.width; context.canvas.height = data.height; context.drawImage(data, 0, 0); image = new Image; image.src = context.canvas.toDataURL(); }); return image; } static imageFromImageData(data) { console.assert(data instanceof ImageData); let image = null; WI.ImageUtilities.scratchCanvasContext2D((context) => { context.canvas.width = data.width; context.canvas.height = data.height; context.putImageData(data, 0, 0); image = new Image; image.src = context.canvas.toDataURL(); }); return image; } static imageFromCanvasGradient(gradient, width, height) { console.assert(gradient instanceof CanvasGradient); let image = null; WI.ImageUtilities.scratchCanvasContext2D((context) => { context.canvas.width = width; context.canvas.height = height; context.fillStyle = gradient; context.fillRect(0, 0, width, height); image = new Image; image.src = context.canvas.toDataURL(); }); return image; } }; WI.ImageUtilities._scratchContext2D = null; /* Base/MIMETypeUtilities.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. */ WI.fileExtensionForFilename = function(filename) { if (!filename) return null; let index = filename.lastIndexOf("."); if (index === -1) return null; if (index === filename.length - 1) return null; return filename.substr(index + 1); }; WI.fileExtensionForURL = function(url) { let lastPathComponent = parseURL(url).lastPathComponent; return WI.fileExtensionForFilename(lastPathComponent); }; WI.mimeTypeForFileExtension = function(extension) { const extensionToMIMEType = { // Document types. "html": "text/html", "xhtml": "application/xhtml+xml", "xml": "text/xml", // Script types. "js": "text/javascript", "mjs": "text/javascript", "json": "application/json", "clj": "text/x-clojure", "coffee": "text/x-coffeescript", "ls": "text/x-livescript", "ts": "text/typescript", "ps": "application/postscript", "jsx": "text/jsx", // Stylesheet types. "css": "text/css", "less": "text/x-less", "sass": "text/x-sass", "scss": "text/x-scss", // Image types. "avif": "image/avif", "bmp": "image/bmp", "gif": "image/gif", "ico": "image/x-icon", "jp2": "image/jp2", "jpeg": "image/jpeg", "jpg": "image/jpeg", "jxl": "image/jxl", "pdf": "application/pdf", "png": "image/png", "tif": "image/tiff", "tiff": "image/tiff", "webp": "image/webp", "xbm": "image/x-xbitmap", "ogx": "application/ogg", "ogg": "audio/ogg", "oga": "audio/ogg", "ogv": "video/ogg", // Annodex "anx": "application/annodex", "axa": "audio/annodex", "axv": "video/annodex", "spx": "audio/speex", // WebM "webm": "video/webm", // MPEG "m1a": "audio/mpeg", "m2a": "audio/mpeg", "mpg": "video/mpeg", "m15": "video/mpeg", "m1s": "video/mpeg", "m1v": "video/mpeg", "m75": "video/mpeg", "mpa": "video/mpeg", "mpeg": "video/mpeg", "mpm": "video/mpeg", "mpv": "video/mpeg", // MPEG playlist "m3u8": "application/x-mpegurl", "m3url": "audio/x-mpegurl", "m3u": "audio/x-mpegurl", // MPEG-4 "m4v": "video/x-m4v", "m4a": "audio/x-m4a", "m4b": "audio/x-m4b", "m4p": "audio/x-m4p", // MP3 "mp3": "audio/mp3", // MPEG-2 "mp2": "video/x-mpeg2", "vob": "video/mpeg2", "mod": "video/mpeg2", "m2ts": "video/m2ts", "m2t": "video/x-m2ts", // 3GP/3GP2 "3gpp": "audio/3gpp", "3g2": "audio/3gpp2", "amc": "application/x-mpeg", // AAC "aac": "audio/aac", "adts": "audio/aac", "m4r": "audio/x-aac", // CoreAudio File "caf": "audio/x-caf", "gsm": "audio/x-gsm", // ADPCM "wav": "audio/x-wav", // Text Track "vtt": "text/vtt", // Font "woff": "font/woff", "woff2": "font/woff2", "otf": "font/otf", "ttf": "font/ttf", "sfnt": "font/sfnt", // Miscellaneous types. "svg": "image/svg+xml", "txt": "text/plain", "xsl": "text/xsl" }; return extensionToMIMEType[extension] || null; }; WI.fileExtensionForMIMEType = function(mimeType) { if (!mimeType) return null; const mimeTypeToExtension = { // Document types. "text/html": "html", "application/xhtml+xml": "xhtml", "application/xml": "xml", "text/xml": "xml", // Script types. "application/ecmascript": "js", "application/javascript": "js", "application/x-ecmascript": "js", "application/x-javascript": "js", "text/ecmascript": "js", "text/javascript": "js", "text/javascript1.0": "js", "text/javascript1.1": "js", "text/javascript1.2": "js", "text/javascript1.3": "js", "text/javascript1.4": "js", "text/javascript1.5": "js", "text/jscript": "js", "text/x-ecmascript": "js", "text/x-javascript": "js", "application/json": "json", "text/x-clojure": "clj", "text/x-coffeescript": "coffee", "text/livescript": "ls", "text/x-livescript": "ls", "text/typescript": "ts", "application/postscript": "ps", "text/jsx": "jsx", // Stylesheet types. "text/css": "css", "text/x-less": "less", "text/x-sass": "sass", "text/x-scss": "scss", // Image types. "image/avif": "avif", "image/bmp": "bmp", "image/gif": "gif", "image/vnd.microsoft.icon": "ico", "image/x-icon": "ico", "image/jp2": "jp2", "image/jpeg": "jpg", "image/jxl": "jxl", "application/pdf": "pdf", "text/pdf": "pdf", "image/png": "png", "image/tiff": "tiff", "image/webp": "webp", "image/x-xbitmap": "xbm", // Ogg "application/ogg": "ogx", "audio/ogg": "ogg", // Annodex "application/annodex": "anx", "audio/annodex": "axa", "video/annodex": "axv", "audio/speex": "spx", // WebM "video/webm": "webm", "audio/webm": "webm", // MPEG "video/mpeg": "mpeg", // MPEG playlist "application/vnd.apple.mpegurl": "m3u8", "application/mpegurl": "m3u8", "application/x-mpegurl": "m3u8", "audio/mpegurl": "m3u", "audio/x-mpegurl": "m3u", // MPEG-4 "video/x-m4v": "m4v", "audio/x-m4a": "m4a", "audio/x-m4b": "m4b", "audio/x-m4p": "m4p", "audio/mp4": "m4a", // MP3 "audio/mp3": "mp3", "audio/x-mp3": "mp3", "audio/x-mpeg": "mp3", // MPEG-2 "video/x-mpeg2": "mp2", "video/mpeg2": "vob", "video/m2ts": "m2ts", "video/x-m2ts": "m2t", // 3GP/3GP2 "audio/3gpp": "3gpp", "audio/3gpp2": "3g2", "application/x-mpeg": "amc", // AAC "audio/aac": "aac", "audio/x-aac": "m4r", // CoreAudio File "audio/x-caf": "caf", "audio/x-gsm": "gsm", // ADPCM "audio/x-wav": "wav", "audio/vnd.wave": "wav", // Text Track "text/vtt": "vtt", // Font "font/woff": "woff", "font/woff2": "woff2", "font/otf": "otf", "font/ttf": "ttf", "font/sfnt": "sfnt", // Miscellaneous types. "image/svg+xml": "svg", "text/plain": "txt", "text/xsl": "xsl", }; let extension = mimeTypeToExtension[mimeType]; if (extension) return extension; if (mimeType.endsWith("+json")) return "json"; if (mimeType.endsWith("+xml")) return "xml"; return null; }; WI.shouldTreatMIMETypeAsText = function(mimeType) { if (!mimeType) return false; if (mimeType.startsWith("text/")) return true; if (mimeType.endsWith("+json") || mimeType.endsWith("+xml")) return true; let extension = WI.fileExtensionForMIMEType(mimeType); if (extension === "xml") return true; // Various script and JSON mime types. if (extension === "js" || extension === "json") return true; // Various media text mime types. if (extension === "m3u8" || extension === "m3u") return true; if (mimeType.startsWith("application/")) return mimeType.endsWith("script") || mimeType.endsWith("json") || mimeType.endsWith("xml"); return false; }; /* Base/ObjectStore.js */ /* * Copyright (C) 2018 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.ObjectStore = class ObjectStore { constructor(name, options = {}) { this._name = name; this._options = options; } // Static static supported() { return (!window.InspectorTest || WI.ObjectStore.__testObjectStore) && window.indexedDB; } static async reset() { if (WI.ObjectStore._database) WI.ObjectStore._database.close(); await window.indexedDB.deleteDatabase(ObjectStore._databaseName); } static get _databaseName() { let inspectionLevel = InspectorFrontendHost ? InspectorFrontendHost.inspectionLevel : 1; let levelString = (inspectionLevel > 1) ? "-" + inspectionLevel : ""; return "com.apple.WebInspector" + levelString; } static _open(callback) { if (WI.ObjectStore._database) { callback(WI.ObjectStore._database); return; } if (Array.isArray(WI.ObjectStore._databaseCallbacks)) { WI.ObjectStore._databaseCallbacks.push(callback); return; } WI.ObjectStore._databaseCallbacks = [callback]; const version = 8; // Increment this for every edit to `WI.objectStores`. let databaseRequest = window.indexedDB.open(WI.ObjectStore._databaseName, version); databaseRequest.addEventListener("upgradeneeded", (event) => { let database = databaseRequest.result; let objectStores = Object.values(WI.objectStores); if (WI.ObjectStore.__testObjectStore) objectStores.push(WI.ObjectStore.__testObjectStore); let existingNames = new Set; for (let objectStore of objectStores) { if (!database.objectStoreNames.contains(objectStore._name)) database.createObjectStore(objectStore._name, objectStore._options); existingNames.add(objectStore._name); } for (let objectStoreName of database.objectStoreNames) { if (!existingNames.has(objectStoreName)) database.deleteObjectStore(objectStoreName); } }); databaseRequest.addEventListener("success", (successEvent) => { WI.ObjectStore._database = databaseRequest.result; WI.ObjectStore._database.addEventListener("close", (closeEvent) => { WI.ObjectStore._database = null; }); for (let databaseCallback of WI.ObjectStore._databaseCallbacks) databaseCallback(WI.ObjectStore._database); WI.ObjectStore._databaseCallbacks = null; }); } // Public get keyPath() { return (this._options || {}).keyPath; } associateObject(object, key, value) { if (typeof value === "object") value = this._resolveKeyPath(value, key).value; let resolved = this._resolveKeyPath(object, key); resolved.object[resolved.key] = value; } async get(...args) { if (!WI.ObjectStore.supported()) return undefined; return this._operation("readonly", (objectStore) => objectStore.get(...args)); } async getAll(...args) { if (!WI.ObjectStore.supported()) return []; return this._operation("readonly", (objectStore) => objectStore.getAll(...args)); } async getAllKeys(...args) { if (!WI.ObjectStore.supported()) return []; return this._operation("readonly", (objectStore) => objectStore.getAllKeys(...args)); } async put(...args) { if (!WI.ObjectStore.supported()) return undefined; return this._operation("readwrite", (objectStore) => objectStore.put(...args)); } async putObject(object, ...args) { if (!WI.ObjectStore.supported()) return undefined; console.assert(typeof object.toJSON === "function", "ObjectStore cannot store an object without JSON serialization", object.constructor.name); let result = await this.put(object.toJSON(WI.ObjectStore.toJSONSymbol), ...args); this.associateObject(object, args[0], result); return result; } async delete(...args) { if (!WI.ObjectStore.supported()) return undefined; return this._operation("readwrite", (objectStore) => objectStore.delete(...args)); } async deleteObject(object, ...args) { if (!WI.ObjectStore.supported()) return undefined; return this.delete(this._resolveKeyPath(object).value, ...args); } async clear(...args) { if (!WI.ObjectStore.supported()) return undefined; return this._operation("readwrite", (objectStore) => objectStore.clear(...args)); } // Private _resolveKeyPath(object, keyPath) { keyPath = keyPath || this._options.keyPath || ""; let parts = keyPath.split("."); let key = parts.splice(-1, 1); while (parts.length) { if (!object.hasOwnProperty(parts[0])) break; object = object[parts.shift()]; } if (parts.length) key = parts.join(".") + "." + key; return { object, key, value: object[key], }; } async _operation(mode, func) { // IndexedDB transactions will auto-close if there are no active operations at the end of a // microtask, so we need to do everything using event listeners instead of promises. return new Promise((resolve, reject) => { WI.ObjectStore._open((database) => { let transaction = database.transaction([this._name], mode); let objectStore = transaction.objectStore(this._name); let request = null; try { request = func(objectStore); } catch (e) { reject(e); return; } function listener(event) { transaction.removeEventListener("complete", listener); transaction.removeEventListener("error", listener); request.removeEventListener("success", listener); request.removeEventListener("error", listener); if (request.error) { reject(request.error); return; } resolve(request.result); } transaction.addEventListener("complete", listener, {once: true}); transaction.addEventListener("error", listener, {once: true}); request.addEventListener("success", listener, {once: true}); request.addEventListener("error", listener, {once: true}); }); }); } }; WI.ObjectStore._database = null; WI.ObjectStore._databaseCallbacks = null; WI.ObjectStore.toJSONSymbol = Symbol("ObjectStore-toJSON"); // Be sure to update the `version` above when making changes. WI.objectStores = { // Version 1 audits: new WI.ObjectStore("audit-manager-tests", {keyPath: "__id", autoIncrement: true}), // Version 2 breakpoints: new WI.ObjectStore("debugger-breakpoints", {keyPath: "__id"}), // Version 3 domBreakpoints: new WI.ObjectStore("dom-debugger-dom-breakpoints", {keyPath: "__id"}), eventBreakpoints: new WI.ObjectStore("dom-debugger-event-breakpoints", {keyPath: "__id"}), urlBreakpoints: new WI.ObjectStore("dom-debugger-url-breakpoints", {keyPath: "__id"}), // Version 4 localResourceOverrides: new WI.ObjectStore("local-resource-overrides", {keyPath: "__id"}), // Version 5 general: new WI.ObjectStore("general"), // Version 6 cssPropertyNameCounts: new WI.ObjectStore("css-property-name-counts"), // Version 7 symbolicBreakpoints: new WI.ObjectStore("debugger-symbolic-breakpoints", {keyPath: "__id"}), // Version 8 consoleSnippets: new WI.ObjectStore("console-snippets", {keyPath: "__id"}), }; /* Base/URLUtilities.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. */ function removeURLFragment(url) { var hashIndex = url.indexOf("#"); if (hashIndex >= 0) return url.substring(0, hashIndex); return url; } function relativePath(path, basePath) { console.assert(path.charAt(0) === "/"); console.assert(basePath.charAt(0) === "/"); var pathComponents = path.split("/"); var baseComponents = basePath.replace(/\/$/, "").split("/"); var finalComponents = []; var index = 1; for (; index < pathComponents.length && index < baseComponents.length; ++index) { if (pathComponents[index] !== baseComponents[index]) break; } for (var i = index; i < baseComponents.length; ++i) finalComponents.push(".."); for (var i = index; i < pathComponents.length; ++i) finalComponents.push(pathComponents[i]); return finalComponents.join("/"); } function parseSecurityOrigin(securityOrigin) { securityOrigin = securityOrigin ? securityOrigin.trim() : ""; let match = securityOrigin.match(/^(?[^:]+):\/\/(?[^\/:]*)(?::(?[\d]+))?$/i); if (!match) return {scheme: null, host: null, port: null}; let scheme = match.groups.scheme.toLowerCase(); let host = match.groups.host.toLowerCase(); let port = Number(match.groups.port) || null; return {scheme, host, port}; } function parseDataURL(url) { if (!url.startsWith("data:")) return null; // data:[][;charset=][;base64], let match = url.match(/^data:(?[^;,]*)?(?:;charset=(?[^;,]*?))?(?;base64)?,(?.*)$/); if (!match) return null; let scheme = "data"; let mimeType = match.groups.mime || "text/plain"; let charset = match.groups.charset || "US-ASCII"; let base64 = !!match.groups.base64; let data = decodeURIComponent(match.groups.data); return {scheme, mimeType, charset, base64, data}; } function parseURL(url) { let result = { scheme: null, userinfo: null, host: null, port: null, origin: null, path: null, queryString: null, fragment: null, lastPathComponent: null, }; // dataURLs should be handled by `parseDataURL`. if (url && url.startsWith("data:")) { result.scheme = "data"; return result; } // Internal sourceURLs will fail in URL constructor anyways. if (isWebKitInternalScript(url)) return result; let parsed = null; try { parsed = new URL(url); } catch { return result; } result.scheme = parsed.protocol.slice(0, -1); // remove trailing ":" if (parsed.username) result.userinfo = parsed.username; if (parsed.password) result.userinfo = (result.userinfo || "") + ":" + parsed.password; if (parsed.hostname) result.host = parsed.hostname; if (parsed.port) result.port = Number(parsed.port); if (parsed.origin && parsed.origin !== "null") result.origin = parsed.origin; else if (result.scheme && result.host) { result.origin = result.scheme + "://" + result.host; if (result.port) result.origin += ":" + result.port; } if (parsed.pathname) result.path = parsed.pathname; if (parsed.search) result.queryString = parsed.search.substring(1); // remove leading "?" if (parsed.hash) result.fragment = parsed.hash.substring(1); // remove leading "#" // Find last path component. if (result.path && result.path !== "/") { // Skip the trailing slash if there is one. let endOffset = result.path.endsWith("/") ? 1 : 0; let lastSlashIndex = result.path.lastIndexOf("/", result.path.length - 1 - endOffset); if (lastSlashIndex !== -1) result.lastPathComponent = result.path.substring(lastSlashIndex + 1, result.path.length - endOffset); } return result; } function absoluteURL(partialURL, baseURL) { partialURL = partialURL ? partialURL.trim() : ""; // Return data and javascript URLs as-is. if (partialURL.startsWith("data:") || partialURL.startsWith("javascript:") || partialURL.startsWith("mailto:")) return partialURL; // If the URL has a scheme it is already a full URL, so return it. if (parseURL(partialURL).scheme) return partialURL; // If there is no partial URL, just return the base URL. if (!partialURL) return baseURL || null; var baseURLComponents = parseURL(baseURL); // The base URL needs to be an absolute URL. Return null if it isn't. if (!baseURLComponents.scheme) return null; // A URL that starts with "//" is a full URL without the scheme. Use the base URL scheme. if (partialURL[0] === "/" && partialURL[1] === "/") return baseURLComponents.scheme + ":" + partialURL; // The path can be null for URLs that have just a scheme and host (like "http://apple.com"). So make the path be "/". if (!baseURLComponents.path) baseURLComponents.path = "/"; // Generate the base URL prefix that is used in the rest of the cases. var baseURLPrefix = baseURLComponents.scheme + "://" + baseURLComponents.host + (baseURLComponents.port ? (":" + baseURLComponents.port) : ""); // A URL that starts with "?" is just a query string that gets applied to the base URL (replacing the base URL query string and fragment). if (partialURL[0] === "?") return baseURLPrefix + baseURLComponents.path + partialURL; // A URL that starts with "/" is an absolute path that gets applied to the base URL (replacing the base URL path, query string and fragment). if (partialURL[0] === "/") return baseURLPrefix + resolveDotsInPath(partialURL); // A URL that starts with "#" is just a fragment that gets applied to the base URL (replacing the base URL fragment, maintaining the query string). if (partialURL[0] === "#") { let queryStringComponent = baseURLComponents.queryString ? "?" + baseURLComponents.queryString : ""; return baseURLPrefix + baseURLComponents.path + queryStringComponent + partialURL; } // Generate the base path that is used in the final case by removing everything after the last "/" from the base URL's path. var basePath = baseURLComponents.path.substring(0, baseURLComponents.path.lastIndexOf("/")) + "/"; return baseURLPrefix + resolveDotsInPath(basePath + partialURL); } function parseQueryString(queryString, arrayResult) { if (!queryString) return arrayResult ? [] : {}; function decode(string) { try { // Replace "+" with " " then decode percent encoded values. return decodeURIComponent(string.replace(/\+/g, " ")); } catch { return string; } } var parameters = arrayResult ? [] : {}; for (let parameterString of queryString.split("&")) { let index = parameterString.indexOf("="); if (index === -1) index = parameterString.length; let name = decode(parameterString.substring(0, index)); let value = decode(parameterString.substring(index + 1)); if (arrayResult) parameters.push({name, value}); else parameters[name] = value; } return parameters; } WI.displayNameForURL = function(url, urlComponents, options = {}) { if (url.startsWith("data:")) return WI.truncateURL(url); if (!urlComponents) urlComponents = parseURL(url); var displayName; try { displayName = decodeURIComponent(urlComponents.lastPathComponent || ""); } catch { displayName = urlComponents.lastPathComponent; } if (options.allowDirectoryAsName && (urlComponents.path === "/" || (displayName && urlComponents.path.endsWith(displayName + "/")))) displayName = "/"; return displayName || WI.displayNameForHost(urlComponents.host) || url; }; WI.truncateURL = function(url, multiline = false, dataURIMaxSize = 6) { if (!url.startsWith("data:")) return url; const dataIndex = url.indexOf(",") + 1; let header = url.slice(0, dataIndex); if (multiline) header += "\n"; const data = url.slice(dataIndex); if (data.length < dataURIMaxSize) return header + data; const firstChunk = data.slice(0, Math.ceil(dataURIMaxSize / 2)); const ellipsis = "\u2026"; const middleChunk = multiline ? `\n${ellipsis}\n` : ellipsis; const lastChunk = data.slice(-Math.floor(dataURIMaxSize / 2)); return header + firstChunk + middleChunk + lastChunk; }; WI.urlWithoutExtension = function(urlString) { let url = null; try { url = new URL(urlString); } catch { } if (!url) return urlString; let firstDotInLastPathComponentIndex = url.pathname.indexOf(".", url.pathname.lastIndexOf("/")); if (firstDotInLastPathComponentIndex !== -1) url.pathname = url.pathname.substring(0, firstDotInLastPathComponentIndex); url.search = ""; url.hash = ""; return url.toString(); }; WI.urlWithoutFragment = function(urlString) { try { let url = new URL(urlString); if (url.hash) { url.hash = ""; return url.toString(); } // URL.toString with an empty hash leaves the hash symbol, so we strip it. let result = url.toString(); if (result.endsWith("#")) return result.substring(0, result.length - 1); return result; } catch { } return urlString; }; WI.urlWithoutUserQueryOrFragment = function(urlString) { try { let url = new URL(urlString); if (url.username) { url.username = ""; } if (url.password) { url.password = ""; } if (url.search) { url.search = ""; } if (url.hash) { url.hash = ""; } let result = url.toString(); if (result.endsWith("#")) return result.substring(0, result.length - 1); return result; } catch { } return urlString; } WI.displayNameForHost = function(host) { let extensionName = WI.browserManager.extensionNameForId(host); if (extensionName) return extensionName; // FIXME : This should decode punycode hostnames. return host; }; // https://tools.ietf.org/html/rfc7540#section-8.1.2.3 WI.h2Authority = function(components) { let {scheme, userinfo, host, port} = components; let result = host || ""; // The authority MUST NOT include the deprecated "userinfo" // subcomponent for "http" or "https" schemed URIs. if (userinfo && (scheme !== "http" && scheme !== "https")) result = userinfo + "@" + result; if (port) result += ":" + port; return result; }; // https://tools.ietf.org/html/rfc7540#section-8.1.2.3 WI.h2Path = function(components) { let {scheme, path, queryString} = components; let result = path || ""; // The ":path" pseudo-header field includes the path and query parts // of the target URI. [...] This pseudo-header field MUST NOT be empty // for "http" or "https" URIs; "http" or "https" URIs that do not contain // a path component MUST include a value of '/'. if (!path && (scheme === "http" || scheme === "https")) result = "/"; if (queryString) result += "?" + queryString; return result; }; /* 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; }; /* Base/Setting.js */ /* * Copyright (C) 2009 Google Inc. All rights reserved. * 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: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 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. * * Neither the name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT * OWNER 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.Setting = class Setting extends WI.Object { constructor(name, defaultValue) { super(); this._name = name; this._defaultValue = defaultValue; } // Static static migrateValue(key) { let localStorageKey = WI.Setting._localStorageKeyPrefix + key; let value = undefined; if (!window.InspectorTest && window.localStorage) { let item = window.localStorage.getItem(localStorageKey); if (item !== null) { try { value = JSON.parse(item); } catch { } window.localStorage.removeItem(localStorageKey); } } return value; } static reset() { let prefix = WI.Setting._localStorageKeyPrefix; let keysToRemove = []; for (let i = 0; i < window.localStorage.length; ++i) { let key = window.localStorage.key(i); if (key.startsWith(prefix)) keysToRemove.push(key); } for (let key of keysToRemove) window.localStorage.removeItem(key); } // Public get name() { return this._name; } get defaultValue() { return this._defaultValue; } get value() { if ("_value" in this) return this._value; // Make a copy of the default value so changes to object values don't modify the default value. this._value = JSON.parse(JSON.stringify(this._defaultValue)); if (!window.InspectorTest && window.localStorage) { let key = WI.Setting._localStorageKeyPrefix + this._name; let item = window.localStorage.getItem(key); if (item !== null) { try { this._value = JSON.parse(item); } catch { window.localStorage.removeItem(key); } } } return this._value; } set value(value) { if (this._value === value) return; this._value = value; this.save(); } save() { if (!window.InspectorTest && window.localStorage) { let key = WI.Setting._localStorageKeyPrefix + this._name; try { if (Object.shallowEqual(this._value, this._defaultValue)) window.localStorage.removeItem(key); else window.localStorage.setItem(key, JSON.stringify(this._value)); } catch { console.error("Error saving setting with name: " + this._name); } } this.dispatchEventToListeners(WI.Setting.Event.Changed, this._value, {name: this._name}); } reset() { // Make a copy of the default value so changes to object values don't modify the default value. this.value = JSON.parse(JSON.stringify(this._defaultValue)); } }; WI.Setting._localStorageKeyPrefix = (function() { let inspectionLevel = InspectorFrontendHost ? InspectorFrontendHost.inspectionLevel : 1; let levelString = inspectionLevel > 1 ? "-" + inspectionLevel : ""; return `com.apple.WebInspector${levelString}.`; })(); WI.Setting.isFirstLaunch = !!window.InspectorTest || (window.localStorage && Object.keys(window.localStorage).every((key) => !key.startsWith(WI.Setting._localStorageKeyPrefix))); WI.Setting.Event = { Changed: "setting-changed" }; WI.EngineeringSetting = class EngineeringSetting extends WI.Setting { get value() { if (WI.engineeringSettingsAllowed()) return super.value; return this.defaultValue; } set value(value) { console.assert(WI.engineeringSettingsAllowed()); if (WI.engineeringSettingsAllowed()) super.value = value; } }; WI.DebugSetting = class DebugSetting extends WI.Setting { get value() { if (WI.isDebugUIEnabled()) return super.value; return this.defaultValue; } set value(value) { console.assert(WI.isDebugUIEnabled()); if (WI.isDebugUIEnabled()) super.value = value; } }; WI.settings = { blackboxBreakpointEvaluations: new WI.Setting("blackbox-breakpoint-evaluations", true), canvasRecordingAutoCaptureEnabled: new WI.Setting("canvas-recording-auto-capture-enabled", false), canvasRecordingAutoCaptureFrameCount: new WI.Setting("canvas-recording-auto-capture-frame-count", 1), consoleAutoExpandTrace: new WI.Setting("console-auto-expand-trace", true), consoleSavedResultAlias: new WI.Setting("console-saved-result-alias", ""), cssChangesPerNode: new WI.Setting("css-changes-per-node", false), clearLogOnNavigate: new WI.Setting("clear-log-on-navigate", true), clearNetworkOnNavigate: new WI.Setting("clear-network-on-navigate", true), cpuTimelineThreadDetailsExpanded: new WI.Setting("cpu-timeline-thread-details-expanded", false), domTreeDeemphasizesNodesThatAreNotRendered: new WI.Setting("dom-tree-deemphasizes-nodes-that-are-not-rendered", true), emulateInUserGesture: new WI.Setting("emulate-in-user-gesture", false), enableControlFlowProfiler: new WI.Setting("enable-control-flow-profiler", false), enableElementsTabIndependentStylesDetailsSidebarPanel: new WI.Setting("elements-tab-independent-styles-details-panel", true), enableLineWrapping: new WI.Setting("enable-line-wrapping", true), enableNarrowLayoutMode: new WI.Setting("enable-narrow-layout-mode", true), flexOverlayShowOrderNumbers: new WI.Setting("flex-overlay-show-order-numbers", false), frontendAppearance: new WI.Setting("frontend-appearance", "system"), gridOverlayShowAreaNames: new WI.Setting("grid-overlay-show-area-names", false), gridOverlayShowExtendedGridLines: new WI.Setting("grid-overlay-show-extended-grid-lines", false), gridOverlayShowLineNames: new WI.Setting("grid-overlay-show-line-names", false), gridOverlayShowLineNumbers: new WI.Setting("grid-overlay-show-line-numbers", true), gridOverlayShowTrackSizes: new WI.Setting("grid-overlay-show-track-sizes", true), groupMediaRequestsByDOMNode: new WI.Setting("group-media-requests-by-dom-node", WI.Setting.migrateValue("group-by-dom-node") || false), indentUnit: new WI.Setting("indent-unit", 4), indentWithTabs: new WI.Setting("indent-with-tabs", false), resourceCachingDisabled: new WI.Setting("disable-resource-caching", false), searchCaseSensitive: new WI.Setting("search-case-sensitive", false), searchFromSelection: new WI.Setting("search-from-selection", false), searchRegularExpression: new WI.Setting("search-regular-expression", false), selectedNetworkDetailContentViewIdentifier: new WI.Setting("network-detail-content-view-identifier", "preview"), sourceMapsEnabled: new WI.Setting("source-maps-enabled", true), showConsoleMessageTimestamps: new WI.Setting("show-console-message-timestamps", false), showCSSPropertySyntaxInDocumentationPopover: new WI.Setting("show-css-property-syntax-in-documentation-popover", false), showCanvasPath: new WI.Setting("show-canvas-path", false), showFlexOverlayDuringElementSelection: new WI.Setting("show-grid-overlay-during-element-selection", true), showGridOverlayDuringElementSelection: new WI.Setting("show-flex-overlay-during-element-selection", true), showImageGrid: new WI.Setting("show-image-grid", true), showInvisibleCharacters: new WI.Setting("show-invisible-characters", !!WI.Setting.migrateValue("show-invalid-characters")), showJavaScriptTypeInformation: new WI.Setting("show-javascript-type-information", false), showRulers: new WI.Setting("show-rulers", false), showRulersDuringElementSelection: new WI.Setting("show-rulers-during-element-selection", true), showScopeChainOnPause: new WI.Setting("show-scope-chain-sidebar", true), showWhitespaceCharacters: new WI.Setting("show-whitespace-characters", false), tabSize: new WI.Setting("tab-size", 4), timelinesAutoStop: new WI.Setting("timelines-auto-stop", true), timelineOverviewGroupBySourceCode: new WI.Setting("timeline-overview-group-by-source-code", true), zoomFactor: new WI.Setting("zoom-factor", 1), // Experimental experimentalEnableStylesJumpToEffective: new WI.Setting("experimental-styles-jump-to-effective", false), experimentalEnableStylesJumpToVariableDeclaration: new WI.Setting("experimental-styles-jump-to-variable-declaration", false), experimentalAllowInspectingInspector: new WI.Setting("experimental-allow-inspecting-inspector", false), experimentalCSSSortPropertyNameAutocompletionByUsage: new WI.Setting("experimental-css-sort-property-name-autocompletion-by-usage", true), experimentalEnableNetworkEmulatedCondition: new WI.Setting("experimental-enable-network-emulated-condition", false), experimentalGroupSourceMapErrors: new WI.Setting("experimental-group-source-map-errors", true), experimentalLimitSourceCodeHighlighting: new WI.Setting("engineering-limit-source-code-highlighting", false), // Protocol protocolLogAsText: new WI.Setting("protocol-log-as-text", false), protocolAutoLogMessages: new WI.Setting("protocol-auto-log-messages", false), protocolAutoLogTimeStats: new WI.Setting("protocol-auto-log-time-stats", false), protocolFilterMultiplexingBackendMessages: new WI.Setting("protocol-filter-multiplexing-backend-messages", true), // Engineering engineeringShowInternalExecutionContexts: new WI.EngineeringSetting("engineering-show-internal-execution-contexts", false), engineeringShowInternalScripts: new WI.EngineeringSetting("engineering-show-internal-scripts", false), engineeringPauseForInternalScripts: new WI.EngineeringSetting("engineering-pause-for-internal-scripts", false), engineeringShowInternalObjectsInHeapSnapshot: new WI.EngineeringSetting("engineering-show-internal-objects-in-heap-snapshot", false), engineeringShowPrivateSymbolsInHeapSnapshot: new WI.EngineeringSetting("engineering-show-private-symbols-in-heap-snapshot", false), engineeringAllowEditingUserAgentShadowTrees: new WI.EngineeringSetting("engineering-allow-editing-user-agent-shadow-trees", false), // Debug debugShowConsoleEvaluations: new WI.DebugSetting("debug-show-console-evaluations", false), debugOutlineFocusedElement: new WI.DebugSetting("debug-outline-focused-element", false), debugEnableLayoutFlashing: new WI.DebugSetting("debug-enable-layout-flashing", false), debugEnableStyleEditingDebugMode: new WI.DebugSetting("debug-enable-style-editing-debug-mode", false), debugEnableUncaughtExceptionReporter: new WI.DebugSetting("debug-enable-uncaught-exception-reporter", true), debugEnableDiagnosticLogging: new WI.DebugSetting("debug-enable-diagnostic-logging", true), debugAutoLogDiagnosticEvents: new WI.DebugSetting("debug-auto-log-diagnostic-events", false), debugLayoutDirection: new WI.DebugSetting("debug-layout-direction-override", "system"), debugShowMockWebExtensionTab: new WI.DebugSetting("debug-show-mock-web-extension-tab", false), }; /* Base/YieldableTask.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.YieldableTask = class YieldableTask { constructor(delegate, items, options = {}) { let {workInterval, idleInterval} = options; console.assert(!workInterval || workInterval > 0, workInterval); console.assert(!idleInterval || idleInterval > 0, idleInterval); console.assert(delegate && typeof delegate.yieldableTaskWillProcessItem === "function", "Delegate provide an implementation of method 'yieldableTaskWillProcessItem'."); console.assert(items instanceof Object && Symbol.iterator in items, "Argument `items` must subclass Object and be iterable.", items); // Milliseconds to run before the task should yield. this._workInterval = workInterval || 10; // Milliseconds to idle before asynchronously resuming the task. this._idleInterval = idleInterval || 0; this._delegate = delegate; this._items = items; this._idleTimeoutIdentifier = undefined; this._processing = false; this._processing = false; this._cancelled = false; } // Public get processing() { return this._processing; } get cancelled() { return this._cancelled; } get idleInterval() { return this._idleInterval; } get workInterval() { return this._workInterval; } start() { console.assert(!this._processing); if (this._processing) return; console.assert(!this._cancelled); if (this._cancelled) return; function* createIteratorForProcessingItems() { let startTime = Date.now(); let processedItems = []; for (let item of this._items) { if (this._cancelled) break; this._delegate.yieldableTaskWillProcessItem(this, item); processedItems.push(item); // Calling out to the delegate may cause the task to be cancelled. if (this._cancelled) break; let elapsedTime = Date.now() - startTime; if (elapsedTime > this._workInterval) { let returnedItems = processedItems.slice(); processedItems = []; this._willYield(returnedItems, elapsedTime); yield; startTime = Date.now(); } } // The task sends a fake yield notification to the delegate so that // the delegate receives notification of all processed items before finishing. if (processedItems.length) this._willYield(processedItems, Date.now() - startTime); } this._processing = true; this._pendingItemsIterator = createIteratorForProcessingItems.call(this); this._processPendingItems(); } cancel() { if (!this._processing) return; this._cancelled = true; } // Private _processPendingItems() { console.assert(this._processing); if (this._cancelled) return; if (!this._pendingItemsIterator.next().done) { this._idleTimeoutIdentifier = setTimeout(() => { this._processPendingItems(); }, this._idleInterval); return; } this._didFinish(); } _willYield(processedItems, elapsedTime) { if (typeof this._delegate.yieldableTaskDidYield === "function") this._delegate.yieldableTaskDidYield(this, processedItems, elapsedTime); } _didFinish() { this._processing = false; this._pendingItemsIterator = null; if (this._idleTimeoutIdentifier) { clearTimeout(this._idleTimeoutIdentifier); this._idleTimeoutIdentifier = undefined; } if (typeof this._delegate.yieldableTaskDidFinish === "function") this._delegate.yieldableTaskDidFinish(this); } }; /* Base/IDLExtensions.js */ /* * Copyright (C) 2022 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. */ // The following should only be used for response local override file mapping. Object.defineProperty(File.prototype, "getPath", { value() { return InspectorFrontendHost.getPath(this); }, }); // The following should only be used for rendering (and interacting with) canvas 2D recordings. Object.defineProperty(CanvasRenderingContext2D.prototype, "currentX", { get() { return InspectorFrontendHost.getCurrentX(this); }, }); Object.defineProperty(CanvasRenderingContext2D.prototype, "currentY", { get() { return InspectorFrontendHost.getCurrentY(this); }, }); Object.defineProperty(CanvasRenderingContext2D.prototype, "getPath", { value() { return InspectorFrontendHost.getPath(this); }, }); Object.defineProperty(CanvasRenderingContext2D.prototype, "setPath", { value(path) { return InspectorFrontendHost.setPath(this, path); }, }); if (window.OffscreenCanvasRenderingContext2D) { Object.defineProperty(OffscreenCanvasRenderingContext2D.prototype, "currentX", { get() { return InspectorFrontendHost.getCurrentX(this); }, }); Object.defineProperty(OffscreenCanvasRenderingContext2D.prototype, "currentY", { get() { return InspectorFrontendHost.getCurrentY(this); }, }); Object.defineProperty(OffscreenCanvasRenderingContext2D.prototype, "getPath", { value() { return InspectorFrontendHost.getPath(this); }, }); Object.defineProperty(OffscreenCanvasRenderingContext2D.prototype, "setPath", { value(path) { return InspectorFrontendHost.setPath(this, path); }, }); } /* Protocol/ProtocolTracer.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.ProtocolTracer = class ProtocolTracer { // Public logStarted() { // To be overridden by subclasses. } logFrontendException(connection, message, exception) { // To be overridden by subclasses. } logFrontendRequest(connection, message) { // To be overridden by subclasses. } logWillHandleResponse(connection, message) { // To be overridden by subclasses. } logDidHandleResponse(connection, message, timings = null) { // To be overridden by subclasses. } logWillHandleEvent(connection, message) { // To be overridden by subclasses. } logDidHandleEvent(connection, message, timings = null) { // To be overridden by subclasses. } logFinished() { // To be overridden by subclasses. } }; /* Protocol/LoggingProtocolTracer.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.LoggingProtocolTracer = class LoggingProtocolTracer extends WI.ProtocolTracer { constructor() { super(); this._dumpMessagesToConsole = false; this._dumpTimingDataToConsole = false; this._filterMultiplexingBackend = true; this._logToConsole = window.InspectorTest ? InspectorFrontendHost.unbufferedLog.bind(InspectorFrontendHost) : console.log; } // Public set dumpMessagesToConsole(value) { this._dumpMessagesToConsole = !!value; } get dumpMessagesToConsole() { return this._dumpMessagesToConsole; } set dumpTimingDataToConsole(value) { this._dumpTimingDataToConsole = !!value; } get dumpTimingDataToConsole() { return this._dumpTimingDataToConsole; } set filterMultiplexingBackend(value) { this._filterMultiplexingBackend = !!value; } get filterMultiplexingBackend() { return this._filterMultiplexingBackend; } logFrontendException(connection, message, exception) { this._processEntry({type: "exception", connection, message, exception}); } logFrontendRequest(connection, message) { this._processEntry({type: "request", connection, message}); } logWillHandleResponse(connection, message) { let entry = {type: "response", connection, message}; this._processEntry(entry); } logDidHandleResponse(connection, message, timings = null) { let entry = {type: "response", connection, message}; if (timings) entry.timings = Object.shallowCopy(timings); this._processEntry(entry); } logWillHandleEvent(connection, message) { let entry = {type: "event", connection, message}; this._processEntry(entry); } logDidHandleEvent(connection, message, timings = null) { let entry = {type: "event", connection, message}; if (timings) entry.timings = Object.shallowCopy(timings); this._processEntry(entry); } _processEntry(entry) { if (this._dumpTimingDataToConsole && entry.timings) { if (entry.timings.rtt && entry.timings.dispatch) this._logToConsole(`time-stats: Handling: ${entry.timings.dispatch || NaN}ms; RTT: ${entry.timings.rtt}ms`); else if (entry.timings.dispatch) this._logToConsole(`time-stats: Handling: ${entry.timings.dispatch || NaN}ms`); } else if (this._dumpMessagesToConsole && !entry.timings) { let connection = entry.connection; let targetId = connection && connection.target ? connection.target.identifier : "unknown"; if (this._filterMultiplexingBackend && targetId === "multi") return; let prefix = `${entry.type} (${targetId})`; if (!window.InspectorTest && InspectorFrontendHost.isBeingInspected() && !WI.settings.protocolLogAsText.value) { if (entry.type === "request" || entry.type === "exception") console.trace(prefix, entry.message); else this._logToConsole(prefix, entry.message); } else this._logToConsole(`${prefix}: ${JSON.stringify(entry.message)}`); if (entry.exception) { this._logToConsole(entry.exception); if (entry.exception.stack) this._logToConsole(entry.exception.stack); } } } }; /* Protocol/InspectorBackend.js */ /* * Copyright (C) 2011 Google Inc. All rights reserved. * Copyright (C) 2013, 2015, 2016 Apple Inc. All rights reserved. * Copyright (C) 2014 University of Washington. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 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. * * Neither the name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT * OWNER 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. */ InspectorBackendClass = class InspectorBackendClass { constructor() { this._registeredDomains = {}; this._activeDomains = {}; this._customTracer = null; this._defaultTracer = new WI.LoggingProtocolTracer; this._activeTracers = [this._defaultTracer]; // FIXME: Web Inspector: release unused backend domains/events/commands once the debuggable type is known this._supportedDomainsForTargetType = new Multimap; this._supportedCommandParameters = new Map; this._supportedEventParameters = new Map; WI.settings.protocolAutoLogMessages.addEventListener(WI.Setting.Event.Changed, this._startOrStopAutomaticTracing, this); WI.settings.protocolAutoLogTimeStats.addEventListener(WI.Setting.Event.Changed, this._startOrStopAutomaticTracing, this); this._startOrStopAutomaticTracing(); this.currentDispatchState = { event: null, request: null, response: null, }; } // Public // This should only be used for getting enum values (`InspectorBackend.Enum.Domain.Type.Item`). // Domain/Command/Event feature checking should use one of the `has*` functions below. get Enum() { // Enums should not be conditionally enabled by debuggable and/or target type. return this._registeredDomains; } // It's still possible to set this flag on InspectorBackend to just // dump protocol traffic as it happens. For more complex uses of // protocol data, install a subclass of WI.ProtocolTracer. set dumpInspectorProtocolMessages(value) { // Implicitly cause automatic logging to start if it's allowed. WI.settings.protocolAutoLogMessages.value = value; this._defaultTracer.dumpMessagesToConsole = value; } get dumpInspectorProtocolMessages() { return WI.settings.protocolAutoLogMessages.value; } set dumpInspectorTimeStats(value) { WI.settings.protocolAutoLogTimeStats.value = value; if (!this.dumpInspectorProtocolMessages) this.dumpInspectorProtocolMessages = true; this._defaultTracer.dumpTimingDataToConsole = value; } get dumpInspectorTimeStats() { return WI.settings.protocolAutoLogTimeStats.value; } set filterMultiplexingBackendInspectorProtocolMessages(value) { WI.settings.protocolFilterMultiplexingBackendMessages.value = value; this._defaultTracer.filterMultiplexingBackend = value; } get filterMultiplexingBackendInspectorProtocolMessages() { return WI.settings.protocolFilterMultiplexingBackendMessages.value; } set customTracer(tracer) { console.assert(!tracer || tracer instanceof WI.ProtocolTracer, tracer); console.assert(!tracer || tracer !== this._defaultTracer, tracer); // Bail early if no state change is to be made. if (!tracer && !this._customTracer) return; if (tracer === this._customTracer) return; if (tracer === this._defaultTracer) return; if (this._customTracer) this._customTracer.logFinished(); this._customTracer = tracer; this._activeTracers = [this._defaultTracer]; if (this._customTracer) { this._customTracer.logStarted(); this._activeTracers.push(this._customTracer); } } get activeTracers() { return this._activeTracers; } registerDomain(domainName, targetTypes) { targetTypes = targetTypes || WI.TargetType.all; for (let targetType of targetTypes) this._supportedDomainsForTargetType.add(targetType, domainName); this._registeredDomains[domainName] = new InspectorBackend.Domain(domainName); } registerVersion(domainName, version) { let domain = this._registeredDomains[domainName]; domain.VERSION = version; } registerEnum(qualifiedName, enumValues) { let [domainName, enumName] = qualifiedName.split("."); let domain = this._registeredDomains[domainName]; domain._addEnum(enumName, enumValues); } registerCommand(qualifiedName, targetTypes, callSignature, replySignature) { let [domainName, commandName] = qualifiedName.split("."); let domain = this._registeredDomains[domainName]; domain._addCommand(targetTypes, new InspectorBackend.Command(qualifiedName, commandName, callSignature, replySignature)); } registerEvent(qualifiedName, targetTypes, signature) { let [domainName, eventName] = qualifiedName.split("."); let domain = this._registeredDomains[domainName]; domain._addEvent(targetTypes, new InspectorBackend.Event(qualifiedName, eventName, signature)); } registerDispatcher(domainName, dispatcher) { let domain = this._registeredDomains[domainName]; domain._addDispatcher(dispatcher); } activateDomain(domainName, debuggableTypes) { // FIXME: Web Inspector: remove "extra domains" concept now that domains can be added based on the debuggable type // Ask `WI.sharedApp` (if it exists) as it may have a different debuggable type if extra // domains were activated, which is the only other time this will be called. let currentDebuggableType = WI.sharedApp?.debuggableType || InspectorFrontendHost.debuggableInfo.debuggableType; if (debuggableTypes && !debuggableTypes.includes(currentDebuggableType)) return; console.assert(domainName in this._registeredDomains); console.assert(!(domainName in this._activeDomains)); let domain = this._registeredDomains[domainName]; this._activeDomains[domainName] = domain; let supportedTargetTypes = WI.DebuggableType.supportedTargetTypes(currentDebuggableType); for (let [targetType, command] of domain._supportedCommandsForTargetType) { if (!supportedTargetTypes.has(targetType)) continue; let parameters = this._supportedCommandParameters.get(command._qualifiedName); if (!parameters) { parameters = new Set; this._supportedCommandParameters.set(command._qualifiedName, parameters); } parameters.addAll(command._callSignature.map((item) => item.name)); } for (let [targetType, event] of domain._supportedEventsForTargetType) { if (!supportedTargetTypes.has(targetType)) continue; let parameters = this._supportedEventParameters.get(event._qualifiedName); if (!parameters) { parameters = new Set; this._supportedEventParameters.set(event._qualifiedName, parameters); } parameters.addAll(event._parameterNames); } } dispatch(message) { InspectorBackend.backendConnection.dispatch(message); } runAfterPendingDispatches(callback) { if (!WI.mainTarget) { callback(); return; } // FIXME: Should this respect pending dispatches in all connections? WI.mainTarget.connection.runAfterPendingDispatches(callback); } supportedDomainsForTargetType(type) { console.assert(WI.TargetType.all.includes(type), "Unknown target type", type); return this._supportedDomainsForTargetType.get(type) || new Set; } hasDomain(domainName) { console.assert(!domainName.includes(".") && !domainName.endsWith("Agent")); return domainName in this._activeDomains; } hasCommand(qualifiedName, parameterName) { console.assert(qualifiedName.includes(".") && !qualifiedName.includes("Agent.")); let parameters = this._supportedCommandParameters.get(qualifiedName); if (!parameters) return false; return parameterName === undefined || parameters.has(parameterName); } hasEvent(qualifiedName, parameterName) { console.assert(qualifiedName.includes(".") && !qualifiedName.includes("Agent.")); let parameters = this._supportedEventParameters.get(qualifiedName); if (!parameters) return false; return parameterName === undefined || parameters.has(parameterName); } getVersion(domainName) { console.assert(!domainName.includes(".") && !domainName.endsWith("Agent")); let domain = this._activeDomains[domainName]; if (domain && "VERSION" in domain) return domain.VERSION; return -Infinity; } invokeCommand(qualifiedName, targetType, connection, commandArguments, callback) { let [domainName, commandName] = qualifiedName.split("."); let domain = this._activeDomains[domainName]; return domain._invokeCommand(commandName, targetType, connection, commandArguments, callback); } // Private _makeAgent(domainName, target) { let domain = this._activeDomains[domainName]; return domain._makeAgent(target); } _startOrStopAutomaticTracing() { this._defaultTracer.dumpMessagesToConsole = this.dumpInspectorProtocolMessages; this._defaultTracer.dumpTimingDataToConsole = this.dumpTimingDataToConsole; this._defaultTracer.filterMultiplexingBackend = this.filterMultiplexingBackendInspectorProtocolMessages; } }; InspectorBackend = new InspectorBackendClass; InspectorBackend.Domain = class InspectorBackendDomain { constructor(domainName) { this._domainName = domainName; // Enums are stored directly on the Domain instance using their unqualified // type name as the property. Thus, callers can write: Domain.EnumType. this._dispatcher = null; // FIXME: Web Inspector: release unused backend domains/events/commands once the debuggable type is known this._supportedCommandsForTargetType = new Multimap; this._supportedEventsForTargetType = new Multimap; } // Private _addEnum(enumName, enumValues) { console.assert(!(enumName in this)); this[enumName] = enumValues; } _addCommand(targetTypes, command) { targetTypes = targetTypes || WI.TargetType.all; for (let type of targetTypes) this._supportedCommandsForTargetType.add(type, command); } _addEvent(targetTypes, event) { targetTypes = targetTypes || WI.TargetType.all; for (let type of targetTypes) this._supportedEventsForTargetType.add(type, event); } _addDispatcher(dispatcher) { console.assert(!this._dispatcher); this._dispatcher = dispatcher; } _makeAgent(target) { let commands = this._supportedCommandsForTargetType.get(target.type) || new Set; let events = this._supportedEventsForTargetType.get(target.type) || new Set; return new InspectorBackend.Agent(target, commands, events, this._dispatcher); } _invokeCommand(commandName, targetType, connection, commandArguments, callback) { let commands = this._supportedCommandsForTargetType.get(targetType); for (let command of commands) { if (command._commandName === commandName) return command._makeCallable(connection).invoke(commandArguments, callback); } console.assert(); } }; InspectorBackend.Agent = class InspectorBackendAgent { constructor(target, commands, events, dispatcher) { // Commands are stored directly on the Agent instance using their unqualified // method name as the property. Thus, callers can write: DomainAgent.commandName(). for (let command of commands) { this[command._commandName] = command._makeCallable(target.connection); target._supportedCommandParameters.set(command._qualifiedName, command); } this._events = {}; for (let event of events) { this._events[event._eventName] = event; target._supportedEventParameters.set(event._qualifiedName, event); } this._dispatcher = dispatcher ? new dispatcher(target) : null; } }; InspectorBackend.Dispatcher = class InspectorBackendDispatcher { constructor(target) { console.assert(target instanceof WI.Target); this._target = target; } }; InspectorBackend.Command = class InspectorBackendCommand { constructor(qualifiedName, commandName, callSignature, replySignature) { this._qualifiedName = qualifiedName; this._commandName = commandName; this._callSignature = callSignature || []; this._replySignature = replySignature || []; } // Private _hasParameter(parameterName) { return this._callSignature.some((item) => item.name === parameterName); } _makeCallable(connection) { let instance = new InspectorBackend.Callable(this, connection); function callable() { console.assert(this instanceof InspectorBackend.Agent); return instance._invokeWithArguments.call(instance, Array.from(arguments)); } callable._instance = instance; Object.setPrototypeOf(callable, InspectorBackend.Callable.prototype); return callable; } }; InspectorBackend.Event = class InspectorBackendEvent { constructor(qualifiedName, eventName, parameterNames) { this._qualifiedName = qualifiedName; this._eventName = eventName; this._parameterNames = parameterNames || []; } // Private _hasParameter(parameterName) { return this._parameterNames.includes(parameterName); } }; // InspectorBackend.Callable can't use ES6 classes because of its trampoline nature. // But we can use strict mode to get stricter handling of the code inside its functions. InspectorBackend.Callable = function(command, connection) { "use strict"; this._command = command; this._connection = connection; this._instance = this; }; // As part of the workaround to make commands callable, these functions use `this._instance`. // `this` could refer to the callable trampoline, or the InspectorBackend.Callable instance. InspectorBackend.Callable.prototype = { __proto__: Function.prototype, // Public invoke(commandArguments, callback) { "use strict"; let command = this._instance._command; let connection = this._instance._connection; function deliverFailure(message) { console.error(`Protocol Error: ${message}`); if (callback) setTimeout(callback.bind(null, message), 0); else return Promise.reject(new Error(message)); } if (typeof commandArguments !== "object") return deliverFailure(`invoke expects an object for command arguments but its type is '${typeof commandArguments}'.`); let parameters = {}; for (let {name, type, optional} of command._callSignature) { if (!(name in commandArguments) && !optional) return deliverFailure(`Missing argument '${name}' for command '${command._qualifiedName}'.`); let value = commandArguments[name]; if (optional && value === undefined) continue; if (typeof value !== type) return deliverFailure(`Invalid type of argument '${name}' for command '${command._qualifiedName}' call. It must be '${type}' but it is '${typeof value}'.`); parameters[name] = value; } if (typeof callback === "function") connection._sendCommandToBackendWithCallback(command, parameters, callback); else return connection._sendCommandToBackendExpectingPromise(command, parameters); }, // Private _invokeWithArguments(commandArguments) { "use strict"; let command = this._instance._command; let connection = this._instance._connection; let callback = typeof commandArguments.lastValue === "function" ? commandArguments.pop() : null; function deliverFailure(message) { console.error(`Protocol Error: ${message}`); if (callback) setTimeout(callback.bind(null, message), 0); else return Promise.reject(new Error(message)); } let parameters = {}; for (let {name, type, optional} of command._callSignature) { if (!commandArguments.length && !optional) return deliverFailure(`Invalid number of arguments for command '${command._qualifiedName}'.`); let value = commandArguments.shift(); if (optional && value === undefined) continue; if (typeof value !== type) return deliverFailure(`Invalid type of argument '${name}' for command '${command._qualifiedName}' call. It must be '${type}' but it is '${typeof value}'.`); parameters[name] = value; } if (!callback && commandArguments.length === 1 && commandArguments[0] !== undefined) return deliverFailure(`Protocol Error: Optional callback argument for command '${command._qualifiedName}' call must be a function but its type is '${typeof commandArguments[0]}'.`); if (callback) connection._sendCommandToBackendWithCallback(command, parameters, callback); else return connection._sendCommandToBackendExpectingPromise(command, parameters); } }; /* Protocol/Connection.js */ /* * Copyright (C) 2011 Google Inc. All rights reserved. * Copyright (C) 2013-2016 Apple Inc. All rights reserved. * Copyright (C) 2014 University of Washington. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 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. * * Neither the name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT * OWNER 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. */ InspectorBackend.globalSequenceId = 1; InspectorBackend.Connection = class InspectorBackendConnection { constructor() { this._pendingResponses = new Map; this._deferredCallbacks = []; this._target = null; this._provisionalMessages = []; } // Public get target() { return this._target; } set target(target) { console.assert(!this._target); this._target = target; } addProvisionalMessage(message) { console.assert(this.target && this.target.isProvisional); this._provisionalMessages.push(message); } dispatchProvisionalMessages() { console.assert(this.target && !this.target.isProvisional); for (let message of this._provisionalMessages) this.dispatch(message); this._provisionalMessages = []; } dispatch(message) { let messageObject = typeof message === "string" ? JSON.parse(message) : message; if ("id" in messageObject) this._dispatchResponse(messageObject); else this._dispatchEvent(messageObject); } runAfterPendingDispatches(callback) { console.assert(typeof callback === "function"); if (!this._pendingResponses.size) callback.call(this); else this._deferredCallbacks.push(callback); } // Protected sendMessageToBackend(message) { throw new Error("Should be implemented by a InspectorBackend.Connection subclass"); } // Private _dispatchResponse(messageObject) { console.assert(this._pendingResponses.size >= 0); if (messageObject.error && messageObject.error.code !== -32_000) console.error("Request with id = " + messageObject["id"] + " failed. " + JSON.stringify(messageObject.error)); let sequenceId = messageObject["id"]; console.assert(this._pendingResponses.has(sequenceId), sequenceId, this._target ? this._target.identifier : "(unknown)", this._pendingResponses); let responseData = this._pendingResponses.take(sequenceId) || {}; let {request, command, callback, promise} = responseData; let processingStartTimestamp = performance.now(); for (let tracer of InspectorBackend.activeTracers) tracer.logWillHandleResponse(this, messageObject); InspectorBackend.currentDispatchState.request = request; InspectorBackend.currentDispatchState.response = messageObject; if (typeof callback === "function") this._dispatchResponseToCallback(command, request, messageObject, callback); else if (typeof promise === "object") this._dispatchResponseToPromise(command, messageObject, promise); else console.error("Received a command response without a corresponding callback or promise.", messageObject, command); InspectorBackend.currentDispatchState.request = null; InspectorBackend.currentDispatchState.response = null; let processingTime = (performance.now() - processingStartTimestamp).toFixed(3); let roundTripTime = (processingStartTimestamp - responseData.sendRequestTimestamp).toFixed(3); for (let tracer of InspectorBackend.activeTracers) tracer.logDidHandleResponse(this, messageObject, {rtt: roundTripTime, dispatch: processingTime}); if (this._deferredCallbacks.length && !this._pendingResponses.size) this._flushPendingScripts(); } _dispatchResponseToCallback(command, requestObject, responseObject, callback) { let callbackArguments = []; callbackArguments.push(responseObject["error"] ? responseObject["error"].message : null); if (responseObject["result"]) { for (let parameterName of command._replySignature) callbackArguments.push(responseObject["result"][parameterName]); } try { callback.apply(null, callbackArguments); } catch (e) { WI.reportInternalError(e, {"cause": `An uncaught exception was thrown while dispatching response callback for command ${command._qualifiedName}.`}); } } _dispatchResponseToPromise(command, messageObject, promise) { let {resolve, reject} = promise; if (messageObject["error"]) reject(new Error(messageObject["error"].message)); else resolve(messageObject["result"]); } _dispatchEvent(messageObject) { let qualifiedName = messageObject.method; let [domainName, eventName] = qualifiedName.split("."); // COMPATIBILITY (iOS 12.2 and iOS 13): because the multiplexing target isn't created until // `Target.exists` returns, any `Target.targetCreated` won't have a dispatcher for the // message, so create a multiplexing target here to force this._target._agents.Target. if (!this._target && this === InspectorBackend.backendConnection && WI.sharedApp.debuggableType === WI.DebuggableType.WebPage && qualifiedName === "Target.targetCreated") WI.targetManager.createMultiplexingBackendTarget(); let agent = this._target._agents[domainName]; if (!agent) { console.error(`Protocol Error: Attempted to dispatch method '${qualifiedName}' for non-existing domain '${domainName}'`, messageObject); return; } let dispatcher = agent._dispatcher; if (!dispatcher) { console.error(`Protocol Error: Missing dispatcher for domain '${domainName}', for event '${qualifiedName}'`, messageObject); return; } let event = agent._events[eventName]; if (!event) { console.error(`Protocol Error: Attempted to dispatch an unspecified method '${qualifiedName}'`, messageObject); return; } let handler = dispatcher[eventName]; if (!handler) { console.error(`Protocol Error: Attempted to dispatch an unimplemented method '${qualifiedName}'`, messageObject); return; } let processingStartTimestamp = performance.now(); for (let tracer of InspectorBackend.activeTracers) tracer.logWillHandleEvent(this, messageObject); InspectorBackend.currentDispatchState.event = messageObject; try { let params = messageObject.params || {}; handler.apply(dispatcher, event._parameterNames.map((name) => params[name])); } catch (e) { for (let tracer of InspectorBackend.activeTracers) tracer.logFrontendException(this, messageObject, e); WI.reportInternalError(e, {"cause": `An uncaught exception was thrown while handling event: ${qualifiedName}`}); } InspectorBackend.currentDispatchState.event = null; let processingDuration = (performance.now() - processingStartTimestamp).toFixed(3); for (let tracer of InspectorBackend.activeTracers) tracer.logDidHandleEvent(this, messageObject, {dispatch: processingDuration}); } _sendCommandToBackendWithCallback(command, parameters, callback) { let sequenceId = InspectorBackend.globalSequenceId++; let messageObject = { "id": sequenceId, "method": command._qualifiedName, }; if (!isEmptyObject(parameters)) messageObject["params"] = parameters; let responseData = {command, request: messageObject, callback}; if (InspectorBackend.activeTracer) responseData.sendRequestTimestamp = performance.now(); this._pendingResponses.set(sequenceId, responseData); this._sendMessageToBackend(messageObject); } _sendCommandToBackendExpectingPromise(command, parameters) { let sequenceId = InspectorBackend.globalSequenceId++; let messageObject = { "id": sequenceId, "method": command._qualifiedName, }; if (!isEmptyObject(parameters)) messageObject["params"] = parameters; let responseData = {command, request: messageObject}; if (InspectorBackend.activeTracer) responseData.sendRequestTimestamp = performance.now(); let responsePromise = new Promise(function(resolve, reject) { responseData.promise = {resolve, reject}; }); this._pendingResponses.set(sequenceId, responseData); this._sendMessageToBackend(messageObject); return responsePromise; } _sendMessageToBackend(messageObject) { for (let tracer of InspectorBackend.activeTracers) tracer.logFrontendRequest(this, messageObject); this.sendMessageToBackend(JSON.stringify(messageObject)); } _flushPendingScripts() { console.assert(this._pendingResponses.size === 0); let scriptsToRun = this._deferredCallbacks; this._deferredCallbacks = []; for (let script of scriptsToRun) script.call(this); } }; InspectorBackend.BackendConnection = class InspectorBackendBackendConnection extends InspectorBackend.Connection { sendMessageToBackend(message) { InspectorFrontendHost.sendMessageToBackend(message); } }; InspectorBackend.WorkerConnection = class InspectorBackendWorkerConnection extends InspectorBackend.Connection { sendMessageToBackend(message) { let workerId = this.target.identifier; this.target.parentTarget.WorkerAgent.sendMessageToWorker(workerId, message).catch((error) => { // Ignore errors if a worker went away quickly. if (this.target.isDestroyed) return; WI.reportInternalError(error); }); } }; InspectorBackend.TargetConnection = class InspectorBackendTargetConnection extends InspectorBackend.Connection { sendMessageToBackend(message) { let targetId = this.target.identifier; this.target.parentTarget.TargetAgent.sendMessageToTarget(targetId, message).catch((error) => { // Ignore errors if the target was destroyed after the command was dispatched. if (this.target.isDestroyed) return; WI.reportInternalError(error); }); } }; InspectorBackend.backendConnection = new InspectorBackend.BackendConnection; /* Protocol/InspectorFrontendAPI.js */ /* * Copyright (C) 2013-2021 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. */ InspectorFrontendAPI = { _loaded: false, _pendingCommands: [], isTimelineProfilingEnabled: function() { return WI.timelineManager.isCapturing(); }, setTimelineProfilingEnabled: function(enabled) { if (!WI.targetsAvailable()) { WI.whenTargetsAvailable().then(() => { InspectorFrontendAPI.setTimelineProfilingEnabled(enabled); }); return; } WI.showTimelineTab({ initiatorHint: WI.TabBrowser.TabNavigationInitiator.FrontendAPI }); if (WI.timelineManager.isCapturing() === enabled) return; if (enabled) WI.timelineManager.startCapturing(); else WI.timelineManager.stopCapturing(); }, setElementSelectionEnabled: function(enabled) { if (!WI.targetsAvailable()) { WI.whenTargetsAvailable().then(() => { InspectorFrontendAPI.setElementSelectionEnabled(enabled); }); return; } WI.domManager.inspectModeEnabled = enabled; }, setDockingUnavailable: function(unavailable) { WI.updateDockingAvailability(!unavailable); }, setDockSide: function(side) { WI.updateDockedState(side); }, setIsVisible: function(visible) { WI.updateVisibilityState(visible); }, updateFindString: function(findString) { WI.updateFindString(findString); }, setDiagnosticLoggingAvailable: function(available) { if (WI.diagnosticController) WI.diagnosticController.diagnosticLoggingAvailable = available; }, showConsole: function() { WI.showConsoleTab({ initiatorHint: WI.TabBrowser.TabNavigationInitiator.FrontendAPI, }); WI.quickConsole.prompt.focus(); // If the page is still loading, focus the quick console again after tabindex autofocus. if (document.readyState !== "complete") document.addEventListener("readystatechange", this); if (document.visibilityState !== "visible") document.addEventListener("visibilitychange", this); }, handleEvent: function(event) { console.assert(event.type === "readystatechange" || event.type === "visibilitychange"); if (document.readyState === "complete" && document.visibilityState === "visible") { WI.quickConsole.prompt.focus(); document.removeEventListener("readystatechange", this); document.removeEventListener("visibilitychange", this); } }, showResources: function() { WI.showSourcesTab({ initiatorHint: WI.TabBrowser.TabNavigationInitiator.FrontendAPI, }); }, // COMPATIBILITY (iOS 13): merged into InspectorFrontendAPI.setTimelineProfilingEnabled. showTimelines: function() { WI.showTimelineTab({ initiatorHint: WI.TabBrowser.TabNavigationInitiator.FrontendAPI }); }, showMainResourceForFrame: function(frameIdentifier) { WI.showSourceCodeForFrame(frameIdentifier, { ignoreNetworkTab: true, ignoreSearchTab: true, initiatorHint: WI.TabBrowser.TabNavigationInitiator.FrontendAPI, }); }, contextMenuItemSelected: function(id) { WI.ContextMenu.contextMenuItemSelected(id); }, contextMenuCleared: function() { WI.ContextMenu.contextMenuCleared(); }, dispatchMessageAsync: function(messageObject) { WI.dispatchMessageFromBackend(messageObject); }, dispatchMessage: function(messageObject) { InspectorBackend.dispatch(messageObject); }, dispatch: function(signature) { if (!InspectorFrontendAPI._loaded) { InspectorFrontendAPI._pendingCommands.push(signature); return null; } var methodName = signature.shift(); console.assert(InspectorFrontendAPI[methodName], "Unexpected InspectorFrontendAPI method name: " + methodName); if (!InspectorFrontendAPI[methodName]) return null; return InspectorFrontendAPI[methodName].apply(InspectorFrontendAPI, signature); }, loadCompleted: function() { InspectorFrontendAPI._loaded = true; for (var i = 0; i < InspectorFrontendAPI._pendingCommands.length; ++i) InspectorFrontendAPI.dispatch(InspectorFrontendAPI._pendingCommands[i]); delete InspectorFrontendAPI._pendingCommands; }, // Returns a WI.WebInspectorExtension.ErrorCode if an error occurred, otherwise nothing. registerExtension(extensionID, extensionBundleIdentifier, displayName) { return WI.sharedApp.extensionController.registerExtension(extensionID, extensionBundleIdentifier, displayName); }, // Returns a WI.WebInspectorExtension.ErrorCode if an error occurred, otherwise nothing. unregisterExtension(extensionID) { return WI.sharedApp.extensionController.unregisterExtension(extensionID); }, // Returns a string (WI.WebInspectorExtension.ErrorCode) if an error occurred that prevented creating a tab. // Returns a Promise that is resolved if the evaluation completes and rejected if there was an internal error. // When the promise is fulfilled, it will be either: // - resolved with an object containing a 'result' key and value that is the tab identifier for the new tab. // - rejected with an object containing an 'error' key and value that is the exception that was thrown while evaluating script. createTabForExtension(extensionID, tabName, tabIconURL, sourceURL) { return WI.sharedApp.extensionController.createTabForExtension(extensionID, tabName, tabIconURL, sourceURL); }, // Returns a string (WI.WebInspectorExtension.ErrorCode) if an error occurred that prevented evaluation. // Returns a Promise that is resolved if the evaluation completes and rejected if there was an internal error. // When the promise is fulfilled, it will be either: // - resolved with an object containing a 'result' key and value that is the result of the script evaluation. // - rejected with an object containing an 'error' key and value that is the exception that was thrown while evaluating script. evaluateScriptForExtension(extensionID, scriptSource, {frameURL, contextSecurityOrigin, useContentScriptContext} = {}) { return WI.sharedApp.extensionController.evaluateScriptForExtension(extensionID, scriptSource, {frameURL, contextSecurityOrigin, useContentScriptContext}); }, // Returns a string (WI.WebInspectorExtension.ErrorCode) if an error occurred that prevented reloading. reloadForExtension(extensionID, {ignoreCache, userAgent, injectedScript} = {}) { return WI.sharedApp.extensionController.reloadForExtension(extensionID, {ignoreCache, userAgent, injectedScript}); }, // Returns a string (WI.WebInspectorExtension.ErrorCode) if an error occurred before attempting to switch tabs. // Returns a Promise that is resolved if the tab could be shown and rejected if the tab could not be shown. // When the promise is fulfilled, it will be either: // - resolved with no value. // - rejected with an object containing an 'error' key and value that is the exception that was thrown while showing the tab. showExtensionTab(extensionTabID) { return WI.sharedApp.extensionController.showExtensionTab(extensionTabID); }, // Returns a string (WI.WebInspectorExtension.ErrorCode) if an error occurred that prevented evaluation. // Returns a Promise that is resolved if the evaluation completes and rejected if there was an internal error. // When the promise is fulfilled, it will be either: // - resolved with an object containing a 'result' key and value that is the result of the script evaluation. // - rejected with an object containing an 'error' key and value that is the exception that was thrown while evaluating script. evaluateScriptInExtensionTab(extensionTabID, scriptSource) { return WI.sharedApp.extensionController.evaluateScriptInExtensionTab(extensionTabID, scriptSource); }, // Returns a string (WI.WebInspectorExtension.ErrorCode) if an error occurred. navigateTabForExtension(extensionTabID, sourceURL) { return WI.sharedApp.extensionController.navigateTabForExtension(extensionTabID, sourceURL); }, }; /* Protocol/LoadInspectorBackendCommands.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. */ (function() { let backendCommandsURL = InspectorFrontendHost.backendCommandsURL || "Protocol/InspectorBackendCommands.js"; document.write(""); })(); /* Protocol/MessageDispatcher.js */ /* * Copyright (C) 2013, 2015 Apple Inc. All rights reserved. * Copyright (C) 2014 University of Washington * * 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._messagesToDispatch = []; WI.dispatchNextQueuedMessageFromBackend = function() { const startCount = WI._messagesToDispatch.length; const startTimestamp = performance.now(); const timeLimitPerRunLoop = 10; // milliseconds let i = 0; for (; i < WI._messagesToDispatch.length; ++i) { // Defer remaining messages if we have taken too long. In practice, single // messages like Page.getResourceContent blow through the time budget. if (performance.now() - startTimestamp > timeLimitPerRunLoop) break; InspectorBackend.dispatch(WI._messagesToDispatch[i]); } if (i === WI._messagesToDispatch.length) { WI._messagesToDispatch = []; WI._dispatchTimeout = null; } else { WI._messagesToDispatch = WI._messagesToDispatch.slice(i); WI._dispatchTimeout = setTimeout(WI.dispatchNextQueuedMessageFromBackend, 0); } if (InspectorBackend.dumpInspectorTimeStats) { let messageDuration = (performance.now() - startTimestamp).toFixed(3); let dispatchedCount = startCount - WI._messagesToDispatch.length; let remainingCount = WI._messagesToDispatch.length; console.log(`time-stats: --- RunLoop duration: ${messageDuration}ms; dispatched: ${dispatchedCount}; remaining: ${remainingCount}`); } }; WI.dispatchMessageFromBackend = function(message) { // Enforce asynchronous interaction between the backend and the frontend by queueing messages. // The messages are dequeued on a zero delay timeout. this._messagesToDispatch.push(message); // If something has gone wrong and the uncaught exception sheet is showing, // then don't try to dispatch more messages. Dispatching causes spurious uncaught // exceptions and cause the sheet to overflow with hundreds of logged exceptions. if (window.__uncaughtExceptions && window.__uncaughtExceptions.length) return; if (this._dispatchTimeout) return; this._dispatchTimeout = setTimeout(this.dispatchNextQueuedMessageFromBackend, 0); }; /* Protocol/Target.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.Target = class Target extends WI.Object { constructor(parentTarget, identifier, name, type, connection, {isPaused, isProvisional} = {}) { console.assert(parentTarget === null || parentTarget instanceof WI.Target); console.assert(!isPaused || parentTarget.hasCommand("Target.setPauseOnStart")); super(); this._parentTarget = parentTarget; this._identifier = identifier; this._name = name; this._type = type; this._connection = connection; this._isPaused = !!isPaused; this._isProvisional = !!isProvisional; this._executionContext = null; this._mainResource = null; this._resourceCollection = new WI.ResourceCollection; this._extraScriptCollection = new WI.ScriptCollection; this._supportedCommandParameters = new Map; this._supportedEventParameters = new Map; // Restrict the agents to the list of supported agents for this target type. // This makes it so `target.FooAgent` only exists if the "Foo" domain is // supported by the target. this._agents = {}; for (let domainName of InspectorBackend.supportedDomainsForTargetType(this._type)) this._agents[domainName] = InspectorBackend._makeAgent(domainName, this); this._connection.target = this; // Agents we always expect in every target. console.assert(this.hasDomain("Target") || this.hasDomain("Runtime")); console.assert(this.hasDomain("Target") || this.hasDomain("Debugger")); console.assert(this.hasDomain("Target") || this.hasDomain("Console")); } // Target initialize() { // Intentionally initialize InspectorAgent first if it is available. // This may not be strictly necessary anymore, but is historical. if (this.hasDomain("Inspector")) this.InspectorAgent.enable(); // Initialize agents. for (let manager of WI.managers) { if (manager.initializeTarget) manager.initializeTarget(this); } // Non-manager specific initialization. WI.initializeTarget(this); // Intentionally defer ConsoleAgent initialization to the end. We do this so that any // previous initialization messages will have their responses arrive before a stream // of console message added events come in after enabling Console. this.ConsoleAgent.enable(); setTimeout(() => { // Use this opportunity to run any one time frontend initialization // now that we have a target with potentially new capabilities. WI.performOneTimeFrontendInitializationsUsingTarget(this); }); console.assert(Target._initializationPromises.length || Target._completedInitializationPromiseCount); Promise.all(Target._initializationPromises).then(() => { // Tell the backend we are initialized after all our initialization messages have been sent. // This allows an automatically paused backend to resume execution, but we want to ensure // our breakpoints were already sent to that backend. if (this.hasDomain("Inspector")) this.InspectorAgent.initialized(); }); this._resumeIfPaused(); } _resumeIfPaused() { if (this._isPaused) { console.assert(this._parentTarget.hasCommand("Target.resume")); this._parentTarget.TargetAgent.resume(this._identifier, (error) => { if (error) { // Ignore errors if the target was destroyed after the command was sent. if (!this.isDestroyed) WI.reportInternalError(error); return; } this._isPaused = false; }); } } activateExtraDomain(domainName) { // COMPATIBILITY (iOS 14.0): Inspector.activateExtraDomains was removed in favor of a declared debuggable type this._agents[domainName] = InspectorBackend._makeAgent(domainName, this); } // Agents get AnimationAgent() { return this._agents.Animation; } get ApplicationCacheAgent() { return this._agents.ApplicationCache; } get AuditAgent() { return this._agents.Audit; } get BrowserAgent() { return this._agents.Browser; } get CPUProfilerAgent() { return this._agents.CPUProfiler; } get CSSAgent() { return this._agents.CSS; } get CanvasAgent() { return this._agents.Canvas; } get ConsoleAgent() { return this._agents.Console; } get DOMAgent() { return this._agents.DOM; } get DOMDebuggerAgent() { return this._agents.DOMDebugger; } get DOMStorageAgent() { return this._agents.DOMStorage; } get DatabaseAgent() { return this._agents.Database; } get DebuggerAgent() { return this._agents.Debugger; } get HeapAgent() { return this._agents.Heap; } get IndexedDBAgent() { return this._agents.IndexedDB; } get InspectorAgent() { return this._agents.Inspector; } get LayerTreeAgent() { return this._agents.LayerTree; } get MemoryAgent() { return this._agents.Memory; } get NetworkAgent() { return this._agents.Network; } get PageAgent() { return this._agents.Page; } get RecordingAgent() { return this._agents.Recording; } get RuntimeAgent() { return this._agents.Runtime; } get ScriptProfilerAgent() { return this._agents.ScriptProfiler; } get ServiceWorkerAgent() { return this._agents.ServiceWorker; } get TargetAgent() { return this._agents.Target; } get TimelineAgent() { return this._agents.Timeline; } get WorkerAgent() { return this._agents.Worker; } // Static static registerInitializationPromise(promise) { // This can be called for work that has to be done before `Inspector.initialized` is called. // Should only be called before the first target is created. console.assert(!Target._completedInitializationPromiseCount); Target._initializationPromises.push(promise); promise.then(() => { ++Target._completedInitializationPromiseCount; Target._initializationPromises.remove(promise); }); } // Public get parentTarget() { return this._parentTarget; } get rootTarget() { if (this._type === WI.TargetType.Page) return this; if (this._parentTarget) return this._parentTarget.rootTarget; return this; } get identifier() { return this._identifier; } set identifier(identifier) { this._identifier = identifier; } get name() { return this._name; } set name(name) { this._name = name; } get type() { return this._type; } get connection() { return this._connection; } get executionContext() { return this._executionContext; } get resourceCollection() { return this._resourceCollection; } get extraScriptCollection() { return this._extraScriptCollection; } get isProvisional() { return this._isProvisional; } get isPaused() { return this._isPaused; } get isDestroyed() { return this._isDestroyed; } get displayName() { return this._name; } get mainResource() { return this._mainResource; } set mainResource(resource) { console.assert(!this._mainResource); this._mainResource = resource; this.dispatchEventToListeners(WI.Target.Event.MainResourceAdded, {resource}); } addResource(resource) { this._resourceCollection.add(resource); this.dispatchEventToListeners(WI.Target.Event.ResourceAdded, {resource}); } adoptResource(resource) { resource._target = this; this.addResource(resource); } addScript(script) { this._extraScriptCollection.add(script); this.dispatchEventToListeners(WI.Target.Event.ScriptAdded, {script}); } didCommitProvisionalTarget() { console.assert(this._isProvisional); this._isProvisional = false; } destroy() { this._isDestroyed = true; } hasDomain(domainName) { console.assert(!domainName.includes(".") && !domainName.endsWith("Agent")); return domainName in this._agents; } hasCommand(qualifiedName, parameterName) { console.assert(qualifiedName.includes(".") && !qualifiedName.includes("Agent.")); let command = this._supportedCommandParameters.get(qualifiedName); if (!command) return false; return parameterName === undefined || command._hasParameter(parameterName); } hasEvent(qualifiedName, parameterName) { console.assert(qualifiedName.includes(".") && !qualifiedName.includes("Agent.")); let event = this._supportedEventParameters.get(qualifiedName); if (!event) return false; return parameterName === undefined || event._hasParameter(parameterName); } }; WI.Target.Event = { MainResourceAdded: "target-main-resource-added", ResourceAdded: "target-resource-added", ScriptAdded: "target-script-added", }; WI.Target._initializationPromises = []; WI.Target._completedInitializationPromiseCount = 0; /* Protocol/DirectBackendTarget.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. */ // This class is used when connecting directly to a single target. // The main connection is a direct connection to a target. WI.DirectBackendTarget = class DirectBackendTarget extends WI.Target { constructor() { const parentTarget = null; const targetId = "direct"; let {type, displayName} = DirectBackendTarget.connectionInfoForDebuggable(); super(parentTarget, targetId, displayName, type, InspectorBackend.backendConnection); this._executionContext = new WI.ExecutionContext(this, WI.RuntimeManager.TopLevelContextExecutionIdentifier, WI.ExecutionContext.Type.Normal, displayName); this._mainResource = null; } // Static static connectionInfoForDebuggable() { switch (WI.sharedApp.debuggableType) { case WI.DebuggableType.ITML: return { type: WI.TargetType.ITML, displayName: WI.UIString("ITML Context"), }; case WI.DebuggableType.JavaScript: return { type: WI.TargetType.JavaScript, displayName: WI.UIString("JavaScript Context"), }; case WI.DebuggableType.Page: return { type: WI.TargetType.Page, displayName: WI.UIString("Page"), }; case WI.DebuggableType.ServiceWorker: return { type: WI.TargetType.ServiceWorker, displayName: WI.UIString("ServiceWorker"), }; case WI.DebuggableType.WebPage: return { type: WI.TargetType.WebPage, displayName: WI.UIString("Page"), }; default: console.error("Unexpected debuggable type: ", WI.sharedApp.debuggableType); return { type: WI.TargetType.JavaScript, displayName: WI.UIString("JavaScript Context"), }; } } // Protected (Target) get mainResource() { if (this._mainResource) return this._mainResource; let mainFrame = WI.networkManager.mainFrame; return mainFrame ? mainFrame.mainResource : null; } set mainResource(resource) { this._mainResource = resource; } }; /* Protocol/MultiplexingBackendTarget.js */ /* * Copyright (C) 2018 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. */ // This class is used when connecting to a target which multiplexes to other targets. WI.MultiplexingBackendTarget = class MultiplexingBackendTarget extends WI.Target { constructor() { const parentTarget = null; const targetId = "multi"; super(parentTarget, targetId, WI.UIString("Web Page"), WI.TargetType.WebPage, InspectorBackend.backendConnection); console.assert(Array.shallowEqual(Object.keys(this._agents), ["Browser", "Target"])); } // Target initialize() { // Only initialize with the managers that are known to support a multiplexing target. WI.browserManager.initializeTarget(this); WI.targetManager.initializeTarget(this); } // Protected (Target) get name() { console.error("Called name on a MultiplexingBackendTarget"); return WI.UIString("Page"); } get executionContext() { console.error("Called executionContext on a MultiplexingBackendTarget"); return null; } get mainResource() { console.error("Called mainResource on a MultiplexingBackendTarget"); return null; } }; /* Protocol/PageTarget.js */ /* * Copyright (C) 2018 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.PageTarget = class PageTarget extends WI.Target { constructor(parentTarget, targetId, name, connection, options = {}) { super(parentTarget, targetId, name, WI.TargetType.Page, connection, options); this._executionContext = new WI.ExecutionContext(this, WI.RuntimeManager.TopLevelContextExecutionIdentifier, WI.ExecutionContext.Type.Normal, this.displayName); } }; /* Protocol/WorkerTarget.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.WorkerTarget = class WorkerTarget extends WI.Target { constructor(parentTarget, workerId, url, displayName, connection, options = {}) { super(parentTarget, workerId, url, WI.TargetType.Worker, connection, options); this._displayName = displayName; this._executionContext = new WI.ExecutionContext(this, WI.RuntimeManager.TopLevelContextExecutionIdentifier, WI.ExecutionContext.Type.Normal, this.displayName); } // Protected (Target) get customName() { return this._displayName; } get displayName() { return this._displayName || this.displayURL; } get displayURL() { return WI.displayNameForURL(this._name); } }; /* Protocol/InspectorObserver.js */ /* * Copyright (C) 2013-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 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.InspectorObserver = class InspectorObserver extends InspectorBackend.Dispatcher { // Events defined by the "Inspector" domain. evaluateForTestInFrontend(script) { if (!InspectorFrontendHost.isUnderTest()) return; InspectorBackend.runAfterPendingDispatches(function() { window.eval(script); }); } inspect(payload, hints) { let remoteObject = WI.RemoteObject.fromPayload(payload, WI.mainTarget); if (remoteObject.subtype === "node") { WI.domManager.inspectNodeObject(remoteObject); return; } if (remoteObject.type === "function") { remoteObject.findFunctionSourceCodeLocation().then((sourceCodeLocation) => { if (sourceCodeLocation instanceof WI.SourceCodeLocation) { WI.showSourceCodeLocation(sourceCodeLocation, { ignoreNetworkTab: true, ignoreSearchTab: true, }); } }); remoteObject.release(); return; } if (hints.databaseId) WI.databaseManager.inspectDatabase(hints.databaseId); else if (hints.domStorageId) WI.domStorageManager.inspectDOMStorage(hints.domStorageId); remoteObject.release(); } activateExtraDomains(domains) { // COMPATIBILITY (iOS 14.0): Inspector.activateExtraDomains was removed in favor of a declared debuggable type WI.sharedApp.activateExtraDomains(domains); } }; /* Protocol/AnimationObserver.js */ /* * Copyright (C) 2019 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.AnimationObserver = class AnimationObserver extends InspectorBackend.Dispatcher { // Events defined by the "Animation" domain. animationCreated(animation) { WI.animationManager.animationCreated(animation); } nameChanged(animationId, name) { WI.animationManager.nameChanged(animationId, name); } effectChanged(animationId, effect) { WI.animationManager.effectChanged(animationId, effect); } targetChanged(animationId) { WI.animationManager.targetChanged(animationId); } animationDestroyed(animationId) { WI.animationManager.animationDestroyed(animationId); } trackingStart(timestamp) { WI.timelineManager.animationTrackingStarted(timestamp); } trackingUpdate(timestamp, event) { WI.timelineManager.animationTrackingUpdated(timestamp, event); } trackingComplete(timestamp) { WI.timelineManager.animationTrackingCompleted(timestamp); } }; /* Protocol/ApplicationCacheObserver.js */ /* * Copyright (C) 2013, 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 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.ApplicationCacheObserver = class ApplicationCacheObserver extends InspectorBackend.Dispatcher { // Events defined by the "ApplicationCache" domain. applicationCacheStatusUpdated(frameId, manifestURL, status) { WI.applicationCacheManager.applicationCacheStatusUpdated(frameId, manifestURL, status); } networkStateUpdated(isNowOnline) { WI.applicationCacheManager.networkStateUpdated(isNowOnline); } }; /* Protocol/BrowserObserver.js */ /* * Copyright (C) 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. */ WI.BrowserObserver = class BrowserObserver extends InspectorBackend.Dispatcher { // Events defined by the "Browser" domain. extensionsEnabled(extensions) { WI.browserManager.extensionsEnabled(extensions); } extensionsDisabled(extensionIds) { WI.browserManager.extensionsDisabled(extensionIds); } }; /* Protocol/CPUProfilerObserver.js */ /* * Copyright (C) 2019 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.CPUProfilerObserver = class CPUProfilerObserver extends InspectorBackend.Dispatcher { // Events defined by the "CPUProfiler" domain. trackingStart(timestamp) { WI.timelineManager.cpuProfilerTrackingStarted(timestamp); } trackingUpdate(event) { WI.timelineManager.cpuProfilerTrackingUpdated(event); } trackingComplete(timestamp) { WI.timelineManager.cpuProfilerTrackingCompleted(timestamp); } }; /* Protocol/CSSObserver.js */ /* * Copyright (C) 2013, 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 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.CSSObserver = class CSSObserver extends InspectorBackend.Dispatcher { // Events defined by the "CSS" domain. mediaQueryResultChanged() { WI.cssManager.mediaQueryResultChanged(); } styleSheetChanged(styleSheetId) { WI.cssManager.styleSheetChanged(styleSheetId); } styleSheetAdded(styleSheetInfo) { WI.cssManager.styleSheetAdded(styleSheetInfo); } styleSheetRemoved(id) { WI.cssManager.styleSheetRemoved(id); } nodeLayoutFlagsChanged(nodeId, layoutFlags) { WI.domManager.nodeLayoutFlagsChanged(nodeId, layoutFlags); } nodeLayoutContextTypeChanged(nodeId, layoutContextType) { // COMPATIBILITY (macOS 13.0, iOS 16.0): CSS.nodeLayoutContextTypeChanged was renamed/expanded to CSS.nodeLayoutFlagsChanged. WI.domManager.nodeLayoutFlagsChanged(nodeId, [WI.DOMNode.LayoutFlag.Rendered, layoutContextType]); } }; /* Protocol/CanvasObserver.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 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.CanvasObserver = class CanvasObserver extends InspectorBackend.Dispatcher { // Events defined by the "Canvas" domain. canvasAdded(canvas) { WI.canvasManager.canvasAdded(this._target, canvas); } canvasRemoved(canvasId) { WI.canvasManager.canvasRemoved(this._target, canvasId); } canvasSizeChanged(canvasId, width, height) { WI.canvasManager.canvasSizeChanged(this._target, canvasId, width, height); } canvasMemoryChanged(canvasId, memoryCost) { WI.canvasManager.canvasMemoryChanged(this._target, canvasId, memoryCost); } clientNodesChanged(canvasId) { WI.canvasManager.clientNodesChanged(this._target, canvasId); } recordingStarted(canvasId, initiator) { WI.canvasManager.recordingStarted(this._target, canvasId, initiator); } recordingProgress(canvasId, frames, bufferUsed) { WI.canvasManager.recordingProgress(this._target, canvasId, frames, bufferUsed); } recordingFinished(canvasId, recording) { WI.canvasManager.recordingFinished(this._target, canvasId, recording); } extensionEnabled(canvasId, extension) { WI.canvasManager.extensionEnabled(this._target, canvasId, extension); } programCreated(shaderProgram) { // COMPATIBILITY (iOS 13.0): `shaderProgram` replaced `canvasId` and `programId`. if (arguments.length === 2) { shaderProgram = { canvasId: arguments[0], programId: arguments[1], }; } WI.canvasManager.programCreated(this._target, shaderProgram); } programDeleted(programId) { WI.canvasManager.programDeleted(this._target, programId); } // COMPATIBILITY (iOS 13): Canvas.events.cssCanvasClientNodesChanged was renamed to Canvas.events.clientNodesChanged. cssCanvasClientNodesChanged(canvasId) { WI.canvasManager.clientNodesChanged(this._target, canvasId); } }; /* Protocol/ConsoleObserver.js */ /* * Copyright (C) 2013, 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 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.ConsoleObserver = class ConsoleObserver extends InspectorBackend.Dispatcher { // Events defined by the "Console" domain. messageAdded(message) { if (message.source === "console-api" && message.type === "clear") return; if (message.type === "assert" && !message.text) message.text = WI.UIString("Assertion"); WI.consoleManager.messageWasAdded(this._target, message.source, message.level, message.text, message.type, message.url, message.line, message.column || 0, message.repeatCount, message.parameters, message.stackTrace, message.networkRequestId, message.timestamp); } messageRepeatCountUpdated(count, timestamp) { WI.consoleManager.messageRepeatCountUpdated(count, timestamp); } messagesCleared(reason) { WI.consoleManager.messagesCleared(reason); } heapSnapshot(timestamp, snapshotStringData, title) { let workerProxy = WI.HeapSnapshotWorkerProxy.singleton(); workerProxy.createSnapshot(snapshotStringData, title || null, ({objectId, snapshot: serializedSnapshot}) => { let snapshot = WI.HeapSnapshotProxy.deserialize(objectId, serializedSnapshot); snapshot.snapshotStringData = snapshotStringData; WI.timelineManager.heapSnapshotAdded(timestamp, snapshot); }); } }; /* Protocol/DOMObserver.js */ /* * Copyright (C) 2013, 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 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.DOMObserver = class DOMObserver extends InspectorBackend.Dispatcher { // Events defined by the "DOM" domain. documentUpdated() { WI.domManager._documentUpdated(); } inspect(nodeId) { WI.domManager.inspectElement(nodeId); } setChildNodes(parentId, nodes) { WI.domManager._setChildNodes(parentId, nodes); } attributeModified(nodeId, name, value) { WI.domManager._attributeModified(nodeId, name, value); } attributeRemoved(nodeId, name) { WI.domManager._attributeRemoved(nodeId, name); } inlineStyleInvalidated(nodeIds) { WI.domManager._inlineStyleInvalidated(nodeIds); } characterDataModified(nodeId, characterData) { WI.domManager._characterDataModified(nodeId, characterData); } childNodeCountUpdated(nodeId, childNodeCount) { WI.domManager._childNodeCountUpdated(nodeId, childNodeCount); } childNodeInserted(parentNodeId, previousNodeId, node) { WI.domManager._childNodeInserted(parentNodeId, previousNodeId, node); } childNodeRemoved(parentNodeId, nodeId) { WI.domManager._childNodeRemoved(parentNodeId, nodeId); } willDestroyDOMNode(nodeId) { WI.domManager.willDestroyDOMNode(nodeId); } shadowRootPushed(hostId, root) { WI.domManager._childNodeInserted(hostId, 0, root); } shadowRootPopped(hostId, rootId) { WI.domManager._childNodeRemoved(hostId, rootId); } customElementStateChanged(nodeId, customElementState) { WI.domManager._customElementStateChanged(nodeId, customElementState); } pseudoElementAdded(parentNodeId, pseudoElement) { WI.domManager._pseudoElementAdded(parentNodeId, pseudoElement); } pseudoElementRemoved(parentNodeId, pseudoElementId) { WI.domManager._pseudoElementRemoved(parentNodeId, pseudoElementId); } didAddEventListener(nodeId) { WI.domManager.didAddEventListener(nodeId); } willRemoveEventListener(nodeId) { WI.domManager.willRemoveEventListener(nodeId); } didFireEvent(nodeId, eventName, timestamp, data) { WI.domManager.didFireEvent(nodeId, eventName, timestamp, data); } videoLowPowerChanged(nodeId, timestamp, isLowPower) { // COMPATIBILITY (iOS 12.2): DOM.videoLowPowerChanged was renamed to DOM.powerEfficientPlaybackStateChanged. WI.domManager.powerEfficientPlaybackStateChanged(nodeId, timestamp, isLowPower); } powerEfficientPlaybackStateChanged(nodeId, timestamp, isPowerEfficient) { WI.domManager.powerEfficientPlaybackStateChanged(nodeId, timestamp, isPowerEfficient); } }; /* Protocol/DOMStorageObserver.js */ /* * Copyright (C) 2013 Apple Inc. All rights reserved. * Copyright (C) 2013 Samsung Electronics. 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.DOMStorageObserver = class DOMStorageObserver extends InspectorBackend.Dispatcher { // Events defined by the "DOMStorage" domain. domStorageItemsCleared(storageId) { WI.domStorageManager.itemsCleared(storageId); } domStorageItemRemoved(storageId, key) { WI.domStorageManager.itemRemoved(storageId, key); } domStorageItemAdded(storageId, key, value) { WI.domStorageManager.itemAdded(storageId, key, value); } domStorageItemUpdated(storageId, key, oldValue, newValue) { WI.domStorageManager.itemUpdated(storageId, key, oldValue, newValue); } }; /* Protocol/DatabaseObserver.js */ /* * Copyright (C) 2013, 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 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.DatabaseObserver = class DatabaseObserver extends InspectorBackend.Dispatcher { // Events defined by the "Database" domain. addDatabase(database) { WI.databaseManager.databaseWasAdded(database.id, database.domain, database.name, database.version); } }; /* Protocol/DebuggerObserver.js */ /* * Copyright (C) 2013, 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 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.DebuggerObserver = class DebuggerObserver extends InspectorBackend.Dispatcher { constructor(target) { super(target); this._legacyScriptParsed = this._target.hasEvent("Debugger.scriptParsed", "hasSourceURL"); } // Events defined by the "Debugger" domain. globalObjectCleared() { WI.debuggerManager.globalObjectCleared(this._target); } scriptParsed(scriptId, url, startLine, startColumn, endLine, endColumn, isContentScript, sourceURL, sourceMapURL, isModule) { WI.debuggerManager.scriptDidParse(this._target, scriptId, url, startLine, startColumn, endLine, endColumn, isModule, isContentScript, sourceURL, sourceMapURL); } scriptFailedToParse(url, scriptSource, startLine, errorLine, errorMessage) { // NOTE: A Console.messageAdded event will handle the error message. WI.debuggerManager.scriptDidFail(this._target, url, scriptSource); } breakpointResolved(breakpointId, location) { WI.debuggerManager.breakpointResolved(this._target, breakpointId, location); } paused(callFrames, reason, data, asyncStackTrace) { WI.debuggerManager.debuggerDidPause(this._target, callFrames, reason, data, asyncStackTrace); } resumed() { WI.debuggerManager.debuggerDidResume(this._target); } playBreakpointActionSound(breakpointActionIdentifier) { WI.debuggerManager.playBreakpointActionSound(breakpointActionIdentifier); } didSampleProbe(sample) { WI.debuggerManager.didSampleProbe(this._target, sample); } }; /* Protocol/HeapObserver.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 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.HeapObserver = class HeapObserver extends InspectorBackend.Dispatcher { // Events defined by the "Heap" domain. garbageCollected(collection) { WI.heapManager.garbageCollected(this._target, collection); } trackingStart(timestamp, snapshotStringData) { let workerProxy = WI.HeapSnapshotWorkerProxy.singleton(); workerProxy.createSnapshot(snapshotStringData, ({objectId, snapshot: serializedSnapshot}) => { let snapshot = WI.HeapSnapshotProxy.deserialize(objectId, serializedSnapshot); snapshot.snapshotStringData = snapshotStringData; WI.timelineManager.heapTrackingStarted(timestamp, snapshot); }); } trackingComplete(timestamp, snapshotStringData) { let workerProxy = WI.HeapSnapshotWorkerProxy.singleton(); workerProxy.createSnapshot(snapshotStringData, ({objectId, snapshot: serializedSnapshot}) => { let snapshot = WI.HeapSnapshotProxy.deserialize(objectId, serializedSnapshot); snapshot.snapshotStringData = snapshotStringData; WI.timelineManager.heapTrackingCompleted(timestamp, snapshot); }); } }; /* Protocol/InspectorObserver.js */ /* * Copyright (C) 2013-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 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.InspectorObserver = class InspectorObserver extends InspectorBackend.Dispatcher { // Events defined by the "Inspector" domain. evaluateForTestInFrontend(script) { if (!InspectorFrontendHost.isUnderTest()) return; InspectorBackend.runAfterPendingDispatches(function() { window.eval(script); }); } inspect(payload, hints) { let remoteObject = WI.RemoteObject.fromPayload(payload, WI.mainTarget); if (remoteObject.subtype === "node") { WI.domManager.inspectNodeObject(remoteObject); return; } if (remoteObject.type === "function") { remoteObject.findFunctionSourceCodeLocation().then((sourceCodeLocation) => { if (sourceCodeLocation instanceof WI.SourceCodeLocation) { WI.showSourceCodeLocation(sourceCodeLocation, { ignoreNetworkTab: true, ignoreSearchTab: true, }); } }); remoteObject.release(); return; } if (hints.databaseId) WI.databaseManager.inspectDatabase(hints.databaseId); else if (hints.domStorageId) WI.domStorageManager.inspectDOMStorage(hints.domStorageId); remoteObject.release(); } activateExtraDomains(domains) { // COMPATIBILITY (iOS 14.0): Inspector.activateExtraDomains was removed in favor of a declared debuggable type WI.sharedApp.activateExtraDomains(domains); } }; /* Protocol/LayerTreeObserver.js */ /* * Copyright (C) 2013, 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 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 OdF 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.LayerTreeObserver = class LayerTreeObserver extends InspectorBackend.Dispatcher { // Events defined by the "LayerTree" domain. layerTreeDidChange() { WI.layerTreeManager.layerTreeDidChange(); } }; /* Protocol/MemoryObserver.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.MemoryObserver = class MemoryObserver extends InspectorBackend.Dispatcher { // Events defined by the "Memory" domain. memoryPressure(timestamp, severity) { WI.memoryManager.memoryPressure(timestamp, severity); } trackingStart(timestamp) { WI.timelineManager.memoryTrackingStarted(timestamp); } trackingUpdate(event) { WI.timelineManager.memoryTrackingUpdated(event); } trackingComplete(timestamp) { WI.timelineManager.memoryTrackingCompleted(timestamp); } }; /* Protocol/NetworkObserver.js */ /* * Copyright (C) 2013, 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 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.NetworkObserver = class NetworkObserver extends InspectorBackend.Dispatcher { // Events defined by the "Network" domain. requestWillBeSent(requestId, frameId, loaderId, documentURL, request, timestamp, walltime, initiator, redirectResponse, type, targetId) { WI.networkManager.resourceRequestWillBeSent(requestId, frameId, loaderId, request, type, redirectResponse, timestamp, walltime, initiator, targetId); } responseReceived(requestId, frameId, loaderId, timestamp, type, response) { WI.networkManager.resourceRequestDidReceiveResponse(requestId, frameId, loaderId, type, response, timestamp); } dataReceived(requestId, timestamp, dataLength, encodedDataLength) { WI.networkManager.resourceRequestDidReceiveData(requestId, dataLength, encodedDataLength, timestamp); } loadingFinished(requestId, timestamp, sourceMapURL, metrics) { WI.networkManager.resourceRequestDidFinishLoading(requestId, timestamp, sourceMapURL, metrics); } loadingFailed(requestId, timestamp, errorText, canceled) { WI.networkManager.resourceRequestDidFailLoading(requestId, canceled, timestamp, errorText); } requestServedFromMemoryCache(requestId, frameId, loaderId, documentURL, timestamp, initiator, resource) { WI.networkManager.resourceRequestWasServedFromMemoryCache(requestId, frameId, loaderId, resource, timestamp, initiator); } webSocketCreated(requestId, url) { WI.networkManager.webSocketCreated(requestId, url); } webSocketWillSendHandshakeRequest(requestId, timestamp, walltime, request) { WI.networkManager.webSocketWillSendHandshakeRequest(requestId, timestamp, walltime, request); } webSocketHandshakeResponseReceived(requestId, timestamp, response) { WI.networkManager.webSocketHandshakeResponseReceived(requestId, timestamp, response); } webSocketClosed(requestId, timestamp) { WI.networkManager.webSocketClosed(requestId, timestamp); } webSocketFrameReceived(requestId, timestamp, response) { WI.networkManager.webSocketFrameReceived(requestId, timestamp, response); } webSocketFrameSent(requestId, timestamp, response) { WI.networkManager.webSocketFrameSent(requestId, timestamp, response); } webSocketFrameError(requestId, timestamp, errorMessage) { // FIXME: Not implemented. } requestIntercepted(requestId, request) { WI.networkManager.requestIntercepted(this._target, requestId, request); } responseIntercepted(requestId, response) { WI.networkManager.responseIntercepted(this._target, requestId, response); } }; /* Protocol/PageObserver.js */ /* * Copyright (C) 2013, 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 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.PageObserver = class PageObserver extends InspectorBackend.Dispatcher { // Events defined by the "Page" domain. domContentEventFired(timestamp) { WI.timelineManager.pageDOMContentLoadedEventFired(timestamp); } loadEventFired(timestamp) { WI.timelineManager.pageLoadEventFired(timestamp); } frameNavigated(frame, loaderId) { WI.networkManager.frameDidNavigate(frame, loaderId); } frameDetached(frameId) { WI.networkManager.frameDidDetach(frameId); } // COMPATIBILITY (macOS 13, iOS 16.0): `Page.defaultAppearanceDidChange` was removed in favor of `Page.defaultUserPreferencesDidChange` defaultAppearanceDidChange(appearance) { WI.cssManager.defaultAppearanceDidChange(appearance); } defaultUserPreferencesDidChange(userPreferences) { WI.cssManager.defaultUserPreferencesDidChange(userPreferences); } frameStartedLoading(frameId) { // Not handled yet. } frameStoppedLoading(frameId) { // Not handled yet. } frameScheduledNavigation(frameId, delay) { // Not handled yet. } frameClearedScheduledNavigation(frameId) { // Not handled yet. } }; /* Protocol/RemoteObject.js */ /* * Copyright (C) 2009 Google Inc. All rights reserved. * 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: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 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. * * Neither the name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT * OWNER 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.RemoteObject = class RemoteObject { constructor(target, objectId, type, subtype, value, description, size, classPrototype, className, preview) { console.assert(type); console.assert(!preview || preview instanceof WI.ObjectPreview); console.assert(!target || target instanceof WI.Target); this._target = target || WI.mainTarget; this._type = type; this._subtype = subtype; if (objectId) { // Object, Function, or Symbol. console.assert(!subtype || typeof subtype === "string"); console.assert(!description || typeof description === "string"); console.assert(!value); this._objectId = objectId; this._description = description || ""; this._hasChildren = type !== "symbol"; this._size = size; this._classPrototype = classPrototype; this._preview = preview; if (subtype === "class") { this._functionDescription = this._description; this._description = "class " + className; } } else { // Primitive, BigInt, or null. console.assert(type !== "object" || value === null); console.assert(!preview); this._description = description || (value + ""); this._hasChildren = false; this._value = value; if (type === "bigint") { console.assert(value === undefined); console.assert(description.endsWith("n")); if (window.BigInt) this._value = BigInt(description.substring(0, description.length - 1)); else this._value = `${description} [BigInt Not Enabled in Web Inspector]`; } } } // Static static createFakeRemoteObject() { return new WI.RemoteObject(undefined, WI.RemoteObject.FakeRemoteObjectId, "object"); } static fromPrimitiveValue(value) { return new WI.RemoteObject(undefined, undefined, typeof value, undefined, value, undefined, undefined, undefined, undefined); } static createBigIntFromDescriptionString(description) { console.assert(description.endsWith("n")); return new WI.RemoteObject(undefined, undefined, "bigint", undefined, undefined, description, undefined, undefined, undefined); } static fromPayload(payload, target) { console.assert(typeof payload === "object", "Remote object payload should only be an object"); if (payload.classPrototype) payload.classPrototype = WI.RemoteObject.fromPayload(payload.classPrototype, target); if (payload.preview) payload.preview = WI.ObjectPreview.fromPayload(payload.preview); return new WI.RemoteObject(target, payload.objectId, payload.type, payload.subtype, payload.value, payload.description, payload.size, payload.classPrototype, payload.className, payload.preview); } static createCallArgument(valueOrObject) { if (valueOrObject instanceof WI.RemoteObject) { if (valueOrObject.objectId) return {objectId: valueOrObject.objectId}; return {value: valueOrObject.value}; } return {value: valueOrObject}; } static resolveNode(node, objectGroup) { console.assert(node instanceof WI.DOMNode, node); if (node.destroyed) return Promise.reject("ERROR: node is destroyed"); let target = WI.assumingMainTarget(); return target.DOMAgent.resolveNode(node.id, objectGroup) .then(({object}) => WI.RemoteObject.fromPayload(object, WI.mainTarget)); } static resolveWebSocket(webSocketResource, objectGroup, callback) { console.assert(typeof callback === "function"); let target = WI.assumingMainTarget(); target.NetworkAgent.resolveWebSocket(webSocketResource.requestIdentifier, objectGroup, (error, object) => { if (error || !object) callback(null); else callback(WI.RemoteObject.fromPayload(object, webSocketResource.target)); }); } static resolveCanvasContext(canvas, objectGroup) { let promise = null; // COMPATIBILITY (iOS 13): Canvas.resolveCanvasContext was renamed to Canvas.resolveContext. if (!canvas.target.hasCommand("Canvas.resolveContext")) promise = canvas.target.CanvasAgent.resolveCanvasContext(canvas.identifier, objectGroup); else promise = canvas.target.CanvasAgent.resolveContext(canvas.identifier, objectGroup); return promise.then(({object}) => WI.RemoteObject.fromPayload(object, canvas.target)); } static resolveAnimation(animation, objectGroup, callback) { console.assert(typeof callback === "function"); function wrapCallback(error, object) { if (error || !object) callback(null); else callback(WI.RemoteObject.fromPayload(object, WI.mainTarget)); } let target = WI.assumingMainTarget(); // COMPATIBILITY (iOS 13.1): Animation.resolveAnimation did not exist yet. console.assert(target.hasCommand("Animation.resolveAnimation")); target.AnimationAgent.resolveAnimation(animation.animationId, objectGroup, wrapCallback); } // Public get target() { return this._target; } get objectId() { return this._objectId; } get type() { return this._type; } get subtype() { return this._subtype; } get description() { return this._description; } get functionDescription() { console.assert(this.type === "function"); return this._functionDescription || this._description; } get hasChildren() { return this._hasChildren; } get value() { return this._value; } get size() { return this._size || 0; } get classPrototype() { return this._classPrototype; } get preview() { return this._preview; } hasSize() { return this.isArray() || this.isCollectionType(); } hasValue() { return "_value" in this; } canLoadPreview() { if (this._failedToLoadPreview) return false; if (this._type !== "object") return false; if (!this._objectId || this._isSymbol() || this._isFakeObject()) return false; return true; } updatePreview(callback) { if (!this.canLoadPreview()) { callback(null); return; } if (!this._target.hasCommand("Runtime.getPreview")) { this._failedToLoadPreview = true; callback(null); return; } this._target.RuntimeAgent.getPreview(this._objectId, (error, payload) => { if (error) { this._failedToLoadPreview = true; callback(null); return; } this._preview = WI.ObjectPreview.fromPayload(payload); callback(this._preview); }); } getPropertyDescriptors(callback, options = {}) { if (!this._objectId || this._isSymbol() || this._isFakeObject()) { callback([]); return; } this._getProperties(this._getPropertyDescriptorsResolver.bind(this, callback), options); } getDisplayablePropertyDescriptors(callback, options = {}) { if (!this._objectId || this._isSymbol() || this._isFakeObject()) { callback([]); return; } this._getDisplayableProperties(this._getPropertyDescriptorsResolver.bind(this, callback), options); } setPropertyValue(name, value, callback) { if (!this._objectId || this._isSymbol() || this._isFakeObject()) { callback("Can't set a property of non-object."); return; } // FIXME: It doesn't look like setPropertyValue is used yet. This will need to be tested when it is again (editable ObjectTrees). this._target.RuntimeAgent.evaluate.invoke({expression: appendWebInspectorSourceURL(value), doNotPauseOnExceptionsAndMuteConsole: true}, evaluatedCallback.bind(this)); function evaluatedCallback(error, result, wasThrown) { if (error || wasThrown) { callback(error || result.description); return; } function setPropertyValue(propertyName, propertyValue) { this[propertyName] = propertyValue; } delete result.description; // Optimize on traffic. this._target.RuntimeAgent.callFunctionOn(this._objectId, appendWebInspectorSourceURL(setPropertyValue.toString()), [{value: name}, result], true, undefined, propertySetCallback.bind(this)); if (result._objectId) this._target.RuntimeAgent.releaseObject(result._objectId); } function propertySetCallback(error, result, wasThrown) { if (error || wasThrown) { callback(error || result.description); return; } callback(); } } isUndefined() { return this._type === "undefined"; } isNode() { return this._subtype === "node"; } isArray() { return this._subtype === "array"; } isClass() { return this._subtype === "class"; } isCollectionType() { return this._subtype === "map" || this._subtype === "set" || this._subtype === "weakmap" || this._subtype === "weakset"; } isWeakCollection() { return this._subtype === "weakmap" || this._subtype === "weakset"; } getCollectionEntries(callback, {fetchStart, fetchCount} = {}) { console.assert(this.isCollectionType()); console.assert(typeof fetchStart === "undefined" || (typeof fetchStart === "number" && fetchStart >= 0), fetchStart); console.assert(typeof fetchCount === "undefined" || (typeof fetchCount === "number" && fetchCount > 0), fetchCount); // WeakMaps and WeakSets are not ordered. We should never send a non-zero start. console.assert(!this.isWeakCollection() || typeof fetchStart === "undefined" || fetchStart === 0, fetchStart); let objectGroup = this.isWeakCollection() ? this._weakCollectionObjectGroup() : ""; // COMPATIBILITY (iOS 13): `startIndex` and `numberToFetch` were renamed to `fetchStart` and `fetchCount` (but kept in the same position). this._target.RuntimeAgent.getCollectionEntries(this._objectId, objectGroup, fetchStart, fetchCount, (error, entries) => { callback(entries.map((x) => WI.CollectionEntry.fromPayload(x, this._target))); }); } releaseWeakCollectionEntries() { console.assert(this.isWeakCollection()); this._target.RuntimeAgent.releaseObjectGroup(this._weakCollectionObjectGroup()); } pushNodeToFrontend(callback) { if (this._objectId && InspectorBackend.hasCommand("DOM.requestNode")) WI.domManager.pushNodeToFrontend(this._objectId, callback); else callback(0); } async fetchProperties(propertyNames, resultObject = {}) { let seenPropertyNames = new Set; let requestedValues = []; for (let propertyName of propertyNames) { // Check this here, otherwise things like '{}' would be valid Set keys. if (typeof propertyName !== "string" && typeof propertyName !== "number") throw new Error(`Tried to get property using key is not a string or number: ${propertyName}`); if (seenPropertyNames.has(propertyName)) continue; seenPropertyNames.add(propertyName); requestedValues.push(this.getProperty(propertyName)); } // Return primitive values directly, otherwise return a WI.RemoteObject instance. function maybeUnwrapValue(remoteObject) { return remoteObject.hasValue() ? remoteObject.value : remoteObject; } // Request property values one by one, since returning an array of property // values would then be subject to arbitrary object preview size limits. let fetchedKeys = Array.from(seenPropertyNames); let fetchedValues = await Promise.all(requestedValues); for (let i = 0; i < fetchedKeys.length; ++i) resultObject[fetchedKeys[i]] = maybeUnwrapValue(fetchedValues[i]); return resultObject; } getProperty(propertyName, callback = null) { function inspectedPage_object_getProperty(property) { if (typeof property !== "string" && typeof property !== "number") throw new Error(`Tried to get property using key is not a string or number: ${property}`); return this[property]; } if (callback && typeof callback === "function") this.callFunction(inspectedPage_object_getProperty, [propertyName], true, callback); else return this.callFunction(inspectedPage_object_getProperty, [propertyName], true); } callFunction(functionDeclaration, args, generatePreview, callback = null) { let translateResult = (result) => result ? WI.RemoteObject.fromPayload(result, this._target) : null; if (args) args = args.map(WI.RemoteObject.createCallArgument); if (callback && typeof callback === "function") { this._target.RuntimeAgent.callFunctionOn(this._objectId, appendWebInspectorSourceURL(functionDeclaration.toString()), args, true, undefined, !!generatePreview, (error, result, wasThrown) => { callback(error, translateResult(result), wasThrown); }); } else { // Protocol errors and results that were thrown should cause promise rejection with the same. return this._target.RuntimeAgent.callFunctionOn(this._objectId, appendWebInspectorSourceURL(functionDeclaration.toString()), args, true, undefined, !!generatePreview) .then(({result, wasThrown}) => { result = translateResult(result); if (result && wasThrown) return Promise.reject(result); return Promise.resolve(result); }); } } callFunctionJSON(functionDeclaration, args, callback = null) { if (callback && typeof callback === "function") { this._target.RuntimeAgent.callFunctionOn(this._objectId, appendWebInspectorSourceURL(functionDeclaration.toString()), args, true, true, (error, result, wasThrown) => { callback((error || wasThrown) ? null : result.value); }); } else { return this._target.RuntimeAgent.callFunctionOn(this._objectId, appendWebInspectorSourceURL(functionDeclaration.toString()), args, true, true) .then(({result, wasThrown}) => { return wasThrown ? null : result.value; }); } } invokeGetter(getterRemoteObject, callback) { console.assert(getterRemoteObject instanceof WI.RemoteObject); function backendInvokeGetter(getter) { return getter ? getter.call(this) : undefined; } this.callFunction(backendInvokeGetter, [getterRemoteObject], true, callback); } getOwnPropertyDescriptor(propertyName, callback) { function backendGetOwnPropertyDescriptor(propertyName) { return this[propertyName]; } function wrappedCallback(error, result, wasThrown) { if (error || wasThrown || !(result instanceof WI.RemoteObject)) { callback(null); return; } var fakeDescriptor = {name: propertyName, value: result, writable: true, configurable: true}; var fakePropertyDescriptor = new WI.PropertyDescriptor(fakeDescriptor, null, true, false, false, false); callback(fakePropertyDescriptor); } // FIXME: Implement a real RuntimeAgent.getOwnPropertyDescriptor? this.callFunction(backendGetOwnPropertyDescriptor, [propertyName], false, wrappedCallback.bind(this)); } release() { if (this._objectId && !this._isFakeObject()) this._target.RuntimeAgent.releaseObject(this._objectId); } arrayLength() { if (this._subtype !== "array") return 0; var matches = this._description.match(/\[([0-9]+)\]/); if (!matches) return 0; return parseInt(matches[1], 10); } asCallArgument() { return WI.RemoteObject.createCallArgument(this); } findFunctionSourceCodeLocation() { var result = new WI.WrappedPromise; if (!this._isFunction() || !this._objectId) { result.resolve(WI.RemoteObject.SourceCodeLocationPromise.MissingObjectId); return result.promise; } this._target.DebuggerAgent.getFunctionDetails(this._objectId, (error, response) => { if (error) { result.resolve(WI.RemoteObject.SourceCodeLocationPromise.NoSourceFound); return; } var location = response.location; var sourceCode = WI.debuggerManager.scriptForIdentifier(location.scriptId, this._target); if (!sourceCode || (!WI.settings.engineeringShowInternalScripts.value && isWebKitInternalScript(sourceCode.sourceURL))) { result.resolve(WI.RemoteObject.SourceCodeLocationPromise.NoSourceFound); return; } var sourceCodeLocation = sourceCode.createSourceCodeLocation(location.lineNumber, location.columnNumber || 0); result.resolve(sourceCodeLocation); }); return result.promise; } // Private _isFakeObject() { return this._objectId === WI.RemoteObject.FakeRemoteObjectId; } _isSymbol() { return this._type === "symbol"; } _isFunction() { return this._type === "function"; } _weakCollectionObjectGroup() { return JSON.stringify(this._objectId) + "-" + this._subtype; } _getProperties(callback, {ownProperties, fetchStart, fetchCount, generatePreview} = {}) { // COMPATIBILITY (iOS 13): `result` was renamed to `properties` (but kept in the same position). this._target.RuntimeAgent.getProperties.invoke({ objectId: this._objectId, ownProperties, fetchStart, fetchCount, generatePreview, }, callback); } _getDisplayableProperties(callback, {fetchStart, fetchCount, generatePreview} = {}) { console.assert(this._target.hasCommand("Runtime.getDisplayableProperties")); this._target.RuntimeAgent.getDisplayableProperties.invoke({ objectId: this._objectId, fetchStart, fetchCount, generatePreview, }, callback); } _getPropertyDescriptorsResolver(callback, error, properties, internalProperties) { if (error) { callback(null); return; } let descriptors = properties.map((payload) => WI.PropertyDescriptor.fromPayload(payload, false, this._target)); if (internalProperties) { for (let payload of internalProperties) descriptors.push(WI.PropertyDescriptor.fromPayload(payload, true, this._target)); } callback(descriptors); } }; WI.RemoteObject.FakeRemoteObjectId = "fake-remote-object"; WI.RemoteObject.SourceCodeLocationPromise = { NoSourceFound: "remote-object-source-code-location-promise-no-source-found", MissingObjectId: "remote-object-source-code-location-promise-missing-object-id" }; /* Protocol/RuntimeObserver.js */ /* * Copyright (C) 2013, 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 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.RuntimeObserver = class RuntimeObserver extends InspectorBackend.Dispatcher { // Events defined by the "Runtime" domain. executionContextCreated(contextPayload) { WI.networkManager.executionContextCreated(contextPayload); } }; /* Protocol/ScriptProfilerObserver.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 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.ScriptProfilerObserver = class ScriptProfilerObserver extends InspectorBackend.Dispatcher { // Events defined by the "ScriptProfiler" domain. trackingStart(timestamp) { WI.timelineManager.scriptProfilerTrackingStarted(timestamp); } trackingUpdate(event) { WI.timelineManager.scriptProfilerTrackingUpdated(event); } trackingComplete(timestamp, samples) { WI.timelineManager.scriptProfilerTrackingCompleted(timestamp, samples); } programmaticCaptureStarted() { // COMPATIBILITY (iOS 12.2): ScriptProfiler.programmaticCaptureStarted was removed after iOS 12.2. } programmaticCaptureStopped() { // COMPATIBILITY (iOS 12.2): ScriptProfiler.programmaticCaptureStopped was removed after iOS 12.2. } }; /* Protocol/TargetObserver.js */ /* * Copyright (C) 2018 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.TargetObserver = class TargetObserver extends InspectorBackend.Dispatcher { // Events defined by the "Target" domain. targetCreated(targetInfo) { WI.targetManager.targetCreated(this._target, targetInfo); } didCommitProvisionalTarget(oldTargetId, newTargetId) { WI.targetManager.didCommitProvisionalTarget(this._target, oldTargetId, newTargetId); } targetDestroyed(targetId) { WI.targetManager.targetDestroyed(targetId); } dispatchMessageFromTarget(targetId, message) { WI.targetManager.dispatchMessageFromTarget(targetId, message); } }; /* Protocol/TimelineObserver.js */ /* * Copyright (C) 2013, 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 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.TimelineObserver = class TimelineObserver extends InspectorBackend.Dispatcher { // Events defined by the "Timeline" domain. eventRecorded(record) { WI.timelineManager.eventRecorded(record); } recordingStarted(startTime) { WI.timelineManager.capturingStarted(startTime); } recordingStopped(endTime) { WI.timelineManager.capturingStopped(endTime); } autoCaptureStarted() { WI.timelineManager.autoCaptureStarted(); } programmaticCaptureStarted() { // COMPATIBILITY (iOS 12.2): Timeline.programmaticCaptureStarted was removed after iOS 12.2. } programmaticCaptureStopped() { // COMPATIBILITY (iOS 12.2): Timeline.programmaticCaptureStopped was removed after iOS 12.2. } }; /* Protocol/WorkerObserver.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.WorkerObserver = class WorkerObserver extends InspectorBackend.Dispatcher { // Events defined by the "Worker" domain. workerCreated(workerId, url, name) { WI.workerManager.workerCreated(this._target, workerId, url, name); } workerTerminated(workerId) { WI.workerManager.workerTerminated(workerId); } dispatchMessageFromWorker(workerId, message) { WI.workerManager.dispatchMessageFromWorker(workerId, message); } }; /* Models/Breakpoint.js */ /* * Copyright (C) 2013, 2014 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.Breakpoint = class Breakpoint extends WI.Object { constructor({disabled, condition, actions, ignoreCount, autoContinue} = {}) { console.assert(!disabled || typeof disabled === "boolean", disabled); console.assert(!condition || typeof condition === "string", condition); console.assert(!actions || Array.isArray(actions), actions); console.assert(!ignoreCount || !isNaN(ignoreCount), ignoreCount); console.assert(!autoContinue || typeof autoContinue === "boolean", autoContinue); super(); // This class should not be instantiated directly. Create a concrete subclass instead. console.assert(this.constructor !== WI.Breakpoint && this instanceof WI.Breakpoint); console.assert(this.constructor.ReferencePage, "Should have a link to a reference page."); this._disabled = disabled || false; this._condition = condition || ""; this._ignoreCount = ignoreCount || 0; this._autoContinue = autoContinue || false; this._actions = actions || []; for (let action of this._actions) action.addEventListener(WI.BreakpointAction.Event.Modified, this._handleBreakpointActionModified, this); } // Import / Export toJSON(key) { let json = {}; if (this._disabled) json.disabled = this._disabled; if (this.editable) { if (this._condition) json.condition = this._condition; if (this._ignoreCount) json.ignoreCount = this._ignoreCount; if (this._actions.length) json.actions = this._actions.map((action) => action.toJSON()); if (this._autoContinue) json.autoContinue = this._autoContinue; } return json; } // Public get displayName() { throw WI.NotImplementedError.subclassMustOverride(); } get special() { // Overridden by subclasses if needed. return false; } get removable() { // Overridden by subclasses if needed. return true; } get editable() { // Overridden by subclasses if needed. return false; } get resolved() { // Overridden by subclasses if needed. return WI.debuggerManager.breakpointsEnabled; } get disabled() { return this._disabled; } set disabled(disabled) { if (this._disabled === disabled) return; this._disabled = disabled || false; this.dispatchEventToListeners(WI.Breakpoint.Event.DisabledStateDidChange); } get condition() { return this._condition; } set condition(condition) { console.assert(this.editable, this); console.assert(typeof condition === "string"); if (this._condition === condition) return; this._condition = condition; this.dispatchEventToListeners(WI.Breakpoint.Event.ConditionDidChange); } get ignoreCount() { console.assert(this.editable, this); return this._ignoreCount; } set ignoreCount(ignoreCount) { console.assert(this.editable, this); console.assert(ignoreCount >= 0, "Ignore count cannot be negative."); if (ignoreCount < 0) return; if (this._ignoreCount === ignoreCount) return; this._ignoreCount = ignoreCount; this.dispatchEventToListeners(WI.Breakpoint.Event.IgnoreCountDidChange); } get autoContinue() { console.assert(this.editable, this); return this._autoContinue; } set autoContinue(cont) { console.assert(this.editable, this); if (this._autoContinue === cont) return; this._autoContinue = cont; this.dispatchEventToListeners(WI.Breakpoint.Event.AutoContinueDidChange); } get actions() { console.assert(this.editable, this); return this._actions; } get probeActions() { console.assert(this.editable, this); return this._actions.filter(function(action) { return action.type === WI.BreakpointAction.Type.Probe; }); } addAction(action, {precedingAction} = {}) { console.assert(this.editable, this); console.assert(action instanceof WI.BreakpointAction, action); action.addEventListener(WI.BreakpointAction.Event.Modified, this._handleBreakpointActionModified, this); if (!precedingAction) this._actions.push(action); else { var index = this._actions.indexOf(precedingAction); console.assert(index !== -1); if (index === -1) this._actions.push(action); else this._actions.splice(index + 1, 0, action); } this.dispatchEventToListeners(WI.Breakpoint.Event.ActionsDidChange); } removeAction(action) { console.assert(this.editable, this); console.assert(action instanceof WI.BreakpointAction, action); var index = this._actions.indexOf(action); console.assert(index !== -1); if (index === -1) return; this._actions.splice(index, 1); action.removeEventListener(WI.BreakpointAction.Event.Modified, this._handleBreakpointActionModified, this); if (!this._actions.length) this.autoContinue = false; this.dispatchEventToListeners(WI.Breakpoint.Event.ActionsDidChange); } clearActions(type) { console.assert(this.editable, this); if (!type) this._actions = []; else this._actions = this._actions.filter(function(action) { return action.type !== type; }); this.dispatchEventToListeners(WI.Breakpoint.Event.ActionsDidChange); } reset() { console.assert(this.editable, this); this.condition = ""; this.ignoreCount = 0; this.autoContinue = false; this.clearActions(); } remove() { console.assert(this.removable, this); // Overridden by subclasses if needed. } optionsToProtocol() { console.assert(this.editable, this); let payload = {}; if (this._condition) payload.condition = this._condition; if (this._actions.length) { payload.actions = this._actions.map((action) => action.toProtocol()).filter((action) => { if (action.type !== WI.BreakpointAction.Type.Log) return true; if (!/\$\{.*?\}/.test(action.data)) return true; let lexer = new WI.BreakpointLogMessageLexer; let tokens = lexer.tokenize(action.data); if (!tokens) return false; let templateLiteral = tokens.reduce((text, token) => { if (token.type === WI.BreakpointLogMessageLexer.TokenType.PlainText) return text + token.data.escapeCharacters("`\\"); if (token.type === WI.BreakpointLogMessageLexer.TokenType.Expression) return text + "${" + token.data + "}"; return text; }, ""); action.data = "console.log(`" + templateLiteral + "`)"; action.type = WI.BreakpointAction.Type.Evaluate; return true; }); } if (this._autoContinue) payload.autoContinue = this._autoContinue; if (this._ignoreCount) payload.ignoreCount = this._ignoreCount; return !isEmptyObject(payload) ? payload : undefined; } // Private _handleBreakpointActionModified(event) { console.assert(this.editable, this); this.dispatchEventToListeners(WI.Breakpoint.Event.ActionsDidChange); } }; WI.Breakpoint.TypeIdentifier = "breakpoint"; WI.Breakpoint.Event = { DisabledStateDidChange: "breakpoint-disabled-state-did-change", ConditionDidChange: "breakpoint-condition-did-change", IgnoreCountDidChange: "breakpoint-ignore-count-did-change", ActionsDidChange: "breakpoint-actions-did-change", AutoContinueDidChange: "breakpoint-auto-continue-did-change", }; /* Models/BreakpointAction.js */ /* * Copyright (C) 2013, 2014 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.BreakpointAction = class BreakpointAction extends WI.Object { constructor(type, {data, emulateUserGesture} = {}) { console.assert(Object.values(WI.BreakpointAction.Type).includes(type), type); console.assert(!data || typeof data === "string", data); super(); this._type = type; this._data = data || null; this._id = WI.debuggerManager.nextBreakpointActionIdentifier(); this._emulateUserGesture = !!emulateUserGesture; } // Static static supportsEmulateUserAction() { // COMPATIBILITY (iOS 14): the `emulateUserGesture` property of `Debugger.BreakpointAction` did not exist yet. // Since support can't be tested directly, check for the `options` parameter of `Debugger.setPauseOnExceptions`. // FIXME: Use explicit version checking once is fixed. return WI.sharedApp.isWebDebuggable() && InspectorBackend.hasCommand("Debugger.setPauseOnExceptions", "options"); } // Import / Export static fromJSON(json) { return new WI.BreakpointAction(json.type, { data: json.data, emulateUserGesture: json.emulateUserGesture, }); } toJSON() { let json = { type: this._type, }; if (this._data) json.data = this._data; if (this._emulateUserGesture) json.emulateUserGesture = this._emulateUserGesture; return json; } // Public get id() { return this._id; } get type() { return this._type; } set type(type) { console.assert(Object.values(WI.BreakpointAction.Type).includes(type), type); if (type === this._type) return; this._type = type; this.dispatchEventToListeners(WI.BreakpointAction.Event.Modified); } get data() { return this._data; } set data(data) { console.assert(!data || typeof data === "string", data); if (this._data === data) return; this._data = data; this.dispatchEventToListeners(WI.BreakpointAction.Event.Modified); } get emulateUserGesture() { return this._emulateUserGesture; } set emulateUserGesture(emulateUserGesture) { if (this._emulateUserGesture === emulateUserGesture) return; this._emulateUserGesture = emulateUserGesture; this.dispatchEventToListeners(WI.BreakpointAction.Event.Modified); } toProtocol() { let json = this.toJSON(); json.id = this._id; return json; } }; WI.BreakpointAction.Type = { Log: "log", Evaluate: "evaluate", Sound: "sound", Probe: "probe" }; WI.BreakpointAction.Event = { Modified: "breakpoint-action-modified", }; /* Models/Collection.js */ /* * Copyright (C) 2016 Devin Rousso . 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.Collection = class Collection extends WI.Object { constructor(items = []) { super(); this._items = new Set; for (let item of items) this.add(item); } // Public get size() { return this._items.size; } get displayName() { throw WI.NotImplementedError.subclassMustOverride(); } objectIsRequiredType(object) { throw WI.NotImplementedError.subclassMustOverride(); } add(item) { let isValidType = this.objectIsRequiredType(item); console.assert(isValidType); if (!isValidType) return; console.assert(!this._items.has(item)); this._items.add(item); this.itemAdded(item); this.dispatchEventToListeners(WI.Collection.Event.ItemAdded, {item}); } remove(item) { let wasRemoved = this._items.delete(item); console.assert(wasRemoved); this.itemRemoved(item); this.dispatchEventToListeners(WI.Collection.Event.ItemRemoved, {item}); } has(...args) { return this._items.has(...args); } clear() { let items = new Set(this._items); this._items.clear(); this.itemsCleared(items); for (let item of items) this.dispatchEventToListeners(WI.Collection.Event.ItemRemoved, {item}); } toJSON() { return Array.from(this); } [Symbol.iterator]() { return this._items[Symbol.iterator](); } // Protected itemAdded(item) { // Implemented by subclasses. } itemRemoved(item) { // Implemented by subclasses. } itemsCleared(items) { // Implemented by subclasses. } }; WI.Collection.Event = { ItemAdded: "collection-item-added", ItemRemoved: "collection-item-removed", }; /* Models/ConsoleMessage.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 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.ConsoleMessage = class ConsoleMessage { constructor(target, source, level, message, type, url, line, column, repeatCount, parameters, stackTrace, request, timestamp) { console.assert(target instanceof WI.Target); console.assert(typeof source === "string"); console.assert(typeof level === "string"); console.assert(typeof message === "string"); console.assert(!type || Object.values(WI.ConsoleMessage.MessageType).includes(type)); console.assert(!parameters || parameters.every((x) => x instanceof WI.RemoteObject)); console.assert(!stackTrace || stackTrace instanceof WI.StackTrace, stackTrace); console.assert(!timestamp || !isNaN(timestamp), timestamp); this._target = target; this._source = source; this._level = level; this._messageText = message; this._type = type || WI.ConsoleMessage.MessageType.Log; this._url = url || null; this._line = line || 0; this._column = column || 0; this._sourceCodeLocation = undefined; this._repeatCount = repeatCount || 0; this._parameters = parameters; this._stackTrace = stackTrace || null; this._request = request; this._timestamp = timestamp ?? NaN; } // Public get target() { return this._target; } get source() { return this._source; } get level() { return this._level; } get messageText() { return this._messageText; } get type() { return this._type; } get url() { return this._url; } get line() { return this._line; } get column() { return this._column; } get repeatCount() { return this._repeatCount; } get parameters() { return this._parameters; } get stackTrace() { return this._stackTrace; } get request() { return this._request; } get timestamp() { return this._timestamp; } get sourceCodeLocation() { if (this._sourceCodeLocation !== undefined) return this._sourceCodeLocation; // First try to get the location from the top frame of the stack trace. let topCallFrame = this._stackTrace?.callFrames[0]; if (topCallFrame && topCallFrame.sourceCodeLocation) { this._sourceCodeLocation = topCallFrame.sourceCodeLocation; return this._sourceCodeLocation; } // If that doesn't exist try to get a location from the url/line/column in the ConsoleMessage. // FIXME : Remove the string equality checks for undefined once we don't get that value anymore. if (this._url && this._url !== "undefined") { let sourceCode = WI.networkManager.resourcesForURL(this._url).firstValue; if (sourceCode) { let lineNumber = this._line > 0 ? this._line - 1 : 0; let columnNumber = this._column > 0 ? this._column - 1 : 0; this._sourceCodeLocation = new WI.SourceCodeLocation(sourceCode, lineNumber, columnNumber); return this._sourceCodeLocation; } } this._sourceCodeLocation = null; return this._sourceCodeLocation; } }; WI.ConsoleMessage.MessageSource = { HTML: "html", XML: "xml", JS: "javascript", Network: "network", ConsoleAPI: "console-api", Storage: "storage", Appcache: "appcache", Rendering: "rendering", CSS: "css", Security: "security", Media: "media", MediaSource: "mediasource", WebRTC: "webrtc", ITPDebug: "itp-debug", PrivateClickMeasurement: "private-click-measurement", PaymentRequest: "payment-request", Other: "other", // COMPATIBILITY (iOS 14.0): `Console.ChannelSource.AdClickAttribution` was renamed to `Console.ChannelSource.PrivateClickMeasurement`. AdClickAttribution: "ad-click-attribution", }; WI.ConsoleMessage.MessageType = { Log: "log", Dir: "dir", DirXML: "dirxml", Table: "table", Trace: "trace", StartGroup: "startGroup", StartGroupCollapsed: "startGroupCollapsed", EndGroup: "endGroup", Assert: "assert", Timing: "timing", Profile: "profile", ProfileEnd: "profileEnd", Image: "image", Result: "result", // Frontend Only. }; WI.ConsoleMessage.MessageLevel = { Log: "log", Info: "info", Warning: "warning", Error: "error", Debug: "debug", }; /* Models/Instrument.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 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.Instrument = class Instrument { // Static static createForTimelineType(type) { switch (type) { case WI.TimelineRecord.Type.Network: return new WI.NetworkInstrument; case WI.TimelineRecord.Type.Layout: return new WI.LayoutInstrument; case WI.TimelineRecord.Type.Script: return new WI.ScriptInstrument; case WI.TimelineRecord.Type.RenderingFrame: return new WI.FPSInstrument; case WI.TimelineRecord.Type.CPU: return new WI.CPUInstrument; case WI.TimelineRecord.Type.Memory: return new WI.MemoryInstrument; case WI.TimelineRecord.Type.HeapAllocations: return new WI.HeapAllocationsInstrument; case WI.TimelineRecord.Type.Media: return new WI.MediaInstrument; case WI.TimelineRecord.Type.Screenshots: return new WI.ScreenshotsInstrument; default: console.error("Unknown TimelineRecord.Type: " + type); return null; } } static startLegacyTimelineAgent(initiatedByBackend) { console.assert(WI.timelineManager._enabled); if (WI.Instrument._legacyTimelineAgentStarted) return; WI.Instrument._legacyTimelineAgentStarted = true; if (initiatedByBackend) return; let target = WI.assumingMainTarget(); target.TimelineAgent.start(); } static stopLegacyTimelineAgent(initiatedByBackend) { console.assert(WI.timelineManager._enabled); if (!WI.Instrument._legacyTimelineAgentStarted) return; WI.Instrument._legacyTimelineAgentStarted = false; if (initiatedByBackend) return; let target = WI.assumingMainTarget(); target.TimelineAgent.stop(); } // Protected get timelineRecordType() { return null; // Implemented by subclasses. } startInstrumentation(initiatedByBackend) { WI.Instrument.startLegacyTimelineAgent(initiatedByBackend); } stopInstrumentation(initiatedByBackend) { WI.Instrument.stopLegacyTimelineAgent(initiatedByBackend); } }; WI.Instrument._legacyTimelineAgentStarted = false; /* Models/SourceCode.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. */ WI.SourceCode = class SourceCode extends WI.Object { constructor(url) { super(); this._url = url; this._urlComponents = null; this._originalRevision = new WI.SourceCodeRevision(this); this._currentRevision = this._originalRevision; this._sourceMaps = null; this._formatterSourceMap = null; this._requestContentPromise = null; } // Static static generateSpecialContentForURL(url) { if (url === "about:blank") { return Promise.resolve({ content: "", message: WI.unlocalizedString("about:blank") }); } return null; } // Public get displayName() { // Implemented by subclasses. console.error("Needs to be implemented by a subclass."); return ""; } get originalRevision() { return this._originalRevision; } get currentRevision() { return this._currentRevision; } set currentRevision(revision) { console.assert(revision instanceof WI.SourceCodeRevision); if (!(revision instanceof WI.SourceCodeRevision)) return; console.assert(revision.sourceCode === this); if (revision.sourceCode !== this) return; this._currentRevision = revision; this.dispatchEventToListeners(WI.SourceCode.Event.ContentDidChange); } get editableRevision() { if (this._currentRevision === this._originalRevision) this._currentRevision = this._originalRevision.copy(); return this._currentRevision; } get content() { return this._currentRevision.content; } get base64Encoded() { return this._currentRevision.base64Encoded; } get url() { return this._url; } get urlComponents() { if (!this._urlComponents) this._urlComponents = parseURL(this._url); return this._urlComponents; } get contentIdentifier() { // A contentIdentifier is roughly `url || sourceURL` for cases where // the content is consistent between sessions and not ephemeral. // Can be overridden by subclasses if better behavior is possible. return this.url; } get isScript() { // Implemented by subclasses if needed. return false; } get supportsScriptBlackboxing() { if (!this.isScript) return false; if (!WI.DebuggerManager.supportsBlackboxingScripts()) return false; let contentIdentifier = this.contentIdentifier; return contentIdentifier && !isWebKitInjectedScript(contentIdentifier); } get localResourceOverride() { // Overridden by subclasses if needed. return null; } get sourceMaps() { return this._sourceMaps || []; } addSourceMap(sourceMap) { console.assert(sourceMap instanceof WI.SourceMap); if (!this._sourceMaps) this._sourceMaps = []; this._sourceMaps.push(sourceMap); this.dispatchEventToListeners(WI.SourceCode.Event.SourceMapAdded); } get formatterSourceMap() { return this._formatterSourceMap; } set formatterSourceMap(formatterSourceMap) { console.assert(this._formatterSourceMap === null || formatterSourceMap === null); console.assert(formatterSourceMap === null || formatterSourceMap instanceof WI.FormatterSourceMap); this._formatterSourceMap = formatterSourceMap; this.dispatchEventToListeners(WI.SourceCode.Event.FormatterDidChange); } requestContent() { this._requestContentPromise = this._requestContentPromise || this.requestContentFromBackend().then(this._processContent.bind(this)); return this._requestContentPromise; } createSourceCodeLocation(lineNumber, columnNumber) { return new WI.SourceCodeLocation(this, lineNumber, columnNumber); } createLazySourceCodeLocation(lineNumber, columnNumber) { return new WI.LazySourceCodeLocation(this, lineNumber, columnNumber); } createSourceCodeTextRange(textRange) { return new WI.SourceCodeTextRange(this, textRange); } // Protected revisionContentDidChange(revision) { if (this._ignoreRevisionContentDidChangeEvent) return; console.assert(revision === this._currentRevision); if (revision !== this._currentRevision) return; this.handleCurrentRevisionContentChange(); this.dispatchEventToListeners(WI.SourceCode.Event.ContentDidChange); } handleCurrentRevisionContentChange() { // Implemented by subclasses if needed. } get revisionForRequestedContent() { // Implemented by subclasses if needed. return this._originalRevision; } markContentAsStale() { this._requestContentPromise = null; this._contentReceived = false; } requestContentFromBackend() { // Implemented by subclasses. console.error("Needs to be implemented by a subclass."); return Promise.reject(new Error("Needs to be implemented by a subclass.")); } get mimeType() { // Implemented by subclasses. console.error("Needs to be implemented by a subclass."); } // Private _processContent(parameters) { // Different backend APIs return one of `content, `body`, `text`, or `scriptSource`. let rawContent = parameters.content || parameters.body || parameters.text || parameters.scriptSource; let rawBase64Encoded = !!parameters.base64Encoded; let content = rawContent; let error = parameters.error; let message = parameters.message; if (parameters.base64Encoded) content = content ? WI.BlobUtilities.decodeBase64ToBlob(content, this.mimeType) : ""; let revision = this.revisionForRequestedContent; this._ignoreRevisionContentDidChangeEvent = true; revision.updateRevisionContent(rawContent, { base64Encoded: rawBase64Encoded, mimeType: this.mimeType, blobContent: content instanceof Blob ? content : null, }); this._ignoreRevisionContentDidChangeEvent = false; // FIXME: Returning the content in this promise is misleading. It may not be current content // now, and it may become out-dated later on. We should drop content from this promise // and require clients to ask for the current contents from the sourceCode in the result. // That would also avoid confusion around `content` being a Blob and eliminate the work // of creating the Blob if it is not used. return Promise.resolve({ error, message, sourceCode: this, content, rawContent, rawBase64Encoded, }); } }; WI.SourceCode.Event = { ContentDidChange: "source-code-content-did-change", SourceMapAdded: "source-code-source-map-added", FormatterDidChange: "source-code-formatter-did-change", LoadingDidFinish: "source-code-loading-did-finish", LoadingDidFail: "source-code-loading-did-fail" }; /* Models/SourceCodeLocation.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. */ WI.SourceCodeLocation = class SourceCodeLocation extends WI.Object { constructor(sourceCode, lineNumber, columnNumber) { super(); console.assert(sourceCode === null || sourceCode instanceof WI.SourceCode); console.assert(!(sourceCode instanceof WI.SourceMapResource)); console.assert(typeof lineNumber === "number" && !isNaN(lineNumber) && lineNumber >= 0); console.assert(typeof columnNumber === "number" && !isNaN(columnNumber) && columnNumber >= 0); this._sourceCode = sourceCode || null; this._lineNumber = lineNumber; this._columnNumber = columnNumber; this._resolveFormattedLocation(); if (this._sourceCode) { this._sourceCode.addEventListener(WI.SourceCode.Event.SourceMapAdded, this._sourceCodeSourceMapAdded, this); this._sourceCode.addEventListener(WI.SourceCode.Event.FormatterDidChange, this._sourceCodeFormatterDidChange, this); } this._resetMappedLocation(); } // Static static get specialBreakpointLocation() { return new WI.SourceCodeLocation(null, Infinity, Infinity); } // Public isEqual(other) { if (!other) return false; if (this.lineNumber !== other.lineNumber) return false; if (this.columnNumber !== other.columnNumber) return false; function resolveSourceCode(sourceCode) { if (sourceCode instanceof WI.Script) return sourceCode.resource; return sourceCode; } let thisSourceCode = resolveSourceCode(this.sourceCode); let otherSourceCode = resolveSourceCode(other.sourceCode); if (thisSourceCode !== otherSourceCode) return false; return true; } get sourceCode() { return this._sourceCode; } set sourceCode(sourceCode) { this.setSourceCode(sourceCode); } // Raw line and column in the original source code. get lineNumber() { return this._lineNumber; } get columnNumber() { return this._columnNumber; } position() { return new WI.SourceCodePosition(this.lineNumber, this.columnNumber); } // Formatted line and column if the original source code is pretty printed. // This is the same as the raw location if there is no formatter. get formattedLineNumber() { return this._formattedLineNumber; } get formattedColumnNumber() { return this._formattedColumnNumber; } formattedPosition() { return new WI.SourceCodePosition(this.formattedLineNumber, this.formattedColumnNumber); } // Display line and column: // - Mapped line and column if the original source code has a source map. // - Otherwise this is the formatted / raw line and column. get displaySourceCode() { this.resolveMappedLocation(); return this._mappedResource || this._sourceCode; } get displayLineNumber() { this.resolveMappedLocation(); return isNaN(this._mappedLineNumber) ? this._formattedLineNumber : this._mappedLineNumber; } get displayColumnNumber() { this.resolveMappedLocation(); return isNaN(this._mappedColumnNumber) ? this._formattedColumnNumber : this._mappedColumnNumber; } displayPosition() { return new WI.SourceCodePosition(this.displayLineNumber, this.displayColumnNumber); } // User presentable location strings: "file:lineNumber:columnNumber". originalLocationString(columnStyle, nameStyle, prefix) { return this._locationString(this.sourceCode, this.lineNumber, this.columnNumber, columnStyle, nameStyle, prefix); } formattedLocationString(columnStyle, nameStyle, prefix) { return this._locationString(this.sourceCode, this.formattedLineNumber, this.formattedColumn, columnStyle, nameStyle, prefix); } displayLocationString(columnStyle, nameStyle, prefix) { return this._locationString(this.displaySourceCode, this.displayLineNumber, this.displayColumnNumber, columnStyle, nameStyle, prefix); } tooltipString() { if (!this.hasDifferentDisplayLocation()) return this.originalLocationString(WI.SourceCodeLocation.ColumnStyle.Shown, WI.SourceCodeLocation.NameStyle.Full); var tooltip = WI.UIString("Located at %s").format(this.displayLocationString(WI.SourceCodeLocation.ColumnStyle.Shown, WI.SourceCodeLocation.NameStyle.Full)); tooltip += "\n" + WI.UIString("Originally %s").format(this.originalLocationString(WI.SourceCodeLocation.ColumnStyle.Shown, WI.SourceCodeLocation.NameStyle.Full)); return tooltip; } hasMappedLocation() { this.resolveMappedLocation(); return this._mappedResource !== null; } hasFormattedLocation() { return this._formattedLineNumber !== this._lineNumber || this._formattedColumnNumber !== this._columnNumber; } hasDifferentDisplayLocation() { return this.hasMappedLocation() || this.hasFormattedLocation(); } update(sourceCode, lineNumber, columnNumber) { console.assert(sourceCode === this._sourceCode || (this._mappedResource && sourceCode === this._mappedResource)); console.assert(typeof lineNumber === "number" && !isNaN(lineNumber) && lineNumber >= 0); console.assert(typeof columnNumber === "number" && !isNaN(columnNumber) && columnNumber >= 0); if (sourceCode === this._sourceCode && lineNumber === this._lineNumber && columnNumber === this._columnNumber) return; if (this._mappedResource && sourceCode === this._mappedResource && lineNumber === this._mappedLineNumber && columnNumber === this._mappedColumnNumber) return; var newSourceCodeLocation = sourceCode.createSourceCodeLocation(lineNumber, columnNumber); console.assert(newSourceCodeLocation.sourceCode === this._sourceCode); this._makeChangeAndDispatchChangeEventIfNeeded(function() { this._lineNumber = newSourceCodeLocation._lineNumber; this._columnNumber = newSourceCodeLocation._columnNumber; if (newSourceCodeLocation._mappedLocationIsResolved) { this._mappedLocationIsResolved = true; this._mappedResource = newSourceCodeLocation._mappedResource; this._mappedLineNumber = newSourceCodeLocation._mappedLineNumber; this._mappedColumnNumber = newSourceCodeLocation._mappedColumnNumber; } }); } populateLiveDisplayLocationTooltip(element, prefix, suffix) { prefix = prefix || ""; suffix = suffix || ""; element.title = prefix + this.tooltipString() + suffix; this.addEventListener(WI.SourceCodeLocation.Event.DisplayLocationChanged, function(event) { if (this.sourceCode) element.title = prefix + this.tooltipString() + suffix; }, this); } populateLiveDisplayLocationString(element, propertyName, columnStyle, nameStyle, prefix) { var currentDisplay; function updateDisplayString(showAlternativeLocation, forceUpdate) { if (!forceUpdate && currentDisplay === showAlternativeLocation) return; currentDisplay = showAlternativeLocation; if (!showAlternativeLocation) { element[propertyName] = this.displayLocationString(columnStyle, nameStyle, prefix); element.classList.toggle(WI.SourceCodeLocation.DisplayLocationClassName, this.hasDifferentDisplayLocation()); } else if (this.hasDifferentDisplayLocation()) { element[propertyName] = this.originalLocationString(columnStyle, nameStyle, prefix); element.classList.remove(WI.SourceCodeLocation.DisplayLocationClassName); } } function mouseOverOrMove(event) { updateDisplayString.call(this, event.metaKey && !event.altKey && !event.shiftKey); } updateDisplayString.call(this, false); this.addEventListener(WI.SourceCodeLocation.Event.DisplayLocationChanged, function(event) { if (this.sourceCode) updateDisplayString.call(this, currentDisplay, true); }, this); var boundMouseOverOrMove = mouseOverOrMove.bind(this); element.addEventListener("mouseover", boundMouseOverOrMove); element.addEventListener("mousemove", boundMouseOverOrMove); element.addEventListener("mouseout", (event) => { updateDisplayString.call(this, false); }); } // Protected setSourceCode(sourceCode) { console.assert((this._sourceCode === null && sourceCode instanceof WI.SourceCode) || (this._sourceCode instanceof WI.SourceCode && sourceCode === null)); if (sourceCode === this._sourceCode) return; this._makeChangeAndDispatchChangeEventIfNeeded(function() { if (this._sourceCode) { this._sourceCode.removeEventListener(WI.SourceCode.Event.SourceMapAdded, this._sourceCodeSourceMapAdded, this); this._sourceCode.removeEventListener(WI.SourceCode.Event.FormatterDidChange, this._sourceCodeFormatterDidChange, this); } this._sourceCode = sourceCode; if (this._sourceCode) { this._sourceCode.addEventListener(WI.SourceCode.Event.SourceMapAdded, this._sourceCodeSourceMapAdded, this); this._sourceCode.addEventListener(WI.SourceCode.Event.FormatterDidChange, this._sourceCodeFormatterDidChange, this); } }); } resolveMappedLocation() { if (this._mappedLocationIsResolved) return; console.assert(this._mappedResource === null); console.assert(isNaN(this._mappedLineNumber)); console.assert(isNaN(this._mappedColumnNumber)); this._mappedLocationIsResolved = true; if (!this._sourceCode) return; var sourceMaps = this._sourceCode.sourceMaps; if (!sourceMaps.length) return; for (var i = 0; i < sourceMaps.length; ++i) { var sourceMap = sourceMaps[i]; var entry = sourceMap.findEntry(this._lineNumber, this._columnNumber); if (!entry || entry.length === 2) continue; console.assert(entry.length === 5); var url = entry[2]; var sourceMapResource = sourceMap.resourceForURL(url); if (!sourceMapResource) return; this._mappedResource = sourceMapResource; this._mappedLineNumber = entry[3]; this._mappedColumnNumber = entry[4]; return; } } // Private _locationString(sourceCode, lineNumber, columnNumber, columnStyle, nameStyle, prefix) { console.assert(sourceCode); if (!sourceCode) return ""; columnStyle = columnStyle || WI.SourceCodeLocation.ColumnStyle.OnlyIfLarge; nameStyle = nameStyle || WI.SourceCodeLocation.NameStyle.Short; prefix = prefix || ""; let lineString = lineNumber + 1; // The user visible line number is 1-based. if (columnStyle === WI.SourceCodeLocation.ColumnStyle.Shown && columnNumber > 0) lineString += ":" + (columnNumber + 1); // The user visible column number is 1-based. else if (columnStyle === WI.SourceCodeLocation.ColumnStyle.OnlyIfLarge && columnNumber > WI.SourceCodeLocation.LargeColumnNumber) lineString += ":" + (columnNumber + 1); // The user visible column number is 1-based. else if (columnStyle === WI.SourceCodeLocation.ColumnStyle.Hidden) lineString = ""; switch (nameStyle) { case WI.SourceCodeLocation.NameStyle.None: return prefix + lineString; case WI.SourceCodeLocation.NameStyle.Short: case WI.SourceCodeLocation.NameStyle.Full: var displayURL = sourceCode.displayURL; var name = nameStyle === WI.SourceCodeLocation.NameStyle.Full && displayURL ? displayURL : sourceCode.displayName; if (columnStyle === WI.SourceCodeLocation.ColumnStyle.Hidden) return prefix + name; var lineSuffix = displayURL ? ":" + lineString : WI.UIString(" (line %s)").format(lineString); return prefix + name + lineSuffix; default: console.error("Unknown nameStyle: " + nameStyle); return prefix + lineString; } } _resetMappedLocation() { this._mappedLocationIsResolved = false; this._mappedResource = null; this._mappedLineNumber = NaN; this._mappedColumnNumber = NaN; } _setMappedLocation(mappedResource, mappedLineNumber, mappedColumnNumber) { // Called by SourceMapResource when it creates a SourceCodeLocation and already knows the resolved location. this._mappedLocationIsResolved = true; this._mappedResource = mappedResource; this._mappedLineNumber = mappedLineNumber; this._mappedColumnNumber = mappedColumnNumber; } _resolveFormattedLocation() { if (this._sourceCode && this._sourceCode.formatterSourceMap) { var formattedLocation = this._sourceCode.formatterSourceMap.originalToFormatted(this._lineNumber, this._columnNumber); this._formattedLineNumber = formattedLocation.lineNumber; this._formattedColumnNumber = formattedLocation.columnNumber; } else { this._formattedLineNumber = this._lineNumber; this._formattedColumnNumber = this._columnNumber; } } _makeChangeAndDispatchChangeEventIfNeeded(changeFunction) { var oldSourceCode = this._sourceCode; var oldLineNumber = this._lineNumber; var oldColumnNumber = this._columnNumber; var oldFormattedLineNumber = this._formattedLineNumber; var oldFormattedColumnNumber = this._formattedColumnNumber; var oldDisplaySourceCode = this.displaySourceCode; var oldDisplayLineNumber = this.displayLineNumber; var oldDisplayColumnNumber = this.displayColumnNumber; this._resetMappedLocation(); if (changeFunction) changeFunction.call(this); this.resolveMappedLocation(); this._resolveFormattedLocation(); // If the display source code is non-null then the addresses are not NaN and can be compared. var displayLocationChanged = false; var newDisplaySourceCode = this.displaySourceCode; if (oldDisplaySourceCode !== newDisplaySourceCode) displayLocationChanged = true; else if (newDisplaySourceCode && (oldDisplayLineNumber !== this.displayLineNumber || oldDisplayColumnNumber !== this.displayColumnNumber)) displayLocationChanged = true; var anyLocationChanged = false; if (displayLocationChanged) anyLocationChanged = true; else if (oldSourceCode !== this._sourceCode) anyLocationChanged = true; else if (this._sourceCode && (oldLineNumber !== this._lineNumber || oldColumnNumber !== this._columnNumber)) anyLocationChanged = true; else if (this._sourceCode && (oldFormattedLineNumber !== this._formattedLineNumber || oldFormattedColumnNumber !== this._formattedColumnNumber)) anyLocationChanged = true; if (displayLocationChanged || anyLocationChanged) { var oldData = { oldSourceCode, oldLineNumber, oldColumnNumber, oldFormattedLineNumber, oldFormattedColumnNumber, oldDisplaySourceCode, oldDisplayLineNumber, oldDisplayColumnNumber }; if (displayLocationChanged) this.dispatchEventToListeners(WI.SourceCodeLocation.Event.DisplayLocationChanged, oldData); if (anyLocationChanged) this.dispatchEventToListeners(WI.SourceCodeLocation.Event.LocationChanged, oldData); } } _sourceCodeSourceMapAdded() { this._makeChangeAndDispatchChangeEventIfNeeded(null); } _sourceCodeFormatterDidChange() { this._makeChangeAndDispatchChangeEventIfNeeded(null); } }; WI.SourceCodeLocation.DisplayLocationClassName = "display-location"; WI.SourceCodeLocation.LargeColumnNumber = 80; WI.SourceCodeLocation.NameStyle = { None: "none", // File name not included. Short: "short", // Only the file name. Full: "full" // Full URL is used. }; WI.SourceCodeLocation.ColumnStyle = { Hidden: "hidden", // line and column numbers are not included. OnlyIfLarge: "only-if-large", // column numbers greater than 80 are shown. Shown: "shown" // non-zero column numbers are shown. }; WI.SourceCodeLocation.Event = { LocationChanged: "source-code-location-location-changed", DisplayLocationChanged: "source-code-location-display-location-changed" }; /* Models/SourceCodePosition.js */ /* * Copyright (C) 2013-2018 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.SourceCodePosition = class SourceCodePosition { constructor(lineNumber, columnNumber) { this._lineNumber = lineNumber || 0; this._columnNumber = columnNumber || 0; } // Public get lineNumber() { return this._lineNumber; } get columnNumber() { return this._columnNumber; } offsetColumn(delta) { console.assert(this._columnNumber + delta >= 0); return new WI.SourceCodePosition(this._lineNumber, this._columnNumber + delta); } equals(position) { return this._lineNumber === position.lineNumber && this._columnNumber === position.columnNumber; } isBefore(position) { if (this._lineNumber < position.lineNumber) return true; if (this._lineNumber === position.lineNumber && this._columnNumber < position.columnNumber) return true; return false; } isAfter(position) { if (this._lineNumber > position.lineNumber) return true; if (this._lineNumber === position.lineNumber && this._columnNumber > position.columnNumber) return true; return false; } isWithin(startPosition, endPosition) { console.assert(startPosition.isBefore(endPosition) || startPosition.equals(endPosition)); if (this.equals(startPosition) || this.equals(endPosition)) return true; if (this.isAfter(startPosition) && this.isBefore(endPosition)) return true; return false; } toCodeMirror() { return {line: this._lineNumber, ch: this._columnNumber}; } }; /* Models/Timeline.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. */ WI.Timeline = class Timeline extends WI.Object { constructor(type) { super(); this._type = type; this.reset(true); } // Static static create(type) { if (type === WI.TimelineRecord.Type.Network) return new WI.NetworkTimeline(type); if (type === WI.TimelineRecord.Type.CPU) return new WI.CPUTimeline(type); if (type === WI.TimelineRecord.Type.Memory) return new WI.MemoryTimeline(type); if (type === WI.TimelineRecord.Type.Media) return new WI.MediaTimeline(type); return new WI.Timeline(type); } // Public get type() { return this._type; } get startTime() { return this._startTime; } get endTime() { return this._endTime; } get records() { return this._records; } reset(suppressEvents) { this._records = []; this._startTime = NaN; this._endTime = NaN; if (!suppressEvents) { this.dispatchEventToListeners(WI.Timeline.Event.TimesUpdated); this.dispatchEventToListeners(WI.Timeline.Event.Reset); } } addRecord(record, options = {}) { if (record.updatesDynamically) record.addEventListener(WI.TimelineRecord.Event.Updated, this._recordUpdated, this); // Because records can be nested, it is possible that outer records with an early start time // may be completed and added to the Timeline after inner records with a later start time // were already added. In most cases this is a small drift, so make an effort to still keep // the list sorted. Do it now, when inserting, so if the timeline is visible it has the // best chance of being as accurate as possible during a recording. this._tryInsertingRecordInSortedOrder(record); this._updateTimesIfNeeded(record); this.dispatchEventToListeners(WI.Timeline.Event.RecordAdded, {record}); } saveIdentityToCookie(cookie) { cookie[WI.Timeline.TimelineTypeCookieKey] = this._type; } refresh() { this.dispatchEventToListeners(WI.Timeline.Event.Refreshed); } closestRecordTo(timestamp) { let lowerIndex = this._records.lowerBound(timestamp, (time, record) => time - record.endTime); let recordBefore = this._records[lowerIndex - 1]; let recordAfter = this._records[lowerIndex]; if (!recordBefore && !recordAfter) return null; if (!recordBefore && recordAfter) return recordAfter; if (!recordAfter && recordBefore) return recordBefore; let before = Math.abs(recordBefore.endTime - timestamp); let after = Math.abs(recordAfter.startTime - timestamp); return (before < after) ? recordBefore : recordAfter; } recordsInTimeRange(startTime, endTime, {includeRecordBeforeStart, includeRecordAfterEnd} = {}) { let lowerIndex = this._records.lowerBound(startTime, (time, record) => time - record.endTime); if (includeRecordBeforeStart && lowerIndex > 0) { lowerIndex--; // If the record right before is a child of the same type of record, then use the parent as the before index. let recordBefore = this._records[lowerIndex]; if (recordBefore.parent && recordBefore.parent.type === recordBefore.type) { lowerIndex--; while (this._records[lowerIndex] !== recordBefore.parent) lowerIndex--; } } let upperIndex = this._records.upperBound(endTime, (time, record) => time - record.startTime); if (includeRecordAfterEnd && upperIndex < this._records.length) ++upperIndex; return this._records.slice(lowerIndex, upperIndex); } // Private _updateTimesIfNeeded(record) { let changed = false; // Some records adjust their start time / end time to values that may be before // or after the bounds the recording actually ran. Use the unadjusted times for // the Timeline's bounds. Otherwise we may extend the timeline graphs to a time // that was conceptually before / after the user started / stopping recording. let recordStartTime = record.unadjustedStartTime; let recordEndTime = record.unadjustedEndTime; if (isNaN(this._startTime) || recordStartTime < this._startTime) { this._startTime = recordStartTime; changed = true; } if (isNaN(this._endTime) || this._endTime < recordEndTime) { this._endTime = recordEndTime; changed = true; } if (changed) this.dispatchEventToListeners(WI.Timeline.Event.TimesUpdated); } _recordUpdated(event) { this._updateTimesIfNeeded(event.target); } _tryInsertingRecordInSortedOrder(record) { // Fast case add to the end. let lastValue = this._records.lastValue; if (!lastValue || lastValue.startTime < record.startTime || record.updatesDynamically) { this._records.push(record); return; } // Slow case, try to insert in the last 20 records. let start = this._records.length - 2; let end = Math.max(this._records.length - 20, 0); for (let i = start; i >= end; --i) { if (this._records[i].startTime < record.startTime) { this._records.insertAtIndex(record, i + 1); return; } } // Give up and add to the end. this._records.push(record); } }; WI.Timeline.Event = { Reset: "timeline-reset", RecordAdded: "timeline-record-added", TimesUpdated: "timeline-times-updated", Refreshed: "timeline-refreshed", }; WI.Timeline.TimelineTypeCookieKey = "timeline-type"; /* Models/TimelineRange.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.TimelineRange = class TimelineRange { constructor(startValue, endValue) { this._startValue = startValue; this._endValue = endValue; } get startValue() { return this._startValue; } set startValue(x) { this._startValue = x; } get endValue() { return this._endValue; } set endValue(x) { this._endValue = x; } }; /* Models/TimelineRecord.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. */ WI.TimelineRecord = class TimelineRecord extends WI.Object { constructor(type, startTime, endTime, stackTrace, sourceCodeLocation) { super(); console.assert(type); if (type in WI.TimelineRecord.Type) type = WI.TimelineRecord.Type[type]; this._type = type; this._startTime = startTime || NaN; this._endTime = endTime || NaN; this._stackTrace = stackTrace || null; this._sourceCodeLocation = sourceCodeLocation || null; this._children = []; } // Import / Export static async fromJSON(json) { switch (json.type) { case WI.TimelineRecord.Type.Network: return WI.ResourceTimelineRecord.fromJSON(json); case WI.TimelineRecord.Type.Layout: return WI.LayoutTimelineRecord.fromJSON(json); case WI.TimelineRecord.Type.Script: return WI.ScriptTimelineRecord.fromJSON(json); case WI.TimelineRecord.Type.RenderingFrame: return WI.RenderingFrameTimelineRecord.fromJSON(json); case WI.TimelineRecord.Type.CPU: return WI.CPUTimelineRecord.fromJSON(json); case WI.TimelineRecord.Type.Memory: return WI.MemoryTimelineRecord.fromJSON(json); case WI.TimelineRecord.Type.HeapAllocations: return WI.HeapAllocationsTimelineRecord.fromJSON(json); case WI.TimelineRecord.Type.Media: return WI.MediaTimelineRecord.fromJSON(json); case WI.TimelineRecord.Type.Screenshots: return WI.ScreenshotsTimelineRecord.fromJSON(json); default: console.error("Unknown TimelineRecord.Type: " + json.type, json); return null; } } toJSON() { throw WI.NotImplementedError.subclassMustOverride(); } // Public get type() { return this._type; } get startTime() { // Implemented by subclasses if needed. return this._startTime; } get activeStartTime() { // Implemented by subclasses if needed. return this.startTime; } get unadjustedStartTime() { // Overridden by subclasses if needed. return this.startTime; } get endTime() { // Implemented by subclasses if needed. return this._endTime; } get unadjustedEndTime() { // Overridden by subclasses if needed. return this.endTime; } get duration() { // Use the getters instead of the properties so this works for subclasses that override the getters. return this.endTime - this.startTime; } get inactiveDuration() { // Use the getters instead of the properties so this works for subclasses that override the getters. return this.activeStartTime - this.startTime; } get activeDuration() { // Use the getters instead of the properties so this works for subclasses that override the getters. return this.endTime - this.activeStartTime; } get updatesDynamically() { // Implemented by subclasses if needed. return false; } get usesActiveStartTime() { // Implemented by subclasses if needed. return false; } get stackTrace() { return this._stackTrace; } get initiatorCallFrame() { if (!this._stackTrace) return null; // Return the first non-native code call frame as the initiator. for (let frame of this._stackTrace.callFrames) { if (!frame.nativeCode) return frame; } return null; } get sourceCodeLocation() { return this._sourceCodeLocation; } get parent() { return this._parent; } set parent(x) { if (this._parent === x) return; this._parent = x; } get children() { return this._children; } saveIdentityToCookie(cookie) { cookie[WI.TimelineRecord.SourceCodeURLCookieKey] = this._sourceCodeLocation ? this._sourceCodeLocation.sourceCode.url ? this._sourceCodeLocation.sourceCode.url.hash : null : null; cookie[WI.TimelineRecord.SourceCodeLocationLineCookieKey] = this._sourceCodeLocation ? this._sourceCodeLocation.lineNumber : null; cookie[WI.TimelineRecord.SourceCodeLocationColumnCookieKey] = this._sourceCodeLocation ? this._sourceCodeLocation.columnNumber : null; cookie[WI.TimelineRecord.TypeCookieKey] = this._type || null; } }; WI.TimelineRecord.Event = { Updated: "timeline-record-updated" }; WI.TimelineRecord.Type = { Network: "timeline-record-type-network", Layout: "timeline-record-type-layout", Script: "timeline-record-type-script", RenderingFrame: "timeline-record-type-rendering-frame", CPU: "timeline-record-type-cpu", Memory: "timeline-record-type-memory", HeapAllocations: "timeline-record-type-heap-allocations", Media: "timeline-record-type-media", Screenshots: "timeline-record-type-screenshots", }; WI.TimelineRecord.TypeIdentifier = "timeline-record"; WI.TimelineRecord.SourceCodeURLCookieKey = "timeline-record-source-code-url"; WI.TimelineRecord.SourceCodeLocationLineCookieKey = "timeline-record-source-code-location-line"; WI.TimelineRecord.SourceCodeLocationColumnCookieKey = "timeline-record-source-code-location-column"; WI.TimelineRecord.TypeCookieKey = "timeline-record-type"; /* Models/Resource.js */ /* * Copyright (C) 2013 Apple Inc. All rights reserved. * Copyright (C) 2011 Google 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.Resource = class Resource extends WI.SourceCode { constructor(url, {mimeType, type, loaderIdentifier, targetId, requestIdentifier, requestMethod, requestHeaders, requestData, requestSentTimestamp, requestSentWalltime, referrerPolicy, integrity, initiatorStackTrace, initiatorSourceCodeLocation, initiatorNode} = {}) { console.assert(url); console.assert(!initiatorStackTrace || initiatorStackTrace instanceof WI.StackTrace, initiatorStackTrace); super(url); if (type in WI.Resource.Type) type = WI.Resource.Type[type]; else if (type === "Stylesheet") { // COMPATIBILITY (iOS 13): Page.ResourceType.Stylesheet was renamed to Page.ResourceType.StyleSheet. type = WI.Resource.Type.StyleSheet; } this._mimeType = mimeType; this._mimeTypeComponents = null; this._type = Resource.resolvedType(type, mimeType); this._loaderIdentifier = loaderIdentifier || null; this._requestIdentifier = requestIdentifier || null; this._queryStringParameters = undefined; this._requestFormParameters = undefined; this._requestMethod = requestMethod || null; this._requestData = requestData || null; this._requestHeaders = requestHeaders || {}; this._responseHeaders = {}; this._requestCookies = null; this._responseCookies = null; this._serverTimingEntries = null; this._parentFrame = null; this._initiatorStackTrace = initiatorStackTrace || null; this._initiatorSourceCodeLocation = initiatorSourceCodeLocation || null; this._initiatorNode = initiatorNode || null; this._initiatedResources = []; this._requestSentTimestamp = requestSentTimestamp || NaN; this._requestSentWalltime = requestSentWalltime || NaN; this._responseReceivedTimestamp = NaN; this._lastDataReceivedTimestamp = NaN; this._finishedOrFailedTimestamp = NaN; this._finishThenRequestContentPromise = null; this._statusCode = NaN; this._statusText = null; this._cached = false; this._canceled = false; this._finished = false; this._failed = false; this._failureReasonText = null; this._receivedNetworkLoadMetrics = false; this._responseSource = WI.Resource.ResponseSource.Unknown; this._security = null; this._timingData = new WI.ResourceTimingData(this); this._protocol = null; this._priority = WI.Resource.NetworkPriority.Unknown; this._remoteAddress = null; this._connectionIdentifier = null; this._isProxyConnection = false; this._target = targetId ? WI.targetManager.targetForIdentifier(targetId) : WI.mainTarget; this._redirects = []; this._referrerPolicy = referrerPolicy ?? null; this._integrity = integrity ?? null; // Exact sizes if loaded over the network or cache. this._requestHeadersTransferSize = NaN; this._requestBodyTransferSize = NaN; this._responseHeadersTransferSize = NaN; this._responseBodyTransferSize = NaN; this._responseBodySize = NaN; this._cachedResponseBodySize = NaN; // Estimated sizes (if backend does not provide metrics). this._estimatedSize = NaN; this._estimatedTransferSize = NaN; this._estimatedResponseHeadersSize = NaN; if (this._initiatorSourceCodeLocation && this._initiatorSourceCodeLocation.sourceCode instanceof WI.Resource) this._initiatorSourceCodeLocation.sourceCode.addInitiatedResource(this); } // Static static resolvedType(type, mimeType) { if (type && type !== WI.Resource.Type.Other) return type; return Resource.typeFromMIMEType(mimeType); } static typeFromMIMEType(mimeType) { if (!mimeType) return WI.Resource.Type.Other; mimeType = parseMIMEType(mimeType).type; if (mimeType in WI.Resource._mimeTypeMap) return WI.Resource._mimeTypeMap[mimeType]; if (mimeType.startsWith("image/")) return WI.Resource.Type.Image; if (mimeType.startsWith("font/")) return WI.Resource.Type.Font; return WI.Resource.Type.Other; } static displayNameForType(type, plural) { switch (type) { case WI.Resource.Type.Document: if (plural) return WI.UIString("Documents"); return WI.UIString("Document"); case WI.Resource.Type.StyleSheet: if (plural) return WI.UIString("Style Sheets"); return WI.UIString("Style Sheet"); case WI.Resource.Type.Image: if (plural) return WI.UIString("Images"); return WI.UIString("Image"); case WI.Resource.Type.Font: if (plural) return WI.UIString("Fonts"); return WI.UIString("Font"); case WI.Resource.Type.Script: if (plural) return WI.UIString("Scripts"); return WI.UIString("Script"); case WI.Resource.Type.XHR: if (plural) return WI.UIString("XHRs"); return WI.UIString("XHR"); case WI.Resource.Type.Fetch: if (plural) return WI.UIString("Fetches", "Resources loaded via 'fetch' method"); return WI.repeatedUIString.fetch(); case WI.Resource.Type.Ping: if (plural) return WI.UIString("Pings"); return WI.UIString("Ping"); case WI.Resource.Type.Beacon: if (plural) return WI.UIString("Beacons"); return WI.UIString("Beacon"); case WI.Resource.Type.WebSocket: if (plural) return WI.UIString("Sockets"); return WI.UIString("Socket"); case WI.Resource.Type.EventSource: if (plural) return WI.UIString("EventSources", "Display name for the type of network requests sent via EventSource(s) API (https://developer.mozilla.org/en-US/docs/Web/API/EventSource)"); return WI.UIString("EventSource", "Display name for the type of network requests sent via EventSource API (https://developer.mozilla.org/en-US/docs/Web/API/EventSource)"); case WI.Resource.Type.Other: return WI.UIString("Other"); default: console.error("Unknown resource type", type); return null; } } static classNamesForResource(resource) { let classes = []; let localResourceOverride = resource.localResourceOverride || WI.networkManager.localResourceOverridesForURL(resource.url).filter((localResourceOverride) => !localResourceOverride.disabled)[0]; let isOverride = !!resource.localResourceOverride; let wasOverridden = resource.responseSource === WI.Resource.ResponseSource.InspectorOverride; let shouldBeOverridden = resource.isLoading() && localResourceOverride; let shouldBeBlocked = (resource.failed || isOverride) && localResourceOverride?.type === WI.LocalResourceOverride.InterceptType.Block; if (isOverride || wasOverridden || shouldBeOverridden || shouldBeBlocked) { classes.push("override"); if (shouldBeBlocked || localResourceOverride?.type === WI.LocalResourceOverride.InterceptType.ResponseSkippingNetwork) classes.push("skip-network"); if (localResourceOverride?.localResource.mappedFilePath) classes.push("mapped-file"); } if (resource.type === WI.Resource.Type.Other) { if (resource.requestedByteRange) classes.push("resource-type-range"); } else classes.push(resource.type); return classes; } static displayNameForProtocol(protocol) { switch (protocol) { case "h2": return "HTTP/2"; case "http/1.0": return "HTTP/1.0"; case "http/1.1": return "HTTP/1.1"; case "spdy/2": return "SPDY/2"; case "spdy/3": return "SPDY/3"; case "spdy/3.1": return "SPDY/3.1"; default: return null; } } static comparePriority(a, b) { console.assert(typeof a === "symbol"); console.assert(typeof b === "symbol"); const map = { [WI.Resource.NetworkPriority.Unknown]: 0, [WI.Resource.NetworkPriority.Low]: 1, [WI.Resource.NetworkPriority.Medium]: 2, [WI.Resource.NetworkPriority.High]: 3, }; let aNum = map[a] || 0; let bNum = map[b] || 0; return aNum - bNum; } static displayNameForPriority(priority) { switch (priority) { case WI.Resource.NetworkPriority.Low: return WI.UIString("Low", "Low @ Network Priority", "Low network request priority"); case WI.Resource.NetworkPriority.Medium: return WI.UIString("Medium", "Medium @ Network Priority", "Medium network request priority"); case WI.Resource.NetworkPriority.High: return WI.UIString("High", "High @ Network Priority", "High network request priority"); default: return null; } } static responseSourceFromPayload(source) { if (!source) return WI.Resource.ResponseSource.Unknown; switch (source) { case InspectorBackend.Enum.Network.ResponseSource.Unknown: return WI.Resource.ResponseSource.Unknown; case InspectorBackend.Enum.Network.ResponseSource.Network: return WI.Resource.ResponseSource.Network; case InspectorBackend.Enum.Network.ResponseSource.MemoryCache: return WI.Resource.ResponseSource.MemoryCache; case InspectorBackend.Enum.Network.ResponseSource.DiskCache: return WI.Resource.ResponseSource.DiskCache; case InspectorBackend.Enum.Network.ResponseSource.ServiceWorker: return WI.Resource.ResponseSource.ServiceWorker; case InspectorBackend.Enum.Network.ResponseSource.InspectorOverride: return WI.Resource.ResponseSource.InspectorOverride; default: console.error("Unknown response source type", source); return WI.Resource.ResponseSource.Unknown; } } static networkPriorityFromPayload(priority) { switch (priority) { case InspectorBackend.Enum.Network.MetricsPriority.Low: return WI.Resource.NetworkPriority.Low; case InspectorBackend.Enum.Network.MetricsPriority.Medium: return WI.Resource.NetworkPriority.Medium; case InspectorBackend.Enum.Network.MetricsPriority.High: return WI.Resource.NetworkPriority.High; default: console.error("Unknown metrics priority", priority); return WI.Resource.NetworkPriority.Unknown; } } static connectionIdentifierFromPayload(connectionIdentifier) { // Map backend connection identifiers to an easier to read number. if (!WI.Resource.connectionIdentifierMap) { WI.Resource.connectionIdentifierMap = new Map; WI.Resource.nextConnectionIdentifier = 1; } let id = WI.Resource.connectionIdentifierMap.get(connectionIdentifier); if (id) return id; id = WI.Resource.nextConnectionIdentifier++; WI.Resource.connectionIdentifierMap.set(connectionIdentifier, id); return id; } // Public get mimeType() { return this._mimeType; } get target() { return this._target; } get type() { return this._type; } get loaderIdentifier() { return this._loaderIdentifier; } get requestIdentifier() { return this._requestIdentifier; } get requestMethod() { return this._requestMethod; } get requestData() { return this._requestData; } get initiatorStackTrace() { return this._initiatorStackTrace; } get initiatorSourceCodeLocation() { return this._initiatorSourceCodeLocation; } get initiatorNode() { return this._initiatorNode; } get initiatedResources() { return this._initiatedResources; } get statusCode() { return this._statusCode; } get statusText() { return this._statusText; } get responseSource() { return this._responseSource; } get security() { return this._security; } get timingData() { return this._timingData; } get protocol() { return this._protocol; } get priority() { return this._priority; } get remoteAddress() { return this._remoteAddress; } get connectionIdentifier() { return this._connectionIdentifier; } get parentFrame() { return this._parentFrame; } get finished() { return this._finished; } get failed() { return this._failed; } get canceled() { return this._canceled; } get failureReasonText() { return this._failureReasonText; } get requestHeaders() { return this._requestHeaders; } get responseHeaders() { return this._responseHeaders; } get requestSentTimestamp() { return this._requestSentTimestamp; } get requestSentWalltime() { return this._requestSentWalltime; } get responseReceivedTimestamp() { return this._responseReceivedTimestamp; } get lastDataReceivedTimestamp() { return this._lastDataReceivedTimestamp; } get finishedOrFailedTimestamp() { return this._finishedOrFailedTimestamp; } get cached() { return this._cached; } get requestHeadersTransferSize() { return this._requestHeadersTransferSize; } get requestBodyTransferSize() { return this._requestBodyTransferSize; } get responseHeadersTransferSize() { return this._responseHeadersTransferSize; } get responseBodyTransferSize() { return this._responseBodyTransferSize; } get cachedResponseBodySize() { return this._cachedResponseBodySize; } get redirects() { return this._redirects; } get referrerPolicy() { return this._referrerPolicy; } get integrity() { return this._integrity; } get loadedSecurely() { if (this.urlComponents.scheme !== "https" && this.urlComponents.scheme !== "wss" && this.urlComponents.scheme !== "sftp") return false; if (isNaN(this._timingData.secureConnectionStart) && !isNaN(this._timingData.connectionStart)) return false; return true; } get isScript() { return this._type === Resource.Type.Script; } get supportsScriptBlackboxing() { if (this.localResourceOverride) return false; if (!this.finished || this.failed) return false; return super.supportsScriptBlackboxing; } get displayName() { return WI.displayNameForURL(this._url, this.urlComponents); } get displayURL() { const isMultiLine = true; const dataURIMaxSize = 64; return WI.truncateURL(this._url, isMultiLine, dataURIMaxSize); } get displayRemoteAddress() { if (this._isProxyConnection) return WI.UIString("%s (Proxy)", "%s (Proxy) @ Resource Remote Address", "Label for the IP address of a proxy server used to retrieve a network resource.").format(this._remoteAddress); return this._remoteAddress; } get mimeTypeComponents() { if (!this._mimeTypeComponents) this._mimeTypeComponents = parseMIMEType(this._mimeType); return this._mimeTypeComponents; } get syntheticMIMEType() { // Resources are often transferred with a MIME-type that doesn't match the purpose the // resource was loaded for, which is what WI.Resource.Type represents. // This getter generates a MIME-type, if needed, that matches the resource type. // If the type matches the Resource.Type of the MIME-type, then return the actual MIME-type. if (this._type === WI.Resource.typeFromMIMEType(this._mimeType)) return this._mimeType; // Return the default MIME-types for the Resource.Type, since the current MIME-type // does not match what is expected for the Resource.Type. switch (this._type) { case WI.Resource.Type.StyleSheet: return "text/css"; case WI.Resource.Type.Script: return "text/javascript"; } // Return the actual MIME-type since we don't have a better synthesized one to return. return this._mimeType; } get hasMetadata() { // Some metadata is only collected when Web Inspector is open (e.g. resource timing data, HTTP method, request headers, etc.). // Use `_requestIdentifier` as a general signal since it is always included when metadata is collected. return !!this._requestIdentifier; } createObjectURL() { let revision = this.currentRevision; let blobContent = revision.blobContent; if (blobContent) return URL.createObjectURL(blobContent) // If content is not available, fallback to using original URL. // The client may try to revoke it, but nothing will happen. return this._url; } isMainResource() { return this._parentFrame ? this._parentFrame.mainResource === this : false; } addInitiatedResource(resource) { if (!(resource instanceof WI.Resource)) return; this._initiatedResources.push(resource); this.dispatchEventToListeners(WI.Resource.Event.InitiatedResourcesDidChange); } get queryStringParameters() { if (this._queryStringParameters === undefined) this._queryStringParameters = parseQueryString(this.urlComponents.queryString, true); return this._queryStringParameters; } get requestFormParameters() { if (this._requestFormParameters === undefined) this._requestFormParameters = this.hasRequestFormParameters() ? parseQueryString(this.requestData, true) : null; return this._requestFormParameters; } get requestDataContentType() { return this._requestHeaders.valueForCaseInsensitiveKey("Content-Type") || null; } get requestCookies() { if (!this._requestCookies) this._requestCookies = WI.Cookie.parseCookieRequestHeader(this._requestHeaders.valueForCaseInsensitiveKey("Cookie")); return this._requestCookies; } get responseCookies() { if (!this._responseCookies) { // FIXME: The backend sends multiple "Set-Cookie" headers in one "Set-Cookie" with multiple values // separated by ", ". This doesn't allow us to safely distinguish between a ", " that separates // multiple headers or one that may be valid part of a Cookie's value or attribute, such as the // ", " in the the date format "Expires=Tue, 03-Oct-2017 04:39:21 GMT". To improve heuristics // we do a negative lookahead for numbers, but we can still fail on cookie values containing ", ". let rawCombinedHeader = this._responseHeaders.valueForCaseInsensitiveKey("Set-Cookie") || ""; let setCookieHeaders = rawCombinedHeader.split(/, (?![0-9])/); let cookies = []; for (let header of setCookieHeaders) { let cookie = WI.Cookie.parseSetCookieResponseHeader(header); if (cookie) cookies.push(cookie); } this._responseCookies = cookies; } return this._responseCookies; } get requestSentDate() { return isNaN(this._requestSentWalltime) ? null : new Date(this._requestSentWalltime * 1000); } get lastRedirectReceivedTimestamp() { return this._redirects.length ? this._redirects.lastValue.timestamp : NaN; } get firstTimestamp() { return this.timingData.startTime || this.lastRedirectReceivedTimestamp || this.responseReceivedTimestamp || this.lastDataReceivedTimestamp || this.finishedOrFailedTimestamp; } get lastTimestamp() { return this.timingData.responseEnd || this.lastDataReceivedTimestamp || this.responseReceivedTimestamp || this.lastRedirectReceivedTimestamp || this.requestSentTimestamp; } get latency() { return this.timingData.responseStart - this.timingData.requestStart; } get receiveDuration() { return this.timingData.responseEnd - this.timingData.responseStart; } get totalDuration() { return this.timingData.responseEnd - this.timingData.startTime; } get size() { if (!isNaN(this._cachedResponseBodySize)) return this._cachedResponseBodySize; if (!isNaN(this._responseBodySize) && this._responseBodySize !== 0) return this._responseBodySize; return this._estimatedSize; } get networkEncodedSize() { return this._responseBodyTransferSize; } get networkDecodedSize() { return this._responseBodySize; } get networkTotalTransferSize() { return this._responseHeadersTransferSize + this._responseBodyTransferSize; } get estimatedNetworkEncodedSize() { let exact = this.networkEncodedSize; if (!isNaN(exact)) return exact; if (this._cached) return 0; // FIXME: Network: Correctly report encoded data length (transfer size) from CFNetwork to NetworkResourceLoader // macOS provides the decoded transfer size instead of the encoded size // for estimatedTransferSize. So prefer the "Content-Length" property // on mac if it is available. if (WI.Platform.name === "mac") { let contentLength = Number(this._responseHeaders.valueForCaseInsensitiveKey("Content-Length")); if (!isNaN(contentLength)) return contentLength; } if (!isNaN(this._estimatedTransferSize)) return this._estimatedTransferSize; // If we did not receive actual transfer size from network // stack, we prefer using Content-Length over resourceSize as // resourceSize may differ from actual transfer size if platform's // network stack performed decoding (e.g. gzip decompression). // The Content-Length, though, is expected to come from raw // response headers and will reflect actual transfer length. // This won't work for chunked content encoding, so fall back to // resourceSize when we don't have Content-Length. This still won't // work for chunks with non-trivial encodings. We need a way to // get actual transfer size from the network stack. return Number(this._responseHeaders.valueForCaseInsensitiveKey("Content-Length") || this._estimatedSize); } get estimatedTotalTransferSize() { let exact = this.networkTotalTransferSize; if (!isNaN(exact)) return exact; if (this.statusCode === 304) // Not modified return this._estimatedResponseHeadersSize; if (this._cached) return 0; return this._estimatedResponseHeadersSize + this.estimatedNetworkEncodedSize; } get compressed() { let contentEncoding = this._responseHeaders.valueForCaseInsensitiveKey("Content-Encoding"); return !!(contentEncoding && /\b(?:gzip|deflate|br)\b/.test(contentEncoding)); } get requestedByteRange() { let range = this._requestHeaders.valueForCaseInsensitiveKey("Range"); if (!range) return null; let rangeValues = range.match(/bytes=(\d+)-(\d+)/); if (!rangeValues) return null; let start = parseInt(rangeValues[1]); if (isNaN(start)) return null; let end = parseInt(rangeValues[2]); if (isNaN(end)) return null; return {start, end}; } get scripts() { return this._scripts || []; } get serverTiming() { if (!this._serverTimingEntries) this._serverTimingEntries = WI.ServerTimingEntry.parseHeaders(this._responseHeaders.valueForCaseInsensitiveKey("Server-Timing")); return this._serverTimingEntries; } scriptForLocation(sourceCodeLocation) { console.assert(!(this instanceof WI.SourceMapResource)); console.assert(sourceCodeLocation.sourceCode === this, "SourceCodeLocation must be in this Resource"); if (sourceCodeLocation.sourceCode !== this) return null; var lineNumber = sourceCodeLocation.lineNumber; var columnNumber = sourceCodeLocation.columnNumber; for (var i = 0; i < this._scripts.length; ++i) { var script = this._scripts[i]; if (script.range.startLine <= lineNumber && script.range.endLine >= lineNumber) { if (script.range.startLine === lineNumber && columnNumber < script.range.startColumn) continue; if (script.range.endLine === lineNumber && columnNumber > script.range.endColumn) continue; return script; } } return null; } updateForRedirectResponse(request, response, elapsedTime, walltime) { console.assert(!this._finished); console.assert(!this._failed); console.assert(!this._canceled); let oldURL = this._url; let oldHeaders = this._requestHeaders; let oldMethod = this._requestMethod; if (request.url) this._url = request.url; this._requestHeaders = request.headers || {}; this._requestCookies = null; this._requestMethod = request.method || null; this._redirects.push(new WI.Redirect(oldURL, oldMethod, oldHeaders, response.status, response.statusText, response.headers, elapsedTime)); this._referrerPolicy = request.referrerPolicy ?? null; this._integrity = request.integrity ?? null; if (oldURL !== request.url) { // Delete the URL components so the URL is re-parsed the next time it is requested. this._urlComponents = null; this.dispatchEventToListeners(WI.Resource.Event.URLDidChange, {oldURL}); } this.dispatchEventToListeners(WI.Resource.Event.RequestHeadersDidChange); this.dispatchEventToListeners(WI.Resource.Event.TimestampsDidChange); } hasResponse() { return !isNaN(this._statusCode) || this._finished || this._failed; } hasRequestFormParameters() { let requestDataContentType = this.requestDataContentType; return requestDataContentType && requestDataContentType.match(/^application\/x-www-form-urlencoded\s*(;.*)?$/i); } updateForResponse(url, mimeType, type, responseHeaders, statusCode, statusText, elapsedTime, timingData, source, security) { console.assert(!this._finished); console.assert(!this._failed); console.assert(!this._canceled); let oldURL = this._url; let oldMIMEType = this._mimeType; let oldType = this._type; if (type in WI.Resource.Type) type = WI.Resource.Type[type]; else if (type === "Stylesheet") { // COMPATIBILITY (iOS 13): Page.ResourceType.Stylesheet was renamed to Page.ResourceType.StyleSheet. type = WI.Resource.Type.StyleSheet; } if (url) this._url = url; this._mimeType = mimeType; this._type = Resource.resolvedType(type, mimeType); this._statusCode = statusCode; this._statusText = statusText; this._responseHeaders = responseHeaders || {}; this._responseCookies = null; this._serverTimingEntries = null; this._responseReceivedTimestamp = elapsedTime || NaN; this._timingData = WI.ResourceTimingData.fromPayload(timingData, this); if (source) this._responseSource = WI.Resource.responseSourceFromPayload(source); this._security = security || {}; const headerBaseSize = 12; // Length of "HTTP/1.1 ", " ", and "\r\n". const headerPad = 4; // Length of ": " and "\r\n". this._estimatedResponseHeadersSize = String(this._statusCode).length + this._statusText.length + headerBaseSize; for (let name in this._responseHeaders) this._estimatedResponseHeadersSize += name.length + this._responseHeaders[name].length + headerPad; if (!this._cached) { if (statusCode === 304 || (this._responseSource === WI.Resource.ResponseSource.MemoryCache || this._responseSource === WI.Resource.ResponseSource.DiskCache)) this.markAsCached(); } if (oldURL !== url) { // Delete the URL components so the URL is re-parsed the next time it is requested. this._urlComponents = null; this.dispatchEventToListeners(WI.Resource.Event.URLDidChange, {oldURL}); } if (oldMIMEType !== mimeType) { // Delete the MIME-type components so the MIME-type is re-parsed the next time it is requested. this._mimeTypeComponents = null; this.dispatchEventToListeners(WI.Resource.Event.MIMETypeDidChange, {oldMIMEType}); } if (oldType !== type) this.dispatchEventToListeners(WI.Resource.Event.TypeDidChange, {oldType}); console.assert(isNaN(this._estimatedSize)); console.assert(isNaN(this._estimatedTransferSize)); // The transferSize becomes 0 when status is 304 or Content-Length is available, so // notify listeners of that change. if (statusCode === 304 || this._responseHeaders.valueForCaseInsensitiveKey("Content-Length")) this.dispatchEventToListeners(WI.Resource.Event.TransferSizeDidChange); this.dispatchEventToListeners(WI.Resource.Event.ResponseReceived); this.dispatchEventToListeners(WI.Resource.Event.TimestampsDidChange); } updateWithMetrics(metrics) { this._receivedNetworkLoadMetrics = true; if (metrics.protocol) this._protocol = metrics.protocol; if (metrics.priority) this._priority = WI.Resource.networkPriorityFromPayload(metrics.priority); if (metrics.remoteAddress) this._remoteAddress = metrics.remoteAddress; if (metrics.connectionIdentifier) this._connectionIdentifier = WI.Resource.connectionIdentifierFromPayload(metrics.connectionIdentifier); if (metrics.requestHeaders) { this._requestHeaders = metrics.requestHeaders; this._requestCookies = null; this.dispatchEventToListeners(WI.Resource.Event.RequestHeadersDidChange); } if ("requestHeaderBytesSent" in metrics) { this._requestHeadersTransferSize = metrics.requestHeaderBytesSent; this._requestBodyTransferSize = metrics.requestBodyBytesSent; this._responseHeadersTransferSize = metrics.responseHeaderBytesReceived; this._responseBodyTransferSize = metrics.responseBodyBytesReceived; this._responseBodySize = metrics.responseBodyDecodedSize; console.assert(this._requestHeadersTransferSize >= 0); console.assert(this._requestBodyTransferSize >= 0); console.assert(this._responseHeadersTransferSize >= 0); console.assert(this._responseBodyTransferSize >= 0); console.assert(this._responseBodySize >= 0); // There may have been no size updates received during load if Content-Length was 0. if (isNaN(this._estimatedSize)) this._estimatedSize = 0; this.dispatchEventToListeners(WI.Resource.Event.SizeDidChange, {previousSize: this._estimatedSize}); this.dispatchEventToListeners(WI.Resource.Event.TransferSizeDidChange); } if (metrics.securityConnection) { if (!this._security) this._security = {}; this._security.connection = metrics.securityConnection; } this._isProxyConnection = !!metrics.isProxyConnection; this.dispatchEventToListeners(WI.Resource.Event.MetricsDidChange); } setCachedResponseBodySize(size) { console.assert(!isNaN(size), "Size should be a valid number."); console.assert(isNaN(this._cachedResponseBodySize), "This should only be set once."); console.assert(this._estimatedSize === size, "The legacy path was updated already and matches."); this._cachedResponseBodySize = size; } requestContentFromBackend() { let specialContentPromise = WI.SourceCode.generateSpecialContentForURL(this._url); if (specialContentPromise) return specialContentPromise; if (this._target.type === WI.TargetType.Worker) { console.assert(this.isScript); let scriptForTarget = this.scripts.find((script) => script.target === this._target); console.assert(scriptForTarget); if (scriptForTarget) return scriptForTarget.requestContentFromBackend(); } else { // If we have the requestIdentifier we can get the actual response for this specific resource. // Otherwise the content will be cached resource data, which might not exist anymore. if (this._requestIdentifier) return this._target.NetworkAgent.getResponseBody(this._requestIdentifier); // There is no request identifier or frame to request content from. if (this._parentFrame) return this._target.PageAgent.getResourceContent(this._parentFrame.id, this._url); } return Promise.reject(new Error("Content request failed.")); } increaseSize(dataLength, elapsedTime) { console.assert(dataLength >= 0); console.assert(!this._receivedNetworkLoadMetrics, "If we received metrics we don't need to change the estimated size."); if (isNaN(this._estimatedSize)) this._estimatedSize = 0; let previousSize = this._estimatedSize; this._estimatedSize += dataLength; this._lastDataReceivedTimestamp = elapsedTime || NaN; this.dispatchEventToListeners(WI.Resource.Event.SizeDidChange, {previousSize}); // The estimatedTransferSize is based off of size when status is not 304 or Content-Length is missing. if (isNaN(this._estimatedTransferSize) && this._statusCode !== 304 && !this._responseHeaders.valueForCaseInsensitiveKey("Content-Length")) this.dispatchEventToListeners(WI.Resource.Event.TransferSizeDidChange); } increaseTransferSize(encodedDataLength) { console.assert(encodedDataLength >= 0); console.assert(!this._receivedNetworkLoadMetrics, "If we received metrics we don't need to change the estimated transfer size."); if (isNaN(this._estimatedTransferSize)) this._estimatedTransferSize = 0; this._estimatedTransferSize += encodedDataLength; this.dispatchEventToListeners(WI.Resource.Event.TransferSizeDidChange); } markAsCached() { this._cached = true; this.dispatchEventToListeners(WI.Resource.Event.CacheStatusDidChange); // The transferSize starts returning 0 when cached is true, unless status is 304. if (this._statusCode !== 304) this.dispatchEventToListeners(WI.Resource.Event.TransferSizeDidChange); } markAsFinished(elapsedTime) { console.assert(!this._failed); console.assert(!this._canceled); this._finished = true; this._finishedOrFailedTimestamp = elapsedTime || NaN; this._timingData.markResponseEndTime(elapsedTime || NaN); if (this._finishThenRequestContentPromise) this._finishThenRequestContentPromise = null; this.dispatchEventToListeners(WI.Resource.Event.LoadingDidFinish); this.dispatchEventToListeners(WI.Resource.Event.TimestampsDidChange); } markAsFailed(canceled, elapsedTime, errorText) { console.assert(!this._finished); this._failed = true; this._canceled = canceled; this._finishedOrFailedTimestamp = elapsedTime || NaN; if (!this._failureReasonText) this._failureReasonText = errorText || null; this.dispatchEventToListeners(WI.Resource.Event.LoadingDidFail); this.dispatchEventToListeners(WI.Resource.Event.TimestampsDidChange); } revertMarkAsFinished() { console.assert(!this._failed); console.assert(!this._canceled); console.assert(this._finished); this._finished = false; this._finishedOrFailedTimestamp = NaN; } isLoading() { return !this._finished && !this._failed; } hadLoadingError() { return this._failed || this._canceled || this._statusCode >= 400; } getImageSize(callback) { // Throw an error in the case this resource is not an image. if (this.type !== WI.Resource.Type.Image) throw "Resource is not an image."; // See if we've already computed and cached the image size, // in which case we can provide them directly. if (this._imageSize !== undefined) { callback(this._imageSize); return; } var objectURL = null; // Event handler for the image "load" event. function imageDidLoad() { URL.revokeObjectURL(objectURL); // Cache the image metrics. this._imageSize = { width: image.width, height: image.height }; callback(this._imageSize); } function requestContentFailure() { this._imageSize = null; callback(this._imageSize); } // Create an element that we'll use to load the image resource // so that we can query its intrinsic size. var image = new Image; image.addEventListener("load", imageDidLoad.bind(this), false); // Set the image source using an object URL once we've obtained its data. this.requestContent().then((content) => { objectURL = image.src = content.sourceCode.createObjectURL(); if (!objectURL) requestContentFailure.call(this); }, requestContentFailure.bind(this)); } requestContent() { if (this._finished) return super.requestContent().catch(this._requestContentFailure.bind(this)); if (this._failed) return this._requestContentFailure(); if (!this._finishThenRequestContentPromise) { this._finishThenRequestContentPromise = new Promise((resolve, reject) => { this.singleFireEventListener(WI.Resource.Event.LoadingDidFinish, resolve, this); this.singleFireEventListener(WI.Resource.Event.LoadingDidFail, reject, this); }).then(this.requestContent.bind(this)); } return this._finishThenRequestContentPromise; } associateWithScript(script) { if (!this._scripts) this._scripts = []; this._scripts.push(script); if (this._type === WI.Resource.Type.Other || this._type === WI.Resource.Type.XHR) { let oldType = this._type; this._type = WI.Resource.Type.Script; this.dispatchEventToListeners(WI.Resource.Event.TypeDidChange, {oldType}); } } saveIdentityToCookie(cookie) { cookie[WI.Resource.URLCookieKey] = this.url.hash; cookie[WI.Resource.MainResourceCookieKey] = this.isMainResource(); } async createLocalResourceOverride(type, {mimeType, base64Encoded, content} = {}) { console.assert(!this.localResourceOverride); console.assert(WI.NetworkManager.supportsOverridingResponses()); let resourceData = { requestURL: this.url, }; switch (type) { case WI.LocalResourceOverride.InterceptType.Request: resourceData.requestMethod = this.requestMethod ?? WI.HTTPUtilities.RequestMethod.GET; resourceData.requestHeaders = Object.shallowCopy(this.requestHeaders); resourceData.requestData = this.requestData ?? ""; break; case WI.LocalResourceOverride.InterceptType.Response: case WI.LocalResourceOverride.InterceptType.ResponseSkippingNetwork: resourceData.responseMIMEType = this.mimeType ?? WI.mimeTypeForFileExtension(WI.fileExtensionForFilename(this.urlComponents.lastPathComponent)); resourceData.responseStatusCode = this.statusCode; resourceData.responseStatusText = this.statusText; if (!resourceData.responseStatusCode) { resourceData.responseStatusCode = 200; resourceData.responseStatusText = null; } resourceData.responseStatusText ||= WI.HTTPUtilities.statusTextForStatusCode(resourceData.responseStatusCode); if (base64Encoded === undefined || content === undefined) { try { let {rawContent, rawBase64Encoded} = await this.requestContent(); content ??= rawContent; base64Encoded ??= rawBase64Encoded; } catch { content ??= ""; base64Encoded ??= !WI.shouldTreatMIMETypeAsText(resourceData.mimeType); } } resourceData.responseContent = content; resourceData.responseBase64Encoded = base64Encoded; resourceData.responseHeaders = Object.shallowCopy(this.responseHeaders); break; } return WI.LocalResourceOverride.create(this.url, type, resourceData); } updateLocalResourceOverrideRequestData(data) { console.assert(this.localResourceOverride); if (data === this._requestData) return; this._requestData = data; this.dispatchEventToListeners(WI.Resource.Event.RequestDataDidChange); } generateFetchCode() { let options = {}; if (this.requestData) options.body = this.requestData; options.cache = "default"; options.credentials = (this.requestCookies.length || this._requestHeaders.valueForCaseInsensitiveKey("Authorization")) ? "include" : "omit"; // https://fetch.spec.whatwg.org/#forbidden-header-name const forbiddenHeaders = new Set([ "accept-charset", "accept-encoding", "access-control-request-headers", "access-control-request-method", "connection", "content-length", "cookie", "cookie2", "date", "dnt", "expect", "host", "keep-alive", "origin", "referer", "te", "trailer", "transfer-encoding", "upgrade", "via", ]); let headers = Object.entries(this.requestHeaders) .filter((header) => { let key = header[0].toLowerCase(); if (forbiddenHeaders.has(key)) return false; if (key.startsWith("proxy-") || key.startsWith("sec-")) return false; return true; }) .sort((a, b) => a[0].extendedLocaleCompare(b[0])) .reduce((accumulator, current) => { accumulator[current[0]] = current[1]; return accumulator; }, {}); if (!isEmptyObject(headers)) options.headers = headers; if (this._integrity) options.integrity = this._integrity; if (this.requestMethod) options.method = this.requestMethod; options.mode = "cors"; options.redirect = "follow"; let referrer = this.requestHeaders.valueForCaseInsensitiveKey("Referer"); if (referrer) options.referrer = referrer; if (this._referrerPolicy) options.referrerPolicy = this._referrerPolicy; return `fetch(${JSON.stringify(this.url)}, ${JSON.stringify(options, null, WI.indentString())})`; } generateCURLCommand() { function escapeStringPosix(str) { function escapeCharacter(x) { let code = x.charCodeAt(0); let hex = code.toString(16); if (code < 256) return "\\x" + hex.padStart(2, "0"); return "\\u" + hex.padStart(4, "0"); } if (/[^\x20-\x7E]|'/.test(str)) { // Use ANSI-C quoting syntax. return "$'" + str.replace(/\\/g, "\\\\") .replace(/'/g, "\\'") .replace(/\n/g, "\\n") .replace(/\r/g, "\\r") .replace(/!/g, "\\041") .replace(/[^\x20-\x7E]/g, escapeCharacter) + "'"; } // Use single quote syntax. return `'${str}'`; } let command = ["curl " + escapeStringPosix(this.url).replace(/[[{}\]]/g, "\\$&")]; command.push("-X " + escapeStringPosix(this.requestMethod)); for (let key in this.requestHeaders) command.push("-H " + escapeStringPosix(`${key}: ${this.requestHeaders[key]}`)); if (this.requestDataContentType && this.requestMethod !== "GET" && this.requestData) { if (this.requestDataContentType.match(/^application\/x-www-form-urlencoded\s*(;.*)?$/i)) command.push("--data " + escapeStringPosix(this.requestData)); else command.push("--data-binary " + escapeStringPosix(this.requestData)); } return command.join(" \\\n"); } stringifyHTTPRequest() { let lines = []; let protocol = this.protocol || ""; if (protocol === "h2") { // HTTP/2 Request pseudo headers: // https://tools.ietf.org/html/rfc7540#section-8.1.2.3 lines.push(`:method: ${this.requestMethod}`); lines.push(`:scheme: ${this.urlComponents.scheme}`); lines.push(`:authority: ${WI.h2Authority(this.urlComponents)}`); lines.push(`:path: ${WI.h2Path(this.urlComponents)}`); } else { // HTTP/1.1 request line: // https://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1 lines.push(`${this.requestMethod} ${this.urlComponents.path}${protocol ? " " + protocol.toUpperCase() : ""}`); } for (let key in this.requestHeaders) lines.push(`${key}: ${this.requestHeaders[key]}`); return lines.join("\n") + "\n"; } stringifyHTTPResponse() { let lines = []; let protocol = this.protocol || ""; if (protocol === "h2") { // HTTP/2 Response pseudo headers: // https://tools.ietf.org/html/rfc7540#section-8.1.2.4 lines.push(`:status: ${this.statusCode}`); } else { // HTTP/1.1 response status line: // https://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html#sec6.1 lines.push(`${protocol ? protocol.toUpperCase() + " " : ""}${this.statusCode} ${this.statusText}`); } for (let key in this.responseHeaders) lines.push(`${key}: ${this.responseHeaders[key]}`); return lines.join("\n") + "\n"; } async showCertificate() { let errorString = WI.UIString("Unable to show certificate for \u201C%s\u201D").format(this.url); try { let {serializedCertificate} = await this._target.NetworkAgent.getSerializedCertificate(this._requestIdentifier); if (InspectorFrontendHost.showCertificate(serializedCertificate)) return; } catch (e) { console.error(e); throw errorString; } let consoleMessage = new WI.ConsoleMessage(this._target, WI.ConsoleMessage.MessageSource.Other, WI.ConsoleMessage.MessageLevel.Error, errorString); consoleMessage.shouldRevealConsole = true; WI.consoleLogViewController.appendConsoleMessage(consoleMessage); throw errorString; } // Private _requestContentFailure(error) { return Promise.resolve({ error: WI.UIString("An error occurred trying to load the resource."), reason: error?.message || this._failureReasonText, sourceCode: this, }); } }; WI.Resource.TypeIdentifier = "resource"; WI.Resource.URLCookieKey = "resource-url"; WI.Resource.MainResourceCookieKey = "resource-is-main-resource"; WI.Resource.Event = { URLDidChange: "resource-url-did-change", MIMETypeDidChange: "resource-mime-type-did-change", TypeDidChange: "resource-type-did-change", RequestHeadersDidChange: "resource-request-headers-did-change", RequestDataDidChange: "resource-request-data-did-change", ResponseReceived: "resource-response-received", LoadingDidFinish: "resource-loading-did-finish", LoadingDidFail: "resource-loading-did-fail", TimestampsDidChange: "resource-timestamps-did-change", SizeDidChange: "resource-size-did-change", TransferSizeDidChange: "resource-transfer-size-did-change", CacheStatusDidChange: "resource-cached-did-change", MetricsDidChange: "resource-metrics-did-change", InitiatedResourcesDidChange: "resource-initiated-resources-did-change", }; // Keep these in sync with the "ResourceType" enum defined by the "Page" domain. WI.Resource.Type = { Document: "resource-type-document", StyleSheet: "resource-type-style-sheet", Image: "resource-type-image", Font: "resource-type-font", Script: "resource-type-script", XHR: "resource-type-xhr", Fetch: "resource-type-fetch", Ping: "resource-type-ping", Beacon: "resource-type-beacon", WebSocket: "resource-type-websocket", EventSource: "resource-type-eventsource", Other: "resource-type-other", }; WI.Resource.ResponseSource = { Unknown: Symbol("unknown"), Network: Symbol("network"), MemoryCache: Symbol("memory-cache"), DiskCache: Symbol("disk-cache"), ServiceWorker: Symbol("service-worker"), InspectorOverride: Symbol("inspector-override"), }; WI.Resource.NetworkPriority = { Unknown: Symbol("unknown"), Low: Symbol("low"), Medium: Symbol("medium"), High: Symbol("high"), }; WI.Resource.GroupingMode = { Path: "group-resource-by-path", Type: "group-resource-by-type", }; WI.settings.resourceGroupingMode = new WI.Setting("resource-grouping-mode", WI.Resource.GroupingMode.Type); // This MIME Type map is private, use WI.Resource.typeFromMIMEType(). WI.Resource._mimeTypeMap = { "text/html": WI.Resource.Type.Document, "text/xml": WI.Resource.Type.Document, "application/xhtml+xml": WI.Resource.Type.Document, "text/plain": WI.Resource.Type.Other, "text/css": WI.Resource.Type.StyleSheet, "text/xsl": WI.Resource.Type.StyleSheet, "text/x-less": WI.Resource.Type.StyleSheet, "text/x-sass": WI.Resource.Type.StyleSheet, "text/x-scss": WI.Resource.Type.StyleSheet, "application/pdf": WI.Resource.Type.Image, "image/svg+xml": WI.Resource.Type.Image, "application/x-font-type1": WI.Resource.Type.Font, "application/x-font-ttf": WI.Resource.Type.Font, "application/x-font-woff": WI.Resource.Type.Font, "application/x-truetype-font": WI.Resource.Type.Font, "text/javascript": WI.Resource.Type.Script, "text/ecmascript": WI.Resource.Type.Script, "application/javascript": WI.Resource.Type.Script, "application/ecmascript": WI.Resource.Type.Script, "application/x-javascript": WI.Resource.Type.Script, "application/json": WI.Resource.Type.Script, "application/x-json": WI.Resource.Type.Script, "text/x-javascript": WI.Resource.Type.Script, "text/x-json": WI.Resource.Type.Script, "text/javascript1.1": WI.Resource.Type.Script, "text/javascript1.2": WI.Resource.Type.Script, "text/javascript1.3": WI.Resource.Type.Script, "text/jscript": WI.Resource.Type.Script, "text/livescript": WI.Resource.Type.Script, "text/x-livescript": WI.Resource.Type.Script, "text/typescript": WI.Resource.Type.Script, "text/typescript-jsx": WI.Resource.Type.Script, "text/jsx": WI.Resource.Type.Script, "text/x-clojure": WI.Resource.Type.Script, "text/x-coffeescript": WI.Resource.Type.Script, }; /* Models/Script.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. */ WI.Script = class Script extends WI.SourceCode { constructor(target, id, range, url, sourceType, injected, sourceURL, sourceMapURL) { super(url); console.assert(target instanceof WI.Target || this instanceof WI.LocalScript); console.assert(range instanceof WI.TextRange); this._target = target; this._id = id || null; this._range = range || null; this._sourceType = sourceType || WI.Script.SourceType.Program; this._sourceURL = sourceURL || null; this._sourceMappingURL = sourceMapURL || null; this._injected = injected || false; this._dynamicallyAddedScriptElement = false; this._scriptSyntaxTree = null; this._resource = this._resolveResource(); // If this Script was a dynamically added `); lines.push(``); return lines.join(`\n`); } // Private async _process() { if (!this._processContext) { this._processContext = this.createContext(); if (this.isCanvas2D) { let initialContent = await WI.ImageUtilities.promisifyLoad(this._initialState.content); this._processContext.drawImage(initialContent, 0, 0); for (let initialState of this._initialState.states) { let state = await WI.RecordingState.swizzleInitialState(this, initialState); state.apply(this._type, this._processContext); // The last state represents the current state, which should not be saved. if (initialState !== this._initialState.states.lastValue) { this._processContext.save(); this._processStates.push(WI.RecordingState.fromCanvasContext2D(this._processContext)); } } } } // The first action is always a WI.RecordingInitialStateAction, which doesn't need to swizzle(). // Since it is not associated with a WI.RecordingFrame, it has to manually process(). if (!this._actions[0].ready) { this._actions[0].process(this, this._processContext, this._processStates); this.dispatchEventToListeners(WI.Recording.Event.ProcessedAction, {action: this._actions[0], index: 0}); } const workInterval = 10; let startTime = Date.now(); let cumulativeActionIndex = 0; let lastAction = this._actions[cumulativeActionIndex]; for (let frameIndex = 0; frameIndex < this._frames.length; ++frameIndex) { let frame = this._frames[frameIndex]; if (frame.actions.lastValue.ready) { cumulativeActionIndex += frame.actions.length; lastAction = frame.actions.lastValue; continue; } for (let actionIndex = 0; actionIndex < frame.actions.length; ++actionIndex) { ++cumulativeActionIndex; let action = frame.actions[actionIndex]; if (action.ready) { lastAction = action; continue; } await action.swizzle(this); action.process(this, this._processContext, this._processStates, {lastAction}); if (action.isVisual) this._visualActionIndexes.push(cumulativeActionIndex); if (!actionIndex) this.dispatchEventToListeners(WI.Recording.Event.StartProcessingFrame, {frame, index: frameIndex}); this.dispatchEventToListeners(WI.Recording.Event.ProcessedAction, {action, index: cumulativeActionIndex}); if (Date.now() - startTime > workInterval) { await Promise.delay(); // yield startTime = Date.now(); } lastAction = action; if (!this._processing) return; } if (!this._processing) return; } this._swizzle = null; this._processContext = null; this._processing = false; } }; // Keep this in sync with Inspector::Protocol::Recording::VERSION. WI.Recording.Version = 2; WI.Recording.Event = { ProcessedAction: "recording-processed-action", StartProcessingFrame: "recording-start-processing-frame", }; WI.Recording._importedRecordingNameSet = new Set; WI.Recording.CanvasRecordingNamesSymbol = Symbol("canvas-recording-names"); WI.Recording.Type = { Canvas2D: "canvas-2d", OffscreenCanvas2D: "offscreen-canvas-2d", CanvasBitmapRenderer: "canvas-bitmaprenderer", OffscreenCanvasBitmapRenderer: "offscreen-canvas-bitmaprenderer", CanvasWebGL: "canvas-webgl", OffscreenCanvasWebGL: "offscreen-canvas-webgl", CanvasWebGL2: "canvas-webgl2", OffscreenCanvasWebGL2: "offscreen-canvas-webgl2", }; // Keep this in sync with WebCore::RecordingSwizzleType. WI.Recording.Swizzle = { None: 0, Number: 1, Boolean: 2, String: 3, Array: 4, TypedArray: 5, Image: 6, ImageData: 7, DOMMatrix: 8, Path2D: 9, CanvasGradient: 10, CanvasPattern: 11, WebGLBuffer: 12, WebGLFramebuffer: 13, WebGLRenderbuffer: 14, WebGLTexture: 15, WebGLShader: 16, WebGLProgram: 17, WebGLUniformLocation: 18, ImageBitmap: 19, WebGLQuery: 20, WebGLSampler: 21, WebGLSync: 22, WebGLTransformFeedback: 23, WebGLVertexArrayObject: 24, // Special frontend-only swizzle types. CallStack: Symbol("CallStack"), CallFrame: Symbol("CallFrame"), }; /* Models/RecordingAction.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 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.RecordingAction = class RecordingAction extends WI.Object { constructor(name, parameters, swizzleTypes, stackTrace, snapshot) { super(); this._payloadName = name; this._payloadParameters = parameters; this._payloadSwizzleTypes = swizzleTypes; this._payloadStackTrace = stackTrace; this._payloadSnapshot = snapshot ?? -1; this._name = ""; this._parameters = []; this._stackTrace = null; this._snapshot = ""; this._valid = true; this._isFunction = false; this._isGetter = false; this._isVisual = false; this._contextReplacer = null; this._states = []; this._stateModifiers = new Set; this._warning = null; this._swizzled = false; this._processed = false; } // Static // Payload format: (name, parameters, swizzleTypes, [stackTrace, [snapshot]]) static fromPayload(payload) { if (!Array.isArray(payload)) payload = []; if (typeof payload[0] !== "number") { if (payload.length > 0) WI.Recording.synthesizeWarning(WI.UIString("non-number %s").format(WI.unlocalizedString("name"))); payload[0] = -1; } if (!Array.isArray(payload[1])) { if (payload.length > 1) WI.Recording.synthesizeWarning(WI.UIString("non-array %s").format(WI.unlocalizedString("parameters"))); payload[1] = []; } if (!Array.isArray(payload[2])) { if (payload.length > 2) WI.Recording.synthesizeWarning(WI.UIString("non-array %s").format(WI.unlocalizedString("swizzleTypes"))); payload[2] = []; } if (typeof payload[3] !== "number" || isNaN(payload[3]) || (!payload[3] && payload[3] !== 0)) { // COMPATIBILITY (iOS 12.1): "stackTrace" was sent as an array of call frames instead of a single call stack if (!Array.isArray(payload[3])) { if (payload.length > 3) WI.Recording.synthesizeWarning(WI.UIString("non-number %s").format(WI.unlocalizedString("stackTrace"))); payload[3] = []; } } if (typeof payload[4] !== "number" || isNaN(payload[4])) { if (payload.length > 4) WI.Recording.synthesizeWarning(WI.UIString("non-number %s").format(WI.unlocalizedString("snapshot"))); payload[4] = -1; } return new WI.RecordingAction(...payload); } static isFunctionForType(type, name) { let prototype = WI.RecordingAction._prototypeForType(type); if (!prototype) return false; let propertyDescriptor = Object.getOwnPropertyDescriptor(prototype, name); if (!propertyDescriptor) return false; return typeof propertyDescriptor.value === "function"; } static bitfieldNamesForParameter(type, name, value, index, count) { if (!value) return null; let prototype = WI.RecordingAction._prototypeForType(type); if (!prototype) return null; function testAndClearBit(name) { let bit = prototype[name]; if (!bit) return; if (value & bit) names.push(name); value = value & ~bit; } function hexString(value) { return "0x" + value.toString(16); } let names = []; if ((name === "clear" && index === 0 && (type === WI.Recording.Type.CanvasWebGL || type === WI.Recording.Type.CanvasWebGL2)) || (name === "blitFramebuffer" && index === 8 && type === WI.Recording.Type.CanvasWebGL2)) { testAndClearBit("COLOR_BUFFER_BIT"); testAndClearBit("DEPTH_BUFFER_BIT"); testAndClearBit("STENCIL_BUFFER_BIT"); if (value) names.push(hexString(value)); } if (name === "clientWaitSync" && index === 1 && type === WI.Recording.Type.CanvasWebGL2) { testAndClearBit("SYNC_FLUSH_COMMANDS_BIT"); if (value) names.push(hexString(value)); } if (!names.length) return null; return names; } static constantNameForParameter(type, name, value, index, count) { let indexesForType = WI.RecordingAction._constantIndexes[type]; if (!indexesForType) return null; let indexesForAction = indexesForType[name]; if (!indexesForAction) return null; if (Array.isArray(indexesForAction)) { if (!indexesForAction.includes(index)) return null; } else if (typeof indexesForAction === "object") { let indexesForActionVariant = indexesForAction[count]; if (!indexesForActionVariant) return null; if (Array.isArray(indexesForActionVariant) && !indexesForActionVariant.includes(index)) return null; } if (value === 0 && (type === WI.Recording.Type.CanvasWebGL || type === WI.Recording.Type.CanvasWebGL2)) { if (name === "blendFunc" || name === "blendFuncSeparate") return "ZERO"; if (index === 0) { if (name === "drawArrays" || name === "drawElements") return "POINTS"; if (name === "pixelStorei") return "NONE"; } } let prototype = WI.RecordingAction._prototypeForType(type); if (prototype) { for (let key in prototype) { let descriptor = Object.getOwnPropertyDescriptor(prototype, key); if (descriptor && descriptor.value === value) return key; } } return null; } static _prototypeForType(type) { switch (type) { case WI.Recording.Type.Canvas2D: return CanvasRenderingContext2D.prototype; case WI.Recording.Type.OffscreenCanvas2D: if (window.OffscreenCanvasRenderingContext2D) return OffscreenCanvasRenderingContext2D.prototype; break; case WI.Recording.Type.CanvasBitmapRenderer: case WI.Recording.Type.OffscreenCanvasBitmapRenderer: if (window.ImageBitmapRenderingContext) return ImageBitmapRenderingContext.prototype; break; case WI.Recording.Type.CanvasWebGL: case WI.Recording.Type.OffscreenCanvasWebGL: if (window.WebGLRenderingContext) return WebGLRenderingContext.prototype; break; case WI.Recording.Type.CanvasWebGL2: case WI.Recording.Type.OffscreenCanvasWebGL2: if (window.WebGL2RenderingContext) return WebGL2RenderingContext.prototype; break; } WI.reportInternalError("Unknown recording type: " + type); return null; } // Public get name() { return this._name; } get parameters() { return this._parameters; } get swizzleTypes() { return this._payloadSwizzleTypes; } get stackTrace() { return this._stackTrace; } get snapshot() { return this._snapshot; } get valid() { return this._valid; } get isFunction() { return this._isFunction; } get isGetter() { return this._isGetter; } get isVisual() { return this._isVisual; } get contextReplacer() { return this._contextReplacer; } get states() { return this._states; } get stateModifiers() { return this._stateModifiers; } get warning() { return this._warning; } get ready() { return this._swizzled && this._processed; } process(recording, context, states, {lastAction} = {}) { console.assert(this._swizzled, "You must swizzle() before you can process()."); console.assert(!this._processed, "You should only process() once."); this._processed = true; if (recording.type === WI.Recording.Type.CanvasWebGL || recording.type === WI.Recording.Type.CanvasWebGL2) { // We add each RecordingAction to the list of visualActionIndexes after it is processed. if (this._valid && this._isVisual) { let contentBefore = recording.visualActionIndexes.length ? recording.actions[recording.visualActionIndexes.lastValue].snapshot : recording.initialState.content; if (this._snapshot === contentBefore) this._warning = WI.UIString("This action causes no visual change"); } return; } function getContent() { if (context instanceof CanvasRenderingContext2D || (window.OffscreenCanvasRenderingContext2D && context instanceof OffscreenCanvasRenderingContext2D)) return context.getImageData(0, 0, context.canvas.width, context.canvas.height).data; if ((window.WebGLRenderingContext && context instanceof WebGLRenderingContext) || (window.WebGL2RenderingContext && context instanceof WebGL2RenderingContext)) { let pixels = new Uint8Array(context.drawingBufferWidth * context.drawingBufferHeight * 4); context.readPixels(0, 0, context.canvas.width, context.canvas.height, context.RGBA, context.UNSIGNED_BYTE, pixels); return pixels; } if (context.canvas instanceof HTMLCanvasElement) return [context.canvas.toDataURL()]; console.assert("Unknown context type", context); return []; } let contentBefore = null; let shouldCheckHasVisualEffect = this._valid && this._isVisual; if (shouldCheckHasVisualEffect) contentBefore = getContent(); this.apply(context); if (shouldCheckHasVisualEffect) { let contentAfter = getContent(); if (Array.shallowEqual(contentBefore, contentAfter)) this._warning = WI.UIString("This action causes no visual change"); } if (recording.isCanvas2D) { let currentState = WI.RecordingState.fromCanvasContext2D(context, {source: this}); console.assert(currentState); if (this.name === "save") states.push(currentState); else if (this.name === "restore") states.pop(); this._states = states.slice(); this._states.push(currentState); let lastState = null; if (lastAction) { lastState = lastAction.states.lastValue; for (let [name, value] of currentState) { let previousValue = lastState.get(name); if (value !== previousValue && !Object.shallowEqual(value, previousValue)) this._stateModifiers.add(name); } } let currentX = currentState.get("currentX"); let invalidX = (currentX < 0 || currentX >= context.canvas.width) && (!lastState || currentX !== lastState.get("currentX")); let currentY = currentState.get("currentY"); let invalidY = (currentY < 0 || currentY >= context.canvas.height) && (!lastState || currentY !== lastState.get("currentY")); if (invalidX || invalidY) this._warning = WI.UIString("This action moves the path outside the visible area"); } } async swizzle(recording, lastAction) { console.assert(!this._swizzled, "You should only swizzle() once."); if (!this._valid) { this._swizzled = true; return; } let swizzleParameter = (item, index) => { return recording.swizzle(item, this._payloadSwizzleTypes[index]); }; let swizzlePromises = [ recording.swizzle(this._payloadName, WI.Recording.Swizzle.String), Promise.all(this._payloadParameters.map(swizzleParameter)), ]; if (!isNaN(this._payloadStackTrace)) swizzlePromises.push(recording.swizzle(this._payloadStackTrace, WI.Recording.Swizzle.CallStack)); else { // COMPATIBILITY (iOS 12.1): "stackTrace" was sent as an array of call frames instead of a single call stack let stackTracePromise = Promise.all(this._payloadStackTrace.map((item) => recording.swizzle(item, WI.Recording.Swizzle.CallFrame))) .then((callFrames) => WI.StackTrace.fromPayload(WI.assumingMainTarget(), callFrames)); swizzlePromises.push(stackTracePromise); } if (this._payloadSnapshot >= 0) swizzlePromises.push(recording.swizzle(this._payloadSnapshot, WI.Recording.Swizzle.String)); let [name, parameters, stackTrace, snapshot] = await Promise.all(swizzlePromises); this._name = name; this._parameters = parameters; this._stackTrace = stackTrace; if (this._payloadSnapshot >= 0) this._snapshot = snapshot; if (recording.isCanvas) { if (this._name === "width" || this._name === "height") { this._contextReplacer = "canvas"; this._isFunction = false; this._isGetter = !this._parameters.length; this._isVisual = !this._isGetter; } } if (!this._contextReplacer) { this._isFunction = WI.RecordingAction.isFunctionForType(recording.type, this._name); this._isGetter = !this._isFunction && !this._parameters.length; if (this._snapshot) this._isVisual = true; else { let visualNames = WI.RecordingAction._visualNames[recording.type]; this._isVisual = visualNames ? visualNames.has(this._name) : false; } if (this._valid) { let prototype = WI.RecordingAction._prototypeForType(recording.type); if (prototype && !(name in prototype)) { this.markInvalid(); WI.Recording.synthesizeWarning(WI.UIString("\u0022%s\u0022 is not valid for %s").format(name, prototype.constructor.name)); } } } if (this._valid) { let parametersSpecified = this._parameters.every((parameter) => parameter !== undefined); let parametersCanBeSwizzled = this._payloadSwizzleTypes.every((swizzleType) => swizzleType !== WI.Recording.Swizzle.None); if (!parametersSpecified || !parametersCanBeSwizzled) this.markInvalid(); } if (this._valid) { let stateModifiers = WI.RecordingAction._stateModifiers[recording.type]; if (stateModifiers) { this._stateModifiers.add(this._name); let modifiedByAction = stateModifiers[this._name] || []; for (let item of modifiedByAction) this._stateModifiers.add(item); } } this._swizzled = true; } apply(context, options = {}) { console.assert(this._swizzled, "You must swizzle() before you can apply()."); console.assert(this._processed, "You must process() before you can apply()."); if (!this.valid) return; try { let name = options.nameOverride || this._name; if (this._contextReplacer) context = context[this._contextReplacer]; if (this.isFunction) context[name](...this._parameters); else { if (this.isGetter) context[name]; else context[name] = this._parameters[0]; } } catch { this.markInvalid(); WI.Recording.synthesizeWarning(WI.UIString("\u0022%s\u0022 threw an error").format(this._name)); } } markInvalid() { if (!this._valid) return; this._valid = false; this.dispatchEventToListeners(WI.RecordingAction.Event.ValidityChanged); } getColorParameters() { switch (this._name) { // 2D case "fillStyle": case "strokeStyle": case "shadowColor": // 2D (non-standard, legacy) case "setFillColor": case "setStrokeColor": // WebGL case "blendColor": case "clearColor": case "colorMask": return this._parameters; // 2D (non-standard, legacy) case "setShadow": return this._parameters.slice(3); } return []; } getImageParameters() { switch (this._name) { // 2D case "createImageData": case "createPattern": case "drawImage": case "fillStyle": case "putImageData": case "strokeStyle": // 2D (non-standard) case "drawImageFromRect": // BitmapRenderer case "transferFromImageBitmap": return this._parameters.slice(0, 1); // WebGL case "texImage2D": case "texSubImage2D": case "compressedTexImage2D": return [this._parameters.lastValue]; } return []; } toJSON() { let json = [this._payloadName, this._payloadParameters, this._payloadSwizzleTypes, this._payloadStackTrace]; if (this._payloadSnapshot >= 0) json.push(this._payloadSnapshot); return json; } }; WI.RecordingAction.Event = { ValidityChanged: "recording-action-marked-invalid", }; WI.RecordingAction._constantIndexes = { [WI.Recording.Type.CanvasWebGL]: { "activeTexture": true, "bindBuffer": true, "bindFramebuffer": true, "bindRenderbuffer": true, "bindTexture": true, "blendEquation": true, "blendEquationSeparate": true, "blendFunc": true, "blendFuncSeparate": true, "bufferData": [0, 2], "bufferSubData": [0], "checkFramebufferStatus": true, "compressedTexImage2D": [0, 2], "compressedTexSubImage2D": [0], "copyTexImage2D": [0, 2], "copyTexSubImage2D": [0], "createShader": true, "cullFace": true, "depthFunc": true, "disable": true, "drawArrays": [0], "drawElements": [0, 2], "enable": true, "framebufferRenderbuffer": true, "framebufferTexture2D": [0, 1, 2], "frontFace": true, "generateMipmap": true, "getBufferParameter": true, "getFramebufferAttachmentParameter": true, "getParameter": true, "getProgramParameter": true, "getRenderbufferParameter": true, "getShaderParameter": true, "getShaderPrecisionFormat": true, "getTexParameter": true, "getVertexAttrib": [1], "getVertexAttribOffset": [1], "hint": true, "isEnabled": true, "pixelStorei": [0], "readPixels": [4, 5], "renderbufferStorage": [0, 1], "stencilFunc": [0], "stencilFuncSeparate": [0, 1], "stencilMaskSeparate": [0], "stencilOp": true, "stencilOpSeparate": true, "texImage2D": { 5: [0, 2, 3, 4], 6: [0, 2, 3, 4], 8: [0, 2, 6, 7], 9: [0, 2, 6, 7], }, "texParameterf": [0, 1], "texParameteri": [0, 1], "texSubImage2D": { 6: [0, 4, 5], 7: [0, 4, 5], 8: [0, 6, 7], 9: [0, 6, 7], }, "vertexAttribPointer": [2], }, [WI.Recording.Type.CanvasWebGL2]: { "activeTexture": true, "beginQuery": [0], "beginTransformFeedback": true, "bindBuffer": true, "bindBufferBase": [0], "bindBufferRange": [0], "bindFramebuffer": true, "bindRenderbuffer": true, "bindTexture": true, "bindTransformFeedback": [0], "blendEquation": true, "blendEquationSeparate": true, "blendFunc": true, "blendFuncSeparate": true, "blitFramebuffer": [10], "bufferData": [0, 2], "bufferSubData": [0], "checkFramebufferStatus": true, "clearBufferfi": [0], "clearBufferfv": [0], "clearBufferiv": [0], "clearBufferuiv": [0], "compressedTexImage2D": [0, 2], "compressedTexSubImage2D": [0], "compressedTexSubImage3D": [0], "copyBufferSubData": [0, 1], "copyTexImage2D": [0, 2], "copyTexSubImage2D": [0], "copyTexSubImage3D": [0], "createShader": true, "cullFace": true, "depthFunc": true, "disable": true, "drawArrays": [0], "drawArraysInstanced": [0], "drawBuffers": true, "drawElements": [0, 2], "drawElementsInstanced": [0, 2], "drawRangeElements": [0, 4], "enable": true, "endQuery": true, "fenceSync": [0], "framebufferRenderbuffer": true, "framebufferTexture2D": [0, 1, 2], "framebufferTextureLayer": [0, 1], "frontFace": true, "generateMipmap": true, "getActiveUniformBlockParameter": [2], "getActiveUniforms": [2], "getBufferParameter": true, "getBufferSubData": [0], "getFramebufferAttachmentParameter": true, "getIndexedParameter": [0], "getInternalformatParameter": true, "getParameter": true, "getProgramParameter": true, "getQuery": true, "getQueryParameter": [1], "getRenderbufferParameter": true, "getSamplerParameter": [1], "getShaderParameter": true, "getShaderPrecisionFormat": true, "getSyncParameter": [1], "getTexParameter": true, "getVertexAttrib": [1], "getVertexAttribOffset": [1], "hint": true, "invalidateFramebuffer": [0, 1], "invalidateSubFramebuffer": [0, 1], "isEnabled": true, "pixelStorei": [0], "readBuffer": true, "readPixels": [4, 5], "renderbufferStorage": [0, 1], "renderbufferStorageMultisample": [0, 2], "samplerParameterf": [1], "samplerParameteri": [1], "stencilFunc": [0], "stencilFuncSeparate": [0, 1], "stencilMaskSeparate": [0], "stencilOp": true, "stencilOpSeparate": true, "texImage2D": { 5: [0, 2, 3, 4], 6: [0, 2, 3, 4], 8: [0, 2, 6, 7], 9: [0, 2, 6, 7], 10: [0, 2, 6, 7], 11: [0, 2, 7, 8], }, "texParameterf": [0, 1], "texParameteri": [0, 1], "texStorage2D":[0, 2], "texSubImage2D": { 6: [0, 4, 5], 7: [0, 4, 5], 8: [0, 6, 7], 9: [0, 6, 7], 10: [0, 6, 7], 11: [0, 8, 9], 12: [0, 8, 9], }, "transformFeedbackVaryings": [2], "vertexAttribIPointer": [2], "vertexAttribPointer": [2], }, }; WI.RecordingAction._constantIndexes[WI.Recording.Type.OffscreenCanvasWebGL] = WI.RecordingAction._constantIndexes[WI.Recording.Type.CanvasWebGL]; WI.RecordingAction._constantIndexes[WI.Recording.Type.OffscreenCanvasWebGL2] = WI.RecordingAction._constantIndexes[WI.Recording.Type.CanvasWebGL2]; WI.RecordingAction._visualNames = { [WI.Recording.Type.Canvas2D]: new Set([ "clearRect", "drawFocusIfNeeded", "drawImage", "drawImageFromRect", "fill", "fillRect", "fillText", "putImageData", "stroke", "strokeRect", "strokeText", ]), [WI.Recording.Type.OffscreenCanvas2D]: new Set([ "clearRect", "drawImage", "fill", "fillRect", "fillText", "putImageData", "strokeRect", "strokeText", ]), [WI.Recording.Type.CanvasBitmapRenderer]: new Set([ "transferFromImageBitmap", ]), [WI.Recording.Type.CanvasWebGL]: new Set([ "clear", "drawArrays", "drawElements", ]), [WI.Recording.Type.CanvasWebGL2]: new Set([ "clear", "drawArrays", "drawArraysInstanced", "drawElements", "drawElementsInstanced", ]), }; WI.RecordingAction._visualNames[WI.Recording.Type.OffscreenCanvasBitmapRenderer] = WI.RecordingAction._visualNames[WI.Recording.Type.CanvasBitmapRenderer]; WI.RecordingAction._visualNames[WI.Recording.Type.OffscreenCanvasWebGL] = WI.RecordingAction._visualNames[WI.Recording.Type.CanvasWebGL]; WI.RecordingAction._visualNames[WI.Recording.Type.OffscreenCanvasWebGL2] = WI.RecordingAction._visualNames[WI.Recording.Type.CanvasWebGL2]; WI.RecordingAction._stateModifiers = { [WI.Recording.Type.Canvas2D]: { arc: ["currentX", "currentY"], arcTo: ["currentX", "currentY"], beginPath: ["currentX", "currentY"], bezierCurveTo: ["currentX", "currentY"], clearShadow: ["shadowOffsetX", "shadowOffsetY", "shadowBlur", "shadowColor"], closePath: ["currentX", "currentY"], ellipse: ["currentX", "currentY"], lineTo: ["currentX", "currentY"], moveTo: ["currentX", "currentY"], quadraticCurveTo: ["currentX", "currentY"], rect: ["currentX", "currentY"], resetTransform: ["transform"], rotate: ["transform"], scale: ["transform"], setAlpha: ["globalAlpha"], setCompositeOperation: ["globalCompositeOperation"], setFillColor: ["fillStyle"], setLineCap: ["lineCap"], setLineJoin: ["lineJoin"], setLineWidth: ["lineWidth"], setMiterLimit: ["miterLimit"], setShadow: ["shadowOffsetX", "shadowOffsetY", "shadowBlur", "shadowColor"], setStrokeColor: ["strokeStyle"], setTransform: ["transform"], translate: ["transform"], }, [WI.Recording.Type.OffscreenCanvas2D]: { arc: ["currentX", "currentY"], arcTo: ["currentX", "currentY"], beginPath: ["currentX", "currentY"], bezierCurveTo: ["currentX", "currentY"], closePath: ["currentX", "currentY"], ellipse: ["currentX", "currentY"], lineTo: ["currentX", "currentY"], moveTo: ["currentX", "currentY"], quadraticCurveTo: ["currentX", "currentY"], rect: ["currentX", "currentY"], resetTransform: ["transform"], rotate: ["transform"], scale: ["transform"], setTransform: ["transform"], translate: ["transform"], }, }; /* Models/RecordingFrame.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 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.RecordingFrame = class RecordingFrame { constructor(actions, {duration, incomplete} = {}) { this._actions = actions; this._duration = duration || NaN; this._incomplete = incomplete || false; } // Static static fromPayload(payload) { if (typeof payload !== "object" || payload === null) payload = {}; if (!Array.isArray(payload.actions)) { if ("actions" in payload) WI.Recording.synthesizeWarning(WI.UIString("non-array %s").format(WI.unlocalizedString("actions"))); payload.actions = []; } let actions = payload.actions.map(WI.RecordingAction.fromPayload); return new WI.RecordingFrame(actions, { duration: payload.duration || NaN, incomplete: !!payload.incomplete, }); } // Public get actions() { return this._actions; } get duration() { return this._duration; } get incomplete() { return this._incomplete; } toJSON() { let json = { actions: this._actions.map((action) => action.toJSON()), }; if (!isNaN(this._duration)) json.duration = this._duration; if (this._incomplete) json.incomplete = this._incomplete; return json; } }; /* Models/RecordingInitialStateAction.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 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.RecordingInitialStateAction = class RecordingInitialStateAction extends WI.RecordingAction { constructor() { super(); this._name = WI.UIString("Initial State"); this._valid = false; this._swizzled = true; } }; /* Models/RecordingState.js */ /* * Copyright (C) 2018 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.RecordingState = class RecordingState { constructor(data, {source} = {}) { this._data = data; this._source = source || null; } // Static static fromCanvasContext2D(context, options = {}) { console.assert(context instanceof CanvasRenderingContext2D || (window.OffscreenCanvasRenderingContext2D && context instanceof OffscreenCanvasRenderingContext2D), context); let matrix = context.getTransform(); let data = {}; data.direction = context.direction; data.fillStyle = context.fillStyle; data.font = context.font; data.globalAlpha = context.globalAlpha; data.globalCompositeOperation = context.globalCompositeOperation; data.imageSmoothingEnabled = context.imageSmoothingEnabled; data.imageSmoothingQuality = context.imageSmoothingQuality; data.lineCap = context.lineCap; data.lineDash = context.getLineDash(); data.lineDashOffset = context.lineDashOffset; data.lineJoin = context.lineJoin; data.lineWidth = context.lineWidth; data.miterLimit = context.miterLimit; data.shadowBlur = context.shadowBlur; data.shadowColor = context.shadowColor; data.shadowOffsetX = context.shadowOffsetX; data.shadowOffsetY = context.shadowOffsetY; data.strokeStyle = context.strokeStyle; data.textAlign = context.textAlign; data.textBaseline = context.textBaseline; data.transform = [matrix.a, matrix.b, matrix.c, matrix.d, matrix.e, matrix.f]; data.webkitImageSmoothingEnabled = context.webkitImageSmoothingEnabled; data.webkitLineDash = context.webkitLineDash; data.webkitLineDashOffset = context.webkitLineDashOffset; data.currentX = context.currentX; data.currentY = context.currentY; data.setPath = [context.getPath()]; return new WI.RecordingState(data, options); } static async swizzleInitialState(recording, initialState) { if (recording.isCanvas2D) { let swizzledState = {}; for (let [name, value] of Object.entries(initialState)) { // COMPATIBILITY (iOS 12.0): Recording.InitialState.states did not exist yet let nameIndex = parseInt(name); if (!isNaN(nameIndex)) name = await recording.swizzle(nameIndex, WI.Recording.Swizzle.String); switch (name) { case "setTransform": value = [await recording.swizzle(value, WI.Recording.Swizzle.DOMMatrix)]; break; case "fillStyle": case "strokeStyle": var [gradient, pattern, string] = await Promise.all([ recording.swizzle(value, WI.Recording.Swizzle.CanvasGradient), recording.swizzle(value, WI.Recording.Swizzle.CanvasPattern), recording.swizzle(value, WI.Recording.Swizzle.String), ]); if (gradient && !pattern) value = gradient; else if (pattern && !gradient) value = pattern; else value = string; break; case "direction": case "font": case "globalCompositeOperation": case "imageSmoothingQuality": case "lineCap": case "lineJoin": case "shadowColor": case "textAlign": case "textBaseline": value = await recording.swizzle(value, WI.Recording.Swizzle.String); break; case "globalAlpha": case "lineWidth": case "miterLimit": case "shadowOffsetX": case "shadowOffsetY": case "shadowBlur": case "lineDashOffset": value = await recording.swizzle(value, WI.Recording.Swizzle.Number); break; case "setPath": value = [await recording.swizzle(value[0], WI.Recording.Swizzle.Path2D)]; break; } if (value === undefined || (Array.isArray(value) && value.includes(undefined))) continue; swizzledState[name] = value; } return new WI.RecordingState(swizzledState); } return null; } // Public get source() { return this._source; } has(name) { return name in this._data; } get(name) { return this._data[name]; } apply(type, context) { for (let [name, value] of this) { if (!(name in context)) continue; // Skip internal state used for path debugging. if (name === "currentX" || name === "currentY") continue; try { if (WI.RecordingAction.isFunctionForType(type, name)) context[name](...value); else context[name] = value; } catch { } } } toJSON() { return this._data; } [Symbol.iterator]() { return Object.entries(this._data)[Symbol.iterator](); } }; /* Models/Redirect.js */ /* * Copyright (C) 2018 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.Redirect = class Redirect { constructor(url, requestMethod, requestHeaders, responseStatusCode, responseStatusText, responseHeaders, timestamp) { console.assert(typeof url === "string"); console.assert(typeof requestMethod === "string"); console.assert(typeof requestHeaders === "object"); console.assert(!isNaN(responseStatusCode)); console.assert(typeof responseStatusText === "string"); console.assert(typeof responseHeaders === "object"); console.assert(!isNaN(timestamp)); this._url = url; this._urlComponents = null; this._requestMethod = requestMethod; this._requestHeaders = requestHeaders; this._responseStatusCode = responseStatusCode; this._responseStatusText = responseStatusText; this._responseHeaders = responseHeaders; this._timestamp = timestamp; } // Public get url() { return this._url; } get requestMethod() { return this._requestMethod; } get requestHeaders() { return this._requestHeaders; } get responseStatusCode() { return this._responseStatusCode; } get responseStatusText() { return this._responseStatusText; } get responseHeaders() { return this._responseHeaders; } get timestamp() { return this._timestamp; } get urlComponents() { if (!this._urlComponents) this._urlComponents = parseURL(this._url); return this._urlComponents; } }; /* Models/RenderingFrameTimelineRecord.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 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.RenderingFrameTimelineRecord = class RenderingFrameTimelineRecord extends WI.TimelineRecord { constructor(startTime, endTime, name) { super(WI.TimelineRecord.Type.RenderingFrame, startTime, endTime); this._name = name || ""; this._durationByTaskType = new Map; this._frameIndex = -1; } // Static static resetFrameIndex() { WI.RenderingFrameTimelineRecord._nextFrameIndex = 0; } static displayNameForTaskType(taskType) { switch (taskType) { case WI.RenderingFrameTimelineRecord.TaskType.Script: return WI.UIString("Script"); case WI.RenderingFrameTimelineRecord.TaskType.Layout: return WI.repeatedUIString.timelineRecordLayout(); case WI.RenderingFrameTimelineRecord.TaskType.Paint: return WI.repeatedUIString.timelineRecordPaint(); case WI.RenderingFrameTimelineRecord.TaskType.Other: return WI.UIString("Other"); } } static taskTypeForTimelineRecord(record) { switch (record.type) { case WI.TimelineRecord.Type.Script: return WI.RenderingFrameTimelineRecord.TaskType.Script; case WI.TimelineRecord.Type.Layout: if (record.eventType === WI.LayoutTimelineRecord.EventType.Paint || record.eventType === WI.LayoutTimelineRecord.EventType.Composite) return WI.RenderingFrameTimelineRecord.TaskType.Paint; return WI.RenderingFrameTimelineRecord.TaskType.Layout; default: console.error("Unsupported timeline record type: " + record.type); return null; } } // Import / Export static async fromJSON(json) { let {startTime, endTime} = json; let record = new WI.RenderingFrameTimelineRecord(startTime, endTime); record.setupFrameIndex(); return record; } toJSON() { // FIXME: durationByTaskType data cannot be calculated if this does not have children. return { type: this.type, startTime: this.startTime, endTime: this.endTime, }; } // Public get frameIndex() { return this._frameIndex; } get frameNumber() { return this._frameIndex + 1; } get name() { return this._name; } setupFrameIndex() { console.assert(this._frameIndex === -1, "Frame index should only be set once."); if (this._frameIndex >= 0) return; this._frameIndex = WI.RenderingFrameTimelineRecord._nextFrameIndex++; } durationForTask(taskType) { if (this._durationByTaskType.has(taskType)) return this._durationByTaskType.get(taskType); var duration; if (taskType === WI.RenderingFrameTimelineRecord.TaskType.Other) duration = this._calculateDurationRemainder(); else { duration = this.children.reduce(function(previousValue, currentValue) { if (taskType !== WI.RenderingFrameTimelineRecord.taskTypeForTimelineRecord(currentValue)) return previousValue; var currentDuration = currentValue.duration; if (currentValue.usesActiveStartTime) currentDuration -= currentValue.inactiveDuration; return previousValue + currentDuration; }, 0); if (taskType === WI.RenderingFrameTimelineRecord.TaskType.Script) { // Layout events synchronously triggered from JavaScript must be subtracted from the total // script time, to prevent the time from being counted twice. duration -= this.children.reduce(function(previousValue, currentValue) { if (currentValue.type === WI.TimelineRecord.Type.Layout && (currentValue.sourceCodeLocation || currentValue.stackTrace)) return previousValue + currentValue.duration; return previousValue; }, 0); } } this._durationByTaskType.set(taskType, duration); return duration; } // Private _calculateDurationRemainder() { return Object.keys(WI.RenderingFrameTimelineRecord.TaskType).reduce((previousValue, key) => { let taskType = WI.RenderingFrameTimelineRecord.TaskType[key]; if (taskType === WI.RenderingFrameTimelineRecord.TaskType.Other) return previousValue; return previousValue - this.durationForTask(taskType); }, this.duration); } }; WI.RenderingFrameTimelineRecord.TaskType = { Script: "rendering-frame-timeline-record-script", Layout: "rendering-frame-timeline-record-layout", Paint: "rendering-frame-timeline-record-paint", Other: "rendering-frame-timeline-record-other" }; WI.RenderingFrameTimelineRecord.TypeIdentifier = "rendering-frame-timeline-record"; WI.RenderingFrameTimelineRecord._nextFrameIndex = 0; /* Models/ResourceCollection.js */ /* * Copyright (C) 2013 Apple Inc. All rights reserved. * Copyright (C) 2016 Devin Rousso . 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.ResourceCollection = class ResourceCollection extends WI.Collection { constructor(resourceType) { super(); this._resourceType = resourceType || null; this._resourceURLMap = new Multimap; this._resourcesTypeMap = new Map; } // Public get resourceType() { return this._resourceType; } get displayName() { const plural = true; return this._resourceType ? WI.Resource.displayNameForType(this._resourceType, plural) : WI.UIString("Resources"); } objectIsRequiredType(object) { if (this._resourceType === WI.Resource.Type.StyleSheet && object instanceof WI.CSSStyleSheet) return true; if (!(object instanceof WI.Resource)) return false; if (!this._resourceType) return true; return object.type === this._resourceType; } resourcesForURL(url) { return this._resourceURLMap.get(url) || new Set; } resourceCollectionForType(type) { if (this._resourceType) { console.assert(type === this._resourceType); return this; } let resourcesCollectionForType = this._resourcesTypeMap.get(type); if (!resourcesCollectionForType) { resourcesCollectionForType = new WI.ResourceCollection(type); this._resourcesTypeMap.set(type, resourcesCollectionForType); } return resourcesCollectionForType; } clear() { super.clear(); this._resourceURLMap.clear(); if (!this._resourceType) this._resourcesTypeMap.clear(); } // Protected itemAdded(item) { this._associateWithResource(item); } itemRemoved(item) { this._disassociateWithResource(item); } itemsCleared(items) { const skipRemoval = true; for (let item of items) this._disassociateWithResource(item, skipRemoval); } // Private _associateWithResource(resource) { this._resourceURLMap.add(resource.url, resource); if (!this._resourceType) { let resourcesCollectionForType = this.resourceCollectionForType(resource.type); resourcesCollectionForType.add(resource); } resource.addEventListener(WI.Resource.Event.URLDidChange, this._resourceURLDidChange, this); resource.addEventListener(WI.Resource.Event.TypeDidChange, this._resourceTypeDidChange, this); } _disassociateWithResource(resource, skipRemoval) { resource.removeEventListener(WI.Resource.Event.URLDidChange, this._resourceURLDidChange, this); resource.removeEventListener(WI.Resource.Event.TypeDidChange, this._resourceTypeDidChange, this); if (skipRemoval) return; if (!this._resourceType) { let resourcesCollectionForType = this.resourceCollectionForType(resource.type); resourcesCollectionForType.remove(resource); } this._resourceURLMap.delete(resource.url, resource); } _resourceURLDidChange(event) { let resource = event.target; console.assert(resource instanceof WI.Resource); if (!(resource instanceof WI.Resource)) return; let oldURL = event.data.oldURL; console.assert(oldURL); if (!oldURL) return; this._resourceURLMap.add(resource.url, resource); this._resourceURLMap.delete(oldURL, resource); } _resourceTypeDidChange(event) { let resource = event.target; console.assert(resource instanceof WI.Resource); if (!(resource instanceof WI.Resource)) return; if (this._resourceType) { console.assert(resource.type !== this._resourceType); this.remove(resource); return; } console.assert(event.data.oldType); let resourcesWithNewType = this.resourceCollectionForType(resource.type); resourcesWithNewType.add(resource); // It is not necessary to remove the resource from the sub-collection for the old type since // this is handled by that sub-collection's own _resourceTypeDidChange handler (via the // above if statement). } }; /* Models/ResourceQueryResult.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.ResourceQueryResult = class ResourceQueryResult extends WI.QueryResult { constructor(resource, searchString, matches, cookie) { console.assert(resource instanceof WI.Resource, resource); super(resource, matches); this._searchString = searchString; this._cookie = cookie || null; } // Public get resource() { return this.value; } get searchString() { return this._searchString; } get cookie() { return this._cookie; } // Testing __test_createMatchesMask() { let filename = this.resource.displayName; let lastIndex = -1; let result = ""; for (let match of this._matches) { let gap = " ".repeat(match.index - lastIndex - 1); result += gap; result += filename[match.index]; lastIndex = match.index; } return result; } }; /* Models/ResourceTimelineRecord.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. */ WI.ResourceTimelineRecord = class ResourceTimelineRecord extends WI.TimelineRecord { constructor(resource) { super(WI.TimelineRecord.Type.Network); this._resource = resource; this._resource.addEventListener(WI.Resource.Event.TimestampsDidChange, this._dispatchUpdatedEvent, this); } // Import / Export static async fromJSON(json) { let {entry, archiveStartTime} = json; let localResource = WI.LocalResource.fromHAREntry(entry, archiveStartTime); return new WI.ResourceTimelineRecord(localResource); } toJSON() { const content = ""; return { type: this.type, archiveStartTime: this._resource.requestSentWalltime - this.startTime, entry: WI.HARBuilder.entry(this._resource, content), }; } // Public get resource() { return this._resource; } get updatesDynamically() { return true; } get usesActiveStartTime() { return true; } get startTime() { return this._resource.timingData.startTime; } get activeStartTime() { return this._resource.timingData.responseStart; } get endTime() { return this._resource.timingData.responseEnd; } // Private _dispatchUpdatedEvent() { this.dispatchEventToListeners(WI.TimelineRecord.Event.Updated); } }; /* Models/ResourceTimingData.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.ResourceTimingData = class ResourceTimingData { constructor(resource, data) { data = data || {}; console.assert(isNaN(data.startTime) || data.startTime <= data.fetchStart); console.assert(isNaN(data.redirectStart) === isNaN(data.redirectEnd)); console.assert(isNaN(data.domainLookupStart) === isNaN(data.domainLookupEnd)); console.assert(isNaN(data.connectStart) === isNaN(data.connectEnd)); this._resource = resource; this._startTime = data.startTime || NaN; this._redirectStart = data.redirectStart || NaN; this._redirectEnd = data.redirectEnd || NaN; this._fetchStart = data.fetchStart || NaN; this._domainLookupStart = data.domainLookupStart || NaN; this._domainLookupEnd = data.domainLookupEnd || NaN; this._connectStart = data.connectStart || NaN; this._connectEnd = data.connectEnd || NaN; this._secureConnectionStart = data.secureConnectionStart || NaN; this._requestStart = data.requestStart || NaN; this._responseStart = data.responseStart || NaN; this._responseEnd = data.responseEnd || NaN; if (this._domainLookupStart >= this._domainLookupEnd) this._domainLookupStart = this._domainLookupEnd = NaN; if (this._connectStart >= this._connectEnd) this._connectStart = this._connectEnd = NaN; } // Static static fromPayload(payload, resource) { payload = payload || {}; // COMPATIBILITY (iOS 12.0): Resource Timing data was based on startTime, not fetchStart. let startTime = payload.startTime; let fetchStart = payload.fetchStart; let redirectStart = payload.redirectStart; let redirectEnd = payload.redirectEnd; if (isNaN(fetchStart) || fetchStart < startTime) fetchStart = startTime; if (redirectStart < startTime || redirectStart > fetchStart || redirectStart > redirectEnd) redirectStart = NaN; if (redirectEnd < startTime || redirectEnd > fetchStart || redirectEnd < redirectStart) redirectEnd = NaN; function offsetToTimestamp(offset) { return offset > 0 ? fetchStart + (offset / 1000) : NaN; } let data = { startTime, redirectStart, redirectEnd, fetchStart, domainLookupStart: offsetToTimestamp(payload.domainLookupStart), domainLookupEnd: offsetToTimestamp(payload.domainLookupEnd), connectStart: offsetToTimestamp(payload.connectStart), connectEnd: offsetToTimestamp(payload.connectEnd), secureConnectionStart: offsetToTimestamp(payload.secureConnectionStart), requestStart: offsetToTimestamp(payload.requestStart), responseStart: offsetToTimestamp(payload.responseStart), responseEnd: offsetToTimestamp(payload.responseEnd) }; return new WI.ResourceTimingData(resource, data); } // Public get startTime() { return this._startTime || this._resource.requestSentTimestamp; } get redirectStart() { return this._redirectStart; } get redirectEnd() { return this._redirectEnd; } get fetchStart() { return this._fetchStart || this._resource.requestSentTimestamp; } get domainLookupStart() { return this._domainLookupStart; } get domainLookupEnd() { return this._domainLookupEnd; } get connectStart() { return this._connectStart; } get connectEnd() { return this._connectEnd; } get secureConnectionStart() { return this._secureConnectionStart; } get requestStart() { return this._requestStart || this._startTime || this._resource.requestSentTimestamp; } get responseStart() { return this._responseStart || this._startTime || this._resource.responseReceivedTimestamp || this._resource.finishedOrFailedTimestamp; } get responseEnd() { return this._responseEnd || this._resource.finishedOrFailedTimestamp; } markResponseEndTime(responseEnd) { console.assert(typeof responseEnd === "number"); console.assert(isNaN(responseEnd) || responseEnd >= this.startTime, "responseEnd time should be greater than the start time", this.startTime, responseEnd); console.assert(isNaN(responseEnd) || responseEnd >= this.requestStart, "responseEnd time should be greater than the request time", this.requestStart, responseEnd); this._responseEnd = responseEnd; } }; /* Models/Revision.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. */ WI.Revision = class Revision { // Public apply() { // Implemented by subclasses. console.error("Needs to be implemented by a subclass."); } revert() { // Implemented by subclasses. console.error("Needs to be implemented by a subclass."); } copy() { // Override by subclasses. return this; } }; /* Models/ScopeChainNode.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. */ WI.ScopeChainNode = class ScopeChainNode { constructor(type, objects, name, location, empty) { console.assert(typeof type === "string"); console.assert(objects.every((x) => x instanceof WI.RemoteObject)); if (type in WI.ScopeChainNode.Type) type = WI.ScopeChainNode.Type[type]; this._type = type || null; this._objects = objects || []; this._name = name || ""; this._location = location || null; this._empty = empty || false; } // Public get type() { return this._type; } get objects() { return this._objects; } get name() { return this._name; } get location() { return this._location; } get empty() { return this._empty; } get hash() { if (this._hash) return this._hash; this._hash = this._name; if (this._location) this._hash += `:${this._location.scriptId}:${this._location.lineNumber}:${this._location.columnNumber}`; return this._hash; } convertToLocalScope() { this._type = WI.ScopeChainNode.Type.Local; } }; WI.ScopeChainNode.Type = { Local: "scope-chain-type-local", Global: "scope-chain-type-global", GlobalLexicalEnvironment: "scope-chain-type-global-lexical-environment", With: "scope-chain-type-with", Closure: "scope-chain-type-closure", Catch: "scope-chain-type-catch", FunctionName: "scope-chain-type-function-name", Block: "scope-chain-type-block", }; /* Models/ScreenshotsInstrument.js */ /* * Copyright (C) 2022 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.ScreenshotsInstrument = class ScreenshotsInstrument extends WI.Instrument { constructor() { super(); console.assert(WI.ScreenshotsInstrument.supported()); } // Static static supported() { // FIXME: Screenshots timeline does not capture screenshots for changes in WebKitLegacy. if (WI.sharedApp.debuggableType === WI.DebuggableType.Page) return false; // COMPATIBILITY (macOS 12.3, iOS 15.4): Timeline.Instrument.Screenshot did not exist yet. return InspectorBackend.Enum.Timeline.Instrument.Screenshot; } // Protected get timelineRecordType() { return WI.TimelineRecord.Type.Screenshots; } }; /* Models/ScreenshotsTimelineRecord.js */ /* * Copyright (C) 2022 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.ScreenshotsTimelineRecord = class ScreenshotsTimelineRecord extends WI.TimelineRecord { constructor(timestamp, imageData) { console.assert(timestamp); console.assert(imageData && typeof imageData === "string", imageData); // Pass the startTime as the endTime since this record type has no duration. super(WI.TimelineRecord.Type.Screenshots, timestamp, timestamp); this._imageData = imageData; } // Import / Export static async fromJSON(json) { return new WI.ScreenshotsTimelineRecord(json.timestamp, json.imageData); } toJSON() { return { type: this.type, timestamp: this.startTime, imageData: this._imageData, }; } // Public get imageData() { return this._imageData; } }; /* Models/ScriptInstrument.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 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.ScriptInstrument = class ScriptInstrument extends WI.Instrument { // Protected get timelineRecordType() { return WI.TimelineRecord.Type.Script; } startInstrumentation(initiatedByBackend) { let target = WI.assumingMainTarget(); // FIXME: Make this some UI visible option. const includeSamples = true; if (!initiatedByBackend) target.ScriptProfilerAgent.startTracking(includeSamples); } stopInstrumentation(initiatedByBackend) { let target = WI.assumingMainTarget(); if (!initiatedByBackend) target.ScriptProfilerAgent.stopTracking(); } }; /* Models/ScriptSyntaxTree.js */ /* * Copyright (C) 2014-2018 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.ScriptSyntaxTree = class ScriptSyntaxTree { constructor(sourceText, script) { console.assert(script && script instanceof WI.Script, script); this._script = script; try { let sourceType = this._script.sourceType === WI.Script.SourceType.Module ? "module" : "script"; let esprimaSyntaxTree = esprima.parse(sourceText, {loc: true, range: true, sourceType}); this._syntaxTree = this._createInternalSyntaxTree(esprimaSyntaxTree); this._parsedSuccessfully = true; } catch (error) { this._parsedSuccessfully = false; this._syntaxTree = null; console.error("Couldn't parse JavaScript File: " + script.url, error); } } // Public get parsedSuccessfully() { return this._parsedSuccessfully; } forEachNode(callback) { console.assert(this._parsedSuccessfully); if (!this._parsedSuccessfully) return; this._recurse(this._syntaxTree, callback, this._defaultParserState()); } filter(predicate, startNode) { console.assert(startNode && this._parsedSuccessfully); if (!this._parsedSuccessfully) return []; var nodes = []; function filter(node, state) { if (predicate(node)) nodes.push(node); else state.skipChildNodes = true; } this._recurse(startNode, filter, this._defaultParserState()); return nodes; } containersOfPosition(position) { console.assert(this._parsedSuccessfully); if (!this._parsedSuccessfully) return []; let allNodes = []; this.forEachNode((node, state) => { if (node.endPosition.isBefore(position)) state.skipChildNodes = true; else if (node.startPosition.isAfter(position)) state.shouldStopEarly = true; else allNodes.push(node); }); return allNodes; } filterByRange(startPosition, endPosition) { console.assert(this._parsedSuccessfully); if (!this._parsedSuccessfully) return []; var allNodes = []; function filterForNodesInRange(node, state) { // program start range program end // [ [ ] ] // [ ] [ [ ] ] [ ] // If a node's range ends before the range we're interested in starts, we don't need to search any of its // enclosing ranges, because, by definition, those enclosing ranges are contained within this node's range. if (node.endPosition.isBefore(startPosition)) { state.skipChildNodes = true; return; } // We are only interested in nodes whose start position is within our range. if (node.startPosition.isWithin(startPosition, endPosition)) { allNodes.push(node); return; } // Once we see nodes that start beyond our range, we can quit traversing the AST. We can do this safely // because we know the AST is traversed using depth first search, so it will traverse into enclosing ranges // before it traverses into adjacent ranges. if (node.startPosition.isAfter(endPosition)) state.shouldStopEarly = true; } this.forEachNode(filterForNodesInRange); return allNodes; } containsNonEmptyReturnStatement(startNode) { console.assert(startNode && this._parsedSuccessfully); if (!this._parsedSuccessfully) return false; if (startNode.attachments._hasNonEmptyReturnStatement !== undefined) return startNode.attachments._hasNonEmptyReturnStatement; function removeFunctionsFilter(node) { return node.type !== WI.ScriptSyntaxTree.NodeType.FunctionExpression && node.type !== WI.ScriptSyntaxTree.NodeType.FunctionDeclaration && node.type !== WI.ScriptSyntaxTree.NodeType.ArrowFunctionExpression; } var nodes = this.filter(removeFunctionsFilter, startNode); var hasNonEmptyReturnStatement = false; var returnStatementType = WI.ScriptSyntaxTree.NodeType.ReturnStatement; for (var node of nodes) { if (node.type === returnStatementType && node.argument) { hasNonEmptyReturnStatement = true; break; } } startNode.attachments._hasNonEmptyReturnStatement = hasNonEmptyReturnStatement; return hasNonEmptyReturnStatement; } static functionReturnDivot(node) { console.assert(node.type === WI.ScriptSyntaxTree.NodeType.FunctionDeclaration || node.type === WI.ScriptSyntaxTree.NodeType.FunctionExpression || node.type === WI.ScriptSyntaxTree.NodeType.MethodDefinition || node.type === WI.ScriptSyntaxTree.NodeType.ArrowFunctionExpression); // "f" in "function". "s" in "set". "g" in "get". First letter in any method name for classes and object literals. // The "[" for computed methods in classes and object literals. return node.typeProfilingReturnDivot; } updateTypes(nodesToUpdate, callback) { console.assert(this._script.target.hasCommand("Runtime.getRuntimeTypesForVariablesAtOffsets")); console.assert(Array.isArray(nodesToUpdate) && this._parsedSuccessfully); if (!this._parsedSuccessfully) return; var allRequests = []; var allRequestNodes = []; var sourceID = this._script.id; for (var node of nodesToUpdate) { switch (node.type) { case WI.ScriptSyntaxTree.NodeType.FunctionDeclaration: case WI.ScriptSyntaxTree.NodeType.FunctionExpression: case WI.ScriptSyntaxTree.NodeType.ArrowFunctionExpression: for (var param of node.params) { for (var identifier of this._gatherIdentifiersInDeclaration(param)) { allRequests.push({ typeInformationDescriptor: WI.ScriptSyntaxTree.TypeProfilerSearchDescriptor.NormalExpression, sourceID, divot: identifier.range[0] }); allRequestNodes.push(identifier); } } allRequests.push({ typeInformationDescriptor: WI.ScriptSyntaxTree.TypeProfilerSearchDescriptor.FunctionReturn, sourceID, divot: WI.ScriptSyntaxTree.functionReturnDivot(node) }); allRequestNodes.push(node); break; case WI.ScriptSyntaxTree.NodeType.VariableDeclarator: for (var identifier of this._gatherIdentifiersInDeclaration(node.id)) { allRequests.push({ typeInformationDescriptor: WI.ScriptSyntaxTree.TypeProfilerSearchDescriptor.NormalExpression, sourceID, divot: identifier.range[0] }); allRequestNodes.push(identifier); } break; } } console.assert(allRequests.length === allRequestNodes.length); function handleTypes(error, typeInformationArray) { if (error) return; console.assert(typeInformationArray.length === allRequests.length); for (var i = 0; i < typeInformationArray.length; i++) { var node = allRequestNodes[i]; var typeInformation = WI.TypeDescription.fromPayload(typeInformationArray[i]); if (allRequests[i].typeInformationDescriptor === WI.ScriptSyntaxTree.TypeProfilerSearchDescriptor.FunctionReturn) node.attachments.returnTypes = typeInformation; else node.attachments.types = typeInformation; } callback(allRequestNodes); } this._script.target.RuntimeAgent.getRuntimeTypesForVariablesAtOffsets(allRequests, handleTypes); } // Private _gatherIdentifiersInDeclaration(node) { function gatherIdentifiers(node) { switch (node.type) { case WI.ScriptSyntaxTree.NodeType.Identifier: return [node]; case WI.ScriptSyntaxTree.NodeType.Property: return gatherIdentifiers(node.value); case WI.ScriptSyntaxTree.NodeType.ObjectPattern: var identifiers = []; for (var property of node.properties) { for (var identifier of gatherIdentifiers(property)) identifiers.push(identifier); } return identifiers; case WI.ScriptSyntaxTree.NodeType.ArrayPattern: var identifiers = []; for (var element of node.elements) { for (var identifier of gatherIdentifiers(element)) identifiers.push(identifier); } return identifiers; case WI.ScriptSyntaxTree.NodeType.AssignmentPattern: return gatherIdentifiers(node.left); case WI.ScriptSyntaxTree.NodeType.RestElement: return gatherIdentifiers(node.argument); default: console.assert(false, "Unexpected node type in variable declarator: " + node.type); return []; } } console.assert(node.type === WI.ScriptSyntaxTree.NodeType.Identifier || node.type === WI.ScriptSyntaxTree.NodeType.ObjectPattern || node.type === WI.ScriptSyntaxTree.NodeType.ArrayPattern || node.type === WI.ScriptSyntaxTree.NodeType.RestElement); return gatherIdentifiers(node); } _defaultParserState() { return { shouldStopEarly: false, skipChildNodes: false }; } _recurse(node, callback, state) { if (!node) return; if (state.shouldStopEarly || state.skipChildNodes) return; callback(node, state); switch (node.type) { case WI.ScriptSyntaxTree.NodeType.AssignmentExpression: this._recurse(node.left, callback, state); this._recurse(node.right, callback, state); break; case WI.ScriptSyntaxTree.NodeType.ArrayExpression: case WI.ScriptSyntaxTree.NodeType.ArrayPattern: this._recurseArray(node.elements, callback, state); break; case WI.ScriptSyntaxTree.NodeType.AssignmentPattern: this._recurse(node.left, callback, state); this._recurse(node.right, callback, state); break; case WI.ScriptSyntaxTree.NodeType.AwaitExpression: this._recurse(node.argument, callback, state); break; case WI.ScriptSyntaxTree.NodeType.BlockStatement: case WI.ScriptSyntaxTree.NodeType.StaticBlock: this._recurseArray(node.body, callback, state); break; case WI.ScriptSyntaxTree.NodeType.BinaryExpression: this._recurse(node.left, callback, state); this._recurse(node.right, callback, state); break; case WI.ScriptSyntaxTree.NodeType.BreakStatement: this._recurse(node.label, callback, state); break; case WI.ScriptSyntaxTree.NodeType.CatchClause: this._recurse(node.param, callback, state); this._recurse(node.body, callback, state); break; case WI.ScriptSyntaxTree.NodeType.CallExpression: this._recurse(node.callee, callback, state); this._recurseArray(node.arguments, callback, state); break; case WI.ScriptSyntaxTree.NodeType.ClassBody: this._recurseArray(node.body, callback, state); break; case WI.ScriptSyntaxTree.NodeType.ClassDeclaration: case WI.ScriptSyntaxTree.NodeType.ClassExpression: this._recurse(node.id, callback, state); this._recurse(node.superClass, callback, state); this._recurse(node.body, callback, state); break; case WI.ScriptSyntaxTree.NodeType.ContinueStatement: this._recurse(node.label, callback, state); break; case WI.ScriptSyntaxTree.NodeType.DoWhileStatement: this._recurse(node.body, callback, state); this._recurse(node.test, callback, state); break; case WI.ScriptSyntaxTree.NodeType.ChainExpression: case WI.ScriptSyntaxTree.NodeType.ExpressionStatement: this._recurse(node.expression, callback, state); break; case WI.ScriptSyntaxTree.NodeType.ForStatement: this._recurse(node.init, callback, state); this._recurse(node.test, callback, state); this._recurse(node.update, callback, state); this._recurse(node.body, callback, state); break; case WI.ScriptSyntaxTree.NodeType.ForInStatement: case WI.ScriptSyntaxTree.NodeType.ForOfStatement: this._recurse(node.left, callback, state); this._recurse(node.right, callback, state); this._recurse(node.body, callback, state); break; case WI.ScriptSyntaxTree.NodeType.FunctionDeclaration: case WI.ScriptSyntaxTree.NodeType.FunctionExpression: case WI.ScriptSyntaxTree.NodeType.ArrowFunctionExpression: this._recurse(node.id, callback, state); this._recurseArray(node.params, callback, state); this._recurse(node.body, callback, state); break; case WI.ScriptSyntaxTree.NodeType.IfStatement: this._recurse(node.test, callback, state); this._recurse(node.consequent, callback, state); this._recurse(node.alternate, callback, state); break; case WI.ScriptSyntaxTree.NodeType.LabeledStatement: this._recurse(node.label, callback, state); this._recurse(node.body, callback, state); break; case WI.ScriptSyntaxTree.NodeType.LogicalExpression: this._recurse(node.left, callback, state); this._recurse(node.right, callback, state); break; case WI.ScriptSyntaxTree.NodeType.MemberExpression: this._recurse(node.object, callback, state); this._recurse(node.property, callback, state); break; case WI.ScriptSyntaxTree.NodeType.MethodDefinition: this._recurse(node.key, callback, state); this._recurse(node.value, callback, state); break; case WI.ScriptSyntaxTree.NodeType.NewExpression: this._recurse(node.callee, callback, state); this._recurseArray(node.arguments, callback, state); break; case WI.ScriptSyntaxTree.NodeType.ObjectExpression: case WI.ScriptSyntaxTree.NodeType.ObjectPattern: this._recurseArray(node.properties, callback, state); break; case WI.ScriptSyntaxTree.NodeType.Program: this._recurseArray(node.body, callback, state); break; case WI.ScriptSyntaxTree.NodeType.Property: this._recurse(node.key, callback, state); this._recurse(node.value, callback, state); break; case WI.ScriptSyntaxTree.NodeType.RestElement: this._recurse(node.argument, callback, state); break; case WI.ScriptSyntaxTree.NodeType.ReturnStatement: this._recurse(node.argument, callback, state); break; case WI.ScriptSyntaxTree.NodeType.SequenceExpression: this._recurseArray(node.expressions, callback, state); break; case WI.ScriptSyntaxTree.NodeType.SpreadElement: this._recurse(node.argument, callback, state); break; case WI.ScriptSyntaxTree.NodeType.SwitchStatement: this._recurse(node.discriminant, callback, state); this._recurseArray(node.cases, callback, state); break; case WI.ScriptSyntaxTree.NodeType.SwitchCase: this._recurse(node.test, callback, state); this._recurseArray(node.consequent, callback, state); break; case WI.ScriptSyntaxTree.NodeType.ConditionalExpression: this._recurse(node.test, callback, state); this._recurse(node.consequent, callback, state); this._recurse(node.alternate, callback, state); break; case WI.ScriptSyntaxTree.NodeType.TaggedTemplateExpression: this._recurse(node.tag, callback, state); this._recurse(node.quasi, callback, state); break; case WI.ScriptSyntaxTree.NodeType.TemplateLiteral: this._recurseArray(node.quasis, callback, state); this._recurseArray(node.expressions, callback, state); break; case WI.ScriptSyntaxTree.NodeType.ThrowStatement: this._recurse(node.argument, callback, state); break; case WI.ScriptSyntaxTree.NodeType.TryStatement: this._recurse(node.block, callback, state); this._recurse(node.handler, callback, state); this._recurse(node.finalizer, callback, state); break; case WI.ScriptSyntaxTree.NodeType.UnaryExpression: this._recurse(node.argument, callback, state); break; case WI.ScriptSyntaxTree.NodeType.UpdateExpression: this._recurse(node.argument, callback, state); break; case WI.ScriptSyntaxTree.NodeType.VariableDeclaration: this._recurseArray(node.declarations, callback, state); break; case WI.ScriptSyntaxTree.NodeType.VariableDeclarator: this._recurse(node.id, callback, state); this._recurse(node.init, callback, state); break; case WI.ScriptSyntaxTree.NodeType.WhileStatement: this._recurse(node.test, callback, state); this._recurse(node.body, callback, state); break; case WI.ScriptSyntaxTree.NodeType.WithStatement: this._recurse(node.object, callback, state); this._recurse(node.body, callback, state); break; case WI.ScriptSyntaxTree.NodeType.YieldExpression: this._recurse(node.argument, callback, state); break; // Modules. case WI.ScriptSyntaxTree.NodeType.ExportAllDeclaration: this._recurse(node.source, callback, state); this._recurseArray(node.assertions, callback, state); break; case WI.ScriptSyntaxTree.NodeType.ExportNamedDeclaration: this._recurse(node.declaration, callback, state); this._recurseArray(node.specifiers, callback, state); this._recurse(node.source, callback, state); this._recurseArray(node.assertions, callback, state); break; case WI.ScriptSyntaxTree.NodeType.ExportDefaultDeclaration: this._recurse(node.declaration, callback, state); break; case WI.ScriptSyntaxTree.NodeType.ExportSpecifier: this._recurse(node.local, callback, state); this._recurse(node.exported, callback, state); break; case WI.ScriptSyntaxTree.NodeType.ImportAttribute: this._recurse(node.key, callback, state); this._recurse(node.value, callback, state); break; case WI.ScriptSyntaxTree.NodeType.ImportDeclaration: this._recurseArray(node.specifiers, callback, state); this._recurse(node.source, callback, state); this._recurseArray(node.assertions, callback, state); break; case WI.ScriptSyntaxTree.NodeType.ImportDefaultSpecifier: this._recurse(node.local, callback, state); break; case WI.ScriptSyntaxTree.NodeType.ImportExpression: this._recurse(node.source, callback, state); this._recurse(node.attributes, callback, state); break; case WI.ScriptSyntaxTree.NodeType.ImportNamespaceSpecifier: this._recurse(node.local, callback, state); break; case WI.ScriptSyntaxTree.NodeType.ImportSpecifier: this._recurse(node.imported, callback, state); this._recurse(node.local, callback, state); break; // All the leaf nodes go here. case WI.ScriptSyntaxTree.NodeType.DebuggerStatement: case WI.ScriptSyntaxTree.NodeType.EmptyStatement: case WI.ScriptSyntaxTree.NodeType.Identifier: case WI.ScriptSyntaxTree.NodeType.Literal: case WI.ScriptSyntaxTree.NodeType.MetaProperty: case WI.ScriptSyntaxTree.NodeType.Super: case WI.ScriptSyntaxTree.NodeType.ThisExpression: case WI.ScriptSyntaxTree.NodeType.TemplateElement: break; } state.skipChildNodes = false; } _recurseArray(array, callback, state) { for (var node of array) this._recurse(node, callback, state); } // This function translates from esprima's Abstract Syntax Tree to ours. // Mostly, this is just the identity function. We've added an extra typeProfilingReturnDivot property for functions/methods. // Our AST complies with the Mozilla parser API: // https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/Parser_API _createInternalSyntaxTree(node) { if (!node) return null; var result = null; switch (node.type) { case "ArrayExpression": result = { type: WI.ScriptSyntaxTree.NodeType.ArrayExpression, elements: node.elements.map(this._createInternalSyntaxTree, this) }; break; case "ArrayPattern": result = { type: WI.ScriptSyntaxTree.NodeType.ArrayPattern, elements: node.elements.map(this._createInternalSyntaxTree, this) }; break; case "ArrowFunctionExpression": result = { type: WI.ScriptSyntaxTree.NodeType.ArrowFunctionExpression, id: this._createInternalSyntaxTree(node.id), params: node.params.map(this._createInternalSyntaxTree, this), body: this._createInternalSyntaxTree(node.body), generator: node.generator, expression: node.expression, // Boolean indicating if the body a single expression or a block statement. async: node.async, typeProfilingReturnDivot: node.range[0] }; break; case "AssignmentExpression": result = { type: WI.ScriptSyntaxTree.NodeType.AssignmentExpression, operator: node.operator, left: this._createInternalSyntaxTree(node.left), right: this._createInternalSyntaxTree(node.right) }; break; case "AssignmentPattern": result = { type: WI.ScriptSyntaxTree.NodeType.AssignmentPattern, left: this._createInternalSyntaxTree(node.left), right: this._createInternalSyntaxTree(node.right), }; break; case "AwaitExpression": result = { type: WI.ScriptSyntaxTree.NodeType.AwaitExpression, argument: this._createInternalSyntaxTree(node.argument), }; break; case "BlockStatement": result = { type: WI.ScriptSyntaxTree.NodeType.BlockStatement, body: node.body.map(this._createInternalSyntaxTree, this) }; break; case "BinaryExpression": result = { type: WI.ScriptSyntaxTree.NodeType.BinaryExpression, operator: node.operator, left: this._createInternalSyntaxTree(node.left), right: this._createInternalSyntaxTree(node.right) }; break; case "BreakStatement": result = { type: WI.ScriptSyntaxTree.NodeType.BreakStatement, label: this._createInternalSyntaxTree(node.label) }; break; case "CallExpression": result = { type: WI.ScriptSyntaxTree.NodeType.CallExpression, callee: this._createInternalSyntaxTree(node.callee), arguments: node.arguments.map(this._createInternalSyntaxTree, this) }; break; case "CatchClause": result = { type: WI.ScriptSyntaxTree.NodeType.CatchClause, param: this._createInternalSyntaxTree(node.param), body: this._createInternalSyntaxTree(node.body) }; break; case "ChainExpression": result = { type: WI.ScriptSyntaxTree.NodeType.ChainExpression, expression: this._createInternalSyntaxTree(node.expression), }; break; case "ClassBody": result = { type: WI.ScriptSyntaxTree.NodeType.ClassBody, body: node.body.map(this._createInternalSyntaxTree, this) }; break; case "ClassDeclaration": result = { type: WI.ScriptSyntaxTree.NodeType.ClassDeclaration, id: this._createInternalSyntaxTree(node.id), superClass: this._createInternalSyntaxTree(node.superClass), body: this._createInternalSyntaxTree(node.body), }; break; case "ClassExpression": result = { type: WI.ScriptSyntaxTree.NodeType.ClassExpression, id: this._createInternalSyntaxTree(node.id), superClass: this._createInternalSyntaxTree(node.superClass), body: this._createInternalSyntaxTree(node.body), }; break; case "ConditionalExpression": result = { type: WI.ScriptSyntaxTree.NodeType.ConditionalExpression, test: this._createInternalSyntaxTree(node.test), consequent: this._createInternalSyntaxTree(node.consequent), alternate: this._createInternalSyntaxTree(node.alternate) }; break; case "ContinueStatement": result = { type: WI.ScriptSyntaxTree.NodeType.ContinueStatement, label: this._createInternalSyntaxTree(node.label) }; break; case "DoWhileStatement": result = { type: WI.ScriptSyntaxTree.NodeType.DoWhileStatement, body: this._createInternalSyntaxTree(node.body), test: this._createInternalSyntaxTree(node.test) }; break; case "DebuggerStatement": result = { type: WI.ScriptSyntaxTree.NodeType.DebuggerStatement }; break; case "EmptyStatement": result = { type: WI.ScriptSyntaxTree.NodeType.EmptyStatement }; break; case "ExpressionStatement": result = { type: WI.ScriptSyntaxTree.NodeType.ExpressionStatement, expression: this._createInternalSyntaxTree(node.expression) }; break; case "ForStatement": result = { type: WI.ScriptSyntaxTree.NodeType.ForStatement, init: this._createInternalSyntaxTree(node.init), test: this._createInternalSyntaxTree(node.test), update: this._createInternalSyntaxTree(node.update), body: this._createInternalSyntaxTree(node.body) }; break; case "ForInStatement": result = { type: WI.ScriptSyntaxTree.NodeType.ForInStatement, left: this._createInternalSyntaxTree(node.left), right: this._createInternalSyntaxTree(node.right), body: this._createInternalSyntaxTree(node.body) }; break; case "ForOfStatement": result = { type: WI.ScriptSyntaxTree.NodeType.ForOfStatement, left: this._createInternalSyntaxTree(node.left), right: this._createInternalSyntaxTree(node.right), body: this._createInternalSyntaxTree(node.body), await: node.await }; break; case "FunctionDeclaration": result = { type: WI.ScriptSyntaxTree.NodeType.FunctionDeclaration, id: this._createInternalSyntaxTree(node.id), params: node.params.map(this._createInternalSyntaxTree, this), body: this._createInternalSyntaxTree(node.body), generator: node.generator, async: node.async, typeProfilingReturnDivot: node.range[0] }; break; case "FunctionExpression": result = { type: WI.ScriptSyntaxTree.NodeType.FunctionExpression, id: this._createInternalSyntaxTree(node.id), params: node.params.map(this._createInternalSyntaxTree, this), body: this._createInternalSyntaxTree(node.body), generator: node.generator, async: node.async, typeProfilingReturnDivot: node.range[0] // This may be overridden in the Property AST node. }; break; case "Identifier": result = { type: WI.ScriptSyntaxTree.NodeType.Identifier, name: node.name }; break; case "IfStatement": result = { type: WI.ScriptSyntaxTree.NodeType.IfStatement, test: this._createInternalSyntaxTree(node.test), consequent: this._createInternalSyntaxTree(node.consequent), alternate: this._createInternalSyntaxTree(node.alternate) }; break; case "Literal": result = { type: WI.ScriptSyntaxTree.NodeType.Literal, value: node.value, raw: node.raw }; break; case "LabeledStatement": result = { type: WI.ScriptSyntaxTree.NodeType.LabeledStatement, label: this._createInternalSyntaxTree(node.label), body: this._createInternalSyntaxTree(node.body) }; break; case "LogicalExpression": result = { type: WI.ScriptSyntaxTree.NodeType.LogicalExpression, left: this._createInternalSyntaxTree(node.left), right: this._createInternalSyntaxTree(node.right), operator: node.operator }; break; case "MemberExpression": result = { type: WI.ScriptSyntaxTree.NodeType.MemberExpression, object: this._createInternalSyntaxTree(node.object), property: this._createInternalSyntaxTree(node.property), computed: node.computed }; break; case "MetaProperty": // i.e: new.target produces {meta: "new", property: "target"} result = { type: WI.ScriptSyntaxTree.NodeType.MetaProperty, meta: this._createInternalSyntaxTree(node.meta), property: this._createInternalSyntaxTree(node.property), }; break; case "MethodDefinition": result = { type: WI.ScriptSyntaxTree.NodeType.MethodDefinition, key: this._createInternalSyntaxTree(node.key), value: this._createInternalSyntaxTree(node.value), computed: node.computed, kind: node.kind, static: node.static }; result.value.typeProfilingReturnDivot = node.range[0]; // "g" in "get" or "s" in "set" or "[" in "['computed']" or "m" in "methodName". break; case "NewExpression": result = { type: WI.ScriptSyntaxTree.NodeType.NewExpression, callee: this._createInternalSyntaxTree(node.callee), arguments: node.arguments.map(this._createInternalSyntaxTree, this) }; break; case "ObjectExpression": result = { type: WI.ScriptSyntaxTree.NodeType.ObjectExpression, properties: node.properties.map(this._createInternalSyntaxTree, this) }; break; case "ObjectPattern": result = { type: WI.ScriptSyntaxTree.NodeType.ObjectPattern, properties: node.properties.map(this._createInternalSyntaxTree, this) }; break; case "PrivateIdentifier": result = { type: WI.ScriptSyntaxTree.NodeType.PrivateIdentifier, name: node.name, }; break; case "Program": result = { type: WI.ScriptSyntaxTree.NodeType.Program, sourceType: node.sourceType, body: node.body.map(this._createInternalSyntaxTree, this) }; break; case "Property": result = { type: WI.ScriptSyntaxTree.NodeType.Property, key: this._createInternalSyntaxTree(node.key), value: this._createInternalSyntaxTree(node.value), kind: node.kind, method: node.method, computed: node.computed }; if (result.kind === "get" || result.kind === "set" || result.method) result.value.typeProfilingReturnDivot = node.range[0]; // "g" in "get" or "s" in "set" or "[" in "['computed']" method or "m" in "methodName". break; case "RestElement": result = { type: WI.ScriptSyntaxTree.NodeType.RestElement, argument: this._createInternalSyntaxTree(node.argument) }; break; case "ReturnStatement": result = { type: WI.ScriptSyntaxTree.NodeType.ReturnStatement, argument: this._createInternalSyntaxTree(node.argument) }; break; case "SequenceExpression": result = { type: WI.ScriptSyntaxTree.NodeType.SequenceExpression, expressions: node.expressions.map(this._createInternalSyntaxTree, this) }; break; case "SpreadElement": result = { type: WI.ScriptSyntaxTree.NodeType.SpreadElement, argument: this._createInternalSyntaxTree(node.argument), }; break; case "StaticBlock": result = { type: WI.ScriptSyntaxTree.NodeType.StaticBlock, body: node.body.map(this._createInternalSyntaxTree, this), }; break; case "Super": result = { type: WI.ScriptSyntaxTree.NodeType.Super }; break; case "SwitchStatement": result = { type: WI.ScriptSyntaxTree.NodeType.SwitchStatement, discriminant: this._createInternalSyntaxTree(node.discriminant), cases: node.cases.map(this._createInternalSyntaxTree, this) }; break; case "SwitchCase": result = { type: WI.ScriptSyntaxTree.NodeType.SwitchCase, test: this._createInternalSyntaxTree(node.test), consequent: node.consequent.map(this._createInternalSyntaxTree, this) }; break; case "TaggedTemplateExpression": result = { type: WI.ScriptSyntaxTree.NodeType.TaggedTemplateExpression, tag: this._createInternalSyntaxTree(node.tag), quasi: this._createInternalSyntaxTree(node.quasi) }; break; case "TemplateElement": result = { type: WI.ScriptSyntaxTree.NodeType.TemplateElement, value: node.value, tail: node.tail }; break; case "TemplateLiteral": result = { type: WI.ScriptSyntaxTree.NodeType.TemplateLiteral, quasis: node.quasis.map(this._createInternalSyntaxTree, this), expressions: node.expressions.map(this._createInternalSyntaxTree, this) }; break; case "ThisExpression": result = { type: WI.ScriptSyntaxTree.NodeType.ThisExpression }; break; case "ThrowStatement": result = { type: WI.ScriptSyntaxTree.NodeType.ThrowStatement, argument: this._createInternalSyntaxTree(node.argument) }; break; case "TryStatement": result = { type: WI.ScriptSyntaxTree.NodeType.TryStatement, block: this._createInternalSyntaxTree(node.block), handler: this._createInternalSyntaxTree(node.handler), finalizer: this._createInternalSyntaxTree(node.finalizer) }; break; case "UnaryExpression": result = { type: WI.ScriptSyntaxTree.NodeType.UnaryExpression, operator: node.operator, argument: this._createInternalSyntaxTree(node.argument) }; break; case "UpdateExpression": result = { type: WI.ScriptSyntaxTree.NodeType.UpdateExpression, operator: node.operator, prefix: node.prefix, argument: this._createInternalSyntaxTree(node.argument) }; break; case "VariableDeclaration": result = { type: WI.ScriptSyntaxTree.NodeType.VariableDeclaration, declarations: node.declarations.map(this._createInternalSyntaxTree, this), kind: node.kind }; break; case "VariableDeclarator": result = { type: WI.ScriptSyntaxTree.NodeType.VariableDeclarator, id: this._createInternalSyntaxTree(node.id), init: this._createInternalSyntaxTree(node.init) }; break; case "WhileStatement": result = { type: WI.ScriptSyntaxTree.NodeType.WhileStatement, test: this._createInternalSyntaxTree(node.test), body: this._createInternalSyntaxTree(node.body) }; break; case "WithStatement": result = { type: WI.ScriptSyntaxTree.NodeType.WithStatement, object: this._createInternalSyntaxTree(node.object), body: this._createInternalSyntaxTree(node.body) }; break; case "YieldExpression": result = { type: WI.ScriptSyntaxTree.NodeType.YieldExpression, argument: this._createInternalSyntaxTree(node.argument), delegate: node.delegate }; break; // Modules. case "ExportAllDeclaration": result = { type: WI.ScriptSyntaxTree.NodeType.ExportAllDeclaration, source: this._createInternalSyntaxTree(node.source), assertions: node.assertions?.map(this._createInternalSyntaxTree, this) ?? [], }; break; case "ExportNamedDeclaration": result = { type: WI.ScriptSyntaxTree.NodeType.ExportNamedDeclaration, declaration: this._createInternalSyntaxTree(node.declaration), specifiers: node.specifiers.map(this._createInternalSyntaxTree, this), source: this._createInternalSyntaxTree(node.source), assertions: node.assertions?.map(this._createInternalSyntaxTree, this) ?? [], }; break; case "ExportDefaultDeclaration": result = { type: WI.ScriptSyntaxTree.NodeType.ExportDefaultDeclaration, declaration: this._createInternalSyntaxTree(node.declaration), }; break; case "ExportSpecifier": result = { type: WI.ScriptSyntaxTree.NodeType.ExportSpecifier, local: this._createInternalSyntaxTree(node.local), exported: this._createInternalSyntaxTree(node.exported), }; break; case "ImportAttribute": result = { type: WI.ScriptSyntaxTree.NodeType.ImportAttribute, key: this._createInternalSyntaxTree(node.key), value: this._createInternalSyntaxTree(node.value), }; break; case "ImportExpression": result = { type: WI.ScriptSyntaxTree.NodeType.ImportExpression, source: this._createInternalSyntaxTree(node.source), attributes: this._createInternalSyntaxTree(node.attributes), }; break; case "ImportDeclaration": result = { type: WI.ScriptSyntaxTree.NodeType.ImportDeclaration, specifiers: node.specifiers.map(this._createInternalSyntaxTree, this), source: this._createInternalSyntaxTree(node.source), assertions: node.assertions?.map(this._createInternalSyntaxTree, this) ?? [], }; break; case "ImportDefaultSpecifier": result = { type: WI.ScriptSyntaxTree.NodeType.ImportDefaultSpecifier, local: this._createInternalSyntaxTree(node.local), }; break; case "ImportNamespaceSpecifier": result = { type: WI.ScriptSyntaxTree.NodeType.ImportNamespaceSpecifier, local: this._createInternalSyntaxTree(node.local), }; break; case "ImportSpecifier": result = { type: WI.ScriptSyntaxTree.NodeType.ImportSpecifier, imported: this._createInternalSyntaxTree(node.imported), local: this._createInternalSyntaxTree(node.local), }; break; default: console.error("Unsupported Syntax Tree Node: " + node.type, JSON.stringify(node)); return null; } console.assert(node.loc || node.type === "ChainExpression"); if (node.loc) { let {start, end} = node.loc; result.startPosition = new WI.SourceCodePosition(start.line - 1, start.column); result.endPosition = new WI.SourceCodePosition(end.line - 1, end.column); } result.range = node.range; // This is an object for which you can add fields to an AST node without worrying about polluting the syntax-related fields of the node. result.attachments = {}; return result; } }; // This should be kept in sync with an enum in JavaSciptCore/runtime/TypeProfiler.h WI.ScriptSyntaxTree.TypeProfilerSearchDescriptor = { NormalExpression: 1, FunctionReturn: 2 }; WI.ScriptSyntaxTree.NodeType = { ArrayExpression: Symbol("array-expression"), ArrayPattern: Symbol("array-pattern"), ArrowFunctionExpression: Symbol("arrow-function-expression"), AssignmentExpression: Symbol("assignment-expression"), AssignmentPattern: Symbol("assignment-pattern"), AwaitExpression: Symbol("await-expression"), BinaryExpression: Symbol("binary-expression"), BlockStatement: Symbol("block-statement"), BreakStatement: Symbol("break-statement"), CallExpression: Symbol("call-expression"), CatchClause: Symbol("catch-clause"), ChainExpression: Symbol("chain-expression"), ClassBody: Symbol("class-body"), ClassDeclaration: Symbol("class-declaration"), ClassExpression: Symbol("class-expression"), ConditionalExpression: Symbol("conditional-expression"), ContinueStatement: Symbol("continue-statement"), DebuggerStatement: Symbol("debugger-statement"), DoWhileStatement: Symbol("do-while-statement"), EmptyStatement: Symbol("empty-statement"), ExportAllDeclaration: Symbol("export-all-declaration"), ExportDefaultDeclaration: Symbol("export-default-declaration"), ExportNamedDeclaration: Symbol("export-named-declaration"), ExportSpecifier: Symbol("export-specifier"), ExpressionStatement: Symbol("expression-statement"), ForInStatement: Symbol("for-in-statement"), ForOfStatement: Symbol("for-of-statement"), ForStatement: Symbol("for-statement"), FunctionDeclaration: Symbol("function-declaration"), FunctionExpression: Symbol("function-expression"), Identifier: Symbol("identifier"), IfStatement: Symbol("if-statement"), ImportAttribute: Symbol("import-attribute"), ImportDeclaration: Symbol("import-declaration"), ImportDefaultSpecifier: Symbol("import-default-specifier"), ImportExpression: Symbol("import-expression"), ImportNamespaceSpecifier: Symbol("import-namespace-specifier"), ImportSpecifier: Symbol("import-specifier"), LabeledStatement: Symbol("labeled-statement"), Literal: Symbol("literal"), LogicalExpression: Symbol("logical-expression"), MemberExpression: Symbol("member-expression"), MetaProperty: Symbol("meta-property"), MethodDefinition: Symbol("method-definition"), NewExpression: Symbol("new-expression"), ObjectExpression: Symbol("object-expression"), ObjectPattern: Symbol("object-pattern"), PrivateIdentifier: Symbol("private-identifier"), Program: Symbol("program"), Property: Symbol("property"), RestElement: Symbol("rest-element"), ReturnStatement: Symbol("return-statement"), SequenceExpression: Symbol("sequence-expression"), SpreadElement: Symbol("spread-element"), StaticBlock: Symbol("static-block"), Super: Symbol("super"), SwitchCase: Symbol("switch-case"), SwitchStatement: Symbol("switch-statement"), TaggedTemplateExpression: Symbol("tagged-template-expression"), TemplateElement: Symbol("template-element"), TemplateLiteral: Symbol("template-literal"), ThisExpression: Symbol("this-expression"), ThrowStatement: Symbol("throw-statement"), TryStatement: Symbol("try-statement"), UnaryExpression: Symbol("unary-expression"), UpdateExpression: Symbol("update-expression"), VariableDeclaration: Symbol("variable-declaration"), VariableDeclarator: Symbol("variable-declarator"), WhileStatement: Symbol("while-statement"), WithStatement: Symbol("with-statement"), YieldExpression: Symbol("yield-expression"), }; /* Models/ScriptTimelineRecord.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. */ WI.ScriptTimelineRecord = class ScriptTimelineRecord extends WI.TimelineRecord { constructor(eventType, startTime, endTime, stackTrace, sourceCodeLocation, details, profilePayload, extraDetails) { super(WI.TimelineRecord.Type.Script, startTime, endTime, stackTrace, sourceCodeLocation); console.assert(eventType); if (eventType in WI.ScriptTimelineRecord.EventType) eventType = WI.ScriptTimelineRecord.EventType[eventType]; this._eventType = eventType; this._details = details || ""; this._profilePayload = profilePayload || null; this._profile = null; this._extraDetails = extraDetails || null; // NOTE: _callCountOrSamples is being treated as the number of samples. this._callCountOrSamples = 0; } // Import / Export static async fromJSON(json) { let {eventType, startTime, endTime, stackTrace, sourceCodeLocation, details, profilePayload, extraDetails} = json; if (typeof details === "object" && details.__type === "GarbageCollection") details = WI.GarbageCollection.fromJSON(details); return new WI.ScriptTimelineRecord(eventType, startTime, endTime, stackTrace, sourceCodeLocation, details, profilePayload, extraDetails); } toJSON() { // FIXME: stackTrace // FIXME: sourceCodeLocation // FIXME: profilePayload return { type: this.type, eventType: this._eventType, startTime: this.startTime, endTime: this.endTime, details: this._details, extraDetails: this._extraDetails, }; } // Public get eventType() { return this._eventType; } get details() { return this._details; } get extraDetails() { return this._extraDetails; } get callCountOrSamples() { return this._callCountOrSamples; } get profile() { this._initializeProfileFromPayload(); return this._profile; } isGarbageCollection() { return this._eventType === WI.ScriptTimelineRecord.EventType.GarbageCollected; } saveIdentityToCookie(cookie) { super.saveIdentityToCookie(cookie); cookie[WI.ScriptTimelineRecord.EventTypeCookieKey] = this._eventType; cookie[WI.ScriptTimelineRecord.DetailsCookieKey] = this._details; } get profilePayload() { return this._profilePayload; } set profilePayload(payload) { this._profilePayload = payload; } // Private _initializeProfileFromPayload(payload) { if (this._profile || !this._profilePayload) return; var payload = this._profilePayload; this._profilePayload = undefined; console.assert(payload.rootNodes instanceof Array); function profileNodeFromPayload(nodePayload) { console.assert("id" in nodePayload); if (nodePayload.url) { let sourceCode = WI.networkManager.resourcesForURL(nodePayload.url).firstValue; if (!sourceCode) sourceCode = WI.debuggerManager.scriptsForURL(nodePayload.url, WI.assumingMainTarget())[0]; // The lineNumber is 1-based, but we expect 0-based. var lineNumber = nodePayload.lineNumber - 1; var sourceCodeLocation = sourceCode ? sourceCode.createLazySourceCodeLocation(lineNumber, nodePayload.columnNumber) : null; } var isProgramCode = nodePayload.functionName === "(program)"; var isAnonymousFunction = nodePayload.functionName === "(anonymous function)"; var type = isProgramCode ? WI.ProfileNode.Type.Program : WI.ProfileNode.Type.Function; var functionName = !isProgramCode && !isAnonymousFunction && nodePayload.functionName !== "(unknown)" ? nodePayload.functionName : null; return new WI.ProfileNode(nodePayload.id, type, functionName, sourceCodeLocation, nodePayload.callInfo, nodePayload.children); } function profileNodeCallFromPayload(nodeCallPayload) { console.assert("startTime" in nodeCallPayload); console.assert("totalTime" in nodeCallPayload); var startTime = WI.timelineManager.computeElapsedTime(nodeCallPayload.startTime); return new WI.ProfileNodeCall(startTime, nodeCallPayload.totalTime); } var rootNodes = payload.rootNodes; // Iterate over the node tree using a stack. Doing this recursively can easily cause a stack overflow. // We traverse the profile in post-order and convert the payloads in place until we get back to the root. var stack = [{parent: {children: rootNodes}, index: 0, root: true}]; while (stack.length) { var entry = stack.lastValue; if (entry.index < entry.parent.children.length) { var childNodePayload = entry.parent.children[entry.index]; if (childNodePayload.children && childNodePayload.children.length) stack.push({parent: childNodePayload, index: 0}); ++entry.index; } else { if (!entry.root) entry.parent.children = entry.parent.children.map(profileNodeFromPayload); else rootNodes = rootNodes.map(profileNodeFromPayload); stack.pop(); } } for (let rootNode of rootNodes) this._callCountOrSamples += rootNode.callInfo.callCount; this._profile = new WI.Profile(rootNodes); } }; WI.ScriptTimelineRecord.EventType = { ScriptEvaluated: "script-evaluated", APIScriptEvaluated: "api-script-evaluated", MicrotaskDispatched: "microtask-dispatched", EventDispatched: "event-dispatched", ProbeSampleRecorded: "probe-sample-recorded", TimerFired: "timer-fired", TimerInstalled: "timer-installed", TimerRemoved: "timer-removed", AnimationFrameFired: "animation-frame-fired", AnimationFrameRequested: "animation-frame-requested", AnimationFrameCanceled: "animation-frame-canceled", ObserverCallback: "observer-callback", ConsoleProfileRecorded: "console-profile-recorded", GarbageCollected: "garbage-collected", }; WI.ScriptTimelineRecord.EventType.displayName = function(eventType, details, includeDetailsInMainTitle) { if (details && !WI.ScriptTimelineRecord._eventDisplayNames) { // These display names are not localized because they closely represent // the real API name, just with word spaces and Title Case. var nameMap = new Map; nameMap.set("DOMActivate", "DOM Activate"); nameMap.set("DOMCharacterDataModified", "DOM Character Data Modified"); nameMap.set("DOMContentLoaded", "DOM Content Loaded"); nameMap.set("DOMNodeInserted", "DOM Node Inserted"); nameMap.set("DOMNodeInsertedIntoDocument", "DOM Node Inserted Into Document"); nameMap.set("DOMNodeRemoved", "DOM Node Removed"); nameMap.set("DOMNodeRemovedFromDocument", "DOM Node Removed From Document"); nameMap.set("DOMSubtreeModified", "DOM Sub-Tree Modified"); nameMap.set("addsourcebuffer", "Add Source Buffer"); nameMap.set("addstream", "Add Stream"); nameMap.set("addtrack", "Add Track"); nameMap.set("animationcancel", "Animation Cancel"); nameMap.set("animationend", "Animation End"); nameMap.set("animationiteration", "Animation Iteration"); nameMap.set("animationstart", "Animation Start"); nameMap.set("audioend", "Audio End"); nameMap.set("audioprocess", "Audio Process"); nameMap.set("audiostart", "Audio Start"); nameMap.set("beforecopy", "Before Copy"); nameMap.set("beforecut", "Before Cut"); nameMap.set("beforeload", "Before Load"); nameMap.set("beforepaste", "Before Paste"); nameMap.set("beforeunload", "Before Unload"); nameMap.set("cancel", "Animation Cancel"); nameMap.set("canplay", "Can Play"); nameMap.set("canplaythrough", "Can Play Through"); nameMap.set("chargingchange", "Charging Change"); nameMap.set("chargingtimechange", "Charging Time Change"); nameMap.set("compositionend", "Composition End"); nameMap.set("compositionstart", "Composition Start"); nameMap.set("compositionupdate", "Composition Update"); nameMap.set("contextmenu", "Context Menu"); nameMap.set("cuechange", "Cue Change"); nameMap.set("datachannel", "Data Channel"); nameMap.set("dblclick", "Double Click"); nameMap.set("devicemotion", "Device Motion"); nameMap.set("deviceorientation", "Device Orientation"); nameMap.set("dischargingtimechange", "Discharging Time Change"); nameMap.set("dragend", "Drag End"); nameMap.set("dragenter", "Drag Enter"); nameMap.set("dragleave", "Drag Leave"); nameMap.set("dragover", "Drag Over"); nameMap.set("dragstart", "Drag Start"); nameMap.set("durationchange", "Duration Change"); nameMap.set("finish", "Animation Finish"); nameMap.set("focusin", "Focus In"); nameMap.set("focusout", "Focus Out"); nameMap.set("formdata", "Form submission or invocation of FormData()"); nameMap.set("gesturechange", "Gesture Change"); nameMap.set("gestureend", "Gesture End"); nameMap.set("gesturescrollend", "Gesture Scroll End"); nameMap.set("gesturescrollstart", "Gesture Scroll Start"); nameMap.set("gesturescrollupdate", "Gesture Scroll Update"); nameMap.set("gesturestart", "Gesture Start"); nameMap.set("gesturetap", "Gesture Tap"); nameMap.set("gesturetapdown", "Gesture Tap Down"); nameMap.set("hashchange", "Hash Change"); nameMap.set("icecandidate", "ICE Candidate"); nameMap.set("iceconnectionstatechange", "ICE Connection State Change"); nameMap.set("keydown", "Key Down"); nameMap.set("keypress", "Key Press"); nameMap.set("keyup", "Key Up"); nameMap.set("levelchange", "Level Change"); nameMap.set("loadeddata", "Loaded Data"); nameMap.set("loadedmetadata", "Loaded Metadata"); nameMap.set("loadend", "Load End"); nameMap.set("loadingdone", "Loading Done"); nameMap.set("loadstart", "Load Start"); nameMap.set("mousedown", "Mouse Down"); nameMap.set("mouseenter", "Mouse Enter"); nameMap.set("mouseleave", "Mouse Leave"); nameMap.set("mousemove", "Mouse Move"); nameMap.set("mouseout", "Mouse Out"); nameMap.set("mouseover", "Mouse Over"); nameMap.set("mouseup", "Mouse Up"); nameMap.set("mousewheel", "Mouse Wheel"); nameMap.set("negotiationneeded", "Negotiation Needed"); nameMap.set("nomatch", "No Match"); nameMap.set("noupdate", "No Update"); nameMap.set("orientationchange", "Orientation Change"); nameMap.set("overflowchanged", "Overflow Changed"); nameMap.set("pagehide", "Page Hide"); nameMap.set("pageshow", "Page Show"); nameMap.set("popstate", "Pop State"); nameMap.set("ratechange", "Rate Change"); nameMap.set("readystatechange", "Ready State Change"); nameMap.set("remove", "Animation Remove"); nameMap.set("removesourcebuffer", "Remove Source Buffer"); nameMap.set("removestream", "Remove Stream"); nameMap.set("removetrack", "Remove Track"); nameMap.set("resize", "Resize"); nameMap.set("securitypolicyviolation", "Security Policy Violation"); nameMap.set("selectionchange", "Selection Change"); nameMap.set("selectstart", "Select Start"); nameMap.set("signalingstatechange", "Signaling State Change"); nameMap.set("soundend", "Sound End"); nameMap.set("soundstart", "Sound Start"); nameMap.set("sourceclose", "Source Close"); nameMap.set("sourceended", "Source Ended"); nameMap.set("sourceopen", "Source Open"); nameMap.set("speechend", "Speech End"); nameMap.set("speechstart", "Speech Start"); nameMap.set("textInput", "Text Input"); nameMap.set("timeupdate", "Time Update"); nameMap.set("tonechange", "Tone Change"); nameMap.set("touchcancel", "Touch Cancel"); nameMap.set("touchend", "Touch End"); nameMap.set("touchmove", "Touch Move"); nameMap.set("touchstart", "Touch Start"); nameMap.set("transitioncancel", "Transition Cancel"); nameMap.set("transitionend", "Transition End"); nameMap.set("transitionrun", "Transition Run"); nameMap.set("transitionstart", "Transition Start"); nameMap.set("updateend", "Update End"); nameMap.set("updateready", "Update Ready"); nameMap.set("updatestart", "Update Start"); nameMap.set("upgradeneeded", "Upgrade Needed"); nameMap.set("versionchange", "Version Change"); nameMap.set("visibilitychange", "Visibility Change"); nameMap.set("volumechange", "Volume Change"); nameMap.set("webglcontextcreationerror", "WebGL Context Creation Error"); nameMap.set("webglcontextlost", "WebGL Context Lost"); nameMap.set("webglcontextrestored", "WebGL Context Restored"); nameMap.set("webkitAnimationEnd", "Animation End"); nameMap.set("webkitAnimationIteration", "Animation Iteration"); nameMap.set("webkitAnimationStart", "Animation Start"); nameMap.set("webkitBeforeTextInserted", "Before Text Inserted"); nameMap.set("webkitEditableContentChanged", "Editable Content Changed"); nameMap.set("webkitTransitionEnd", "Transition End"); nameMap.set("webkitaddsourcebuffer", "Add Source Buffer"); nameMap.set("webkitbeginfullscreen", "Begin Full-Screen"); nameMap.set("webkitcurrentplaybacktargetiswirelesschanged", "Current Playback Target Is Wireless Changed"); nameMap.set("webkitendfullscreen", "End Full-Screen"); nameMap.set("webkitfullscreenchange", "Full-Screen Change"); nameMap.set("webkitfullscreenerror", "Full-Screen Error"); nameMap.set("webkitkeyadded", "Key Added"); nameMap.set("webkitkeyerror", "Key Error"); nameMap.set("webkitkeymessage", "Key Message"); nameMap.set("webkitneedkey", "Need Key"); nameMap.set("webkitnetworkinfochange", "Network Info Change"); nameMap.set("webkitplaybacktargetavailabilitychanged", "Playback Target Availability Changed"); nameMap.set("webkitpointerlockchange", "Pointer Lock Change"); nameMap.set("webkitpointerlockerror", "Pointer Lock Error"); nameMap.set("webkitremovesourcebuffer", "Remove Source Buffer"); nameMap.set("webkitresourcetimingbufferfull", "Resource Timing Buffer Full"); nameMap.set("webkitsourceclose", "Source Close"); nameMap.set("webkitsourceended", "Source Ended"); nameMap.set("webkitsourceopen", "Source Open"); nameMap.set("writeend", "Write End"); nameMap.set("writestart", "Write Start"); WI.ScriptTimelineRecord._eventDisplayNames = nameMap; } switch (eventType) { case WI.ScriptTimelineRecord.EventType.ScriptEvaluated: case WI.ScriptTimelineRecord.EventType.APIScriptEvaluated: return WI.UIString("Script Evaluated"); case WI.ScriptTimelineRecord.EventType.MicrotaskDispatched: return WI.UIString("Microtask Dispatched"); case WI.ScriptTimelineRecord.EventType.EventDispatched: if (details && (details instanceof String || typeof details === "string")) { var eventDisplayName = WI.ScriptTimelineRecord._eventDisplayNames.get(details) || details.capitalize(); return WI.UIString("%s Event Dispatched").format(eventDisplayName); } return WI.UIString("Event Dispatched"); case WI.ScriptTimelineRecord.EventType.ProbeSampleRecorded: return WI.UIString("Probe Sample Recorded"); case WI.ScriptTimelineRecord.EventType.ConsoleProfileRecorded: if (details && (details instanceof String || typeof details === "string")) return WI.UIString("\u201C%s\u201D Profile Recorded").format(details); return WI.UIString("Console Profile Recorded"); case WI.ScriptTimelineRecord.EventType.GarbageCollected: console.assert(details); if (details && (details instanceof WI.GarbageCollection) && includeDetailsInMainTitle) { switch (details.type) { case WI.GarbageCollection.Type.Partial: return WI.UIString("Partial Garbage Collection"); case WI.GarbageCollection.Type.Full: return WI.UIString("Full Garbage Collection"); } } return WI.UIString("Garbage Collection"); case WI.ScriptTimelineRecord.EventType.TimerFired: if (details && includeDetailsInMainTitle) return WI.UIString("Timer %d Fired").format(details); return WI.UIString("Timer Fired"); case WI.ScriptTimelineRecord.EventType.TimerInstalled: if (details && includeDetailsInMainTitle) return WI.UIString("Timer %d Installed").format(details.timerId); return WI.UIString("Timer Installed"); case WI.ScriptTimelineRecord.EventType.TimerRemoved: if (details && includeDetailsInMainTitle) return WI.UIString("Timer %d Removed").format(details); return WI.UIString("Timer Removed"); case WI.ScriptTimelineRecord.EventType.AnimationFrameFired: if (details && includeDetailsInMainTitle) return WI.UIString("Animation Frame %d Fired").format(details); return WI.UIString("Animation Frame Fired"); case WI.ScriptTimelineRecord.EventType.ObserverCallback: if (details && (details instanceof String || typeof details === "string")) return WI.UIString("%s Callback").format(details); return WI.UIString("Observer Callback"); case WI.ScriptTimelineRecord.EventType.AnimationFrameRequested: if (details && includeDetailsInMainTitle) return WI.UIString("Animation Frame %d Requested").format(details); return WI.UIString("Animation Frame Requested"); case WI.ScriptTimelineRecord.EventType.AnimationFrameCanceled: if (details && includeDetailsInMainTitle) return WI.UIString("Animation Frame %d Canceled").format(details); return WI.UIString("Animation Frame Canceled"); } }; WI.ScriptTimelineRecord.TypeIdentifier = "script-timeline-record"; WI.ScriptTimelineRecord.EventTypeCookieKey = "script-timeline-record-event-type"; WI.ScriptTimelineRecord.DetailsCookieKey = "script-timeline-record-details"; /* Models/ServerTimingEntry.js */ /* * Copyright (C) 2018 Apple Inc. All rights reserved. * Copyright 2017 The Chromium Authors. 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.ServerTimingEntry = class ServerTimingEntry { constructor(name) { this._name = name; this._duration = undefined; this._description = undefined; } // Static static parseHeaders(valueString = "") { // https://w3c.github.io/server-timing/#the-server-timing-header-field function trimLeadingWhiteSpace() { valueString = valueString.trimStart(); } function consumeDelimiter(char) { console.assert(char.length === 1); trimLeadingWhiteSpace(); if (valueString.charAt(0) !== char) return false; valueString = valueString.substring(1); return true; } function consumeToken() { // https://tools.ietf.org/html/rfc7230#appendix-B let result = /^(?:\s*)([\w!#$%&'*+\-.^`|~]+)(?:\s*)(.*)/.exec(valueString); if (!result) return null; valueString = result[2]; return result[1]; } function consumeTokenOrQuotedString() { trimLeadingWhiteSpace(); if (valueString.charAt(0) === "\"") return consumeQuotedString(); return consumeToken(); } function consumeQuotedString() { // https://tools.ietf.org/html/rfc7230#section-3.2.6 console.assert(valueString.charAt(0) === "\""); // Consume the leading DQUOTE. valueString = valueString.substring(1); let unescapedValueString = ""; for (let i = 0; i < valueString.length; ++i) { let char = valueString.charAt(i); switch (char) { case "\\": // Backslash character found, ignore it. ++i; if (i < valueString.length) { // Take the character after the backslash. unescapedValueString += valueString.charAt(i); } break; case "\"": // Trailing DQUOTE. valueString = valueString.substring(i + 1); return unescapedValueString; default: unescapedValueString += char; break; } } // No trailing DQUOTE found, this was not a valid quoted-string. Consume the entire string to complete parsing. valueString = ""; return null; } function consumeExtraneous() { let result = /([,;].*)/.exec(valueString); if (result) valueString = result[1]; } function getParserForParameter(paramName) { switch (paramName) { case "dur": return function(paramValue, entry) { if (paramValue !== null) { let duration = parseFloat(paramValue); if (!isNaN(duration)) { entry.duration = duration; return; } } entry.duration = 0; }; case "desc": return function(paramValue, entry) { entry.description = paramValue || ""; }; default: return null; } } let entries = []; let name; while ((name = consumeToken()) !== null) { let entry = new WI.ServerTimingEntry(name); while (consumeDelimiter(";")) { let paramName; if ((paramName = consumeToken()) === null) continue; paramName = paramName.toLowerCase(); let parseParameter = getParserForParameter(paramName); let paramValue = null; if (consumeDelimiter("=")) { // Always parse the value, even if we don't recognize the parameter name. paramValue = consumeTokenOrQuotedString(); consumeExtraneous(); } if (parseParameter) parseParameter(paramValue, entry); else console.warn("Unknown Server-Timing parameter:", paramName, paramValue); } entries.push(entry); if (!consumeDelimiter(",")) break; } return entries; } // Public get name() { return this._name; } get duration() { return this._duration; } get description() { return this._description; } set duration(duration) { if (this._duration !== undefined) { console.warn("Ignoring redundant duration."); return; } this._duration = duration; } set description(description) { if (this._description !== undefined) { console.warn("Ignoring redundant description."); return; } this._description = description; } }; /* Models/ShaderProgram.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 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.ShaderProgram = class ShaderProgram extends WI.Object { constructor(target, identifier, programType, canvas, {sharesVertexFragmentShader} = {}) { console.assert(target instanceof WI.Target, target); console.assert(target === canvas.target, target, canvas.target); console.assert(identifier); console.assert(Object.values(ShaderProgram.ProgramType).includes(programType)); console.assert(canvas instanceof WI.Canvas); console.assert(ShaderProgram.contextTypeSupportsProgramType(canvas.contextType, programType)); super(); this._target = target; this._identifier = identifier; this._programType = programType; this._canvas = canvas; this._sharesVertexFragmentShader = !!sharesVertexFragmentShader; console.assert(!this._sharesVertexFragmentShader || (this._canvas.contextType === WI.Canvas.ContextType.WebGPU && this._programType === ShaderProgram.ProgramType.Render)); this._disabled = false; } // Static static contextTypeSupportsProgramType(contextType, programType) { switch (contextType) { case WI.Canvas.ContextType.WebGL: case WI.Canvas.ContextType.OffscreenWebGL: case WI.Canvas.ContextType.WebGL2: case WI.Canvas.ContextType.OffscreenWebGL2: return programType === ShaderProgram.ProgramType.Render; case WI.Canvas.ContextType.WebGPU: return programType === ShaderProgram.ProgramType.Compute || programType === ShaderProgram.ProgramType.Render; } console.assert(); return false; } static programTypeSupportsShaderType(programType, shaderType) { switch (programType) { case ShaderProgram.ProgramType.Compute: return shaderType === ShaderProgram.ShaderType.Compute; case ShaderProgram.ProgramType.Render: return shaderType === ShaderProgram.ShaderType.Fragment || shaderType === ShaderProgram.ShaderType.Vertex; } console.assert(); return false; } // Public get target() { return this._target; } get identifier() { return this._identifier; } get programType() { return this._programType; } get canvas() { return this._canvas; } get sharesVertexFragmentShader() { return this._sharesVertexFragmentShader; } get displayName() { let format = null; switch (this._canvas.contextType) { case WI.Canvas.ContextType.WebGL: case WI.Canvas.ContextType.OffscreenWebGL: case WI.Canvas.ContextType.WebGL2: case WI.Canvas.ContextType.OffscreenWebGL2: format = WI.UIString("Program %d"); break; case WI.Canvas.ContextType.WebGPU: switch (this._programType) { case ShaderProgram.ProgramType.Compute: format = WI.UIString("Compute Pipeline %d"); break; case ShaderProgram.ProgramType.Render: format = WI.UIString("Render Pipeline %d"); break; } break; } console.assert(format); if (!this._uniqueDisplayNumber) this._uniqueDisplayNumber = this._canvas.nextShaderProgramDisplayNumberForProgramType(this._programType); return format.format(this._uniqueDisplayNumber); } get disabled() { return this._disabled; } set disabled(disabled) { console.assert(this._programType === ShaderProgram.ProgramType.Render); console.assert(this._canvas.isWebGL || this._canvas.isWebGL2); if (this._canvas.contextType === WI.Canvas.ContextType.WebGPU) return; if (this._disabled === disabled) return; this._disabled = disabled; this._target.CanvasAgent.setShaderProgramDisabled(this._identifier, disabled); this.dispatchEventToListeners(ShaderProgram.Event.DisabledChanged); } requestShaderSource(shaderType, callback) { console.assert(Object.values(ShaderProgram.ShaderType).includes(shaderType)); console.assert(ShaderProgram.programTypeSupportsShaderType(this._programType, shaderType)); // COMPATIBILITY (iOS 13): `content` was renamed to `source`. this._target.CanvasAgent.requestShaderSource(this._identifier, shaderType, (error, source) => { if (error) { WI.reportInternalError(error); callback(null); return; } callback(source); }); } updateShader(shaderType, source) { console.assert(Object.values(ShaderProgram.ShaderType).includes(shaderType)); console.assert(ShaderProgram.programTypeSupportsShaderType(this._programType, shaderType)); this._target.CanvasAgent.updateShader(this._identifier, shaderType, source); } showHighlight() { console.assert(this._programType === ShaderProgram.ProgramType.Render); console.assert(this._canvas.isWebGL || this._canvas.isWebGL2); this._target.CanvasAgent.setShaderProgramHighlighted(this._identifier, true); } hideHighlight() { console.assert(this._programType === ShaderProgram.ProgramType.Render); console.assert(this._canvas.isWebGL || this._canvas.isWebGL2); this._target.CanvasAgent.setShaderProgramHighlighted(this._identifier, false); } }; WI.ShaderProgram.ProgramType = { Compute: "compute", Render: "render", }; WI.ShaderProgram.ShaderType = { Compute: "compute", Fragment: "fragment", Vertex: "vertex", }; WI.ShaderProgram.Event = { DisabledChanged: "shader-program-disabled-changed", }; /* Models/SourceCodeRevision.js */ /* * Copyright (C) 2013-2019 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.SourceCodeRevision = class SourceCodeRevision extends WI.Revision { constructor(sourceCode, content, base64Encoded, mimeType) { super(); console.assert(sourceCode instanceof WI.SourceCode); console.assert(content === undefined || typeof content === "string"); console.assert(base64Encoded === undefined || typeof base64Encoded === "boolean"); console.assert(mimeType === undefined || typeof mimeType === "string"); this._sourceCode = sourceCode; this._content = content || ""; this._base64Encoded = !!base64Encoded; this._mimeType = mimeType; this._blobContent = null; } // Public get sourceCode() { return this._sourceCode; } get content() { return this._content; } get base64Encoded() { return this._base64Encoded; } get mimeType() { return this._mimeType; } get blobContent() { if (!this._blobContent && this._content) this._blobContent = WI.BlobUtilities.blobForContent(this._content, this._base64Encoded, this._mimeType); console.assert(!this._blobContent || this._blobContent instanceof Blob); return this._blobContent; } updateRevisionContent(content, {base64Encoded, mimeType, blobContent} = {}) { console.assert(content === undefined || typeof content === "string"); this._content = content || ""; if (base64Encoded !== undefined) { console.assert(typeof base64Encoded === "boolean"); this._base64Encoded = !!base64Encoded; } if (mimeType !== undefined) { console.assert(typeof mimeType === "string"); this._mimeType = mimeType; } console.assert(!blobContent || blobContent instanceof Blob); this._blobContent = blobContent !== undefined ? blobContent : null; this._sourceCode.revisionContentDidChange(this); } apply() { this._sourceCode.currentRevision = this; } revert() { this._sourceCode.currentRevision = this._sourceCode.originalRevision; } copy() { return new WI.SourceCodeRevision(this._sourceCode, this._content, this._base64Encoded, this._mimeType); } }; /* Models/SourceCodeTimeline.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. */ WI.SourceCodeTimeline = class SourceCodeTimeline extends WI.Timeline { constructor(sourceCode, sourceCodeLocation, recordType, recordEventType) { super(); console.assert(sourceCode); console.assert(!sourceCodeLocation || sourceCodeLocation.sourceCode === sourceCode); console.assert(recordType); this._sourceCode = sourceCode; this._sourceCodeLocation = sourceCodeLocation || null; this._recordType = recordType; this._recordEventType = recordEventType || null; } // Public get sourceCode() { return this._sourceCode; } get sourceCodeLocation() { return this._sourceCodeLocation; } get recordType() { return this._recordType; } get recordEventType() { return this._recordEventType; } saveIdentityToCookie(cookie) { cookie[WI.SourceCodeTimeline.SourceCodeURLCookieKey] = this._sourceCode.url ? this._sourceCode.url.hash : null; cookie[WI.SourceCodeTimeline.SourceCodeLocationLineCookieKey] = this._sourceCodeLocation ? this._sourceCodeLocation.lineNumber : null; cookie[WI.SourceCodeTimeline.SourceCodeLocationColumnCookieKey] = this._sourceCodeLocation ? this._sourceCodeLocation.columnNumber : null; cookie[WI.SourceCodeTimeline.RecordTypeCookieKey] = this._recordType || null; cookie[WI.SourceCodeTimeline.RecordEventTypeCookieKey] = this._recordEventType || null; } }; WI.SourceCodeTimeline.TypeIdentifier = "source-code-timeline"; WI.SourceCodeTimeline.SourceCodeURLCookieKey = "source-code-timeline-source-code-url"; WI.SourceCodeTimeline.SourceCodeLocationLineCookieKey = "source-code-timeline-source-code-location-line"; WI.SourceCodeTimeline.SourceCodeLocationColumnCookieKey = "source-code-timeline-source-code-location-column"; WI.SourceCodeTimeline.SourceCodeURLCookieKey = "source-code-timeline-source-code-url"; WI.SourceCodeTimeline.RecordTypeCookieKey = "source-code-timeline-record-type"; WI.SourceCodeTimeline.RecordEventTypeCookieKey = "source-code-timeline-record-event-type"; /* Models/SourceMap.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. */ WI.SourceMap = class SourceMap { constructor(sourceMappingURL, payload, originalSourceCode) { if (!WI.SourceMap._base64Map) { var base64Digits = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; WI.SourceMap._base64Map = {}; for (var i = 0; i < base64Digits.length; ++i) WI.SourceMap._base64Map[base64Digits.charAt(i)] = i; } this._originalSourceCode = originalSourceCode || null; this._sourceMapResources = {}; this._sourceMapResourcesList = []; this._sourceMappingURL = sourceMappingURL; this._reverseMappingsBySourceURL = {}; this._mappings = []; this._sources = {}; this._sourceRoot = null; this._sourceContentByURL = {}; this._parseMappingPayload(payload); } // Public get originalSourceCode() { return this._originalSourceCode; } get sourceMappingBasePathURLComponents() { if (this._sourceMappingURLBasePathComponents) return this._sourceMappingURLBasePathComponents; if (this._sourceRoot) { var baseURLPath = absoluteURL(this._sourceRoot, this._sourceMappingURL); console.assert(baseURLPath); if (baseURLPath) { var urlComponents = parseURL(baseURLPath); if (!/\/$/.test(urlComponents.path)) urlComponents.path += "/"; this._sourceMappingURLBasePathComponents = urlComponents; return this._sourceMappingURLBasePathComponents; } } var urlComponents = parseURL(this._sourceMappingURL); // Fallback for JavaScript debuggable named scripts that may not have a complete URL. if (!urlComponents.path) urlComponents.path = this._sourceMappingURL || ""; urlComponents.path = urlComponents.path.substr(0, urlComponents.path.lastIndexOf(urlComponents.lastPathComponent)); urlComponents.lastPathComponent = null; this._sourceMappingURLBasePathComponents = urlComponents; return this._sourceMappingURLBasePathComponents; } get resources() { return this._sourceMapResourcesList; } addResource(resource) { console.assert(!(resource.url in this._sourceMapResources)); this._sourceMapResources[resource.url] = resource; this._sourceMapResourcesList.push(resource); } resourceForURL(url) { return this._sourceMapResources[url]; } sources() { return Object.keys(this._sources); } sourceContent(sourceURL) { return this._sourceContentByURL[sourceURL]; } _parseMappingPayload(mappingPayload) { if (mappingPayload.sections) this._parseSections(mappingPayload.sections); else this._parseMap(mappingPayload, 0, 0); } _parseSections(sections) { for (var i = 0; i < sections.length; ++i) { var section = sections[i]; this._parseMap(section.map, section.offset.line, section.offset.column); } } findEntry(lineNumber, columnNumber) { var first = 0; var count = this._mappings.length; while (count > 1) { var step = count >> 1; var middle = first + step; var mapping = this._mappings[middle]; if (lineNumber < mapping[0] || (lineNumber === mapping[0] && columnNumber < mapping[1])) count = step; else { first = middle; count -= step; } } var entry = this._mappings[first]; if (!first && entry && (lineNumber < entry[0] || (lineNumber === entry[0] && columnNumber < entry[1]))) return null; return entry; } findEntryReversed(sourceURL, lineNumber) { var mappings = this._reverseMappingsBySourceURL[sourceURL]; for ( ; lineNumber < mappings.length; ++lineNumber) { var mapping = mappings[lineNumber]; if (mapping) return mapping; } return this._mappings[0]; } _parseMap(map, lineNumber, columnNumber) { var sourceIndex = 0; var sourceLineNumber = 0; var sourceColumnNumber = 0; var nameIndex = 0; var sources = []; var originalToCanonicalURLMap = {}; for (var i = 0; i < map.sources.length; ++i) { var originalSourceURL = map.sources[i]; var href = originalSourceURL; if (map.sourceRoot && href.charAt(0) !== "/") href = map.sourceRoot.replace(/\/+$/, "") + "/" + href; var url = absoluteURL(href, this._sourceMappingURL) || href; originalToCanonicalURLMap[originalSourceURL] = url; sources.push(url); this._sources[url] = true; if (map.sourcesContent && map.sourcesContent[i]) this._sourceContentByURL[url] = map.sourcesContent[i]; } this._sourceRoot = map.sourceRoot || null; var stringCharIterator = new WI.SourceMap.StringCharIterator(map.mappings); var sourceURL = sources[sourceIndex]; while (true) { if (stringCharIterator.peek() === ",") stringCharIterator.next(); else { while (stringCharIterator.peek() === ";") { lineNumber += 1; columnNumber = 0; stringCharIterator.next(); } if (!stringCharIterator.hasNext()) break; } columnNumber += this._decodeVLQ(stringCharIterator); if (this._isSeparator(stringCharIterator.peek())) { this._mappings.push([lineNumber, columnNumber]); continue; } var sourceIndexDelta = this._decodeVLQ(stringCharIterator); if (sourceIndexDelta) { sourceIndex += sourceIndexDelta; sourceURL = sources[sourceIndex]; } sourceLineNumber += this._decodeVLQ(stringCharIterator); sourceColumnNumber += this._decodeVLQ(stringCharIterator); if (!this._isSeparator(stringCharIterator.peek())) nameIndex += this._decodeVLQ(stringCharIterator); this._mappings.push([lineNumber, columnNumber, sourceURL, sourceLineNumber, sourceColumnNumber]); } for (var i = 0; i < this._mappings.length; ++i) { var mapping = this._mappings[i]; var url = mapping[2]; if (!url) continue; if (!this._reverseMappingsBySourceURL[url]) this._reverseMappingsBySourceURL[url] = []; var reverseMappings = this._reverseMappingsBySourceURL[url]; var sourceLine = mapping[3]; if (!reverseMappings[sourceLine]) reverseMappings[sourceLine] = [mapping[0], mapping[1]]; } } _isSeparator(char) { return char === "," || char === ";"; } _decodeVLQ(stringCharIterator) { // Read unsigned value. var result = 0; var shift = 0; do { var digit = WI.SourceMap._base64Map[stringCharIterator.next()]; result += (digit & WI.SourceMap.VLQ_BASE_MASK) << shift; shift += WI.SourceMap.VLQ_BASE_SHIFT; } while (digit & WI.SourceMap.VLQ_CONTINUATION_MASK); // Fix the sign. var negative = result & 1; result >>= 1; return negative ? -result : result; } }; WI.SourceMap.VLQ_BASE_SHIFT = 5; WI.SourceMap.VLQ_BASE_MASK = (1 << 5) - 1; WI.SourceMap.VLQ_CONTINUATION_MASK = 1 << 5; WI.SourceMap.StringCharIterator = class StringCharIterator { constructor(string) { this._string = string; this._position = 0; } next() { return this._string.charAt(this._position++); } peek() { return this._string.charAt(this._position); } hasNext() { return this._position < this._string.length; } }; /* Models/SpringTimingFunction.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. */ WI.SpringTimingFunction = class SpringTimingFunction { constructor(mass, stiffness, damping, initialVelocity) { this.mass = Math.max(1, mass); this.stiffness = Math.max(1, stiffness); this.damping = Math.max(0, damping); this.initialVelocity = initialVelocity; } // Static static fromValues(values) { if (!values || values.length < 4) return null; values = values.map(Number); if (values.includes(NaN)) return null; return new WI.SpringTimingFunction(...values); } static fromString(text) { if (!text || !text.length) return null; let trimmedText = text.toLowerCase().trim(); if (!trimmedText.length) return null; let matches = trimmedText.match(/^spring\(([\d.]+)\s+([\d.]+)\s+([\d.]+)\s+([-\d.]+)\)$/); if (!matches) return null; return WI.SpringTimingFunction.fromValues(matches.slice(1)); } // Public copy() { return new WI.SpringTimingFunction(this.mass, this.stiffness, this.damping, this.initialVelocity); } toString() { return `spring(${this.mass} ${this.stiffness} ${this.damping} ${this.initialVelocity})`; } solve(t) { let w0 = Math.sqrt(this.stiffness / this.mass); let zeta = this.damping / (2 * Math.sqrt(this.stiffness * this.mass)); let wd = 0; let A = 1; let B = -this.initialVelocity + w0; if (zeta < 1) { // Under-damped. wd = w0 * Math.sqrt(1 - zeta * zeta); A = 1; B = (zeta * w0 + -this.initialVelocity) / wd; } if (zeta < 1) // Under-damped t = Math.exp(-t * zeta * w0) * (A * Math.cos(wd * t) + B * Math.sin(wd * t)); else // Critically damped (ignoring over-damped case). t = (A + B * t) * Math.exp(-t * w0); return 1 - t; // Map range from [1..0] to [0..1]. } calculateDuration(epsilon) { epsilon = epsilon || 0.0001; let t = 0; let current = 0; let minimum = Number.POSITIVE_INFINITY; while (current >= epsilon || minimum >= epsilon) { current = Math.abs(1 - this.solve(t)); // Undo the range mapping if (minimum < epsilon && current >= epsilon) minimum = Number.POSITIVE_INFINITY; // Spring reversed direction else if (current < minimum) minimum = current; t += 0.1; } return t; } }; /* Models/StackTrace.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 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.StackTrace = class StackTrace { constructor(callFrames, {topCallFrameIsBoundary, truncated, parentStackTrace} = {}) { console.assert(callFrames.every((callFrame) => callFrame instanceof WI.CallFrame), callFrames); console.assert(!parentStackTrace || parentStackTrace instanceof WI.StackTrace, parentStackTrace); this._callFrames = callFrames; this._topCallFrameIsBoundary = topCallFrameIsBoundary || false; this._truncated = truncated || false; this._parentStackTrace = parentStackTrace || null; } // Static static fromPayload(target, payload) { let result = null; let previousStackTrace = null; while (payload) { let callFrames = payload.callFrames.map((x) => WI.CallFrame.fromPayload(target, x)); let stackTrace = new WI.StackTrace(callFrames, { topCallFrameIsBoundary: payload.topCallFrameIsBoundary, truncated: payload.truncated, }); if (!result) result = stackTrace; if (previousStackTrace) previousStackTrace._parentStackTrace = stackTrace; previousStackTrace = stackTrace; payload = payload.parentStackTrace; } return result; } static fromString(target, stack) { let callFrames = WI.StackTrace._parseStackTrace(stack); return WI.StackTrace.fromPayload(target, {callFrames}); } // May produce false negatives; must not produce any false positives. // It may return false on a valid stack trace, but it will never return true on an invalid stack trace. static isLikelyStackTrace(stack) { // This function runs for every logged string. It penalizes the performance. // As most logged strings are not stack traces, exit as early as possible. const smallestPossibleStackTraceLength = "http://a.bc/:9:1".length; if (stack.length < smallestPossibleStackTraceLength.length * 2) return false; const approximateStackLengthOf50Items = 5000; if (stack.length > approximateStackLengthOf50Items) return false; if (/^[^a-z$_@]/i.test(stack[0])) return false; if (!WI.StackTrace._likelyStackTraceRegex) { const reasonablyLongProtocolLength = 10; const reasonablyLongLineLength = 500; const reasonablyLongNativeMethodLength = 120; const stackTraceLine = `(global code|eval code|module code|\\w+)?([^:]{1,${reasonablyLongProtocolLength}}://[^:]{1,${reasonablyLongLineLength}}:\\d+:\\d+|[^@]{1,${reasonablyLongNativeMethodLength}}@\\[native code\\])`; WI.StackTrace._likelyStackTraceRegex = new RegExp(`^${stackTraceLine}([\\n\\r]${stackTraceLine})+$`); } WI.StackTrace._likelyStackTraceRegex.lastIndex = 0; return WI.StackTrace._likelyStackTraceRegex.test(stack); } static _parseStackTrace(stack) { var lines = stack.split(/\n/g); var result = []; for (var line of lines) { var functionName = ""; var url = ""; var lineNumber = 0; var columnNumber = 0; var atIndex = line.indexOf("@"); if (atIndex !== -1) { functionName = line.slice(0, atIndex); ({url, lineNumber, columnNumber} = WI.StackTrace._parseLocation(line.slice(atIndex + 1))); } else if (line.includes("/")) ({url, lineNumber, columnNumber} = WI.StackTrace._parseLocation(line)); else functionName = line; result.push({functionName, url, lineNumber, columnNumber}); } return result; } static _parseLocation(locationString) { var result = {url: "", lineNumber: 0, columnNumber: 0}; var locationRegEx = /(.+?)(?::(\d+)(?::(\d+))?)?$/; var matched = locationString.match(locationRegEx); if (!matched) return result; result.url = matched[1]; if (matched[2]) result.lineNumber = parseInt(matched[2]); if (matched[3]) result.columnNumber = parseInt(matched[3]); return result; } // Public get callFrames() { return this._callFrames; } get topCallFrameIsBoundary() { return this._topCallFrameIsBoundary; } get truncated() { return this._truncated; } get parentStackTrace() { return this._parentStackTrace; } get firstNonNativeNonAnonymousNotBlackboxedCallFrame() { let firstNonNativeNonAnonymousCallFrame = null; for (let frame of this._callFrames) { if (frame.nativeCode) continue; if (frame.sourceCodeLocation) { let sourceCode = frame.sourceCodeLocation.sourceCode; if (sourceCode instanceof WI.Script && sourceCode.anonymous) continue; // Save the first non-native non-anonymous call frame so it can be used as a // fallback if all remaining call frames are blackboxed. firstNonNativeNonAnonymousCallFrame ??= frame; } if (frame.blackboxed) continue; return frame; } return firstNonNativeNonAnonymousCallFrame; } }; /* Models/StepsTimingFunction.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. */ WI.StepsTimingFunction = class StepsTimingFunction { constructor(type, count) { console.assert(Object.values(WI.StepsTimingFunction.Type).includes(type), type); console.assert(count > 0, count); this._type = type; this._count = count; } // Static static fromString(text) { if (!text?.length) return null; let trimmedText = text.toLowerCase().replace(/\s/g, ""); if (!trimmedText.length) return null; let keywordValue = WI.StepsTimingFunction.keywordValues[trimmedText]; if (keywordValue) return new WI.StepsTimingFunction(...keywordValue); let matches = trimmedText.match(/^steps\((\d+)(?:,([a-z-]+))?\)$/); if (!matches) return null; let type = matches[2] || WI.StepsTimingFunction.Type.JumpEnd; if (Object.values(WI.StepsTimingFunction).includes(type)) return null; let count = Number(matches[1]); if (isNaN(count) || count <= 0) return null; return new WI.StepsTimingFunction(type, count); } // Public get type() { return this._type; } get count() { return this._count; } copy() { return new WI.StepsTimingFunction(this._type, this._count); } toString() { if (this._type === WI.StepsTimingFunction.Type.JumpStart && this._count === 1) return "step-start"; if (this._type === WI.StepsTimingFunction.Type.JumpEnd && this._count === 1) return "step-end"; return `steps(${this._count}, ${this._type})`; } }; WI.StepsTimingFunction.Type = { JumpStart: "jump-start", JumpEnd: "jump-end", JumpNone: "jump-none", JumpBoth: "jump-both", Start: "start", End: "end", }; WI.StepsTimingFunction.keywordValues = { "step-start": [WI.StepsTimingFunction.Type.JumpStart, 1], "step-end": [WI.StepsTimingFunction.Type.JumpEnd, 1], }; /* Models/SymbolicBreakpoint.js */ /* * Copyright (C) 2022 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.SymbolicBreakpoint = class SymbolicBreakpoint extends WI.Breakpoint { constructor(symbol, {caseSensitive, isRegex, disabled, actions, condition, ignoreCount, autoContinue} = {}) { console.assert(typeof symbol === "string" && symbol.trim().length, symbol); super({disabled, condition, actions, ignoreCount, autoContinue}); this._symbol = symbol; this._caseSensitive = caseSensitive !== undefined ? !!caseSensitive : true; this._isRegex = isRegex !== undefined ? !!isRegex : false; } // Static static supported(target) { // COMPATIBILITY (iOS 16.0): Debugger.addSymbolicBreakpoint did exist yet. return (target || InspectorBackend).hasCommand("Debugger.addSymbolicBreakpoint"); } static fromJSON(json) { return new WI.SymbolicBreakpoint(json.symbol, { caseSensitive: json.caseSensitive, isRegex: json.isRegex, disabled: json.disabled, condition: json.condition, actions: json.actions?.map((actionJSON) => WI.BreakpointAction.fromJSON(actionJSON)) || [], ignoreCount: json.ignoreCount, autoContinue: json.autoContinue, }); } // Public get symbol() { return this._symbol; } get caseSensitive() { return this._caseSensitive; } get isRegex() { return this._isRegex; } get displayName() { if (this._isRegex) return "/" + this._symbol + "/" + (!this._caseSensitive ? "i" : ""); let displayName = this._symbol; if (!this._caseSensitive) displayName = WI.UIString("%s (Case Insensitive)", "%s (Case Insensitive) @ Symbolic Breakpoint", "Label for case-insensitive match pattern of a symbolic breakpoint.").format(displayName); return displayName; } get editable() { return true; } matches(symbol) { if (!symbol || this.disabled) return false; if (this._isRegex) return (new RegExp(this._symbol, !this._caseSensitive ? "i" : "")).test(symbol); if (!this._caseSensitive) return symbol.toLowerCase() === this._symbol.toLowerCase(); return symbol === this._symbol; } equals(other) { console.assert(other instanceof WI.SymbolicBreakpoint, other); return this._symbol === other.symbol && this._caseSensitive === other.caseSensitive && this._isRegex === other.isRegex; } remove() { super.remove(); WI.debuggerManager.removeSymbolicBreakpoint(this); } saveIdentityToCookie(cookie) { cookie["symbolic-breakpoint-symbol"] = this._symbol; cookie["symbolic-breakpoint-symbolic-case-sensitive"] = this._caseSensitive; cookie["symbolic-breakpoint-symbolic-is-regex"] = this._isRegex; } toJSON(key) { let json = super.toJSON(key); json.symbol = this._symbol; json.caseSensitive = this._caseSensitive; json.isRegex = this._isRegex; if (key === WI.ObjectStore.toJSONSymbol) json[WI.objectStores.eventBreakpoints.keyPath] = this._symbol + "-" + this._caseSensitive + "-" + this._isRegex; return json; } }; WI.SymbolicBreakpoint.ReferencePage = WI.ReferencePage.SymbolicBreakpoints; /* Models/TextRange.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. */ WI.TextRange = class TextRange { constructor(startLineOrStartOffset, startColumnOrEndOffset, endLine, endColumn) { if (arguments.length === 4) { console.assert(startLineOrStartOffset <= endLine); console.assert(startLineOrStartOffset !== endLine || startColumnOrEndOffset <= endColumn); this._startLine = typeof startLineOrStartOffset === "number" ? startLineOrStartOffset : NaN; this._startColumn = typeof startColumnOrEndOffset === "number" ? startColumnOrEndOffset : NaN; this._endLine = typeof endLine === "number" ? endLine : NaN; this._endColumn = typeof endColumn === "number" ? endColumn : NaN; this._startOffset = NaN; this._endOffset = NaN; } else if (arguments.length === 2) { console.assert(startLineOrStartOffset <= startColumnOrEndOffset); this._startOffset = typeof startLineOrStartOffset === "number" ? startLineOrStartOffset : NaN; this._endOffset = typeof startColumnOrEndOffset === "number" ? startColumnOrEndOffset : NaN; this._startLine = NaN; this._startColumn = NaN; this._endLine = NaN; this._endColumn = NaN; } } // Static static fromText(text) { let lines = text.split("\n"); return new WI.TextRange(0, 0, lines.length - 1, lines.lastValue.length); } // Public get startLine() { return this._startLine; } get startColumn() { return this._startColumn; } get endLine() { return this._endLine; } get endColumn() { return this._endColumn; } get startOffset() { return this._startOffset; } get endOffset() { return this._endOffset; } startPosition() { return new WI.SourceCodePosition(this._startLine, this._startColumn); } endPosition() { return new WI.SourceCodePosition(this._endLine, this._endColumn); } resolveOffsets(text) { console.assert(typeof text === "string"); if (typeof text !== "string") return; console.assert(!isNaN(this._startLine)); console.assert(!isNaN(this._startColumn)); console.assert(!isNaN(this._endLine)); console.assert(!isNaN(this._endColumn)); if (isNaN(this._startLine) || isNaN(this._startColumn) || isNaN(this._endLine) || isNaN(this._endColumn)) return; var lastNewLineOffset = 0; for (var i = 0; i < this._startLine; ++i) lastNewLineOffset = text.indexOf("\n", lastNewLineOffset) + 1; this._startOffset = lastNewLineOffset + this._startColumn; for (var i = this._startLine; i < this._endLine; ++i) lastNewLineOffset = text.indexOf("\n", lastNewLineOffset) + 1; this._endOffset = lastNewLineOffset + this._endColumn; } contains(line, column) { console.assert(!isNaN(this._startLine), "TextRange needs line/column data"); if (line < this._startLine || line > this._endLine) return false; if (line === this._startLine && column < this._startColumn) return false; if (line === this._endLine && column > this._endColumn) return false; return true; } clone() { console.assert(!isNaN(this._startLine), "TextRange needs line/column data."); return new WI.TextRange(this._startLine, this._startColumn, this._endLine, this._endColumn); } cloneAndModify(deltaStartLine, deltaStartColumn, deltaEndLine, deltaEndColumn) { console.assert(!isNaN(this._startLine), "TextRange needs line/column data."); let startLine = this._startLine + deltaStartLine; let startColumn = this._startColumn + deltaStartColumn; let endLine = this._endLine + deltaEndLine; let endColumn = this._endColumn + deltaEndColumn; console.assert(startLine >= 0 && startColumn >= 0 && endLine >= 0 && endColumn >= 0, `Cannot have negative numbers in TextRange ${startLine}:${startColumn}...${endLine}:${endColumn}`); return new WI.TextRange(startLine, startColumn, endLine, endColumn); } collapseToStart() { console.assert(!isNaN(this._startLine), "TextRange needs line/column data."); return new WI.TextRange(this._startLine, this._startColumn, this._startLine, this._startColumn); } collapseToEnd() { console.assert(!isNaN(this._endLine), "TextRange needs line/column data."); return new WI.TextRange(this._endLine, this._endColumn, this._endLine, this._endColumn); } relativeTo(line, column) { let deltaStartColumn = 0; if (this._startLine === line) deltaStartColumn = -column; let deltaEndColumn = 0; if (this._endLine === line) deltaEndColumn = -column; return this.cloneAndModify(-line, deltaStartColumn, -line, deltaEndColumn); } }; /* Models/TimelineMarker.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. */ WI.TimelineMarker = class TimelineMarker extends WI.Object { constructor(time, type, details) { super(); console.assert(type); this._time = time || 0; this._type = type; this._details = details || null; } // Import / Export static fromJSON(json) { let {time, type, details} = json; return new WI.TimelineMarker(time, type, details); } toJSON() { return { time: this._time, type: this._type, details: this._details || undefined, }; } // Public get type() { return this._type; } get details() { return this._details; } get time() { return this._time; } set time(x) { console.assert(typeof x === "number", "Time should be a number."); x = x || 0; if (this._time === x) return; this._time = x; this.dispatchEventToListeners(WI.TimelineMarker.Event.TimeChanged); } }; WI.TimelineMarker.Event = { TimeChanged: "timeline-marker-time-changed" }; WI.TimelineMarker.Type = { CurrentTime: "current-time", LoadEvent: "load-event", DOMContentEvent: "dom-content-event", TimeStamp: "timestamp", Scanner: "scanner", }; /* Models/TimelineRecording.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. */ WI.TimelineRecording = class TimelineRecording extends WI.Object { constructor(identifier, displayName, instruments) { super(); this._identifier = identifier; this._timelines = new Map; this._displayName = displayName; this._capturing = false; this._readonly = false; this._imported = false; this._instruments = instruments || []; this._startTime = NaN; this._endTime = NaN; this._discontinuityStartTime = NaN; this._discontinuities = null; this._firstRecordOfTypeAfterDiscontinuity = new Set; this._exportDataMarkers = null; this._exportDataRecords = null; this._exportDataMemoryPressureEvents = null; this._exportDataSampleStackTraces = null; this._exportDataSampleDurations = null; this._topDownCallingContextTree = new WI.CallingContextTree(WI.CallingContextTree.Type.TopDown); this._bottomUpCallingContextTree = new WI.CallingContextTree(WI.CallingContextTree.Type.BottomUp); this._topFunctionsTopDownCallingContextTree = new WI.CallingContextTree(WI.CallingContextTree.Type.TopFunctionsTopDown); this._topFunctionsBottomUpCallingContextTree = new WI.CallingContextTree(WI.CallingContextTree.Type.TopFunctionsBottomUp); for (let type of WI.TimelineManager.availableTimelineTypes()) { let timeline = WI.Timeline.create(type); this._timelines.set(type, timeline); timeline.addEventListener(WI.Timeline.Event.TimesUpdated, this._timelineTimesUpdated, this); } this.reset(true); } // Static static sourceCodeTimelinesSupported() { // FIXME: Support Network Timeline in ServiceWorker. return WI.sharedApp.isWebDebuggable(); } // Import / Export static async import(identifier, json, displayName) { let {startTime, endTime, discontinuities, instrumentTypes, records, markers, memoryPressureEvents, sampleStackTraces, sampleDurations} = json; let importedDisplayName = WI.UIString("Imported - %s").format(displayName); let instruments = instrumentTypes.map((type) => WI.Instrument.createForTimelineType(type)); let recording = new WI.TimelineRecording(identifier, importedDisplayName, instruments); recording._readonly = true; recording._imported = true; recording._startTime = startTime; recording._endTime = endTime; recording._discontinuities = discontinuities; recording.initializeCallingContextTrees(sampleStackTraces, sampleDurations); for (let recordJSON of records) { let record = await WI.TimelineRecord.fromJSON(recordJSON); if (record) { recording.addRecord(record); if (record instanceof WI.ScriptTimelineRecord) record.profilePayload = recording._topDownCallingContextTree.toCPUProfilePayload(record.startTime, record.endTime); } } for (let memoryPressureJSON of memoryPressureEvents) { let memoryPressureEvent = WI.MemoryPressureEvent.fromJSON(memoryPressureJSON); if (memoryPressureEvent) recording.addMemoryPressureEvent(memoryPressureEvent); } // Add markers once we've transitioned the active recording. setTimeout(() => { recording.__importing = true; for (let markerJSON of markers) { let marker = WI.TimelineMarker.fromJSON(markerJSON); if (marker) recording.addEventMarker(marker); } recording.__importing = false; }); return recording; } exportData() { console.assert(this.canExport(), "Attempted to export a recording which should not be exportable."); // FIXME: Overview data (sourceCodeTimelinesMap). // FIXME: Record hierarchy (parent / child relationship) is lost. return { displayName: this._displayName, startTime: this._startTime, endTime: this._endTime, discontinuities: this._discontinuities, instrumentTypes: this._instruments.map((instrument) => instrument.timelineRecordType), records: this._exportDataRecords, markers: this._exportDataMarkers, memoryPressureEvents: this._exportDataMemoryPressureEvents, sampleStackTraces: this._exportDataSampleStackTraces, sampleDurations: this._exportDataSampleDurations, }; } // Public get displayName() { return this._displayName; } get identifier() { return this._identifier; } get timelines() { return this._timelines; } get instruments() { return this._instruments; } get capturing() { return this._capturing; } get readonly() { return this._readonly; } get imported() { return this._imported; } get startTime() { return this._startTime; } get endTime() { return this._endTime; } get topDownCallingContextTree() { return this._topDownCallingContextTree; } get bottomUpCallingContextTree() { return this._bottomUpCallingContextTree; } get topFunctionsTopDownCallingContextTree() { return this._topFunctionsTopDownCallingContextTree; } get topFunctionsBottomUpCallingContextTree() { return this._topFunctionsBottomUpCallingContextTree; } start(initiatedByBackend) { console.assert(!this._capturing, "Attempted to start an already started session."); console.assert(!this._readonly, "Attempted to start a readonly session."); this._capturing = true; for (let instrument of this._instruments) instrument.startInstrumentation(initiatedByBackend); if (!isNaN(this._discontinuityStartTime)) { for (let instrument of this._instruments) this._firstRecordOfTypeAfterDiscontinuity.add(instrument.timelineRecordType); } } stop(initiatedByBackend) { console.assert(this._capturing, "Attempted to stop an already stopped session."); console.assert(!this._readonly, "Attempted to stop a readonly session."); this._capturing = false; for (let instrument of this._instruments) instrument.stopInstrumentation(initiatedByBackend); } capturingStarted(startTime) { // A discontinuity occurs when the recording is stopped and resumed at // a future time. Capturing started signals the end of the current // discontinuity, if one exists. if (!isNaN(this._discontinuityStartTime)) { this._discontinuities.push({ startTime: this._discontinuityStartTime, endTime: startTime, }); this._discontinuityStartTime = NaN; } } capturingStopped(endTime) { this._discontinuityStartTime = endTime; } saveIdentityToCookie() { // Do nothing. Timeline recordings are not persisted when the inspector is // re-opened, so do not attempt to restore by identifier or display name. } isEmpty() { for (var timeline of this._timelines.values()) { if (timeline.records.length) return false; } return true; } unloaded(importing) { console.assert(importing || !this.isEmpty(), "Shouldn't unload an empty recording; it should be reused instead."); this._readonly = true; this.dispatchEventToListeners(WI.TimelineRecording.Event.Unloaded); } reset(suppressEvents) { console.assert(!this._readonly, "Can't reset a read-only recording."); this._sourceCodeTimelinesMap = new Map; this._startTime = NaN; this._endTime = NaN; this._discontinuityStartTime = NaN; this._discontinuities = []; this._firstRecordOfTypeAfterDiscontinuity.clear(); this._exportDataMarkers = []; this._exportDataRecords = []; this._exportDataMemoryPressureEvents = []; this._exportDataSampleStackTraces = []; this._exportDataSampleDurations = []; this._topDownCallingContextTree.reset(); this._bottomUpCallingContextTree.reset(); this._topFunctionsTopDownCallingContextTree.reset(); this._topFunctionsBottomUpCallingContextTree.reset(); for (var timeline of this._timelines.values()) timeline.reset(suppressEvents); WI.RenderingFrameTimelineRecord.resetFrameIndex(); if (!suppressEvents) { this.dispatchEventToListeners(WI.TimelineRecording.Event.Reset); this.dispatchEventToListeners(WI.TimelineRecording.Event.TimesUpdated); } } get sourceCodeTimelines() { let timelines = []; for (let timelinesForSourceCode of this._sourceCodeTimelinesMap.values()) timelines.pushAll(timelinesForSourceCode.values()); return timelines; } timelineForInstrument(instrument) { return this._timelines.get(instrument.timelineRecordType); } instrumentForTimeline(timeline) { return this._instruments.find((instrument) => instrument.timelineRecordType === timeline.type); } timelineForRecordType(recordType) { return this._timelines.get(recordType); } addInstrument(instrument) { console.assert(instrument instanceof WI.Instrument, instrument); console.assert(!this._instruments.includes(instrument), this._instruments, instrument); this._instruments.push(instrument); this.dispatchEventToListeners(WI.TimelineRecording.Event.InstrumentAdded, {instrument}); } removeInstrument(instrument) { console.assert(instrument instanceof WI.Instrument, instrument); console.assert(this._instruments.includes(instrument), this._instruments, instrument); this._instruments.remove(instrument); this.dispatchEventToListeners(WI.TimelineRecording.Event.InstrumentRemoved, {instrument}); } addEventMarker(marker) { this._exportDataMarkers.push(marker); if (!this._capturing && !this.__importing) return; this.dispatchEventToListeners(WI.TimelineRecording.Event.MarkerAdded, {marker}); } addRecord(record) { this._exportDataRecords.push(record); let timeline = this._timelines.get(record.type); console.assert(timeline, record, this._timelines); if (!timeline) return; let discontinuity = null; if (this._firstRecordOfTypeAfterDiscontinuity.take(record.type)) discontinuity = this._discontinuities.lastValue; // Add the record to the global timeline by type. timeline.addRecord(record, {discontinuity}); // Some records don't have source code timelines. if (record.type === WI.TimelineRecord.Type.Network || record.type === WI.TimelineRecord.Type.RenderingFrame || record.type === WI.TimelineRecord.Type.CPU || record.type === WI.TimelineRecord.Type.Memory || record.type === WI.TimelineRecord.Type.HeapAllocations || record.type === WI.TimelineRecord.Type.Screenshots) return; if (!WI.TimelineRecording.sourceCodeTimelinesSupported()) return; // Add the record to the source code timelines. let sourceCode = null; if (record.sourceCodeLocation) sourceCode = record.sourceCodeLocation.sourceCode; else if (record.type === WI.TimelineRecord.Type.Media) { if (record.domNode && record.domNode.frame) sourceCode = record.domNode.frame.mainResource; } if (!sourceCode) sourceCode = WI.networkManager.mainFrame.provisionalMainResource || WI.networkManager.mainFrame.mainResource; var sourceCodeTimelines = this._sourceCodeTimelinesMap.get(sourceCode); if (!sourceCodeTimelines) { sourceCodeTimelines = new Map; this._sourceCodeTimelinesMap.set(sourceCode, sourceCodeTimelines); } var newTimeline = false; var key = this._keyForRecord(record); var sourceCodeTimeline = sourceCodeTimelines.get(key); if (!sourceCodeTimeline) { sourceCodeTimeline = new WI.SourceCodeTimeline(sourceCode, record.sourceCodeLocation, record.type, record.eventType); sourceCodeTimelines.set(key, sourceCodeTimeline); newTimeline = true; } sourceCodeTimeline.addRecord(record); if (newTimeline) this.dispatchEventToListeners(WI.TimelineRecording.Event.SourceCodeTimelineAdded, {sourceCodeTimeline}); } addMemoryPressureEvent(memoryPressureEvent) { this._exportDataMemoryPressureEvents.push(memoryPressureEvent); let memoryTimeline = this._timelines.get(WI.TimelineRecord.Type.Memory); console.assert(memoryTimeline, this._timelines); if (!memoryTimeline) return; memoryTimeline.addMemoryPressureEvent(memoryPressureEvent); } discontinuitiesInTimeRange(startTime, endTime) { return this._discontinuities.filter((item) => item.startTime <= endTime && item.endTime >= startTime); } addScriptInstrumentForProgrammaticCapture() { for (let instrument of this._instruments) { if (instrument instanceof WI.ScriptInstrument) return; } this.addInstrument(new WI.ScriptInstrument); let instrumentTypes = this._instruments.map((instrument) => instrument.timelineRecordType); WI.timelineManager.enabledTimelineTypes = instrumentTypes; } computeElapsedTime(timestamp) { if (!timestamp || isNaN(timestamp)) return NaN; return timestamp; } initializeTimeBoundsIfNecessary(timestamp) { if (isNaN(this._startTime)) { console.assert(isNaN(this._endTime)); this._startTime = timestamp; this._endTime = timestamp; this.dispatchEventToListeners(WI.TimelineRecording.Event.TimesUpdated); } } initializeCallingContextTrees(stackTraces, sampleDurations) { this._exportDataSampleStackTraces.pushAll(stackTraces); this._exportDataSampleDurations.pushAll(sampleDurations); for (let i = 0; i < stackTraces.length; i++) { this._topDownCallingContextTree.updateTreeWithStackTrace(stackTraces[i], sampleDurations[i]); this._bottomUpCallingContextTree.updateTreeWithStackTrace(stackTraces[i], sampleDurations[i]); this._topFunctionsTopDownCallingContextTree.updateTreeWithStackTrace(stackTraces[i], sampleDurations[i]); this._topFunctionsBottomUpCallingContextTree.updateTreeWithStackTrace(stackTraces[i], sampleDurations[i]); } } get exportMode() { return WI.FileUtilities.SaveMode.SingleFile; } canExport() { if (!WI.FileUtilities.canSave(this.exportMode)) return false; if (this._capturing) return false; if (isNaN(this._startTime)) return false; return true; } // Testing get markersForTesting() { return this._exportDataMarkers; } // Private _keyForRecord(record) { var key = record.type; if (record instanceof WI.ScriptTimelineRecord || record instanceof WI.LayoutTimelineRecord) key += ":" + record.eventType; if (record instanceof WI.ScriptTimelineRecord && record.eventType === WI.ScriptTimelineRecord.EventType.EventDispatched) key += ":" + record.details; if (record instanceof WI.MediaTimelineRecord) { key += ":" + record.eventType; if (record.eventType === WI.MediaTimelineRecord.EventType.DOMEvent) { if (record.domEvent && record.domEvent.eventName) key += ":" + record.domEvent.eventName; } else if (record.eventType === WI.MediaTimelineRecord.EventType.PowerEfficientPlaybackStateChanged) key += ":" + (record.isPowerEfficient ? "enabled" : "disabled"); } if (record.sourceCodeLocation) key += ":" + record.sourceCodeLocation.lineNumber + ":" + record.sourceCodeLocation.columnNumber; return key; } _timelineTimesUpdated(event) { var timeline = event.target; var changed = false; if (isNaN(this._startTime) || timeline.startTime < this._startTime) { this._startTime = timeline.startTime; changed = true; } if (isNaN(this._endTime) || this._endTime < timeline.endTime) { this._endTime = timeline.endTime; changed = true; } if (changed) this.dispatchEventToListeners(WI.TimelineRecording.Event.TimesUpdated); } }; WI.TimelineRecording.Event = { Reset: "timeline-recording-reset", Unloaded: "timeline-recording-unloaded", SourceCodeTimelineAdded: "timeline-recording-source-code-timeline-added", InstrumentAdded: "timeline-recording-instrument-added", InstrumentRemoved: "timeline-recording-instrument-removed", TimesUpdated: "timeline-recording-times-updated", MarkerAdded: "timeline-recording-marker-added", }; WI.TimelineRecording.SerializationVersion = 1; /* Models/URLBreakpoint.js */ /* * Copyright (C) 2019 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.URLBreakpoint = class URLBreakpoint extends WI.Breakpoint { constructor(type, url, {disabled, actions, condition, ignoreCount, autoContinue} = {}) { console.assert(Object.values(WI.URLBreakpoint.Type).includes(type), type); console.assert(typeof url === "string", url); super({disabled, actions, condition, ignoreCount, autoContinue}); this._type = type; this._url = url; } // Static static get supportsEditing() { // COMPATIBILITY (iOS 14): DOMDebugger.setURLBreakpoint did not have an "options" parameter yet. return InspectorBackend.hasCommand("DOMDebugger.setURLBreakpoint", "options"); } static fromJSON(json) { return new WI.URLBreakpoint(json.type, json.url, { disabled: json.disabled, condition: json.condition, actions: json.actions?.map((actionJSON) => WI.BreakpointAction.fromJSON(actionJSON)) || [], ignoreCount: json.ignoreCount, autoContinue: json.autoContinue, }); } // Public get type() { return this._type; } get url() { return this._url; } get displayName() { if (this === WI.domDebuggerManager.allRequestsBreakpoint) return WI.repeatedUIString.allRequests(); switch (this._type) { case WI.URLBreakpoint.Type.Text: return doubleQuotedString(this._url); case WI.URLBreakpoint.Type.RegularExpression: return "/" + this._url + "/"; } console.assert(); return WI.UIString("URL"); } get special() { return this === WI.domDebuggerManager.allRequestsBreakpoint || super.special; } get editable() { return WI.URLBreakpoint.supportsEditing || super.editable; } remove() { super.remove(); WI.domDebuggerManager.removeURLBreakpoint(this); } saveIdentityToCookie(cookie) { cookie["url-breakpoint-type"] = this._type; cookie["url-breakpoint-url"] = this._url; } toJSON(key) { let json = super.toJSON(key); json.type = this._type; json.url = this._url; if (key === WI.ObjectStore.toJSONSymbol) json[WI.objectStores.urlBreakpoints.keyPath] = this._type + ":" + this._url; return json; } }; WI.URLBreakpoint.Type = { Text: "text", RegularExpression: "regex", }; WI.URLBreakpoint.ReferencePage = WI.ReferencePage.URLBreakpoints; /* Models/WebInspectorExtension.js */ /* * Copyright (C) 2020-2021 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.WebInspectorExtension = class WebInspectorExtension { constructor(extensionID, extensionBundleIdentifier, displayName) { console.assert(typeof extensionID === "string", extensionID); console.assert(typeof extensionBundleIdentifier === "string", extensionBundleIdentifier); console.assert(typeof displayName === "string", displayName); this._extensionID = extensionID; this._extensionBundleIdentifier = extensionBundleIdentifier; this._displayName = displayName; } // Public get extensionID() { return this._extensionID; } get extensionBundleIdentifier() { return this._extensionBundleIdentifier; } get displayName() { return this._displayName; } }; // Note: these values are synonymous with the values of enum class Inspector::ExtensionError. WI.WebInspectorExtension.ErrorCode = { ContextDestroyed: "ContextDestroyed", InternalError: "InternalError", InvalidRequest: "InvalidRequest", RegistrationFailed: "RegistrationFailed", NotImplemented: "NotImplemented", }; /* Models/WebSocketResource.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 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.WebSocketResource = class WebSocketResource extends WI.Resource { constructor(url, {loaderIdentifier, requestIdentifier, requestHeaders, timestamp, walltime, requestSentTimestamp} = {}) { super(url, { type: WI.Resource.Type.WebSocket, loaderIdentifier, requestIdentifier, requestMethod: "GET", requestHeaders, requestSentTimestamp, }); this._timestamp = timestamp; this._walltime = walltime; this._readyState = WI.WebSocketResource.ReadyState.Connecting; this._frames = []; } // Public get frames() { return this._frames; } get walltime() { return this._walltime; } get readyState() { return this._readyState; } set readyState(state) { if (state === this._readyState) return; let previousState = this._readyState; this._readyState = state; this.dispatchEventToListeners(WI.WebSocketResource.Event.ReadyStateChanged, {previousState, state}); } addFrame(data, payloadLength, isOutgoing, opcode, timestamp, elapsedTime) { let frameData; // Binary data is never shown in the UI, don't clog memory with it. if (opcode === WI.WebSocketResource.OpCodes.BinaryFrame) frameData = null; else frameData = data; let frame = {data: frameData, isOutgoing, opcode, walltime: this._walltimeForWebSocketTimestamp(timestamp)}; this._frames.push(frame); if (InspectorFrontendHost.isUnderTest()) frame.dataForTest = data; this.increaseSize(payloadLength, elapsedTime); this.dispatchEventToListeners(WI.WebSocketResource.Event.FrameAdded, frame); } // Protected requestContentFromBackend() { console.assert(false, "A WebSocket's content was requested. WebSockets do not have content so the request is nonsensical."); return super.requestContentFromBackend(); } // Private _walltimeForWebSocketTimestamp(timestamp) { return this._walltime + (timestamp - this._timestamp); } }; WI.WebSocketResource.Event = { FrameAdded: "web-socket-frame-added", ReadyStateChanged: "web-socket-resource-ready-state-changed", }; WI.WebSocketResource.ReadyState = { Closed: Symbol("closed"), Connecting: Symbol("connecting"), Open: Symbol("open"), }; WI.WebSocketResource.OpCodes = { ContinuationFrame: 0, TextFrame: 1, BinaryFrame: 2, ConnectionCloseFrame: 8, PingFrame: 9, PongFrame: 10, }; /* 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); } }; /* Models/LocalResource.js */ /* * Copyright (C) 2017-2019 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 NOEVENT 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. */ // LocalResource is a complete resource constructed by the frontend. It // does not need to be tied to an object in the backend. The local resource // does not need to be complete at creation and can get its content later. // // Construction values try to mimic protocol inputs to WI.Resource: // // request: { url, method, headers, timestamp, walltime, finishedTimestamp, data } // response: { mimeType, headers, statusCode, statusText, failureReasonText, content, base64Encoded } // metrics: { responseSource, protocol, priority, remoteAddress, connectionIdentifier, sizes } // timing: { startTime, domainLookupStart, domainLookupEnd, connectStart, connectEnd, secureConnectionStart, requestStart, responseStart, responseEnd } WI.LocalResource = class LocalResource extends WI.Resource { constructor({request, response, metrics, timing, mappedFilePath}) { console.assert(request); console.assert(typeof request.url === "string"); console.assert(response); metrics = metrics || {}; timing = timing || {}; super(request.url, { mimeType: response.mimeType || (response.headers || {}).valueForCaseInsensitiveKey("Content-Type") || null, requestMethod: request.method, requestHeaders: request.headers, requestData: request.data, requestSentTimestamp: request.timestamp, requestSentWalltime: request.walltime, }); // NOTE: This directly overwrites WI.Resource properties. this._finishedOrFailedTimestamp = request.finishedTimestamp || NaN; this._statusCode = response.statusCode || NaN; this._statusText = response.statusText || null; this._responseHeaders = response.headers || {}; this._failureReasonText = response.failureReasonText || null; this._timingData = new WI.ResourceTimingData(this, timing); this._responseSource = metrics.responseSource || WI.Resource.ResponseSource.Unknown; this._protocol = metrics.protocol || null; this._priority = metrics.priority || WI.Resource.NetworkPriority.Unknown; this._remoteAddress = metrics.remoteAddress || null; this._connectionIdentifier = metrics.connectionIdentifier || null; this._requestHeadersTransferSize = !isNaN(metrics.requestHeaderBytesSent) ? metrics.requestHeaderBytesSent : NaN; this._requestBodyTransferSize = !isNaN(metrics.requestBodyBytesSent) ? metrics.requestBodyBytesSent : NaN; this._responseHeadersTransferSize = !isNaN(metrics.responseHeaderBytesReceived) ? metrics.responseHeaderBytesReceived : NaN; this._responseBodyTransferSize = !isNaN(metrics.responseBodyBytesReceived) ? metrics.responseBodyBytesReceived : NaN; this._responseBodySize = !isNaN(metrics.responseBodyDecodedSize) ? metrics.responseBodyDecodedSize : NaN; this._isProxyConnection = !!metrics.isProxyConnection; // Set by `WI.LocalResourceOverride`. this._localResourceOverride = null; // Finalize WI.Resource. this._finished = true; this._failed = false; // FIXME: How should we denote a failure? Assume from status code / failure reason? this._cached = false; // FIXME: How should we denote cached? Assume from response source? // Finalize WI.SourceCode. let content = response.content || ""; let base64Encoded = response.base64Encoded || false; this._originalRevision = new WI.SourceCodeRevision(this, content, base64Encoded, this._mimeType); this._currentRevision = this._originalRevision; this._mappedFilePath = mappedFilePath || null; } // Static static canMapToFile() { return InspectorFrontendHost.canLoad(); } static resetPathsThatFailedToLoadFromFileSystem() { WI.LocalResource._pathsThatFailedToLoadFromFileSystem.clear(); } static headersArrayToHeadersObject(headers) { let result = {}; if (headers) { for (let {name, value} of headers) result[name] = value; } return result; } static fromHAREntry(entry, archiveStartWalltime) { // FIXME: Web Inspector: HAR Extension for Redirect Timing Info let {request, response, startedDateTime, timings} = entry; let requestSentWalltime = WI.HARBuilder.dateFromHARDate(startedDateTime) / 1000; let requestSentTimestamp = requestSentWalltime - archiveStartWalltime; let finishedTimestamp = NaN; let timing = { startTime: NaN, domainLookupStart: NaN, domainLookupEnd: NaN, connectStart: NaN, connectEnd: NaN, secureConnectionStart: NaN, requestStart: NaN, responseStart: NaN, responseEnd: NaN, }; if (!isNaN(requestSentWalltime) && !isNaN(archiveStartWalltime)) { let hasBlocked = timings.blocked !== -1; let hasDNS = timings.dns !== -1; let hasConnect = timings.connect !== -1; let hasSecureConnect = timings.ssl !== -1; // FIXME: Web Inspector: ResourceTimingData should allow a startTime of 0 timing.startTime = requestSentTimestamp || Number.EPSILON; timing.fetchStart = timing.startTime; let accumulation = timing.startTime; if (hasBlocked) accumulation += (timings.blocked / 1000); if (hasDNS) { timing.domainLookupStart = accumulation; accumulation += (timings.dns / 1000); timing.domainLookupEnd = accumulation; } if (hasConnect) { timing.connectStart = accumulation; accumulation += (timings.connect / 1000); timing.connectEnd = accumulation; if (hasSecureConnect) timing.secureConnectionStart = timing.connectEnd - (timings.ssl / 1000); } accumulation += (timings.send / 1000); timing.requestStart = accumulation; accumulation += (timings.wait / 1000); timing.responseStart = accumulation; accumulation += (timings.receive / 1000); timing.responseEnd = accumulation; finishedTimestamp = timing.responseEnd; } let serverAddress = entry.serverIPAddress || null; if (serverAddress && typeof entry._serverPort === "number") serverAddress += ":" + entry._serverPort; return new WI.LocalResource({ request: { url: request.url, method: request.method, headers: LocalResource.headersArrayToHeadersObject(request.headers), timestamp: requestSentTimestamp, walltime: requestSentWalltime, finishedTimestamp: finishedTimestamp, data: request.postData ? request.postData.text : null, }, response: { headers: LocalResource.headersArrayToHeadersObject(response.headers), mimeType: response.content.mimeType, statusCode: response.status, statusText: response.statusText, failureReasonText: response._error || null, content: response.content.text, base64Encoded: response.content.encoding === "base64", }, metrics: { responseSource: WI.HARBuilder.responseSourceFromHARFetchType(entry._fetchType), protocol: WI.HARBuilder.protocolFromHARProtocol(response.httpVersion), priority: WI.HARBuilder.networkPriorityFromHARPriority(entry._priority), remoteAddress: serverAddress, connectionIdentifier: entry.connection ? parseInt(entry.connection) : null, requestHeaderBytesSent: request.headersSize >= 0 ? request.headersSize : NaN, requestBodyBytesSent: request.bodySize >= 0 ? request.bodySize : NaN, responseHeaderBytesReceived: response.headersSize >= 0 ? response.headersSize : NaN, responseBodyBytesReceived: response.bodySize >= 0 ? response.bodySize : NaN, responseBodyDecodedSize: response.content.size || NaN, }, timing, }); } // Import / Export static fromJSON(json) { return new WI.LocalResource(json); } toJSON(key) { return { request: { url: this.url, method: this.requestMethod, headers: this.requestHeaders, data: this.requestData, }, response: { headers: this.responseHeaders, mimeType: this.mimeType, statusCode: this.statusCode, statusText: this.statusText, content: this.currentRevision.content, base64Encoded: this.currentRevision.base64Encoded, }, mappedFilePath: this._mappedFilePath, }; } // Public get localResourceOverride() { return this._localResourceOverride; } get mappedFilePath() { return this._mappedFilePath; } set mappedFilePath(mappedFilePath) { console.assert(WI.LocalResource.canMapToFile()); console.assert(mappedFilePath); if (mappedFilePath === this._mappedFilePath) return; this._mappedFilePath = mappedFilePath; const forceUpdate = true; this._updateContentFromFileSystem(forceUpdate).then(() => { this.dispatchEventToListeners(WI.LocalResource.Event.MappedFilePathChanged); }); } get isMappedToDirectory() { return this._mappedFilePath?.endsWith("/"); } async requestContentFromMappedDirectory(subpath) { return this._loadFromFileSystem({subpath}); } // Protected async requestContent() { await this._updateContentFromFileSystem(); return super.requestContent(); } requestContentFromBackend() { return Promise.resolve({ content: this._originalRevision.content, base64Encoded: this._originalRevision.base64Encoded, }); } handleCurrentRevisionContentChange() { if (this._mimeType !== this.currentRevision.mimeType) { let oldMIMEType = this._mimeType; this._mimeType = this.currentRevision.mimeType; this.dispatchEventToListeners(WI.Resource.Event.MIMETypeDidChange, {oldMIMEType}); } } // Private async _loadFromFileSystem({subpath} = {}) { let path = this._mappedFilePath; if (!path) return null; if (this.isMappedToDirectory) { if (!subpath) return null; path += subpath; } let content = null; try { content = await InspectorFrontendHost.load(path); } catch { } if (typeof content === "string") WI.LocalResource._pathsThatFailedToLoadFromFileSystem.delete(path); else if (!WI.LocalResource._pathsThatFailedToLoadFromFileSystem.has(path)) { WI.LocalResource._pathsThatFailedToLoadFromFileSystem.add(path); let message = WI.UIString("Local Override: could not load \u201C%s\u201D").format(path); if (window.InspectorTest) console.warn(message); else { let consoleMessage = new WI.ConsoleMessage(WI.mainTarget, WI.ConsoleMessage.MessageSource.Other, WI.ConsoleMessage.MessageLevel.Warning, message); consoleMessage.shouldRevealConsole = true; WI.consoleLogViewController.appendConsoleMessage(consoleMessage); } } return content; } async _updateContentFromFileSystem(forceUpdate) { let content = await this._loadFromFileSystem(); if (typeof content !== "string") return; if (!forceUpdate && content === this.currentRevision.content) return; this.editableRevision.updateRevisionContent(content); } }; WI.LocalResource._pathsThatFailedToLoadFromFileSystem = new Set; WI.LocalResource.Event = { MappedFilePathChanged: "local-resource-mapped-file-path-changed", }; /* Models/SourceMapResource.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. */ WI.SourceMapResource = class SourceMapResource extends WI.Resource { constructor(url, sourceMap) { super(url); console.assert(url); console.assert(sourceMap); this._sourceMap = sourceMap; var inheritedMIMEType = this._sourceMap.originalSourceCode instanceof WI.Resource ? this._sourceMap.originalSourceCode.syntheticMIMEType : null; let fileExtension = WI.fileExtensionForURL(url) || ""; // React serves JSX resources with "js" extension. let fileExtensionMIMEType = fileExtension === "js" ? "text/jsx" : WI.mimeTypeForFileExtension(fileExtension, true); // FIXME: This is a layering violation. It should use a helper function on the // Resource base-class to set _mimeType and _type. this._mimeType = fileExtensionMIMEType || inheritedMIMEType || "text/javascript"; this._type = WI.Resource.typeFromMIMEType(this._mimeType); // Mark the resource as loaded so it does not show a spinner in the sidebar. // We will really load the resource the first time content is requested. this.markAsFinished(); } // Public get sourceMap() { return this._sourceMap; } get sourceMapDisplaySubpath() { var sourceMappingBasePathURLComponents = this._sourceMap.sourceMappingBasePathURLComponents; var resourceURLComponents = this.urlComponents; // Fallback for JavaScript debuggable named scripts that may not have a complete URL. if (!resourceURLComponents.path) resourceURLComponents.path = this.url; // Different schemes / hosts. Return the host + path of this resource. if (resourceURLComponents.scheme !== sourceMappingBasePathURLComponents.scheme || resourceURLComponents.host !== sourceMappingBasePathURLComponents.host) { let subpath = ""; if (resourceURLComponents.host) { subpath += resourceURLComponents.host; if (resourceURLComponents.port) subpath += ":" + resourceURLComponents.port; subpath += resourceURLComponents.path; } else { // Remove the leading "/" so there isn't an empty folder. subpath += resourceURLComponents.path.substring(1); } return subpath; } // Same host, but not a subpath of the base. This implies a ".." in the relative path. if (!resourceURLComponents.path.startsWith(sourceMappingBasePathURLComponents.path)) return relativePath(resourceURLComponents.path, sourceMappingBasePathURLComponents.path); // Same host. Just a subpath of the base. return resourceURLComponents.path.substring(sourceMappingBasePathURLComponents.path.length, resourceURLComponents.length); } get supportsScriptBlackboxing() { return false; } requestContentFromBackend() { // Revert the markAsFinished that was done in the constructor. this.revertMarkAsFinished(); var inlineContent = this._sourceMap.sourceContent(this.url); if (inlineContent) { // Force inline content to be asynchronous to match the expected load pattern. // FIXME: We don't know the MIME-type for inline content. Guess by analyzing the content? // Returns a promise. return Promise.resolve().then(sourceMapResourceLoaded.bind(this, {content: inlineContent, mimeType: this.mimeType, statusCode: 200})); } function sourceMapResourceNotAvailable(error, content, mimeType, statusCode) { this.markAsFailed(); return Promise.resolve({ error: WI.UIString("An error occurred trying to load the resource."), content, mimeType, statusCode }); } function sourceMapResourceLoadError(error) { console.error(error || "There was an unknown error calling Network.loadResource."); this.markAsFailed(); return Promise.resolve({error: WI.UIString("An error occurred trying to load the resource.")}); } function sourceMapResourceLoaded(parameters) { var {error, content, mimeType, statusCode} = parameters; var base64encoded = false; if (statusCode >= 400 || error) return sourceMapResourceNotAvailable(error, content, mimeType, statusCode); // FIXME: Add support for picking the best MIME-type. Right now the file extension is the best bet. // The constructor set MIME-type based on the file extension and we ignore mimeType here. this.markAsFinished(); return Promise.resolve({ content, mimeType, base64encoded, statusCode }); } if (!this._target.hasCommand("Network.loadResource")) return sourceMapResourceLoadError.call(this); var frameIdentifier = null; if (this._sourceMap.originalSourceCode instanceof WI.Resource && this._sourceMap.originalSourceCode.parentFrame) frameIdentifier = this._sourceMap.originalSourceCode.parentFrame.id; if (!frameIdentifier) frameIdentifier = WI.networkManager.mainFrame ? WI.networkManager.mainFrame.id : ""; return this._target.NetworkAgent.loadResource(frameIdentifier, this.url).then(sourceMapResourceLoaded.bind(this)).catch(sourceMapResourceLoadError.bind(this)); } createSourceCodeLocation(lineNumber, columnNumber) { // SourceCodeLocations are always constructed with raw resources and raw locations. Lookup the raw location. var entry = this._sourceMap.findEntryReversed(this.url, lineNumber); var rawLineNumber = entry[0]; var rawColumnNumber = entry[1]; // If the raw location is an inline script we need to include that offset. var originalSourceCode = this._sourceMap.originalSourceCode; if (originalSourceCode instanceof WI.Script) { if (rawLineNumber === 0) rawColumnNumber += originalSourceCode.range.startColumn; rawLineNumber += originalSourceCode.range.startLine; } // Create the SourceCodeLocation and since we already know the the mapped location set it directly. var location = originalSourceCode.createSourceCodeLocation(rawLineNumber, rawColumnNumber); location._setMappedLocation(this, lineNumber, columnNumber); return location; } createSourceCodeTextRange(textRange) { // SourceCodeTextRanges are always constructed with raw resources and raw locations. // However, we can provide the most accurate mapped locations in construction. var startSourceCodeLocation = this.createSourceCodeLocation(textRange.startLine, textRange.startColumn); var endSourceCodeLocation = this.createSourceCodeLocation(textRange.endLine, textRange.endColumn); return new WI.SourceCodeTextRange(this._sourceMap.originalSourceCode, startSourceCodeLocation, endSourceCodeLocation); } }; /* Models/AuditTestBase.js */ /* * Copyright (C) 2018 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.AuditTestBase = class AuditTestBase extends WI.Object { constructor(name, {description, supports, setup, disabled} = {}) { console.assert(typeof name === "string", name); console.assert(!description || typeof description === "string", description); console.assert(supports === undefined || typeof supports === "number", supports); console.assert(!setup || typeof setup === "string", setup); console.assert(disabled === undefined || typeof disabled === "boolean", disabled); super(); // This class should not be instantiated directly. Create a concrete subclass instead. console.assert(this.constructor !== WI.AuditTestBase && this instanceof WI.AuditTestBase, this); this._name = name; this._description = description || ""; this._supports = supports ?? NaN; this._setup = setup || ""; this.determineIfSupported({warn: true}); this._runningState = disabled ? WI.AuditManager.RunningState.Disabled : WI.AuditManager.RunningState.Inactive; this._result = null; this._parent = null; this._default = false; } // Public get runningState() { return this._runningState; } get result() { return this._result; } get supported() { return this._supported; } get name() { return this._name; } set name(name) { console.assert(this.editable); console.assert(WI.auditManager.editing); console.assert(name && typeof name === "string", name); if (name === this._name) return; let oldName = this._name; this._name = name; this.dispatchEventToListeners(WI.AuditTestBase.Event.NameChanged, {oldName}); } get description() { return this._description; } set description(description) { console.assert(this.editable); console.assert(WI.auditManager.editing); console.assert(typeof description === "string", description); if (description === this._description) return; this._description = description; } get supports() { return this._supports; } set supports(supports) { console.assert(this.editable); console.assert(WI.auditManager.editing); console.assert(typeof supports === "number", supports); if (supports === this._supports) return; this._supports = supports; this.determineIfSupported(); } get setup() { return this._setup; } set setup(setup) { console.assert(this.editable); console.assert(typeof setup === "string", setup); if (setup === this._setup) return; this._setup = setup; this.clearResult(); } get disabled() { return this._runningState === WI.AuditManager.RunningState.Disabled; } set disabled(disabled) { this.updateDisabled(disabled); } get editable() { return !this._default; } get default() { return this._default; } markAsDefault() { console.assert(!this._default); this._default = true; } get topLevelTest() { let test = this; while (test._parent) test = test._parent; return test; } async runSetup() { console.assert(this.topLevelTest === this); if (!this._setup) return; let target = WI.assumingMainTarget(); let agentCommandFunction = null; let agentCommandArguments = {}; if (target.hasDomain("Audit")) { agentCommandFunction = target.AuditAgent.run; agentCommandArguments.test = this._setup; } else { agentCommandFunction = target.RuntimeAgent.evaluate; agentCommandArguments.expression = `(function() { "use strict"; return eval(\`(${this._setup.replace(/`/g, "\\`")})\`)(); })()`; agentCommandArguments.objectGroup = AuditTestBase.ObjectGroup; agentCommandArguments.doNotPauseOnExceptionsAndMuteConsole = true; } try { let response = await agentCommandFunction.invoke(agentCommandArguments); if (response.result.type === "object" && response.result.className === "Promise") { if (WI.RuntimeManager.supportsAwaitPromise()) response = await target.RuntimeAgent.awaitPromise(response.result.objectId); else { response = null; WI.AuditManager.synthesizeError(WI.UIString("Async audits are not supported.")); } } if (response) { let remoteObject = WI.RemoteObject.fromPayload(response.result, WI.mainTarget); if (response.wasThrown || (remoteObject.type === "object" && remoteObject.subtype === "error")) WI.AuditManager.synthesizeError(remoteObject.description); } } catch (error) { WI.AuditManager.synthesizeError(error.message); } } async start() { // Called from WI.AuditManager. if (!this._supported || this.disabled) return; console.assert(WI.auditManager.runningState === WI.AuditManager.RunningState.Active); console.assert(this._runningState === WI.AuditManager.RunningState.Inactive); if (this._runningState !== WI.AuditManager.RunningState.Inactive) return; this._runningState = WI.AuditManager.RunningState.Active; this.dispatchEventToListeners(WI.AuditTestBase.Event.Scheduled); await this.run(); this._runningState = WI.AuditManager.RunningState.Inactive; this.dispatchEventToListeners(WI.AuditTestBase.Event.Completed); } stop() { // Called from WI.AuditManager. // Overridden by sub-classes if needed. if (!this._supported || this.disabled) return; console.assert(WI.auditManager.runningState === WI.AuditManager.RunningState.Stopping); if (this._runningState !== WI.AuditManager.RunningState.Active) return; this._runningState = WI.AuditManager.RunningState.Stopping; this.dispatchEventToListeners(WI.AuditTestBase.Event.Stopping); } clearResult(options = {}) { // Overridden by sub-classes if needed. if (!this._result) return false; this._result = null; if (!options.suppressResultChangedEvent) this.dispatchEventToListeners(WI.AuditTestBase.Event.ResultChanged); return true; } async clone() { console.assert(WI.auditManager.editing); return this.constructor.fromPayload(this.toJSON()); } remove() { console.assert(WI.auditManager.editing); if (!this._parent || this._default) { WI.auditManager.removeTest(this); return; } console.assert(this.editable); console.assert(this._parent instanceof WI.AuditTestGroup); this._parent.removeTest(this); } saveIdentityToCookie(cookie) { let path = []; let test = this; while (test) { path.push(test.name); test = test._parent; } path.reverse(); cookie["audit-path"] = path.join(","); } toJSON(key) { // Overridden by sub-classes if needed. let json = { type: this.constructor.TypeIdentifier, name: this._name, }; if (this._description) json.description = this._description; if (!isNaN(this._supports)) json.supports = Number.isFinite(this._supports) ? this._supports : WI.AuditTestBase.Version + 1; if (this._setup) json.setup = this._setup; if (key === WI.ObjectStore.toJSONSymbol) json.disabled = this.disabled; return json; } // Protected async run() { throw WI.NotImplementedError.subclassMustOverride(); } determineIfSupported(options = {}) { // Overridden by sub-classes if needed. let supportedBefore = this._supported; if (this._supports > WI.AuditTestBase.Version) { this.updateSupported(false, options); if (options.warn && supportedBefore !== this._supported && Number.isFinite(this._supports)) WI.AuditManager.synthesizeWarning(WI.UIString("\u0022%s\u0022 is too new to run in this Web Inspector").format(this.name)); } else if (InspectorBackend.hasDomain("Audit") && this._supports > InspectorBackend.getVersion("Audit")) { this.updateSupported(false, options); if (options.warn && supportedBefore !== this._supported && Number.isFinite(this._supports)) WI.AuditManager.synthesizeWarning(WI.UIString("\u0022%s\u0022 is too new to run in the inspected page").format(this.name)); } else this.updateSupported(true, options); return this._supported; } updateSupported(supported, options = {}) { // Overridden by sub-classes if needed. if (supported === this._supported) return; this._supported = supported; if (!options.silent) this.dispatchEventToListeners(WI.AuditTestBase.Event.SupportedChanged); if (!this._supported) this.clearResult(); } updateDisabled(disabled, options = {}) { // Overridden by sub-classes if needed. console.assert(this._runningState === WI.AuditManager.RunningState.Disabled || this._runningState === WI.AuditManager.RunningState.Inactive); if (this._runningState !== WI.AuditManager.RunningState.Disabled && this._runningState !== WI.AuditManager.RunningState.Inactive) return; let runningState = disabled ? WI.AuditManager.RunningState.Disabled : WI.AuditManager.RunningState.Inactive; if (runningState === this._runningState) return; this._runningState = runningState; if (!options.silent) this.dispatchEventToListeners(WI.AuditTestBase.Event.DisabledChanged); if (this.disabled) this.clearResult(); } updateResult(result) { // Overridden by sub-classes if needed. console.assert(result instanceof WI.AuditTestResultBase, result); this._result = result; this.dispatchEventToListeners(WI.AuditTestBase.Event.ResultChanged); } }; // Keep this in sync with Inspector::Protocol::Audit::VERSION. WI.AuditTestBase.Version = 4; WI.AuditTestBase.ObjectGroup = "audit"; WI.AuditTestBase.Event = { Completed: "audit-test-base-completed", DisabledChanged: "audit-test-base-disabled-changed", NameChanged: "audit-test-base-name-changed", Progress: "audit-test-base-progress", ResultChanged: "audit-test-base-result-changed", Scheduled: "audit-test-base-scheduled", Stopping: "audit-test-base-stopping", SupportedChanged: "audit-test-base-supported-changed", TestChanged: "audit-test-base-test-changed", }; /* Models/AuditTestCase.js */ /* * Copyright (C) 2018 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.AuditTestCase = class AuditTestCase extends WI.AuditTestBase { constructor(name, test, options = {}) { console.assert(typeof test === "string", test); super(name, options); this._test = test; } // Static static async fromPayload(payload) { if (typeof payload !== "object" || payload === null) return null; if (payload.type !== WI.AuditTestCase.TypeIdentifier) return null; if (typeof payload.name !== "string") { WI.AuditManager.synthesizeError(WI.UIString("\u0022%s\u0022 has a non-string \u0022%s\u0022 value").format(payload.name, WI.unlocalizedString("name"))); return null; } if (typeof payload.test !== "string") { WI.AuditManager.synthesizeError(WI.UIString("\u0022%s\u0022 has a non-string \u0022%s\u0022 value").format(payload.name, WI.unlocalizedString("test"))); return null; } let options = {}; if (typeof payload.description === "string") options.description = payload.description; else if ("description" in payload) WI.AuditManager.synthesizeWarning(WI.UIString("\u0022%s\u0022 has a non-string \u0022%s\u0022 value").format(payload.name, WI.unlocalizedString("description"))); if (typeof payload.supports === "number") options.supports = payload.supports; else if ("supports" in payload) WI.AuditManager.synthesizeWarning(WI.UIString("\u0022%s\u0022 has a non-number \u0022%s\u0022 value").format(payload.name, WI.unlocalizedString("supports"))); if (typeof payload.setup === "string") options.setup = payload.setup; else if ("setup" in payload) WI.AuditManager.synthesizeWarning(WI.UIString("\u0022%s\u0022 has a non-string \u0022%s\u0022 value").format(payload.name, WI.unlocalizedString("setup"))); if (typeof payload.disabled === "boolean") options.disabled = payload.disabled; return new WI.AuditTestCase(payload.name, payload.test, options); } // Public get test() { return this._test; } set test(test) { console.assert(this.editable); console.assert(typeof test === "string", test); if (test === this._test) return; this._test = test; this.clearResult(); this.dispatchEventToListeners(WI.AuditTestBase.Event.TestChanged); } toJSON(key) { let json = super.toJSON(key); json.test = this._test; return json; } // Protected async run() { const levelStrings = Object.values(WI.AuditTestCaseResult.Level); let level = null; let data = {}; let metadata = { url: WI.networkManager.mainFrame.url, startTimestamp: null, endTimestamp: null, }; let resolvedDOMNodes = null; function setLevel(newLevel) { let newLevelIndex = levelStrings.indexOf(newLevel); if (newLevelIndex < 0) { addError(WI.UIString("Return string must be one of %s").format(JSON.stringify(levelStrings))); return; } if (newLevelIndex <= levelStrings.indexOf(level)) return; level = newLevel; } function addError(value) { setLevel(WI.AuditTestCaseResult.Level.Error); if (!data.errors) data.errors = []; data.errors.push(value); } async function parseResponse(response) { let remoteObject = WI.RemoteObject.fromPayload(response.result, WI.mainTarget); if (response.wasThrown || (remoteObject.type === "object" && remoteObject.subtype === "error")) { addError(remoteObject.description); return; } if (remoteObject.type === "boolean") { setLevel(remoteObject.value ? WI.AuditTestCaseResult.Level.Pass : WI.AuditTestCaseResult.Level.Fail); return; } if (remoteObject.type === "string") { setLevel(remoteObject.value.trim().toLowerCase()); return; } if (remoteObject.type !== "object" || remoteObject.subtype) { addError(WI.UIString("Return value is not an object, string, or boolean")); return; } const options = { ownProperties: true, }; function checkResultProperty(key, value, type, subtype) { function addErrorForValueType(valueType) { let errorString = null; if (valueType === "object" || valueType === "array") errorString = WI.UIString("\u0022%s\u0022 must be an %s"); else errorString = WI.UIString("\u0022%s\u0022 must be a %s"); addError(errorString.format(key, valueType)); } if (value.subtype !== subtype) { addErrorForValueType(subtype); return null; } if (value.type !== type) { addErrorForValueType(type); return null; } if (type === "boolean" || type === "string") return value.value; return value; } async function resultArrayForEach(key, value, callback) { let array = checkResultProperty(key, value, "object", "array"); if (!array) return; let arrayProperties = await new Promise((resolve, reject) => array.getPropertyDescriptors(resolve, options)); for (let i = 0; i < array.size; ++i) { let arrayPropertyForIndex = arrayProperties.find((arrayProperty) => arrayProperty.name === String(i)); if (arrayPropertyForIndex) await callback(arrayPropertyForIndex); } } let properties = await new Promise((resolve, reject) => remoteObject.getPropertyDescriptors(resolve, options)); for (let property of properties) { let key = property.name; if (key === "__proto__") continue; let value = property.value; switch (key) { case "level": { let levelString = checkResultProperty(key, value, "string"); if (levelString) setLevel(levelString.trim().toLowerCase()); break; } case "pass": if (checkResultProperty(key, value, "boolean")) setLevel(WI.AuditTestCaseResult.Level.Pass); break; case "warn": if (checkResultProperty(key, value, "boolean")) setLevel(WI.AuditTestCaseResult.Level.Warn); break; case "fail": if (checkResultProperty(key, value, "boolean")) setLevel(WI.AuditTestCaseResult.Level.Fail); break; case "error": if (checkResultProperty(key, value, "boolean")) setLevel(WI.AuditTestCaseResult.Level.Error); break; case "unsupported": if (checkResultProperty(key, value, "boolean")) setLevel(WI.AuditTestCaseResult.Level.Unsupported); break; case "domNodes": await resultArrayForEach(key, value, async (item) => { if (!item || !item.value || item.value.type !== "object" || item.value.subtype !== "node") { addError(WI.UIString("All items in \u0022%s\u0022 must be valid DOM nodes").format(WI.unlocalizedString("domNodes"))); return; } let domNodeId = await new Promise((resolve, reject) => item.value.pushNodeToFrontend(resolve)); let domNode = WI.domManager.nodeForId(domNodeId); if (!domNode) return; if (!data.domNodes) data.domNodes = []; data.domNodes.push(WI.cssPath(domNode, {full: true})); if (!resolvedDOMNodes) resolvedDOMNodes = []; resolvedDOMNodes.push(domNode); }); break; case "domAttributes": await resultArrayForEach(key, value, (item) => { if (!item || !item.value || item.value.type !== "string" || !item.value.value.length) { addError(WI.UIString("All items in \u0022%s\u0022 must be non-empty strings").format(WI.unlocalizedString("domAttributes"))); return; } if (!data.domAttributes) data.domAttributes = []; data.domAttributes.push(item.value.value); }); break; case "errors": await resultArrayForEach(key, value, (item) => { if (!item || !item.value || item.value.type !== "object" || item.value.subtype !== "error") { addError(WI.UIString("All items in \u0022%s\u0022 must be error objects").format(WI.unlocalizedString("errors"))); return; } addError(item.value.description); }); break; default: if (value.objectId) { try { function inspectedPage_stringify() { return JSON.stringify(this); } let stringifiedValue = await value.callFunction(inspectedPage_stringify); data[key] = JSON.parse(stringifiedValue.value); } catch { addError(WI.UIString("\u0022%s\u0022 is not JSON serializable").format(key)); } } else data[key] = value.value; break; } } } let target = WI.assumingMainTarget(); let agentCommandFunction = null; let agentCommandArguments = {}; if (target.hasDomain("Audit")) { agentCommandFunction = target.AuditAgent.run; agentCommandArguments.test = this._test; } else { agentCommandFunction = target.RuntimeAgent.evaluate; agentCommandArguments.expression = `(function() { "use strict"; return eval(\`(${this._test.replace(/`/g, "\\`")})\`)(); })()`; agentCommandArguments.objectGroup = WI.AuditTestCase.ObjectGroup; agentCommandArguments.doNotPauseOnExceptionsAndMuteConsole = true; } try { metadata.startTimestamp = new Date; let response = await agentCommandFunction.invoke(agentCommandArguments); metadata.endTimestamp = new Date; if (response.result.type === "object" && response.result.className === "Promise") { if (WI.RuntimeManager.supportsAwaitPromise()) { metadata.asyncTimestamp = metadata.endTimestamp; response = await target.RuntimeAgent.awaitPromise(response.result.objectId); metadata.endTimestamp = new Date; } else { response = null; addError(WI.UIString("Async audits are not supported.")); setLevel(WI.AuditTestCaseResult.Level.Unsupported); } } if (response) await parseResponse(response); } catch (error) { metadata.endTimestamp = new Date; addError(error.message); } if (!level) addError(WI.UIString("Missing result level")); let options = { description: this.description, metadata, }; if (!isEmptyObject(data)) options.data = data; if (resolvedDOMNodes) options.resolvedDOMNodes = resolvedDOMNodes; this.updateResult(new WI.AuditTestCaseResult(this.name, level, options)); } }; WI.AuditTestCase.TypeIdentifier = "test-case"; /* Models/AuditTestGroup.js */ /* * Copyright (C) 2018 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.AuditTestGroup = class AuditTestGroup extends WI.AuditTestBase { constructor(name, tests, options = {}) { console.assert(Array.isArray(tests), tests); // Set disabled once `_tests` is set so that it propagates. let disabled = options.disabled; options.disabled = false; super(name, options); this._tests = []; for (let test of tests) this.addTest(test); if (disabled) this.updateDisabled(true); } // Static static async fromPayload(payload) { if (typeof payload !== "object" || payload === null) return null; if (payload.type !== WI.AuditTestGroup.TypeIdentifier) return null; if (typeof payload.name !== "string") { WI.AuditManager.synthesizeError(WI.UIString("\u0022%s\u0022 has a non-string \u0022%s\u0022 value").format(payload.name, WI.unlocalizedString("name"))); return null; } if (!Array.isArray(payload.tests)) { WI.AuditManager.synthesizeError(WI.UIString("\u0022%s\u0022 has a non-array \u0022%s\u0022 value").format(payload.name, WI.unlocalizedString("tests"))); return null; } let tests = await Promise.all(payload.tests.map(async (test) => { let testCase = await WI.AuditTestCase.fromPayload(test); if (testCase) return testCase; let testGroup = await WI.AuditTestGroup.fromPayload(test); if (testGroup) return testGroup; return null; })); tests = tests.filter((test) => !!test); if (!tests.length) return null; let options = {}; if (typeof payload.description === "string") options.description = payload.description; else if ("description" in payload) WI.AuditManager.synthesizeWarning(WI.UIString("\u0022%s\u0022 has a non-string \u0022%s\u0022 value").format(payload.name, WI.unlocalizedString("description"))); if (typeof payload.supports === "number") options.supports = payload.supports; else if ("supports" in payload) WI.AuditManager.synthesizeWarning(WI.UIString("\u0022%s\u0022 has a non-number \u0022%s\u0022 value").format(payload.name, WI.unlocalizedString("supports"))); if (typeof payload.setup === "string") options.setup = payload.setup; else if ("setup" in payload) WI.AuditManager.synthesizeWarning(WI.UIString("\u0022%s\u0022 has a non-string \u0022%s\u0022 value").format(payload.name, WI.unlocalizedString("setup"))); if (typeof payload.disabled === "boolean") options.disabled = payload.disabled; return new WI.AuditTestGroup(payload.name, tests, options); } // Public get tests() { return this._tests; } addTest(test) { console.assert(test instanceof WI.AuditTestBase, test); console.assert(!this._tests.includes(test), test); console.assert(!test._parent, test); this._tests.push(test); test._parent = this; test.addEventListener(WI.AuditTestBase.Event.Completed, this._handleTestCompleted, this); test.addEventListener(WI.AuditTestBase.Event.DisabledChanged, this._handleTestDisabledChanged, this); test.addEventListener(WI.AuditTestBase.Event.Progress, this._handleTestProgress, this); if (this.editable) { test.addEventListener(WI.AuditTestBase.Event.SupportedChanged, this._handleTestSupportedChanged, this); test.addEventListener(WI.AuditTestBase.Event.TestChanged, this._handleTestChanged, this); } this.dispatchEventToListeners(WI.AuditTestGroup.Event.TestAdded, {test}); this.determineIfSupported(); if (this._checkDisabled(test)) test.updateDisabled(true, {silent: true}); } removeTest(test) { console.assert(this.editable); console.assert(WI.auditManager.editing); console.assert(test instanceof WI.AuditTestBase, test); console.assert(test.editable, test); console.assert(this._tests.includes(test), test); console.assert(test._parent === this, test); test.removeEventListener(WI.AuditTestBase.Event.Completed, this._handleTestCompleted, this); test.removeEventListener(WI.AuditTestBase.Event.DisabledChanged, this._handleTestDisabledChanged, this); test.removeEventListener(WI.AuditTestBase.Event.Progress, this._handleTestProgress, this); test.removeEventListener(WI.AuditTestBase.Event.SupportedChanged, this._handleTestSupportedChanged, this); test.removeEventListener(WI.AuditTestBase.Event.TestChanged, this._handleTestChanged, this); this._tests.remove(test); test._parent = null; this.dispatchEventToListeners(WI.AuditTestGroup.Event.TestRemoved, {test}); this.determineIfSupported(); this._checkDisabled(); } stop() { // Called from WI.AuditManager. for (let test of this._tests) test.stop(); super.stop(); } clearResult(options = {}) { let cleared = !!this.result; if (!options.excludeTests && this._tests) { for (let test of this._tests) { if (test.clearResult(options)) cleared = true; } } return super.clearResult({ ...options, suppressResultChangedEvent: !cleared, }); } toJSON(key) { let json = super.toJSON(key); json.tests = this._tests.map((testCase) => testCase.toJSON(key)); return json; } // Protected async run() { let count = this._tests.length; for (let index = 0; index < count && this._runningState === WI.AuditManager.RunningState.Active; ++index) { let test = this._tests[index]; if (test.disabled || !test.supported) continue; await test.start(); if (test instanceof WI.AuditTestCase) this.dispatchEventToListeners(WI.AuditTestBase.Event.Progress, {index, count}); } this.updateResult(); } determineIfSupported(options = {}) { if (this._tests) { for (let test of this._tests) test.determineIfSupported({...options, warn: false, silent: true}); } return super.determineIfSupported(options); } updateSupported(supported, options = {}) { if (this._tests && (!supported || this._tests.every((test) => !test.supported))) { supported = false; for (let test of this._tests) test.updateSupported(supported, {silent: true}); } super.updateSupported(supported, options); } updateDisabled(disabled, options = {}) { if (!options.excludeTests && this._tests) { for (let test of this._tests) test.updateDisabled(disabled, options); } super.updateDisabled(disabled, options); } updateResult() { let results = this._tests.map((test) => test.result).filter((result) => !!result); if (!results.length) return; super.updateResult(new WI.AuditTestGroupResult(this.name, results, { description: this.description, })); } // Private _checkDisabled(test) { let testDisabled = !test || !test.supported || test.disabled; let enabledTestCount = this._tests.filter((existing) => existing.supported && !existing.disabled).length; if (testDisabled && !enabledTestCount) this.updateDisabled(true); else if (!testDisabled && enabledTestCount === 1) this.updateDisabled(false, {excludeTests: true}); else { // Don't change `disabled`, as we're currently in an "indeterminate" state. this.dispatchEventToListeners(WI.AuditTestBase.Event.DisabledChanged); } return this.disabled; } _handleTestCompleted(event) { if (this._runningState === WI.AuditManager.RunningState.Active) return; this.updateResult(); this.dispatchEventToListeners(WI.AuditTestBase.Event.Completed); } _handleTestDisabledChanged(event) { this._checkDisabled(event.target); } _handleTestProgress(event) { if (this._runningState !== WI.AuditManager.RunningState.Active) return; let walk = (tests) => { let count = 0; for (let test of tests) { if (test.disabled || !test.supported) continue; if (test instanceof WI.AuditTestCase) ++count; else if (test instanceof WI.AuditTestGroup) count += walk(test.tests); } return count; }; this.dispatchEventToListeners(WI.AuditTestBase.Event.Progress, { index: event.data.index + walk(this._tests.slice(0, this._tests.indexOf(event.target))), count: walk(this._tests), }); } _handleTestSupportedChanged(event) { this.determineIfSupported(); } _handleTestChanged(event) { console.assert(WI.auditManager.editing); this.clearResult({excludeTests: true}); this.dispatchEventToListeners(WI.AuditTestBase.Event.TestChanged); } }; WI.AuditTestGroup.TypeIdentifier = "test-group"; WI.AuditTestGroup.Event = { TestAdded: "audit-test-group-test-added", TestRemoved: "audit-test-group-test-removed", }; /* Models/AuditTestResultBase.js */ /* * Copyright (C) 2018 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.AuditTestResultBase = class AuditTestResultBase { constructor(name, {description} = {}) { console.assert(typeof name === "string"); console.assert(!description || typeof description === "string"); this._name = name; this._description = description || null; } // Public get name() { return this._name; } get description() { return this._description; } get result() { return this; } get didPass() { throw WI.NotImplementedError.subclassMustOverride(); } get didWarn() { throw WI.NotImplementedError.subclassMustOverride(); } get didFail() { throw WI.NotImplementedError.subclassMustOverride(); } get didError() { throw WI.NotImplementedError.subclassMustOverride(); } get unsupported() { throw WI.NotImplementedError.subclassMustOverride(); } get disabled() { return false; } get editable() { return false; } saveIdentityToCookie(cookie) { cookie["audit-" + this.constructor.TypeIdentifier + "-name"] = this._name; } toJSON() { let json = { type: this.constructor.TypeIdentifier, name: this._name, }; if (this._description) json.description = this._description; return json; } }; /* Models/AuditTestCaseResult.js */ /* * Copyright (C) 2018 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.AuditTestCaseResult = class AuditTestCaseResult extends WI.AuditTestResultBase { constructor(name, level, {description, data, metadata, resolvedDOMNodes} = {}) { console.assert(Object.values(WI.AuditTestCaseResult.Level).includes(level)); console.assert(!data || typeof data === "object"); console.assert(!metadata || typeof metadata === "object"); super(name, {description}); this._level = level; this._data = data || {}; this._metadata = metadata || {}; // This array is a mirror of `this._data.domNodes` where each item is a `WI.DOMNode`. this._resolvedDOMNodes = resolvedDOMNodes || []; } // Static static async fromPayload(payload) { if (typeof payload !== "object" || payload === null) return null; if (payload.type !== WI.AuditTestCaseResult.TypeIdentifier) return null; if (typeof payload.name !== "string") { WI.AuditManager.synthesizeError(WI.UIString("\u0022%s\u0022 has a non-string \u0022%s\u0022 value").format(payload.name, WI.unlocalizedString("name"))); return null; } if (!Object.values(WI.AuditTestCaseResult.Level).includes(payload.level)) { WI.AuditManager.synthesizeError(WI.UIString("\u0022%s\u0022 has an invalid \u0022%s\u0022 value").format(payload.name, WI.unlocalizedString("level"))); return null; } if (typeof payload.data !== "object" || payload.data === null) { if ("data" in payload) WI.AuditManager.synthesizeWarning(WI.UIString("\u0022%s\u0022 has a non-object \u0022%s\u0022 value").format(payload.name, WI.unlocalizedString("data"))); payload.data = {}; } else { function checkArray(key) { if (!(key in payload.data)) return; if (!Array.isArray(payload.data[key])) { WI.AuditManager.synthesizeWarning(WI.UIString("\u0022%s\u0022 has a non-array \u0022%s\u0022 value").format(payload.name, WI.unlocalizedString("data.%s").format(key))); payload.data[key] = []; } payload.data[key] = payload.data[key].filter((item) => typeof item === "string"); } checkArray("domNodes"); checkArray("domAttributes"); checkArray("errors"); } if (typeof payload.metadata !== "object" || payload.metadata === null) { if ("metadata" in payload) WI.AuditManager.synthesizeWarning(WI.UIString("\u0022%s\u0022 has a non-object \u0022%s\u0022 value").format(payload.name, WI.unlocalizedString("metadata"))); payload.metadata = {}; } else { if (typeof payload.metadata.startTimestamp === "string") payload.metadata.startTimestamp = new Date(payload.metadata.startTimestamp); else { if ("startTimestamp" in payload.metadata) WI.AuditManager.synthesizeWarning(WI.UIString("\u0022%s\u0022 has a non-object \u0022%s\u0022 value").format(payload.name, WI.unlocalizedString("metadata.startTimestamp"))); payload.metadata.startTimestamp = null; } if (typeof payload.metadata.asyncTimestamp === "string") payload.metadata.asyncTimestamp = new Date(payload.metadata.asyncTimestamp); else { if ("asyncTimestamp" in payload.metadata) WI.AuditManager.synthesizeWarning(WI.UIString("\u0022%s\u0022 has a non-object \u0022%s\u0022 value").format(payload.name, WI.unlocalizedString("metadata.asyncTimestamp"))); payload.metadata.asyncTimestamp = null; } if (typeof payload.metadata.endTimestamp === "string") payload.metadata.endTimestamp = new Date(payload.metadata.endTimestamp); else { if ("endTimestamp" in payload.metadata) WI.AuditManager.synthesizeWarning(WI.UIString("\u0022%s\u0022 has a non-object \u0022%s\u0022 value").format(payload.name, WI.unlocalizedString("metadata.endTimestamp"))); payload.metadata.endTimestamp = null; } if (typeof payload.metadata.url !== "string") { if ("url" in payload.metadata) WI.AuditManager.synthesizeWarning(WI.UIString("\u0022%s\u0022 has a non-object \u0022%s\u0022 value").format(payload.name, WI.unlocalizedString("metadata.url"))); payload.metadata.url = null; } } let options = {}; if (typeof payload.description === "string") options.description = payload.description; else if ("description" in payload) WI.AuditManager.synthesizeWarning(WI.UIString("\u0022%s\u0022 has a non-string \u0022%s\u0022 value").format(payload.name, WI.unlocalizedString("description"))); if (!isEmptyObject(payload.data)) { options.data = {}; for (let key in payload.data) { if (key === "domNodes" || key === "domAttributes" || key === "errors") { if (!payload.data[key].length) continue; } if (key === "domNodes") { if (InspectorBackend.hasDomain("DOM") && (!payload.metadata.url || payload.metadata.url === WI.networkManager.mainFrame.url)) { let documentNode = await new Promise((resolve) => WI.domManager.requestDocument(resolve)); options.resolvedDOMNodes = await Promise.all(payload.data.domNodes.map(async (domNodeString) => { let nodeId = 0; try { nodeId = await documentNode.querySelector(domNodeString); } catch { } return WI.domManager.nodeForId(nodeId); })); } } options.data[key] = payload.data[key]; } } if (!isEmptyObject(payload.metadata)) { options.metadata = {}; if (payload.metadata.startTimestamp && !isNaN(payload.metadata.startTimestamp)) options.metadata.startTimestamp = payload.metadata.startTimestamp; if (payload.metadata.asyncTimestamp && !isNaN(payload.metadata.asyncTimestamp)) options.metadata.asyncTimestamp = payload.metadata.asyncTimestamp; if (payload.metadata.endTimestamp && !isNaN(payload.metadata.endTimestamp)) options.metadata.endTimestamp = payload.metadata.endTimestamp; if (payload.metadata.url) options.metadata.url = payload.metadata.url; } return new WI.AuditTestCaseResult(payload.name, payload.level, options); } // Public get level() { return this._level; } get data() { return this._data; } get metadata() { return this._metadata; } get resolvedDOMNodes() { return this._resolvedDOMNodes; } get result() { return this; } get didPass() { return this._level === WI.AuditTestCaseResult.Level.Pass; } get didWarn() { return this._level === WI.AuditTestCaseResult.Level.Warn; } get didFail() { return this._level === WI.AuditTestCaseResult.Level.Fail; } get didError() { return this._level === WI.AuditTestCaseResult.Level.Error; } get unsupported() { return this._level === WI.AuditTestCaseResult.Level.Unsupported; } toJSON() { let json = super.toJSON(); json.level = this._level; let data = {}; for (let key in this._data) { if (key === "domNodes" || key === "domAttributes" || key === "errors") { if (!this._data[key].length) continue; } data[key] = this._data[key]; } if (!isEmptyObject(data)) json.data = data; let metadata = {}; if (this._metadata.startTimestamp && !isNaN(this._metadata.startTimestamp)) metadata.startTimestamp = this._metadata.startTimestamp; if (this._metadata.asyncTimestamp && !isNaN(this._metadata.asyncTimestamp)) metadata.asyncTimestamp = this._metadata.asyncTimestamp; if (this._metadata.endTimestamp && !isNaN(this._metadata.endTimestamp)) metadata.endTimestamp = this._metadata.endTimestamp; if (this._metadata.url) metadata.url = this._metadata.url; if (!isEmptyObject(metadata)) json.metadata = metadata; return json; } }; WI.AuditTestCaseResult.TypeIdentifier = "test-case-result"; // Keep this ordered by precedence. WI.AuditTestCaseResult.Level = { Pass: "pass", Warn: "warn", Fail: "fail", Error: "error", Unsupported: "unsupported", }; /* Models/AuditTestGroupResult.js */ /* * Copyright (C) 2018 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.AuditTestGroupResult = class AuditTestGroupResult extends WI.AuditTestResultBase { constructor(name, results, {description} = {}) { console.assert(Array.isArray(results)); super(name, {description}); this._results = results; } // Static static async fromPayload(payload) { if (typeof payload !== "object" || payload === null) return null; if (payload.type !== WI.AuditTestGroupResult.TypeIdentifier) return null; if (typeof payload.name !== "string") { WI.AuditManager.synthesizeError(WI.UIString("\u0022%s\u0022 has a non-string \u0022%s\u0022 value").format(payload.name, WI.unlocalizedString("name"))); return null; } if (!Array.isArray(payload.results)) { WI.AuditManager.synthesizeError(WI.UIString("\u0022%s\u0022 has a non-array \u0022%s\u0022 value").format(payload.name, WI.unlocalizedString("results"))); return null; } let results = await Promise.all(payload.results.map(async (test) => { let testCaseResult = await WI.AuditTestCaseResult.fromPayload(test); if (testCaseResult) return testCaseResult; let testGroupResult = await WI.AuditTestGroupResult.fromPayload(test); if (testGroupResult) return testGroupResult; return null; })); results = results.filter((result) => !!result); if (!results.length) return null; let options = {}; if (typeof payload.description === "string") options.description = payload.description; else if ("description" in payload) WI.AuditManager.synthesizeWarning(WI.UIString("\u0022%s\u0022 has a non-string \u0022%s\u0022 value").format(payload.name, WI.unlocalizedString("description"))); return new WI.AuditTestGroupResult(payload.name, results, options); } // Public get results() { return this._results; } get levelCounts() { let counts = {}; for (let level of Object.values(WI.AuditTestCaseResult.Level)) counts[level] = 0; for (let result of this._results) { if (result instanceof WI.AuditTestCaseResult) ++counts[result.level]; else if (result instanceof WI.AuditTestGroupResult) { for (let [level, count] of Object.entries(result.levelCounts)) counts[level] += count; } } return counts; } get didPass() { return this._results.some((result) => result.didPass); } get didWarn() { return this._results.some((result) => result.didWarn); } get didFail() { return this._results.some((result) => result.didFail); } get didError() { return this._results.some((result) => result.didError); } get unsupported() { return this._results.some((result) => result.unsupported); } toJSON() { let json = super.toJSON(); json.results = this._results.map((result) => result.toJSON()); return json; } }; WI.AuditTestGroupResult.TypeIdentifier = "test-group-result"; /* Proxies/FormatterWorkerProxy.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.FormatterWorkerProxy = class FormatterWorkerProxy { constructor() { this._formatterWorker = new Worker("Workers/Formatter/FormatterWorker.js"); this._formatterWorker.addEventListener("message", this._handleMessage.bind(this)); this._nextCallId = 1; this._callbacks = new Map; } // Static static singleton() { if (!FormatterWorkerProxy.instance) FormatterWorkerProxy.instance = new FormatterWorkerProxy; return FormatterWorkerProxy.instance; } // Actions formatJavaScript(sourceText, isModule, indentString, includeSourceMapData) { this.performAction("formatJavaScript", ...arguments); } formatCSS(sourceText, indentString, includeSourceMapData) { this.performAction("formatCSS", ...arguments); } formatHTML(sourceText, indentString, includeSourceMapData) { this.performAction("formatHTML", ...arguments); } formatXML(sourceText, indentString, includeSourceMapData) { this.performAction("formatXML", ...arguments); } // Public performAction(actionName) { let callId = this._nextCallId++; let callback = arguments[arguments.length - 1]; let actionArguments = Array.prototype.slice.call(arguments, 1, arguments.length - 1); console.assert(typeof actionName === "string", "performAction should always have an actionName"); console.assert(typeof callback === "function", "performAction should always have a callback"); this._callbacks.set(callId, callback); this._postMessage({callId, actionName, actionArguments}); } // Private _postMessage() { this._formatterWorker.postMessage(...arguments); } _handleMessage(event) { let data = event.data; // Action response. if (data.callId) { let callback = this._callbacks.get(data.callId); this._callbacks.delete(data.callId); callback(data.result); return; } console.error("Unexpected FormatterWorker message", data); } }; /* Proxies/HeapSnapshotDiffProxy.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.HeapSnapshotDiffProxy = class HeapSnapshotDiffProxy extends WI.Object { constructor(snapshotDiffObjectId, snapshot1, snapshot2, totalSize, totalObjectCount, categories) { super(); this._proxyObjectId = snapshotDiffObjectId; console.assert(snapshot1 instanceof WI.HeapSnapshotProxy); console.assert(snapshot2 instanceof WI.HeapSnapshotProxy); this._snapshot1 = snapshot1; this._snapshot2 = snapshot2; this._totalSize = totalSize; this._totalObjectCount = totalObjectCount; this._categories = Map.fromObject(categories); } // Static static deserialize(objectId, serializedSnapshotDiff) { let {snapshot1: serializedSnapshot1, snapshot2: serializedSnapshot2, totalSize, totalObjectCount, categories} = serializedSnapshotDiff; // FIXME: The objectId for these snapshots is the snapshotDiff's objectId. Currently these // snapshots are only used for static data so the proxing doesn't matter. However, // should we serialize the objectId with the snapshot so we have the right objectId? let snapshot1 = WI.HeapSnapshotProxy.deserialize(objectId, serializedSnapshot1); let snapshot2 = WI.HeapSnapshotProxy.deserialize(objectId, serializedSnapshot2); return new WI.HeapSnapshotDiffProxy(objectId, snapshot1, snapshot2, totalSize, totalObjectCount, categories); } // Public get snapshot1() { return this._snapshot1; } get snapshot2() { return this._snapshot2; } get totalSize() { return this._totalSize; } get totalObjectCount() { return this._totalObjectCount; } get categories() { return this._categories; } get invalid() { return this._snapshot1.invalid || this._snapshot2.invalid; } updateForCollectionEvent(event) { console.assert(!this.invalid); if (!event.data.affectedSnapshots.includes(this._snapshot2._identifier)) return; this.update(() => { this.dispatchEventToListeners(WI.HeapSnapshotProxy.Event.CollectedNodes, event.data); }); } allocationBucketCounts(bucketSizes, callback) { console.assert(!this.invalid); WI.HeapSnapshotWorkerProxy.singleton().callMethod(this._proxyObjectId, "allocationBucketCounts", bucketSizes, callback); } instancesWithClassName(className, callback) { console.assert(!this.invalid); WI.HeapSnapshotWorkerProxy.singleton().callMethod(this._proxyObjectId, "instancesWithClassName", className, (serializedNodes) => { callback(serializedNodes.map(WI.HeapSnapshotNodeProxy.deserialize.bind(null, this._proxyObjectId))); }); } update(callback) { console.assert(!this.invalid); WI.HeapSnapshotWorkerProxy.singleton().callMethod(this._proxyObjectId, "update", ({liveSize, categories}) => { this._categories = Map.fromObject(categories); callback(); }); } nodeWithIdentifier(nodeIdentifier, callback) { console.assert(!this.invalid); WI.HeapSnapshotWorkerProxy.singleton().callMethod(this._proxyObjectId, "nodeWithIdentifier", nodeIdentifier, (serializedNode) => { callback(WI.HeapSnapshotNodeProxy.deserialize(this._proxyObjectId, serializedNode)); }); } }; /* Proxies/HeapSnapshotEdgeProxy.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. */ // Directed edge between two HeapSnapshotNodes 'from' and 'to'. WI.HeapSnapshotEdgeProxy = class HeapSnapshotEdgeProxy { constructor(objectId, fromIdentifier, toIdentifier, type, data) { this._proxyObjectId = objectId; console.assert(type in WI.HeapSnapshotEdgeProxy.EdgeType); this.fromIdentifier = fromIdentifier; this.toIdentifier = toIdentifier; this.type = type; this.data = data; this.from = null; this.to = null; } isPrivateSymbol() { if (WI.settings.engineeringShowPrivateSymbolsInHeapSnapshot.value) return false; return typeof this.data === "string" && this.data.startsWith("PrivateSymbol"); } // Static static deserialize(objectId, serializedEdge) { let {from, to, type, data} = serializedEdge; return new WI.HeapSnapshotEdgeProxy(objectId, from, to, type, data); } }; WI.HeapSnapshotEdgeProxy.EdgeType = { Internal: "Internal", // No data. Property: "Property", // data is string property name. Index: "Index", // data is numeric index. Variable: "Variable", // data is string variable name. }; /* Proxies/HeapSnapshotNodeProxy.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.HeapSnapshotNodeProxy = class HeapSnapshotNodeProxy { constructor(snapshotObjectId, {id, className, size, retainedSize, internal, isObjectType, gcRoot, dead, dominatorNodeIdentifier, hasChildren}) { this._proxyObjectId = snapshotObjectId; this.id = id; this.className = className; this.size = size; this.retainedSize = retainedSize; this.internal = internal; this.isObjectType = isObjectType; this.gcRoot = gcRoot; this.dead = dead; this.dominatorNodeIdentifier = dominatorNodeIdentifier; this.hasChildren = hasChildren; } // Static static deserialize(objectId, serializedNode) { return new WI.HeapSnapshotNodeProxy(objectId, serializedNode); } // Proxied shortestGCRootPath(callback) { WI.HeapSnapshotWorkerProxy.singleton().callMethod(this._proxyObjectId, "shortestGCRootPath", this.id, (serializedPath) => { let isNode = false; let path = serializedPath.map((component) => { isNode = !isNode; if (isNode) return WI.HeapSnapshotNodeProxy.deserialize(this._proxyObjectId, component); return WI.HeapSnapshotEdgeProxy.deserialize(this._proxyObjectId, component); }); for (let i = 1; i < path.length; i += 2) { console.assert(path[i] instanceof WI.HeapSnapshotEdgeProxy); let edge = path[i]; edge.from = path[i - 1]; edge.to = path[i + 1]; } callback(path); }); } dominatedNodes(callback) { WI.HeapSnapshotWorkerProxy.singleton().callMethod(this._proxyObjectId, "dominatedNodes", this.id, (serializedNodes) => { callback(serializedNodes.map(WI.HeapSnapshotNodeProxy.deserialize.bind(null, this._proxyObjectId))); }); } retainedNodes(callback) { WI.HeapSnapshotWorkerProxy.singleton().callMethod(this._proxyObjectId, "retainedNodes", this.id, ({retainedNodes: serializedNodes, edges: serializedEdges}) => { let deserializedNodes = serializedNodes.map(WI.HeapSnapshotNodeProxy.deserialize.bind(null, this._proxyObjectId)); let deserializedEdges = serializedEdges.map(WI.HeapSnapshotEdgeProxy.deserialize.bind(null, this._proxyObjectId)); callback(deserializedNodes, deserializedEdges); }); } retainers(callback) { WI.HeapSnapshotWorkerProxy.singleton().callMethod(this._proxyObjectId, "retainers", this.id, ({retainers: serializedNodes, edges: serializedEdges}) => { let deserializedNodes = serializedNodes.map(WI.HeapSnapshotNodeProxy.deserialize.bind(null, this._proxyObjectId)); let deserializedEdges = serializedEdges.map(WI.HeapSnapshotEdgeProxy.deserialize.bind(null, this._proxyObjectId)); callback(deserializedNodes, deserializedEdges); }); } }; /* Proxies/HeapSnapshotProxy.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.HeapSnapshotProxy = class HeapSnapshotProxy extends WI.Object { constructor(snapshotObjectId, identifier, title, totalSize, totalObjectCount, liveSize, categories, imported) { super(); this._proxyObjectId = snapshotObjectId; this._identifier = identifier; this._title = title; this._totalSize = totalSize; this._totalObjectCount = totalObjectCount; this._liveSize = liveSize; this._categories = Map.fromObject(categories); this._imported = imported; this._snapshotStringData = null; console.assert(!this.invalid); if (!WI.HeapSnapshotProxy.ValidSnapshotProxies) WI.HeapSnapshotProxy.ValidSnapshotProxies = []; WI.HeapSnapshotProxy.ValidSnapshotProxies.push(this); } // Static static deserialize(objectId, serializedSnapshot) { let {identifier, title, totalSize, totalObjectCount, liveSize, categories, imported} = serializedSnapshot; return new WI.HeapSnapshotProxy(objectId, identifier, title, totalSize, totalObjectCount, liveSize, categories, imported); } static invalidateSnapshotProxies() { if (!WI.HeapSnapshotProxy.ValidSnapshotProxies) return; for (let snapshotProxy of WI.HeapSnapshotProxy.ValidSnapshotProxies) snapshotProxy._invalidate(); WI.HeapSnapshotProxy.ValidSnapshotProxies = null; } // Public get proxyObjectId() { return this._proxyObjectId; } get identifier() { return this._identifier; } get title() { return this._title; } get totalSize() { return this._totalSize; } get totalObjectCount() { return this._totalObjectCount; } get liveSize() { return this._liveSize; } get categories() { return this._categories; } get imported() { return this._imported; } get invalid() { return this._proxyObjectId === 0; } get snapshotStringData() { return this._snapshotStringData; } set snapshotStringData(data) { this._snapshotStringData = data; } updateForCollectionEvent(event) { console.assert(!this.invalid); if (!event.data.affectedSnapshots.includes(this._identifier)) return; this.update(() => { this.dispatchEventToListeners(WI.HeapSnapshotProxy.Event.CollectedNodes, event.data); }); } allocationBucketCounts(bucketSizes, callback) { console.assert(!this.invalid); WI.HeapSnapshotWorkerProxy.singleton().callMethod(this._proxyObjectId, "allocationBucketCounts", bucketSizes, callback); } instancesWithClassName(className, callback) { console.assert(!this.invalid); WI.HeapSnapshotWorkerProxy.singleton().callMethod(this._proxyObjectId, "instancesWithClassName", className, (serializedNodes) => { callback(serializedNodes.map(WI.HeapSnapshotNodeProxy.deserialize.bind(null, this._proxyObjectId))); }); } update(callback) { console.assert(!this.invalid); WI.HeapSnapshotWorkerProxy.singleton().callMethod(this._proxyObjectId, "update", ({liveSize, categories}) => { this._liveSize = liveSize; this._categories = Map.fromObject(categories); callback(); }); } nodeWithIdentifier(nodeIdentifier, callback) { console.assert(!this.invalid); WI.HeapSnapshotWorkerProxy.singleton().callMethod(this._proxyObjectId, "nodeWithIdentifier", nodeIdentifier, (serializedNode) => { callback(WI.HeapSnapshotNodeProxy.deserialize(this._proxyObjectId, serializedNode)); }); } // Private _invalidate() { this._proxyObjectId = 0; this._liveSize = 0; this.dispatchEventToListeners(WI.HeapSnapshotProxy.Event.Invalidated); } }; WI.HeapSnapshotProxy.Event = { CollectedNodes: "heap-snapshot-proxy-collected-nodes", Invalidated: "heap-snapshot-proxy-invalidated", }; /* Proxies/HeapSnapshotWorkerProxy.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.HeapSnapshotWorkerProxy = class HeapSnapshotWorkerProxy extends WI.Object { constructor() { super(); this._heapSnapshotWorker = new Worker("Workers/HeapSnapshot/HeapSnapshotWorker.js"); this._heapSnapshotWorker.addEventListener("message", this._handleMessage.bind(this)); this._nextCallId = 1; this._callbacks = new Map; WI.Frame.addEventListener(WI.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this); } // Static static singleton() { if (!HeapSnapshotWorkerProxy.instance) HeapSnapshotWorkerProxy.instance = new HeapSnapshotWorkerProxy; return HeapSnapshotWorkerProxy.instance; } // Actions clearSnapshots(callback) { this.performAction("clearSnapshots", callback); } createSnapshot(snapshotStringData, callback) { this.performAction("createSnapshot", ...arguments); } createSnapshotDiff(objectId1, objectId2, callback) { this.performAction("createSnapshotDiff", ...arguments); } createImportedSnapshot(snapshotStringData, title, callback) { const imported = true; this.performAction("createSnapshot", snapshotStringData, title, imported, callback); } // Public performAction(actionName) { let callId = this._nextCallId++; let callback = arguments[arguments.length - 1]; let actionArguments = Array.prototype.slice.call(arguments, 1, arguments.length - 1); console.assert(typeof actionName === "string", "performAction should always have an actionName"); console.assert(typeof callback === "function", "performAction should always have a callback"); this._callbacks.set(callId, callback); this._postMessage({callId, actionName, actionArguments}); } callMethod(objectId, methodName) { let callId = this._nextCallId++; let callback = arguments[arguments.length - 1]; let methodArguments = Array.prototype.slice.call(arguments, 2, arguments.length - 1); console.assert(typeof objectId === "number", "callMethod should always have an objectId"); console.assert(typeof methodName === "string", "callMethod should always have a methodName"); console.assert(typeof callback === "function", "callMethod should always have a callback"); this._callbacks.set(callId, callback); this._postMessage({callId, objectId, methodName, methodArguments}); } // Private _mainResourceDidChange(event) { if (!event.target.isMainFrame()) return; this.clearSnapshots(() => { WI.HeapSnapshotProxy.invalidateSnapshotProxies(); }); } _postMessage() { this._heapSnapshotWorker.postMessage(...arguments); } _handleMessage(event) { let data = event.data; // Error. if (data.error) { console.assert(data.callId); this._callbacks.delete(data.callId); return; } // Event. if (data.eventName) { this.dispatchEventToListeners(data.eventName, data.eventData); return; } // Action or Method Response. if (data.callId) { let callback = this._callbacks.get(data.callId); this._callbacks.delete(data.callId); callback(data.result); return; } console.error("Unexpected HeapSnapshotWorker message", data); } }; WI.HeapSnapshotWorkerProxy.Event = { Collection: "heap-snapshot-collection", }; /* Controllers/QueryController.js */ /* * Copyright (C) 2021 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.QueryController = class QueryController { // Public executeQuery(query) { throw WI.NotImplementedError.subclassMustOverride(); } findQueryMatches(query, searchString, specialCharacterIndices) { if (query.length > searchString.length) return []; let matches = []; let queryIndex = 0; let searchIndex = 0; let specialIndex = 0; let deadBranches = (new Array(query.length)).fill(Infinity); let type = WI.QueryMatch.Type.Special; function pushMatch(index) { matches.push(new WI.QueryMatch(type, index, queryIndex)); searchIndex = index + 1; queryIndex++; } function matchNextSpecialCharacter() { if (specialIndex >= specialCharacterIndices.length) return false; let originalSpecialIndex = specialIndex; while (specialIndex < specialCharacterIndices.length) { // Normal character matching can move past special characters, // so advance the special character index if it's before the // current search string position. let index = specialCharacterIndices[specialIndex++]; if (index < searchIndex) continue; if (query[queryIndex] === searchString[index]) { pushMatch(index); return true; } } specialIndex = originalSpecialIndex; return false; } function backtrack() { while (matches.length) { queryIndex--; let lastMatch = matches.pop(); if (lastMatch.type !== WI.QueryMatch.Type.Special) continue; deadBranches[lastMatch.queryIndex] = lastMatch.index; searchIndex = matches.lastValue ? matches.lastValue.index + 1 : 0; return true; } return false; } while (queryIndex < query.length && searchIndex <= searchString.length) { if (type === WI.QueryMatch.Type.Special && !matchNextSpecialCharacter()) type = WI.QueryMatch.Type.Normal; if (type === WI.QueryMatch.Type.Normal) { let index = searchString.indexOf(query[queryIndex], searchIndex); if (index >= 0 && index < deadBranches[queryIndex]) { pushMatch(index); type = WI.QueryMatch.Type.Special; } else if (!backtrack()) return []; } } if (queryIndex < query.length) return []; return matches; } // Protected findSpecialCharacterIndices(string, separators) { if (!string.length) return []; // Special characters include the following: // - The first character. // - Uppercase characters that follow a lowercase letter. // - Separators and the first character following the separator. let indices = [0]; for (let i = 1; i < string.length; ++i) { let character = string[i]; let isSpecial = false; if (separators.includes(character)) isSpecial = true; else { let previousCharacter = string[i - 1]; if (separators.includes(previousCharacter)) isSpecial = true; else if (character.isUpperCase() && previousCharacter.isLowerCase()) isSpecial = true; } if (isSpecial) indices.push(i); } return indices; } }; /* Controllers/AnimationManager.js */ /* * Copyright (C) 2019 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. */ // FIXME: AnimationManager lacks advanced multi-target support. (Animations per-target) WI.AnimationManager = class AnimationManager { constructor() { this._enabled = false; this._animationCollection = new WI.AnimationCollection; this._animationIdMap = new Map; WI.Frame.addEventListener(WI.Frame.Event.MainResourceDidChange, this._handleMainResourceDidChange, this); } // Agent get domains() { return ["Animation"]; } activateExtraDomain(domain) { // COMPATIBILITY (iOS 14.0): Inspector.activateExtraDomains was removed in favor of a declared debuggable type console.assert(domain === "Animation"); for (let target of WI.targets) this.initializeTarget(target); } // Target initializeTarget(target) { if (!this._enabled) return; // COMPATIBILITY (iOS 13.1): Animation.enable did not exist yet. if (target.hasCommand("Animation.enable")) target.AnimationAgent.enable(); } // Public get animationCollection() { return this._animationCollection; } get supported() { // COMPATIBILITY (iOS 13.1): Animation.enable did not exist yet. return InspectorBackend.hasCommand("Animation.enable"); } enable() { console.assert(!this._enabled); this._enabled = true; for (let target of WI.targets) this.initializeTarget(target); } disable() { console.assert(this._enabled); for (let target of WI.targets) { // COMPATIBILITY (iOS 13.1): Animation.disable did not exist yet. if (target.hasCommand("Animation.disable")) target.AnimationAgent.disable(); } this._animationCollection.clear(); this._animationIdMap.clear(); this._enabled = false; } // AnimationObserver animationCreated(animationPayload) { console.assert(!this._animationIdMap.has(animationPayload.animationId), `Animation already exists with id ${animationPayload.animationId}.`); let animation = WI.Animation.fromPayload(animationPayload); this._animationCollection.add(animation); this._animationIdMap.set(animation.animationId, animation); } nameChanged(animationId, name) { let animation = this._animationIdMap.get(animationId); console.assert(animation); if (!animation) return; animation.nameChanged(name); } effectChanged(animationId, effect) { let animation = this._animationIdMap.get(animationId); console.assert(animation); if (!animation) return; animation.effectChanged(effect); } targetChanged(animationId, effect) { let animation = this._animationIdMap.get(animationId); console.assert(animation); if (!animation) return; animation.targetChanged(effect); } animationDestroyed(animationId) { let animation = this._animationIdMap.take(animationId); console.assert(animation); if (!animation) return; this._animationCollection.remove(animation); } // Private _handleMainResourceDidChange(event) { console.assert(event.target instanceof WI.Frame); if (!event.target.isMainFrame()) return; WI.Animation.resetUniqueDisplayNameNumbers(); this._animationCollection.clear(); this._animationIdMap.clear(); } }; /* Controllers/AuditManager.js */ /* * Copyright (C) 2018 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.AuditManager = class AuditManager extends WI.Object { constructor() { super(); this._tests = []; this._results = []; this._runningState = WI.AuditManager.RunningState.Inactive; this._runningTests = []; this._disabledDefaultTestsSetting = new WI.Setting("audit-disabled-default-tests", []); WI.Frame.addEventListener(WI.Frame.Event.MainResourceDidChange, this._handleFrameMainResourceDidChange, this); } // Static static synthesizeWarning(message) { message = WI.UIString("Audit Warning: %s").format(message); if (window.InspectorTest) { console.warn(message); return; } let consoleMessage = new WI.ConsoleMessage(WI.mainTarget, WI.ConsoleMessage.MessageSource.Other, WI.ConsoleMessage.MessageLevel.Warning, message); consoleMessage.shouldRevealConsole = true; WI.consoleLogViewController.appendConsoleMessage(consoleMessage); } static synthesizeError(message) { message = WI.UIString("Audit Error: %s").format(message); if (window.InspectorTest) { console.error(message); return; } let consoleMessage = new WI.ConsoleMessage(WI.mainTarget, WI.ConsoleMessage.MessageSource.Other, WI.ConsoleMessage.MessageLevel.Error, message); consoleMessage.shouldRevealConsole = true; WI.consoleLogViewController.appendConsoleMessage(consoleMessage); } // Public get tests() { return this._tests; } get results() { return this._results; } get runningState() { return this._runningState; } get editing() { return this._runningState === WI.AuditManager.RunningState.Disabled; } set editing(editing) { console.assert(this._runningState === WI.AuditManager.RunningState.Disabled || this._runningState === WI.AuditManager.RunningState.Inactive); if (this._runningState !== WI.AuditManager.RunningState.Disabled && this._runningState !== WI.AuditManager.RunningState.Inactive) return; let runningState = editing ? WI.AuditManager.RunningState.Disabled : WI.AuditManager.RunningState.Inactive; console.assert(runningState !== this._runningState); if (runningState === this._runningState) return; this._runningState = runningState; this.dispatchEventToListeners(WI.AuditManager.Event.EditingChanged); if (!this.editing) { WI.objectStores.audits.clear(); let disabledDefaultTests = []; let saveDisabledDefaultTest = (test) => { if (test.supported && test.disabled) disabledDefaultTests.push(test.name); if (test instanceof WI.AuditTestGroup) { for (let child of test.tests) saveDisabledDefaultTest(child); } }; for (let test of this._tests) { if (test.default) saveDisabledDefaultTest(test); else WI.objectStores.audits.putObject(test); } this._disabledDefaultTestsSetting.value = disabledDefaultTests; } } async start(tests) { console.assert(this._runningState === WI.AuditManager.RunningState.Inactive); if (this._runningState !== WI.AuditManager.RunningState.Inactive) return null; if (tests && tests.length) tests = tests.filter((test) => typeof test === "object" && test instanceof WI.AuditTestBase); else tests = this._tests; console.assert(tests.length); if (!tests.length) return null; let mainResource = WI.networkManager.mainFrame.mainResource; this._runningState = WI.AuditManager.RunningState.Active; this.dispatchEventToListeners(WI.AuditManager.Event.RunningStateChanged); this._runningTests = tests; for (let test of this._runningTests) test.clearResult(); this.dispatchEventToListeners(WI.AuditManager.Event.TestScheduled); let target = WI.assumingMainTarget(); await Promise.chain(this._runningTests.map((test) => async () => { if (this._runningState !== WI.AuditManager.RunningState.Active) return; if (target.hasDomain("Audit")) await target.AuditAgent.setup(); let topLevelTest = test.topLevelTest; console.assert(topLevelTest || window.InspectorTest, "No matching top-level test found", test); if (topLevelTest) await topLevelTest.runSetup(); await test.start(); if (target.hasDomain("Audit")) await target.AuditAgent.teardown(); })); let result = this._runningTests.map((test) => test.result).filter((result) => !!result); this._runningState = WI.AuditManager.RunningState.Inactive; this.dispatchEventToListeners(WI.AuditManager.Event.RunningStateChanged); this._runningTests = []; this._addResult(result); if (mainResource !== WI.networkManager.mainFrame.mainResource) { // Navigated while tests were running. for (let test of this._tests) test.clearResult(); } return this._results.lastValue === result ? result : null; } stop() { console.assert(this._runningState === WI.AuditManager.RunningState.Active); if (this._runningState !== WI.AuditManager.RunningState.Active) return; this._runningState = WI.AuditManager.RunningState.Stopping; this.dispatchEventToListeners(WI.AuditManager.Event.RunningStateChanged); for (let test of this._runningTests) test.stop(); } async processJSON({json, error}) { if (error) { WI.AuditManager.synthesizeError(error); return; } if (typeof json !== "object" || json === null) { WI.AuditManager.synthesizeError(WI.UIString("invalid JSON")); return; } if (json.type !== WI.AuditTestCase.TypeIdentifier && json.type !== WI.AuditTestGroup.TypeIdentifier && json.type !== WI.AuditTestCaseResult.TypeIdentifier && json.type !== WI.AuditTestGroupResult.TypeIdentifier) { WI.AuditManager.synthesizeError(WI.UIString("unknown %s \u0022%s\u0022").format(WI.unlocalizedString("type"), json.type)); return; } let object = await WI.AuditTestGroup.fromPayload(json) || await WI.AuditTestCase.fromPayload(json) || await WI.AuditTestGroupResult.fromPayload(json) || await WI.AuditTestCaseResult.fromPayload(json); if (!object) return; if (object instanceof WI.AuditTestBase) this.addTest(object, {save: true}); else if (object instanceof WI.AuditTestResultBase) this._addResult(object); WI.showRepresentedObject(object); } export(saveMode, object) { console.assert(object instanceof WI.AuditTestCase || object instanceof WI.AuditTestGroup || object instanceof WI.AuditTestCaseResult || object instanceof WI.AuditTestGroupResult, object); function dataForObject(object) { return { displayType: object instanceof WI.AuditTestResultBase ? WI.UIString("Result") : WI.UIString("Audit"), content: JSON.stringify(object), suggestedName: object.name + (object instanceof WI.AuditTestResultBase ? ".result" : ".audit"), }; } let data = [dataForObject(object)]; if (saveMode === WI.FileUtilities.SaveMode.FileVariants && object instanceof WI.AuditTestBase && object.result) data.push(dataForObject(object.result)); WI.FileUtilities.save(saveMode, data); } loadStoredTests() { if (this._tests.length) return; this._addDefaultTests(); WI.objectStores.audits.getAll().then(async (tests) => { for (let payload of tests) { let test = await WI.AuditTestGroup.fromPayload(payload) || await WI.AuditTestCase.fromPayload(payload); if (!test) continue; const key = null; WI.objectStores.audits.associateObject(test, key, payload); this.addTest(test); } }); } addTest(test, {save} = {}) { console.assert(test instanceof WI.AuditTestBase, test); console.assert(!this._tests.includes(test), test); this._tests.push(test); if (save) WI.objectStores.audits.putObject(test); this.dispatchEventToListeners(WI.AuditManager.Event.TestAdded, {test}); } removeTest(test) { console.assert(this.editing); console.assert(test instanceof WI.AuditTestBase, test); console.assert(this._tests.includes(test) || test.default, test); if (test.default) { test.clearResult(); if (test.disabled) { InspectorFrontendHost.beep(); return; } test.disabled = true; let disabledTests = this._disabledDefaultTestsSetting.value.slice(); disabledTests.push(test.name); this._disabledDefaultTestsSetting.value = disabledTests; return; } console.assert(test.editable, test); this._tests.remove(test); this.dispatchEventToListeners(WI.AuditManager.Event.TestRemoved, {test}); WI.objectStores.audits.deleteObject(test); } // Private _addResult(result) { if (!result || (Array.isArray(result) && !result.length)) return; this._results.push(result); this.dispatchEventToListeners(WI.AuditManager.Event.TestCompleted, { result, index: this._results.length - 1, }); } _handleFrameMainResourceDidChange(event) { if (!event.target.isMainFrame()) return; if (this._runningState === WI.AuditManager.RunningState.Active) this.stop(); else { for (let test of this._tests) test.clearResult(); } } _addDefaultTests() { console.assert(WI.DefaultAudits, "Default audits not loaded."); if (!WI.DefaultAudits) return; const defaultTests = [ new WI.AuditTestGroup(WI.UIString("Demo Audit"), [ new WI.AuditTestGroup(WI.UIString("Result Levels"), [ new WI.AuditTestCase("level-pass", WI.DefaultAudits.levelPass.toString(), {description: WI.UIString("This is what the result of a passing test with no data looks like.")}), new WI.AuditTestCase("level-warn", WI.DefaultAudits.levelWarn.toString(), {description: WI.UIString("This is what the result of a warning test with no data looks like.")}), new WI.AuditTestCase("level-fail", WI.DefaultAudits.levelFail.toString(), {description: WI.UIString("This is what the result of a failing test with no data looks like.")}), new WI.AuditTestCase("level-error", WI.DefaultAudits.levelError.toString(), {description: WI.UIString("This is what the result of a test that threw an error with no data looks like.")}), new WI.AuditTestCase("level-unsupported", WI.DefaultAudits.levelUnsupported.toString(), {description: WI.UIString("This is what the result of an unsupported test with no data looks like.")}), ], {description: WI.UIString("These are all of the different test result levels.")}), new WI.AuditTestGroup(WI.UIString("Result Data"), [ new WI.AuditTestCase("data-domNodes", WI.DefaultAudits.dataDOMNodes.toString(), {description: WI.UIString("This is an example of how result DOM nodes are shown. It will pass with the element.")}), new WI.AuditTestCase("data-domAttributes", WI.DefaultAudits.dataDOMAttributes.toString(), {description: WI.UIString("This is an example of how result DOM attributes are highlighted on any returned DOM nodes. It will pass with all elements with an id attribute.")}), new WI.AuditTestCase("data-errors", WI.DefaultAudits.dataErrors.toString(), {description: WI.UIString("This is an example of how errors are shown. The error was thrown manually, but execution errors will appear in the same way.")}), new WI.AuditTestCase("data-custom", WI.DefaultAudits.dataCustom.toString(), {description: WI.UIString("This is an example of how custom result data is shown."), supports: 3}), ], {description: WI.UIString("These are example tests that demonstrate all of the different types of data that can be returned with the test result.")}), new WI.AuditTestGroup(WI.UIString("Specially Exposed Data"), [ new WI.AuditTestGroup(WI.UIString("Accessibility"), [ new WI.AuditTestCase("getElementsByComputedRole", WI.DefaultAudits.getElementsByComputedRole.toString(), {description: WI.UIString("This is an example test that uses %s to find elements with a computed role of \u201Clink\u201D.").format(WI.unlocalizedString("WebInspectorAudit.Accessibility.getElementsByComputedRole")), supports: 1}), new WI.AuditTestCase("getActiveDescendant", WI.DefaultAudits.getActiveDescendant.toString(), {description: WI.UIString("This is an example test that uses %s to find any element that meets criteria for active descendant (\u201C%s\u201D) of the element, if it exists.").format(WI.unlocalizedString("WebInspectorAudit.Accessibility.getActiveDescendant"), WI.unlocalizedString("aria-activedescendant")), supports: 1}), new WI.AuditTestCase("getChildNodes", WI.DefaultAudits.getChildNodes.toString(), {description: WI.UIString("This is an example test that uses %s to find child nodes of the element in the accessibility tree.").format(WI.unlocalizedString("WebInspectorAudit.Accessibility.getChildNodes")), supports: 1}), new WI.AuditTestCase("getComputedProperties", WI.DefaultAudits.getComputedProperties.toString(), {description: WI.UIString("This is an example test that uses %s to find a variety of accessibility information about the element.").format(WI.unlocalizedString("WebInspectorAudit.Accessibility.getComputedProperties")), supports: 3}), new WI.AuditTestCase("getControlledNodes", WI.DefaultAudits.getControlledNodes.toString(), {description: WI.UIString("This is an example test that uses %s to find all nodes controlled (\u201C%s\u201D) by the element, if any exist.").format(WI.unlocalizedString("WebInspectorAudit.Accessibility.getControlledNodes"), WI.unlocalizedString("aria-controls")), supports: 1}), new WI.AuditTestCase("getFlowedNodes", WI.DefaultAudits.getFlowedNodes.toString(), {description: WI.UIString("This is an example test that uses %s to find all nodes flowed to (\u201C%s\u201D) from the element, if any exist.").format(WI.unlocalizedString("WebInspectorAudit.Accessibility.getFlowedNodes"), WI.unlocalizedString("aria-flowto")), supports: 1}), new WI.AuditTestCase("getMouseEventNode", WI.DefaultAudits.getMouseEventNode.toString(), {description: WI.UIString("This is an example test that uses %s to find the node that would handle mouse events for the element, if applicable.").format(WI.unlocalizedString("WebInspectorAudit.Accessibility.getMouseEventNode")), supports: 1}), new WI.AuditTestCase("getOwnedNodes", WI.DefaultAudits.getOwnedNodes.toString(), {description: WI.UIString("This is an example test that uses %s to find all nodes owned (\u201C%s\u201D) by the element, if any exist.").format(WI.unlocalizedString("WebInspectorAudit.Accessibility.getOwnedNodes"), WI.unlocalizedString("aria-owns")), supports: 1}), new WI.AuditTestCase("getParentNode", WI.DefaultAudits.getParentNode.toString(), {description: WI.UIString("This is an example test that uses %s to find the parent node of the element in the accessibility tree.").format(WI.unlocalizedString("WebInspectorAudit.Accessibility.getParentNode")), supports: 1}), new WI.AuditTestCase("getSelectedChildNodes", WI.DefaultAudits.getSelectedChildNodes.toString(), {description: WI.UIString("This is an example test that uses %s to find all child nodes that are selected (\u201C%s\u201D) of the element in the accessibility tree.").format(WI.unlocalizedString("WebInspectorAudit.Accessibility.getSelectedChildNodes"), WI.unlocalizedString("aria-selected")), supports: 1}), ], {description: WI.UIString("These are example tests that demonstrate how to use %s to get information about the accessibility tree.").format(WI.unlocalizedString("WebInspectorAudit.Accessibility")), supports: 1}), new WI.AuditTestGroup(WI.UIString("DOM"), [ new WI.AuditTestCase("hasEventListeners", WI.DefaultAudits.hasEventListeners.toString(), {description: WI.UIString("This is an example test that uses %s to find data indicating whether the element has any event listeners.").format(WI.unlocalizedString("WebInspectorAudit.Accessibility.hasEventListeners")), supports: 3}), new WI.AuditTestCase("hasEventListeners-click", WI.DefaultAudits.hasEventListenersClick.toString(), {description: WI.UIString("This is an example test that uses %s to find data indicating whether the element has any click event listeners.").format(WI.unlocalizedString("WebInspectorAudit.Accessibility.hasEventListenersClick")), supports: 3}), ], {description: WI.UIString("These are example tests that demonstrate how to use %s to get information about DOM nodes.").format(WI.unlocalizedString("WebInspectorAudit.DOM")), supports: 1}), new WI.AuditTestGroup(WI.UIString("Resources"), [ new WI.AuditTestCase("getResources", WI.DefaultAudits.getResources.toString(), {description: WI.UIString("This is an example test that uses %s to find basic information about each resource.").format(WI.unlocalizedString("WebInspectorAudit.Accessibility.getResources")), supports: 3}), new WI.AuditTestCase("getResourceContent", WI.DefaultAudits.getResourceContent.toString(), {description: WI.UIString("This is an example test that uses %s to find the contents of the main resource.").format(WI.unlocalizedString("WebInspectorAudit.Accessibility.getResourceContent")), supports: 3}), ], {description: WI.UIString("These are example tests that demonstrate how to use %s to get information about loaded resources.").format(WI.unlocalizedString("WebInspectorAudit.Resources")), supports: 2}), ], {description: WI.UIString("These are example tests that demonstrate how to use %s to access information not normally available to JavaScript.").format(WI.unlocalizedString("WebInspectorAudit")), supports: 1}), new WI.AuditTestCase("unsupported", WI.DefaultAudits.unsupported.toString(), {description: WI.UIString("This is an example of a test that will not run because it is unsupported."), supports: Infinity}), ], {description: WI.UIString("These are example tests that demonstrate the functionality and structure of audits.")}), new WI.AuditTestGroup(WI.UIString("Accessibility"), [ new WI.AuditTestCase("testMenuRoleForRequiredChildren", WI.DefaultAudits.testMenuRoleForRequiredChildren.toString(), {description: WI.UIString("Ensure that elements of role \u201C%s\u201D and \u201C%s\u201D have required owned elements in accordance with WAI-ARIA.").format(WI.unlocalizedString("menu"), WI.unlocalizedString("menubar")), supports: 1}), new WI.AuditTestCase("testGridRoleForRequiredChildren", WI.DefaultAudits.testGridRoleForRequiredChildren.toString(), {description: WI.UIString("Ensure that elements of role \u201C%s\u201D have required owned elements in accordance with WAI-ARIA.").format(WI.unlocalizedString("grid")), supports: 1}), new WI.AuditTestCase("testForAriaLabelledBySpelling", WI.DefaultAudits.testForAriaLabelledBySpelling.toString(), {description: WI.UIString("Ensure that \u201C%s\u201D is spelled correctly.").format(WI.unlocalizedString("aria-labelledby")), supports: 1}), new WI.AuditTestCase("testForMultipleBanners", WI.DefaultAudits.testForMultipleBanners.toString(), {description: WI.UIString("Ensure that only one banner is used on the page."), supports: 1}), new WI.AuditTestCase("testForLinkLabels", WI.DefaultAudits.testForLinkLabels.toString(), {description: WI.UIString("Ensure that links have accessible labels for assistive technology."), supports: 1}), new WI.AuditTestCase("testRowGroupRoleForRequiredChildren", WI.DefaultAudits.testRowGroupRoleForRequiredChildren.toString(), {description: WI.UIString("Ensure that elements of role \u201C%s\u201D have required owned elements in accordance with WAI-ARIA.").format(WI.unlocalizedString("rowgroup")), supports: 1}), new WI.AuditTestCase("testTableRoleForRequiredChildren", WI.DefaultAudits.testTableRoleForRequiredChildren.toString(), {description: WI.UIString("Ensure that elements of role \u201C%s\u201D have required owned elements in accordance with WAI-ARIA.").format(WI.unlocalizedString("table")), supports: 1}), new WI.AuditTestCase("testForMultipleLiveRegions", WI.DefaultAudits.testForMultipleLiveRegions.toString(), {description: WI.UIString("Ensure that only one live region is used on the page."), supports: 1}), new WI.AuditTestCase("testListBoxRoleForRequiredChildren", WI.DefaultAudits.testListBoxRoleForRequiredChildren.toString(), {description: WI.UIString("Ensure that elements of role \u201C%s\u201D have required owned elements in accordance with WAI-ARIA.").format(WI.unlocalizedString("listbox")), supports: 1}), new WI.AuditTestCase("testImageLabels", WI.DefaultAudits.testImageLabels.toString(), {description: WI.UIString("Ensure that elements of role \u201C%s\u201D have accessible labels for assistive technology.").format(WI.unlocalizedString("img")), supports: 1}), new WI.AuditTestCase("testForAriaHiddenFalse", WI.DefaultAudits.testForAriaHiddenFalse.toString(), {description: WI.UIString("Ensure aria-hidden=\u0022%s\u0022 is not used.").format(WI.unlocalizedString("false")), supports: 1}), new WI.AuditTestCase("testTreeRoleForRequiredChildren", WI.DefaultAudits.testTreeRoleForRequiredChildren.toString(), {description: WI.UIString("Ensure that elements of role \u201C%s\u201D have required owned elements in accordance with WAI-ARIA.").format(WI.unlocalizedString("tree")), supports: 1}), new WI.AuditTestCase("testRadioGroupRoleForRequiredChildren", WI.DefaultAudits.testRadioGroupRoleForRequiredChildren.toString(), {description: WI.UIString("Ensure that elements of role \u201C%s\u201D have required owned elements in accordance with WAI-ARIA.").format(WI.unlocalizedString("radiogroup")), supports: 1}), new WI.AuditTestCase("testFeedRoleForRequiredChildren", WI.DefaultAudits.testFeedRoleForRequiredChildren.toString(), {description: WI.UIString("Ensure that elements of role \u201C%s\u201D have required owned elements in accordance with WAI-ARIA.").format(WI.unlocalizedString("feed")), supports: 1}), new WI.AuditTestCase("testTabListRoleForRequiredChildren", WI.DefaultAudits.testTabListRoleForRequiredChildren.toString(), {description: WI.UIString("Ensure that elements of role \u201C%s\u201D have required owned elements in accordance with WAI-ARIA.").format(WI.unlocalizedString("tablist")), supports: 1}), new WI.AuditTestCase("testButtonLabels", WI.DefaultAudits.testButtonLabels.toString(), {description: WI.UIString("Ensure that buttons have accessible labels for assistive technology."), supports: 1}), new WI.AuditTestCase("testRowRoleForRequiredChildren", WI.DefaultAudits.testRowRoleForRequiredChildren.toString(), {description: WI.UIString("Ensure that elements of role \u201C%s\u201D have required owned elements in accordance with WAI-ARIA.").format(WI.unlocalizedString("row")), supports: 1}), new WI.AuditTestCase("testListRoleForRequiredChildren", WI.DefaultAudits.testListRoleForRequiredChildren.toString(), {description: WI.UIString("Ensure that elements of role \u201C%s\u201D have required owned elements in accordance with WAI-ARIA.").format(WI.unlocalizedString("list")), supports: 1}), new WI.AuditTestCase("testComboBoxRoleForRequiredChildren", WI.DefaultAudits.testComboBoxRoleForRequiredChildren.toString(), {description: WI.UIString("Ensure that elements of role \u201C%s\u201D have required owned elements in accordance with WAI-ARIA.").format(WI.unlocalizedString("combobox")), supports: 1}), new WI.AuditTestCase("testForMultipleMainContentSections", WI.DefaultAudits.testForMultipleMainContentSections.toString(), {description: WI.UIString("Ensure that only one main content section is used on the page."), supports: 1}), new WI.AuditTestCase("testDialogsForLabels", WI.DefaultAudits.testDialogsForLabels.toString(), {description: WI.UIString("Ensure that dialogs have accessible labels for assistive technology."), supports: 1}), new WI.AuditTestCase("testForInvalidAriaHiddenValue", WI.DefaultAudits.testForInvalidAriaHiddenValue.toString(), {description: WI.UIString("Ensure that values for \u201C%s\u201D are valid.").format(WI.unlocalizedString("aria-hidden")), supports: 1}) ], {description: WI.UIString("Diagnoses common accessibility problems affecting screen readers and other assistive technology.")}), ]; let checkDisabledDefaultTest = (test) => { test.markAsDefault(); if (this._disabledDefaultTestsSetting.value.includes(test.name)) test.disabled = true; if (test instanceof WI.AuditTestGroup) { for (let child of test.tests) checkDisabledDefaultTest(child); } }; for (let test of defaultTests) { checkDisabledDefaultTest(test); this.addTest(test); } } }; WI.AuditManager.RunningState = { Disabled: "disabled", Inactive: "inactive", Active: "active", Stopping: "stopping", }; WI.AuditManager.Event = { EditingChanged: "audit-manager-editing-changed", RunningStateChanged: "audit-manager-running-state-changed", TestAdded: "audit-manager-test-added", TestCompleted: "audit-manager-test-completed", TestRemoved: "audit-manager-test-removed", TestScheduled: "audit-manager-test-scheduled", }; /* Controllers/ApplicationCacheManager.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. */ // FIXME: ApplicationCacheManager lacks advanced multi-target support. (ApplciationCache objects per-target) WI.ApplicationCacheManager = class ApplicationCacheManager extends WI.Object { constructor() { super(); this._enabled = false; this._reset(); } // Agent get domains() { return ["ApplicationCache"]; } activateExtraDomain(domain) { // COMPATIBILITY (iOS 14.0): Inspector.activateExtraDomains was removed in favor of a declared debuggable type console.assert(domain === "ApplicationCache"); for (let target of WI.targets) this.initializeTarget(target); } // Target initializeTarget(target) { if (!this._enabled) return; if (target.hasDomain("ApplicationCache")) { target.ApplicationCacheAgent.enable(); target.ApplicationCacheAgent.getFramesWithManifests(this._framesWithManifestsLoaded.bind(this)); } } // Public get online() { return this._online; } get applicationCacheObjects() { var applicationCacheObjects = []; for (var id in this._applicationCacheObjects) applicationCacheObjects.push(this._applicationCacheObjects[id]); return applicationCacheObjects; } enable() { console.assert(!this._enabled); this._enabled = true; this._reset(); for (let target of WI.targets) this.initializeTarget(target); WI.Frame.addEventListener(WI.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this); WI.Frame.addEventListener(WI.Frame.Event.ChildFrameWasRemoved, this._childFrameWasRemoved, this); } disable() { console.assert(this._enabled); this._enabled = false; for (let target of WI.targets) { // COMPATIBILITY (iOS 13): ApplicationCache.disable did not exist yet. if (target.hasCommand("ApplicationCache.disable")) target.ApplicationCacheAgent.disable(); } WI.Frame.removeEventListener(WI.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this); WI.Frame.removeEventListener(WI.Frame.Event.ChildFrameWasRemoved, this._childFrameWasRemoved, this); this._reset(); } requestApplicationCache(frame, callback) { console.assert(this._enabled); function callbackWrapper(error, applicationCache) { if (error) { callback(null); return; } callback(applicationCache); } let target = WI.assumingMainTarget(); target.ApplicationCacheAgent.getApplicationCacheForFrame(frame.id, callbackWrapper); } // ApplicationCacheObserver networkStateUpdated(isNowOnline) { console.assert(this._enabled); this._online = isNowOnline; this.dispatchEventToListeners(WI.ApplicationCacheManager.Event.NetworkStateUpdated, {online: this._online}); } applicationCacheStatusUpdated(frameId, manifestURL, status) { console.assert(this._enabled); let frame = WI.networkManager.frameForIdentifier(frameId); if (!frame) return; this._frameManifestUpdated(frame, manifestURL, status); } // Private _reset() { this._online = true; this._applicationCacheObjects = {}; this.dispatchEventToListeners(WI.ApplicationCacheManager.Event.Cleared); } _mainResourceDidChange(event) { console.assert(event.target instanceof WI.Frame); if (event.target.isMainFrame()) { this._reset(); return; } let target = WI.assumingMainTarget(); if (target.hasDomain("ApplicationCache")) target.ApplicationCacheAgent.getManifestForFrame(event.target.id, this._manifestForFrameLoaded.bind(this, event.target.id)); } _childFrameWasRemoved(event) { this._frameManifestRemoved(event.data.childFrame); } _manifestForFrameLoaded(frameId, error, manifestURL) { if (!this._enabled) return; var frame = WI.networkManager.frameForIdentifier(frameId); if (!frame) return; // A frame can go away between `ApplicationCache.getManifestForFrame` being called and the // response being received. if (error) { WI.reportInternalError(error); return; } if (!manifestURL) this._frameManifestRemoved(frame); } _framesWithManifestsLoaded(error, framesWithManifests) { if (error) { WI.reportInternalError(error); return; } if (!this._enabled) return; for (var i = 0; i < framesWithManifests.length; ++i) { var frame = WI.networkManager.frameForIdentifier(framesWithManifests[i].frameId); if (!frame) continue; this._frameManifestUpdated(frame, framesWithManifests[i].manifestURL, framesWithManifests[i].status); } } _frameManifestUpdated(frame, manifestURL, status) { if (status === WI.ApplicationCacheManager.Status.Uncached) { this._frameManifestRemoved(frame); return; } if (!manifestURL) return; var manifestFrame = this._applicationCacheObjects[frame.id]; if (manifestFrame && manifestURL !== manifestFrame.manifest.manifestURL) this._frameManifestRemoved(frame); var oldStatus = manifestFrame ? manifestFrame.status : -1; var statusChanged = manifestFrame && status !== oldStatus; if (manifestFrame) manifestFrame.status = status; if (!this._applicationCacheObjects[frame.id]) { var cacheManifest = new WI.ApplicationCacheManifest(manifestURL); this._applicationCacheObjects[frame.id] = new WI.ApplicationCacheFrame(frame, cacheManifest, status); this.dispatchEventToListeners(WI.ApplicationCacheManager.Event.FrameManifestAdded, {frameManifest: this._applicationCacheObjects[frame.id]}); } if (statusChanged) this.dispatchEventToListeners(WI.ApplicationCacheManager.Event.FrameManifestStatusChanged, {frameManifest: this._applicationCacheObjects[frame.id]}); } _frameManifestRemoved(frame) { if (!this._applicationCacheObjects[frame.id]) return; delete this._applicationCacheObjects[frame.id]; this.dispatchEventToListeners(WI.ApplicationCacheManager.Event.FrameManifestRemoved, {frame}); } }; WI.ApplicationCacheManager.Event = { Cleared: "application-cache-manager-cleared", FrameManifestAdded: "application-cache-manager-frame-manifest-added", FrameManifestRemoved: "application-cache-manager-frame-manifest-removed", FrameManifestStatusChanged: "application-cache-manager-frame-manifest-status-changed", NetworkStateUpdated: "application-cache-manager-network-state-updated" }; WI.ApplicationCacheManager.Status = { Uncached: 0, Idle: 1, Checking: 2, Downloading: 3, UpdateReady: 4, Obsolete: 5 }; /* Controllers/BreakpointLogMessageLexer.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.BreakpointLogMessageLexer = class BreakpointLogMessageLexer extends WI.Object { constructor() { super(); this._stateFunctions = { [WI.BreakpointLogMessageLexer.State.Expression]: this._expression, [WI.BreakpointLogMessageLexer.State.PlainText]: this._plainText, [WI.BreakpointLogMessageLexer.State.PossiblePlaceholder]: this._possiblePlaceholder, [WI.BreakpointLogMessageLexer.State.RegExpOrStringLiteral]: this._regExpOrStringLiteral, }; this.reset(); } // Public tokenize(input) { this.reset(); this._input = input; while (this._index < this._input.length) { let stateFunction = this._stateFunctions[this._states.lastValue]; console.assert(stateFunction); if (!stateFunction) { this.reset(); return null; } stateFunction.call(this); } // Needed for trailing plain text. this._finishPlainText(); return this._tokens; } reset() { this._input = ""; this._buffer = ""; this._index = 0; this._states = [WI.BreakpointLogMessageLexer.State.PlainText]; this._literalStartCharacter = ""; this._curlyBraceDepth = 0; this._tokens = []; } // Private _finishPlainText() { this._appendToken(WI.BreakpointLogMessageLexer.TokenType.PlainText); } _finishExpression() { this._appendToken(WI.BreakpointLogMessageLexer.TokenType.Expression); } _appendToken(type) { if (!this._buffer) return; this._tokens.push({type, data: this._buffer}); this._buffer = ""; } _consume() { console.assert(this._index < this._input.length); let character = this._peek(); this._index++; return character; } _peek() { return this._input[this._index] || null; } // States _expression() { let character = this._consume(); if (character === "}") { if (this._curlyBraceDepth === 0) { this._finishExpression(); console.assert(this._states.lastValue === WI.BreakpointLogMessageLexer.State.Expression); this._states.pop(); return; } this._curlyBraceDepth--; } this._buffer += character; if (character === "/" || character === "\"" || character === "'") { this._literalStartCharacter = character; this._states.push(WI.BreakpointLogMessageLexer.State.RegExpOrStringLiteral); } else if (character === "{") this._curlyBraceDepth++; } _plainText() { let character = this._peek(); if (character === "$") this._states.push(WI.BreakpointLogMessageLexer.State.PossiblePlaceholder); else { this._buffer += character; this._consume(); } } _possiblePlaceholder() { let character = this._consume(); console.assert(character === "$"); let nextCharacter = this._peek(); console.assert(this._states.lastValue === WI.BreakpointLogMessageLexer.State.PossiblePlaceholder); this._states.pop(); if (nextCharacter === "{") { this._finishPlainText(); this._consume(); this._states.push(WI.BreakpointLogMessageLexer.State.Expression); } else this._buffer += character; } _regExpOrStringLiteral() { let character = this._consume(); this._buffer += character; if (character === "\\") { if (this._peek() !== null) this._buffer += this._consume(); return; } if (character === this._literalStartCharacter) { console.assert(this._states.lastValue === WI.BreakpointLogMessageLexer.State.RegExpOrStringLiteral); this._states.pop(); } } }; WI.BreakpointLogMessageLexer.State = { Expression: Symbol("expression"), PlainText: Symbol("plain-text"), PossiblePlaceholder: Symbol("possible-placeholder"), RegExpOrStringLiteral: Symbol("regexp-or-string-literal"), }; WI.BreakpointLogMessageLexer.TokenType = { PlainText: "token-type-plain-text", Expression: "token-type-expression", }; /* Controllers/BrowserManager.js */ /* * Copyright (C) 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. */ WI.BrowserManager = class BrowserManager { constructor() { this._enabled = false; this._extensionNameIdentifierMap = new Map; } // Target initializeTarget(target) { if (!this._enabled) return; // COMPATIBILITY (iOS 13.4): Browser did not exist yet. if (target.hasDomain("Browser")) target.BrowserAgent.enable(); } // Public enable() { console.assert(!this._enabled); this._enabled = true; for (let target of WI.targetManager.allTargets) this.initializeTarget(target); } disable() { console.assert(this._enabled); for (let target of WI.targetManager.allTargets) { // COMPATIBILITY (iOS 13.4): Browser did not exist yet. if (target.hasDomain("Browser")) target.BrowserAgent.disable(); } this._extensionNameIdentifierMap.clear(); this._enabled = false; } isExtensionScheme(scheme) { return scheme && scheme.endsWith("-extension"); } extensionNameForId(extensionId) { return this._extensionNameIdentifierMap.get(extensionId) || null; } extensionNameForURL(url) { console.assert(this.isExtensionScheme(parseURL(url).scheme)); let match = url.match(/^[a-z\-]*extension:\/\/([0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12})\//); if (!match) return null; return this.extensionNameForId(match[1]); } extensionNameForExecutionContext(context) { console.assert(context instanceof WI.ExecutionContext); console.assert(context.type === WI.ExecutionContext.Type.User); let match = context.name.match(/^[A-Za-z]*ExtensionWorld-([0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12})$/); if (!match) return null; return this.extensionNameForId(match[1]); } // BrowserObserver extensionsEnabled(extensions) { for (let {extensionId, name} of extensions) { console.assert(!this._extensionNameIdentifierMap.has(extensionId), `Extension already exists with id '${extensionId}'.`); this._extensionNameIdentifierMap.set(extensionId, name); } } extensionsDisabled(extensionIds) { for (let extensionId of extensionIds) { let name = this._extensionNameIdentifierMap.take(extensionId); console.assert(name, `Missing extension with id '${extensionId}'.`); } } }; /* Controllers/CSSManager.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. */ // FIXME: CSSManager lacks advanced multi-target support. (Stylesheets per-target) WI.CSSManager = class CSSManager extends WI.Object { constructor() { super(); WI.Frame.addEventListener(WI.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this); WI.Frame.addEventListener(WI.Frame.Event.ResourceWasAdded, this._resourceAdded, this); WI.Resource.addEventListener(WI.SourceCode.Event.ContentDidChange, this._resourceContentDidChange, this); WI.Resource.addEventListener(WI.Resource.Event.TypeDidChange, this._resourceTypeDidChange, this); WI.DOMNode.addEventListener(WI.DOMNode.Event.AttributeModified, this._nodeAttributesDidChange, this); WI.DOMNode.addEventListener(WI.DOMNode.Event.AttributeRemoved, this._nodeAttributesDidChange, this); WI.DOMNode.addEventListener(WI.DOMNode.Event.EnabledPseudoClassesChanged, this._nodePseudoClassesDidChange, this); this._colorFormatSetting = new WI.Setting("default-color-format", WI.Color.Format.Original); this._styleSheetIdentifierMap = new Map; this._styleSheetFrameURLMap = new Map; this._nodeStylesMap = {}; this._modifiedStyles = new Map; this._defaultUserPreferences = new Map; this._overriddenUserPreferences = new Map; this._propertyNameCompletions = null; } // Target initializeTarget(target) { if (target.hasDomain("CSS")) target.CSSAgent.enable(); } initializeCSSPropertyNameCompletions(target) { console.assert(target.hasDomain("CSS")); if (this._propertyNameCompletions) return; target.CSSAgent.getSupportedCSSProperties((error, cssProperties) => { if (error) return; this._propertyNameCompletions = new WI.CSSPropertyNameCompletions(cssProperties); WI.CSSKeywordCompletions.addCustomCompletions(cssProperties); // CodeMirror is not included by tests so we shouldn't assume it always exists. // If it isn't available we skip MIME type associations. if (!window.CodeMirror) return; let propertyNamesForCodeMirror = {}; let valueKeywordsForCodeMirror = {"inherit": true, "initial": true, "unset": true, "revert": true, "revert-layer": true, "var": true, "env": true}; let colorKeywordsForCodeMirror = {}; function nameForCodeMirror(name) { // CodeMirror parses the vendor prefix separate from the property or keyword name, // so we need to strip vendor prefixes from our names. Also strip function parenthesis. return name.replace(/^-[^-]+-/, "").replace(/\(\)$/, "").toLowerCase(); } for (let property of cssProperties) { // Properties can also be value keywords, like when used in a transition. // So we add them to both lists. let codeMirrorPropertyName = nameForCodeMirror(property.name); propertyNamesForCodeMirror[codeMirrorPropertyName] = true; valueKeywordsForCodeMirror[codeMirrorPropertyName] = true; } for (let propertyName in WI.CSSKeywordCompletions._propertyKeywordMap) { let keywords = WI.CSSKeywordCompletions._propertyKeywordMap[propertyName]; for (let keyword of keywords) { // Skip numbers, like the ones defined for font-weight. if (keyword === WI.CSSKeywordCompletions.AllPropertyNamesPlaceholder || !isNaN(Number(keyword))) continue; valueKeywordsForCodeMirror[nameForCodeMirror(keyword)] = true; } } for (let color of WI.CSSKeywordCompletions._colors) colorKeywordsForCodeMirror[nameForCodeMirror(color)] = true; // TODO: Remove these keywords once they are built-in codemirror or once we get values from WebKit itself. valueKeywordsForCodeMirror["conic-gradient"] = true; valueKeywordsForCodeMirror["repeating-conic-gradient"] = true; function updateCodeMirrorCSSMode(mimeType) { let modeSpec = CodeMirror.resolveMode(mimeType); console.assert(modeSpec.propertyKeywords); console.assert(modeSpec.valueKeywords); console.assert(modeSpec.colorKeywords); modeSpec.propertyKeywords = propertyNamesForCodeMirror; modeSpec.valueKeywords = valueKeywordsForCodeMirror; modeSpec.colorKeywords = colorKeywordsForCodeMirror; CodeMirror.defineMIME(mimeType, modeSpec); } updateCodeMirrorCSSMode("text/css"); updateCodeMirrorCSSMode("text/x-scss"); }); if (target.hasCommand("CSS.getSupportedSystemFontFamilyNames")) { target.CSSAgent.getSupportedSystemFontFamilyNames((error, fontFamilyNames) =>{ if (error) return; WI.CSSKeywordCompletions.addPropertyCompletionValues("font-family", fontFamilyNames); WI.CSSKeywordCompletions.addPropertyCompletionValues("font", fontFamilyNames); }); } } // Static static supportsInspectorStyleSheet() { return InspectorBackend.hasCommand("CSS.createStyleSheet"); } static protocolStyleSheetOriginToEnum(origin) { switch (origin) { case InspectorBackend.Enum.CSS.StyleSheetOrigin.User: return WI.CSSStyleSheet.Type.User; case InspectorBackend.Enum.CSS.StyleSheetOrigin.UserAgent: return WI.CSSStyleSheet.Type.UserAgent; case InspectorBackend.Enum.CSS.StyleSheetOrigin.Inspector: return WI.CSSStyleSheet.Type.Inspector; } // COMPATIBILITY (iOS 14): CSS.StyleSheetOrigin.Regular was replaced with CSS.StyleSheetOrigin.Author. console.assert(!InspectorBackend.Enum.CSS.StyleSheetOrigin.Author || origin === InspectorBackend.Enum.CSS.StyleSheetOrigin.Author); console.assert(!InspectorBackend.Enum.CSS.StyleSheetOrigin.Regular || origin === InspectorBackend.Enum.CSS.StyleSheetOrigin.Regular); return WI.CSSStyleSheet.Type.Author; } static protocolGroupingTypeToEnum(type) { // COMPATIBILITY (iOS 13): CSS.Grouping did not exist yet. if (!InspectorBackend.Enum.CSS.Grouping) { switch (type) { case "mediaRule": return WI.CSSGrouping.Type.MediaRule; case "importRule": return WI.CSSGrouping.Type.MediaImportRule; case "linkedSheet": return WI.CSSGrouping.Type.MediaLinkNode; case "inlineSheet": return WI.CSSGrouping.Type.MediaStyleNode; } } return type; } static displayNameForPseudoId(pseudoId) { // Compatibility (iOS 12.2): CSS.PseudoId did not exist. if (!InspectorBackend.Enum.CSS.PseudoId) { switch (pseudoId) { case 1: // PseudoId.FirstLine return WI.unlocalizedString("::first-line"); case 2: // PseudoId.FirstLetter return WI.unlocalizedString("::first-letter"); case 3: // PseudoId.Marker return WI.unlocalizedString("::marker"); case 4: // PseudoId.Before return WI.unlocalizedString("::before"); case 5: // PseudoId.After return WI.unlocalizedString("::after"); case 6: // PseudoId.Selection return WI.unlocalizedString("::-webkit-selection"); case 7: // PseudoId.Scrollbar return WI.unlocalizedString("::-webkit-scrollbar"); case 8: // PseudoId.ScrollbarThumb return WI.unlocalizedString("::-webkit-scrollbar-thumb"); case 9: // PseudoId.ScrollbarButton return WI.unlocalizedString("::-webkit-scrollbar-button"); case 10: // PseudoId.ScrollbarTrack return WI.unlocalizedString("::-webkit-scrollbar-track"); case 11: // PseudoId.ScrollbarTrackPiece return WI.unlocalizedString("::-webkit-scrollbar-track-piece"); case 12: // PseudoId.ScrollbarCorner return WI.unlocalizedString("::-webkit-scrollbar-corner"); case 13: // PseudoId.Resizer return WI.unlocalizedString("::-webkit-resizer"); default: console.error("Unknown pseudo id", pseudoId); return ""; } } switch (pseudoId) { case CSSManager.PseudoSelectorNames.FirstLine: return WI.unlocalizedString("::first-line"); case CSSManager.PseudoSelectorNames.FirstLetter: return WI.unlocalizedString("::first-letter"); case CSSManager.PseudoSelectorNames.Highlight: return WI.unlocalizedString("::highlight"); case CSSManager.PseudoSelectorNames.GrammarError: return WI.unlocalizedString("::grammar-error"); case CSSManager.PseudoSelectorNames.Marker: return WI.unlocalizedString("::marker"); case CSSManager.PseudoSelectorNames.Before: return WI.unlocalizedString("::before"); case CSSManager.PseudoSelectorNames.After: return WI.unlocalizedString("::after"); case CSSManager.PseudoSelectorNames.Selection: return WI.unlocalizedString("::selection"); case CSSManager.PseudoSelectorNames.Backdrop: return WI.unlocalizedString("::backdrop"); case CSSManager.PseudoSelectorNames.SpellingError: return WI.unlocalizedString("::spelling-error"); case CSSManager.PseudoSelectorNames.ViewTransition: return WI.unlocalizedString("::view-transition"); case CSSManager.PseudoSelectorNames.ViewTransitionGroup: return WI.unlocalizedString("::view-transition-group"); case CSSManager.PseudoSelectorNames.ViewTransitionImagePair: return WI.unlocalizedString("::view-transition-image-pair"); case CSSManager.PseudoSelectorNames.ViewTransitionNew: return WI.unlocalizedString("::view-transition-new"); case CSSManager.PseudoSelectorNames.ViewTransitionOld: return WI.unlocalizedString("::view-transition-old"); // COMPATIBILITY (iOS 17.0): PseudoId unprefixed aliases for prefixed protocol names. case CSSManager.PseudoSelectorNames.WebKitResizer: case "resizer": return WI.unlocalizedString("::-webkit-resizer"); case CSSManager.PseudoSelectorNames.WebKitScrollbar: case "scrollbar": return WI.unlocalizedString("::-webkit-scrollbar"); case CSSManager.PseudoSelectorNames.WebKitScrollbarThumb: case "scrollbar-thumb": return WI.unlocalizedString("::-webkit-scrollbar-thumb"); case CSSManager.PseudoSelectorNames.WebKitScrollbarButton: case "scrollbar-button": return WI.unlocalizedString("::-webkit-scrollbar-button"); case CSSManager.PseudoSelectorNames.WebKitScrollbarTrack: case "scrollbar-track": return WI.unlocalizedString("::-webkit-scrollbar-track"); case CSSManager.PseudoSelectorNames.WebKitScrollbarTrackPiece: case "scrollbar-track-piece": return WI.unlocalizedString("::-webkit-scrollbar-track-piece"); case CSSManager.PseudoSelectorNames.WebKitScrollbarCorner: case "scrollbar-corner": return WI.unlocalizedString("::-webkit-scrollbar-corner"); default: console.error("Unknown pseudo id", pseudoId); return ""; } } static displayNameForForceablePseudoClass(pseudoClass) { switch (pseudoClass) { case WI.CSSManager.ForceablePseudoClass.Active: return WI.unlocalizedString(":active"); case WI.CSSManager.ForceablePseudoClass.Focus: return WI.unlocalizedString(":focus"); case WI.CSSManager.ForceablePseudoClass.FocusVisible: return WI.unlocalizedString(":focus-visible"); case WI.CSSManager.ForceablePseudoClass.FocusWithin: return WI.unlocalizedString(":focus-within"); case WI.CSSManager.ForceablePseudoClass.Hover: return WI.unlocalizedString(":hover"); case WI.CSSManager.ForceablePseudoClass.Target: return WI.unlocalizedString(":target"); case WI.CSSManager.ForceablePseudoClass.Visited: return WI.unlocalizedString(":visited"); } console.assert(false, "Unknown pseudo class", pseudoClass); return ""; } // Public get propertyNameCompletions() { return this._propertyNameCompletions; } get overriddenUserPreferences() { return this._overriddenUserPreferences; } get defaultUserPreferences() { return this._defaultUserPreferences; } get overriddenUserPreferences() { return this._overriddenUserPreferences; } get preferredColorFormat() { return this._colorFormatSetting.value; } get styleSheets() { return Array.from(this._styleSheetIdentifierMap.values()); } get supportsOverrideUserPreference() { return InspectorBackend.hasCommand("Page.overrideUserPreference") && this._defaultUserPreferences.size; } get supportsOverrideColorScheme() { // A backend for a platform that does not support color schemes will not dispatch an initial event (Page.defaultAppearanceDidChange or Page.defaultUserPreferencesDidChange) // with the default value for the color scheme preference which gets stored on the frontend. // COMPATIBILITY (macOS 13.0, iOS 16.0): `PrefersColorScheme` value for `Page.UserPreferenceName` did not exist yet. return this._defaultUserPreferences.has(InspectorBackend.Enum.Page.UserPreferenceName?.PrefersColorScheme) || this._defaultUserPreferences.has(WI.CSSManager.ForcedAppearancePreference); } // COMPATIBILITY (macOS 13, iOS 16.0): `Page.setForcedAppearance()` was removed in favor of `Page.overrideUserPreference()` setForcedAppearance(name) { let commandArguments = {}; switch (name) { case WI.CSSManager.Appearance.Light: commandArguments.appearance = InspectorBackend.Enum.Page.Appearance.Light; break; case WI.CSSManager.Appearance.Dark: commandArguments.appearance = InspectorBackend.Enum.Page.Appearance.Dark; break; case null: // COMPATIBILITY (iOS 14): the `appearance`` parameter of `Page.setForcedAppearance` was not optional. // Since support can't be tested directly, check for the `options`` parameter of `DOMDebugger.setDOMBreakpoint` (iOS 14.0+). // FIXME: Use explicit version checking once https://webkit.org/b/148680 is fixed. if (!InspectorBackend.hasCommand("DOMDebugger.setDOMBreakpoint", "options")) commandArguments.appearance = ""; break; default: console.assert(false, "Unknown appearance", name); return; } let target = WI.assumingMainTarget(); return target.PageAgent.setForcedAppearance.invoke(commandArguments); } set layoutContextTypeChangedMode(layoutContextTypeChangedMode) { for (let target of WI.targets) { // COMPATIBILITY (iOS 14.5): CSS.setLayoutContextTypeChangedMode did not exist. if (target.hasCommand("CSS.setLayoutContextTypeChangedMode")) target.CSSAgent.setLayoutContextTypeChangedMode(layoutContextTypeChangedMode); } } canForcePseudoClass(pseudoClass) { if (!InspectorBackend.hasCommand("CSS.forcePseudoState")) return false; if (!pseudoClass) return true; switch (pseudoClass) { case WI.CSSManager.ForceablePseudoClass.Active: case WI.CSSManager.ForceablePseudoClass.Focus: case WI.CSSManager.ForceablePseudoClass.Hover: case WI.CSSManager.ForceablePseudoClass.Visited: return true; case WI.CSSManager.ForceablePseudoClass.FocusVisible: case WI.CSSManager.ForceablePseudoClass.FocusWithin: case WI.CSSManager.ForceablePseudoClass.Target: // COMPATIBILITY (macOS 12.3, iOS 15.4): CSS.ForceablePseudoClass did not exist yet. return !!InspectorBackend.Enum.CSS.ForceablePseudoClass; } console.assert(false, "Unknown pseudo class", pseudoClass); return false; } overrideUserPreference(preference, value) { let promises = []; for (let target of WI.targets) { // COMPATIBILITY (macOS 13.0, iOS 16.0): `Page.overrideUserPreference()` did not exist yet. if (target.hasCommand("Page.overrideUserPreference") && InspectorBackend.Enum.Page.UserPreferenceName[preference]) promises.push(target.PageAgent.overrideUserPreference(preference, value)); // COMPATIBILITY (macOS 13, iOS 16.0): `Page.setForcedAppearance()` was removed in favor of `Page.overrideUserPreference()` if (preference === WI.CSSManager.ForcedAppearancePreference && target.hasCommand("Page.setForcedAppearance")) promises.push(this.setForcedAppearance(value || null)); } if (value) this._overriddenUserPreferences.set(preference, value); else this._overriddenUserPreferences.delete(preference); Promise.allSettled(promises).then(() => { this.mediaQueryResultChanged(); this.dispatchEventToListeners(WI.CSSManager.Event.OverriddenUserPreferencesDidChange); }) } propertyNameHasOtherVendorPrefix(name) { if (!name || name.length < 4 || name.charAt(0) !== "-") return false; var match = name.match(/^(?:-moz-|-ms-|-o-|-epub-)/); if (!match) return false; return true; } propertyValueHasOtherVendorKeyword(value) { var match = value.match(/(?:-moz-|-ms-|-o-|-epub-)[-\w]+/); if (!match) return false; return true; } canonicalNameForPropertyName(name) { if (!name || name.length < 8 || name.charAt(0) !== "-") return name; // Keep in sync with prefix list from Source/WebInspectorUI/Scripts/update-inspector-css-documentation var match = name.match(/^(?:-webkit-|-khtml-|-apple-)(.+)/); if (!match) return name; return match[1]; } styleSheetForIdentifier(id) { let styleSheet = this._styleSheetIdentifierMap.get(id); if (styleSheet) return styleSheet; styleSheet = new WI.CSSStyleSheet(id); this._styleSheetIdentifierMap.set(id, styleSheet); return styleSheet; } stylesForNode(node) { if (node.id in this._nodeStylesMap) return this._nodeStylesMap[node.id]; var styles = new WI.DOMNodeStyles(node); this._nodeStylesMap[node.id] = styles; return styles; } inspectorStyleSheetsForFrame(frame) { return this.styleSheets.filter((styleSheet) => styleSheet.isInspectorStyleSheet() && styleSheet.parentFrame === frame); } preferredInspectorStyleSheetForFrame(frame, callback) { var inspectorStyleSheets = this.inspectorStyleSheetsForFrame(frame); for (let styleSheet of inspectorStyleSheets) { if (styleSheet[WI.CSSManager.PreferredInspectorStyleSheetSymbol]) { callback(styleSheet); return; } } let target = WI.assumingMainTarget(); target.CSSAgent.createStyleSheet(frame.id, function(error, styleSheetId) { if (error || !styleSheetId) { WI.reportInternalError(error || styleSheetId); return; } const url = null; let styleSheet = WI.cssManager.styleSheetForIdentifier(styleSheetId); styleSheet.updateInfo(url, frame, styleSheet.origin, styleSheet.isInlineStyleTag(), styleSheet.startLineNumber, styleSheet.startColumnNumber); styleSheet[WI.CSSManager.PreferredInspectorStyleSheetSymbol] = true; callback(styleSheet); }); } mediaTypeChanged() { // Act the same as if media queries changed. this.mediaQueryResultChanged(); } get modifiedStyles() { return Array.from(this._modifiedStyles.values()); } addModifiedStyle(style) { this._modifiedStyles.set(style.stringId, style); this.dispatchEventToListeners(WI.CSSManager.Event.ModifiedStylesChanged); } getModifiedStyle(style) { return this._modifiedStyles.get(style.stringId); } removeModifiedStyle(style) { this._modifiedStyles.delete(style.stringId); this.dispatchEventToListeners(WI.CSSManager.Event.ModifiedStylesChanged); } // PageObserver // COMPATIBILITY (macOS 13, iOS 16.0): `Page.defaultAppearanceDidChange` was removed in favor of `Page.defaultUserPreferencesDidChange` defaultAppearanceDidChange(protocolName) { let appearance = null; switch (protocolName) { case InspectorBackend.Enum.Page.Appearance.Light: appearance = WI.CSSManager.Appearance.Light; break; case InspectorBackend.Enum.Page.Appearance.Dark: appearance = WI.CSSManager.Appearance.Dark; break; default: console.error("Unknown default appearance name:", protocolName); break; } this.mediaQueryResultChanged(); this._defaultUserPreferences.set(WI.CSSManager.ForcedAppearancePreference, appearance); this.dispatchEventToListeners(WI.CSSManager.Event.DefaultUserPreferencesDidChange); } defaultUserPreferencesDidChange(userPreferences) { this._defaultUserPreferences.clear(); for (let userPreference of userPreferences) this._defaultUserPreferences.set(userPreference.name, userPreference.value) this.dispatchEventToListeners(WI.CSSManager.Event.DefaultUserPreferencesDidChange); } // CSSObserver mediaQueryResultChanged() { for (var key in this._nodeStylesMap) this._nodeStylesMap[key].mediaQueryResultDidChange(); } styleSheetChanged(styleSheetIdentifier) { var styleSheet = this.styleSheetForIdentifier(styleSheetIdentifier); console.assert(styleSheet); // Do not observe inline styles if (styleSheet.isInlineStyleAttributeStyleSheet()) return; if (!styleSheet.noteContentDidChange()) return; this._updateResourceContent(styleSheet); } styleSheetAdded(styleSheetInfo) { console.assert(!this._styleSheetIdentifierMap.has(styleSheetInfo.styleSheetId), "Attempted to add a CSSStyleSheet but identifier was already in use"); let styleSheet = this.styleSheetForIdentifier(styleSheetInfo.styleSheetId); let parentFrame = WI.networkManager.frameForIdentifier(styleSheetInfo.frameId); let origin = WI.CSSManager.protocolStyleSheetOriginToEnum(styleSheetInfo.origin); styleSheet.updateInfo(styleSheetInfo.sourceURL, parentFrame, origin, styleSheetInfo.isInline, styleSheetInfo.startLine, styleSheetInfo.startColumn); this.dispatchEventToListeners(WI.CSSManager.Event.StyleSheetAdded, {styleSheet}); } styleSheetRemoved(styleSheetIdentifier) { let styleSheet = this._styleSheetIdentifierMap.get(styleSheetIdentifier); console.assert(styleSheet, "Attempted to remove a CSSStyleSheet that was not tracked"); if (!styleSheet) return; this._styleSheetIdentifierMap.delete(styleSheetIdentifier); this.dispatchEventToListeners(WI.CSSManager.Event.StyleSheetRemoved, {styleSheet}); } // Private _nodePseudoClassesDidChange(event) { var node = event.target; for (var key in this._nodeStylesMap) { var nodeStyles = this._nodeStylesMap[key]; if (nodeStyles.node !== node && !nodeStyles.node.isDescendant(node)) continue; nodeStyles.pseudoClassesDidChange(node); } } _nodeAttributesDidChange(event) { var node = event.target; for (var key in this._nodeStylesMap) { var nodeStyles = this._nodeStylesMap[key]; if (nodeStyles.node !== node && !nodeStyles.node.isDescendant(node)) continue; nodeStyles.attributeDidChange(node, event.data.name); } } _mainResourceDidChange(event) { console.assert(event.target instanceof WI.Frame); if (!event.target.isMainFrame()) return; // Clear our maps when the main frame navigates. this._styleSheetIdentifierMap.clear(); this._styleSheetFrameURLMap.clear(); this._modifiedStyles.clear(); // COMPATIBILITY (macOS 14.0, iOS 17.0): the `PrefersColorScheme` override used to be cleared on main frame navigation // Since support can't be tested directly, check for the `reason` parameter of `Console.messagesCleared` as that change shipped in the same release. // FIXME: Use explicit version checking once is fixed. if (!InspectorBackend.hasEvent("Console.messagesCleared", "reason")) { this._overriddenUserPreferences.delete(InspectorBackend.Enum.Page.UserPreferenceName.PrefersColorScheme); this.dispatchEventToListeners(WI.CSSManager.Event.OverriddenUserPreferencesDidChange); } this._nodeStylesMap = {}; } _resourceAdded(event) { console.assert(event.target instanceof WI.Frame); var resource = event.data.resource; console.assert(resource); if (resource.type !== WI.Resource.Type.StyleSheet) return; this._clearStyleSheetsForResource(resource); } _resourceTypeDidChange(event) { console.assert(event.target instanceof WI.Resource); var resource = event.target; if (resource.type !== WI.Resource.Type.StyleSheet) return; this._clearStyleSheetsForResource(resource); } _clearStyleSheetsForResource(resource) { // Clear known stylesheets for this URL and frame. This will cause the style sheets to // be updated next time _fetchInfoForAllStyleSheets is called. this._styleSheetIdentifierMap.delete(this._frameURLMapKey(resource.parentFrame, resource.url)); } _frameURLMapKey(frame, url) { return frame.id + ":" + url; } _lookupStyleSheetForResource(resource, callback) { this._lookupStyleSheet(resource.parentFrame, resource.url, callback); } _lookupStyleSheet(frame, url, callback) { console.assert(frame instanceof WI.Frame); let key = this._frameURLMapKey(frame, url); function styleSheetsFetched() { callback(this._styleSheetFrameURLMap.get(key) || null); } let styleSheet = this._styleSheetFrameURLMap.get(key) || null; if (styleSheet) callback(styleSheet); else this._fetchInfoForAllStyleSheets(styleSheetsFetched.bind(this)); } _fetchInfoForAllStyleSheets(callback) { console.assert(typeof callback === "function"); function processStyleSheets(error, styleSheets) { this._styleSheetFrameURLMap.clear(); if (error) { callback(); return; } for (let styleSheetInfo of styleSheets) { let parentFrame = WI.networkManager.frameForIdentifier(styleSheetInfo.frameId); let origin = WI.CSSManager.protocolStyleSheetOriginToEnum(styleSheetInfo.origin); let styleSheet = this.styleSheetForIdentifier(styleSheetInfo.styleSheetId); styleSheet.updateInfo(styleSheetInfo.sourceURL, parentFrame, origin, styleSheetInfo.isInline, styleSheetInfo.startLine, styleSheetInfo.startColumn); let key = this._frameURLMapKey(parentFrame, styleSheetInfo.sourceURL); this._styleSheetFrameURLMap.set(key, styleSheet); } callback(); } let target = WI.assumingMainTarget(); target.CSSAgent.getAllStyleSheets(processStyleSheets.bind(this)); } _resourceContentDidChange(event) { var resource = event.target; if (resource === this._ignoreResourceContentDidChangeEventForResource) return; // Ignore changes to resource overrides, those are not live on the page. if (resource.localResourceOverride) return; // Ignore if it isn't a CSS style sheet. if (resource.type !== WI.Resource.Type.StyleSheet || resource.syntheticMIMEType !== "text/css") return; function applyStyleSheetChanges() { function styleSheetFound(styleSheet) { resource.__pendingChangeTimeout.cancel(); console.assert(styleSheet); if (!styleSheet) return; // To prevent updating a TextEditor's content while the user is typing in it we want to // ignore the next _updateResourceContent call. resource.__ignoreNextUpdateResourceContent = true; let revision = styleSheet.editableRevision; revision.updateRevisionContent(resource.content); } this._lookupStyleSheetForResource(resource, styleSheetFound.bind(this)); } if (!resource.__pendingChangeTimeout) resource.__pendingChangeTimeout = new Throttler(applyStyleSheetChanges.bind(this), 100); resource.__pendingChangeTimeout.fire(); } _updateResourceContent(styleSheet) { console.assert(styleSheet); function fetchedStyleSheetContent(parameters) { styleSheet.__pendingChangeTimeout.cancel(); let representedObject = parameters.sourceCode; console.assert(representedObject.url); if (!representedObject.url) return; if (!styleSheet.isInspectorStyleSheet()) { // Only try to update stylesheet resources. Other resources, like documents, can contain // multiple stylesheets and we don't have the source ranges to update those. representedObject = representedObject.parentFrame.resourcesForURL(representedObject.url).find((resource) => resource.type === WI.Resource.Type.StyleSheet); if (!representedObject) return; } if (representedObject.__ignoreNextUpdateResourceContent) { representedObject.__ignoreNextUpdateResourceContent = false; return; } this._ignoreResourceContentDidChangeEventForResource = representedObject; let revision = representedObject.editableRevision; if (styleSheet.isInspectorStyleSheet()) { revision.updateRevisionContent(representedObject.content); styleSheet.dispatchEventToListeners(WI.SourceCode.Event.ContentDidChange); } else revision.updateRevisionContent(parameters.content); this._ignoreResourceContentDidChangeEventForResource = null; } function styleSheetReady() { styleSheet.requestContent().then(fetchedStyleSheetContent.bind(this)); } function applyStyleSheetChanges() { if (styleSheet.url) styleSheetReady.call(this); else this._fetchInfoForAllStyleSheets(styleSheetReady.bind(this)); } if (!styleSheet.__pendingChangeTimeout) styleSheet.__pendingChangeTimeout = new Throttler(applyStyleSheetChanges.bind(this), 100); styleSheet.__pendingChangeTimeout.fire(); } }; WI.CSSManager.Event = { StyleSheetAdded: "css-manager-style-sheet-added", StyleSheetRemoved: "css-manager-style-sheet-removed", ModifiedStylesChanged: "css-manager-modified-styles-changed", DefaultUserPreferencesDidChange: "css-manager-default-user-preferences-did-change", OverriddenUserPreferencesDidChange: "css-manager-overriden-user-preferences-did-change", }; WI.CSSManager.UserPreferenceDefaultValue = "System"; // COMPATIBILITY (macOS 13, iOS 16.0): `Page.setForcedAppearance()` was removed in favor of `Page.overrideUserPreference()` WI.CSSManager.ForcedAppearancePreference = "ForcedAppearancePreference"; WI.CSSManager.Appearance = { Light: "Light", Dark: "Dark", }; WI.CSSManager.PseudoSelectorNames = { After: "after", Before: "before", Backdrop: "backdrop", FirstLetter: "first-letter", FirstLine: "first-line", Highlight: "highlight", GrammarError: "grammar-error", Marker: "marker", Selection: "selection", SpellingError: "spelling-error", ViewTransition: "view-transition", ViewTransitionGroup: "view-transition-group", ViewTransitionImagePair: "view-transition-image-pair", ViewTransitionNew: "view-transition-new", ViewTransitionOld: "view-transition-old", WebKitResizer: "-webkit-resizer", WebKitScrollbar: "-webkit-scrollbar", WebKitScrollbarButton: "-webkit-scrollbar-button", WebKitScrollbarCorner: "-webkit-scrollbar-corner", WebKitScrollbarThumb: "-webkit-scrollbar-thumb", WebKitScrollbarTrack: "-webkit-scrollbar-track", WebKitScrollbarTrackPiece: "-webkit-scrollbar-track-piece", }; WI.CSSManager.LayoutContextTypeChangedMode = { Observed: "observed", All: "all", }; WI.CSSManager.PseudoElementNames = ["before", "after"]; WI.CSSManager.ForceablePseudoClass = { Active: "active", Focus: "focus", FocusVisible: "focus-visible", FocusWithin: "focus-within", Hover: "hover", Target: "target", Visited: "visited", }; WI.CSSManager.PreferredInspectorStyleSheetSymbol = Symbol("css-manager-preferred-inspector-style-sheet"); /* Controllers/CSSQueryController.js */ /* * Copyright (C) 2021 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.CSSQueryController = class CSSQueryController extends WI.QueryController { constructor(values) { console.assert(Array.isArray(values), values); super(); this._values = values || []; this._cachedSpecialCharacterIndicesForValueMap = new Map; } // Public addValues(values) { console.assert(Array.isArray(values), values); if (!values.length) return; this._values.pushAll(values); } reset() { this._values = []; this._cachedSpecialCharacterIndicesForValueMap.clear(); } executeQuery(query) { if (!query || !this._values.length) return []; query = query.toLowerCase(); let results = []; for (let value of this._values) { if (!this._cachedSpecialCharacterIndicesForValueMap.has(value)) this._cachedSpecialCharacterIndicesForValueMap.set(value, this._findSpecialCharacterIndicesInPropertyName(value)); let matches = this.findQueryMatches(query, value.toLowerCase(), this._cachedSpecialCharacterIndicesForValueMap.get(value)); if (matches.length) results.push(new WI.QueryResult(value, matches)); } return results.sort((a, b) => { if (a.rank === b.rank) return WI.CSSProperty.sortPreferringNonPrefixed(a.value, b.value); return b.rank - a.rank; }); } // Private _findSpecialCharacterIndicesInPropertyName(propertyName) { return this.findSpecialCharacterIndices(propertyName, "-_"); } }; /* Controllers/CanvasManager.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 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.CanvasManager = class CanvasManager extends WI.Object { constructor() { super(); this._enabled = false; this._canvasCollection = new WI.CanvasCollection; this._canvasForIdentifierForTargetMap = new Map; this._shaderProgramForIdentifierForTargetMap = new Map; this._savedRecordings = new Set; WI.targetManager.addEventListener(WI.TargetManager.Event.TargetRemoved, this._handleTargetRemoved, this); WI.Frame.addEventListener(WI.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this); } // Agent get domains() { return ["Canvas"]; } activateExtraDomain(domain) { // COMPATIBILITY (iOS 14.0): Inspector.activateExtraDomains was removed in favor of a declared debuggable type console.assert(domain === "Canvas"); for (let target of WI.targets) this.initializeTarget(target); } // Target initializeTarget(target) { if (!this._enabled) return; if (target.hasDomain("Canvas")) { target.CanvasAgent.enable(); // COMPATIBILITY (iOS 12): Canvas.setRecordingAutoCaptureFrameCount did not exist yet. if (target.hasCommand("Canvas.setRecordingAutoCaptureFrameCount") && WI.settings.canvasRecordingAutoCaptureEnabled.value && WI.settings.canvasRecordingAutoCaptureFrameCount.value) target.CanvasAgent.setRecordingAutoCaptureFrameCount(WI.settings.canvasRecordingAutoCaptureFrameCount.value); } } // Static static supportsRecordingAutoCapture() { return InspectorBackend.hasCommand("Canvas.setRecordingAutoCaptureFrameCount"); } // Public get canvasCollection() { return this._canvasCollection; } get savedRecordings() { return this._savedRecordings; } async processJSON({filename, json, error}) { if (error) { WI.Recording.synthesizeError(error); return; } if (typeof json !== "object" || json === null) { WI.Recording.synthesizeError(WI.UIString("invalid JSON")); return; } let recording = WI.Recording.fromPayload(json); if (!recording) return; let extensionStart = filename.lastIndexOf("."); if (extensionStart !== -1) filename = filename.substring(0, extensionStart); recording.createDisplayName(filename); this._savedRecordings.add(recording); this.dispatchEventToListeners(WI.CanvasManager.Event.RecordingSaved, {recording, imported: true, initiatedByUser: true}); } enable() { console.assert(!this._enabled); this._enabled = true; for (let target of WI.targets) this.initializeTarget(target); } disable() { console.assert(this._enabled); for (let target of WI.targets) { if (target.hasDomain("Canvas")) target.CanvasAgent.disable(); } this._canvasCollection.clear(); this._canvasForIdentifierForTargetMap.clear(); this._shaderProgramForIdentifierForTargetMap.clear(); this._savedRecordings.clear(); this._enabled = false; } setRecordingAutoCaptureFrameCount(enabled, count) { console.assert(!isNaN(count) && count >= 0); for (let target of WI.targets) { // COMPATIBILITY (iOS 12): Canvas.setRecordingAutoCaptureFrameCount did not exist yet. if (target.hasCommand("Canvas.setRecordingAutoCaptureFrameCount")) target.CanvasAgent.setRecordingAutoCaptureFrameCount(enabled ? count : 0); } WI.settings.canvasRecordingAutoCaptureEnabled.value = enabled && count; WI.settings.canvasRecordingAutoCaptureFrameCount.value = count; } // CanvasObserver canvasAdded(target, canvasPayload) { let canvas = WI.Canvas.fromPayload(target, canvasPayload); let canvasForIdentifierMap = this._canvasForIdentifierForTargetMap.getOrInitialize(target, () => new Map); console.assert(!canvasForIdentifierMap.has(canvas.identifier), `Canvas already exists with id ${canvas.identifier}.`); canvasForIdentifierMap.set(canvas.identifier, canvas); this._canvasCollection.add(canvas); } canvasRemoved(target, canvasIdentifier) { let canvasForIdentifierMap = this._canvasForIdentifierForTargetMap.get(target); console.assert(canvasForIdentifierMap); if (!canvasForIdentifierMap) return; let canvas = canvasForIdentifierMap.take(canvasIdentifier); console.assert(canvas); if (!canvas) return; this._saveRecordings(canvas); this._canvasCollection.remove(canvas); let shaderProgramForIdentifierMap = this._shaderProgramForIdentifierForTargetMap.get(target); if (shaderProgramForIdentifierMap) { for (let program of canvas.shaderProgramCollection) shaderProgramForIdentifierMap.delete(program.identifier); } canvas.shaderProgramCollection.clear(); } canvasSizeChanged(target, canvasIdentifier, width, height) { let canvas = this._canvasForIdentifier(target, canvasIdentifier); if (!canvas) return; canvas.sizeChanged(new WI.Size(width, height)); } canvasMemoryChanged(target, canvasIdentifier, memoryCost) { let canvas = this._canvasForIdentifier(target, canvasIdentifier); if (!canvas) return; canvas.memoryChanged(memoryCost); } clientNodesChanged(target, canvasIdentifier) { let canvas = this._canvasForIdentifier(target, canvasIdentifier); if (!canvas) return; canvas.clientNodesChanged(); } recordingStarted(target, canvasIdentifier, initiator) { let canvas = this._canvasForIdentifier(target, canvasIdentifier); if (!canvas) return; canvas.recordingStarted(initiator); } recordingProgress(target, canvasIdentifier, framesPayload, bufferUsed) { let canvas = this._canvasForIdentifier(target, canvasIdentifier); if (!canvas) return; canvas.recordingProgress(framesPayload, bufferUsed); } recordingFinished(target, canvasIdentifier, recordingPayload) { let canvas = this._canvasForIdentifier(target, canvasIdentifier); if (!canvas) return; canvas.recordingFinished(recordingPayload); } extensionEnabled(target, canvasIdentifier, extension) { let canvas = this._canvasForIdentifier(target, canvasIdentifier); if (!canvas) return; canvas.enableExtension(extension); } programCreated(target, shaderProgramPayload) { let canvas = this._canvasForIdentifier(target, shaderProgramPayload.canvasId); if (!canvas) return; // COMPATIBILITY (iOS 13.0): `Canvas.ShaderProgram.programType` did not exist yet. let programType = shaderProgramPayload.programType; if (!programType) programType = WI.ShaderProgram.ProgramType.Render; let options = {}; // COMPATIBILITY (iOS 13.0): `Canvas.ShaderProgram.sharesVertexFragmentShader` did not exist yet. if (shaderProgramPayload.sharesVertexFragmentShader) options.sharesVertexFragmentShader = true; let program = new WI.ShaderProgram(target, shaderProgramPayload.programId, programType, canvas, options); let shaderProgramForIdentifierMap = this._shaderProgramForIdentifierForTargetMap.getOrInitialize(target, () => new Map); console.assert(!shaderProgramForIdentifierMap.has(program.identifier), `ShaderProgram already exists with id ${program.identifier}.`); shaderProgramForIdentifierMap.set(program.identifier, program); canvas.shaderProgramCollection.add(program); } programDeleted(target, programIdentifier) { let shaderProgramForIdentifierMap = this._shaderProgramForIdentifierForTargetMap.get(target); console.assert(shaderProgramForIdentifierMap); if (!shaderProgramForIdentifierMap) return; let program = shaderProgramForIdentifierMap.take(programIdentifier); console.assert(program); if (!program) return; program.canvas.shaderProgramCollection.remove(program); } // Private _canvasForIdentifier(target, canvasIdentifier) { let canvasForIdentifierMap = this._canvasForIdentifierForTargetMap.get(target); console.assert(canvasForIdentifierMap); if (!canvasForIdentifierMap) return null; let canvas = canvasForIdentifierMap.get(canvasIdentifier); console.assert(canvas); if (!canvas) return null; return canvas; } _saveRecordings(canvas) { for (let recording of canvas.recordingCollection) { recording.source = null; recording.createDisplayName(recording.displayName); this._savedRecordings.add(recording); this.dispatchEventToListeners(WI.CanvasManager.Event.RecordingSaved, {recording}); } } _handleTargetRemoved(event) { let {target} = event.data; this._canvasForIdentifierForTargetMap.delete(target); this._shaderProgramForIdentifierForTargetMap.delete(target); } _mainResourceDidChange(event) { console.assert(event.target instanceof WI.Frame); if (!event.target.isMainFrame()) return; WI.Canvas.resetUniqueDisplayNameNumbers(); for (let canvasForIdentifierMap of this._canvasForIdentifierForTargetMap.values()) { for (let canvas of canvasForIdentifierMap.values()) this._saveRecordings(canvas); } this._canvasCollection.clear(); this._canvasForIdentifierForTargetMap.clear(); this._shaderProgramForIdentifierForTargetMap.clear(); } // Testing get shaderPrograms() { let programs = []; for (let shaderProgramForIdentifierMap of this._shaderProgramForIdentifierForTargetMap.values()) programs.pushAll(shaderProgramForIdentifierMap.values()); return programs; } }; WI.CanvasManager.Event = { RecordingSaved: "canvas-manager-recording-saved", }; /* Controllers/ConsoleManager.js */ /* * Copyright (C) 2013 Apple Inc. All rights reserved. * Copyright (C) 2015 Tobias Reiss * * 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.ConsoleManager = class ConsoleManager extends WI.Object { constructor() { super(); this._warningCount = 0; this._errorCount = 0; this._issues = []; this._lastMessageLevel = null; this._clearMessagesRequested = false; this._isNewPageOrReload = false; this._remoteObjectsToRelease = null; this._customLoggingChannels = []; this._snippets = new Set; this._restoringSnippets = false; this._failedSourceMapConsoleMessages = new Set; WI.ConsoleSnippet.addEventListener(WI.SourceCode.Event.ContentDidChange, this._handleSnippetContentChanged, this); WI.Frame.addEventListener(WI.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this); WI.Target.registerInitializationPromise((async () => { let serializedSnippets = await WI.objectStores.consoleSnippets.getAll(); this._restoringSnippets = true; for (let serializedSnippet of serializedSnippets) { let snippet = WI.ConsoleSnippet.fromJSON(serializedSnippet); const key = null; WI.objectStores.consoleSnippets.associateObject(snippet, key, serializedSnippet); this.addSnippet(snippet); } this._restoringSnippets = false; })()); } // Static static supportsLogChannels() { return InspectorBackend.hasCommand("Console.getLoggingChannels"); } static issueMatchSourceCode(issue, sourceCode) { if (sourceCode instanceof WI.SourceMapResource) return issue.sourceCodeLocation && issue.sourceCodeLocation.displaySourceCode === sourceCode; if (sourceCode instanceof WI.Resource) return issue.url === sourceCode.url && (!issue.sourceCodeLocation || issue.sourceCodeLocation.sourceCode === sourceCode); if (sourceCode instanceof WI.Script) return issue.sourceCodeLocation && issue.sourceCodeLocation.sourceCode === sourceCode; return false; } // Public get warningCount() { return this._warningCount; } get errorCount() { return this._errorCount; } get snippets() { return this._snippets; } get customLoggingChannels() { return this._customLoggingChannels; } get failedSourceMapConsoleMessages() { return this._failedSourceMapConsoleMessages; } issuesForSourceCode(sourceCode) { var issues = []; for (var i = 0; i < this._issues.length; ++i) { var issue = this._issues[i]; if (WI.ConsoleManager.issueMatchSourceCode(issue, sourceCode)) issues.push(issue); } return issues; } releaseRemoteObjectWithConsoleClear(remoteObject) { if (!this._remoteObjectsToRelease) this._remoteObjectsToRelease = new Set; this._remoteObjectsToRelease.add(remoteObject); } addSnippet(snippet) { console.assert(snippet instanceof WI.ConsoleSnippet, snippet); console.assert(!this._snippets.has(snippet), snippet); console.assert(!this._snippets.some((existingSnippet) => snippet.contentIdentifier === existingSnippet.contentIdentifier), snippet); this._snippets.add(snippet); if (!this._restoringSnippets) WI.objectStores.consoleSnippets.putObject(snippet); this.dispatchEventToListeners(WI.ConsoleManager.Event.SnippetAdded, {snippet}); } removeSnippet(snippet) { console.assert(snippet instanceof WI.ConsoleSnippet, snippet); console.assert(this._snippets.has(snippet), snippet); this._snippets.delete(snippet); if (!this._restoringSnippets) WI.objectStores.consoleSnippets.deleteObject(snippet); this.dispatchEventToListeners(WI.ConsoleManager.Event.SnippetRemoved, {snippet}); } // ConsoleObserver messageWasAdded(target, source, level, text, type, url, line, column, repeatCount, parameters, stackTrace, requestId, timestamp) { // FIXME: Get a request from request ID. if (parameters) parameters = parameters.map((x) => WI.RemoteObject.fromPayload(x, target)); // COMPATIBILITY (macOS 13.0, iOS 16.0): `stackTrace` was an array of `Console.CallFrame`. if (Array.isArray(stackTrace)) stackTrace = {callFrames: stackTrace}; if (stackTrace) stackTrace = WI.StackTrace.fromPayload(target, stackTrace); const request = null; let message = new WI.ConsoleMessage(target, source, level, text, type, url, line, column, repeatCount, parameters, stackTrace, request, timestamp); this._incrementMessageLevelCount(message.level, message.repeatCount); this.dispatchEventToListeners(WI.ConsoleManager.Event.MessageAdded, {message}); if (message.level === WI.ConsoleMessage.MessageLevel.Warning || message.level === WI.ConsoleMessage.MessageLevel.Error) { let issue = new WI.IssueMessage(message); this._issues.push(issue); this.dispatchEventToListeners(WI.ConsoleManager.Event.IssueAdded, {issue}); this._collectFailedSourceMapConsoleMessage(message); } } messagesCleared(reason) { if (this._remoteObjectsToRelease) { for (let remoteObject of this._remoteObjectsToRelease) remoteObject.release(); this._remoteObjectsToRelease = null; } WI.ConsoleCommandResultMessage.clearMaximumSavedResultIndex(); // COMPATIBILITY (iOS 16.4, macOS 13.3): `Console.ClearReason` did not exist. if (!reason) { if (this._clearMessagesRequested) { // Frontend requested "clear console" and Backend successfully completed the request. this._clearMessagesRequested = false; this._clearMessages(); return; } // Received an unrequested clear console event. // This could be for a navigation or other reasons (like console.clear()). // If this was a reload, we may not want to dispatch WI.ConsoleManager.Event.Cleared. // To detect if this is a reload we wait a turn and check if there was a main resource change reload. setTimeout(this._delayedMessagesCleared.bind(this), 0); return; } switch (reason) { case WI.ConsoleManager.ClearReason.ConsoleAPI: this._clearMessages(); return; case WI.ConsoleManager.ClearReason.MainFrameNavigation: console.assert(this._isNewPageOrReload); this._isNewPageOrReload = false; if (WI.settings.clearLogOnNavigate.value) this._clearMessages(); return; } console.assert(false, "not reached"); } messageRepeatCountUpdated(count, timestamp) { this._incrementMessageLevelCount(this._lastMessageLevel, 1); this.dispatchEventToListeners(WI.ConsoleManager.Event.PreviousMessageRepeatCountUpdated, {count, timestamp}); } requestClearMessages() { this._clearMessagesRequested = true; for (let target of WI.targets) target.ConsoleAgent.clearMessages(); } initializeLogChannels(target) { console.assert(target.hasDomain("Console")); if (!WI.ConsoleManager.supportsLogChannels()) return; if (this._customLoggingChannels.length) return; target.ConsoleAgent.getLoggingChannels((error, channels) => { if (error) return; this._customLoggingChannels = channels.map(WI.LoggingChannel.fromPayload); }); } // Private _incrementMessageLevelCount(level, count) { switch (level) { case WI.ConsoleMessage.MessageLevel.Warning: this._warningCount += count; break; case WI.ConsoleMessage.MessageLevel.Error: this._errorCount += count; break; } this._lastMessageLevel = level; } _clearMessages() { this._warningCount = 0; this._errorCount = 0; this._issues = []; this._lastMessageLevel = null; this.dispatchEventToListeners(WI.ConsoleManager.Event.Cleared); } _collectFailedSourceMapConsoleMessage(message) { if (!WI.settings.experimentalGroupSourceMapErrors.value) return; if (message.source !== WI.ConsoleMessage.MessageSource.Network && message.level !== WI.ConsoleMessage.MessageLevel.Error) return; if (WI.networkManager.isSourceMapURL(message.url)) this._failedSourceMapConsoleMessages.add(message); } _delayedMessagesCleared() { if (this._isNewPageOrReload) { this._isNewPageOrReload = false; if (!WI.settings.clearLogOnNavigate.value) return; } this._clearMessages(); } _handleSnippetContentChanged(event) { let snippet = event.target; console.assert(this._snippets.has(snippet), snippet); WI.objectStores.consoleSnippets.putObject(snippet); } _mainResourceDidChange(event) { console.assert(event.target instanceof WI.Frame); if (!event.target.isMainFrame()) return; this._isNewPageOrReload = true; let timestamp = Date.now(); let wasReloaded = event.data.oldMainResource && event.data.oldMainResource.url === event.target.mainResource.url; this.dispatchEventToListeners(WI.ConsoleManager.Event.SessionStarted, {timestamp, wasReloaded}); WI.ConsoleCommandResultMessage.clearMaximumSavedResultIndex(); } }; WI.ConsoleManager.Event = { SessionStarted: "console-manager-session-was-started", Cleared: "console-manager-cleared", MessageAdded: "console-manager-message-added", IssueAdded: "console-manager-issue-added", PreviousMessageRepeatCountUpdated: "console-manager-previous-message-repeat-count-updated", SnippetAdded: "console-manager-snippet-added", SnippetRemoved: "console-manager-snippet-removed", }; WI.ConsoleManager.ClearReason = { ConsoleAPI: "console-api", MainFrameNavigation: "main-frame-navigation", } /* Controllers/DOMDebuggerManager.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 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.DOMDebuggerManager = class DOMDebuggerManager extends WI.Object { constructor() { super(); this._domBreakpointURLMap = new Multimap; this._domBreakpointFrameIdentifierMap = new Map; this._clearingDOMBreakpointsForRemovedDOMNode = false; this._listenerBreakpoints = []; this._allAnimationFramesBreakpoint = null; this._allIntervalsBreakpoint = null; this._allListenersBreakpoint = null; this._allTimeoutsBreakpoint = null; this._urlBreakpoints = []; this._allRequestsBreakpoint = null; WI.DOMBreakpoint.addEventListener(WI.Breakpoint.Event.DisabledStateDidChange, this._handleDOMBreakpointDisabledStateChanged, this); WI.DOMBreakpoint.addEventListener(WI.Breakpoint.Event.ConditionDidChange, this._handleDOMBreakpointEditablePropertyChanged, this); WI.DOMBreakpoint.addEventListener(WI.Breakpoint.Event.IgnoreCountDidChange, this._handleDOMBreakpointEditablePropertyChanged, this); WI.DOMBreakpoint.addEventListener(WI.Breakpoint.Event.AutoContinueDidChange, this._handleDOMBreakpointEditablePropertyChanged, this); WI.DOMBreakpoint.addEventListener(WI.Breakpoint.Event.ActionsDidChange, this._handleDOMBreakpointActionsChanged, this); WI.DOMBreakpoint.addEventListener(WI.DOMBreakpoint.Event.DOMNodeWillChange, this._handleDOMBreakpointDOMNodeWillChange, this); WI.DOMBreakpoint.addEventListener(WI.DOMBreakpoint.Event.DOMNodeDidChange, this._handleDOMBreakpointDOMNodeDidChange, this); WI.EventBreakpoint.addEventListener(WI.Breakpoint.Event.DisabledStateDidChange, this._handleEventBreakpointDisabledStateChanged, this); WI.EventBreakpoint.addEventListener(WI.Breakpoint.Event.ConditionDidChange, this._handleEventBreakpointEditablePropertyChanged, this); WI.EventBreakpoint.addEventListener(WI.Breakpoint.Event.IgnoreCountDidChange, this._handleEventBreakpointEditablePropertyChanged, this); WI.EventBreakpoint.addEventListener(WI.Breakpoint.Event.AutoContinueDidChange, this._handleEventBreakpointEditablePropertyChanged, this); WI.EventBreakpoint.addEventListener(WI.Breakpoint.Event.ActionsDidChange, this._handleEventBreakpointActionsChanged, this); WI.URLBreakpoint.addEventListener(WI.Breakpoint.Event.DisabledStateDidChange, this._handleURLBreakpointDisabledStateChanged, this); WI.URLBreakpoint.addEventListener(WI.Breakpoint.Event.ConditionDidChange, this._handleURLBreakpointEditablePropertyChanged, this); WI.URLBreakpoint.addEventListener(WI.Breakpoint.Event.IgnoreCountDidChange, this._handleURLBreakpointEditablePropertyChanged, this); WI.URLBreakpoint.addEventListener(WI.Breakpoint.Event.AutoContinueDidChange, this._handleURLBreakpointEditablePropertyChanged, this); WI.URLBreakpoint.addEventListener(WI.Breakpoint.Event.ActionsDidChange, this._handleURLBreakpointActionsChanged, this); WI.domManager.addEventListener(WI.DOMManager.Event.NodeRemoved, this._nodeRemoved, this); WI.domManager.addEventListener(WI.DOMManager.Event.NodeInserted, this._nodeInserted, this); WI.networkManager.addEventListener(WI.NetworkManager.Event.MainFrameDidChange, this._mainFrameDidChange, this); WI.Frame.addEventListener(WI.Frame.Event.ChildFrameWasRemoved, this._childFrameWasRemoved, this); WI.Frame.addEventListener(WI.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this); let loadBreakpoints = (constructor, objectStore, oldSettings, callback) => { WI.Target.registerInitializationPromise((async () => { for (let key of oldSettings) { let existingSerializedBreakpoints = WI.Setting.migrateValue(key); if (existingSerializedBreakpoints) { for (let existingSerializedBreakpoint of existingSerializedBreakpoints) await objectStore.putObject(constructor.fromJSON(existingSerializedBreakpoint)); } } let serializedBreakpoints = await objectStore.getAll(); this._restoringBreakpoints = true; for (let serializedBreakpoint of serializedBreakpoints) { let breakpoint = constructor.fromJSON(serializedBreakpoint); const key = null; objectStore.associateObject(breakpoint, key, serializedBreakpoint); callback(breakpoint); } this._restoringBreakpoints = false; })()); }; function loadLegacySpecialBreakpoint(shownSettingsKey, enabledSettingsKey, callback) { if (!WI.Setting.migrateValue(shownSettingsKey)) return; return callback({ disabled: !WI.Setting.migrateValue(enabledSettingsKey), }); } loadBreakpoints(WI.DOMBreakpoint, WI.objectStores.domBreakpoints, ["dom-breakpoints"], (breakpoint) => { this.addDOMBreakpoint(breakpoint); }); if (DOMDebuggerManager.supportsEventBreakpoints() || DOMDebuggerManager.supportsEventListenerBreakpoints()) { loadBreakpoints(WI.EventBreakpoint, WI.objectStores.eventBreakpoints, ["event-breakpoints"], (breakpoint) => { this.addEventBreakpoint(breakpoint); }); this._allAnimationFramesBreakpoint ??= loadLegacySpecialBreakpoint("show-all-animation-frames-breakpoint", "break-on-all-animation-frames", (options) => new WI.EventBreakpoint(WI.EventBreakpoint.Type.AnimationFrame, options)); this._allIntervalsBreakpoint ??= loadLegacySpecialBreakpoint("show-all-inteverals-breakpoint", "break-on-all-intervals", (options) => new WI.EventBreakpoint(WI.EventBreakpoint.Type.Interval, options)); this._allListenersBreakpoint ??= loadLegacySpecialBreakpoint("show-all-listeners-breakpoint", "break-on-all-listeners", (options) => new WI.EventBreakpoint(WI.EventBreakpoint.Type.Listener, options)); this._allTimeoutsBreakpoint ??= loadLegacySpecialBreakpoint("show-all-timeouts-breakpoint", "break-on-all-timeouts", (options) => new WI.EventBreakpoint(WI.EventBreakpoint.Type.Timeout, options)); } if (DOMDebuggerManager.supportsURLBreakpoints() || DOMDebuggerManager.supportsXHRBreakpoints()) { loadBreakpoints(WI.URLBreakpoint, WI.objectStores.urlBreakpoints, ["xhr-breakpoints", "url-breakpoints"], (breakpoint) => { this.addURLBreakpoint(breakpoint); }); this._allRequestsBreakpoint ??= loadLegacySpecialBreakpoint("show-all-requests-breakpoint", "break-on-all-requests", (options) => new WI.URLBreakpoint(WI.URLBreakpoint.Type.Text, "", options)); } } // Target initializeTarget(target) { if (target.hasDomain("DOMDebugger")) { this._restoringBreakpoints = true; if (target === WI.assumingMainTarget() && target.mainResource) this._speculativelyResolveDOMBreakpointsForURL(target.mainResource.url); if (this._allAnimationFramesBreakpoint && !this._allAnimationFramesBreakpoint.disabled) this._setEventBreakpoint(this._allAnimationFramesBreakpoint, target); if (this._allIntervalsBreakpoint && !this._allIntervalsBreakpoint.disabled) this._setEventBreakpoint(this._allIntervalsBreakpoint, target); if (this._allListenersBreakpoint && !this._allListenersBreakpoint.disabled) this._setEventBreakpoint(this._allListenersBreakpoint, target); if (this._allTimeoutsBreakpoint && !this._allTimeoutsBreakpoint.disabled) this._setEventBreakpoint(this._allTimeoutsBreakpoint, target); if (this._allRequestsBreakpoint) this._setURLBreakpoint(this._allRequestsBreakpoint, target); for (let breakpoint of this._listenerBreakpoints) { if (!breakpoint.disabled) this._setEventBreakpoint(breakpoint, target); } for (let breakpoint of this._urlBreakpoints) { if (!breakpoint.disabled) this._setURLBreakpoint(breakpoint, target); } this._restoringBreakpoints = false; } } // Static static supportsEventBreakpoints() { // COMPATIBILITY (iOS 13): DOMDebugger.setEventBreakpoint and DOMDebugger.removeEventBreakpoint did not exist yet. return InspectorBackend.hasCommand("DOMDebugger.setEventBreakpoint") && InspectorBackend.hasCommand("DOMDebugger.removeEventBreakpoint"); } static supportsEventListenerBreakpoints() { // COMPATIBILITY (iOS 12.2): Replaced by DOMDebugger.setEventBreakpoint and DOMDebugger.removeEventBreakpoint. return InspectorBackend.hasCommand("DOMDebugger.setEventListenerBreakpoint") && InspectorBackend.hasCommand("DOMDebugger.removeEventListenerBreakpoint"); } static supportsURLBreakpoints() { // COMPATIBILITY (iOS 13): DOMDebugger.setURLBreakpoint and DOMDebugger.removeURLBreakpoint did not exist yet. return InspectorBackend.hasCommand("DOMDebugger.setURLBreakpoint") && InspectorBackend.hasCommand("DOMDebugger.removeURLBreakpoint"); } static supportsXHRBreakpoints() { // COMPATIBILITY (iOS 13): Replaced by DOMDebugger.setURLBreakpoint and DOMDebugger.removeURLBreakpoint. return InspectorBackend.hasCommand("DOMDebugger.setXHRBreakpoint") && InspectorBackend.hasCommand("DOMDebugger.removeXHRBreakpoint"); } static supportsAllListenersBreakpoint() { // COMPATIBILITY (iOS 13): DOMDebugger.EventBreakpointType.Interval and DOMDebugger.EventBreakpointType.Timeout did not exist yet. return DOMDebuggerManager.supportsEventBreakpoints() && InspectorBackend.Enum.DOMDebugger.EventBreakpointType.Interval && InspectorBackend.Enum.DOMDebugger.EventBreakpointType.Timeout; } // Public get supported() { return InspectorBackend.hasDomain("DOMDebugger"); } get allAnimationFramesBreakpoint() { return this._allAnimationFramesBreakpoint; } get allIntervalsBreakpoint() { return this._allIntervalsBreakpoint; } get allListenersBreakpoint() { return this._allListenersBreakpoint; } get allTimeoutsBreakpoint() { return this._allTimeoutsBreakpoint; } get allRequestsBreakpoint() { return this._allRequestsBreakpoint; } get domBreakpoints() { let mainFrame = WI.networkManager.mainFrame; if (!mainFrame) return []; let resolvedBreakpoints = []; let frames = [mainFrame]; while (frames.length) { let frame = frames.shift(); let domBreakpointNodeIdentifierMap = this._domBreakpointFrameIdentifierMap.get(frame); if (domBreakpointNodeIdentifierMap) resolvedBreakpoints.pushAll(domBreakpointNodeIdentifierMap.values()); frames.pushAll(frame.childFrameCollection); } return resolvedBreakpoints; } get listenerBreakpoints() { return this._listenerBreakpoints; } get urlBreakpoints() { return this._urlBreakpoints; } domBreakpointsForNode(node) { console.assert(node instanceof WI.DOMNode); if (!node || !node.frame) return []; let domBreakpointNodeIdentifierMap = this._domBreakpointFrameIdentifierMap.get(node.frame); if (!domBreakpointNodeIdentifierMap) return []; let breakpoints = domBreakpointNodeIdentifierMap.get(node); return breakpoints ? Array.from(breakpoints) : []; } domBreakpointsInSubtree(node) { console.assert(node instanceof WI.DOMNode); let breakpoints = []; if (node.children) { let children = Array.from(node.children); while (children.length) { let child = children.pop(); if (child.children) children.pushAll(child.children); breakpoints.pushAll(this.domBreakpointsForNode(child)); } } return breakpoints; } addDOMBreakpoint(breakpoint) { console.assert(breakpoint instanceof WI.DOMBreakpoint, breakpoint); console.assert(breakpoint.url, breakpoint); if (!breakpoint || !breakpoint.url) return; console.assert(!breakpoint.special, breakpoint); this._domBreakpointURLMap.add(breakpoint.url, breakpoint); if (breakpoint.domNode) { this._resolveDOMBreakpoint(breakpoint, breakpoint.domNode); if (!breakpoint.disabled) { // We should get the target associated with the nodeIdentifier of this breakpoint. let target = WI.assumingMainTarget(); if (target) this._setDOMBreakpoint(breakpoint, target); } this.dispatchEventToListeners(WI.DOMDebuggerManager.Event.DOMBreakpointAdded, {breakpoint}); WI.debuggerManager.addProbesForBreakpoint(breakpoint); } else this._speculativelyResolveDOMBreakpoint(breakpoint); if (!this._restoringBreakpoints) WI.objectStores.domBreakpoints.putObject(breakpoint); } removeDOMBreakpoint(breakpoint) { console.assert(breakpoint instanceof WI.DOMBreakpoint, breakpoint); console.assert(breakpoint.url, breakpoint); if (!breakpoint || !breakpoint.url) return; console.assert(!breakpoint.special, breakpoint); // Disable the breakpoint first, so removing actions doesn't re-add the breakpoint. breakpoint.disabled = true; breakpoint.clearActions(); this._domBreakpointURLMap.delete(breakpoint.url); if (breakpoint.domNode) { if (breakpoint.domNode.frame) { let domBreakpointNodeIdentifierMap = this._domBreakpointFrameIdentifierMap.get(breakpoint.domNode.frame); domBreakpointNodeIdentifierMap.delete(breakpoint.domNode, breakpoint); if (!domBreakpointNodeIdentifierMap.size) this._domBreakpointFrameIdentifierMap.delete(breakpoint.domNode.frame); } this.dispatchEventToListeners(WI.DOMDebuggerManager.Event.DOMBreakpointRemoved, {breakpoint}); breakpoint.domNode = null; } if (!this._restoringBreakpoints) WI.objectStores.domBreakpoints.deleteObject(breakpoint); } removeDOMBreakpointsForNode(node) { this.domBreakpointsForNode(node).forEach(this.removeDOMBreakpoint, this); } listenerBreakpointsForEventName(eventName) { if (DOMDebuggerManager.supportsAllListenersBreakpoint() && this._allListenersBreakpoint && !this._allListenersBreakpoint.disabled) return this._allListenersBreakpoint; // Order event breakpoints based on how closely they match the given symbol. As an example, // a regular expression is likely going to match more events than a case-insensitive string. const rankFunctions = [ (breakpoint) => breakpoint.caseSensitive && !breakpoint.isRegex, // exact match (breakpoint) => !breakpoint.caseSensitive && !breakpoint.isRegex, // case-insensitive (breakpoint) => breakpoint.caseSensitive && breakpoint.isRegex, // case-sensitive regex (breakpoint) => !breakpoint.caseSensitive && breakpoint.isRegex, // case-insensitive regex ]; return this._listenerBreakpoints .filter((breakpoint) => breakpoint.matches(eventName)) .sort((a, b) => { let aRank = rankFunctions.findIndex((rankFunction) => rankFunction(a)); let bRank = rankFunctions.findIndex((rankFunction) => rankFunction(b)); return aRank - bRank; }); } addEventBreakpoint(breakpoint) { console.assert(breakpoint instanceof WI.EventBreakpoint, breakpoint); if (!breakpoint) return false; console.assert(!breakpoint.special, breakpoint); switch (breakpoint.type) { case WI.EventBreakpoint.Type.AnimationFrame: console.assert(!this._allAnimationFramesBreakpoint, this._allAnimationFramesBreakpoint, breakpoint); this._allAnimationFramesBreakpoint = breakpoint; break; case WI.EventBreakpoint.Type.Interval: console.assert(!this._allIntervalsBreakpoint, this._allIntervalsBreakpoint, breakpoint); this._allIntervalsBreakpoint = breakpoint; break; case WI.EventBreakpoint.Type.Listener: if (breakpoint.eventName) { if (this._listenerBreakpoints.some((existing) => existing.equals(breakpoint))) return false; this._listenerBreakpoints.push(breakpoint); } else { console.assert(!this._allListenersBreakpoint, this._allListenersBreakpoint, breakpoint); this._allListenersBreakpoint = breakpoint; } break; case WI.EventBreakpoint.Type.Timeout: console.assert(!this._allTimeoutsBreakpoint, this._allTimeoutsBreakpoint, breakpoint); this._allTimeoutsBreakpoint = breakpoint; break; } WI.debuggerManager.addProbesForBreakpoint(breakpoint); this.dispatchEventToListeners(WI.DOMDebuggerManager.Event.EventBreakpointAdded, {breakpoint}); if (!breakpoint.disabled) { for (let target of WI.targets) this._setEventBreakpoint(breakpoint, target); } if (!this._restoringBreakpoints) WI.objectStores.eventBreakpoints.putObject(breakpoint); return true; } removeEventBreakpoint(breakpoint) { console.assert(breakpoint instanceof WI.EventBreakpoint, breakpoint); if (!breakpoint) return; // Disable the breakpoint first, so removing actions doesn't re-add the breakpoint. breakpoint.disabled = true; breakpoint.clearActions(); switch (breakpoint.type) { case WI.EventBreakpoint.Type.AnimationFrame: console.assert(this._allAnimationFramesBreakpoint, this._allAnimationFramesBreakpoint); this._allAnimationFramesBreakpoint = null; break; case WI.EventBreakpoint.Type.Interval: console.assert(this._allIntervalsBreakpoint, this._allIntervalsBreakpoint); this._allIntervalsBreakpoint = null; break; case WI.EventBreakpoint.Type.Listener: if (breakpoint.eventName) { console.assert(this._listenerBreakpoints.includes(breakpoint), breakpoint); if (!this._listenerBreakpoints.includes(breakpoint)) return; this._listenerBreakpoints.remove(breakpoint); } else { console.assert(this._allListenersBreakpoint, this._allListenersBreakpoint); this._allListenersBreakpoint = null; } break; case WI.EventBreakpoint.Type.Timeout: console.assert(this._allTimeoutsBreakpoint, this._allTimeoutsBreakpoint); this._allTimeoutsBreakpoint = null; break; } if (!this._restoringBreakpoints) WI.objectStores.eventBreakpoints.deleteObject(breakpoint); WI.debuggerManager.removeProbesForBreakpoint(breakpoint); this.dispatchEventToListeners(WI.DOMDebuggerManager.Event.EventBreakpointRemoved, {breakpoint}); } urlBreakpointForURL(url) { return this._urlBreakpoints.find((breakpoint) => breakpoint.url === url) || null; } urlBreakpointsMatchingURL(url) { return this._urlBreakpoints .filter((urlBreakpoint) => { switch (urlBreakpoint.type) { case WI.URLBreakpoint.Type.Text: return urlBreakpoint.url.toLowerCase() === url.toLowerCase(); case WI.URLBreakpoint.Type.RegularExpression: return (new RegExp(urlBreakpoint.url, "i")).test(url); } return false; }) .sort((a, b) => { // Order URL breakpoints based on how closely they match the given URL. const typeRankings = [ WI.URLBreakpoint.Type.Text, WI.URLBreakpoint.Type.RegularExpression, ]; return typeRankings.indexOf(a.type) - typeRankings.indexOf(b.type); }); } addURLBreakpoint(breakpoint) { console.assert(breakpoint instanceof WI.URLBreakpoint, breakpoint); if (!breakpoint) return false; console.assert(!breakpoint.special, breakpoint); if (breakpoint.url) { if (this._urlBreakpoints.some((entry) => entry.type === breakpoint.type && entry.url === breakpoint.url)) return false; this._urlBreakpoints.push(breakpoint); } else { console.assert(!this._allRequestsBreakpoint, this._allRequestsBreakpoint, breakpoint); this._allRequestsBreakpoint = breakpoint; } WI.debuggerManager.addProbesForBreakpoint(breakpoint); this.dispatchEventToListeners(WI.DOMDebuggerManager.Event.URLBreakpointAdded, {breakpoint}); if (!breakpoint.disabled) { for (let target of WI.targets) this._setURLBreakpoint(breakpoint, target); } if (!this._restoringBreakpoints) WI.objectStores.urlBreakpoints.putObject(breakpoint); return true; } removeURLBreakpoint(breakpoint) { console.assert(breakpoint instanceof WI.URLBreakpoint, breakpoint); if (!breakpoint) return; // Disable the breakpoint first, so removing actions doesn't re-add the breakpoint. breakpoint.disabled = true; breakpoint.clearActions(); if (breakpoint.url) { console.assert(this._urlBreakpoints.includes(breakpoint), breakpoint); if (!this._urlBreakpoints.includes(breakpoint)) return; this._urlBreakpoints.remove(breakpoint); } else { console.assert(this._allRequestsBreakpoint, this._allRequestsBreakpoint); this._allRequestsBreakpoint = null; } if (!this._restoringBreakpoints) WI.objectStores.urlBreakpoints.deleteObject(breakpoint); WI.debuggerManager.removeProbesForBreakpoint(breakpoint); this.dispatchEventToListeners(WI.DOMDebuggerManager.Event.URLBreakpointRemoved, {breakpoint}); } // Private _detachDOMBreakpointsForFrame(frame) { let domBreakpointNodeIdentifierMap = this._domBreakpointFrameIdentifierMap.get(frame); if (domBreakpointNodeIdentifierMap) { this._domBreakpointFrameIdentifierMap.delete(frame); this._clearingDOMBreakpointsForRemovedDOMNode = true; for (let breakpoint of domBreakpointNodeIdentifierMap.values()) breakpoint.domNode = null; this._clearingDOMBreakpointsForRemovedDOMNode = false; } for (let childFrame of frame.childFrameCollection) this._detachDOMBreakpointsForFrame(childFrame); } _speculativelyResolveDOMBreakpointsForURL(url) { let domBreakpoints = this._domBreakpointURLMap.get(url); if (!domBreakpoints) return; for (let breakpoint of domBreakpoints) this._speculativelyResolveDOMBreakpoint(breakpoint); } _speculativelyResolveDOMBreakpoint(breakpoint) { if (breakpoint.domNode) return; WI.domManager.pushNodeByPathToFrontend(breakpoint.path, (nodeIdentifier) => { if (!nodeIdentifier) return; if (breakpoint.domNode) { // This breakpoint may have been resolved by a node being inserted before this // callback is invoked. If so, the `nodeIdentifier` should match, so don't try // to resolve it again as it would've already been resolved. console.assert(breakpoint.domNode.id === nodeIdentifier); return; } this._restoringBreakpoints = true; this._resolveDOMBreakpoint(breakpoint, WI.domManager.nodeForId(nodeIdentifier)); this._restoringBreakpoints = false; }); } _resolveDOMBreakpoint(breakpoint, node) { console.assert(node instanceof WI.DOMNode, node); if (!node.frame) return; let domBreakpointNodeIdentifierMap = this._domBreakpointFrameIdentifierMap.get(node.frame); if (!domBreakpointNodeIdentifierMap) { domBreakpointNodeIdentifierMap = new Multimap; this._domBreakpointFrameIdentifierMap.set(node.frame, domBreakpointNodeIdentifierMap); } domBreakpointNodeIdentifierMap.add(node, breakpoint); breakpoint.domNode = node; } _setDOMBreakpoint(breakpoint, target) { console.assert(!breakpoint.disabled, breakpoint); console.assert(breakpoint.domNode instanceof WI.DOMNode, breakpoint); console.assert(target.type !== WI.TargetType.Worker, "Worker targets do not support DOM breakpoints", target); if (!this._restoringBreakpoints && !WI.debuggerManager.breakpointsDisabledTemporarily) WI.debuggerManager.breakpointsEnabled = true; target.DOMDebuggerAgent.setDOMBreakpoint.invoke({ nodeId: breakpoint.domNode.id, type: breakpoint.type, options: breakpoint.optionsToProtocol(), }); } _removeDOMBreakpoint(breakpoint, target) { console.assert(breakpoint.domNode instanceof WI.DOMNode, breakpoint); console.assert(target.type !== WI.TargetType.Worker, "Worker targets do not support DOM breakpoints", target); target.DOMDebuggerAgent.removeDOMBreakpoint(breakpoint.domNode.id, breakpoint.type); } _commandArgumentsForEventBreakpoint(breakpoint) { let commandArguments = {}; switch (breakpoint) { case this._allAnimationFramesBreakpoint: commandArguments.breakpointType = WI.EventBreakpoint.Type.AnimationFrame; if (!DOMDebuggerManager.supportsAllListenersBreakpoint()) commandArguments.eventName = "requestAnimationFrame"; break; case this._allIntervalsBreakpoint: if (DOMDebuggerManager.supportsAllListenersBreakpoint()) commandArguments.breakpointType = WI.EventBreakpoint.Type.Interval; else { commandArguments.breakpointType = WI.EventBreakpoint.Type.Timer; commandArguments.eventName = "setInterval"; } break; case this._allListenersBreakpoint: if (!DOMDebuggerManager.supportsAllListenersBreakpoint()) return; commandArguments.breakpointType = WI.EventBreakpoint.Type.Listener; break; case this._allTimeoutsBreakpoint: if (DOMDebuggerManager.supportsAllListenersBreakpoint()) commandArguments.breakpointType = WI.EventBreakpoint.Type.Timeout; else { commandArguments.breakpointType = WI.EventBreakpoint.Type.Timer; commandArguments.eventName = "setTimeout"; } break; default: console.assert(breakpoint.type === WI.EventBreakpoint.Type.Listener, breakpoint.type); console.assert(breakpoint.eventName, breakpoint.eventName); commandArguments.breakpointType = breakpoint.type; commandArguments.eventName = breakpoint.eventName; commandArguments.caseSensitive = breakpoint.caseSensitive; commandArguments.isRegex = breakpoint.isRegex; break; } return commandArguments; } _setEventBreakpoint(breakpoint, target) { console.assert(!breakpoint.disabled, breakpoint); // Worker targets do not support `requestAnimationFrame` breakpoints. if (breakpoint === this._allAnimationFramesBreakpoint && target.type === WI.TargetType.Worker) return; // COMPATIBILITY (iOS 12.0): DOMDebugger.setEventListenerBreakpoint was replaced by DOMDebugger.setEventBreakpoint. if (!target.hasCommand("DOMDebugger.setEventBreakpoint")) { console.assert(breakpoint.type === WI.EventBreakpoint.Type.Listener); if (!this._restoringBreakpoints && !WI.debuggerManager.breakpointsDisabledTemporarily) WI.debuggerManager.breakpointsEnabled = true; target.DOMDebuggerAgent.setEventListenerBreakpoint(breakpoint.eventName); return; } let commandArguments = this._commandArgumentsForEventBreakpoint(breakpoint); if (!this._restoringBreakpoints && !WI.debuggerManager.breakpointsDisabledTemporarily) WI.debuggerManager.breakpointsEnabled = true; commandArguments.options = breakpoint.optionsToProtocol(); target.DOMDebuggerAgent.setEventBreakpoint.invoke(commandArguments); } _removeEventBreakpoint(breakpoint, target) { // Worker targets do not support `requestAnimationFrame` breakpoints. if (breakpoint === this._allAnimationFramesBreakpoint && target.type === WI.TargetType.Worker) return; // COMPATIBILITY (iOS 12.0): DOMDebugger.removeEventListenerBreakpoint was replaced by DOMDebugger.removeEventBreakpoint. if (!target.hasCommand("DOMDebugger.removeEventBreakpoint")) { console.assert(breakpoint.type === WI.EventBreakpoint.Type.Listener); target.DOMDebuggerAgent.removeEventListenerBreakpoint(breakpoint.eventName); return; } let commandArguments = this._commandArgumentsForEventBreakpoint(breakpoint); target.DOMDebuggerAgent.removeEventBreakpoint.invoke(commandArguments); } _setURLBreakpoint(breakpoint, target) { console.assert(!breakpoint.disabled, breakpoint); if (!this._restoringBreakpoints && !WI.debuggerManager.breakpointsDisabledTemporarily) WI.debuggerManager.breakpointsEnabled = true; // COMPATIBILITY (iOS 12.2): DOMDebugger.setXHRBreakpoint was replaced by DOMDebugger.setURLBreakpoint. if (!target.hasCommand("DOMDebugger.setURLBreakpoint")) { let isRegex = breakpoint.type === WI.URLBreakpoint.Type.RegularExpression; target.DOMDebuggerAgent.setXHRBreakpoint(breakpoint.url, isRegex); return; } target.DOMDebuggerAgent.setURLBreakpoint.invoke({ url: breakpoint.url, isRegex: breakpoint.type === WI.URLBreakpoint.Type.RegularExpression, options: breakpoint.optionsToProtocol(), }); } _removeURLBreakpoint(breakpoint, target) { // COMPATIBILITY (iOS 12.2): DOMDebugger.removeXHRBreakpoint was replaced by DOMDebugger.removeURLBreakpoint. if (!target.hasCommand("DOMDebugger.removeURLBreakpoint")) { target.DOMDebuggerAgent.removeXHRBreakpoint(breakpoint.url); return; } target.DOMDebuggerAgent.removeURLBreakpoint.invoke({ url: breakpoint.url, isRegex: breakpoint.type === WI.URLBreakpoint.Type.RegularExpression, }); } _handleDOMBreakpointDisabledStateChanged(event) { let breakpoint = event.target; if (!this._restoringBreakpoints) WI.objectStores.domBreakpoints.putObject(breakpoint); if (!breakpoint.domNode) return; // We should get the target associated with the nodeIdentifier of this breakpoint. let target = WI.assumingMainTarget(); if (target) { if (breakpoint.disabled) this._removeDOMBreakpoint(breakpoint, target); else this._setDOMBreakpoint(breakpoint, target); } } _handleDOMBreakpointEditablePropertyChanged(event) { let breakpoint = event.target; if (!this._restoringBreakpoints) WI.objectStores.domBreakpoints.putObject(breakpoint); if (!breakpoint.domNode) return; if (breakpoint.disabled) return; this._restoringBreakpoints = true; // We should get the target associated with the nodeIdentifier of this breakpoint. let target = WI.assumingMainTarget(); if (target) { // Clear the old breakpoint from the backend before setting the new one. this._removeDOMBreakpoint(breakpoint, target); this._setDOMBreakpoint(breakpoint, target); } this._restoringBreakpoints = false; } _handleDOMBreakpointActionsChanged(event) { let breakpoint = event.target; this._handleDOMBreakpointEditablePropertyChanged(event); if (!breakpoint.domNode) return; WI.debuggerManager.updateProbesForBreakpoint(breakpoint); } _handleDOMBreakpointDOMNodeWillChange(event) { if (this._clearingDOMBreakpointsForRemovedDOMNode) return; let breakpoint = event.target; if (!breakpoint.domNode) return; if (!breakpoint.disabled) { // We should get the target associated with the nodeIdentifier of this breakpoint. let target = WI.assumingMainTarget(); if (target) this._removeDOMBreakpoint(breakpoint, target); } WI.debuggerManager.removeProbesForBreakpoint(breakpoint); } _handleDOMBreakpointDOMNodeDidChange(event) { let breakpoint = event.target; if (!breakpoint.domNode) return; if (!breakpoint.disabled) { // We should get the target associated with the nodeIdentifier of this breakpoint. let target = WI.assumingMainTarget(); if (target) this._setDOMBreakpoint(breakpoint, target); } WI.debuggerManager.addProbesForBreakpoint(breakpoint); } _handleEventBreakpointDisabledStateChanged(event) { let breakpoint = event.target; // Specific event listener breakpoints are handled by `DOMManager`. if (breakpoint.eventListener) return; for (let target of WI.targets) { if (breakpoint.disabled) this._removeEventBreakpoint(breakpoint, target); else this._setEventBreakpoint(breakpoint, target); } if (!this._restoringBreakpoints) WI.objectStores.eventBreakpoints.putObject(breakpoint); } _handleEventBreakpointEditablePropertyChanged(event) { let breakpoint = event.target; // Specific event listener breakpoints are handled by `DOMManager`. if (breakpoint.eventListener) return; if (!this._restoringBreakpoints) WI.objectStores.eventBreakpoints.putObject(breakpoint); if (breakpoint.disabled) return; this._restoringBreakpoints = true; for (let target of WI.targets) { // Clear the old breakpoint from the backend before setting the new one. this._removeEventBreakpoint(breakpoint, target); this._setEventBreakpoint(breakpoint, target); } this._restoringBreakpoints = false; } _handleEventBreakpointActionsChanged(event) { let breakpoint = event.target; // Specific event listener breakpoints are handled by `DOMManager`. if (breakpoint.eventListener) return; this._handleEventBreakpointEditablePropertyChanged(event); WI.debuggerManager.updateProbesForBreakpoint(breakpoint); } _handleURLBreakpointDisabledStateChanged(event) { let breakpoint = event.target; for (let target of WI.targets) { if (breakpoint.disabled) this._removeURLBreakpoint(breakpoint, target); else this._setURLBreakpoint(breakpoint, target); } if (!this._restoringBreakpoints) WI.objectStores.urlBreakpoints.putObject(breakpoint); } _handleURLBreakpointEditablePropertyChanged(event) { let breakpoint = event.target; if (!this._restoringBreakpoints) WI.objectStores.urlBreakpoints.putObject(breakpoint); if (breakpoint.disabled) return; this._restoringBreakpoints = true; for (let target of WI.targets) { // Clear the old breakpoint from the backend before setting the new one. this._removeURLBreakpoint(breakpoint, target) this._setURLBreakpoint(breakpoint, target); } this._restoringBreakpoints = false; } _handleURLBreakpointActionsChanged(event) { let breakpoint = event.target; this._handleURLBreakpointEditablePropertyChanged(event); WI.debuggerManager.updateProbesForBreakpoint(breakpoint); } _childFrameWasRemoved(event) { let frame = event.data.childFrame; this._detachDOMBreakpointsForFrame(frame); } _mainFrameDidChange(event) { this._speculativelyResolveDOMBreakpointsForURL(WI.networkManager.mainFrame.url); } _mainResourceDidChange(event) { let frame = event.target; if (frame.isMainFrame()) { this._clearingDOMBreakpointsForRemovedDOMNode = true; for (let breakpoint of this._domBreakpointURLMap.values()) breakpoint.domNode = null; this._clearingDOMBreakpointsForRemovedDOMNode = false; this._domBreakpointFrameIdentifierMap.clear(); } else this._detachDOMBreakpointsForFrame(frame); this._speculativelyResolveDOMBreakpointsForURL(frame.url); } _nodeInserted(event) { let node = event.data.node; if (node.nodeType() !== Node.ELEMENT_NODE || !node.frame) return; let url = node.frame.url; let breakpoints = this._domBreakpointURLMap.get(url); if (!breakpoints) return; let resolvableBreakpoints = []; for (let breakpoint of breakpoints) { if (!breakpoint.domNode) resolvableBreakpoints.push(breakpoint); } if (!resolvableBreakpoints.length) return; // This is not very expensive because `WI.DOMNode` children are lazily populated, so it's // unlikely that there will be a deep subtree to walk. let stack = [node]; while (stack.length) { let child = stack.pop(); let path = child.path(); for (let i = resolvableBreakpoints.length - 1; i >= 0; --i) { if (resolvableBreakpoints[i].path === path) { this._restoringBreakpoints = true; this._resolveDOMBreakpoint(resolvableBreakpoints[i], child); this._restoringBreakpoints = false; resolvableBreakpoints.splice(i, 1); } } if (!resolvableBreakpoints.length) break; if (child.children?.length) stack.pushAll(child.children); } } _nodeRemoved(event) { let node = event.data.node; if (node.nodeType() !== Node.ELEMENT_NODE || !node.frame) return; let domBreakpointNodeIdentifierMap = this._domBreakpointFrameIdentifierMap.get(node.frame); if (!domBreakpointNodeIdentifierMap) return; for (let [breakpointOwner, breakpoints] of domBreakpointNodeIdentifierMap.sets()) { if (breakpointOwner == node || node.isAncestor(breakpointOwner)) { this._clearingDOMBreakpointsForRemovedDOMNode = true; for (let breakpoint of breakpoints) breakpoint.domNode = null; this._clearingDOMBreakpointsForRemovedDOMNode = false; domBreakpointNodeIdentifierMap.delete(breakpointOwner); if (!domBreakpointNodeIdentifierMap.size) { this._domBreakpointFrameIdentifierMap.delete(node.frame); break; } } } } }; WI.DOMDebuggerManager.Event = { DOMBreakpointAdded: "dom-debugger-manager-dom-breakpoint-added", DOMBreakpointRemoved: "dom-debugger-manager-dom-breakpoint-removed", EventBreakpointAdded: "dom-debugger-manager-event-breakpoint-added", EventBreakpointRemoved: "dom-debugger-manager-event-breakpoint-removed", URLBreakpointAdded: "dom-debugger-manager-url-breakpoint-added", URLBreakpointRemoved: "dom-debugger-manager-url-breakpoint-removed", }; /* Controllers/DOMManager.js */ /* * Copyright (C) 2009, 2010 Google Inc. All rights reserved. * Copyright (C) 2009 Joseph Pecoraro * 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: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 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. * * Neither the name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT * OWNER 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. */ // FIXME: DOMManager lacks advanced multi-target support. (DOMNodes per-target) WI.DOMManager = class DOMManager extends WI.Object { constructor() { super(); this._idToDOMNode = {}; this._document = null; this._documentPromise = null; this._attributeLoadNodeIds = {}; this._restoreSelectedNodeIsAllowed = true; this._loadNodeAttributesTimeout = 0; this._inspectedNode = null; this._breakpointsForEventListeners = new Map; this._hasRequestedDocument = false; this._pendingDocumentRequestCallbacks = null; WI.EventBreakpoint.addEventListener(WI.Breakpoint.Event.DisabledStateDidChange, this._handleEventBreakpointDisabledStateChanged, this); WI.EventBreakpoint.addEventListener(WI.Breakpoint.Event.ConditionDidChange, this._handleEventBreakpointEditablePropertyChanged, this); WI.EventBreakpoint.addEventListener(WI.Breakpoint.Event.IgnoreCountDidChange, this._handleEventBreakpointEditablePropertyChanged, this); WI.EventBreakpoint.addEventListener(WI.Breakpoint.Event.AutoContinueDidChange, this._handleEventBreakpointEditablePropertyChanged, this); WI.EventBreakpoint.addEventListener(WI.Breakpoint.Event.ActionsDidChange, this._handleEventBreakpointActionsChanged, this); WI.Frame.addEventListener(WI.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this); } // Target initializeTarget(target) { // FIXME: This should be improved when adding better DOM multi-target support since it is really per-target. // This currently uses a setTimeout since it doesn't need to happen immediately, and DOMManager uses the // global DOMAgent to request the document, so we want to make sure we've transitioned the global agents // to this target if necessary. if (target.hasDomain("DOM")) { setTimeout(() => { this.ensureDocument(); }); if (WI.engineeringSettingsAllowed()) { if (DOMManager.supportsEditingUserAgentShadowTrees({target})) target.DOMAgent.setAllowEditingUserAgentShadowTrees(WI.settings.engineeringAllowEditingUserAgentShadowTrees.value); } } } transitionPageTarget() { this._documentUpdated(); } // Static static buildHighlightConfigs(mode) { mode = mode || "all"; let commandArguments = { highlightConfig: {showInfo: mode === "all"}, }; if (mode === "all" || mode === "content") commandArguments.highlightConfig.contentColor = {r: 111, g: 168, b: 220, a: 0.66}; if (mode === "all" || mode === "padding") commandArguments.highlightConfig.paddingColor = {r: 147, g: 196, b: 125, a: 0.66}; if (mode === "all" || mode === "border") commandArguments.highlightConfig.borderColor = {r: 255, g: 229, b: 153, a: 0.66}; if (mode === "all" || mode === "margin") commandArguments.highlightConfig.marginColor = {r: 246, g: 178, b: 107, a: 0.66}; if (WI.settings.showGridOverlayDuringElementSelection.value) { commandArguments.gridOverlayConfig = { gridColor: WI.DOMNode.defaultLayoutOverlayColor.toProtocol(), showLineNames: WI.settings.gridOverlayShowLineNames.value, showLineNumbers: WI.settings.gridOverlayShowLineNumbers.value, showExtendedGridLines: WI.settings.gridOverlayShowExtendedGridLines.value, showTrackSizes: WI.settings.gridOverlayShowTrackSizes.value, showAreaNames: WI.settings.gridOverlayShowAreaNames.value, }; } if (WI.settings.showFlexOverlayDuringElementSelection.value) { commandArguments.flexOverlayConfig = { flexColor: WI.DOMNode.defaultLayoutOverlayColor.toProtocol(), showOrderNumbers: WI.settings.flexOverlayShowOrderNumbers.value, }; } if (WI.settings.showRulersDuringElementSelection.value) commandArguments.showRulers = true; return commandArguments; } static wrapClientCallback(callback) { if (!callback) return null; return function(error, result) { if (error) console.error("Error during DOMAgent operation: " + error); callback(error ? null : result); }; } static supportsEventListenerBreakpoints() { return InspectorBackend.hasCommand("DOM.setBreakpointForEventListener") && InspectorBackend.hasCommand("DOM.removeBreakpointForEventListener"); } static supportsEventListenerBreakpointConfiguration() { // COMPATIBILITY (iOS 14): DOM.setBreakpointForEventListener did not have an "options" parameter yet. return InspectorBackend.hasCommand("DOM.setBreakpointForEventListener", "options"); } static supportsEditingUserAgentShadowTrees({frontendOnly, target} = {}) { target = target || InspectorBackend; return WI.settings.engineeringAllowEditingUserAgentShadowTrees.value && (frontendOnly || target.hasCommand("DOM.setAllowEditingUserAgentShadowTrees")); } // Public get inspectedNode() { return this._inspectedNode; } get eventListenerBreakpoints() { return Array.from(this._breakpointsForEventListeners.values()); } *attachedNodes({filter} = {}) { if (!this._document) return; filter ??= (node) => true; // Traverse the node tree in the same order items would appear if the entire tree were expanded in order to // provide a predictable order for the results. let currentBranch = [this._document]; while (currentBranch.length) { let currentNode = currentBranch.at(-1); if (filter(currentNode)) yield currentNode; // The `::before` pseudo element is the first child of any node. let beforePseudoElement = currentNode.beforePseudoElement(); if (beforePseudoElement && filter(beforePseudoElement)) yield beforePseudoElement; let firstChild = currentNode.children?.[0]; if (firstChild) { currentBranch.push(firstChild); continue; } while (currentBranch.length) { let parent = currentBranch.pop(); // The `::after` pseudo element is the last child of any node. let parentAfterPseudoElement = parent.afterPseudoElement(); if (parentAfterPseudoElement && filter(parentAfterPseudoElement)) yield parentAfterPseudoElement; if (parent.nextSibling) { currentBranch.push(parent.nextSibling); break; } } } } requestDocument(callback) { if (typeof callback !== "function") return this._requestDocumentWithPromise(); this._requestDocumentWithCallback(callback); } ensureDocument() { this.requestDocument(function(){}); } pushNodeToFrontend(objectId, callback) { let target = WI.assumingMainTarget(); this._dispatchWhenDocumentAvailable((callbackWrapper) => { target.DOMAgent.requestNode(objectId, callbackWrapper); }, callback); } pushNodeByPathToFrontend(path, callback) { let target = WI.assumingMainTarget(); this._dispatchWhenDocumentAvailable((callbackWrapper) => { target.DOMAgent.pushNodeByPathToFrontend(path, callbackWrapper); }, callback); } // DOMObserver willDestroyDOMNode(nodeId) { let node = this._idToDOMNode[nodeId]; node.markDestroyed(); delete this._idToDOMNode[nodeId]; this.dispatchEventToListeners(WI.DOMManager.Event.NodeRemoved, {node}); } didAddEventListener(nodeId) { let node = this._idToDOMNode[nodeId]; if (!node) return; node.dispatchEventToListeners(WI.DOMNode.Event.EventListenersChanged); } willRemoveEventListener(nodeId) { let node = this._idToDOMNode[nodeId]; if (!node) return; node.dispatchEventToListeners(WI.DOMNode.Event.EventListenersChanged); } didFireEvent(nodeId, eventName, timestamp, data) { let node = this._idToDOMNode[nodeId]; if (!node) return; node.didFireEvent(eventName, timestamp, data); } powerEfficientPlaybackStateChanged(nodeId, timestamp, isPowerEfficient) { let node = this._idToDOMNode[nodeId]; if (!node) return; node.powerEfficientPlaybackStateChanged(timestamp, isPowerEfficient); } // CSSObserver nodeLayoutFlagsChanged(nodeId, layoutFlags) { let domNode = this._idToDOMNode[nodeId]; console.assert(domNode instanceof WI.DOMNode, domNode, nodeId); if (!domNode) return; domNode.layoutFlags = layoutFlags; } // Private _dispatchWhenDocumentAvailable(func, callback) { var callbackWrapper = DOMManager.wrapClientCallback(callback); function onDocumentAvailable() { if (this._document) func(callbackWrapper); else { if (callbackWrapper) callbackWrapper("No document"); } } this.requestDocument(onDocumentAvailable.bind(this)); } _requestDocumentWithPromise() { if (this._documentPromise) return this._documentPromise.promise; this._documentPromise = new WI.WrappedPromise; if (this._document) this._documentPromise.resolve(this._document); else { this._requestDocumentWithCallback((doc) => { this._documentPromise.resolve(doc); }); } return this._documentPromise.promise; } _requestDocumentWithCallback(callback) { if (this._document) { callback(this._document); return; } if (this._pendingDocumentRequestCallbacks) this._pendingDocumentRequestCallbacks.push(callback); else this._pendingDocumentRequestCallbacks = [callback]; if (this._hasRequestedDocument) return; if (!WI.pageTarget) return; if (!WI.pageTarget.hasDomain("DOM")) return; this._hasRequestedDocument = true; WI.pageTarget.DOMAgent.getDocument((error, root) => { if (!error) this._setDocument(root); for (let callback of this._pendingDocumentRequestCallbacks) callback(this._document); this._pendingDocumentRequestCallbacks = null; }); } _attributeModified(nodeId, name, value) { var node = this._idToDOMNode[nodeId]; if (!node) return; node._setAttribute(name, value); this.dispatchEventToListeners(WI.DOMManager.Event.AttributeModified, {node, name}); node.dispatchEventToListeners(WI.DOMNode.Event.AttributeModified, {name}); } _attributeRemoved(nodeId, name) { var node = this._idToDOMNode[nodeId]; if (!node) return; node._removeAttribute(name); this.dispatchEventToListeners(WI.DOMManager.Event.AttributeRemoved, {node, name}); node.dispatchEventToListeners(WI.DOMNode.Event.AttributeRemoved, {name}); } _inlineStyleInvalidated(nodeIds) { for (var nodeId of nodeIds) this._attributeLoadNodeIds[nodeId] = true; if (this._loadNodeAttributesTimeout) return; this._loadNodeAttributesTimeout = setTimeout(this._loadNodeAttributes.bind(this), 0); } _loadNodeAttributes() { function callback(nodeId, error, attributes) { if (error) { console.error("Error during DOMAgent operation: " + error); return; } var node = this._idToDOMNode[nodeId]; if (node) { node._setAttributesPayload(attributes); this.dispatchEventToListeners(WI.DOMManager.Event.AttributeModified, {node, name: "style"}); node.dispatchEventToListeners(WI.DOMNode.Event.AttributeModified, {name: "style"}); } } this._loadNodeAttributesTimeout = 0; let target = WI.assumingMainTarget(); for (var nodeId in this._attributeLoadNodeIds) { if (!(nodeId in this._idToDOMNode)) continue; var nodeIdAsNumber = parseInt(nodeId); target.DOMAgent.getAttributes(nodeIdAsNumber, callback.bind(this, nodeIdAsNumber)); } this._attributeLoadNodeIds = {}; } _characterDataModified(nodeId, newValue) { var node = this._idToDOMNode[nodeId]; node._nodeValue = newValue; this.dispatchEventToListeners(WI.DOMManager.Event.CharacterDataModified, {node}); } nodeForId(nodeId) { return this._idToDOMNode[nodeId] || null; } _documentUpdated() { this._setDocument(null); } _setDocument(payload) { for (let node of Object.values(this._idToDOMNode)) node.markDestroyed(); this._idToDOMNode = {}; for (let breakpoint of this._breakpointsForEventListeners.values()) WI.domDebuggerManager.dispatchEventToListeners(WI.DOMDebuggerManager.Event.EventBreakpointRemoved, {breakpoint}); this._breakpointsForEventListeners.clear(); let newDocument = null; if (payload && "nodeId" in payload) newDocument = new WI.DOMNode(this, null, false, payload); if (this._document === newDocument) return; this._document = newDocument; // Force the promise to be recreated so that it resolves to the new document. this._documentPromise = null; if (!this._document) this._hasRequestedDocument = false; this.dispatchEventToListeners(WI.DOMManager.Event.DocumentUpdated, {document: this._document}); } _setDetachedRoot(payload) { new WI.DOMNode(this, null, false, payload); } _setChildNodes(parentId, payloads) { if (!parentId && payloads.length) { this._setDetachedRoot(payloads[0]); return; } var parent = this._idToDOMNode[parentId]; if (parent.children) { for (let node of parent.children) this.dispatchEventToListeners(WI.DOMManager.Event.NodeRemoved, {node, parent}); } parent._setChildrenPayload(payloads); for (let node of parent.children) this.dispatchEventToListeners(WI.DOMManager.Event.NodeInserted, {node, parent}); } _childNodeCountUpdated(nodeId, newValue) { var node = this._idToDOMNode[nodeId]; node.childNodeCount = newValue; this.dispatchEventToListeners(WI.DOMManager.Event.ChildNodeCountUpdated, node); } _childNodeInserted(parentId, prevId, payload) { var parent = this._idToDOMNode[parentId]; var prev = this._idToDOMNode[prevId]; var node = parent._insertChild(prev, payload); this._idToDOMNode[node.id] = node; this.dispatchEventToListeners(WI.DOMManager.Event.NodeInserted, {node, parent}); } _childNodeRemoved(parentId, nodeId) { var parent = this._idToDOMNode[parentId]; var node = this._idToDOMNode[nodeId]; parent._removeChild(node); this._unbind(node); this.dispatchEventToListeners(WI.DOMManager.Event.NodeRemoved, {node, parent}); } _customElementStateChanged(elementId, newState) { const node = this._idToDOMNode[elementId]; node._customElementState = newState; this.dispatchEventToListeners(WI.DOMManager.Event.CustomElementStateChanged, {node}); } _pseudoElementAdded(parentId, pseudoElement) { var parent = this._idToDOMNode[parentId]; if (!parent) return; var node = new WI.DOMNode(this, parent.ownerDocument, false, pseudoElement); node.parentNode = parent; this._idToDOMNode[node.id] = node; console.assert(!parent.pseudoElements().get(node.pseudoType())); parent.pseudoElements().set(node.pseudoType(), node); this.dispatchEventToListeners(WI.DOMManager.Event.NodeInserted, {node, parent}); } _pseudoElementRemoved(parentId, pseudoElementId) { var pseudoElement = this._idToDOMNode[pseudoElementId]; if (!pseudoElement) return; var parent = pseudoElement.parentNode; console.assert(parent); console.assert(parent.id === parentId); if (!parent) return; parent._removeChild(pseudoElement); this._unbind(pseudoElement); this.dispatchEventToListeners(WI.DOMManager.Event.NodeRemoved, {node: pseudoElement, parent}); } _unbind(node) { node.markDestroyed(); delete this._idToDOMNode[node.id]; for (let i = 0; node.children && i < node.children.length; ++i) this._unbind(node.children[i]); let templateContent = node.templateContent(); if (templateContent) this._unbind(templateContent); for (let pseudoElement of node.pseudoElements().values()) this._unbind(pseudoElement); // FIXME: Handle shadow roots. } get restoreSelectedNodeIsAllowed() { return this._restoreSelectedNodeIsAllowed; } inspectElement(nodeId, options = {}) { var node = this._idToDOMNode[nodeId]; if (!node || !node.ownerDocument) return; // This code path is hit by "Reveal in DOM Tree" and clicking element links/console widgets. // Unless overridden by callers, assume that this is navigation is initiated by a Inspect mode. let initiatorHint = options.initiatorHint || WI.TabBrowser.TabNavigationInitiator.Inspect; this.dispatchEventToListeners(WI.DOMManager.Event.DOMNodeWasInspected, {node, initiatorHint}); this._inspectModeEnabled = false; this.dispatchEventToListeners(WI.DOMManager.Event.InspectModeStateChanged); } inspectNodeObject(remoteObject) { this._restoreSelectedNodeIsAllowed = false; function nodeAvailable(nodeId) { remoteObject.release(); console.assert(nodeId); if (!nodeId) return; this.inspectElement(nodeId); // Re-resolve the node in the console's object group when adding to the console. let domNode = this.nodeForId(nodeId); WI.RemoteObject.resolveNode(domNode, WI.RuntimeManager.ConsoleObjectGroup).then((remoteObject) => { WI.consoleLogViewController.appendImmediateExecutionWithResult(WI.UIString("Selected Element"), remoteObject, {addSpecialUserLogClass: true}); }); } remoteObject.pushNodeToFrontend(nodeAvailable.bind(this)); } highlightDOMNodeList(nodes, mode) { if (this._hideDOMNodeHighlightTimeout) { clearTimeout(this._hideDOMNodeHighlightTimeout); this._hideDOMNodeHighlightTimeout = undefined; } let nodeIds = []; for (let node of nodes) { console.assert(node instanceof WI.DOMNode, node); console.assert(!node.destroyed, node); if (node.destroyed) continue; nodeIds.push(node.id); } let target = WI.assumingMainTarget(); target.DOMAgent.highlightNodeList.invoke({ nodeIds, ...WI.DOMManager.buildHighlightConfigs(mode), }); } highlightSelector(selectorString, frameId, mode) { if (this._hideDOMNodeHighlightTimeout) { clearTimeout(this._hideDOMNodeHighlightTimeout); this._hideDOMNodeHighlightTimeout = undefined; } let target = WI.assumingMainTarget(); target.DOMAgent.highlightSelector.invoke({ selectorString, frameId, ...WI.DOMManager.buildHighlightConfigs(mode), }); } highlightRect(rect, usePageCoordinates) { let target = WI.assumingMainTarget(); target.DOMAgent.highlightRect.invoke({ x: rect.x, y: rect.y, width: rect.width, height: rect.height, color: {r: 111, g: 168, b: 220, a: 0.66}, outlineColor: {r: 255, g: 229, b: 153, a: 0.66}, usePageCoordinates }); } hideDOMNodeHighlight() { for (let target of WI.targets) { if (target.hasCommand("DOM.hideHighlight")) target.DOMAgent.hideHighlight(); } } highlightDOMNodeForTwoSeconds(nodeId) { let node = this._idToDOMNode[nodeId]; if (!node) return; node.highlight(); this._hideDOMNodeHighlightTimeout = setTimeout(this.hideDOMNodeHighlight.bind(this), 2000); } get inspectModeEnabled() { return this._inspectModeEnabled; } set inspectModeEnabled(enabled) { if (enabled === this._inspectModeEnabled) return; let target = WI.assumingMainTarget(); target.DOMAgent.setInspectModeEnabled.invoke({ enabled, ...WI.DOMManager.buildHighlightConfigs(), }, (error) => { if (error) { WI.reportInternalError(error); return; } this._inspectModeEnabled = enabled; this.dispatchEventToListeners(WI.DOMManager.Event.InspectModeStateChanged); }); } setInspectedNode(node) { console.assert(node instanceof WI.DOMNode); if (node === this._inspectedNode) return; console.assert(!node.destroyed, node); if (node.destroyed) return; let callback = (error) => { console.assert(!error, error); if (error) return; let lastInspectedNode = this._inspectedNode; this._inspectedNode = node; this.dispatchEventToListeners(WI.DOMManager.Event.InspectedNodeChanged, {lastInspectedNode}); }; let target = WI.assumingMainTarget(); target.DOMAgent.setInspectedNode(node.id, callback); } getSupportedEventNames(callback) { let target = WI.assumingMainTarget(); if (!target.hasCommand("DOM.getSupportedEventNames")) return Promise.resolve(new Set); if (!this._getSupportedEventNamesPromise) { this._getSupportedEventNamesPromise = target.DOMAgent.getSupportedEventNames() .then(({eventNames}) => new Set(eventNames)); } return this._getSupportedEventNamesPromise; } setEventListenerDisabled(eventListener, disabled) { let target = WI.assumingMainTarget(); target.DOMAgent.setEventListenerDisabled(eventListener.eventListenerId, disabled); } setBreakpointForEventListener(eventListener) { let breakpoint = this._breakpointsForEventListeners.get(eventListener.eventListenerId); if (breakpoint) { console.assert(breakpoint.disabled); breakpoint.disabled = false; return; } breakpoint = new WI.EventBreakpoint(WI.EventBreakpoint.Type.Listener, {eventName: eventListener.type, eventListener}); console.assert(!breakpoint.disabled); this._breakpointsForEventListeners.set(eventListener.eventListenerId, breakpoint); for (let target of WI.targets) { if (target.hasDomain("DOM")) this._setEventBreakpoint(breakpoint, target); } WI.debuggerManager.addProbesForBreakpoint(breakpoint); WI.domDebuggerManager.dispatchEventToListeners(WI.DOMDebuggerManager.Event.EventBreakpointAdded, {breakpoint}); } removeBreakpointForEventListener(eventListener) { let breakpoint = this._breakpointsForEventListeners.take(eventListener.eventListenerId); if (!breakpoint) return; // Disable the breakpoint first, so removing actions doesn't re-add the breakpoint. breakpoint.disabled = true; breakpoint.clearActions(); WI.debuggerManager.removeProbesForBreakpoint(breakpoint); WI.domDebuggerManager.dispatchEventToListeners(WI.DOMDebuggerManager.Event.EventBreakpointRemoved, {breakpoint}); } removeEventListenerBreakpointsForNode(domNode) { for (let breakpoint of Array.from(this._breakpointsForEventListeners.values())) { let eventListener = breakpoint.eventListener; if (eventListener.nodeId === domNode.id) this.removeBreakpointForEventListener(eventListener); } } breakpointForEventListenerId(eventListenerId) { return this._breakpointsForEventListeners.get(eventListenerId) || null; } // Private _setEventBreakpoint(breakpoint, target) { console.assert(!breakpoint.disabled, breakpoint); let eventListener = breakpoint.eventListener; console.assert(eventListener); if (!WI.debuggerManager.breakpointsDisabledTemporarily) WI.debuggerManager.breakpointsEnabled = true; target.DOMAgent.setBreakpointForEventListener.invoke({ eventListenerId: eventListener.eventListenerId, options: breakpoint.optionsToProtocol(), }); } _removeEventBreakpoint(breakpoint, target) { let eventListener = breakpoint.eventListener; console.assert(eventListener); target.DOMAgent.removeBreakpointForEventListener(eventListener.eventListenerId); } _handleEventBreakpointDisabledStateChanged(event) { let breakpoint = event.target; // Non-specific event listener breakpoints are handled by `DOMDebuggerManager`. if (!breakpoint.eventListener) return; for (let target of WI.targets) { if (!target.hasDomain("DOM")) continue; if (breakpoint.disabled) this._removeEventBreakpoint(breakpoint, target); else this._setEventBreakpoint(breakpoint, target); } } _handleEventBreakpointEditablePropertyChanged(event) { let breakpoint = event.target; // Non-specific event listener breakpoints are handled by `DOMDebuggerManager`. if (!breakpoint.eventListener) return; if (breakpoint.disabled) return; for (let target of WI.targets) { // Clear the old breakpoint from the backend before setting the new one. this._removeEventBreakpoint(breakpoint, target); this._setEventBreakpoint(breakpoint, target); } } _handleEventBreakpointActionsChanged(event) { let breakpoint = event.target; // Non-specific event listener breakpoints are handled by `DOMDebuggerManager`. if (!breakpoint.eventListener) return; this._handleEventBreakpointEditablePropertyChanged(event); WI.debuggerManager.updateProbesForBreakpoint(breakpoint); } _mainResourceDidChange(event) { if (!event.target.isMainFrame()) return; this._restoreSelectedNodeIsAllowed = true; this.ensureDocument(); WI.DOMNode.resetDefaultLayoutOverlayConfiguration(); } }; WI.DOMManager.Event = { AttributeModified: "dom-manager-attribute-modified", AttributeRemoved: "dom-manager-attribute-removed", CharacterDataModified: "dom-manager-character-data-modified", NodeInserted: "dom-manager-node-inserted", NodeRemoved: "dom-manager-node-removed", CustomElementStateChanged: "dom-manager-custom-element-state-changed", DocumentUpdated: "dom-manager-document-updated", ChildNodeCountUpdated: "dom-manager-child-node-count-updated", DOMNodeWasInspected: "dom-manager-dom-node-was-inspected", InspectModeStateChanged: "dom-manager-inspect-mode-state-changed", InspectedNodeChanged: "dom-manager-inspected-node-changed", }; /* Controllers/DOMStorageManager.js */ /* * Copyright (C) 2013 Apple Inc. All rights reserved. * Copyright (C) 2013 Samsung Electronics. 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. */ // FIXME: DOMStorageManager lacks advanced multi-target support. (DOMStorage per-target) WI.DOMStorageManager = class DOMStorageManager extends WI.Object { constructor() { super(); this._enabled = false; this._reset(); } // Agent get domains() { return ["DOMStorage"]; } activateExtraDomain(domain) { // COMPATIBILITY (iOS 14.0): Inspector.activateExtraDomains was removed in favor of a declared debuggable type console.assert(domain === "DOMStorage"); for (let target of WI.targets) this.initializeTarget(target); } // Target initializeTarget(target) { if (!this._enabled) return; if (target.hasDomain("DOMStorage")) target.DOMStorageAgent.enable(); } // Public get domStorageObjects() { return this._domStorageObjects; } get cookieStorageObjects() { var cookieStorageObjects = []; for (var host in this._cookieStorageObjects) cookieStorageObjects.push(this._cookieStorageObjects[host]); return cookieStorageObjects; } enable() { console.assert(!this._enabled); this._enabled = true; this._reset(); for (let target of WI.targets) this.initializeTarget(target); WI.Frame.addEventListener(WI.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this); WI.Frame.addEventListener(WI.Frame.Event.SecurityOriginDidChange, this._securityOriginDidChange, this); } disable() { console.assert(this._enabled); this._enabled = false; for (let target of WI.targets) { if (target.hasDomain("DOMStorage")) target.DOMStorageAgent.disable(); } WI.Frame.removeEventListener(WI.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this); WI.Frame.removeEventListener(WI.Frame.Event.SecurityOriginDidChange, this._securityOriginDidChange, this); this._reset(); } // DOMStorageObserver itemsCleared(storageId) { console.assert(this._enabled); let domStorage = this._domStorageForIdentifier(storageId); if (domStorage) domStorage.itemsCleared(storageId); } itemRemoved(storageId, key) { console.assert(this._enabled); let domStorage = this._domStorageForIdentifier(storageId); if (domStorage) domStorage.itemRemoved(key); } itemAdded(storageId, key, value) { console.assert(this._enabled); let domStorage = this._domStorageForIdentifier(storageId); if (domStorage) domStorage.itemAdded(key, value); } itemUpdated(storageId, key, oldValue, newValue) { console.assert(this._enabled); let domStorage = this._domStorageForIdentifier(storageId); if (domStorage) domStorage.itemUpdated(key, oldValue, newValue); } // InspectorObserver inspectDOMStorage(id) { console.assert(this._enabled); var domStorage = this._domStorageForIdentifier(id); console.assert(domStorage); if (!domStorage) return; this.dispatchEventToListeners(WI.DOMStorageManager.Event.DOMStorageObjectWasInspected, {domStorage}); } // Private _reset() { this._domStorageObjects = []; this._cookieStorageObjects = {}; this.dispatchEventToListeners(DOMStorageManager.Event.Cleared); let mainFrame = WI.networkManager.mainFrame; if (mainFrame) { this._addDOMStorageIfNeeded(mainFrame); this._addCookieStorageIfNeeded(mainFrame); } } _domStorageForIdentifier(id) { for (var storageObject of this._domStorageObjects) { // The id is an object, so we need to compare the properties using Object.shallowEqual. if (Object.shallowEqual(storageObject.id, id)) return storageObject; } return null; } _addDOMStorageIfNeeded(frame) { if (!this._enabled) return; if (!InspectorBackend.hasDomain("DOMStorage")) return; // Don't show storage if we don't have a security origin (about:blank). if (!frame.securityOrigin || frame.securityOrigin === "://") return; // FIXME: Consider passing the other parts of the origin along. let addDOMStorage = (isLocalStorage) => { let identifier = {securityOrigin: frame.securityOrigin, isLocalStorage}; if (this._domStorageForIdentifier(identifier)) return; let domStorage = new WI.DOMStorageObject(identifier, frame.mainResource.urlComponents.host, identifier.isLocalStorage); this._domStorageObjects.push(domStorage); this.dispatchEventToListeners(DOMStorageManager.Event.DOMStorageObjectWasAdded, {domStorage}); }; addDOMStorage(true); addDOMStorage(false); } _addCookieStorageIfNeeded(frame) { if (!this._enabled) return; if (!InspectorBackend.hasCommand("Page.getCookies")) return; // Add the host of the frame that changed the main resource to the list of hosts there could be cookies for. let host = parseURL(frame.url).host; if (!host) return; if (this._cookieStorageObjects[host]) return; this._cookieStorageObjects[host] = new WI.CookieStorageObject(host); this.dispatchEventToListeners(WI.DOMStorageManager.Event.CookieStorageObjectWasAdded, {cookieStorage: this._cookieStorageObjects[host]}); } _mainResourceDidChange(event) { console.assert(event.target instanceof WI.Frame); if (event.target.isMainFrame()) { this._reset(); return; } this._addCookieStorageIfNeeded(event.target); } _securityOriginDidChange(event) { console.assert(event.target instanceof WI.Frame); this._addDOMStorageIfNeeded(event.target); } }; WI.DOMStorageManager.Event = { CookieStorageObjectWasAdded: "dom-storage-manager-cookie-storage-object-was-added", DOMStorageObjectWasAdded: "dom-storage-manager-dom-storage-object-was-added", DOMStorageObjectWasInspected: "dom-storage-manager-dom-storage-object-was-inspected", Cleared: "dom-storage-manager-cleared", }; /* Controllers/DatabaseManager.js */ /* * Copyright (C) 2013 Apple Inc. All rights reserved. * Copyright (C) 2013 Samsung Electronics. 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. */ // FIXME: DatabaseManager lacks advanced multi-target support. (DataBase per-target) WI.DatabaseManager = class DatabaseManager extends WI.Object { constructor() { super(); this._enabled = false; this._reset(); } // Agent get domains() { return ["Database"]; } activateExtraDomain(domain) { // COMPATIBILITY (iOS 14.0): Inspector.activateExtraDomains was removed in favor of a declared debuggable type console.assert(domain === "Database"); for (let target of WI.targets) this.initializeTarget(target); } // Target initializeTarget(target) { if (!this._enabled) return; if (target.hasDomain("Database")) target.DatabaseAgent.enable(); } // Public get databases() { return this._databaseObjects; } enable() { console.assert(!this._enabled); this._enabled = true; this._reset(); for (let target of WI.targets) this.initializeTarget(target); WI.Frame.addEventListener(WI.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this); } disable() { console.assert(this._enabled); this._enabled = false; for (let target of WI.targets) { if (target.hasDomain("Database")) target.DatabaseAgent.disable(); } WI.Frame.removeEventListener(WI.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this); this._reset(); } // DatabaseObserver databaseWasAdded(id, host, name, version) { console.assert(this._enabled); var database = new WI.DatabaseObject(id, host, name, version); this._databaseObjects.push(database); this.dispatchEventToListeners(WI.DatabaseManager.Event.DatabaseWasAdded, {database}); } // InspectorObserver inspectDatabase(id) { console.assert(this._enabled); var database = this._databaseForIdentifier(id); console.assert(database); if (!database) return; this.dispatchEventToListeners(WI.DatabaseManager.Event.DatabaseWasInspected, {database}); } // Private _reset() { this._databaseObjects = []; this.dispatchEventToListeners(WI.DatabaseManager.Event.Cleared); } _mainResourceDidChange(event) { console.assert(event.target instanceof WI.Frame); if (event.target.isMainFrame()) this._reset(); } _databaseForIdentifier(id) { for (var i = 0; i < this._databaseObjects.length; ++i) { if (this._databaseObjects[i].id === id) return this._databaseObjects[i]; } return null; } }; WI.DatabaseManager.Event = { DatabaseWasAdded: "database-manager-database-was-added", DatabaseWasInspected: "database-manager-database-was-inspected", Cleared: "database-manager-cleared", }; /* Controllers/DebuggerManager.js */ /* * Copyright (C) 2013-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.DebuggerManager = class DebuggerManager extends WI.Object { constructor() { super(); WI.JavaScriptBreakpoint.addEventListener(WI.Breakpoint.Event.DisabledStateDidChange, this._breakpointDisabledStateDidChange, this); WI.JavaScriptBreakpoint.addEventListener(WI.Breakpoint.Event.ConditionDidChange, this._breakpointEditablePropertyDidChange, this); WI.JavaScriptBreakpoint.addEventListener(WI.Breakpoint.Event.IgnoreCountDidChange, this._breakpointEditablePropertyDidChange, this); WI.JavaScriptBreakpoint.addEventListener(WI.Breakpoint.Event.AutoContinueDidChange, this._breakpointEditablePropertyDidChange, this); WI.JavaScriptBreakpoint.addEventListener(WI.Breakpoint.Event.ActionsDidChange, this._handleBreakpointActionsDidChange, this); WI.JavaScriptBreakpoint.addEventListener(WI.JavaScriptBreakpoint.Event.DisplayLocationDidChange, this._breakpointDisplayLocationDidChange, this); WI.SymbolicBreakpoint.addEventListener(WI.Breakpoint.Event.DisabledStateDidChange, this._handleSymbolicBreakpointDisabledStateChanged, this); WI.SymbolicBreakpoint.addEventListener(WI.Breakpoint.Event.ConditionDidChange, this._handleSymbolicBreakpointEditablePropertyChanged, this); WI.SymbolicBreakpoint.addEventListener(WI.Breakpoint.Event.IgnoreCountDidChange, this._handleSymbolicBreakpointEditablePropertyChanged, this); WI.SymbolicBreakpoint.addEventListener(WI.Breakpoint.Event.AutoContinueDidChange, this._handleSymbolicBreakpointEditablePropertyChanged, this); WI.SymbolicBreakpoint.addEventListener(WI.Breakpoint.Event.ActionsDidChange, this._handleSymbolicBreakpointActionsChanged, this); WI.timelineManager.addEventListener(WI.TimelineManager.Event.CapturingStateChanged, this._handleTimelineCapturingStateChanged, this); WI.auditManager.addEventListener(WI.AuditManager.Event.TestScheduled, this._handleAuditManagerTestScheduled, this); WI.auditManager.addEventListener(WI.AuditManager.Event.TestCompleted, this._handleAuditManagerTestCompleted, this); WI.targetManager.addEventListener(WI.TargetManager.Event.TargetRemoved, this._targetRemoved, this); WI.settings.blackboxBreakpointEvaluations.addEventListener(WI.Setting.Event.Changed, this._handleBlackboxBreakpointEvaluationsChange, this); if (WI.engineeringSettingsAllowed()) { WI.settings.engineeringShowInternalScripts.addEventListener(WI.Setting.Event.Changed, this._handleEngineeringShowInternalScriptsSettingChanged, this); WI.settings.engineeringPauseForInternalScripts.addEventListener(WI.Setting.Event.Changed, this._handleEngineeringPauseForInternalScriptsSettingChanged, this); } WI.Frame.addEventListener(WI.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this); this._breakpointsEnabledSetting = new WI.Setting("breakpoints-enabled", true); this._asyncStackTraceDepthSetting = new WI.Setting("async-stack-trace-depth", 200); this._debuggerStatementsBreakpointSetting = new WI.Setting("debugger-statements-breakpoint", {}); this._debuggerStatementsBreakpoint = null; this._allExceptionsBreakpointSetting = new WI.Setting("all-exceptions-breakpoint", {disabled: true}); this._allExceptionsBreakpoint = null; this._uncaughtExceptionsBreakpointSetting = new WI.Setting("uncaught-exceptions-breakpoint", {disabled: true}); this._uncaughtExceptionsBreakpoint = null; this._assertionFailuresBreakpointSetting = new WI.Setting("assertion-failures-breakpoint", null); if (WI.Setting.isFirstLaunch) this._assertionFailuresBreakpointSetting.value = {disabled: true}; this._assertionFailuresBreakpoint = null; this._allMicrotasksBreakpointSetting = new WI.Setting("all-microtasks-breakpoint", null); this._allMicrotasksBreakpoint = null; this._breakpoints = []; this._breakpointContentIdentifierMap = new Multimap; this._breakpointScriptIdentifierMap = new Multimap; this._breakpointIdMap = new Map; this._symbolicBreakpoints = []; this._nextBreakpointActionIdentifier = 1; this._blackboxedURLsSetting = new WI.Setting("debugger-blackboxed-urls", []); this._blackboxedPatternsSetting = new WI.Setting("debugger-blackboxed-patterns", []); this._blackboxedPatternDataMap = new Map; this._blackboxedCallFrameGroupsToAutoExpand = []; this._activeCallFrame = null; this._internalWebKitScripts = []; this._targetDebuggerDataMap = new Map; // Used to detect deleted probe actions. this._knownProbeIdentifiersForBreakpoint = new Map; // Main lookup tables for probes and probe sets. this._probesByIdentifier = new Map; this._probeSetsByBreakpoint = new Map; // Restore the correct breakpoints enabled setting if Web Inspector had // previously been left in a state where breakpoints were temporarily disabled. this._temporarilyDisabledBreakpointsRestoreSetting = new WI.Setting("temporarily-disabled-breakpoints-restore", null); if (this._temporarilyDisabledBreakpointsRestoreSetting.value !== null) { this._breakpointsEnabledSetting.value = this._temporarilyDisabledBreakpointsRestoreSetting.value; this._temporarilyDisabledBreakpointsRestoreSetting.value = null; } this._temporarilyDisableBreakpointsRequestCount = 0; this._ignoreBreakpointDisplayLocationDidChangeEvent = false; WI.Target.registerInitializationPromise((async () => { let existingSerializedBreakpoints = WI.Setting.migrateValue("breakpoints"); if (existingSerializedBreakpoints) { for (let existingSerializedBreakpoint of existingSerializedBreakpoints) await WI.objectStores.breakpoints.putObject(WI.JavaScriptBreakpoint.fromJSON(existingSerializedBreakpoint)); } let serializedBreakpoints = await WI.objectStores.breakpoints.getAll(); this._restoringBreakpoints = true; for (let serializedBreakpoint of serializedBreakpoints) { let breakpoint = WI.JavaScriptBreakpoint.fromJSON(serializedBreakpoint); const key = null; WI.objectStores.breakpoints.associateObject(breakpoint, key, serializedBreakpoint); this.addBreakpoint(breakpoint); } this._restoringBreakpoints = false; })()); if (WI.SymbolicBreakpoint.supported()) { WI.Target.registerInitializationPromise((async () => { let serializedSymbolicBreakpoints = await WI.objectStores.symbolicBreakpoints.getAll(); this._restoringBreakpoints = true; for (let serializedSymbolicBreakpoint of serializedSymbolicBreakpoints) { let symbolicBreakpoint = WI.SymbolicBreakpoint.fromJSON(serializedSymbolicBreakpoint); const key = null; WI.objectStores.symbolicBreakpoints.associateObject(symbolicBreakpoint, key, serializedSymbolicBreakpoint); this.addSymbolicBreakpoint(symbolicBreakpoint); } this._restoringBreakpoints = false; })()); } WI.Target.registerInitializationPromise((async () => { // Wait one microtask so that `WI.debuggerManager` can be initialized. await new Promise((resolve, reject) => queueMicrotask(resolve)); let loadSpecialBreakpoint = (setting, enabledSettingsKey, shownSettingsKey) => { let serializedBreakpoint = setting.value; if (!serializedBreakpoint && (!shownSettingsKey || WI.Setting.migrateValue(shownSettingsKey))) { serializedBreakpoint = setting.value = {}; setting.save(); } if (WI.Setting.migrateValue(enabledSettingsKey)) { if (!serializedBreakpoint) serializedBreakpoint = setting.value = {}; serializedBreakpoint.disabled = false; setting.save(); } if (!serializedBreakpoint) return null; return this._createSpecialBreakpoint(serializedBreakpoint); }; this._restoringBreakpoints = true; if (WI.JavaScriptBreakpoint.supportsDebuggerStatements()) { this._debuggerStatementsBreakpoint = loadSpecialBreakpoint(this._debuggerStatementsBreakpointSetting, "break-on-debugger-statements"); if (this._debuggerStatementsBreakpoint) this.addBreakpoint(this._debuggerStatementsBreakpoint); } this._allExceptionsBreakpoint = loadSpecialBreakpoint(this._allExceptionsBreakpointSetting, "break-on-all-exceptions"); if (this._allExceptionsBreakpoint) this.addBreakpoint(this._allExceptionsBreakpoint); this._uncaughtExceptionsBreakpoint = loadSpecialBreakpoint(this._uncaughtExceptionsBreakpointSetting, "break-on-uncaught-exceptions"); if (this._uncaughtExceptionsBreakpoint) this.addBreakpoint(this._uncaughtExceptionsBreakpoint); this._assertionFailuresBreakpoint = loadSpecialBreakpoint(this._assertionFailuresBreakpointSetting, "break-on-assertion-failures", "show-assertion-failures-breakpoint"); if (this._assertionFailuresBreakpoint) this.addBreakpoint(this._assertionFailuresBreakpoint); if (WI.JavaScriptBreakpoint.supportsMicrotasks()) { this._allMicrotasksBreakpoint = loadSpecialBreakpoint(this._allMicrotasksBreakpointSetting, "break-on-all-microtasks", "show-all-microtasks-breakpoint"); if (this._allMicrotasksBreakpoint) this.addBreakpoint(this._allMicrotasksBreakpoint); } this._restoringBreakpoints = false; })()); } // Target initializeTarget(target) { let targetData = this.dataForTarget(target); // Initialize global state. target.DebuggerAgent.enable(); target.DebuggerAgent.setBreakpointsActive(this._breakpointsEnabledSetting.value); if (WI.SymbolicBreakpoint.supported(target)) { for (let breakpoint of this._symbolicBreakpoints) { if (!breakpoint.disabled) this._setSymbolicBreakpoint(breakpoint, target); } } if (this._debuggerStatementsBreakpoint) this._updateSpecialBreakpoint(this._debuggerStatementsBreakpoint, target); this._setPauseOnExceptions(target); if (this._assertionFailuresBreakpoint) this._updateSpecialBreakpoint(this._assertionFailuresBreakpoint, target); if (this._allMicrotasksBreakpoint) this._updateSpecialBreakpoint(this._allMicrotasksBreakpoint, target); target.DebuggerAgent.setAsyncStackTraceDepth(this._asyncStackTraceDepthSetting.value); // COMPATIBILITY (iOS 13): Debugger.setShouldBlackboxURL did not exist yet. if (target.hasCommand("Debugger.setShouldBlackboxURL")) { const shouldBlackbox = true; { const caseSensitive = true; for (let url of this._blackboxedURLsSetting.value) target.DebuggerAgent.setShouldBlackboxURL(url, shouldBlackbox, caseSensitive); } { const isRegex = true; for (let data of this._blackboxedPatternsSetting.value) { this._blackboxedPatternDataMap.set(new RegExp(data.url, !data.caseSensitive ? "i" : ""), data); target.DebuggerAgent.setShouldBlackboxURL(data.url, shouldBlackbox, data.caseSensitive, isRegex); } } } this._setBlackboxBreakpointEvaluations(target); if (WI.engineeringSettingsAllowed()) { // COMPATIBILITY (iOS 12): DebuggerAgent.setPauseForInternalScripts did not exist yet. if (target.hasCommand("Debugger.setPauseForInternalScripts")) target.DebuggerAgent.setPauseForInternalScripts(WI.settings.engineeringPauseForInternalScripts.value); } if (this.paused) targetData.pauseIfNeeded(); // Initialize breakpoints. this._restoringBreakpoints = true; for (let breakpoint of this._breakpoints) { if (breakpoint.disabled) continue; if (!breakpoint.contentIdentifier) continue; this._setBreakpoint(breakpoint, target); } this._restoringBreakpoints = false; } // Static static supportsBlackboxingScripts() { // COMPATIBILITY (iOS 13): Debugger.setShouldBlackboxURL did not exist yet. return InspectorBackend.hasCommand("Debugger.setShouldBlackboxURL"); } static supportsBlackboxingBreakpointEvaluations() { // COMPATIBILITY (macOS 12.3, iOS 15.4): Debugger.setBlackboxBreakpointEvaluations did not exist yet. return InspectorBackend.hasCommand("Debugger.setBlackboxBreakpointEvaluations"); } static pauseReasonFromPayload(payload) { switch (payload) { case InspectorBackend.Enum.Debugger.PausedReason.AnimationFrame: return WI.DebuggerManager.PauseReason.AnimationFrame; case InspectorBackend.Enum.Debugger.PausedReason.Assert: return WI.DebuggerManager.PauseReason.Assertion; case InspectorBackend.Enum.Debugger.PausedReason.BlackboxedScript: return WI.DebuggerManager.PauseReason.BlackboxedScript; case InspectorBackend.Enum.Debugger.PausedReason.Breakpoint: return WI.DebuggerManager.PauseReason.Breakpoint; case InspectorBackend.Enum.Debugger.PausedReason.CSPViolation: return WI.DebuggerManager.PauseReason.CSPViolation; case InspectorBackend.Enum.Debugger.PausedReason.DOM: return WI.DebuggerManager.PauseReason.DOM; case InspectorBackend.Enum.Debugger.PausedReason.DebuggerStatement: return WI.DebuggerManager.PauseReason.DebuggerStatement; case InspectorBackend.Enum.Debugger.PausedReason.EventListener: return WI.DebuggerManager.PauseReason.EventListener; case InspectorBackend.Enum.Debugger.PausedReason.Exception: return WI.DebuggerManager.PauseReason.Exception; case InspectorBackend.Enum.Debugger.PausedReason.FunctionCall: return WI.DebuggerManager.PauseReason.FunctionCall; case InspectorBackend.Enum.Debugger.PausedReason.Interval: return WI.DebuggerManager.PauseReason.Interval; case InspectorBackend.Enum.Debugger.PausedReason.Listener: return WI.DebuggerManager.PauseReason.Listener; case InspectorBackend.Enum.Debugger.PausedReason.Microtask: return WI.DebuggerManager.PauseReason.Microtask; case InspectorBackend.Enum.Debugger.PausedReason.PauseOnNextStatement: return WI.DebuggerManager.PauseReason.PauseOnNextStatement; case InspectorBackend.Enum.Debugger.PausedReason.Timeout: return WI.DebuggerManager.PauseReason.Timeout; case InspectorBackend.Enum.Debugger.PausedReason.Timer: return WI.DebuggerManager.PauseReason.Timer; case InspectorBackend.Enum.Debugger.PausedReason.URL: case InspectorBackend.Enum.Debugger.PausedReason.Fetch: // COMPATIBILITY (macOS 13.0, iOS 16.0): Debugger.paused.reason.Fetch was replaced by Debugger.paused.reason.URL case InspectorBackend.Enum.Debugger.PausedReason.XHR: // COMPATIBILITY (macOS 13.0, iOS 16.0): Debugger.paused.reason.XHR was replaced by Debugger.paused.reason.URL return WI.DebuggerManager.PauseReason.URL; default: return WI.DebuggerManager.PauseReason.Other; } } // Public get paused() { for (let [target, targetData] of this._targetDebuggerDataMap) { if (targetData.paused) return true; } return false; } get activeCallFrame() { return this._activeCallFrame; } set activeCallFrame(callFrame) { if (callFrame === this._activeCallFrame) return; this._activeCallFrame = callFrame || null; this.dispatchEventToListeners(WI.DebuggerManager.Event.ActiveCallFrameDidChange); } dataForTarget(target) { let targetData = this._targetDebuggerDataMap.get(target); if (targetData) return targetData; targetData = new WI.DebuggerData(target); this._targetDebuggerDataMap.set(target, targetData); return targetData; } get debuggerStatementsBreakpoint() { return this._debuggerStatementsBreakpoint; } get allExceptionsBreakpoint() { return this._allExceptionsBreakpoint; } get uncaughtExceptionsBreakpoint() { return this._uncaughtExceptionsBreakpoint; } get assertionFailuresBreakpoint() { return this._assertionFailuresBreakpoint; } get allMicrotasksBreakpoint() { return this._allMicrotasksBreakpoint; } get breakpoints() { return this._breakpoints; } createAssertionFailuresBreakpoint(options = {}) { console.assert(!this._assertionFailuresBreakpoint); this._assertionFailuresBreakpoint = this._createSpecialBreakpoint(options); this.addBreakpoint(this._assertionFailuresBreakpoint); } createAllMicrotasksBreakpoint(options = {}) { console.assert(!this._allMicrotasksBreakpoint); this._allMicrotasksBreakpoint = this._createSpecialBreakpoint(options); this.addBreakpoint(this._allMicrotasksBreakpoint); } breakpointForIdentifier(id) { return this._breakpointIdMap.get(id) || null; } breakpointsForSourceCode(sourceCode) { console.assert(sourceCode instanceof WI.Resource || sourceCode instanceof WI.Script); if (sourceCode instanceof WI.SourceMapResource) return Array.from(this.breakpointsForSourceCode(sourceCode.sourceMap.originalSourceCode)).filter((breakpoint) => breakpoint.sourceCodeLocation.displaySourceCode === sourceCode); let contentIdentifierBreakpoints = this._breakpointContentIdentifierMap.get(sourceCode.contentIdentifier); if (contentIdentifierBreakpoints) { this._associateBreakpointsWithSourceCode(contentIdentifierBreakpoints, sourceCode); return contentIdentifierBreakpoints; } if (sourceCode instanceof WI.Script) { let scriptIdentifierBreakpoints = this._breakpointScriptIdentifierMap.get(sourceCode.id); if (scriptIdentifierBreakpoints) { this._associateBreakpointsWithSourceCode(scriptIdentifierBreakpoints, sourceCode); return scriptIdentifierBreakpoints; } } return []; } breakpointsForSourceCodeLocation(sourceCodeLocation) { console.assert(sourceCodeLocation instanceof WI.SourceCodeLocation, sourceCodeLocation); return this.breakpointsForSourceCode(sourceCodeLocation.sourceCode) .filter((breakpoint) => breakpoint.hasResolvedLocation(sourceCodeLocation)); } breakpointForSourceCodeLocation(sourceCodeLocation) { console.assert(sourceCodeLocation instanceof WI.SourceCodeLocation); for (let breakpoint of this.breakpointsForSourceCode(sourceCodeLocation.sourceCode)) { if (breakpoint.sourceCodeLocation.isEqual(sourceCodeLocation)) return breakpoint; } return null; } symbolicBreakpointsForSymbol(symbol) { console.assert(WI.SymbolicBreakpoint.supported()); // Order symbolic breakpoints based on how closely they match the given symbol. As an example, // a regular expression is likely going to match more symbols than a case-insensitive string. const rankFunctions = [ (breakpoint) => breakpoint.caseSensitive && !breakpoint.isRegex, // exact match (breakpoint) => !breakpoint.caseSensitive && !breakpoint.isRegex, // case-insensitive (breakpoint) => breakpoint.caseSensitive && breakpoint.isRegex, // case-sensitive regex (breakpoint) => !breakpoint.caseSensitive && breakpoint.isRegex, // case-insensitive regex ]; return this._symbolicBreakpoints .filter((breakpoint) => breakpoint.matches(symbol)) .sort((a, b) => { let aRank = rankFunctions.findIndex((rankFunction) => rankFunction(a)); let bRank = rankFunctions.findIndex((rankFunction) => rankFunction(b)); return aRank - bRank; }); } get breakpointsEnabled() { return this._breakpointsEnabledSetting.value; } set breakpointsEnabled(enabled) { if (this._breakpointsEnabledSetting.value === enabled) return; console.assert(!(enabled && this.breakpointsDisabledTemporarily), "Should not enable breakpoints when we are temporarily disabling breakpoints."); if (enabled && this.breakpointsDisabledTemporarily) return; this._breakpointsEnabledSetting.value = enabled; for (let target of WI.targets) { target.DebuggerAgent.setBreakpointsActive(enabled); this._setPauseOnExceptions(target); } this.dispatchEventToListeners(WI.DebuggerManager.Event.BreakpointsEnabledDidChange); } get breakpointsDisabledTemporarily() { return this._temporarilyDisabledBreakpointsRestoreSetting.value !== null; } scriptForIdentifier(id, target) { console.assert(target instanceof WI.Target); return this.dataForTarget(target).scriptForIdentifier(id); } scriptsForURL(url, target) { // FIXME: This may not be safe. A Resource's URL may differ from a Script's URL. console.assert(target instanceof WI.Target); return this.dataForTarget(target).scriptsForURL(url); } get searchableScripts() { return this.knownNonResourceScripts.filter((script) => !!script.contentIdentifier); } get knownNonResourceScripts() { let knownScripts = []; for (let targetData of this._targetDebuggerDataMap.values()) { for (let script of targetData.scripts) { if (script.resource) continue; if (!WI.settings.debugShowConsoleEvaluations.value && isWebInspectorConsoleEvaluationScript(script.sourceURL)) continue; if (!WI.settings.engineeringShowInternalScripts.value && isWebKitInternalScript(script.sourceURL)) continue; knownScripts.push(script); } } return knownScripts; } blackboxDataForSourceCode(sourceCode) { for (let regex of this._blackboxedPatternDataMap.keys()) { if (regex.test(sourceCode.contentIdentifier)) return {type: DebuggerManager.BlackboxType.Pattern, regex}; } if (this._blackboxedURLsSetting.value.includes(sourceCode.contentIdentifier)) return {type: DebuggerManager.BlackboxType.URL}; return null; } get blackboxPatterns() { return Array.from(this._blackboxedPatternDataMap.keys()); } setShouldBlackboxScript(sourceCode, shouldBlackbox) { console.assert(DebuggerManager.supportsBlackboxingScripts()); console.assert(sourceCode instanceof WI.SourceCode); console.assert(sourceCode.contentIdentifier); console.assert(!isWebKitInjectedScript(sourceCode.contentIdentifier)); console.assert(shouldBlackbox !== ((this.blackboxDataForSourceCode(sourceCode) || {}).type === DebuggerManager.BlackboxType.URL)); this._blackboxedURLsSetting.value.toggleIncludes(sourceCode.contentIdentifier, shouldBlackbox); this._blackboxedURLsSetting.save(); const caseSensitive = true; for (let target of WI.targets) { // COMPATIBILITY (iOS 13): Debugger.setShouldBlackboxURL did not exist yet. if (target.hasCommand("Debugger.setShouldBlackboxURL")) target.DebuggerAgent.setShouldBlackboxURL(sourceCode.contentIdentifier, !!shouldBlackbox, caseSensitive); } this.dispatchEventToListeners(DebuggerManager.Event.BlackboxChanged); } setShouldBlackboxPattern(regex, shouldBlackbox) { console.assert(DebuggerManager.supportsBlackboxingScripts()); console.assert(regex instanceof RegExp); if (shouldBlackbox) { console.assert(!this._blackboxedPatternDataMap.has(regex)); let data = { url: regex.source, caseSensitive: !regex.ignoreCase, }; this._blackboxedPatternDataMap.set(regex, data); this._blackboxedPatternsSetting.value.push(data); } else { console.assert(this._blackboxedPatternDataMap.has(regex)); this._blackboxedPatternsSetting.value.remove(this._blackboxedPatternDataMap.take(regex)); } this._blackboxedPatternsSetting.save(); const isRegex = true; for (let target of WI.targets) { // COMPATIBILITY (iOS 13): Debugger.setShouldBlackboxURL did not exist yet. if (target.hasCommand("Debugger.setShouldBlackboxURL")) target.DebuggerAgent.setShouldBlackboxURL(regex.source, !!shouldBlackbox, !regex.ignoreCase, isRegex); } this.dispatchEventToListeners(DebuggerManager.Event.BlackboxChanged); } rememberBlackboxedCallFrameGroupToAutoExpand(blackboxedCallFrameGroup) { console.assert(!this.shouldAutoExpandBlackboxedCallFrameGroup(blackboxedCallFrameGroup), blackboxedCallFrameGroup); this._blackboxedCallFrameGroupsToAutoExpand.push(blackboxedCallFrameGroup); } shouldAutoExpandBlackboxedCallFrameGroup(blackboxedCallFrameGroup) { console.assert(Array.isArray(blackboxedCallFrameGroup) && blackboxedCallFrameGroup.length && blackboxedCallFrameGroup.every((callFrame) => callFrame instanceof WI.CallFrame && callFrame.blackboxed), blackboxedCallFrameGroup); return this._blackboxedCallFrameGroupsToAutoExpand.some((blackboxedCallFrameGroupToAutoExpand) => { if (blackboxedCallFrameGroupToAutoExpand.length !== blackboxedCallFrameGroup.length) return false; return blackboxedCallFrameGroupToAutoExpand.every((item, i) => item.isEqual(blackboxedCallFrameGroup[i])); }); } get asyncStackTraceDepth() { return this._asyncStackTraceDepthSetting.value; } set asyncStackTraceDepth(x) { if (this._asyncStackTraceDepthSetting.value === x) return; this._asyncStackTraceDepthSetting.value = x; for (let target of WI.targets) target.DebuggerAgent.setAsyncStackTraceDepth(this._asyncStackTraceDepthSetting.value); } get probeSets() { return [...this._probeSetsByBreakpoint.values()]; } probeForIdentifier(identifier) { return this._probesByIdentifier.get(identifier); } pause() { if (this.paused) return Promise.resolve(); this.dispatchEventToListeners(WI.DebuggerManager.Event.WaitingToPause); let promises = [this.awaitEvent(WI.DebuggerManager.Event.Paused, this)]; for (let targetData of this._targetDebuggerDataMap.values()) promises.push(targetData.pauseIfNeeded()); return Promise.all(promises); } resume() { if (!this.paused) return Promise.resolve(); let promises = [this.awaitEvent(WI.DebuggerManager.Event.Resumed, this)]; for (let targetData of this._targetDebuggerDataMap.values()) promises.push(targetData.resumeIfNeeded()); return Promise.all(promises); } stepNext() { if (!this.paused) return Promise.reject(new Error("Cannot step next because debugger is not paused.")); return Promise.all([ this.awaitEvent(WI.DebuggerManager.Event.ActiveCallFrameDidChange, this), this._activeCallFrame.target.DebuggerAgent.stepNext(), ]); } stepOver() { if (!this.paused) return Promise.reject(new Error("Cannot step over because debugger is not paused.")); return Promise.all([ this.awaitEvent(WI.DebuggerManager.Event.ActiveCallFrameDidChange, this), this._activeCallFrame.target.DebuggerAgent.stepOver(), ]); } stepInto() { if (!this.paused) return Promise.reject(new Error("Cannot step into because debugger is not paused.")); return Promise.all([ this.awaitEvent(WI.DebuggerManager.Event.ActiveCallFrameDidChange, this), this._activeCallFrame.target.DebuggerAgent.stepInto(), ]); } stepOut() { if (!this.paused) return Promise.reject(new Error("Cannot step out because debugger is not paused.")); return Promise.all([ this.awaitEvent(WI.DebuggerManager.Event.ActiveCallFrameDidChange, this), this._activeCallFrame.target.DebuggerAgent.stepOut(), ]); } continueUntilNextRunLoop(target) { return this.dataForTarget(target).continueUntilNextRunLoop(); } continueToLocation(script, lineNumber, columnNumber) { return script.target.DebuggerAgent.continueToLocation({scriptId: script.id, lineNumber, columnNumber}); } addBreakpoint(breakpoint) { console.assert(breakpoint instanceof WI.JavaScriptBreakpoint, breakpoint); if (!breakpoint) return; if (breakpoint.special) this._updateSpecialBreakpoint(breakpoint); else { if (breakpoint.contentIdentifier) this._breakpointContentIdentifierMap.add(breakpoint.contentIdentifier, breakpoint); if (breakpoint.scriptIdentifier) this._breakpointScriptIdentifierMap.add(breakpoint.scriptIdentifier, breakpoint); this._breakpoints.push(breakpoint); if (!breakpoint.disabled) this._setBreakpoint(breakpoint); if (!this._restoringBreakpoints) WI.objectStores.breakpoints.putObject(breakpoint); } this.addProbesForBreakpoint(breakpoint); this.dispatchEventToListeners(WI.DebuggerManager.Event.BreakpointAdded, {breakpoint}); } removeBreakpoint(breakpoint) { console.assert(breakpoint instanceof WI.JavaScriptBreakpoint, breakpoint); if (!breakpoint) return; console.assert(breakpoint.removable, breakpoint); if (!breakpoint.removable) return; let special = breakpoint.special; if (!special) { this._breakpoints.remove(breakpoint); if (breakpoint.identifier) this._removeBreakpoint(breakpoint); if (breakpoint.contentIdentifier) this._breakpointContentIdentifierMap.delete(breakpoint.contentIdentifier, breakpoint); if (breakpoint.scriptIdentifier) this._breakpointScriptIdentifierMap.delete(breakpoint.scriptIdentifier, breakpoint); } // Disable the breakpoint first, so removing actions doesn't re-add the breakpoint. breakpoint.disabled = true; breakpoint.clearActions(); if (special) { switch (breakpoint) { case this._assertionFailuresBreakpoint: this._assertionFailuresBreakpointSetting.reset(); this._assertionFailuresBreakpoint = null; break; case this._allMicrotasksBreakpoint: this._allMicrotasksBreakpointSetting.reset(); this._allMicrotasksBreakpoint = null; break; } } else if (!this._restoringBreakpoints) WI.objectStores.breakpoints.deleteObject(breakpoint); this.removeProbesForBreakpoint(breakpoint); this.dispatchEventToListeners(WI.DebuggerManager.Event.BreakpointRemoved, {breakpoint}); } addSymbolicBreakpoint(breakpoint) { console.assert(WI.SymbolicBreakpoint.supported()); console.assert(breakpoint instanceof WI.SymbolicBreakpoint, breakpoint); console.assert(!breakpoint.special, breakpoint); if (this._symbolicBreakpoints.some((existingBreakpoint) => existingBreakpoint.equals(breakpoint))) return false; this._symbolicBreakpoints.push(breakpoint); if (!breakpoint.disabled) { for (let target of WI.targets) this._setSymbolicBreakpoint(breakpoint, target); } if (!this._restoringBreakpoints) WI.objectStores.symbolicBreakpoints.putObject(breakpoint); this.addProbesForBreakpoint(breakpoint); this.dispatchEventToListeners(WI.DebuggerManager.Event.SymbolicBreakpointAdded, {breakpoint}); return true; } removeSymbolicBreakpoint(breakpoint) { console.assert(WI.SymbolicBreakpoint.supported()); console.assert(breakpoint instanceof WI.SymbolicBreakpoint, breakpoint); console.assert(breakpoint.removable, breakpoint); console.assert(!breakpoint.special, breakpoint); // Disable the breakpoint first, so removing actions doesn't re-add the breakpoint. breakpoint.disabled = true; breakpoint.clearActions(); this._symbolicBreakpoints.remove(breakpoint); if (!this._restoringBreakpoints) WI.objectStores.symbolicBreakpoints.deleteObject(breakpoint); this.removeProbesForBreakpoint(breakpoint); this.dispatchEventToListeners(WI.DebuggerManager.Event.SymbolicBreakpointRemoved, {breakpoint}); } nextBreakpointActionIdentifier() { return this._nextBreakpointActionIdentifier++; } addProbesForBreakpoint(breakpoint) { if (this._knownProbeIdentifiersForBreakpoint.has(breakpoint)) return; this._knownProbeIdentifiersForBreakpoint.set(breakpoint, new Set); this.updateProbesForBreakpoint(breakpoint); } removeProbesForBreakpoint(breakpoint) { console.assert(this._knownProbeIdentifiersForBreakpoint.has(breakpoint)); this.updateProbesForBreakpoint(breakpoint); this._knownProbeIdentifiersForBreakpoint.delete(breakpoint); } updateProbesForBreakpoint(breakpoint) { let knownProbeIdentifiers = this._knownProbeIdentifiersForBreakpoint.get(breakpoint); if (!knownProbeIdentifiers) { // Sometimes actions change before the added breakpoint is fully dispatched. this.addProbesForBreakpoint(breakpoint); return; } let seenProbeIdentifiers = new Set; for (let probeAction of breakpoint.probeActions) { let probeIdentifier = probeAction.id; console.assert(probeIdentifier, "Probe added without breakpoint action identifier: ", breakpoint); seenProbeIdentifiers.add(probeIdentifier); if (!knownProbeIdentifiers.has(probeIdentifier)) { // New probe; find or create relevant probe set. knownProbeIdentifiers.add(probeIdentifier); let probeSet = this._probeSetForBreakpoint(breakpoint); let newProbe = new WI.Probe(probeIdentifier, breakpoint, probeAction.data); this._probesByIdentifier.set(probeIdentifier, newProbe); probeSet.addProbe(newProbe); break; } let probe = this._probesByIdentifier.get(probeIdentifier); console.assert(probe, "Probe known but couldn't be found by identifier: ", probeIdentifier); // Update probe expression; if it differed, change events will fire. probe.expression = probeAction.data; } // Look for missing probes based on what we saw last. for (let probeIdentifier of knownProbeIdentifiers) { if (seenProbeIdentifiers.has(probeIdentifier)) break; // The probe has gone missing, remove it. let probeSet = this._probeSetForBreakpoint(breakpoint); let probe = this._probesByIdentifier.get(probeIdentifier); this._probesByIdentifier.delete(probeIdentifier); knownProbeIdentifiers.delete(probeIdentifier); probeSet.removeProbe(probe); // Remove the probe set if it has become empty. if (!probeSet.probes.length) { this._probeSetsByBreakpoint.delete(probeSet.breakpoint); probeSet.willRemove(); this.dispatchEventToListeners(WI.DebuggerManager.Event.ProbeSetRemoved, {probeSet}); } } } // DebuggerObserver breakpointResolved(target, breakpointIdentifier, location) { let breakpoint = this._breakpointIdMap.get(breakpointIdentifier); console.assert(breakpoint); if (!breakpoint) return; console.assert(breakpoint.identifier === breakpointIdentifier); let sourceCodeLocation = this._sourceCodeLocationFromPayload(target, location); if (!breakpoint.sourceCodeLocation.sourceCode) breakpoint.sourceCodeLocation.sourceCode = sourceCodeLocation.sourceCode; breakpoint.addResolvedLocation(sourceCodeLocation); } globalObjectCleared(target) { let wasPaused = this.paused; WI.Script.resetUniqueDisplayNameNumbers(target); this._internalWebKitScripts = []; this._targetDebuggerDataMap.clear(); this._ignoreBreakpointDisplayLocationDidChangeEvent = true; // Mark all the breakpoints as unresolved. They will be reported as resolved when // breakpointResolved is called as the page loads. for (let breakpoint of this._breakpoints) { breakpoint.clearResolvedLocations(); if (breakpoint.sourceCodeLocation.sourceCode) breakpoint.sourceCodeLocation.sourceCode = null; } this._ignoreBreakpointDisplayLocationDidChangeEvent = false; this.dispatchEventToListeners(WI.DebuggerManager.Event.ScriptsCleared); if (wasPaused) this.dispatchEventToListeners(WI.DebuggerManager.Event.Resumed); } debuggerDidPause(target, callFramesPayload, reason, data, asyncStackTracePayload) { if (this._delayedResumeTimeout) { clearTimeout(this._delayedResumeTimeout); this._delayedResumeTimeout = undefined; } let wasPaused = this.paused; let targetData = this._targetDebuggerDataMap.get(target); let callFrames = []; let pauseReason = DebuggerManager.pauseReasonFromPayload(reason); let pauseData = data || null; for (var i = 0; i < callFramesPayload.length; ++i) { var callFramePayload = callFramesPayload[i]; var sourceCodeLocation = this._sourceCodeLocationFromPayload(target, callFramePayload.location); // FIXME: There may be useful call frames without a source code location (native callframes), should we include them? if (!sourceCodeLocation) continue; if (!sourceCodeLocation.sourceCode) continue; // Exclude the case where the call frame is in the inspector code. if (!WI.settings.engineeringShowInternalScripts.value && isWebKitInternalScript(sourceCodeLocation.sourceCode.sourceURL)) continue; let scopeChain = this._scopeChainFromPayload(target, callFramePayload.scopeChain); let callFrame = WI.CallFrame.fromDebuggerPayload(target, callFramePayload, scopeChain, sourceCodeLocation); callFrames.push(callFrame); } let activeCallFrame = callFrames[0]; if (!activeCallFrame) { // FIXME: This may not be safe for multiple threads/targets. // This indicates we were pausing in internal scripts only (Injected Scripts). // Just resume and skip past this pause. We should be fixing the backend to // not send such pauses. if (wasPaused) target.DebuggerAgent.continueUntilNextRunLoop(); else target.DebuggerAgent.resume(); this._didResumeInternal(target); return; } let stackTrace = new WI.StackTrace(callFrames, { parentStackTrace: WI.StackTrace.fromPayload(target, asyncStackTracePayload), }); targetData.updateForPause(stackTrace, pauseReason, pauseData); // Pause other targets because at least one target has paused. // FIXME: Should this be done on the backend? for (let [otherTarget, otherTargetData] of this._targetDebuggerDataMap) otherTargetData.pauseIfNeeded(); let activeCallFrameDidChange = this._activeCallFrame && this._activeCallFrame.target === target; if (activeCallFrameDidChange) this._activeCallFrame = activeCallFrame; else if (!wasPaused) { this._activeCallFrame = activeCallFrame; activeCallFrameDidChange = true; } if (!wasPaused) this.dispatchEventToListeners(WI.DebuggerManager.Event.Paused); this.dispatchEventToListeners(WI.DebuggerManager.Event.CallFramesDidChange, {target}); if (activeCallFrameDidChange) this.dispatchEventToListeners(WI.DebuggerManager.Event.ActiveCallFrameDidChange); } debuggerDidResume(target) { this._didResumeInternal(target); } playBreakpointActionSound(breakpointActionIdentifier) { InspectorFrontendHost.beep(); } scriptDidParse(target, scriptIdentifier, url, startLine, startColumn, endLine, endColumn, isModule, isContentScript, sourceURL, sourceMapURL) { // Don't add the script again if it is already known. let targetData = this.dataForTarget(target); let existingScript = targetData.scriptForIdentifier(scriptIdentifier); if (existingScript) { console.assert(existingScript.url === (url || null)); console.assert(existingScript.range.startLine === startLine); console.assert(existingScript.range.startColumn === startColumn); console.assert(existingScript.range.endLine === endLine); console.assert(existingScript.range.endColumn === endColumn); return; } if (!WI.settings.engineeringShowInternalScripts.value && isWebKitInternalScript(sourceURL)) return; let range = new WI.TextRange(startLine, startColumn, endLine, endColumn); let sourceType = isModule ? WI.Script.SourceType.Module : WI.Script.SourceType.Program; let script = new WI.Script(target, scriptIdentifier, range, url, sourceType, isContentScript, sourceURL, sourceMapURL); targetData.addScript(script); // FIXME: Web Inspector: WorkerTarget's mainResource should be a Resource not a Script // We make the main resource of a WorkerTarget the Script instead of the Resource // because the frontend may not be informed of the Resource. We should guarantee // the frontend is informed of the Resource. if (WI.sharedApp.debuggableType === WI.DebuggableType.ServiceWorker) { // A ServiceWorker starts with a LocalScript for the main resource but we can replace it during initialization. if (target.mainResource instanceof WI.LocalScript) { if (script.url === target.name) target.mainResource = script; } } else if (!target.mainResource && target !== WI.mainTarget) { // A Worker starts without a main resource and we insert one. if (script.url === target.name) { target.mainResource = script; if (script.resource) target.resourceCollection.remove(script.resource); } } if (isWebKitInternalScript(script.sourceURL)) { this._internalWebKitScripts.push(script); if (!WI.settings.engineeringShowInternalScripts.value) return; } if (!WI.settings.debugShowConsoleEvaluations.value && isWebInspectorConsoleEvaluationScript(script.sourceURL)) return; this.dispatchEventToListeners(WI.DebuggerManager.Event.ScriptAdded, {script}); if ((target !== WI.mainTarget || WI.sharedApp.debuggableType === WI.DebuggableType.ServiceWorker) && !script.isMainResource() && !script.resource) target.addScript(script); } scriptDidFail(target, url, scriptSource) { const sourceURL = null; const sourceType = WI.Script.SourceType.Program; let script = new WI.LocalScript(target, url, sourceURL, sourceType, scriptSource); // If there is already a resource we don't need to have the script anymore, // we only need a script to use for parser error location links. if (script.resource) return; let targetData = this.dataForTarget(target); targetData.addScript(script); target.addScript(script); this.dispatchEventToListeners(WI.DebuggerManager.Event.ScriptAdded, {script}); } didSampleProbe(target, sample) { console.assert(this._probesByIdentifier.has(sample.probeId), "Unknown probe identifier specified for sample: ", sample); let probe = this._probesByIdentifier.get(sample.probeId); let elapsedTime = WI.timelineManager.computeElapsedTime(sample.timestamp); let object = WI.RemoteObject.fromPayload(sample.payload, target); probe.addSample(new WI.ProbeSample(sample.sampleId, sample.batchId, elapsedTime, object)); } // Private _sourceCodeLocationFromPayload(target, payload) { let targetData = this.dataForTarget(target); let script = targetData.scriptForIdentifier(payload.scriptId); if (!script) return null; return script.createSourceCodeLocation(payload.lineNumber, payload.columnNumber); } _scopeChainFromPayload(target, payload) { let scopeChain = []; for (let i = 0; i < payload.length; ++i) scopeChain.push(this._scopeChainNodeFromPayload(target, payload[i])); return scopeChain; } _scopeChainNodeFromPayload(target, payload) { var type = null; switch (payload.type) { case InspectorBackend.Enum.Debugger.ScopeType.Global: type = WI.ScopeChainNode.Type.Global; break; case InspectorBackend.Enum.Debugger.ScopeType.With: type = WI.ScopeChainNode.Type.With; break; case InspectorBackend.Enum.Debugger.ScopeType.Closure: type = WI.ScopeChainNode.Type.Closure; break; case InspectorBackend.Enum.Debugger.ScopeType.Catch: type = WI.ScopeChainNode.Type.Catch; break; case InspectorBackend.Enum.Debugger.ScopeType.FunctionName: type = WI.ScopeChainNode.Type.FunctionName; break; case InspectorBackend.Enum.Debugger.ScopeType.NestedLexical: type = WI.ScopeChainNode.Type.Block; break; case InspectorBackend.Enum.Debugger.ScopeType.GlobalLexicalEnvironment: type = WI.ScopeChainNode.Type.GlobalLexicalEnvironment; break; default: console.error("Unknown type: " + payload.type); break; } let object = WI.RemoteObject.fromPayload(payload.object, target); return new WI.ScopeChainNode(type, [object], payload.name, payload.location, payload.empty); } _setBreakpoint(breakpoint, specificTarget) { console.assert(!breakpoint.disabled); if (breakpoint.disabled) return; if (!this._restoringBreakpoints && !this.breakpointsDisabledTemporarily) { // Enable breakpoints since a breakpoint is being set. This eliminates // a multi-step process for the user that can be confusing. this.breakpointsEnabled = true; } function didSetBreakpoint(target, error, breakpointIdentifier, locations) { if (error) { WI.reportInternalError(error); return; } this._breakpointIdMap.set(breakpointIdentifier, breakpoint); breakpoint.identifier = breakpointIdentifier; // Debugger.setBreakpoint returns a single location. if (!(locations instanceof Array)) locations = [locations]; for (let location of locations) this.breakpointResolved(target, breakpointIdentifier, location); } // The breakpoint will be resolved again by calling DebuggerAgent, so mark it as unresolved. // If something goes wrong it will stay unresolved and show up as such in the user interface. // When setting for a new target, don't change the resolved target. if (!specificTarget) breakpoint.clearResolvedLocations(); if (breakpoint.contentIdentifier) { let targets = specificTarget ? [specificTarget] : WI.targets; for (let target of targets) { target.DebuggerAgent.setBreakpointByUrl.invoke({ lineNumber: breakpoint.sourceCodeLocation.lineNumber, url: breakpoint.contentIdentifier, urlRegex: undefined, columnNumber: breakpoint.sourceCodeLocation.columnNumber, options: breakpoint.optionsToProtocol(), }, didSetBreakpoint.bind(this, target)); } } else if (breakpoint.scriptIdentifier) { let target = breakpoint.target; target.DebuggerAgent.setBreakpoint.invoke({ location: {scriptId: breakpoint.scriptIdentifier, lineNumber: breakpoint.sourceCodeLocation.lineNumber, columnNumber: breakpoint.sourceCodeLocation.columnNumber}, options: breakpoint.optionsToProtocol(), }, didSetBreakpoint.bind(this, target)); } else WI.reportInternalError("Unknown source for breakpoint."); } _removeBreakpoint(breakpoint, callback) { if (!breakpoint.identifier) return; function didRemoveBreakpoint(target, error) { if (error) { WI.reportInternalError(error); return; } this._breakpointIdMap.delete(breakpoint.identifier); breakpoint.identifier = null; // Don't reset resolved here since we want to keep disabled breakpoints looking like they // are resolved in the user interface. They will get marked as unresolved in reset. if (callback) callback(target); } if (breakpoint.contentIdentifier) { for (let target of WI.targets) target.DebuggerAgent.removeBreakpoint(breakpoint.identifier, didRemoveBreakpoint.bind(this, target)); } else if (breakpoint.scriptIdentifier) { let target = breakpoint.target; target.DebuggerAgent.removeBreakpoint(breakpoint.identifier, didRemoveBreakpoint.bind(this, target)); } } _setSymbolicBreakpoint(breakpoint, target) { console.assert(breakpoint instanceof WI.SymbolicBreakpoint, breakpoint); console.assert(!breakpoint.disabled, breakpoint); console.assert(WI.SymbolicBreakpoint.supported(target), target); if (!this._restoringBreakpoints && !this.breakpointsDisabledTemporarily) this.breakpointsEnabled = true; target.DebuggerAgent.addSymbolicBreakpoint.invoke({ symbol: breakpoint.symbol, caseSensitive: breakpoint.caseSensitive, isRegex: breakpoint.isRegex, options: breakpoint.optionsToProtocol(), }); } _removeSymbolicBreakpoint(breakpoint, target) { console.assert(breakpoint instanceof WI.SymbolicBreakpoint, breakpoint); console.assert(WI.SymbolicBreakpoint.supported(target), target); target.DebuggerAgent.removeSymbolicBreakpoint.invoke({ symbol: breakpoint.symbol, caseSensitive: breakpoint.caseSensitive, isRegex: breakpoint.isRegex, }); } _setPauseOnExceptions(target) { let commandArguments = { state: "none", }; if (this._breakpointsEnabledSetting.value) { if (this._allExceptionsBreakpoint && !this._allExceptionsBreakpoint.disabled) { commandArguments.state = "all"; commandArguments.options = this._allExceptionsBreakpoint.optionsToProtocol(); } else if (this._uncaughtExceptionsBreakpoint && !this._uncaughtExceptionsBreakpoint.disabled) { commandArguments.state = "uncaught"; commandArguments.options = this._uncaughtExceptionsBreakpoint.optionsToProtocol(); } // Mark the uncaught breakpoint as unresolved if "all" as it includes "uncaught". // That way it is clear in the user interface that the breakpoint is ignored. if (this._uncaughtExceptionsBreakpoint) this._uncaughtExceptionsBreakpoint.resolved = commandArguments.state !== "all"; } target.DebuggerAgent.setPauseOnExceptions.invoke(commandArguments); } _createSpecialBreakpoint(serializedBreakpoint = {}) { let location = WI.SourceCodeLocation.specialBreakpointLocation; serializedBreakpoint.lineNumber = location.lineNumber; serializedBreakpoint.columnNumber = location.columnNumber; serializedBreakpoint.resolved = true; return WI.JavaScriptBreakpoint.fromJSON(serializedBreakpoint); } _updateSpecialBreakpoint(breakpoint, specificTarget) { console.assert(breakpoint.special, breakpoint); if (!breakpoint.disabled && !this._restoringBreakpoints && !this.breakpointsDisabledTemporarily) this.breakpointsEnabled = true; let targets = specificTarget ? [specificTarget] : WI.targets; let setting = null; let command = null; switch (breakpoint) { case this._debuggerStatementsBreakpoint: setting = this._debuggerStatementsBreakpointSetting; command = "setPauseOnDebuggerStatements"; break; case this._allExceptionsBreakpoint: setting = this._allExceptionsBreakpointSetting; break; case this._uncaughtExceptionsBreakpoint: setting = this._uncaughtExceptionsBreakpointSetting; break; case this._assertionFailuresBreakpoint: setting = this._assertionFailuresBreakpointSetting; command = "setPauseOnAssertions"; break; case this._allMicrotasksBreakpoint: setting = this._allMicrotasksBreakpointSetting; command = "setPauseOnMicrotasks"; break; } console.assert(setting); if (!specificTarget) setting.value = breakpoint.toJSON(); if (command) { let commandArguments = { enabled: !breakpoint.disabled, }; if (!breakpoint.disabled) commandArguments.options = breakpoint.optionsToProtocol(); for (let target of targets) target.DebuggerAgent[command].invoke(commandArguments); } else { console.assert(breakpoint === this._allExceptionsBreakpoint || breakpoint === this._uncaughtExceptionsBreakpoint, breakpoint); for (let target of targets) this._setPauseOnExceptions(target); } } _setBlackboxBreakpointEvaluations(target) { // COMPATIBILITY (macOS 12.3, iOS 15.4): Debugger.setBlackboxBreakpointEvaluations did not exist yet. if (target.hasCommand("Debugger.setBlackboxBreakpointEvaluations")) target.DebuggerAgent.setBlackboxBreakpointEvaluations(WI.settings.blackboxBreakpointEvaluations.value); } _breakpointDisplayLocationDidChange(event) { if (this._ignoreBreakpointDisplayLocationDidChangeEvent) return; let breakpoint = event.target; if (!breakpoint.identifier || breakpoint.disabled) return; // Remove the breakpoint with its old id. this._removeBreakpoint(breakpoint, (target) => { // Add the breakpoint at its new lineNumber and get a new id. this._restoringBreakpoints = true; this._setBreakpoint(breakpoint, target); this._restoringBreakpoints = false; this.dispatchEventToListeners(WI.DebuggerManager.Event.BreakpointMoved, {breakpoint}); }); } _breakpointDisabledStateDidChange(event) { let breakpoint = event.target; if (breakpoint.special) { this._updateSpecialBreakpoint(breakpoint); return; } if (!this._restoringBreakpoints) WI.objectStores.breakpoints.putObject(breakpoint); if (breakpoint.disabled) this._removeBreakpoint(breakpoint); else this._setBreakpoint(breakpoint); } _breakpointEditablePropertyDidChange(event) { let breakpoint = event.target; if (breakpoint.special) { this._restoringBreakpoints = true; this._updateSpecialBreakpoint(breakpoint); this._restoringBreakpoints = false; return; } if (!this._restoringBreakpoints) WI.objectStores.breakpoints.putObject(breakpoint); if (breakpoint.disabled) return; console.assert(breakpoint.editable); if (!breakpoint.editable) return; // Remove the breakpoint with its old id. this._removeBreakpoint(breakpoint, (target) => { // Add the breakpoint with its new properties and get a new id. this._restoringBreakpoints = true; this._setBreakpoint(breakpoint, target); this._restoringBreakpoints = false; }); } _handleBreakpointActionsDidChange(event) { this._breakpointEditablePropertyDidChange(event); this.updateProbesForBreakpoint(event.target); } _handleSymbolicBreakpointDisabledStateChanged(event) { let breakpoint = event.target; for (let target of WI.targets) { if (breakpoint.disabled) this._removeSymbolicBreakpoint(breakpoint, target); else this._setSymbolicBreakpoint(breakpoint, target); } if (!this._restoringBreakpoints) WI.objectStores.symbolicBreakpoints.putObject(breakpoint); } _handleSymbolicBreakpointEditablePropertyChanged(event) { let breakpoint = event.target; if (!this._restoringBreakpoints) WI.objectStores.symbolicBreakpoints.putObject(breakpoint); if (breakpoint.disabled) return; this._restoringBreakpoints = true; for (let target of WI.targets) { // Clear the old breakpoint from the backend before setting the new one. this._removeSymbolicBreakpoint(breakpoint, target); this._setSymbolicBreakpoint(breakpoint, target); } this._restoringBreakpoints = false; } _handleSymbolicBreakpointActionsChanged(event) { let breakpoint = event.target; this._handleSymbolicBreakpointEditablePropertyChanged(event); this.updateProbesForBreakpoint(breakpoint); } _startDisablingBreakpointsTemporarily() { if (++this._temporarilyDisableBreakpointsRequestCount > 1) return; console.assert(!this.breakpointsDisabledTemporarily, "Already temporarily disabling breakpoints."); if (this.breakpointsDisabledTemporarily) return; this._temporarilyDisabledBreakpointsRestoreSetting.value = this._breakpointsEnabledSetting.value; this.breakpointsEnabled = false; } _stopDisablingBreakpointsTemporarily() { this._temporarilyDisableBreakpointsRequestCount = Math.max(0, this._temporarilyDisableBreakpointsRequestCount - 1); if (this._temporarilyDisableBreakpointsRequestCount > 0) return; console.assert(this.breakpointsDisabledTemporarily, "Was not temporarily disabling breakpoints."); if (!this.breakpointsDisabledTemporarily) return; let restoreState = this._temporarilyDisabledBreakpointsRestoreSetting.value; this._temporarilyDisabledBreakpointsRestoreSetting.value = null; this.breakpointsEnabled = restoreState; } _handleTimelineCapturingStateChanged(event) { switch (WI.timelineManager.capturingState) { case WI.TimelineManager.CapturingState.Starting: this._startDisablingBreakpointsTemporarily(); if (this.paused) this.resume(); break; case WI.TimelineManager.CapturingState.Inactive: this._stopDisablingBreakpointsTemporarily(); break; } } _handleAuditManagerTestScheduled(event) { this._startDisablingBreakpointsTemporarily(); if (this.paused) this.resume(); } _handleAuditManagerTestCompleted(event) { this._stopDisablingBreakpointsTemporarily(); } _targetRemoved(event) { let wasPaused = this.paused; this._targetDebuggerDataMap.delete(event.data.target); if (!this.paused && wasPaused) this.dispatchEventToListeners(WI.DebuggerManager.Event.Resumed); } _handleBlackboxBreakpointEvaluationsChange(event) { for (let target of WI.targets) this._setBlackboxBreakpointEvaluations(target); } _handleEngineeringShowInternalScriptsSettingChanged(event) { let eventType = WI.settings.engineeringShowInternalScripts.value ? WI.DebuggerManager.Event.ScriptAdded : WI.DebuggerManager.Event.ScriptRemoved; for (let script of this._internalWebKitScripts) this.dispatchEventToListeners(eventType, {script}); } _handleEngineeringPauseForInternalScriptsSettingChanged(event) { for (let target of WI.targets) { if (target.hasCommand("Debugger.setPauseForInternalScripts")) target.DebuggerAgent.setPauseForInternalScripts(WI.settings.engineeringPauseForInternalScripts.value); } } _mainResourceDidChange(event) { if (!event.target.isMainFrame()) return; this._didResumeInternal(WI.mainTarget); } _didResumeInternal(target) { if (!this.paused) return; if (this._delayedResumeTimeout) { clearTimeout(this._delayedResumeTimeout); this._delayedResumeTimeout = undefined; } let activeCallFrameDidChange = false; if (this._activeCallFrame && this._activeCallFrame.target === target) { this._activeCallFrame = null; activeCallFrameDidChange = true; } this._blackboxedCallFrameGroupsToAutoExpand = []; this.dataForTarget(target).updateForResume(); if (!this.paused) this.dispatchEventToListeners(WI.DebuggerManager.Event.Resumed); this.dispatchEventToListeners(WI.DebuggerManager.Event.CallFramesDidChange, {target}); if (activeCallFrameDidChange) this.dispatchEventToListeners(WI.DebuggerManager.Event.ActiveCallFrameDidChange); } _associateBreakpointsWithSourceCode(breakpoints, sourceCode) { this._ignoreBreakpointDisplayLocationDidChangeEvent = true; for (let breakpoint of breakpoints) { if (!breakpoint.sourceCodeLocation.sourceCode) breakpoint.sourceCodeLocation.sourceCode = sourceCode; // SourceCodes can be unequal if the SourceCodeLocation is associated with a Script and we are looking at the Resource. console.assert(breakpoint.sourceCodeLocation.sourceCode === sourceCode || breakpoint.sourceCodeLocation.sourceCode.contentIdentifier === sourceCode.contentIdentifier); } this._ignoreBreakpointDisplayLocationDidChangeEvent = false; } _probeSetForBreakpoint(breakpoint) { let probeSet = this._probeSetsByBreakpoint.get(breakpoint); if (!probeSet) { probeSet = new WI.ProbeSet(breakpoint); this._probeSetsByBreakpoint.set(breakpoint, probeSet); this.dispatchEventToListeners(WI.DebuggerManager.Event.ProbeSetAdded, {probeSet}); } return probeSet; } }; WI.DebuggerManager.Event = { BreakpointAdded: "debugger-manager-breakpoint-added", BreakpointRemoved: "debugger-manager-breakpoint-removed", BreakpointMoved: "debugger-manager-breakpoint-moved", SymbolicBreakpointAdded: "debugger-manager-symbolic-breakpoint-added", SymbolicBreakpointRemoved: "debugger-manager-symbolic-breakpoint-removed", WaitingToPause: "debugger-manager-waiting-to-pause", Paused: "debugger-manager-paused", Resumed: "debugger-manager-resumed", CallFramesDidChange: "debugger-manager-call-frames-did-change", ActiveCallFrameDidChange: "debugger-manager-active-call-frame-did-change", ScriptAdded: "debugger-manager-script-added", ScriptRemoved: "debugger-manager-script-removed", ScriptsCleared: "debugger-manager-scripts-cleared", BreakpointsEnabledDidChange: "debugger-manager-breakpoints-enabled-did-change", ProbeSetAdded: "debugger-manager-probe-set-added", ProbeSetRemoved: "debugger-manager-probe-set-removed", BlackboxChanged: "blackboxed-urls-changed", }; WI.DebuggerManager.PauseReason = { AnimationFrame: "animation-frame", Assertion: "assertion", BlackboxedScript: "blackboxed-script", Breakpoint: "breakpoint", CSPViolation: "CSP-violation", DebuggerStatement: "debugger-statement", DOM: "DOM", Exception: "exception", FunctionCall: "function-call", Interval: "interval", Listener: "listener", Microtask: "microtask", PauseOnNextStatement: "pause-on-next-statement", Timeout: "timeout", URL: "url", Other: "other", // COMPATIBILITY (iOS 13): DOMDebugger.EventBreakpointType.Timer was replaced by DOMDebugger.EventBreakpointType.Interval and DOMDebugger.EventBreakpointType.Timeout. Timer: "timer", // COMPATIBILITY (iOS 13): DOMDebugger.EventBreakpointType.EventListener was replaced by DOMDebugger.EventBreakpointType.Listener. EventListener: "event-listener", }; WI.DebuggerManager.BlackboxType = { Pattern: "pattern", URL: "url", }; /* Controllers/DeviceSettingsManager.js */ /* * Copyright (C) 2023 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.DeviceSettingsManager = class DeviceSettingsManager extends WI.Object { constructor() { super(); this._deviceUserAgent = ""; this._overridenDeviceUserAgent = null; this._overridenDeviceScreenSize = null; this._overridenDeviceSettings = new Map; } // Target initializeTarget(target) { if (!target.hasDomain("Page")) return // COMPATIBILITY (iOS 12.2): Page.overrideUserAgent did not exist. if (target.hasCommand("Page.overrideUserAgent") && this._overridenDeviceUserAgent) target.PageAgent.overrideUserAgent(this._overridenDeviceUserAgent); // COMPATIBILITY (iOS 12.2): Page.overrideSetting did not exist. if (target.hasCommand("Page.overrideSetting")) { for (let [setting, value] of this._overridenDeviceSettings) target.PageAgent.overrideSetting(setting, value); } const objectGroup = "user-agent"; WI.runtimeManager.evaluateInInspectedWindow("globalThis.navigator?.userAgent", {objectGroup, returnByValue: true}, (remoteObject, wasThrown, result) => { if (!wasThrown && result) this._deviceUserAgent = result.value; target.RuntimeAgent.releaseObjectGroup(objectGroup); }); } // Static static supportsSetScreenSizeOverride() { return InspectorBackend.hasCommand("Page.setScreenSizeOverride"); } // Public get deviceUserAgent() { return this._deviceUserAgent; } get overridenDeviceUserAgent() { return this._overridenDeviceUserAgent; } get overridenDeviceScreenSize() { return this._overridenDeviceScreenSize; } get overridenDeviceSettings() { return this._overridenDeviceSettings; } get hasOverridenDefaultSettings() { return this._overridenDeviceUserAgent || this._overridenDeviceScreenSize || this._overridenDeviceSettings.size > 0; } overrideDeviceSetting(setting, value, callback) { let target = WI.assumingMainTarget(); if (!target.hasCommand("Page.overrideSetting")) { callback(false); return; } let commandArguments = { setting, }; let shouldOverride = !this._overridenDeviceSettings.has(setting); if (shouldOverride) commandArguments.value = value; target.PageAgent.overrideSetting.invoke(commandArguments, (error) => { if (error) { WI.reportInternalError(error); callback(false); return; } if (shouldOverride) this._overridenDeviceSettings.set(setting, value); else this._overridenDeviceSettings.delete(setting); callback(shouldOverride); this.dispatchEventToListeners(WI.DeviceSettingsManager.Event.SettingChanged); }); } overrideUserAgent(value, force) { if (value === this._overridenDeviceUserAgent) return; let target = WI.assumingMainTarget(); if (!target.hasCommand("Page.overrideUserAgent")) return; let commandArguments = {}; let shouldOverride = value && (value !== WI.DeviceSettingsManager.DefaultValue || force); if (shouldOverride) commandArguments.value = value; target.PageAgent.overrideUserAgent.invoke(commandArguments, (error) => { if (error) { WI.reportInternalError(error); return; } this._overridenDeviceUserAgent = shouldOverride ? value : null; this.dispatchEventToListeners(WI.DeviceSettingsManager.Event.SettingChanged); target.PageAgent.reload(); }); } overrideScreenSize(value, force) { if (value === this._overridenDeviceScreenSize) return; let target = WI.assumingMainTarget(); if (!target.hasCommand("Page.setScreenSizeOverride")) return; let commandArguments = {}; let shouldOverride = value && (value !== WI.DeviceSettingsManager.DefaultValue || force); if (shouldOverride) { let [width, height] = value.split("x"); width = parseInt(width); height = parseInt(height); if (isNaN(width) || isNaN(height)) shouldOverride = false; else { commandArguments.width = width; commandArguments.height = height; } } target.PageAgent.setScreenSizeOverride.invoke(commandArguments, (error) => { if (error) { WI.reportInternalError(error); return; } this._overridenDeviceScreenSize = shouldOverride ? value : null; this.dispatchEventToListeners(WI.DeviceSettingsManager.Event.SettingChanged); target.PageAgent.reload(); }); } }; WI.DeviceSettingsManager.Event = { SettingChanged: "device-setting-changed", }; WI.DeviceSettingsManager.DefaultValue = "default"; /* Controllers/FormatterSourceMap.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. */ WI.FormatterSourceMap = class FormatterSourceMap extends WI.Object { constructor(originalLineEndings, formattedLineEndings, mapping) { super(); this._originalLineEndings = originalLineEndings; this._formattedLineEndings = formattedLineEndings; this._mapping = mapping; } // Static static fromSourceMapData({originalLineEndings, formattedLineEndings, mapping}) { return new WI.FormatterSourceMap(originalLineEndings, formattedLineEndings, mapping); } // Public originalToFormatted(lineNumber, columnNumber) { var originalPosition = this._locationToPosition(this._originalLineEndings, lineNumber || 0, columnNumber || 0); return this.originalPositionToFormatted(originalPosition); } originalPositionToFormatted(originalPosition) { var formattedPosition = this._convertPosition(this._mapping.original, this._mapping.formatted, originalPosition); return this._positionToLocation(this._formattedLineEndings, formattedPosition); } originalPositionToFormattedPosition(originalPosition) { return this._convertPosition(this._mapping.original, this._mapping.formatted, originalPosition); } formattedToOriginal(lineNumber, columnNumber) { var originalPosition = this.formattedToOriginalOffset(lineNumber, columnNumber); return this._positionToLocation(this._originalLineEndings, originalPosition); } formattedToOriginalOffset(lineNumber, columnNumber) { var formattedPosition = this._locationToPosition(this._formattedLineEndings, lineNumber || 0, columnNumber || 0); var originalPosition = this._convertPosition(this._mapping.formatted, this._mapping.original, formattedPosition); return originalPosition; } formattedPositionToOriginalPosition(formattedPosition) { return this._convertPosition(this._mapping.formatted, this._mapping.original, formattedPosition); } // Private _locationToPosition(lineEndings, lineNumber, columnNumber) { var lineOffset = lineNumber ? lineEndings[lineNumber - 1] + 1 : 0; return lineOffset + columnNumber; } _positionToLocation(lineEndings, position) { var lineNumber = lineEndings.upperBound(position - 1); if (!lineNumber) var columnNumber = position; else var columnNumber = position - lineEndings[lineNumber - 1] - 1; return {lineNumber, columnNumber}; } _convertPosition(positions1, positions2, positionInPosition1) { var index = positions1.upperBound(positionInPosition1) - 1; var convertedPosition = positions2[index] + positionInPosition1 - positions1[index]; if (index < positions2.length - 1 && convertedPosition > positions2[index + 1]) convertedPosition = positions2[index + 1]; return convertedPosition; } }; /* Controllers/HARBuilder.js */ /* * Copyright (C) 2017-2018 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. */ // HTTP Archive (HAR) format - Version 1.2 // https://dvcs.w3.org/hg/webperf/raw-file/tip/specs/HAR/Overview.html#sec-har-object-types-creator // http://www.softwareishard.com/blog/har-12-spec/ WI.HARBuilder = class HARBuilder { static async buildArchive(resources) { let promises = []; for (let resource of resources) { console.assert(resource.finished); promises.push(new Promise((resolve, reject) => { // Always resolve. resource.requestContent().then( (x) => resolve(x), () => resolve(null) ); })); } let contents = await Promise.all(promises); console.assert(contents.length === resources.length); return { log: { version: "1.2", creator: HARBuilder.creator(), pages: HARBuilder.pages(), entries: resources.map((resource, index) => HARBuilder.entry(resource, contents[index])), } }; } static creator() { return { name: "WebKit Web Inspector", version: "1.0", }; } static pages() { return [{ startedDateTime: HARBuilder.date(WI.networkManager.mainFrame.mainResource.requestSentDate), id: "page_0", title: WI.networkManager.mainFrame.url || "", pageTimings: HARBuilder.pageTimings(), }]; } static pageTimings() { let result = {}; let domContentReadyEventTimestamp = WI.networkManager.mainFrame.domContentReadyEventTimestamp; if (!isNaN(domContentReadyEventTimestamp)) result.onContentLoad = domContentReadyEventTimestamp * 1000; let loadEventTimestamp = WI.networkManager.mainFrame.loadEventTimestamp; if (!isNaN(loadEventTimestamp)) result.onLoad = loadEventTimestamp * 1000; return result; } static entry(resource, content) { let entry = { pageref: "page_0", startedDateTime: HARBuilder.date(resource.requestSentDate), time: 0, request: HARBuilder.request(resource), response: HARBuilder.response(resource, content), cache: HARBuilder.cache(resource), timings: HARBuilder.timings(resource), }; if (resource.timingData.startTime && resource.timingData.responseEnd) entry.time = (resource.timingData.responseEnd - resource.timingData.startTime) * 1000; if (resource.remoteAddress) { entry.serverIPAddress = HARBuilder.ipAddress(resource.remoteAddress); // WebKit Custom Field `_serverPort`. if (entry.serverIPAddress) entry._serverPort = HARBuilder.port(resource.remoteAddress); } if (resource.connectionIdentifier) entry.connection = "" + resource.connectionIdentifier; // CFNetwork Custom Field `_fetchType`. if (resource.responseSource !== WI.Resource.ResponseSource.Unknown) entry._fetchType = HARBuilder.fetchType(resource.responseSource); // WebKit Custom Field `_priority`. if (resource.priority !== WI.Resource.NetworkPriority.Unknown) entry._priority = HARBuilder.priority(resource.priority); return entry; } static request(resource) { let result = { method: resource.requestMethod || "", url: resource.url || "", httpVersion: WI.Resource.displayNameForProtocol(resource.protocol) || "", cookies: HARBuilder.cookies(resource.requestCookies, null), headers: HARBuilder.headers(resource.requestHeaders), queryString: resource.queryStringParameters || [], headersSize: !isNaN(resource.requestHeadersTransferSize) ? resource.requestHeadersTransferSize : -1, bodySize: !isNaN(resource.requestBodyTransferSize) ? resource.requestBodyTransferSize : -1, }; if (resource.requestData) result.postData = HARBuilder.postData(resource); return result; } static response(resource, content) { let result = { status: resource.statusCode || 0, statusText: resource.statusText || "", httpVersion: WI.Resource.displayNameForProtocol(resource.protocol) || "", cookies: HARBuilder.cookies(resource.responseCookies, resource.requestSentDate), headers: HARBuilder.headers(resource.responseHeaders), content: HARBuilder.content(resource, content), redirectURL: resource.responseHeaders.valueForCaseInsensitiveKey("Location") || "", headersSize: !isNaN(resource.responseHeadersTransferSize) ? resource.responseHeadersTransferSize : -1, bodySize: !isNaN(resource.responseBodyTransferSize) ? resource.responseBodyTransferSize : -1, }; // Chrome Custom Field `_transferSize`. if (!isNaN(resource.networkTotalTransferSize)) result._transferSize = resource.networkTotalTransferSize; // Chrome Custom Field `_error`. if (resource.failureReasonText) result._error = resource.failureReasonText; return result; } static cookies(cookies, requestSentDate) { let result = []; for (let cookie of cookies) { let json = { name: cookie.name, value: cookie.value, }; if (cookie.type === WI.Cookie.Type.Response) { if (cookie.path) json.path = cookie.path; if (cookie.domain) json.domain = cookie.domain; json.expires = HARBuilder.date(cookie.expirationDate(requestSentDate)); json.httpOnly = cookie.httpOnly; json.secure = cookie.secure; if (cookie.sameSite !== WI.Cookie.SameSiteType.None) json.sameSite = cookie.sameSite; } result.push(json); } return result; } static headers(headers) { let result = []; for (let key in headers) result.push({name: key, value: headers[key]}); return result; } static content(resource, content) { let encodedSize = !isNaN(resource.networkEncodedSize) ? resource.networkEncodedSize : resource.estimatedNetworkEncodedSize; let decodedSize = !isNaN(resource.networkDecodedSize) ? resource.networkDecodedSize : resource.size; if (isNaN(decodedSize)) decodedSize = 0; if (isNaN(encodedSize)) encodedSize = 0; let result = { size: decodedSize, compression: decodedSize - encodedSize, mimeType: resource.mimeType || "x-unknown", }; if (content) { if (content.rawContent) result.text = content.rawContent; if (content.rawBase64Encoded) result.encoding = "base64"; } return result; } static postData(resource) { return { mimeType: resource.requestDataContentType || "", text: resource.requestData, params: resource.requestFormParameters || [], }; } static cache(resource) { // FIXME: Web Inspector: Include details in HAR Export // http://www.softwareishard.com/blog/har-12-spec/#cache return {}; } static timings(resource) { // FIXME: Web Inspector: HAR Extension for Redirect Timing Info // Chrome has Custom Fields `_blocked_queueing` and `_blocked_proxy`. let result = { blocked: -1, dns: -1, connect: -1, ssl: -1, send: 0, wait: 0, receive: 0, }; if (resource.timingData.startTime && resource.timingData.responseEnd) { let {startTime, domainLookupStart, domainLookupEnd, connectStart, connectEnd, secureConnectionStart, requestStart, responseStart, responseEnd} = resource.timingData; result.blocked = ((domainLookupStart || connectStart || requestStart) - startTime) * 1000; if (domainLookupStart) result.dns = ((domainLookupEnd || connectStart || requestStart) - domainLookupStart) * 1000; if (connectStart) result.connect = ((connectEnd || requestStart) - connectStart) * 1000; if (secureConnectionStart) result.ssl = ((connectEnd || requestStart) - secureConnectionStart) * 1000; // If all the time before requestStart was included in blocked, then make send time zero // as send time is essentially just blocked time after dns / connection time, and we // do not want to double count it. result.send = (domainLookupEnd || connectEnd) ? (requestStart - (connectEnd || domainLookupEnd)) * 1000 : 0; result.wait = (responseStart - requestStart) * 1000; result.receive = (responseEnd - responseStart) * 1000; } return result; } // Helpers static ipAddress(remoteAddress) { // IP Address, without port. if (!remoteAddress) return ""; // NOTE: Resource.remoteAddress always includes the port at the end. // So this always strips the last part. return remoteAddress.replace(/:\d+$/, ""); } static port(remoteAddress) { // IP Address, without port. if (!remoteAddress) return undefined; // NOTE: Resource.remoteAddress always includes the port at the end. // So this always matches the last part. let index = remoteAddress.lastIndexOf(":"); if (!index) return undefined; let portString = remoteAddress.substr(index + 1); let port = parseInt(portString); if (isNaN(port)) return undefined; return port; } static date(date) { // ISO 8601 if (!date) return ""; return date.toISOString(); } static fetchType(responseSource) { switch (responseSource) { case WI.Resource.ResponseSource.Network: return "Network Load"; case WI.Resource.ResponseSource.MemoryCache: return "Memory Cache"; case WI.Resource.ResponseSource.DiskCache: return "Disk Cache"; case WI.Resource.ResponseSource.ServiceWorker: return "Service Worker"; case WI.Resource.ResponseSource.InspectorOverride: return "Inspector Override"; } console.assert(); return undefined; } static priority(priority) { switch (priority) { case WI.Resource.NetworkPriority.Low: return "low"; case WI.Resource.NetworkPriority.Medium: return "medium"; case WI.Resource.NetworkPriority.High: return "high"; } console.assert(); return undefined; } // Consuming. static dateFromHARDate(isoString) { return Date.parse(isoString); } static protocolFromHARProtocol(protocol) { switch (protocol) { case "HTTP/2": return "h2"; case "HTTP/1.0": return "http/1.0"; case "HTTP/1.1": return "http/1.1"; case "SPDY/2": return "spdy/2"; case "SPDY/3": return "spdy/3"; case "SPDY/3.1": return "spdy/3.1"; } if (protocol) console.warn("Unknown HAR protocol value", protocol); return null; } static responseSourceFromHARFetchType(fetchType) { switch (fetchType) { case "Network Load": return WI.Resource.ResponseSource.Network; case "Memory Cache": return WI.Resource.ResponseSource.MemoryCache; case "Disk Cache": return WI.Resource.ResponseSource.DiskCache; case "Service Worker": return WI.Resource.ResponseSource.ServiceWorker; case "Inspector Override": return WI.Resource.ResponseSource.InspectorOverride; } if (fetchType) console.warn("Unknown HAR _fetchType value", fetchType); return WI.Resource.ResponseSource.Other; } static networkPriorityFromHARPriority(priority) { switch (priority) { case "low": return WI.Resource.NetworkPriority.Low; case "medium": return WI.Resource.NetworkPriority.Medium; case "high": return WI.Resource.NetworkPriority.High; } if (priority) console.warn("Unknown HAR priority value", priority); return WI.Resource.NetworkPriority.Unknown; } }; /* Controllers/HeapManager.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 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. */ // FIXME: HeapManager lacks advanced multi-target support. (Instruments/Profilers per-target) WI.HeapManager = class HeapManager extends WI.Object { constructor() { super(); this._enabled = false; } // Agent get domains() { return ["Heap"]; } activateExtraDomain(domain) { // COMPATIBILITY (iOS 14.0): Inspector.activateExtraDomains was removed in favor of a declared debuggable type console.assert(domain === "Heap"); for (let target of WI.targets) this.initializeTarget(target); } // Target initializeTarget(target) { if (!this._enabled) return; if (target.hasDomain("Heap")) target.HeapAgent.enable(); } // Public enable() { if (this._enabled) return; this._enabled = true; for (let target of WI.targets) this.initializeTarget(target); } disable() { if (!this._enabled) return; for (let target of WI.targets) { if (target.hasDomain("Heap")) target.HeapAgent.disable(); } this._enabled = false; } snapshot(callback) { console.assert(this._enabled); let target = WI.assumingMainTarget(); target.HeapAgent.snapshot((error, timestamp, snapshotStringData) => { if (error) console.error(error); callback(error, timestamp, snapshotStringData); }); } getPreview(node, callback) { console.assert(this._enabled); console.assert(node instanceof WI.HeapSnapshotNodeProxy); let target = WI.assumingMainTarget(); target.HeapAgent.getPreview(node.id, (error, string, functionDetails, preview) => { if (error) console.error(error); callback(error, string, functionDetails, preview); }); } getRemoteObject(node, objectGroup, callback) { console.assert(this._enabled); console.assert(node instanceof WI.HeapSnapshotNodeProxy); let target = WI.assumingMainTarget(); target.HeapAgent.getRemoteObject(node.id, objectGroup, (error, result) => { if (error) console.error(error); callback(error, result); }); } // HeapObserver garbageCollected(target, payload) { if (!this._enabled) return; // FIXME: Web Inspector: Enable Memory profiling in Workers if (target !== WI.mainTarget) return; let collection = WI.GarbageCollection.fromPayload(payload); this.dispatchEventToListeners(WI.HeapManager.Event.GarbageCollected, {collection}); } }; WI.HeapManager.Event = { GarbageCollected: "heap-manager-garbage-collected" }; /* Controllers/IndexedDBManager.js */ /* * Copyright (C) 2013 Apple Inc. All rights reserved. * Copyright (C) 2013 Samsung Electronics. 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. */ // FIXME: IndexedDBManager lacks advanced multi-target support. (IndexedDatabase per-target) WI.IndexedDBManager = class IndexedDBManager extends WI.Object { constructor() { super(); this._enabled = false; this._requestedSecurityOrigins = new Set; this._reset(); } // Agent get domains() { return ["IndexedDB"]; } activateExtraDomain(domain) { // COMPATIBILITY (iOS 14.0): Inspector.activateExtraDomains was removed in favor of a declared debuggable type console.assert(domain === "IndexedDB"); for (let target of WI.targets) this.initializeTarget(target); } // Target initializeTarget(target) { if (!this._enabled) return; if (target.hasDomain("IndexedDB")) target.IndexedDBAgent.enable(); } // Public get indexedDatabases() { return this._indexedDatabases; } enable() { console.assert(!this._enabled); this._enabled = true; this._reset(); for (let target of WI.targets) this.initializeTarget(target); WI.Frame.addEventListener(WI.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this); WI.Frame.addEventListener(WI.Frame.Event.SecurityOriginDidChange, this._securityOriginDidChange, this); } disable() { console.assert(this._enabled); this._enabled = false; for (let target of WI.targets) { if (target.hasDomain("IndexedDB")) target.IndexedDBAgent.disable(); } WI.Frame.removeEventListener(WI.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this); WI.Frame.removeEventListener(WI.Frame.Event.SecurityOriginDidChange, this._securityOriginDidChange, this); this._reset(); } requestIndexedDatabaseData(objectStore, objectStoreIndex, startEntryIndex, maximumEntryCount, callback) { console.assert(this._enabled); console.assert(InspectorBackend.hasDomain("IndexedDB")); console.assert(objectStore); console.assert(callback); function processData(error, entryPayloads, moreAvailable) { if (error) { callback(null, false); return; } var entries = []; for (var entryPayload of entryPayloads) { var entry = {}; entry.primaryKey = WI.RemoteObject.fromPayload(entryPayload.primaryKey); entry.key = WI.RemoteObject.fromPayload(entryPayload.key); entry.value = WI.RemoteObject.fromPayload(entryPayload.value); entries.push(entry); } callback(entries, moreAvailable); } var requestArguments = { securityOrigin: objectStore.parentDatabase.securityOrigin, databaseName: objectStore.parentDatabase.name, objectStoreName: objectStore.name, indexName: objectStoreIndex && objectStoreIndex.name || "", skipCount: startEntryIndex || 0, pageSize: maximumEntryCount || 100 }; let target = WI.assumingMainTarget(); target.IndexedDBAgent.requestData.invoke(requestArguments, processData); } clearObjectStore(objectStore) { console.assert(this._enabled); let securityOrigin = objectStore.parentDatabase.securityOrigin; let databaseName = objectStore.parentDatabase.name; let objectStoreName = objectStore.name; let target = WI.assumingMainTarget(); target.IndexedDBAgent.clearObjectStore(securityOrigin, databaseName, objectStoreName); } // Private _reset() { this._indexedDatabases = []; this._requestedSecurityOrigins.clear(); this.dispatchEventToListeners(WI.IndexedDBManager.Event.Cleared); let mainFrame = WI.networkManager.mainFrame; if (mainFrame) this._addIndexedDBDatabasesIfNeeded(mainFrame); } _addIndexedDBDatabasesIfNeeded(frame) { if (!this._enabled) return; let target = WI.assumingMainTarget(); if (!target.hasDomain("IndexedDB")) return; var securityOrigin = frame.securityOrigin; // Don't show storage if we don't have a security origin (about:blank). if (!securityOrigin || securityOrigin === "://") return; if (this._requestedSecurityOrigins.has(securityOrigin)) return; this._requestedSecurityOrigins.add(securityOrigin); function processDatabaseNames(error, names) { if (error || !names) return; for (var name of names) target.IndexedDBAgent.requestDatabase(securityOrigin, name, processDatabase.bind(this)); } function processDatabase(error, databasePayload) { if (error || !databasePayload) return; var objectStores = databasePayload.objectStores.map(processObjectStore); var indexedDatabase = new WI.IndexedDatabase(databasePayload.name, securityOrigin, databasePayload.version, objectStores); this._indexedDatabases.push(indexedDatabase); this.dispatchEventToListeners(WI.IndexedDBManager.Event.IndexedDatabaseWasAdded, {indexedDatabase}); } function processKeyPath(keyPathPayload) { switch (keyPathPayload.type) { case InspectorBackend.Enum.IndexedDB.KeyPathType.Null: return null; case InspectorBackend.Enum.IndexedDB.KeyPathType.String: return keyPathPayload.string; case InspectorBackend.Enum.IndexedDB.KeyPathType.Array: return keyPathPayload.array; default: console.error("Unknown KeyPath type:", keyPathPayload.type); return null; } } function processObjectStore(objectStorePayload) { var keyPath = processKeyPath(objectStorePayload.keyPath); var indexes = objectStorePayload.indexes.map(processObjectStoreIndex); return new WI.IndexedDatabaseObjectStore(objectStorePayload.name, keyPath, objectStorePayload.autoIncrement, indexes); } function processObjectStoreIndex(objectStoreIndexPayload) { var keyPath = processKeyPath(objectStoreIndexPayload.keyPath); return new WI.IndexedDatabaseObjectStoreIndex(objectStoreIndexPayload.name, keyPath, objectStoreIndexPayload.unique, objectStoreIndexPayload.multiEntry); } target.IndexedDBAgent.requestDatabaseNames(securityOrigin, processDatabaseNames.bind(this)); } _mainResourceDidChange(event) { console.assert(event.target instanceof WI.Frame); if (event.target.isMainFrame()) this._reset(); } _securityOriginDidChange(event) { console.assert(event.target instanceof WI.Frame); this._addIndexedDBDatabasesIfNeeded(event.target); } }; WI.IndexedDBManager.Event = { IndexedDatabaseWasAdded: "indexed-db-manager-indexed-database-was-added", Cleared: "indexed-db-manager-cleared", }; /* Controllers/LayerTreeManager.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. */ // FIXME: LayerTreeManager lacks advanced multi-target support. (Layers per-target) WI.LayerTreeManager = class LayerTreeManager extends WI.Object { constructor() { super(); this._showPaintRects = false; this._compositingBordersVisible = false; } // Target initializeTarget(target) { if (target.hasDomain("LayerTree")) target.LayerTreeAgent.enable(); if (target.hasDomain("Page")) { if (target.hasCommand("Page.setShowPaintRects") && this._showPaintRects) target.PageAgent.setShowPaintRects(this._showPaintRects); if (this._compositingBordersVisible) { // COMPATIBILITY(iOS 13.1): Page.setCompositingBordersVisible was replaced by Page.Setting.ShowDebugBorders and Page.Setting.ShowRepaintCounter. if (target.hasCommand("Page.overrideSetting") && InspectorBackend.Enum.Page.Setting.ShowDebugBorders && InspectorBackend.Enum.Page.Setting.ShowRepaintCounter) { target.PageAgent.overrideSetting(InspectorBackend.Enum.Page.Setting.ShowDebugBorders, this._compositingBordersVisible); target.PageAgent.overrideSetting(InspectorBackend.Enum.Page.Setting.ShowRepaintCounter, this._compositingBordersVisible); } else if (target.hasCommand("Page.setCompositingBordersVisible")) target.PageAgent.setCompositingBordersVisible(this._compositingBordersVisible); } } } // Static static supportsShowingPaintRects() { return InspectorBackend.hasCommand("Page.setShowPaintRects"); } static supportsVisibleCompositingBorders() { return InspectorBackend.hasCommand("Page.setCompositingBordersVisible") || (InspectorBackend.hasCommand("Page.overrideSetting") && InspectorBackend.Enum.Page.Setting.ShowDebugBorders && InspectorBackend.Enum.Page.Setting.ShowRepaintCounter); } // Public get supported() { return InspectorBackend.hasDomain("LayerTree"); } get showPaintRects() { return this._showPaintRects; } set showPaintRects(showPaintRects) { if (this._showPaintRects === showPaintRects) return; this._showPaintRects = showPaintRects; for (let target of WI.targets) { if (target.hasCommand("Page.setShowPaintRects")) target.PageAgent.setShowPaintRects(this._showPaintRects); } this.dispatchEventToListeners(LayerTreeManager.Event.ShowPaintRectsChanged); } get compositingBordersVisible() { return this._compositingBordersVisible; } set compositingBordersVisible(compositingBordersVisible) { if (this._compositingBordersVisible === compositingBordersVisible) return; this._compositingBordersVisible = compositingBordersVisible; for (let target of WI.targets) { // COMPATIBILITY(iOS 13.1): Page.setCompositingBordersVisible was replaced by Page.Setting.ShowDebugBorders and Page.Setting.ShowRepaintCounter. if (target.hasCommand("Page.overrideSetting") && InspectorBackend.Enum.Page.Setting.ShowDebugBorders && InspectorBackend.Enum.Page.Setting.ShowRepaintCounter) { target.PageAgent.overrideSetting(InspectorBackend.Enum.Page.Setting.ShowDebugBorders, this._compositingBordersVisible); target.PageAgent.overrideSetting(InspectorBackend.Enum.Page.Setting.ShowRepaintCounter, this._compositingBordersVisible); } else if (target.hasCommand("Page.setCompositingBordersVisible")) target.PageAgent.setCompositingBordersVisible(this._compositingBordersVisible); } this.dispatchEventToListeners(LayerTreeManager.Event.CompositingBordersVisibleChanged); } updateCompositingBordersVisibleFromPageIfNeeded() { if (!WI.targetsAvailable()) { WI.whenTargetsAvailable().then(() => { this.updateCompositingBordersVisibleFromPageIfNeeded(); }); return; } let target = WI.assumingMainTarget(); // COMPATIBILITY(iOS 13.1): Page.getCompositingBordersVisible was replaced by Page.Setting.ShowDebugBorders and Page.Setting.ShowRepaintCounter. if (!target.hasCommand("Page.getCompositingBordersVisible")) return; target.PageAgent.getCompositingBordersVisible((error, compositingBordersVisible) => { if (error) { WI.reportInternalError(error); return; } this.compositingBordersVisible = compositingBordersVisible; }); } layerTreeMutations(previousLayers, newLayers) { console.assert(this.supported); if (isEmptyObject(previousLayers)) return {preserved: [], additions: newLayers, removals: []}; let previousLayerIds = new Set; let newLayerIds = new Set; let preserved = []; let additions = []; for (let layer of previousLayers) previousLayerIds.add(layer.layerId); for (let layer of newLayers) { newLayerIds.add(layer.layerId); if (previousLayerIds.has(layer.layerId)) preserved.push(layer); else additions.push(layer); } let removals = previousLayers.filter((layer) => !newLayerIds.has(layer.layerId)); return {preserved, additions, removals}; } layersForNode(node, callback) { console.assert(this.supported); let target = WI.assumingMainTarget(); target.LayerTreeAgent.layersForNode(node.id, (error, layers) => { callback(error ? [] : layers.map(WI.Layer.fromPayload)); }); } reasonsForCompositingLayer(layer, callback) { console.assert(this.supported); let target = WI.assumingMainTarget(); target.LayerTreeAgent.reasonsForCompositingLayer(layer.layerId, function(error, reasons) { callback(error ? 0 : reasons); }); } // LayerTreeObserver layerTreeDidChange() { this.dispatchEventToListeners(WI.LayerTreeManager.Event.LayerTreeDidChange); } }; WI.LayerTreeManager.Event = { ShowPaintRectsChanged: "show-paint-rects-changed", CompositingBordersVisibleChanged: "compositing-borders-visible-changed", LayerTreeDidChange: "layer-tree-did-change", }; /* Controllers/MemoryManager.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.MemoryManager = class MemoryManager extends WI.Object { constructor() { super(); this._enabled = false; } // Agent get domains() { return ["Memory"]; } activateExtraDomain(domain) { // COMPATIBILITY (iOS 14.0): Inspector.activateExtraDomains was removed in favor of a declared debuggable type console.assert(domain === "Memory"); for (let target of WI.targets) this.initializeTarget(target); } // Target initializeTarget(target) { if (!this._enabled) return; if (target.hasDomain("Memory")) target.MemoryAgent.enable(); } // Public enable() { if (this._enabled) return; this._enabled = true; for (let target of WI.targets) this.initializeTarget(target); } disable() { if (!this._enabled) return; for (let target of WI.targets) { if (target.hasDomain("Memory")) target.MemoryAgent.disable(); } this._enabled = false; } // MemoryObserver memoryPressure(timestamp, protocolSeverity) { if (!this._enabled) return; let memoryPressureEvent = WI.MemoryPressureEvent.fromPayload(timestamp, protocolSeverity); this.dispatchEventToListeners(WI.MemoryManager.Event.MemoryPressure, {memoryPressureEvent}); } }; WI.MemoryManager.Event = { MemoryPressure: "memory-manager-memory-pressure", }; /* Controllers/NetworkManager.js */ /* * Copyright (C) 2013-2019 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. */ // FIXME: NetworkManager lacks advanced multi-target support. (Network.loadResource invocations per-target) WI.NetworkManager = class NetworkManager extends WI.Object { constructor() { super(); this._frameIdentifierMap = new Map; this._mainFrame = null; this._resourceRequestIdentifierMap = new Map; this._orphanedResources = new Map; this._webSocketIdentifierToURL = new Map; this._waitingForMainFrameResourceTreePayload = true; this._transitioningPageTarget = false; this._sourceMapURLMap = new Map; this._downloadingSourceMaps = new Set; this._failedSourceMapURLs = new Set; this._localResourceOverrides = []; this._harImportLocalResourceMap = new Set; this._pendingLocalResourceOverrideSaves = null; this._saveLocalResourceOverridesDebouncer = null; // FIXME: Provide dedicated UI to toggle Network Interception globally? this._interceptionEnabled = true; this._emulatedCondition = WI.NetworkManager.EmulatedCondition.None; // COMPATIBILITY (iOS 14.0): Inspector.activateExtraDomains was removed in favor of a declared debuggable type WI.notifications.addEventListener(WI.Notification.ExtraDomainsActivated, this._extraDomainsActivated, this); WI.Frame.addEventListener(WI.Frame.Event.MainResourceDidChange, this._handleFrameMainResourceDidChange, this); if (NetworkManager.supportsOverridingResponses()) { WI.Resource.addEventListener(WI.SourceCode.Event.ContentDidChange, this._handleResourceContentChangedForLocalResourceOverride, this); WI.Resource.addEventListener(WI.Resource.Event.RequestDataDidChange, this._handleResourceContentChangedForLocalResourceOverride, this); WI.LocalResourceOverride.addEventListener(WI.LocalResourceOverride.Event.DisabledChanged, this._handleResourceOverrideDisabledChanged, this); WI.LocalResourceOverride.addEventListener(WI.LocalResourceOverride.Event.ResourceErrorTypeChanged, this._handleResourceOverrideResourceErrorTypeChanged, this); WI.Target.registerInitializationPromise((async () => { let serializedLocalResourceOverrides = await WI.objectStores.localResourceOverrides.getAll(); this._restoringLocalResourceOverrides = true; for (let serializedLocalResourceOverride of serializedLocalResourceOverrides) { let localResourceOverride = WI.LocalResourceOverride.fromJSON(serializedLocalResourceOverride); let supported = false; switch (localResourceOverride.type) { case WI.LocalResourceOverride.InterceptType.Block: supported = WI.NetworkManager.supportsBlockingRequests(); break; case WI.LocalResourceOverride.InterceptType.Request: supported = WI.NetworkManager.supportsOverridingRequests(); break; case WI.LocalResourceOverride.InterceptType.Response: supported = WI.NetworkManager.supportsOverridingResponses(); break; case WI.LocalResourceOverride.InterceptType.ResponseMappedDirectory: supported = WI.NetworkManager.supportsOverridingResponses() && WI.LocalResource.canMapToFile(); break; case WI.LocalResourceOverride.InterceptType.ResponseSkippingNetwork: supported = WI.NetworkManager.supportsOverridingRequestsWithResponses(); break; } if (!supported) continue; const key = null; WI.objectStores.localResourceOverrides.associateObject(localResourceOverride, key, serializedLocalResourceOverride); this.addLocalResourceOverride(localResourceOverride); } this._restoringLocalResourceOverrides = false; })()); } this._bootstrapScript = null; if (NetworkManager.supportsBootstrapScript()) { this._bootstrapScriptEnabledSetting = new WI.Setting("bootstrap-script-enabled", true); WI.Target.registerInitializationPromise((async () => { let bootstrapScriptSource = await WI.objectStores.general.get(NetworkManager.bootstrapScriptSourceObjectStoreKey); if (bootstrapScriptSource !== undefined) this.createBootstrapScript(bootstrapScriptSource); })()); } } // Static static supportsShowCertificate() { return InspectorFrontendHost.supportsShowCertificate && InspectorBackend.hasCommand("Network.getSerializedCertificate"); } static supportsBlockingRequests() { // COMPATIBILITY (iOS 13.4): Network.interceptRequestWithError did not exist yet. return InspectorBackend.hasCommand("Network.interceptRequestWithError"); } static supportsOverridingRequests() { // COMPATIBILITY (iOS 13.4): Network.interceptWithRequest did not exist yet. return InspectorBackend.hasCommand("Network.interceptWithRequest"); } static supportsOverridingRequestsWithResponses() { // COMPATIBILITY (iOS 13.4): Network.interceptRequestWithResponse did not exist yet. return InspectorBackend.hasCommand("Network.interceptRequestWithResponse"); } static supportsOverridingResponses() { // COMPATIBILITY (iOS 13.0): Network.interceptWithResponse did not exist yet. return InspectorBackend.hasCommand("Network.interceptWithResponse"); } static supportsBootstrapScript() { return InspectorBackend.hasCommand("Page.setBootstrapScript"); } static get bootstrapScriptURL() { return "web-inspector://bootstrap.js"; } static get bootstrapScriptSourceObjectStoreKey() { return "bootstrap-script-source"; } static synthesizeImportError(message) { message = WI.UIString("HAR Import Error: %s").format(message); if (window.InspectorTest) { console.error(message); return; } let consoleMessage = new WI.ConsoleMessage(WI.mainTarget, WI.ConsoleMessage.MessageSource.Other, WI.ConsoleMessage.MessageLevel.Error, message); consoleMessage.shouldRevealConsole = true; WI.consoleLogViewController.appendConsoleMessage(consoleMessage); } // Target initializeTarget(target) { if (target.hasDomain("Page")) { target.PageAgent.enable(); target.PageAgent.getResourceTree(this._processMainFrameResourceTreePayload.bind(this)); // COMPATIBILITY (iOS 13.0): Page.setBootstrapScript did not exist yet. if (target.hasCommand("Page.setBootstrapScript") && this._bootstrapScript && this._bootstrapScriptEnabledSetting.value) target.PageAgent.setBootstrapScript(this._bootstrapScript.content); } if (target.hasDomain("ServiceWorker")) target.ServiceWorkerAgent.getInitializationInfo(this._processServiceWorkerConfiguration.bind(this)); if (target.hasDomain("Network")) { target.NetworkAgent.enable(); target.NetworkAgent.setResourceCachingDisabled(WI.settings.resourceCachingDisabled.value); // COMPATIBILITY (iOS 13.0): Network.setInterceptionEnabled did not exist. if (target.hasCommand("Network.setInterceptionEnabled")) { if (this._interceptionEnabled) target.NetworkAgent.setInterceptionEnabled(this._interceptionEnabled); for (let localResourceOverride of this._localResourceOverrides) { if (!localResourceOverride.disabled) this._addInterception(localResourceOverride, target); } } } this._applyEmulatedCondition(target); if (target.type === WI.TargetType.Worker) this.adoptOrphanedResourcesForTarget(target); } transitionPageTarget() { this._transitioningPageTarget = true; this._waitingForMainFrameResourceTreePayload = true; } // Public get mainFrame() { return this._mainFrame; } get localResourceOverrides() { return this._localResourceOverrides; } get bootstrapScript() { return this._bootstrapScript; } get frames() { return Array.from(this._frameIdentifierMap.values()); } get interceptionEnabled() { return this._interceptionEnabled; } set interceptionEnabled(enabled) { if (this._interceptionEnabled === enabled) return; this._interceptionEnabled = enabled; for (let target of WI.targets) { // COMPATIBILITY (iOS 13.0): Network.setInterceptionEnabled did not exist. if (target.hasCommand("Network.setInterceptionEnabled")) target.NetworkAgent.setInterceptionEnabled(this._interceptionEnabled); } } get emulatedCondition() { return this._emulatedCondition; } set emulatedCondition(condition) { console.assert(Object.values(WI.NetworkManager.EmulatedCondition).includes(condition), condition); console.assert(WI.settings.experimentalEnableNetworkEmulatedCondition.value); console.assert(InspectorBackend.hasCommand("Network.setEmulatedConditions")); if (condition === this._emulatedCondition) return; this._emulatedCondition = condition; for (let target of WI.targets) this._applyEmulatedCondition(target); this.dispatchEventToListeners(WI.NetworkManager.Event.EmulatedConditionChanged); } frameForIdentifier(frameId) { return this._frameIdentifierMap.get(frameId) || null; } resourceForRequestIdentifier(requestIdentifier) { return this._resourceRequestIdentifierMap.get(requestIdentifier) || null; } downloadSourceMap(sourceMapURL, baseURL, originalSourceCode) { if (!WI.settings.sourceMapsEnabled.value) return; // The baseURL could have come from a "//# sourceURL". Attempt to get a // reasonable absolute URL for the base by using the main resource's URL. if (WI.networkManager.mainFrame) baseURL = absoluteURL(baseURL, WI.networkManager.mainFrame.url); if (sourceMapURL.startsWith("data:")) { this._loadAndParseSourceMap(sourceMapURL, baseURL, originalSourceCode); return; } sourceMapURL = absoluteURL(sourceMapURL, baseURL); if (!sourceMapURL) return; console.assert(originalSourceCode.url); if (!originalSourceCode.url) return; // FIXME: Source Maps: Better handle when multiple resources reference the same SourceMap if (this._sourceMapURLMap.has(sourceMapURL) || this._downloadingSourceMaps.has(sourceMapURL)) return; let loadAndParseSourceMap = () => { this._loadAndParseSourceMap(sourceMapURL, baseURL, originalSourceCode); }; if (!WI.networkManager.mainFrame) { // If we don't have a main frame, then we are likely in the middle of building the resource tree. // Delaying until the next runloop is enough in this case to then start loading the source map. setTimeout(loadAndParseSourceMap, 0); return; } loadAndParseSourceMap(); } isSourceMapURL(url) { return this._downloadingSourceMaps.has(url) || this._failedSourceMapURLs.has(url); } get bootstrapScriptEnabled() { console.assert(NetworkManager.supportsBootstrapScript()); console.assert(this._bootstrapScript); return this._bootstrapScriptEnabledSetting.value; } set bootstrapScriptEnabled(enabled) { console.assert(NetworkManager.supportsBootstrapScript()); console.assert(this._bootstrapScript); this._bootstrapScriptEnabledSetting.value = !!enabled; let source = this._bootstrapScriptEnabledSetting.value ? this._bootstrapScript.content : undefined; // COMPATIBILITY (iOS 13.0): Page.setBootstrapScript did not exist yet. for (let target of WI.targets) { if (target.hasCommand("Page.setBootstrapScript")) target.PageAgent.setBootstrapScript(source); } this.dispatchEventToListeners(NetworkManager.Event.BootstrapScriptEnabledChanged, {bootstrapScript: this._bootstrapScript}); } async createBootstrapScript(source) { console.assert(NetworkManager.supportsBootstrapScript()); if (this._bootstrapScript) return; if (!arguments.length) source = await WI.objectStores.general.get(NetworkManager.bootstrapScriptSourceObjectStoreKey); if (!source) { source = ` /* * ${WI.UIString("The Inspector Bootstrap Script is guaranteed to be the first script evaluated in any page, as well as any sub-frames.")} * ${WI.UIString("It is evaluated immediately after the global object is created, before any other content has loaded.")} * * ${WI.UIString("Modifications made here will take effect on the next load of any page or sub-frame.")} * ${WI.UIString("The contents and enabled state will be preserved across Web Inspector sessions.")} * * ${WI.UIString("Some examples of ways to use this script include (but are not limited to):")} * - ${WI.UIString("overriding built-in functions to log call traces or add %s statements").format(WI.unlocalizedString("`debugger`"))} * - ${WI.UIString("ensuring that common debugging functions are available on every page via the Console")} * * ${WI.UIString("More information is available at .")} */ `.trimStart(); } const target = null; const url = null; const sourceURL = NetworkManager.bootstrapScriptURL; this._bootstrapScript = new WI.LocalScript(target, url, sourceURL, WI.Script.SourceType.Program, source, {injected: true, editable: true}); this._bootstrapScript.addEventListener(WI.SourceCode.Event.ContentDidChange, this._handleBootstrapScriptContentDidChange, this); this._handleBootstrapScriptContentDidChange(); this.dispatchEventToListeners(NetworkManager.Event.BootstrapScriptCreated, {bootstrapScript: this._bootstrapScript}); } destroyBootstrapScript() { console.assert(NetworkManager.supportsBootstrapScript()); if (!this._bootstrapScript) return; let bootstrapScript = this._bootstrapScript; this._bootstrapScript = null; WI.objectStores.general.delete(NetworkManager.bootstrapScriptSourceObjectStoreKey); // COMPATIBILITY (iOS 13.0): Page.setBootstrapScript did not exist yet. for (let target of WI.targets) { if (target.hasCommand("Page.setBootstrapScript")) target.PageAgent.setBootstrapScript(); } this.dispatchEventToListeners(NetworkManager.Event.BootstrapScriptDestroyed, {bootstrapScript}); } addLocalResourceOverride(localResourceOverride) { console.assert(localResourceOverride instanceof WI.LocalResourceOverride); console.assert(!this._localResourceOverrides.includes(localResourceOverride)); this._localResourceOverrides.push(localResourceOverride); if (!this._restoringLocalResourceOverrides) WI.objectStores.localResourceOverrides.putObject(localResourceOverride); if (!localResourceOverride.disabled) this._addInterception(localResourceOverride); this.dispatchEventToListeners(WI.NetworkManager.Event.LocalResourceOverrideAdded, {localResourceOverride}); } removeLocalResourceOverride(localResourceOverride) { console.assert(localResourceOverride instanceof WI.LocalResourceOverride); if (!this._localResourceOverrides.remove(localResourceOverride)) { console.assert(false, "Attempted to remove a local resource override that was not known."); return; } if (this._pendingLocalResourceOverrideSaves) this._pendingLocalResourceOverrideSaves.delete(localResourceOverride); if (!this._restoringLocalResourceOverrides) WI.objectStores.localResourceOverrides.deleteObject(localResourceOverride); if (!localResourceOverride.disabled) this._removeInterception(localResourceOverride); this.dispatchEventToListeners(WI.NetworkManager.Event.LocalResourceOverrideRemoved, {localResourceOverride}); } localResourceOverridesForURL(url) { // Order local resource overrides based on how closely they match the given URL. As an example, // a regular expression is likely going to match more URLs than a case-insensitive string. const rankFunctions = [ (localResourceOverride) => localResourceOverride.isCaseSensitive && !localResourceOverride.isRegex, // exact match (localResourceOverride) => !localResourceOverride.isCaseSensitive && !localResourceOverride.isRegex, // case-insensitive (localResourceOverride) => localResourceOverride.isCaseSensitive && localResourceOverride.isRegex, // case-sensitive regex (localResourceOverride) => !localResourceOverride.isCaseSensitive && localResourceOverride.isRegex, // case-insensitive regex ]; return this._localResourceOverrides .filter((localResourceOverride) => localResourceOverride.matches(url)) .sort((a, b) => { let aRank = rankFunctions.findIndex((rankFunction) => rankFunction(a)); let bRank = rankFunctions.findIndex((rankFunction) => rankFunction(b)); return aRank - bRank; }); } canBeOverridden(resource) { if (!(resource instanceof WI.Resource)) return false; if (resource instanceof WI.SourceMapResource) return false; if (resource.localResourceOverride) return false; const schemes = ["http:", "https:", "file:"]; if (!schemes.some((scheme) => resource.url.startsWith(scheme))) return false; if (this.localResourceOverridesForURL(resource.url).length) return false; switch (resource.type) { case WI.Resource.Type.Document: case WI.Resource.Type.StyleSheet: case WI.Resource.Type.Script: case WI.Resource.Type.XHR: case WI.Resource.Type.Fetch: case WI.Resource.Type.Image: case WI.Resource.Type.Font: case WI.Resource.Type.EventSource: case WI.Resource.Type.Other: break; case WI.Resource.Type.Ping: case WI.Resource.Type.Beacon: // Responses aren't really expected for Ping/Beacon. return false; case WI.Resource.Type.WebSocket: // Non-HTTP traffic. console.assert(false, "Scheme check above should have been sufficient."); return false; } return true; } resourcesForURL(url) { let resources = new Set; if (this._mainFrame) { if (this._mainFrame.mainResource.url === url) resources.add(this._mainFrame.mainResource); const recursivelySearchChildFrames = true; resources.addAll(this._mainFrame.resourcesForURL(url, recursivelySearchChildFrames)); } return resources; } adoptOrphanedResourcesForTarget(target) { let resources = this._orphanedResources.take(target.identifier); if (!resources) return; for (let resource of resources) target.adoptResource(resource); } processHAR({json, error}) { if (error) { WI.NetworkManager.synthesizeImportError(error); return null; } if (typeof json !== "object" || json === null) { WI.NetworkManager.synthesizeImportError(WI.UIString("invalid JSON")); return null; } if (typeof json.log !== "object" || typeof json.log.version !== "string") { WI.NetworkManager.synthesizeImportError(WI.UIString("invalid HAR")); return null; } if (json.log.version !== "1.2") { WI.NetworkManager.synthesizeImportError(WI.UIString("unsupported HAR version")); return null; } if (!Array.isArray(json.log.entries) || !Array.isArray(json.log.pages) || !json.log.pages[0] || !json.log.pages[0].startedDateTime) { WI.NetworkManager.synthesizeImportError(WI.UIString("invalid HAR")); return null; } let mainResourceSentWalltime = WI.HARBuilder.dateFromHARDate(json.log.pages[0].startedDateTime) / 1000; if (isNaN(mainResourceSentWalltime)) { WI.NetworkManager.synthesizeImportError(WI.UIString("invalid HAR")); return null; } let localResources = []; for (let entry of json.log.entries) { let localResource = WI.LocalResource.fromHAREntry(entry, mainResourceSentWalltime); this._harImportLocalResourceMap.add(localResource); localResources.push(localResource); } return localResources; } // PageObserver frameDidNavigate(framePayload) { // Ignore this while waiting for the whole frame/resource tree. if (this._waitingForMainFrameResourceTreePayload) return; var frameWasLoadedInstantly = false; var frame = this.frameForIdentifier(framePayload.id); if (!frame) { // If the frame wasn't known before now, then the main resource was loaded instantly (about:blank, etc.) // Make a new resource (which will make the frame). Mark will mark it as loaded at the end too since we // don't expect any more events about the load finishing for these frames. let resourceOptions = { loaderIdentifier: framePayload.loaderId, }; let frameOptions = { name: framePayload.name, securityOrigin: framePayload.securityOrigin, }; let frameResource = this._addNewResourceToFrameOrTarget(framePayload.url, framePayload.id, resourceOptions, frameOptions); frame = frameResource.parentFrame; frameWasLoadedInstantly = true; console.assert(frame); if (!frame) return; } if (framePayload.loaderId === frame.provisionalLoaderIdentifier) { // There was a provisional load in progress, commit it. frame.commitProvisionalLoad(framePayload.securityOrigin); } else { let mainResource = null; if (frame.mainResource.url !== framePayload.url || frame.loaderIdentifier !== framePayload.loaderId) { // Navigations like back/forward do not have provisional loads, so create a new main resource here. mainResource = new WI.Resource(framePayload.url, { mimeType: framePayload.mimeType, loaderIdentifier: framePayload.loaderId, }); } else { // The main resource is already correct, so reuse it. mainResource = frame.mainResource; } frame.initialize(framePayload.name, framePayload.securityOrigin, framePayload.loaderId, mainResource); } var oldMainFrame = this._mainFrame; if (framePayload.parentId) { var parentFrame = this.frameForIdentifier(framePayload.parentId); console.assert(parentFrame); if (frame === this._mainFrame) this._mainFrame = null; if (frame.parentFrame !== parentFrame) parentFrame.addChildFrame(frame); } else { if (frame.parentFrame) frame.parentFrame.removeChildFrame(frame); this._mainFrame = frame; } if (this._mainFrame !== oldMainFrame) this._mainFrameDidChange(oldMainFrame); if (frameWasLoadedInstantly) frame.mainResource.markAsFinished(); } frameDidDetach(frameId) { // Ignore this while waiting for the whole frame/resource tree. if (this._waitingForMainFrameResourceTreePayload) return; var frame = this.frameForIdentifier(frameId); if (!frame) return; if (frame.parentFrame) frame.parentFrame.removeChildFrame(frame); this._frameIdentifierMap.delete(frame.id); var oldMainFrame = this._mainFrame; if (frame === this._mainFrame) this._mainFrame = null; frame.clearExecutionContexts(); this.dispatchEventToListeners(WI.NetworkManager.Event.FrameWasRemoved, {frame}); if (this._mainFrame !== oldMainFrame) this._mainFrameDidChange(oldMainFrame); } // NetworkObserver resourceRequestWillBeSent(requestIdentifier, frameIdentifier, loaderIdentifier, request, type, redirectResponse, timestamp, walltime, initiator, targetId) { // Ignore this while waiting for the whole frame/resource tree. if (this._waitingForMainFrameResourceTreePayload) return; var elapsedTime = WI.timelineManager.computeElapsedTime(timestamp); let resource = this._resourceRequestIdentifierMap.get(requestIdentifier); if (resource) { // This is an existing request which is being redirected, update the resource. console.assert(resource.parentFrame.id === frameIdentifier); console.assert(resource.loaderIdentifier === loaderIdentifier); console.assert(!targetId); resource.updateForRedirectResponse(request, redirectResponse, elapsedTime, walltime); return; } // This is a new request, make a new resource and add it to the right frame. resource = this._addNewResourceToFrameOrTarget(request.url, frameIdentifier, { type, loaderIdentifier, targetId, requestIdentifier, requestMethod: request.method, requestHeaders: request.headers, requestData: request.postData, requestSentTimestamp: elapsedTime, requestSentWalltime: walltime, referrerPolicy: request.referrerPolicy, integrity: request.integrity, initiatorStackTrace: this._initiatorStackTraceFromPayload(initiator), initiatorSourceCodeLocation: this._initiatorSourceCodeLocationFromPayload(initiator), initiatorNode: this._initiatorNodeFromPayload(initiator), }); // Associate the resource with the requestIdentifier so it can be found in future loading events. this._resourceRequestIdentifierMap.set(requestIdentifier, resource); } webSocketCreated(requestId, url) { this._webSocketIdentifierToURL.set(requestId, url); } webSocketWillSendHandshakeRequest(requestId, timestamp, walltime, request) { let url = this._webSocketIdentifierToURL.get(requestId); console.assert(url); if (!url) return; // FIXME: Web Inspector: Correctly display iframe's and worker's WebSockets let resource = new WI.WebSocketResource(url, { loaderIdentifier: WI.networkManager.mainFrame.id, requestIdentifier: requestId, requestHeaders: request.headers, timestamp, walltime, requestSentTimestamp: WI.timelineManager.computeElapsedTime(timestamp), }); let frame = this.frameForIdentifier(WI.networkManager.mainFrame.id); frame.addResource(resource); this._resourceRequestIdentifierMap.set(requestId, resource); } webSocketHandshakeResponseReceived(requestId, timestamp, response) { let resource = this._resourceRequestIdentifierMap.get(requestId); console.assert(resource); if (!resource) return; resource.readyState = WI.WebSocketResource.ReadyState.Open; let elapsedTime = WI.timelineManager.computeElapsedTime(timestamp); // FIXME: Web Inspector: WebSockets: Implement timing information let responseTiming = response.timing || null; resource.updateForResponse(resource.url, resource.mimeType, resource.type, response.headers, response.status, response.statusText, elapsedTime, responseTiming); resource.markAsFinished(elapsedTime); } webSocketFrameReceived(requestId, timestamp, response) { this._webSocketFrameReceivedOrSent(requestId, timestamp, response); } webSocketFrameSent(requestId, timestamp, response) { this._webSocketFrameReceivedOrSent(requestId, timestamp, response); } webSocketClosed(requestId, timestamp) { let resource = this._resourceRequestIdentifierMap.get(requestId); console.assert(resource); if (!resource) return; resource.readyState = WI.WebSocketResource.ReadyState.Closed; let elapsedTime = WI.timelineManager.computeElapsedTime(timestamp); resource.markAsFinished(elapsedTime); this._webSocketIdentifierToURL.delete(requestId); this._resourceRequestIdentifierMap.delete(requestId); } _webSocketFrameReceivedOrSent(requestId, timestamp, response) { let resource = this._resourceRequestIdentifierMap.get(requestId); console.assert(resource); if (!resource) return; // Data going from the client to the server is always masked. let isOutgoing = !!response.mask; let {payloadData, payloadLength, opcode} = response; let elapsedTime = WI.timelineManager.computeElapsedTime(timestamp); resource.addFrame(payloadData, payloadLength, isOutgoing, opcode, timestamp, elapsedTime); } resourceRequestWasServedFromMemoryCache(requestIdentifier, frameIdentifier, loaderIdentifier, cachedResourcePayload, timestamp, initiator) { // Ignore this while waiting for the whole frame/resource tree. if (this._waitingForMainFrameResourceTreePayload) return; console.assert(!this._resourceRequestIdentifierMap.has(requestIdentifier)); let elapsedTime = WI.timelineManager.computeElapsedTime(timestamp); let response = cachedResourcePayload.response; const responseSource = InspectorBackend.Enum.Network.ResponseSource.MemoryCache; let resource = this._addNewResourceToFrameOrTarget(cachedResourcePayload.url, frameIdentifier, { type: cachedResourcePayload.type, loaderIdentifier, requestIdentifier, requestMethod: "GET", requestSentTimestamp: elapsedTime, initiatorStackTrace: this._initiatorStackTraceFromPayload(initiator), initiatorSourceCodeLocation: this._initiatorSourceCodeLocationFromPayload(initiator), initiatorNode: this._initiatorNodeFromPayload(initiator), }); resource.updateForResponse(cachedResourcePayload.url, response.mimeType, cachedResourcePayload.type, response.headers, response.status, response.statusText, elapsedTime, response.timing, responseSource, response.security); resource.increaseSize(cachedResourcePayload.bodySize, elapsedTime); resource.increaseTransferSize(cachedResourcePayload.bodySize); resource.setCachedResponseBodySize(cachedResourcePayload.bodySize); resource.markAsFinished(elapsedTime); console.assert(resource.cached, "This resource should be classified as cached since it was served from the MemoryCache", resource); if (cachedResourcePayload.sourceMapURL) this.downloadSourceMap(cachedResourcePayload.sourceMapURL, resource.url, resource); // No need to associate the resource with the requestIdentifier, since this is the only event // sent for memory cache resource loads. } resourceRequestDidReceiveResponse(requestIdentifier, frameIdentifier, loaderIdentifier, type, response, timestamp) { // Ignore this while waiting for the whole frame/resource tree. if (this._waitingForMainFrameResourceTreePayload) return; var elapsedTime = WI.timelineManager.computeElapsedTime(timestamp); let resource = this._resourceRequestIdentifierMap.get(requestIdentifier); // We might not have a resource if the inspector was opened during the page load (after resourceRequestWillBeSent is called). // We don't want to assert in this case since we do likely have the resource, via Page.getResourceTree. The Resource // just doesn't have a requestIdentifier for us to look it up, but we can try to look it up by its URL. if (!resource) { var frame = this.frameForIdentifier(frameIdentifier); if (frame) resource = frame.resourcesForURL(response.url).firstValue; // If we find the resource this way we had marked it earlier as finished via Page.getResourceTree. // Associate the resource with the requestIdentifier so it can be found in future loading events. // and roll it back to an unfinished state, we know now it is still loading. if (resource) { this._resourceRequestIdentifierMap.set(requestIdentifier, resource); resource.revertMarkAsFinished(); } } // If we haven't found an existing Resource by now, then it is a resource that was loading when the inspector // opened and we just missed the resourceRequestWillBeSent for it. So make a new resource and add it. if (!resource) { resource = this._addNewResourceToFrameOrTarget(response.url, frameIdentifier, { type, loaderIdentifier, requestIdentifier, requestHeaders: response.requestHeaders, requestSentTimestamp: elapsedTime, }); // Associate the resource with the requestIdentifier so it can be found in future loading events. this._resourceRequestIdentifierMap.set(requestIdentifier, resource); } resource.updateForResponse(response.url, response.mimeType, type, response.headers, response.status, response.statusText, elapsedTime, response.timing, response.source, response.security); } resourceRequestDidReceiveData(requestIdentifier, dataLength, encodedDataLength, timestamp) { // Ignore this while waiting for the whole frame/resource tree. if (this._waitingForMainFrameResourceTreePayload) return; let resource = this._resourceRequestIdentifierMap.get(requestIdentifier); var elapsedTime = WI.timelineManager.computeElapsedTime(timestamp); // We might not have a resource if the inspector was opened during the page load (after resourceRequestWillBeSent is called). // We don't want to assert in this case since we do likely have the resource, via Page.getResourceTree. The Resource // just doesn't have a requestIdentifier for us to look it up. if (!resource) return; resource.increaseSize(dataLength, elapsedTime); if (encodedDataLength !== -1) resource.increaseTransferSize(encodedDataLength); } resourceRequestDidFinishLoading(requestIdentifier, timestamp, sourceMapURL, metrics) { // Ignore this while waiting for the whole frame/resource tree. if (this._waitingForMainFrameResourceTreePayload) return; // By now we should always have the Resource. Either it was fetched when the inspector first opened with // Page.getResourceTree, or it was a currently loading resource that we learned about in resourceRequestDidReceiveResponse. let resource = this._resourceRequestIdentifierMap.get(requestIdentifier); console.assert(resource); if (!resource) return; if (metrics) resource.updateWithMetrics(metrics); let elapsedTime = WI.timelineManager.computeElapsedTime(timestamp); resource.markAsFinished(elapsedTime); if (sourceMapURL) this.downloadSourceMap(sourceMapURL, resource.url, resource); this._resourceRequestIdentifierMap.delete(requestIdentifier); } resourceRequestDidFailLoading(requestIdentifier, canceled, timestamp, errorText) { // Ignore this while waiting for the whole frame/resource tree. if (this._waitingForMainFrameResourceTreePayload) return; // By now we should always have the Resource. Either it was fetched when the inspector first opened with // Page.getResourceTree, or it was a currently loading resource that we learned about in resourceRequestDidReceiveResponse. let resource = this._resourceRequestIdentifierMap.get(requestIdentifier); console.assert(resource); if (!resource) return; let elapsedTime = WI.timelineManager.computeElapsedTime(timestamp); resource.markAsFailed(canceled, elapsedTime, errorText); if (resource.parentFrame && resource === resource.parentFrame.provisionalMainResource) resource.parentFrame.clearProvisionalLoad(); this._resourceRequestIdentifierMap.delete(requestIdentifier); } async requestIntercepted(target, requestId, request) { for (let localResourceOverride of this.localResourceOverridesForURL(request.url)) { if (localResourceOverride.disabled) continue; if (localResourceOverride.networkStage !== WI.NetworkManager.NetworkStage.Request) continue; let isPassthrough = localResourceOverride.isPassthrough; let originalHeaders = isPassthrough ? request.headers : {}; let localResource = localResourceOverride.localResource; await localResource.requestContent(); let revision = localResource.currentRevision; switch (localResourceOverride.type) { case WI.LocalResourceOverride.InterceptType.Block: target.NetworkAgent.interceptRequestWithError.invoke({ requestId, errorType: localResourceOverride.resourceErrorType, }); return; case WI.LocalResourceOverride.InterceptType.Request: { target.NetworkAgent.interceptWithRequest.invoke({ requestId, url: localResourceOverride.generateRequestRedirectURL(request.url) ?? undefined, method: localResource.requestMethod ?? (isPassthrough ? request.method : ""), headers: {...originalHeaders, ...localResource.requestHeaders}, postData: (function() { if (!WI.HTTPUtilities.RequestMethodsWithBody.has(localResource.requestMethod)) return undefined; if (localResource.requestData ?? false) return btoa(localResource.requestData); if (isPassthrough) return request.data; return ""; })(), }); return; } case WI.LocalResourceOverride.InterceptType.ResponseSkippingNetwork: console.assert(revision.mimeType === localResource.mimeType); target.NetworkAgent.interceptRequestWithResponse.invoke({ requestId, content: revision.content, base64Encoded: !!revision.base64Encoded, mimeType: revision.mimeType ?? "text/plain", status: !isNaN(localResource.statusCode) ? localResource.statusCode : 200, statusText: (function() { if (localResource.statusText ?? false) return localResource.statusText; if (!isNaN(localResource.statusCode)) return WI.HTTPUtilities.statusTextForStatusCode(localResource.statusCode); return WI.HTTPUtilities.statusTextForStatusCode(200); })(), headers: {...originalHeaders, ...localResource.responseHeaders}, }); return; } } // It's possible for a response regex override to overlap a request regex override, in // which case we should silently continue the request if the response regex override was // used instead (e.g. it was added first). target.NetworkAgent.interceptContinue.invoke({ requestId, stage: WI.NetworkManager.NetworkStage.Request, }); } async responseIntercepted(target, requestId, response) { for (let localResourceOverride of this.localResourceOverridesForURL(response.url)) { if (localResourceOverride.disabled) continue; if (localResourceOverride.networkStage !== WI.NetworkManager.NetworkStage.Response) continue; let isPassthrough = localResourceOverride.isPassthrough; let originalHeaders = isPassthrough ? response.headers : {}; let localResource = localResourceOverride.localResource; await localResource.requestContent(); let revision = localResource.currentRevision; switch (localResourceOverride.type) { case WI.LocalResourceOverride.InterceptType.Response: console.assert(revision.mimeType === localResource.mimeType); target.NetworkAgent.interceptWithResponse.invoke({ requestId, content: revision.content, base64Encoded: !!revision.base64Encoded, mimeType: revision.mimeType ?? (isPassthrough ? response.mimeType : "text/plain"), status: (function() { if (!isNaN(localResource.statusCode)) return localResource.statusCode; if (isPassthrough) return response.status; return 200; })(), statusText: (function() { if (localResource.statusText ?? false) return localResource.statusText; if (isPassthrough) return response.statusText; if (!isNaN(localResource.statusCode)) return WI.HTTPUtilities.statusTextForStatusCode(localResource.statusCode); return WI.HTTPUtilities.statusTextForStatusCode(200); })(), headers: {...originalHeaders, ...localResource.responseHeaders}, }); return; case WI.LocalResourceOverride.InterceptType.ResponseMappedDirectory: { let subpath = localResourceOverride.generateSubpathForMappedDirectory(WI.urlWithoutUserQueryOrFragment(response.url)); let content = await localResource.requestContentFromMappedDirectory(subpath); if (typeof content === "string") { let mimeType = WI.mimeTypeForFileExtension(WI.fileExtensionForURL(response.url)); target.NetworkAgent.interceptWithResponse.invoke({ requestId, content, base64Encoded: !WI.shouldTreatMIMETypeAsText(mimeType), mimeType, status: (function() { if (response.status < 400) return response.status; return 200; })(), statusText: (function() { if (response.status < 400) { if (response.statusText) return response.statusText; return WI.HTTPUtilities.statusTextForStatusCode(response.status); } return WI.HTTPUtilities.statusTextForStatusCode(200); })(), }); } else { // Be lenient by allowing for a very general directory mapping to not have to // contain files for every single possible request that could be intercepted. target.NetworkAgent.interceptContinue.invoke({ requestId, stage: WI.NetworkManager.NetworkStage.Response, }); } return; } } } // It's possible for a request regex override to overlap a response regex override, in // which case we should silently continue the response if the request regex override was // used instead (e.g. it was added first). target.NetworkAgent.interceptContinue.invoke({ requestId, stage: WI.NetworkManager.NetworkStage.Response, }); } // RuntimeObserver executionContextCreated(payload) { let frame = this.frameForIdentifier(payload.frameId); console.assert(frame); if (!frame) return; let type = WI.ExecutionContext.typeFromPayload(payload); let target = frame.mainResource.target; let executionContext = new WI.ExecutionContext(target, payload.id, type, payload.name, frame); frame.addExecutionContext(executionContext); } // Private _addNewResourceToFrameOrTarget(url, frameIdentifier, resourceOptions = {}, frameOptions = {}) { console.assert(!this._waitingForMainFrameResourceTreePayload); let resource = null; if (!frameIdentifier && resourceOptions.targetId) { // This is a new resource for a ServiceWorker target. console.assert(WI.sharedApp.debuggableType === WI.DebuggableType.ServiceWorker); console.assert(resourceOptions.targetId === WI.mainTarget.identifier); resource = new WI.Resource(url, resourceOptions); resource.target.addResource(resource); return resource; } let frame = this.frameForIdentifier(frameIdentifier); if (frame) { if (resourceOptions.type === InspectorBackend.Enum.Page.ResourceType.Document && frame.provisionalMainResource && frame.provisionalMainResource.url === url && frame.provisionalLoaderIdentifier === resourceOptions.loaderIdentifier) resource = frame.provisionalMainResource; else { resource = new WI.Resource(url, resourceOptions); if (resource.target === WI.pageTarget) this._addResourceToFrame(frame, resource); else if (resource.target) resource.target.addResource(resource); else this._addOrphanedResource(resource, resourceOptions.targetId); } } else { // This is a new request for a new frame, which is always the main resource. console.assert(WI.sharedApp.debuggableType !== WI.DebuggableType.ServiceWorker); console.assert(!resourceOptions.targetId); resource = new WI.Resource(url, resourceOptions); frame = new WI.Frame(frameIdentifier, frameOptions.name, frameOptions.securityOrigin, resourceOptions.loaderIdentifier, resource); this._frameIdentifierMap.set(frame.id, frame); // If we don't have a main frame, assume this is it. This can change later in // frameDidNavigate when the parent frame is known. if (!this._mainFrame) { this._mainFrame = frame; this._mainFrameDidChange(null); } this._dispatchFrameWasAddedEvent(frame); } console.assert(resource); return resource; } _addResourceToFrame(frame, resource) { console.assert(!this._waitingForMainFrameResourceTreePayload); if (this._waitingForMainFrameResourceTreePayload) return; console.assert(frame); console.assert(resource); if (resource.loaderIdentifier !== frame.loaderIdentifier && !frame.provisionalLoaderIdentifier) { // This is the start of a provisional load which happens before frameDidNavigate is called. // This resource will be the new mainResource if frameDidNavigate is called. frame.startProvisionalLoad(resource); return; } // This is just another resource, either for the main loader or the provisional loader. console.assert(resource.loaderIdentifier === frame.loaderIdentifier || resource.loaderIdentifier === frame.provisionalLoaderIdentifier); frame.addResource(resource); } _addResourceToTarget(target, resource) { console.assert(target !== WI.pageTarget); console.assert(resource); target.addResource(resource); } _initiatorStackTraceFromPayload(initiatorPayload) { if (!initiatorPayload) return null; let stackTrace = initiatorPayload.stackTrace; if (!stackTrace) return null; // COMPATIBILITY (macOS 13.0, iOS 16.0): `stackTrace` was an array of `Console.CallFrame`. if (Array.isArray(stackTrace)) stackTrace = {callFrames: stackTrace}; return WI.StackTrace.fromPayload(WI.assumingMainTarget(), stackTrace); } _initiatorSourceCodeLocationFromPayload(initiatorPayload) { if (!initiatorPayload) return null; var url = null; var lineNumber = NaN; var columnNumber = 0; // COMPATIBILITY (macOS 13.0, iOS 16.0): `stackTrace` was an array of `Console.CallFrame`. let callFramesPayload = Array.isArray(initiatorPayload.stackTrace) ? initiatorPayload.stackTrace : initiatorPayload.stackTrace?.callFrames; if (callFramesPayload?.length) { for (let callFramePayload of callFramesPayload) { if (!callFramePayload.url || callFramePayload.url === "[native code]") continue; url = callFramePayload.url; // The lineNumber is 1-based, but we expect 0-based. lineNumber = callFramePayload.lineNumber - 1; columnNumber = callFramePayload.columnNumber; break; } } else if (initiatorPayload.url) { url = initiatorPayload.url; // The lineNumber is 1-based, but we expect 0-based. lineNumber = initiatorPayload.lineNumber - 1; } if (!url || isNaN(lineNumber) || lineNumber < 0) return null; let sourceCode = WI.networkManager.resourcesForURL(url).firstValue; if (!sourceCode) sourceCode = WI.debuggerManager.scriptsForURL(url, WI.mainTarget)[0]; if (!sourceCode) return null; return sourceCode.createSourceCodeLocation(lineNumber, columnNumber); } _initiatorNodeFromPayload(initiatorPayload) { return WI.domManager.nodeForId(initiatorPayload.nodeId); } _processServiceWorkerConfiguration(error, initializationPayload) { console.assert(this._waitingForMainFrameResourceTreePayload); this._waitingForMainFrameResourceTreePayload = false; if (error) { console.error(JSON.stringify(error)); return; } console.assert(initializationPayload.targetId.startsWith("serviceworker:")); WI.mainTarget.identifier = initializationPayload.targetId; WI.mainTarget.name = initializationPayload.url; // Create a main resource with this content in case the content never shows up as a WI.Script. const sourceURL = null; const sourceType = WI.Script.SourceType.Program; let script = new WI.LocalScript(WI.mainTarget, initializationPayload.url, sourceURL, sourceType, initializationPayload.content); WI.mainTarget.mainResource = script; InspectorBackend.runAfterPendingDispatches(() => { if (WI.mainTarget.mainResource === script) { // We've now received all the scripts, if we don't have a better main resource use this LocalScript. WI.debuggerManager.dataForTarget(WI.mainTarget).addScript(script); WI.debuggerManager.dispatchEventToListeners(WI.DebuggerManager.Event.ScriptAdded, {script}); } }); } _processMainFrameResourceTreePayload(error, mainFramePayload) { console.assert(this._waitingForMainFrameResourceTreePayload); this._waitingForMainFrameResourceTreePayload = false; if (error) { console.error(JSON.stringify(error)); return; } console.assert(mainFramePayload); console.assert(mainFramePayload.frame); this._resourceRequestIdentifierMap = new Map; this._frameIdentifierMap = new Map; var oldMainFrame = this._mainFrame; this._mainFrame = this._addFrameTreeFromFrameResourceTreePayload(mainFramePayload, true); if (this._mainFrame !== oldMainFrame) this._mainFrameDidChange(oldMainFrame); // Emulate a main resource change within this page even though we are swapping out main frames. // This is because many managers listen only for main resource change events to perform work, // but they don't listen for main frame changes. if (this._transitioningPageTarget) { this._transitioningPageTarget = false; this._mainFrame._dispatchMainResourceDidChangeEvent(oldMainFrame.mainResource); } } _createFrame(payload) { // If payload.url is missing or empty then this page is likely the special empty page. In that case // we will just say it is "about:blank" so we have a URL, which is required for resources. let mainResource = new WI.Resource(payload.url || "about:blank", { mimeType: payload.mimeType, loaderIdentifier: payload.loaderId, }); var frame = new WI.Frame(payload.id, payload.name, payload.securityOrigin, payload.loaderId, mainResource); this._frameIdentifierMap.set(frame.id, frame); mainResource.markAsFinished(); return frame; } _createResource(payload, framePayload) { let resource = new WI.Resource(payload.url, { mimeType: payload.mimeType, type: payload.type, loaderIdentifier: framePayload.loaderId, targetId: payload.targetId, }); if (payload.sourceMapURL) this.downloadSourceMap(payload.sourceMapURL, resource.url, resource); return resource; } _addFrameTreeFromFrameResourceTreePayload(payload, isMainFrame) { var frame = this._createFrame(payload.frame); if (isMainFrame) frame.markAsMainFrame(); for (var i = 0; payload.childFrames && i < payload.childFrames.length; ++i) frame.addChildFrame(this._addFrameTreeFromFrameResourceTreePayload(payload.childFrames[i], false)); for (var i = 0; payload.resources && i < payload.resources.length; ++i) { var resourcePayload = payload.resources[i]; // The main resource is included as a resource. We can skip it since we already created // a main resource when we created the Frame. The resource payload does not include anything // didn't already get from the frame payload. if (resourcePayload.type === "Document" && resourcePayload.url === payload.frame.url) continue; var resource = this._createResource(resourcePayload, payload); if (resource.target === WI.pageTarget) frame.addResource(resource); else if (resource.target) resource.target.addResource(resource); else this._addOrphanedResource(resource, resourcePayload.targetId); if (resourcePayload.failed || resourcePayload.canceled) resource.markAsFailed(resourcePayload.canceled); else resource.markAsFinished(); } this._dispatchFrameWasAddedEvent(frame); return frame; } _addOrphanedResource(resource, targetId) { let resources = this._orphanedResources.get(targetId); if (!resources) { resources = []; this._orphanedResources.set(targetId, resources); } resources.push(resource); } _commandArgumentsForInterception(localResourceOverride) { console.assert(localResourceOverride instanceof WI.LocalResourceOverride, localResourceOverride); return { url: localResourceOverride.url, stage: localResourceOverride.networkStage, caseSensitive: localResourceOverride.isCaseSensitive, isRegex: localResourceOverride.isRegex, }; } _addInterception(localResourceOverride, specificTarget) { console.assert(localResourceOverride instanceof WI.LocalResourceOverride, localResourceOverride); console.assert(!localResourceOverride.disabled, localResourceOverride); let targets = specificTarget ? [specificTarget] : WI.targets; for (let target of targets) { // COMPATIBILITY (iOS 13.0): Network.addInterception did not exist yet. if (!target.hasCommand("Network.addInterception")) continue; target.NetworkAgent.addInterception.invoke(this._commandArgumentsForInterception(localResourceOverride)); } } _removeInterception(localResourceOverride, specificTarget) { console.assert(localResourceOverride instanceof WI.LocalResourceOverride, localResourceOverride); let targets = specificTarget ? [specificTarget] : WI.targets; for (let target of targets) { // COMPATIBILITY (iOS 13.0): Network.removeInterception did not exist yet. if (!target.hasCommand("Network.removeInterception")) continue; target.NetworkAgent.removeInterception.invoke(this._commandArgumentsForInterception(localResourceOverride)); } } _applyEmulatedCondition(target) { if (!WI.settings.experimentalEnableNetworkEmulatedCondition.value) return; // COMPATIBILITY (macOS 13.0, iOS 16.0): Network.setEmulatedConditions did not exist. if (!target.hasCommand("Network.setEmulatedConditions")) return; target.NetworkAgent.setEmulatedConditions(this._emulatedCondition.bytesPerSecondLimit); } _dispatchFrameWasAddedEvent(frame) { this.dispatchEventToListeners(WI.NetworkManager.Event.FrameWasAdded, {frame}); } _mainFrameDidChange(oldMainFrame) { if (oldMainFrame) oldMainFrame.unmarkAsMainFrame(); if (this._mainFrame) this._mainFrame.markAsMainFrame(); this.dispatchEventToListeners(WI.NetworkManager.Event.MainFrameDidChange, {oldMainFrame}); } _loadAndParseSourceMap(sourceMapURL, baseURL, originalSourceCode) { this._downloadingSourceMaps.add(sourceMapURL); let sourceMapLoaded = (error, content, mimeType, statusCode) => { if (error || statusCode >= 400) { this._sourceMapLoadAndParseFailed(sourceMapURL); return; } if (content.slice(0, 3) === ")]}") { let firstNewlineIndex = content.indexOf("\n"); if (firstNewlineIndex === -1) { this._sourceMapLoadAndParseFailed(sourceMapURL); return; } content = content.substring(firstNewlineIndex); } try { let payload = JSON.parse(content); let baseURL = sourceMapURL.startsWith("data:") ? originalSourceCode.url : sourceMapURL; let sourceMap = new WI.SourceMap(baseURL, payload, originalSourceCode); this._sourceMapLoadAndParseSucceeded(sourceMapURL, sourceMap); } catch { this._sourceMapLoadAndParseFailed(sourceMapURL); } }; if (sourceMapURL.startsWith("data:")) { let {mimeType, base64, data} = parseDataURL(sourceMapURL); let content = base64 ? atob(data) : data; sourceMapLoaded(null, content, mimeType, 0); return; } let target = WI.assumingMainTarget(); if (!target.hasCommand("Network.loadResource")) { this._sourceMapLoadAndParseFailed(sourceMapURL); return; } let frameIdentifier = null; if (originalSourceCode instanceof WI.Resource && originalSourceCode.parentFrame) frameIdentifier = originalSourceCode.parentFrame.id; if (!frameIdentifier) frameIdentifier = WI.networkManager.mainFrame ? WI.networkManager.mainFrame.id : ""; target.NetworkAgent.loadResource(frameIdentifier, sourceMapURL, sourceMapLoaded); } _sourceMapLoadAndParseFailed(sourceMapURL) { this._downloadingSourceMaps.delete(sourceMapURL); this._failedSourceMapURLs.add(sourceMapURL); } _sourceMapLoadAndParseSucceeded(sourceMapURL, sourceMap) { if (!this._downloadingSourceMaps.has(sourceMapURL)) return; this._downloadingSourceMaps.delete(sourceMapURL); this._sourceMapURLMap.set(sourceMapURL, sourceMap); for (let source of sourceMap.sources()) sourceMap.addResource(new WI.SourceMapResource(source, sourceMap)); // Associate the SourceMap with the originalSourceCode. sourceMap.originalSourceCode.addSourceMap(sourceMap); // If the originalSourceCode was not a Resource, be sure to also associate with the Resource if one exists. // FIXME: We should try to use the right frame instead of a global lookup by URL. if (!(sourceMap.originalSourceCode instanceof WI.Resource)) { console.assert(sourceMap.originalSourceCode instanceof WI.Script); let resource = sourceMap.originalSourceCode.resource; if (resource) resource.addSourceMap(sourceMap); } } _handleResourceContentChangedForLocalResourceOverride(event) { let localResourceOverride = event.target.localResourceOverride; if (!localResourceOverride) return; if (!this._saveLocalResourceOverridesDebouncer) { this._pendingLocalResourceOverrideSaves = new Set; this._saveLocalResourceOverridesDebouncer = new Debouncer(() => { for (let localResourceOverride of this._pendingLocalResourceOverrideSaves) { console.assert(localResourceOverride instanceof WI.LocalResourceOverride); WI.objectStores.localResourceOverrides.putObject(localResourceOverride); } }); } this._pendingLocalResourceOverrideSaves.add(localResourceOverride); this._saveLocalResourceOverridesDebouncer.delayForTime(500); } _handleResourceOverrideDisabledChanged(event) { console.assert(WI.NetworkManager.supportsOverridingResponses()); let localResourceOverride = event.target; WI.objectStores.localResourceOverrides.putObject(localResourceOverride); if (localResourceOverride.disabled) this._removeInterception(localResourceOverride); else this._addInterception(localResourceOverride); } _handleResourceOverrideResourceErrorTypeChanged(event) { console.assert(WI.NetworkManager.supportsBlockingRequests()); let localResourceOverride = event.target; WI.objectStores.localResourceOverrides.putObject(localResourceOverride); } _handleBootstrapScriptContentDidChange(event) { let source = this._bootstrapScript.content || ""; WI.objectStores.general.put(source, NetworkManager.bootstrapScriptSourceObjectStoreKey); if (!this._bootstrapScriptEnabledSetting.value) return; // COMPATIBILITY (iOS 13.0): Page.setBootstrapScript did not exist yet. for (let target of WI.targets) { if (target.hasCommand("Page.setBootstrapScript")) target.PageAgent.setBootstrapScript(source); } } _extraDomainsActivated(event) { // COMPATIBILITY (iOS 14.0): Inspector.activateExtraDomains was removed in favor of a declared debuggable type let target = WI.assumingMainTarget(); if (target.hasDomain("Page") && event.data.domains.includes("Page")) target.PageAgent.getResourceTree(this._processMainFrameResourceTreePayload.bind(this)); } _handleFrameMainResourceDidChange(event) { if (!event.target.isMainFrame()) return; WI.LocalResource.resetPathsThatFailedToLoadFromFileSystem(); this._sourceMapURLMap.clear(); this._downloadingSourceMaps.clear(); this._failedSourceMapURLs.clear(); } }; // Keep this in sync with `Network.NetworkStage`. WI.NetworkManager.NetworkStage = { Request: "request", Response: "response", }; WI.NetworkManager.EmulatedCondition = { // Keep this first. None: { id: "none", bytesPerSecondLimit: 0, get displayName() { return WI.UIString("No throttling", "Label indicating that network throttling is inactive."); } }, Mobile3G: { id: "mobile-3g", bytesPerSecondLimit: 780 * 1000 / 8, // 780kbps get displayName() { return WI.UIString("3G", "Label indicating that network activity is being simulated with 3G connectivity."); } }, DSL: { id: "dsl", bytesPerSecondLimit: 2 * 1000 * 1000 / 8, // 2mbps get displayName() { return WI.UIString("DSL", "Label indicating that network activity is being simulated with DSL connectivity."); } }, Edge: { id: "edge", bytesPerSecondLimit: 240 * 1000 / 8, // 240kbps get displayName() { return WI.UIString("Edge", "Label indicating that network activity is being simulated with Edge connectivity."); } }, LTE: { id: "lte", bytesPerSecondLimit: 50 * 1000 * 1000 / 8, // 50mbps get displayName() { return WI.UIString("LTE", "Label indicating that network activity is being simulated with LTE connectivity"); } }, WiFi: { id: "wifi", bytesPerSecondLimit: 40 * 1000 * 1000 / 8, // 40mbps get displayName() { return WI.UIString("Wi-Fi", "Label indicating that network activity is being simulated with Wi-Fi connectivity"); } }, WiFi802_11ac: { id: "wifi-802_11ac", bytesPerSecondLimit: 250 * 1000 * 1000 / 8, // 250mbps get displayName() { return WI.UIString("Wi-Fi 802.11ac", "Label indicating that network activity is being simulated with Wi-Fi 802.11ac connectivity"); } }, }; WI.NetworkManager.Event = { FrameWasAdded: "network-manager-frame-was-added", FrameWasRemoved: "network-manager-frame-was-removed", MainFrameDidChange: "network-manager-main-frame-did-change", BootstrapScriptCreated: "network-manager-bootstrap-script-created", BootstrapScriptEnabledChanged: "network-manager-bootstrap-script-enabled-changed", BootstrapScriptDestroyed: "network-manager-bootstrap-script-destroyed", LocalResourceOverrideAdded: "network-manager-local-resource-override-added", LocalResourceOverrideRemoved: "network-manager-local-resource-override-removed", EmulatedConditionChanged: "network-manager-emulated-condition-changed", }; /* Controllers/RuntimeManager.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. */ WI.RuntimeManager = class RuntimeManager extends WI.Object { constructor() { super(); this._activeExecutionContext = null; WI.settings.consoleSavedResultAlias.addEventListener(WI.Setting.Event.Changed, function(event) { for (let target of WI.targets) { // COMPATIBILITY (iOS 12.2): Runtime.setSavedResultAlias did not exist. if (target.hasCommand("Runtime.setSavedResultAlias")) target.RuntimeAgent.setSavedResultAlias(WI.settings.consoleSavedResultAlias.value); } }, this); } // Static static supportsAwaitPromise() { // COMPATIBILITY (iOS 12): Runtime.awaitPromise did not exist. return InspectorBackend.hasCommand("Runtime.awaitPromise"); } static preferredSavedResultPrefix() { // COMPATIBILITY (iOS 12.2): Runtime.setSavedResultAlias did not exist. if (!InspectorBackend.hasCommand("Runtime.setSavedResultAlias")) return "$"; return WI.settings.consoleSavedResultAlias.value || "$"; } // Target initializeTarget(target) { target.RuntimeAgent.enable(); if (WI.settings.showJavaScriptTypeInformation.value) target.RuntimeAgent.enableTypeProfiler(); if (WI.settings.enableControlFlowProfiler.value) target.RuntimeAgent.enableControlFlowProfiler(); // COMPATIBILITY (iOS 12.2): Runtime.setSavedResultAlias did not exist. if (target.hasCommand("Runtime.setSavedResultAlias") && WI.settings.consoleSavedResultAlias.value) target.RuntimeAgent.setSavedResultAlias(WI.settings.consoleSavedResultAlias.value); } // Public get activeExecutionContext() { return this._activeExecutionContext; } set activeExecutionContext(executionContext) { if (this._activeExecutionContext === executionContext) return; this._activeExecutionContext = executionContext; this.dispatchEventToListeners(WI.RuntimeManager.Event.ActiveExecutionContextChanged); } evaluateInInspectedWindow(expression, options, callback) { if (!this._activeExecutionContext) { callback(null, false); return; } let {objectGroup, includeCommandLineAPI, doNotPauseOnExceptionsAndMuteConsole, returnByValue, generatePreview, saveResult, emulateUserGesture, sourceURLAppender} = options; includeCommandLineAPI = includeCommandLineAPI || false; doNotPauseOnExceptionsAndMuteConsole = doNotPauseOnExceptionsAndMuteConsole || false; returnByValue = returnByValue || false; generatePreview = generatePreview || false; saveResult = saveResult || false; emulateUserGesture = emulateUserGesture || false; sourceURLAppender = sourceURLAppender || appendWebInspectorSourceURL; console.assert(objectGroup, "RuntimeManager.evaluateInInspectedWindow should always be called with an objectGroup"); console.assert(typeof sourceURLAppender === "function"); if (!expression) { // There is no expression, so the completion should happen against global properties. expression = "this"; } else if (/^\s*\{/.test(expression) && /\}\s*$/.test(expression)) { // Transform {a:1} to ({a:1}) so it is treated like an object literal instead of a block with a label. expression = "(" + expression + ")"; } else if (/\bawait\b/.test(expression)) { // Transform `await ` into an async function assignment. expression = this._tryApplyAwaitConvenience(expression); } expression = sourceURLAppender(expression); let target = this._activeExecutionContext.target; let executionContextId = this._activeExecutionContext.id; if (WI.debuggerManager.activeCallFrame) { target = WI.debuggerManager.activeCallFrame.target; executionContextId = target.executionContext.id; } function evalCallback(error, result, wasThrown, savedResultIndex) { this.dispatchEventToListeners(WI.RuntimeManager.Event.DidEvaluate, {objectGroup}); if (error) { console.error(error); callback(null, false); return; } if (returnByValue) callback(null, wasThrown, wasThrown ? null : result, savedResultIndex); else callback(WI.RemoteObject.fromPayload(result, target), wasThrown, savedResultIndex); } if (WI.debuggerManager.activeCallFrame) { target.DebuggerAgent.evaluateOnCallFrame.invoke({ callFrameId: WI.debuggerManager.activeCallFrame.id, expression, objectGroup, includeCommandLineAPI, doNotPauseOnExceptionsAndMuteConsole, returnByValue, generatePreview, saveResult, emulateUserGesture, // COMPATIBILITY (iOS 13): "emulateUserGesture" did not exist yet. }, evalCallback.bind(this)); return; } target.RuntimeAgent.evaluate.invoke({ expression, objectGroup, includeCommandLineAPI, doNotPauseOnExceptionsAndMuteConsole, contextId: executionContextId, returnByValue, generatePreview, saveResult, emulateUserGesture, // COMPATIBILITY (iOS 12.2): "emulateUserGesture" did not exist yet. }, evalCallback.bind(this)); } saveResult(remoteObject, callback) { console.assert(remoteObject instanceof WI.RemoteObject); let target = this._activeExecutionContext.target; let executionContextId = this._activeExecutionContext.id; function mycallback(error, savedResultIndex) { callback(savedResultIndex); } if (remoteObject.objectId) target.RuntimeAgent.saveResult(remoteObject.asCallArgument(), mycallback); else target.RuntimeAgent.saveResult(remoteObject.asCallArgument(), executionContextId, mycallback); } // Private _tryApplyAwaitConvenience(originalExpression) { let esprimaSyntaxTree; // Do not transform if the original code parses just fine. try { esprima.parse(originalExpression); return originalExpression; } catch { } // Do not transform if the async function version does not parse. try { esprimaSyntaxTree = esprima.parse("(async function(){" + originalExpression + "})"); } catch { return originalExpression; } // Assert expected AST produced by our wrapping code. console.assert(esprimaSyntaxTree.type === "Program"); console.assert(esprimaSyntaxTree.body.length === 1); console.assert(esprimaSyntaxTree.body[0].type === "ExpressionStatement"); console.assert(esprimaSyntaxTree.body[0].expression.type === "FunctionExpression"); console.assert(esprimaSyntaxTree.body[0].expression.async); console.assert(esprimaSyntaxTree.body[0].expression.body.type === "BlockStatement"); // Do not transform if there is more than one statement. let asyncFunctionBlock = esprimaSyntaxTree.body[0].expression.body; if (asyncFunctionBlock.body.length !== 1) return originalExpression; // Extract the variable name for transformation. let variableName; let anonymous = false; let declarationKind = "var"; let awaitPortion; let statement = asyncFunctionBlock.body[0]; if (statement.type === "ExpressionStatement" && statement.expression.type === "AwaitExpression") { // await anonymous = true; } else if (statement.type === "ExpressionStatement" && statement.expression.type === "AssignmentExpression" && statement.expression.right.type === "AwaitExpression" && statement.expression.left.type === "Identifier") { // x = await variableName = statement.expression.left.name; awaitPortion = originalExpression.substring(originalExpression.indexOf("await")); } else if (statement.type === "VariableDeclaration" && statement.declarations.length === 1 && statement.declarations[0].init.type === "AwaitExpression" && statement.declarations[0].id.type === "Identifier") { // var x = await variableName = statement.declarations[0].id.name; declarationKind = statement.kind; awaitPortion = originalExpression.substring(originalExpression.indexOf("await")); } else { // Do not transform if this was not one of the simple supported syntaxes. return originalExpression; } if (anonymous) { return ` (async function() { try { let result = ${originalExpression}; console.info("%o", result); } catch (e) { console.error(e); } })(); undefined`; } return `${declarationKind} ${variableName}; (async function() { try { ${variableName} = ${awaitPortion}; console.info("%o", ${variableName}); } catch (e) { console.error(e); } })(); undefined;`; } }; WI.RuntimeManager.ConsoleObjectGroup = "console"; WI.RuntimeManager.TopLevelExecutionContextIdentifier = undefined; WI.RuntimeManager.Event = { DidEvaluate: "runtime-manager-did-evaluate", DefaultExecutionContextChanged: "runtime-manager-default-execution-context-changed", ActiveExecutionContextChanged: "runtime-manager-active-execution-context-changed", }; /* Controllers/TargetManager.js */ /* * Copyright (C) 2016-2018 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.TargetManager = class TargetManager extends WI.Object { constructor() { super(); this._targets = new Map; this._cachedTargetsList = null; this._seenPageTarget = false; this._transitionTimeoutIdentifier = undefined; } // Target initializeTarget(target) { // COMPATIBILITY (iOS 13): Target.setPauseOnStart did not exist yet. if (target.hasCommand("Target.setPauseOnStart")) target.TargetAgent.setPauseOnStart(true); } // Public get targets() { if (!this._cachedTargetsList) this._cachedTargetsList = Array.from(this._targets.values()).filter((target) => !(target instanceof WI.MultiplexingBackendTarget)); return this._cachedTargetsList; } get workerTargets() { return this.targets.filter((target) => target.type === WI.TargetType.Worker); } get allTargets() { return Array.from(this._targets.values()); } targetForIdentifier(targetId) { if (!targetId) return null; for (let target of this._targets.values()) { if (target.identifier === targetId) return target; } return null; } addTarget(target) { console.assert(target); console.assert(!this._targets.has(target.identifier)); this._cachedTargetsList = null; this._targets.set(target.identifier, target); this.dispatchEventToListeners(WI.TargetManager.Event.TargetAdded, {target}); } removeTarget(target) { console.assert(target); console.assert(target !== WI.mainTarget); this._cachedTargetsList = null; this._targets.delete(target.identifier); target.destroy(); this.dispatchEventToListeners(WI.TargetManager.Event.TargetRemoved, {target}); } createMultiplexingBackendTarget() { console.assert(WI.sharedApp.debuggableType === WI.DebuggableType.WebPage); let target = new WI.MultiplexingBackendTarget; target.initialize(); this._initializeBackendTarget(target); // Add the target without dispatching an event. this._targets.set(target.identifier, target); } createDirectBackendTarget() { console.assert(WI.sharedApp.debuggableType !== WI.DebuggableType.WebPage); let target = new WI.DirectBackendTarget; target.initialize(); this._initializeBackendTarget(target); if (WI.sharedApp.debuggableType === WI.DebuggableType.ITML || WI.sharedApp.debuggableType === WI.DebuggableType.Page) this._initializePageTarget(target); this.addTarget(target); } // TargetObserver targetCreated(parentTarget, targetInfo) { let connection = new InspectorBackend.TargetConnection(parentTarget, targetInfo.targetId); let subTarget = this._createTarget(parentTarget, targetInfo, connection); this._checkAndHandlePageTargetTransition(subTarget); subTarget.initialize(); this.addTarget(subTarget); } didCommitProvisionalTarget(parentTarget, previousTargetId, newTargetId) { this.targetDestroyed(previousTargetId); let target = this._targets.get(newTargetId); console.assert(target); if (!target) return; target.didCommitProvisionalTarget(); this._checkAndHandlePageTargetTransition(target); target.connection.dispatchProvisionalMessages(); this.dispatchEventToListeners(WI.TargetManager.Event.DidCommitProvisionalTarget, {previousTargetId, target}); } targetDestroyed(targetId) { let target = this._targets.get(targetId); if (!target) return; this._checkAndHandlePageTargetTermination(target); this.removeTarget(target); } dispatchMessageFromTarget(targetId, message) { let target = this._targets.get(targetId); console.assert(target); if (!target) return; if (target.isProvisional) target.connection.addProvisionalMessage(message); else target.connection.dispatch(message); } // Private _createTarget(parentTarget, targetInfo, connection) { // COMPATIBILITY (iOS 13.0): `Target.TargetInfo.isProvisional` and `Target.TargetInfo.isPaused` did not exist yet. let {targetId, type, isProvisional, isPaused} = targetInfo; switch (type) { case InspectorBackend.Enum.Target.TargetInfoType.Page: return new WI.PageTarget(parentTarget, targetId, WI.UIString("Page"), connection, {isProvisional, isPaused}); case InspectorBackend.Enum.Target.TargetInfoType.Worker: return new WI.WorkerTarget(parentTarget, targetId, WI.UIString("Worker"), connection, {isPaused}); case "serviceworker": // COMPATIBILITY (iOS 13): "serviceworker" was renamed to "service-worker". case InspectorBackend.Enum.Target.TargetInfoType.ServiceWorker: return new WI.WorkerTarget(parentTarget, targetId, WI.UIString("ServiceWorker"), connection, {isPaused}); } throw "Unknown Target type: " + type; } _checkAndHandlePageTargetTransition(target) { if (target.type !== WI.TargetType.Page) return; if (target.isProvisional) return; // First page target. if (!WI.pageTarget && !this._seenPageTarget) { this._seenPageTarget = true; this._initializePageTarget(target); return; } // Transitioning page target. this._transitionPageTarget(target); } _checkAndHandlePageTargetTermination(target) { if (target.type !== WI.TargetType.Page) return; if (target.isProvisional) return; console.assert(target === WI.pageTarget); console.assert(this._seenPageTarget); // Terminating the page target. this._terminatePageTarget(target); // Ensure we transition in a reasonable amount of time, otherwise close. const timeToTransition = 2000; clearTimeout(this._transitionTimeoutIdentifier); this._transitionTimeoutIdentifier = setTimeout(() => { this._transitionTimeoutIdentifier = undefined; if (WI.pageTarget) return; if (WI.isEngineeringBuild) throw new Error("Error: No new pageTarget some time after last page target was terminated. Failed transition?"); WI.close(); }, timeToTransition); } _initializeBackendTarget(target) { console.assert(!WI.mainTarget); WI.backendTarget = target; this._resetMainExecutionContext(); WI._backendTargetAvailablePromise.resolve(); } _initializePageTarget(target) { console.assert(WI.sharedApp.isWebDebuggable() || WI.sharedApp.debuggableType === WI.DebuggableType.ITML); console.assert(target.type === WI.TargetType.Page || target instanceof WI.DirectBackendTarget); WI.pageTarget = target; this._resetMainExecutionContext(); WI._pageTargetAvailablePromise.resolve(); } _transitionPageTarget(target) { console.assert(!WI.pageTarget); console.assert(WI.sharedApp.debuggableType === WI.DebuggableType.WebPage); console.assert(target.type === WI.TargetType.Page); WI.pageTarget = target; this._resetMainExecutionContext(); // Actions to transition the page target. WI.notifications.dispatchEventToListeners(WI.Notification.TransitionPageTarget); WI.domManager.transitionPageTarget(); WI.networkManager.transitionPageTarget(); WI.timelineManager.transitionPageTarget(); } _terminatePageTarget(target) { console.assert(WI.pageTarget); console.assert(WI.pageTarget === target); console.assert(WI.sharedApp.debuggableType === WI.DebuggableType.WebPage); // Remove any Worker targets associated with this page. for (let workerTarget of this.workerTargets) WI.workerManager.workerTerminated(workerTarget.identifier); WI.pageTarget = null; } _resetMainExecutionContext() { if (WI.mainTarget instanceof WI.MultiplexingBackendTarget) return; if (WI.mainTarget.executionContext) WI.runtimeManager.activeExecutionContext = WI.mainTarget.executionContext; } }; WI.TargetManager.Event = { TargetAdded: "target-manager-target-added", TargetRemoved: "target-manager-target-removed", DidCommitProvisionalTarget: "target-manager-provisional-target-committed", }; /* Controllers/TimelineManager.js */ /* * Copyright (C) 2013, 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. */ // FIXME: TimelineManager lacks advanced multi-target support. (Instruments/Profilers per-target) WI.TimelineManager = class TimelineManager extends WI.Object { constructor() { super(); this._enabled = false; WI.Frame.addEventListener(WI.Frame.Event.ProvisionalLoadStarted, this._provisionalLoadStarted, this); WI.Frame.addEventListener(WI.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this); WI.consoleManager.addEventListener(WI.ConsoleManager.Event.MessageAdded, this._handleMessageAdded, this); this._enabledTimelineTypesSetting = new WI.Setting("enabled-instrument-types", WI.TimelineManager.defaultTimelineTypes()); this._capturingState = TimelineManager.CapturingState.Inactive; this._capturingInstrumentCount = 0; this._capturingStartTime = NaN; this._capturingEndTime = NaN; this._initiatedByBackendStart = false; this._initiatedByBackendStop = false; this._isCapturingPageReload = false; this._autoCaptureOnPageLoad = false; this._mainResourceForAutoCapturing = null; this._shouldSetAutoCapturingMainResource = false; this._transitioningPageTarget = false; this._webTimelineScriptRecordsExpectingScriptProfilerEvents = null; this._scriptProfilerRecords = null; this._boundStopCapturing = this.stopCapturing.bind(this); this._stopCapturingTimeout = undefined; this._deadTimeTimeout = undefined; this._lastDeadTimeTickle = 0; } // Agent get domains() { return ["Timeline"]; } activateExtraDomain(domain) { // COMPATIBILITY (iOS 14.0): Inspector.activateExtraDomains was removed in favor of a declared debuggable type console.assert(domain === "Timeline"); for (let target of WI.targets) this.initializeTarget(target); } // Target initializeTarget(target) { if (!this._enabled) return; if (target.hasDomain("Timeline")) { // COMPATIBILITY (iOS 13): Timeline.enable did not exist yet. if (target.hasCommand("Timeline.enable")) target.TimelineAgent.enable(); this._updateAutoCaptureInstruments([target]); target.TimelineAgent.setAutoCaptureEnabled(this._autoCaptureOnPageLoad); } } transitionPageTarget() { this._transitioningPageTarget = true; } // Static static defaultTimelineTypes() { if (WI.sharedApp.debuggableType === WI.DebuggableType.JavaScript || WI.sharedApp.debuggableType === WI.DebuggableType.ITML) { return [ WI.TimelineRecord.Type.Script, WI.TimelineRecord.Type.HeapAllocations, ]; } if (WI.sharedApp.debuggableType === WI.DebuggableType.ServiceWorker) { // FIXME: Support Network Timeline in ServiceWorker. return [ WI.TimelineRecord.Type.Script, WI.TimelineRecord.Type.HeapAllocations, ]; } let defaultTypes = []; if (WI.ScreenshotsInstrument.supported()) defaultTypes.push(WI.TimelineRecord.Type.Screenshots); defaultTypes.push(WI.TimelineRecord.Type.Network); defaultTypes.push(WI.TimelineRecord.Type.Layout); defaultTypes.push(WI.TimelineRecord.Type.Script); defaultTypes.push(WI.TimelineRecord.Type.RenderingFrame); if (WI.CPUInstrument.supported()) defaultTypes.push(WI.TimelineRecord.Type.CPU); return defaultTypes; } static availableTimelineTypes() { let types = WI.TimelineManager.defaultTimelineTypes(); if (WI.sharedApp.debuggableType === WI.DebuggableType.JavaScript || WI.sharedApp.debuggableType === WI.DebuggableType.ServiceWorker || WI.sharedApp.debuggableType === WI.DebuggableType.ITML) return types; types.push(WI.TimelineRecord.Type.Memory); types.push(WI.TimelineRecord.Type.HeapAllocations); if (WI.MediaInstrument.supported()) { let insertionIndex = types.indexOf(WI.TimelineRecord.Type.Layout) + 1; types.insertAtIndex(WI.TimelineRecord.Type.Media, insertionIndex || types.length); } return types; } static synthesizeImportError(message) { message = WI.UIString("Timeline Recording Import Error: %s").format(message); if (window.InspectorTest) { console.error(message); return; } let consoleMessage = new WI.ConsoleMessage(WI.mainTarget, WI.ConsoleMessage.MessageSource.Other, WI.ConsoleMessage.MessageLevel.Error, message); consoleMessage.shouldRevealConsole = true; WI.consoleLogViewController.appendConsoleMessage(consoleMessage); } // Public get capturingState() { return this._capturingState; } reset() { if (this._capturingState === TimelineManager.CapturingState.Starting || this._capturingState === TimelineManager.CapturingState.Active) this.stopCapturing(); this._recordings = []; this._activeRecording = null; this._nextRecordingIdentifier = 1; this._loadNewRecording(); } // The current recording that new timeline records will be appended to, if any. get activeRecording() { console.assert(this._activeRecording || !this.isCapturing()); return this._activeRecording; } get autoCaptureOnPageLoad() { return this._autoCaptureOnPageLoad; } set autoCaptureOnPageLoad(autoCapture) { console.assert(this._enabled); autoCapture = !!autoCapture; if (this._autoCaptureOnPageLoad === autoCapture) return; this._autoCaptureOnPageLoad = autoCapture; for (let target of WI.targets) { if (target.hasCommand("Timeline.setAutoCaptureEnabled")) target.TimelineAgent.setAutoCaptureEnabled(this._autoCaptureOnPageLoad); } } get enabledTimelineTypes() { let availableTimelineTypes = WI.TimelineManager.availableTimelineTypes(); return this._enabledTimelineTypesSetting.value.filter((type) => availableTimelineTypes.includes(type)); } set enabledTimelineTypes(x) { this._enabledTimelineTypesSetting.value = x || []; this._updateAutoCaptureInstruments(WI.targets); } isCapturing() { return this._capturingState !== TimelineManager.CapturingState.Inactive; } isCapturingPageReload() { return this._isCapturingPageReload; } willAutoStop() { return !!this._stopCapturingTimeout; } relaxAutoStop() { if (this._stopCapturingTimeout) { clearTimeout(this._stopCapturingTimeout); this._stopCapturingTimeout = undefined; } if (this._deadTimeTimeout) { clearTimeout(this._deadTimeTimeout); this._deadTimeTimeout = undefined; } } enable() { if (this._enabled) return; this._enabled = true; this.reset(); for (let target of WI.targets) this.initializeTarget(target); } disable() { if (!this._enabled) return; this.reset(); for (let target of WI.targets) { // COMPATIBILITY (iOS 13): Timeline.disable did not exist yet. if (target.hasCommand("Timeline.disable")) target.TimelineAgent.disable(); } this._enabled = false; } startCapturing(shouldCreateRecording) { console.assert(this._enabled); console.assert(this._capturingState === TimelineManager.CapturingState.Stopping || this._capturingState === TimelineManager.CapturingState.Inactive, "TimelineManager is already capturing."); if (this._capturingState !== TimelineManager.CapturingState.Stopping && this._capturingState !== TimelineManager.CapturingState.Inactive) return; if (!this._activeRecording || shouldCreateRecording) this._loadNewRecording(); this._updateCapturingState(TimelineManager.CapturingState.Starting); this._capturingStartTime = NaN; this._activeRecording.start(this._initiatedByBackendStart); } stopCapturing() { console.assert(this._enabled); console.assert(this._capturingState === TimelineManager.CapturingState.Starting || this._capturingState === TimelineManager.CapturingState.Active, "TimelineManager is not capturing."); if (this._capturingState !== TimelineManager.CapturingState.Starting && this._capturingState !== TimelineManager.CapturingState.Active) return; this._updateCapturingState(TimelineManager.CapturingState.Stopping); this._capturingEndTime = NaN; this._activeRecording.stop(this._initiatedByBackendStop); } async processJSON({filename, json, error}) { if (error) { WI.TimelineManager.synthesizeImportError(error); return; } if (typeof json !== "object" || json === null) { WI.TimelineManager.synthesizeImportError(WI.UIString("invalid JSON")); return; } if (!json.recording || typeof json.recording !== "object" || !json.overview || typeof json.overview !== "object" || typeof json.version !== "number") { WI.TimelineManager.synthesizeImportError(WI.UIString("invalid JSON")); return; } if (json.version !== WI.TimelineRecording.SerializationVersion) { WI.NetworkManager.synthesizeImportError(WI.UIString("unsupported version")); return; } let recordingData = json.recording; let overviewData = json.overview; let identifier = this._nextRecordingIdentifier++; let newRecording = await WI.TimelineRecording.import(identifier, recordingData, filename); this._recordings.push(newRecording); this.dispatchEventToListeners(WI.TimelineManager.Event.RecordingCreated, {recording: newRecording}); if (this._capturingState === TimelineManager.CapturingState.Starting || this._capturingState === TimelineManager.CapturingState.Active) this.stopCapturing(); let oldRecording = this._activeRecording; if (oldRecording) { const importing = true; oldRecording.unloaded(importing); } this._activeRecording = newRecording; this.dispatchEventToListeners(WI.TimelineManager.Event.RecordingLoaded, {oldRecording}); this.dispatchEventToListeners(WI.TimelineManager.Event.RecordingImported, {overviewData}); } computeElapsedTime(timestamp) { if (!this._activeRecording) return 0; return this._activeRecording.computeElapsedTime(timestamp); } scriptProfilerIsTracking() { return this._scriptProfilerRecords !== null; } // ConsoleObserver heapSnapshotAdded(timestamp, snapshot) { if (!this._enabled) return; this._addRecord(new WI.HeapAllocationsTimelineRecord(timestamp, snapshot)); } // TimelineObserver capturingStarted(startTime) { // The frontend didn't start capturing, so this was a programmatic start. if (this._capturingState === TimelineManager.CapturingState.Inactive) { this._initiatedByBackendStart = true; this._activeRecording.addScriptInstrumentForProgrammaticCapture(); this.startCapturing(); } if (!isNaN(startTime)) { if (isNaN(this._capturingStartTime) || startTime < this._capturingStartTime) this._capturingStartTime = startTime; this._activeRecording.initializeTimeBoundsIfNecessary(startTime); } this._capturingInstrumentCount++; console.assert(this._capturingInstrumentCount); if (this._capturingInstrumentCount > 1) return; if (this._capturingState === TimelineManager.CapturingState.Active) return; this._lastDeadTimeTickle = 0; this._webTimelineScriptRecordsExpectingScriptProfilerEvents = []; this._activeRecording.capturingStarted(this._capturingStartTime); WI.settings.timelinesAutoStop.addEventListener(WI.Setting.Event.Changed, this._handleTimelinesAutoStopSettingChanged, this); WI.Frame.addEventListener(WI.Frame.Event.ResourceWasAdded, this._resourceWasAdded, this); WI.Target.addEventListener(WI.Target.Event.ResourceAdded, this._resourceWasAdded, this); WI.heapManager.addEventListener(WI.HeapManager.Event.GarbageCollected, this._garbageCollected, this); WI.memoryManager.addEventListener(WI.MemoryManager.Event.MemoryPressure, this._memoryPressure, this); WI.DOMNode.addEventListener(WI.DOMNode.Event.DidFireEvent, this._handleDOMNodeDidFireEvent, this); WI.DOMNode.addEventListener(WI.DOMNode.Event.PowerEfficientPlaybackStateChanged, this._handleDOMNodePowerEfficientPlaybackStateChanged, this); this._updateCapturingState(TimelineManager.CapturingState.Active, {startTime: this._capturingStartTime}); } capturingStopped(endTime) { // The frontend didn't stop capturing, so this was a programmatic stop. if (this._capturingState === TimelineManager.CapturingState.Active) { this._initiatedByBackendStop = true; this.stopCapturing(); } if (!isNaN(endTime)) { if (isNaN(this._capturingEndTime) || endTime > this._capturingEndTime) this._capturingEndTime = endTime; } this._capturingInstrumentCount--; console.assert(this._capturingInstrumentCount >= 0); if (this._capturingInstrumentCount) return; if (this._capturingState === TimelineManager.CapturingState.Inactive) return; WI.DOMNode.removeEventListener(WI.DOMNode.Event.DidFireEvent, this._handleDOMNodeDidFireEvent, this); WI.DOMNode.removeEventListener(WI.DOMNode.Event.PowerEfficientPlaybackStateChanged, this._handleDOMNodePowerEfficientPlaybackStateChanged, this); WI.heapManager.removeEventListener(WI.HeapManager.Event.GarbageCollected, this._garbageCollected, this); WI.memoryManager.removeEventListener(WI.MemoryManager.Event.MemoryPressure, this._memoryPressure, this); WI.Target.removeEventListener(WI.Target.Event.ResourceAdded, this._resourceWasAdded, this); WI.Frame.removeEventListener(WI.Frame.Event.ResourceWasAdded, this._resourceWasAdded, this); WI.settings.timelinesAutoStop.removeEventListener(WI.Setting.Event.Changed, this._handleTimelinesAutoStopSettingChanged, this); this._activeRecording.capturingStopped(this._capturingEndTime); this.relaxAutoStop(); this._isCapturingPageReload = false; this._shouldSetAutoCapturingMainResource = false; this._mainResourceForAutoCapturing = null; this._initiatedByBackendStart = false; this._initiatedByBackendStop = false; this._updateCapturingState(TimelineManager.CapturingState.Inactive, {endTime: this._capturingEndTime}); } autoCaptureStarted() { console.assert(this._enabled); let waitingForCapturingStartedEvent = this._capturingState === TimelineManager.CapturingState.Starting; if (this._capturingState === TimelineManager.CapturingState.Starting || this._capturingState === TimelineManager.CapturingState.Active) this.stopCapturing(); this._initiatedByBackendStart = true; // We may already have an fresh TimelineRecording created if autoCaptureStarted is received // between sending the Timeline.start command and receiving Timeline.capturingStarted event. // In that case, there is no need to call startCapturing again. Reuse the fresh recording. if (!waitingForCapturingStartedEvent) { const createNewRecording = true; this.startCapturing(createNewRecording); } this._shouldSetAutoCapturingMainResource = true; } eventRecorded(recordPayload) { if (!this._enabled) return; console.assert(this.isCapturing()); if (!this.isCapturing()) return; var records = []; // Iterate over the records tree using a stack. Doing this recursively has // been known to cause a call stack overflow. https://webkit.org/b/79106 var stack = [{array: [recordPayload], parent: null, parentRecord: null, index: 0}]; while (stack.length) { var entry = stack.lastValue; var recordPayloads = entry.array; if (entry.index < recordPayloads.length) { var recordPayload = recordPayloads[entry.index]; var record = this._processEvent(recordPayload, entry.parent); if (record) { record.parent = entry.parentRecord; records.push(record); if (entry.parentRecord) entry.parentRecord.children.push(record); } if (recordPayload.children && recordPayload.children.length) stack.push({array: recordPayload.children, parent: recordPayload, parentRecord: record || entry.parentRecord, index: 0}); ++entry.index; } else stack.pop(); } for (var record of records) { if (record.type === WI.TimelineRecord.Type.RenderingFrame) { if (!record.children.length) continue; record.setupFrameIndex(); } this._addRecord(record); } } // PageObserver pageDOMContentLoadedEventFired(timestamp) { if (!this._enabled) return; console.assert(this._activeRecording); let computedTimestamp = this._activeRecording.computeElapsedTime(timestamp); if (WI.networkManager.mainFrame) WI.networkManager.mainFrame.markDOMContentReadyEvent(computedTimestamp); let eventMarker = new WI.TimelineMarker(computedTimestamp, WI.TimelineMarker.Type.DOMContentEvent); this._activeRecording.addEventMarker(eventMarker); } pageLoadEventFired(timestamp) { if (!this._enabled) return; console.assert(this._activeRecording); let computedTimestamp = this._activeRecording.computeElapsedTime(timestamp); if (WI.networkManager.mainFrame) WI.networkManager.mainFrame.markLoadEvent(computedTimestamp); let eventMarker = new WI.TimelineMarker(computedTimestamp, WI.TimelineMarker.Type.LoadEvent); this._activeRecording.addEventMarker(eventMarker); this._stopAutoRecordingSoon(); } // CPUProfilerObserver cpuProfilerTrackingStarted(timestamp) { this.capturingStarted(timestamp); } cpuProfilerTrackingUpdated(event) { if (!this._enabled) return; console.assert(this.isCapturing()); if (!this.isCapturing()) return; this._addRecord(new WI.CPUTimelineRecord(event)); } cpuProfilerTrackingCompleted(timestamp) { this.capturingStopped(timestamp); } // ScriptProfilerObserver scriptProfilerTrackingStarted(timestamp) { this._scriptProfilerRecords = []; this.capturingStarted(timestamp); } scriptProfilerTrackingUpdated(event) { if (!this._enabled) return; let {startTime, endTime, type} = event; let scriptRecordType = this._scriptProfilerTypeToScriptTimelineRecordType(type); let record = new WI.ScriptTimelineRecord(scriptRecordType, startTime, endTime, null, null, null, null); record.__scriptProfilerType = type; this._scriptProfilerRecords.push(record); // "Other" events, generated by Web content, will have wrapping Timeline records // and need to be merged. Non-Other events, generated purely by the JavaScript // engine or outside of the page via APIs, will not have wrapping Timeline // records, so these records can just be added right now. if (type !== InspectorBackend.Enum.ScriptProfiler.EventType.Other) this._addRecord(record); } scriptProfilerTrackingCompleted(timestamp, samples) { if (this._enabled) { console.assert(!this._webTimelineScriptRecordsExpectingScriptProfilerEvents || this._scriptProfilerRecords.length >= this._webTimelineScriptRecordsExpectingScriptProfilerEvents.length); if (samples) { let {stackTraces} = samples; let topDownCallingContextTree = this._activeRecording.topDownCallingContextTree; // Calculate a per-sample duration. let timestampIndex = 0; let timestampCount = stackTraces.length; let sampleDurations = new Array(timestampCount); let sampleDurationIndex = 0; const defaultDuration = 1 / 1000; // 1ms. for (let i = 0; i < this._scriptProfilerRecords.length; ++i) { let record = this._scriptProfilerRecords[i]; // Use a default duration for timestamps recorded outside of ScriptProfiler events. while (timestampIndex < timestampCount && stackTraces[timestampIndex].timestamp < record.startTime) { sampleDurations[sampleDurationIndex++] = defaultDuration; timestampIndex++; } // Average the duration per sample across all samples during the record. let samplesInRecord = 0; while (timestampIndex < timestampCount && stackTraces[timestampIndex].timestamp < record.endTime) { timestampIndex++; samplesInRecord++; } if (samplesInRecord) { let averageDuration = (record.endTime - record.startTime) / samplesInRecord; sampleDurations.fill(averageDuration, sampleDurationIndex, sampleDurationIndex + samplesInRecord); sampleDurationIndex += samplesInRecord; } } // Use a default duration for timestamps recorded outside of ScriptProfiler events. if (timestampIndex < timestampCount) sampleDurations.fill(defaultDuration, sampleDurationIndex); this._activeRecording.initializeCallingContextTrees(stackTraces, sampleDurations); // FIXME: This transformation should not be needed after introducing ProfileView. // Once we eliminate ProfileNodeTreeElements and ProfileNodeDataGridNodes. // Web Inspector: Timelines UI redesign: Remove TimelineSidebarPanel for (let i = 0; i < this._scriptProfilerRecords.length; ++i) { let record = this._scriptProfilerRecords[i]; record.profilePayload = topDownCallingContextTree.toCPUProfilePayload(record.startTime, record.endTime); } } // Associate the ScriptProfiler created records with Web Timeline records. // Filter out the already added ScriptProfiler events which should not have been wrapped. if (WI.sharedApp.debuggableType !== WI.DebuggableType.JavaScript && WI.sharedApp.debuggableType !== WI.DebuggableType.ITML) { this._scriptProfilerRecords = this._scriptProfilerRecords.filter((x) => x.__scriptProfilerType === InspectorBackend.Enum.ScriptProfiler.EventType.Other); this._mergeScriptProfileRecords(); } this._scriptProfilerRecords = null; let timeline = this._activeRecording.timelineForRecordType(WI.TimelineRecord.Type.Script); timeline.refresh(); } this.capturingStopped(timestamp); } // MemoryObserver memoryTrackingStarted(timestamp) { this.capturingStarted(timestamp); } memoryTrackingUpdated(event) { if (!this._enabled) return; console.assert(this.isCapturing()); if (!this.isCapturing()) return; this._addRecord(new WI.MemoryTimelineRecord(event.timestamp, event.categories)); } memoryTrackingCompleted(timestamp) { this.capturingStopped(timestamp); } // HeapObserver heapTrackingStarted(timestamp, snapshot) { this.capturingStarted(timestamp); if (this._enabled) this._addRecord(new WI.HeapAllocationsTimelineRecord(timestamp, snapshot)); } heapTrackingCompleted(timestamp, snapshot) { if (this._enabled) this._addRecord(new WI.HeapAllocationsTimelineRecord(timestamp, snapshot)); this.capturingStopped(); } // AnimationObserver animationTrackingStarted(timestamp) { this.capturingStarted(timestamp); } animationTrackingUpdated(timestamp, event) { if (!this._enabled) return; console.assert(this.isCapturing()); if (!this.isCapturing()) return; let mediaTimeline = this._activeRecording.timelineForRecordType(WI.TimelineRecord.Type.Media); console.assert(mediaTimeline); let record = mediaTimeline.recordForTrackingAnimationId(event.trackingAnimationId); if (!record) { let details = { trackingAnimationId: event.trackingAnimationId, }; let eventType; if (event.animationName) { eventType = WI.MediaTimelineRecord.EventType.CSSAnimation; details.animationName = event.animationName; } else if (event.transitionProperty) { eventType = WI.MediaTimelineRecord.EventType.CSSTransition; details.transitionProperty = event.transitionProperty; } else { WI.reportInternalError(`Unknown event type for event '${JSON.stringify(event)}'`); return; } let domNode = WI.domManager.nodeForId(event.nodeId); console.assert(domNode); record = new WI.MediaTimelineRecord(eventType, domNode, details); this._addRecord(record); } record.updateAnimationState(timestamp, event.animationState); } animationTrackingCompleted(timestamp) { this.capturingStopped(timestamp); } // Private _updateCapturingState(state, data = {}) { if (this._capturingState === state) return; this._capturingState = state; this.dispatchEventToListeners(TimelineManager.Event.CapturingStateChanged, data); } _processRecord(recordPayload, parentRecordPayload) { console.assert(this.isCapturing()); var startTime = this._activeRecording.computeElapsedTime(recordPayload.startTime); var endTime = this._activeRecording.computeElapsedTime(recordPayload.endTime); let stackTrace = this._stackTraceFromPayload(recordPayload.stackTrace); var significantCallFrame = null; if (stackTrace) { for (let callFrame of stackTrace.callFrames) { if (callFrame.nativeCode) continue; significantCallFrame = callFrame; break; } } var sourceCodeLocation = significantCallFrame && significantCallFrame.sourceCodeLocation; switch (recordPayload.type) { case InspectorBackend.Enum.Timeline.EventType.ScheduleStyleRecalculation: console.assert(isNaN(endTime)); // Pass the startTime as the endTime since this record type has no duration. return new WI.LayoutTimelineRecord(WI.LayoutTimelineRecord.EventType.InvalidateStyles, startTime, startTime, stackTrace, sourceCodeLocation); case InspectorBackend.Enum.Timeline.EventType.RecalculateStyles: return new WI.LayoutTimelineRecord(WI.LayoutTimelineRecord.EventType.RecalculateStyles, startTime, endTime, stackTrace, sourceCodeLocation); case InspectorBackend.Enum.Timeline.EventType.InvalidateLayout: console.assert(isNaN(endTime)); // Pass the startTime as the endTime since this record type has no duration. return new WI.LayoutTimelineRecord(WI.LayoutTimelineRecord.EventType.InvalidateLayout, startTime, startTime, stackTrace, sourceCodeLocation); case InspectorBackend.Enum.Timeline.EventType.Layout: var layoutRecordType = sourceCodeLocation ? WI.LayoutTimelineRecord.EventType.ForcedLayout : WI.LayoutTimelineRecord.EventType.Layout; var quad = new WI.Quad(recordPayload.data.root); return new WI.LayoutTimelineRecord(layoutRecordType, startTime, endTime, stackTrace, sourceCodeLocation, quad); case InspectorBackend.Enum.Timeline.EventType.Paint: var quad = new WI.Quad(recordPayload.data.clip); return new WI.LayoutTimelineRecord(WI.LayoutTimelineRecord.EventType.Paint, startTime, endTime, stackTrace, sourceCodeLocation, quad); case InspectorBackend.Enum.Timeline.EventType.Composite: return new WI.LayoutTimelineRecord(WI.LayoutTimelineRecord.EventType.Composite, startTime, endTime, stackTrace, sourceCodeLocation); case InspectorBackend.Enum.Timeline.EventType.RenderingFrame: if (!recordPayload.children || !recordPayload.children.length) return null; return new WI.RenderingFrameTimelineRecord(startTime, endTime, recordPayload.data.name); case InspectorBackend.Enum.Timeline.EventType.EvaluateScript: if (!sourceCodeLocation) { var mainFrame = WI.networkManager.mainFrame; const recursivelySearchChildFrames = true; let scriptResource = mainFrame.url === recordPayload.data.url ? mainFrame.mainResource : mainFrame.resourcesForURL(recordPayload.data.url, recursivelySearchChildFrames).lastValue; if (scriptResource) { // The lineNumber is 1-based, but we expect 0-based. let lineNumber = recordPayload.data.lineNumber - 1; let columnNumber = "columnNumber" in recordPayload.data ? recordPayload.data.columnNumber - 1 : 0; sourceCodeLocation = scriptResource.createSourceCodeLocation(lineNumber, columnNumber); } } var profileData = recordPayload.data.profile; var record; switch (parentRecordPayload && parentRecordPayload.type) { case InspectorBackend.Enum.Timeline.EventType.TimerFire: record = new WI.ScriptTimelineRecord(WI.ScriptTimelineRecord.EventType.TimerFired, startTime, endTime, stackTrace, sourceCodeLocation, parentRecordPayload.data.timerId, profileData); break; case InspectorBackend.Enum.Timeline.EventType.ObserverCallback: record = new WI.ScriptTimelineRecord(WI.ScriptTimelineRecord.EventType.ObserverCallback, startTime, endTime, stackTrace, sourceCodeLocation, parentRecordPayload.data.type, profileData); break; case InspectorBackend.Enum.Timeline.EventType.FireAnimationFrame: record = new WI.ScriptTimelineRecord(WI.ScriptTimelineRecord.EventType.AnimationFrameFired, startTime, endTime, stackTrace, sourceCodeLocation, parentRecordPayload.data.id, profileData); break; default: record = new WI.ScriptTimelineRecord(WI.ScriptTimelineRecord.EventType.ScriptEvaluated, startTime, endTime, stackTrace, sourceCodeLocation, null, profileData); break; } this._webTimelineScriptRecordsExpectingScriptProfilerEvents.push(record); return record; case InspectorBackend.Enum.Timeline.EventType.ConsoleProfile: return new WI.ScriptTimelineRecord(WI.ScriptTimelineRecord.EventType.ConsoleProfileRecorded, startTime, endTime, stackTrace, sourceCodeLocation, recordPayload.data.title); case InspectorBackend.Enum.Timeline.EventType.TimerFire: case InspectorBackend.Enum.Timeline.EventType.EventDispatch: case InspectorBackend.Enum.Timeline.EventType.FireAnimationFrame: case InspectorBackend.Enum.Timeline.EventType.ObserverCallback: // These are handled when we see the child FunctionCall or EvaluateScript. break; case InspectorBackend.Enum.Timeline.EventType.FunctionCall: // FunctionCall always happens as a child of another record, and since the FunctionCall record // has useful info we just make the timeline record here (combining the data from both records). if (!parentRecordPayload) { console.warn("Unexpectedly received a FunctionCall timeline record without a parent record"); break; } if (!sourceCodeLocation) { var mainFrame = WI.networkManager.mainFrame; const recursivelySearchChildFrames = true; let scriptResource = mainFrame.url === recordPayload.data.scriptName ? mainFrame.mainResource : mainFrame.resourcesForURL(recordPayload.data.scriptName, recursivelySearchChildFrames).lastValue; if (scriptResource) { // The lineNumber is 1-based, but we expect 0-based. let lineNumber = recordPayload.data.scriptLine - 1; let columnNumber = "scriptColumn" in recordPayload.data ? recordPayload.data.scriptColumn - 1 : 0; sourceCodeLocation = scriptResource.createSourceCodeLocation(lineNumber, columnNumber); } } var profileData = recordPayload.data.profile; var record; switch (parentRecordPayload.type) { case InspectorBackend.Enum.Timeline.EventType.TimerFire: record = new WI.ScriptTimelineRecord(WI.ScriptTimelineRecord.EventType.TimerFired, startTime, endTime, stackTrace, sourceCodeLocation, parentRecordPayload.data.timerId, profileData); break; case InspectorBackend.Enum.Timeline.EventType.EventDispatch: record = new WI.ScriptTimelineRecord(WI.ScriptTimelineRecord.EventType.EventDispatched, startTime, endTime, stackTrace, sourceCodeLocation, parentRecordPayload.data.type, profileData, parentRecordPayload.data); break; case InspectorBackend.Enum.Timeline.EventType.ObserverCallback: record = new WI.ScriptTimelineRecord(WI.ScriptTimelineRecord.EventType.ObserverCallback, startTime, endTime, stackTrace, sourceCodeLocation, parentRecordPayload.data.type, profileData); break; case InspectorBackend.Enum.Timeline.EventType.FireAnimationFrame: record = new WI.ScriptTimelineRecord(WI.ScriptTimelineRecord.EventType.AnimationFrameFired, startTime, endTime, stackTrace, sourceCodeLocation, parentRecordPayload.data.id, profileData); break; case InspectorBackend.Enum.Timeline.EventType.FunctionCall: record = new WI.ScriptTimelineRecord(WI.ScriptTimelineRecord.EventType.ScriptEvaluated, startTime, endTime, stackTrace, sourceCodeLocation, parentRecordPayload.data.id, profileData); break; case InspectorBackend.Enum.Timeline.EventType.RenderingFrame: record = new WI.ScriptTimelineRecord(WI.ScriptTimelineRecord.EventType.ScriptEvaluated, startTime, endTime, stackTrace, sourceCodeLocation, parentRecordPayload.data.id, profileData); break; default: console.assert(false, "Missed FunctionCall embedded inside of: " + parentRecordPayload.type); break; } if (record) { this._webTimelineScriptRecordsExpectingScriptProfilerEvents.push(record); return record; } break; case InspectorBackend.Enum.Timeline.EventType.ProbeSample: { let probe = WI.debuggerManager.probeForIdentifier(recordPayload.data.probeId); if (probe.breakpoint instanceof WI.JavaScriptBreakpoint) sourceCodeLocation = probe.breakpoint.sourceCodeLocation; // Pass the startTime as the endTime since this record type has no duration. return new WI.ScriptTimelineRecord(WI.ScriptTimelineRecord.EventType.ProbeSampleRecorded, startTime, startTime, stackTrace, sourceCodeLocation, recordPayload.data.probeId); } case InspectorBackend.Enum.Timeline.EventType.TimerInstall: console.assert(isNaN(endTime)); // Pass the startTime as the endTime since this record type has no duration. var timerDetails = {timerId: recordPayload.data.timerId, timeout: recordPayload.data.timeout, repeating: !recordPayload.data.singleShot}; return new WI.ScriptTimelineRecord(WI.ScriptTimelineRecord.EventType.TimerInstalled, startTime, startTime, stackTrace, sourceCodeLocation, timerDetails); case InspectorBackend.Enum.Timeline.EventType.TimerRemove: console.assert(isNaN(endTime)); // Pass the startTime as the endTime since this record type has no duration. return new WI.ScriptTimelineRecord(WI.ScriptTimelineRecord.EventType.TimerRemoved, startTime, startTime, stackTrace, sourceCodeLocation, recordPayload.data.timerId); case InspectorBackend.Enum.Timeline.EventType.RequestAnimationFrame: console.assert(isNaN(endTime)); // Pass the startTime as the endTime since this record type has no duration. return new WI.ScriptTimelineRecord(WI.ScriptTimelineRecord.EventType.AnimationFrameRequested, startTime, startTime, stackTrace, sourceCodeLocation, recordPayload.data.id); case InspectorBackend.Enum.Timeline.EventType.CancelAnimationFrame: console.assert(isNaN(endTime)); // Pass the startTime as the endTime since this record type has no duration. return new WI.ScriptTimelineRecord(WI.ScriptTimelineRecord.EventType.AnimationFrameCanceled, startTime, startTime, stackTrace, sourceCodeLocation, recordPayload.data.id); case InspectorBackend.Enum.Timeline.EventType.Screenshot: console.assert(isNaN(endTime)); return new WI.ScreenshotsTimelineRecord(startTime, recordPayload.data.imageData); default: console.error("Missing handling of Timeline Event Type: " + recordPayload.type); } return null; } _processEvent(recordPayload, parentRecordPayload) { console.assert(this.isCapturing()); switch (recordPayload.type) { case InspectorBackend.Enum.Timeline.EventType.TimeStamp: var timestamp = this._activeRecording.computeElapsedTime(recordPayload.startTime); var eventMarker = new WI.TimelineMarker(timestamp, WI.TimelineMarker.Type.TimeStamp, recordPayload.data.message); this._activeRecording.addEventMarker(eventMarker); break; case InspectorBackend.Enum.Timeline.EventType.Time: case InspectorBackend.Enum.Timeline.EventType.TimeEnd: // FIXME: Web Inspector: Show console.time/timeEnd ranges in Timeline // FIXME: Make use of "message" payload properties. break; default: return this._processRecord(recordPayload, parentRecordPayload); } return null; } _loadNewRecording() { if (this._activeRecording && this._activeRecording.isEmpty()) return; let instruments = this.enabledTimelineTypes.map((type) => WI.Instrument.createForTimelineType(type)); let identifier = this._nextRecordingIdentifier++; let newRecording = new WI.TimelineRecording(identifier, WI.UIString("Timeline Recording %d").format(identifier), instruments); this._recordings.push(newRecording); this.dispatchEventToListeners(WI.TimelineManager.Event.RecordingCreated, {recording: newRecording}); if (this._capturingState === TimelineManager.CapturingState.Starting || this._capturingState === TimelineManager.CapturingState.Active) this.stopCapturing(); var oldRecording = this._activeRecording; if (oldRecording) oldRecording.unloaded(); this._activeRecording = newRecording; this.dispatchEventToListeners(WI.TimelineManager.Event.RecordingLoaded, {oldRecording}); } _stackTraceFromPayload(payload) { let target = WI.assumingMainTarget(); // COMPATIBILITY (macOS 13.0, iOS 16.0): `stackTrace` was an array of `Console.CallFrame`. if (Array.isArray(payload)) payload = {callFrames: payload}; return WI.StackTrace.fromPayload(target, payload); } _addRecord(record) { this._activeRecording.addRecord(record); // Only worry about dead time after the load event. if (WI.networkManager.mainFrame && isNaN(WI.networkManager.mainFrame.loadEventTimestamp)) this._resetAutoRecordingDeadTimeTimeout(); } _attemptAutoCapturingForFrame(frame) { if (!this._autoCaptureOnPageLoad) return false; if (!frame.isMainFrame()) return false; if (!InspectorBackend.hasDomain("Timeline")) return false; if (!this._shouldSetAutoCapturingMainResource) return false; console.assert(this.isCapturing(), "We saw autoCaptureStarted so we should already be capturing"); let mainResource = frame.provisionalMainResource || frame.mainResource; if (mainResource === this._mainResourceForAutoCapturing) return false; let oldMainResource = frame.mainResource || null; this._isCapturingPageReload = oldMainResource !== null && oldMainResource.url === mainResource.url; this._mainResourceForAutoCapturing = mainResource; this._addRecord(new WI.ResourceTimelineRecord(mainResource)); this._resetAutoRecordingMaxTimeTimeout(); this._shouldSetAutoCapturingMainResource = false; return true; } _legacyAttemptStartAutoCapturingForFrame(frame) { if (this.isCapturing() && !this._mainResourceForAutoCapturing) return false; let mainResource = frame.provisionalMainResource || frame.mainResource; if (mainResource === this._mainResourceForAutoCapturing) return false; let oldMainResource = frame.mainResource || null; this._isCapturingPageReload = oldMainResource !== null && oldMainResource.url === mainResource.url; if (this._capturingState === TimelineManager.CapturingState.Starting || this._capturingState === TimelineManager.CapturingState.Active) this.stopCapturing(); this._mainResourceForAutoCapturing = mainResource; this._loadNewRecording(); this.startCapturing(); this._addRecord(new WI.ResourceTimelineRecord(mainResource)); this._resetAutoRecordingMaxTimeTimeout(); return true; } _stopAutoRecordingSoon() { if (!WI.settings.timelinesAutoStop.value) return; // Only auto stop when auto capturing. if (!this.isCapturing() || !this._mainResourceForAutoCapturing) return; if (this._stopCapturingTimeout) clearTimeout(this._stopCapturingTimeout); this._stopCapturingTimeout = setTimeout(this._boundStopCapturing, WI.TimelineManager.MaximumAutoRecordDurationAfterLoadEvent); } _resetAutoRecordingMaxTimeTimeout() { if (!WI.settings.timelinesAutoStop.value) return; if (this._stopCapturingTimeout) clearTimeout(this._stopCapturingTimeout); this._stopCapturingTimeout = setTimeout(this._boundStopCapturing, WI.TimelineManager.MaximumAutoRecordDuration); } _resetAutoRecordingDeadTimeTimeout() { if (!WI.settings.timelinesAutoStop.value) return; // Only monitor dead time when auto capturing. if (!this.isCapturing() || !this._mainResourceForAutoCapturing) return; // Avoid unnecessary churning of timeout identifier by not tickling until 10ms have passed. let now = Date.now(); if (now <= this._lastDeadTimeTickle) return; this._lastDeadTimeTickle = now + 10; if (this._deadTimeTimeout) clearTimeout(this._deadTimeTimeout); this._deadTimeTimeout = setTimeout(this._boundStopCapturing, WI.TimelineManager.DeadTimeRequiredToStopAutoRecordingEarly); } _provisionalLoadStarted(event) { if (!this._enabled) return; this._attemptAutoCapturingForFrame(event.target); } _mainResourceDidChange(event) { if (!this._enabled) return; // Ignore resource events when there isn't a main frame yet. Those events are triggered by // loading the cached resources when the inspector opens, and they do not have timing information. if (!WI.networkManager.mainFrame) return; let frame = event.target; // When performing a page transition start a recording once the main resource changes. // We start a legacy capture because the backend wasn't available to automatically // initiate the capture, so the frontend must start the capture. if (this._transitioningPageTarget) { this._transitioningPageTarget = false; if (this._autoCaptureOnPageLoad) this._legacyAttemptStartAutoCapturingForFrame(frame); return; } if (this._attemptAutoCapturingForFrame(frame)) return; if (!this.isCapturing()) return; let mainResource = frame.mainResource; if (mainResource === this._mainResourceForAutoCapturing) return; this._addRecord(new WI.ResourceTimelineRecord(mainResource)); } _handleMessageAdded(event) { if (!this._enabled) return; let {message} = event.data; if (WI.ScreenshotsInstrument.supported() && message.source === WI.ConsoleMessage.MessageSource.ConsoleAPI && message.type === WI.ConsoleMessage.MessageType.Image && message.level === WI.ConsoleMessage.MessageLevel.Log && message.messageText) this._addRecord(new WI.ScreenshotsTimelineRecord(message.timestamp, message.messageText)); } _resourceWasAdded(event) { if (!this._enabled) return; // Ignore resource events when there isn't a main frame yet. Those events are triggered by // loading the cached resources when the inspector opens, and they do not have timing information. if (!WI.networkManager.mainFrame) return; this._addRecord(new WI.ResourceTimelineRecord(event.data.resource)); } _garbageCollected(event) { if (!this._enabled) return; let {collection} = event.data; this._addRecord(new WI.ScriptTimelineRecord(WI.ScriptTimelineRecord.EventType.GarbageCollected, collection.startTime, collection.endTime, null, null, collection)); } _memoryPressure(event) { if (!this._enabled) return; this._activeRecording.addMemoryPressureEvent(event.data.memoryPressureEvent); } _handleTimelinesAutoStopSettingChanged(event) { if (WI.settings.timelinesAutoStop.value) { if (this._mainResourceForAutoCapturing && !isNaN(this._mainResourceForAutoCapturing.parentFrame.loadEventTimestamp)) this._stopAutoRecordingSoon(); else this._resetAutoRecordingMaxTimeTimeout(); this._resetAutoRecordingDeadTimeTimeout(); } else this.relaxAutoStop(); } _scriptProfilerTypeToScriptTimelineRecordType(type) { switch (type) { case InspectorBackend.Enum.ScriptProfiler.EventType.API: return WI.ScriptTimelineRecord.EventType.APIScriptEvaluated; case InspectorBackend.Enum.ScriptProfiler.EventType.Microtask: return WI.ScriptTimelineRecord.EventType.MicrotaskDispatched; case InspectorBackend.Enum.ScriptProfiler.EventType.Other: return WI.ScriptTimelineRecord.EventType.ScriptEvaluated; } } _mergeScriptProfileRecords() { let nextRecord = function(list) { return list.shift() || null; }; let nextWebTimelineRecord = nextRecord.bind(null, this._webTimelineScriptRecordsExpectingScriptProfilerEvents); let nextScriptProfilerRecord = nextRecord.bind(null, this._scriptProfilerRecords); let recordEnclosesRecord = function(record1, record2) { return record1.startTime <= record2.startTime && record1.endTime >= record2.endTime; }; let webRecord = nextWebTimelineRecord(); let profilerRecord = nextScriptProfilerRecord(); while (webRecord && profilerRecord) { // Skip web records with parent web records. For example an EvaluateScript with an EvaluateScript parent. if (webRecord.parent instanceof WI.ScriptTimelineRecord) { console.assert(recordEnclosesRecord(webRecord.parent, webRecord), "Timeline Record incorrectly wrapping another Timeline Record"); webRecord = nextWebTimelineRecord(); continue; } // Normal case of a Web record wrapping a Script record. if (recordEnclosesRecord(webRecord, profilerRecord)) { webRecord.profilePayload = profilerRecord.profilePayload; profilerRecord = nextScriptProfilerRecord(); // If there are more script profile records in the same time interval, add them // as individual script evaluated records with profiles. This can happen with // web microtask checkpoints that are technically inside of other web records. // FIXME: Web Inspector: Timeline Cleanup: Better Timeline Record for Microtask Checkpoints while (profilerRecord && recordEnclosesRecord(webRecord, profilerRecord)) { this._addRecord(profilerRecord); profilerRecord = nextScriptProfilerRecord(); } webRecord = nextWebTimelineRecord(); continue; } // Profiler Record is entirely after the Web Record. This would mean an empty web record. if (profilerRecord.startTime > webRecord.endTime) { console.warn("Unexpected case of a Timeline record not containing a ScriptProfiler event and profile data"); webRecord = nextWebTimelineRecord(); continue; } // Non-wrapped profiler record. console.warn("Unexpected case of a ScriptProfiler event not being contained by a Timeline record"); this._addRecord(profilerRecord); profilerRecord = nextScriptProfilerRecord(); } // Skipping the remaining ScriptProfiler events to match the current UI for handling Timeline records. // However, the remaining ScriptProfiler records are valid and could be shown. // FIXME: Web Inspector: Timeline UI should keep up with processing all incoming records } _updateAutoCaptureInstruments(targets) { console.assert(this._enabled); let enabledTimelineTypes = this.enabledTimelineTypes; for (let target of targets) { if (!target.hasCommand("Timeline.setInstruments")) continue; let instrumentSet = new Set; for (let timelineType of enabledTimelineTypes) { switch (timelineType) { case WI.TimelineRecord.Type.Script: instrumentSet.add(InspectorBackend.Enum.Timeline.Instrument.ScriptProfiler); break; case WI.TimelineRecord.Type.HeapAllocations: instrumentSet.add(InspectorBackend.Enum.Timeline.Instrument.Heap); break; case WI.TimelineRecord.Type.Network: case WI.TimelineRecord.Type.RenderingFrame: case WI.TimelineRecord.Type.Layout: instrumentSet.add(InspectorBackend.Enum.Timeline.Instrument.Timeline); break; case WI.TimelineRecord.Type.CPU: instrumentSet.add(InspectorBackend.Enum.Timeline.Instrument.CPU); break; case WI.TimelineRecord.Type.Screenshots: instrumentSet.add(InspectorBackend.Enum.Timeline.Instrument.Screenshot); break; case WI.TimelineRecord.Type.Memory: instrumentSet.add(InspectorBackend.Enum.Timeline.Instrument.Memory); break; case WI.TimelineRecord.Type.Media: // COMPATIBILITY (iOS 13): Animation domain did not exist yet. if (InspectorBackend.hasDomain("Animation")) instrumentSet.add(InspectorBackend.Enum.Timeline.Instrument.Animation); break; } } target.TimelineAgent.setInstruments(Array.from(instrumentSet)); } } _handleDOMNodeDidFireEvent(event) { if (!this._enabled) return; let domNode = event.target; if (!domNode.isMediaElement()) return; let {domEvent} = event.data; let mediaTimeline = this._activeRecording.timelineForRecordType(WI.TimelineRecord.Type.Media); console.assert(mediaTimeline); let record = mediaTimeline.recordForMediaElementEvents(domNode); if (!record) { record = new WI.MediaTimelineRecord(WI.MediaTimelineRecord.EventType.MediaElement, domNode); this._addRecord(record); } record.addDOMEvent(domEvent.timestamp, domEvent); } _handleDOMNodePowerEfficientPlaybackStateChanged(event) { if (!this._enabled) return; let domNode = event.target; console.assert(domNode.isMediaElement()); let {timestamp, isPowerEfficient} = event.data; let mediaTimeline = this._activeRecording.timelineForRecordType(WI.TimelineRecord.Type.Media); console.assert(mediaTimeline); let record = mediaTimeline.recordForMediaElementEvents(domNode); if (!record) { record = new WI.MediaTimelineRecord(WI.MediaTimelineRecord.EventType.MediaElement, domNode); this._addRecord(record); } record.powerEfficientPlaybackStateChanged(timestamp, isPowerEfficient); } }; WI.TimelineManager.CapturingState = { Inactive: "inactive", Starting: "starting", Active: "active", Stopping: "stopping", }; WI.TimelineManager.Event = { CapturingStateChanged: "timeline-manager-capturing-started", RecordingCreated: "timeline-manager-recording-created", RecordingLoaded: "timeline-manager-recording-loaded", RecordingImported: "timeline-manager-recording-imported", }; WI.TimelineManager.MaximumAutoRecordDuration = 90_000; // 90 seconds WI.TimelineManager.MaximumAutoRecordDurationAfterLoadEvent = 10_000; // 10 seconds WI.TimelineManager.DeadTimeRequiredToStopAutoRecordingEarly = 2_000; // 2 seconds /* Controllers/WebInspectorExtensionController.js */ /* * Copyright (C) 2020-2021 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.WebInspectorExtensionController = class WebInspectorExtensionController extends WI.Object { constructor() { super(); this._extensionForExtensionIDMap = new Map; this._extensionTabContentViewForExtensionTabIDMap = new Map; this._tabIDsForExtensionIDMap = new Multimap; this._nextExtensionTabID = 1; this._extensionTabPositions = null; this._saveTabPositionsDebouncer = null; WI.Frame.addEventListener(WI.Frame.Event.MainResourceDidChange, this._handleMainResourceDidChange, this); } // Static static get extensionTabPositionsObjectStoreKey() { return "extension-tab-positions"; } // Public get registeredExtensionIDs() { return new Set(this._extensionForExtensionIDMap.keys()); } registerExtension(extensionID, extensionBundleIdentifier, displayName) { if (this._extensionForExtensionIDMap.has(extensionID)) { WI.reportInternalError("Unable to register extension, it's already registered: " + extensionID); return WI.WebInspectorExtension.ErrorCode.RegistrationFailed; } if (!this._extensionForExtensionIDMap.size) { WI.tabBrowser.tabBar.addEventListener(WI.TabBar.Event.TabBarItemAdded, this._saveExtensionTabPositions, this); WI.tabBrowser.tabBar.addEventListener(WI.TabBar.Event.TabBarItemRemoved, this._saveExtensionTabPositions, this); WI.tabBrowser.tabBar.addEventListener(WI.TabBar.Event.TabBarItemsReordered, this._saveExtensionTabPositions, this); } let extension = new WI.WebInspectorExtension(extensionID, extensionBundleIdentifier, displayName); this._extensionForExtensionIDMap.set(extensionID, extension); this.dispatchEventToListeners(WI.WebInspectorExtensionController.Event.ExtensionAdded, {extension}); } unregisterExtension(extensionID) { let extension = this._extensionForExtensionIDMap.take(extensionID); if (!extension) { WI.reportInternalError("Unable to unregister extension with unknown ID: " + extensionID); return WI.WebInspectorExtension.ErrorCode.InvalidRequest; } if (!this._extensionForExtensionIDMap.size) { WI.tabBrowser.tabBar.removeEventListener(WI.TabBar.Event.TabBarItemAdded, this._saveExtensionTabPositions, this); WI.tabBrowser.tabBar.removeEventListener(WI.TabBar.Event.TabBarItemRemoved, this._saveExtensionTabPositions, this); WI.tabBrowser.tabBar.removeEventListener(WI.TabBar.Event.TabBarItemsReordered, this._saveExtensionTabPositions, this); } let extensionTabIDsToRemove = this._tabIDsForExtensionIDMap.take(extensionID) || []; for (let extensionTabID of extensionTabIDsToRemove) { let tabContentView = this._extensionTabContentViewForExtensionTabIDMap.take(extensionTabID); // Ensure that the iframe is actually detached and does not leak. WI.tabBrowser.closeTabForContentView(tabContentView, {suppressAnimations: true}); tabContentView.dispose(); } this.dispatchEventToListeners(WI.WebInspectorExtensionController.Event.ExtensionRemoved, {extension}); } async createTabForExtension(extensionID, tabName, tabIconURL, sourceURL) { let extension = this._extensionForExtensionIDMap.get(extensionID); if (!extension) { WI.reportInternalError("Unable to create tab for extension with unknown ID: " + extensionID + " sourceURL: " + sourceURL); return WI.WebInspectorExtension.ErrorCode.InvalidRequest; } let extensionTabID = `WebExtensionTab-${extensionID}-${this._nextExtensionTabID++}`; let tabContentView = new WI.WebInspectorExtensionTabContentView(extension, extensionTabID, tabName, tabIconURL, sourceURL); this._tabIDsForExtensionIDMap.add(extensionID, extensionTabID); this._extensionTabContentViewForExtensionTabIDMap.set(extensionTabID, tabContentView); if (!this._extensionTabPositions) await this._loadExtensionTabPositions(); WI.tabBrowser.addTabForContentView(tabContentView, { suppressAnimations: true, insertionIndex: this._insertionIndexForExtensionTab(tabContentView), }); // The calling convention is to return an error string or a result object. return {"result": extensionTabID}; } async evaluateScriptForExtension(extensionID, scriptSource, {frameURL, contextSecurityOrigin, useContentScriptContext} = {}) { let extension = this._extensionForExtensionIDMap.get(extensionID); if (!extension) { WI.reportInternalError("Unable to evaluate script for extension with unknown ID: " + extensionID); return WI.WebInspectorExtension.ErrorCode.InvalidRequest; } let frame = await retryUntil(() => this._frameForFrameURL(frameURL)); if (!frame) { WI.reportInternalError("evaluateScriptForExtension: No frame matched provided frameURL: " + frameURL); return WI.WebInspectorExtension.ErrorCode.InvalidRequest; } // FIXME: Implement `contextSecurityOrigin` and `useContentScriptContext` options for `devtools.inspectedWindow.eval` command if (contextSecurityOrigin) { WI.reportInternalError("evaluateScriptForExtension: the 'contextSecurityOrigin' option is not yet implemented."); return WI.WebInspectorExtension.ErrorCode.NotImplemented; } if (useContentScriptContext) { WI.reportInternalError("evaluateScriptForExtension: the 'useContentScriptContext' option is not yet implemented."); return WI.WebInspectorExtension.ErrorCode.NotImplemented; } let evaluationContext = await retryUntil(() => frame.pageExecutionContext); if (!evaluationContext) { WI.reportInternalError("evaluateScriptForExtension: No 'pageExecutionContext' was present for frame with URL: " + frame.url); return WI.WebInspectorExtension.ErrorCode.ContextDestroyed; } return evaluationContext.target.RuntimeAgent.evaluate.invoke({ expression: scriptSource, objectGroup: "extension-evaluation", includeCommandLineAPI: true, returnByValue: true, generatePreview: false, saveResult: false, contextId: evaluationContext.id, }).then((payload) => { let resultOrError = payload.result; let wasThrown = payload.wasThrown; let {type, value} = resultOrError; return wasThrown ? {"error": resultOrError.description} : {"result": value}; }).catch((error) => error.description); } reloadForExtension(extensionID, {ignoreCache, userAgent, injectedScript} = {}) { let extension = this._extensionForExtensionIDMap.get(extensionID); if (!extension) { WI.reportInternalError("Unable to evaluate script for extension with unknown ID: " + extensionID); return WI.WebInspectorExtension.ErrorCode.InvalidRequest; } // FIXME: Implement `userAgent` and `injectedScript` options for `devtools.inspectedWindow.reload` command if (userAgent) { WI.reportInternalError("reloadForExtension: the 'userAgent' option is not yet implemented."); return WI.WebInspectorExtension.ErrorCode.NotImplemented; } if (injectedScript) { WI.reportInternalError("reloadForExtension: the 'injectedScript' option is not yet implemented."); return WI.WebInspectorExtension.ErrorCode.NotImplemented; } let target = WI.assumingMainTarget(); if (!target.hasCommand("Page.reload")) return WI.WebInspectorExtension.ErrorCode.InvalidRequest; return target.PageAgent.reload.invoke({ignoreCache}); } navigateTabForExtension(extensionTabID, sourceURL) { let tabContentView = this._extensionTabContentViewForExtensionTabIDMap.get(extensionTabID); if (!tabContentView) { WI.reportInternalError("Unable to navigate extension tab with unknown extensionTabID: " + extensionTabID); return WI.WebInspectorExtension.ErrorCode.InvalidRequest; } tabContentView.iframeURL = sourceURL; } showExtensionTab(extensionTabID, options = {}) { let tabContentView = this._extensionTabContentViewForExtensionTabIDMap.get(extensionTabID); if (!tabContentView) { WI.reportInternalError("Unable to show extension tab with unknown extensionTabID: " + extensionTabID); return WI.WebInspectorExtension.ErrorCode.InvalidRequest; } tabContentView.visible = true; let success = WI.tabBrowser.showTabForContentView(tabContentView, { ...options, insertionIndex: this._insertionIndexForExtensionTab(tabContentView), initiatorHint: WI.TabBrowser.TabNavigationInitiator.FrontendAPI, }); if (!success) { WI.reportInternalError("Unable to show extension tab with extensionTabID: " + extensionTabID); return WI.WebInspectorExtension.ErrorCode.InternalError; } tabContentView.visible = true; // Clients expect to be able to use evaluateScriptInExtensionTab() when this method // returns, so wait for the extension tab to finish its loading sequence. Wrap the result. return tabContentView.whenPageAvailable().then((sourceURL) => { return {"result": sourceURL}; }); } hideExtensionTab(extensionTabID, options = {}) { let tabContentView = this._extensionTabContentViewForExtensionTabIDMap.get(extensionTabID); if (!tabContentView) { WI.reportInternalError("Unable to show extension tab with unknown extensionTabID: " + extensionTabID); return WI.WebInspectorExtension.ErrorCode.InvalidRequest; } tabContentView.visible = false; WI.tabBrowser.closeTabForContentView(tabContentView, options); console.assert(!tabContentView.visible); console.assert(!tabContentView.isClosed); } addContextMenuItemsForClosedExtensionTabs(contextMenu) { contextMenu.appendSeparator(); for (let tabContentView of this._extensionTabContentViewForExtensionTabIDMap.values()) { // If the extension tab has been unchecked in the TabBar context menu, then the tabBarItem // for the extension tab will not be connected to a parent TabBar. let shouldIncludeTab = !tabContentView.visible || !tabContentView.tabBarItem.parentTabBar; if (!shouldIncludeTab) continue; contextMenu.appendItem(tabContentView.tabInfo().displayName, () => { this.showExtensionTab(tabContentView.extensionTabID); }); } } addContextMenuItemsForAllExtensionTabs(contextMenu) { contextMenu.appendSeparator(); for (let tabContentView of this._extensionTabContentViewForExtensionTabIDMap.values()) { let checked = tabContentView.visible || !!tabContentView.tabBarItem.parentTabBar; contextMenu.appendCheckboxItem(tabContentView.tabInfo().displayName, () => { if (!checked) this.showExtensionTab(tabContentView.extensionTabID); else this.hideExtensionTab(tabContentView.extensionTabID); }, checked); } } activeExtensionTabContentViews() { return Array.from(this._extensionTabContentViewForExtensionTabIDMap.values()).filter((tab) => tab.visible || tab.tabBarItem.parentTabBar); } evaluateScriptInExtensionTab(extensionTabID, scriptSource) { let tabContentView = this._extensionTabContentViewForExtensionTabIDMap.get(extensionTabID); if (!tabContentView) { WI.reportInternalError("Unable to evaluate with unknown extensionTabID: " + extensionTabID); return WI.WebInspectorExtension.ErrorCode.InvalidRequest; } let iframe = tabContentView.iframeElement; if (!(iframe instanceof HTMLIFrameElement)) { WI.reportInternalError("Unable to evaluate without an