blob: 068b1993e1f86fc31d75f23405ad79e7b1467b9d [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._domBreakpointsSetting = new WI.Setting("dom-breakpoints", []);
this._domBreakpointURLMap = new Map;
this._domBreakpointFrameIdentifierMap = new Map;
this._xhrBreakpointsSetting = new WI.Setting("xhr-breakpoints", []);
this._xhrBreakpoints = [];
this._allRequestsBreakpointEnabledSetting = new WI.Setting("break-on-all-requests", false);
this._allRequestsBreakpoint = new WI.XHRBreakpoint(null, null, !this._allRequestsBreakpointEnabledSetting.value);
WI.DOMBreakpoint.addEventListener(WI.DOMBreakpoint.Event.DisabledStateDidChange, this._domBreakpointDisabledStateDidChange, this);
WI.XHRBreakpoint.addEventListener(WI.XHRBreakpoint.Event.DisabledStateDidChange, this._xhrBreakpointDisabledStateDidChange, this);
WI.domTreeManager.addEventListener(WI.DOMTreeManager.Event.NodeRemoved, this._nodeRemoved, this);
WI.domTreeManager.addEventListener(WI.DOMTreeManager.Event.NodeInserted, this._nodeInserted, this);
WI.frameResourceManager.addEventListener(WI.FrameResourceManager.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);
if (this.supported) {
this._restoringBreakpoints = true;
for (let cookie of this._domBreakpointsSetting.value) {
let breakpoint = new WI.DOMBreakpoint(cookie, cookie.type, cookie.disabled);
this.addDOMBreakpoint(breakpoint);
}
for (let cookie of this._xhrBreakpointsSetting.value) {
let breakpoint = new WI.XHRBreakpoint(cookie.type, cookie.url, cookie.disabled);
this.addXHRBreakpoint(breakpoint);
}
this._restoringBreakpoints = false;
this._speculativelyResolveBreakpoints();
if (!this._allRequestsBreakpoint.disabled)
this._updateXHRBreakpoint(this._allRequestsBreakpoint);
}
}
// Public
get supported()
{
return !!window.DOMDebuggerAgent;
}
get allRequestsBreakpoint() { return this._allRequestsBreakpoint; }
get domBreakpoints()
{
let mainFrame = WI.frameResourceManager.mainFrame;
if (!mainFrame)
return [];
let resolvedBreakpoints = [];
let frames = [mainFrame];
while (frames.length) {
let frame = frames.shift();
let domBreakpointNodeIdentifierMap = this._domBreakpointFrameIdentifierMap.get(frame.id);
if (domBreakpointNodeIdentifierMap) {
for (let breakpoints of domBreakpointNodeIdentifierMap.values())
resolvedBreakpoints = resolvedBreakpoints.concat(breakpoints);
}
frames = frames.concat(frame.childFrameCollection.toArray());
}
return resolvedBreakpoints;
}
get xhrBreakpoints() { return this._xhrBreakpoints; }
isBreakpointRemovable(breakpoint)
{
return breakpoint !== this._allRequestsBreakpoint;
}
domBreakpointsForNode(node)
{
console.assert(node instanceof WI.DOMNode);
if (!node)
return [];
let domBreakpointNodeIdentifierMap = this._domBreakpointFrameIdentifierMap.get(node.frameIdentifier);
if (!domBreakpointNodeIdentifierMap)
return [];
let breakpoints = domBreakpointNodeIdentifierMap.get(node.id);
return breakpoints ? breakpoints.slice() : [];
}
addDOMBreakpoint(breakpoint)
{
console.assert(breakpoint instanceof WI.DOMBreakpoint);
if (!breakpoint || !breakpoint.url)
return;
let breakpoints = this._domBreakpointURLMap.get(breakpoint.url);
if (!breakpoints) {
breakpoints = [breakpoint];
this._domBreakpointURLMap.set(breakpoint.url, breakpoints);
} else
breakpoints.push(breakpoint);
if (breakpoint.domNodeIdentifier)
this._resolveDOMBreakpoint(breakpoint, breakpoint.domNodeIdentifier);
this.dispatchEventToListeners(WI.DOMDebuggerManager.Event.DOMBreakpointAdded, {breakpoint});
this._saveDOMBreakpoints();
}
removeDOMBreakpoint(breakpoint)
{
console.assert(breakpoint instanceof WI.DOMBreakpoint);
if (!breakpoint)
return;
let nodeIdentifier = breakpoint.domNodeIdentifier;
console.assert(nodeIdentifier, "Cannot remove unresolved DOM breakpoint.");
if (!nodeIdentifier)
return;
this._detachDOMBreakpoint(breakpoint);
let urlBreakpoints = this._domBreakpointURLMap.get(breakpoint.url);
urlBreakpoints.remove(breakpoint, true);
if (!breakpoint.disabled)
DOMDebuggerAgent.removeDOMBreakpoint(nodeIdentifier, breakpoint.type);
if (!urlBreakpoints.length)
this._domBreakpointURLMap.delete(breakpoint.url);
this.dispatchEventToListeners(WI.DOMDebuggerManager.Event.DOMBreakpointRemoved, {breakpoint});
breakpoint.domNodeIdentifier = null;
this._saveDOMBreakpoints();
}
removeDOMBreakpointsForNode(node)
{
this._restoringBreakpoints = true;
this.domBreakpointsForNode(node).forEach(this.removeDOMBreakpoint, this);
this._restoringBreakpoints = false;
this._saveDOMBreakpoints();
}
xhrBreakpointForURL(url)
{
return this._xhrBreakpoints.find((breakpoint) => breakpoint.url === url) || null;
}
addXHRBreakpoint(breakpoint)
{
console.assert(breakpoint instanceof WI.XHRBreakpoint);
if (!breakpoint)
return;
console.assert(!this._xhrBreakpoints.includes(breakpoint), "Already added XHR breakpoint.", breakpoint);
if (this._xhrBreakpoints.includes(breakpoint))
return;
if (this._xhrBreakpoints.some((entry) => entry.type === breakpoint.type && entry.url === breakpoint.url))
return;
this._xhrBreakpoints.push(breakpoint);
this.dispatchEventToListeners(WI.DOMDebuggerManager.Event.XHRBreakpointAdded, {breakpoint});
this._resolveXHRBreakpoint(breakpoint);
this._saveXHRBreakpoints();
}
removeXHRBreakpoint(breakpoint)
{
console.assert(breakpoint instanceof WI.XHRBreakpoint);
if (!breakpoint)
return;
if (!this._xhrBreakpoints.includes(breakpoint))
return;
this._xhrBreakpoints.remove(breakpoint, true);
this._saveXHRBreakpoints();
this.dispatchEventToListeners(WI.DOMDebuggerManager.Event.XHRBreakpointRemoved, {breakpoint});
if (breakpoint.disabled)
return;
DOMDebuggerAgent.removeXHRBreakpoint(breakpoint.url, (error) => {
if (error)
console.error(error);
});
}
// Private
_detachDOMBreakpoint(breakpoint)
{
let nodeIdentifier = breakpoint.domNodeIdentifier;
let node = WI.domTreeManager.nodeForId(nodeIdentifier);
console.assert(node, "Missing DOM node for breakpoint.", breakpoint);
if (!node)
return;
let frameIdentifier = node.frameIdentifier;
let domBreakpointNodeIdentifierMap = this._domBreakpointFrameIdentifierMap.get(frameIdentifier);
console.assert(domBreakpointNodeIdentifierMap, "Missing DOM breakpoints for node parent frame.", node);
if (!domBreakpointNodeIdentifierMap)
return;
let breakpoints = domBreakpointNodeIdentifierMap.get(nodeIdentifier);
console.assert(breakpoints, "Missing DOM breakpoints for node.", node);
if (!breakpoints)
return;
breakpoints.remove(breakpoint, true);
if (breakpoints.length)
return;
domBreakpointNodeIdentifierMap.delete(nodeIdentifier);
if (!domBreakpointNodeIdentifierMap.size)
this._domBreakpointFrameIdentifierMap.delete(frameIdentifier);
}
_detachBreakpointsForFrame(frame)
{
let domBreakpointNodeIdentifierMap = this._domBreakpointFrameIdentifierMap.get(frame.id);
if (!domBreakpointNodeIdentifierMap)
return;
this._domBreakpointFrameIdentifierMap.delete(frame.id);
for (let breakpoints of domBreakpointNodeIdentifierMap.values()) {
for (let breakpoint of breakpoints)
breakpoint.domNodeIdentifier = null;
}
}
_speculativelyResolveBreakpoints()
{
let mainFrame = WI.frameResourceManager.mainFrame;
if (!mainFrame)
return;
let domBreakpoints = this._domBreakpointURLMap.get(mainFrame.url);
if (domBreakpoints) {
for (let breakpoint of domBreakpoints) {
if (breakpoint.domNodeIdentifier)
continue;
WI.domTreeManager.pushNodeByPathToFrontend(breakpoint.path, (nodeIdentifier) => {
if (!nodeIdentifier)
return;
this._resolveDOMBreakpoint(breakpoint, nodeIdentifier);
});
}
}
for (let breakpoint of this._xhrBreakpoints)
this._resolveXHRBreakpoint(breakpoint);
}
_resolveDOMBreakpoint(breakpoint, nodeIdentifier)
{
let node = WI.domTreeManager.nodeForId(nodeIdentifier);
console.assert(node, "Missing DOM node for nodeIdentifier.", nodeIdentifier);
if (!node)
return;
let frameIdentifier = node.frameIdentifier;
let domBreakpointNodeIdentifierMap = this._domBreakpointFrameIdentifierMap.get(frameIdentifier);
if (!domBreakpointNodeIdentifierMap) {
domBreakpointNodeIdentifierMap = new Map;
this._domBreakpointFrameIdentifierMap.set(frameIdentifier, domBreakpointNodeIdentifierMap);
}
let breakpoints = domBreakpointNodeIdentifierMap.get(nodeIdentifier);
if (breakpoints)
breakpoints.push(breakpoint);
else
domBreakpointNodeIdentifierMap.set(nodeIdentifier, [breakpoint]);
breakpoint.domNodeIdentifier = nodeIdentifier;
this._updateDOMBreakpoint(breakpoint);
}
_updateDOMBreakpoint(breakpoint)
{
let nodeIdentifier = breakpoint.domNodeIdentifier;
if (!nodeIdentifier)
return;
function breakpointUpdated(error)
{
if (error)
console.error(error);
}
if (breakpoint.disabled)
DOMDebuggerAgent.removeDOMBreakpoint(nodeIdentifier, breakpoint.type, breakpointUpdated);
else
DOMDebuggerAgent.setDOMBreakpoint(nodeIdentifier, breakpoint.type, breakpointUpdated);
}
_updateXHRBreakpoint(breakpoint, callback)
{
function breakpointUpdated(error)
{
if (error)
console.error(error);
if (callback && typeof callback === "function")
callback(error);
}
if (breakpoint.disabled)
DOMDebuggerAgent.removeXHRBreakpoint(breakpoint.url, breakpointUpdated);
else {
let isRegex = breakpoint.type === WI.XHRBreakpoint.Type.RegularExpression;
DOMDebuggerAgent.setXHRBreakpoint(breakpoint.url, isRegex, breakpointUpdated);
}
}
_resolveXHRBreakpoint(breakpoint)
{
if (breakpoint.disabled)
return;
this._updateXHRBreakpoint(breakpoint, () => {
breakpoint.dispatchEventToListeners(WI.XHRBreakpoint.Event.ResolvedStateDidChange);
});
}
_saveDOMBreakpoints()
{
if (this._restoringBreakpoints)
return;
let breakpointsToSave = [];
for (let breakpoints of this._domBreakpointURLMap.values())
breakpointsToSave = breakpointsToSave.concat(breakpoints);
this._domBreakpointsSetting.value = breakpointsToSave.map((breakpoint) => breakpoint.serializableInfo);
}
_saveXHRBreakpoints()
{
if (this._restoringBreakpoints)
return;
this._xhrBreakpointsSetting.value = this._xhrBreakpoints.map((breakpoint) => breakpoint.serializableInfo);
}
_domBreakpointDisabledStateDidChange(event)
{
let breakpoint = event.target;
this._updateDOMBreakpoint(breakpoint);
this._saveDOMBreakpoints();
}
_xhrBreakpointDisabledStateDidChange(event)
{
let breakpoint = event.target;
if (breakpoint === this._allRequestsBreakpoint)
this._allRequestsBreakpointEnabledSetting.value = !breakpoint.disabled;
this._updateXHRBreakpoint(breakpoint);
this._saveXHRBreakpoints();
}
_childFrameWasRemoved(event)
{
let frame = event.data.childFrame;
this._detachBreakpointsForFrame(frame);
}
_mainFrameDidChange()
{
this._speculativelyResolveBreakpoints();
}
_mainResourceDidChange(event)
{
let frame = event.target;
if (frame.isMainFrame()) {
for (let breakpoints of this._domBreakpointURLMap.values())
breakpoints.forEach((breakpoint) => { breakpoint.domNodeIdentifier = null; });
this._domBreakpointFrameIdentifierMap.clear();
} else
this._detachBreakpointsForFrame(frame);
this._speculativelyResolveBreakpoints();
}
_nodeInserted(event)
{
let node = event.data.node;
if (node.nodeType() !== Node.ELEMENT_NODE || !node.ownerDocument)
return;
let url = node.ownerDocument.documentURL;
let breakpoints = this._domBreakpointURLMap.get(url);
if (!breakpoints)
return;
for (let breakpoint of breakpoints) {
if (breakpoint.domNodeIdentifier)
continue;
if (breakpoint.path !== node.path())
continue;
this._resolveDOMBreakpoint(breakpoint, node.id);
}
}
_nodeRemoved(event)
{
let node = event.data.node;
if (node.nodeType() !== Node.ELEMENT_NODE || !node.ownerDocument)
return;
let domBreakpointNodeIdentifierMap = this._domBreakpointFrameIdentifierMap.get(node.frameIdentifier);
if (!domBreakpointNodeIdentifierMap)
return;
let breakpoints = domBreakpointNodeIdentifierMap.get(node.id);
if (!breakpoints)
return;
domBreakpointNodeIdentifierMap.delete(node.id);
if (!domBreakpointNodeIdentifierMap.size)
this._domBreakpointFrameIdentifierMap.delete(node.frameIdentifier);
for (let breakpoint of breakpoints)
breakpoint.domNodeIdentifier = null;
}
};
WI.DOMDebuggerManager.Event = {
DOMBreakpointAdded: "dom-debugger-manager-dom-breakpoint-added",
DOMBreakpointRemoved: "dom-debugger-manager-dom-breakpoint-removed",
XHRBreakpointAdded: "dom-debugger-manager-xhr-breakpoint-added",
XHRBreakpointRemoved: "dom-debugger-manager-xhr-breakpoint-removed",
};