blob: ee22f36e39d34cd3aa1cb73e47e4c3868f70a736 [file] [log] [blame]
/*
* 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),
});
}
if (DOMDebuggerManager.supportsDOMBreakpoints()) {
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 supportsDOMBreakpoints()
{
return InspectorBackend.hasCommand("DOMDebugger.setDOMBreakpoint")
&& InspectorBackend.hasCommand("DOMDebugger.removeDOMBreakpoint");
}
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);
}
listenerBreakpointForEventName(eventName)
{
if (DOMDebuggerManager.supportsAllListenersBreakpoint() && this._allListenersBreakpoint && !this._allListenersBreakpoint.disabled)
return this._allListenersBreakpoint;
return this._listenerBreakpoints.find((breakpoint) => breakpoint.eventName === eventName) || null;
}
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.find((existing) => existing.eventName === breakpoint.eventName))
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);
// COMPATIBILITY (iOS 10.3): DOMDebugger.setDOMBreakpoint did not exist yet.
if (!target.hasCommand("DOMDebugger.setDOMBreakpoint"))
return;
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);
// COMPATIBILITY (iOS 10.3): DOMDebugger.removeDOMBreakpoint did not exist yet.
if (!target.hasCommand("DOMDebugger.removeDOMBreakpoint"))
return;
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:
commandArguments.breakpointType = breakpoint.type;
commandArguments.eventName = breakpoint.eventName;
console.assert(commandArguments.eventName);
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 10.3): DOMDebugger.setEventListenerBreakpoint did not exist yet.
// COMPATIBILITY (iOS 12.0): DOMDebugger.setEventListenerBreakpoint was replaced by DOMDebugger.setEventBreakpoint.
if (target.hasCommand("DOMDebugger.setEventListenerBreakpoint")) {
console.assert(breakpoint.type === WI.EventBreakpoint.Type.Listener);
if (!this._restoringBreakpoints && !WI.debuggerManager.breakpointsDisabledTemporarily)
WI.debuggerManager.breakpointsEnabled = true;
target.DOMDebuggerAgent.setEventListenerBreakpoint(breakpoint.eventName);
return;
}
// COMPATIBILITY (iOS 12.0): DOMDebugger.setEventBreakpoint did not exist yet.
if (!target.hasCommand("DOMDebugger.setEventBreakpoint"))
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 10.3): DOMDebugger.removeEventListenerBreakpoint did not exist yet.
// COMPATIBILITY (iOS 12.0): DOMDebugger.removeEventListenerBreakpoint was replaced by DOMDebugger.removeEventBreakpoint.
if (target.hasCommand("DOMDebugger.removeEventListenerBreakpoint")) {
console.assert(breakpoint.type === WI.EventBreakpoint.Type.Listener);
target.DOMDebuggerAgent.removeEventListenerBreakpoint(breakpoint.eventName);
return;
}
// COMPATIBILITY (iOS 12.0): DOMDebugger.removeEventBreakpoint did not exist yet.
if (!target.hasCommand("DOMDebugger.removeEventBreakpoint"))
return;
let commandArguments = this._commandArgumentsForEventBreakpoint(breakpoint);
target.DOMDebuggerAgent.removeEventBreakpoint.invoke(commandArguments);
}
_setURLBreakpoint(breakpoint, target)
{
console.assert(!breakpoint.disabled, breakpoint);
// COMPATIBILITY (iOS 10.3): DOMDebugger.setXHRBreakpoint did not exist yet.
// COMPATIBILITY (iOS 12.2): DOMDebugger.setXHRBreakpoint was replaced by DOMDebugger.setURLBreakpoint.
if (target.hasCommand("DOMDebugger.setXHRBreakpoint")) {
if (!this._restoringBreakpoints && !WI.debuggerManager.breakpointsDisabledTemporarily)
WI.debuggerManager.breakpointsEnabled = true;
let isRegex = breakpoint.type === WI.URLBreakpoint.Type.RegularExpression;
target.DOMDebuggerAgent.setXHRBreakpoint(breakpoint.url, isRegex);
return;
}
// COMPATIBILITY (iOS 12.2): DOMDebugger.setURLBreakpoint did not exist yet.
if (!target.hasCommand("DOMDebugger.setURLBreakpoint"))
return;
if (!this._restoringBreakpoints && !WI.debuggerManager.breakpointsDisabledTemporarily)
WI.debuggerManager.breakpointsEnabled = true;
target.DOMDebuggerAgent.setURLBreakpoint.invoke({
url: breakpoint.url,
isRegex: breakpoint.type === WI.URLBreakpoint.Type.RegularExpression,
options: breakpoint.optionsToProtocol(),
});
}
_removeURLBreakpoint(breakpoint, target)
{
// COMPATIBILITY (iOS 10.3): DOMDebugger.removeXHRBreakpoint did not exist yet.
// COMPATIBILITY (iOS 12.2): DOMDebugger.removeXHRBreakpoint was replaced by DOMDebugger.setURLBreakpoint.
if (target.hasCommand("DOMDebugger.removeXHRBreakpoint")) {
target.DOMDebuggerAgent.removeXHRBreakpoint(breakpoint.url);
return;
}
// COMPATIBILITY (iOS 12.2): DOMDebugger.removeURLBreakpoint did not exist yet.
if (!target.hasCommand("DOMDebugger.removeURLBreakpoint"))
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",
};