| /* |
| * 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", |
| }; |