| <!doctype html> |
| <html> |
| <head> |
| <script src="../../http/tests/inspector/resources/inspector-test.js"></script> |
| <script src="../debugger/resources/log-active-stack-trace.js"></script> |
| <script> |
| function subtreeModifiedTest() { |
| document.getElementById("subtree-modified-test").append(document.createElement("p")); |
| } |
| |
| function attributeModifiedTest() { |
| document.getElementById("attribute-modified-test").setAttribute("display", "none"); |
| } |
| |
| function nodeRemovedDirectTest() { |
| let node = document.getElementById("node-removed-direct-test"); |
| let parent = node.parentNode; |
| node.remove(); |
| parent.append(node); |
| } |
| |
| function nodeRemovedAncestorTest() { |
| let node = document.getElementById("node-removed-ancestor-test"); |
| let parent = node.parentNode; |
| let grandparent = parent.parentNode; |
| parent.remove(node); |
| grandparent.append(parent); |
| } |
| |
| function test() |
| { |
| const subtreeModifiedTestId = "subtree-modified-test"; |
| const attributeModifiedTestId = "attribute-modified-test"; |
| const nodeRemovedDirectTestId = "node-removed-direct-test"; |
| const nodeRemovedAncestorTestId = "node-removed-ancestor-test"; |
| const multipleBreakpointsTestId = "multiple-breakpoints-test"; |
| |
| let debuggerPausedListener = null; |
| |
| let suite = InspectorTest.createAsyncSuite("DOMBreakpoints"); |
| |
| function teardown(resolve) { |
| let breakpoints = WI.domDebuggerManager.domBreakpoints; |
| for (let breakpoint of breakpoints) |
| WI.domDebuggerManager.removeDOMBreakpoint(breakpoint); |
| InspectorTest.assert(!WI.domDebuggerManager.domBreakpoints.length); |
| |
| WI.debuggerManager.removeEventListener(WI.DebuggerManager.Event.Paused, debuggerPausedListener); |
| debuggerPausedListener = null; |
| |
| resolve(); |
| } |
| |
| function rejectOnPause() { |
| return new Promise((resolve, reject) => { |
| InspectorTest.assert(!debuggerPausedListener); |
| debuggerPausedListener = WI.debuggerManager.addEventListener(WI.DebuggerManager.Event.Paused, (event) => { |
| WI.debuggerManager.resume(); |
| InspectorTest.fail("Should not pause."); |
| reject(); |
| }); |
| }); |
| } |
| |
| function awaitEvaluateInPage(expression) { |
| return new Promise((resolve, reject) => { |
| InspectorTest.log("Wait for evaluate in page to return."); |
| InspectorTest.evaluateInPage(expression, (error) => { |
| if (error) |
| reject(error); |
| |
| resolve(); |
| }); |
| }); |
| } |
| |
| function awaitQuerySelector(selector) { |
| return new Promise((resolve, reject) => { |
| WI.domManager.requestDocument((documentNode) => { |
| if (!documentNode) { |
| reject(); |
| return; |
| } |
| |
| WI.domManager.querySelector(documentNode.id, selector, (nodeId) => { |
| if (!nodeId) { |
| InspectorTest.fail("Selector returned no nodes.", selector); |
| reject(); |
| return; |
| } |
| |
| let node = WI.domManager.nodeForId(nodeId); |
| InspectorTest.assert(node, "Missing node for id.", nodeId); |
| if (!node) { |
| reject(); |
| return; |
| } |
| |
| resolve(node); |
| }); |
| }); |
| }); |
| } |
| |
| function addBreakpointForElementIdentifier(elementIdentifier, type, disabled) { |
| return new Promise((resolve, reject) => { |
| awaitQuerySelector("#" + elementIdentifier) |
| .then((node) => { |
| WI.domDebuggerManager.awaitEvent(WI.DOMDebuggerManager.Event.DOMBreakpointAdded) |
| .then((event) => { |
| let breakpoint = event.data.breakpoint; |
| InspectorTest.pass(`Added '${type}' breakpoint.`); |
| resolve(event); |
| }); |
| |
| WI.domDebuggerManager.addDOMBreakpoint(new WI.DOMBreakpoint(node, type, {disabled})); |
| }); |
| }); |
| } |
| |
| function addTestsForBreakpointType({name, elementIdentifier, type, expression, insertion, shouldMatch}) { |
| suite.addTestCase({ |
| name: `DOMBreakpoints.${name}.BreakpointEnabled`, |
| description: "Check that debugger pauses for breakpoint type.", |
| teardown, |
| test(resolve, reject) { |
| addBreakpointForElementIdentifier(elementIdentifier, type) |
| .then((event) => { |
| let breakpoint = event.data.breakpoint; |
| InspectorTest.expectEqual(breakpoint.type, type, "Breakpoint should have expected type."); |
| |
| InspectorTest.log("Call DOM operation."); |
| InspectorTest.evaluateInPage(expression); |
| return WI.debuggerManager.awaitEvent(WI.DebuggerManager.Event.Paused); |
| }) |
| .then(() => { |
| let targetData = WI.debuggerManager.dataForTarget(WI.debuggerManager.activeCallFrame.target); |
| InspectorTest.log("PAUSED:"); |
| InspectorTest.expectEqual(targetData.pauseReason, WI.DebuggerManager.PauseReason.DOM, "Pause reason should be DOM."); |
| |
| let pauseData = targetData.pauseData; |
| InspectorTest.expectEqual(pauseData.type, type, `Pause type should be '${type}'.`); |
| InspectorTest.expectEqual(pauseData.nodeId, domNodeIdentifier, `Pause nodeId should be expected value.`); |
| |
| if (insertion !== undefined) |
| InspectorTest.expectEqual(insertion, target.pauseData.insertion, `Pause insertion should be '${insertion}'.`); |
| |
| if (pauseData.targetNodeId) { |
| if (shouldMatch) |
| InspectorTest.expectEqual(pauseData.targetNodeId, pauseData.nodeId, "Pause targetNodeId should match nodeId."); |
| else |
| InspectorTest.expectNotEqual(pauseData.targetNodeId, pauseData.nodeId, "Pause targetNodeId should not match nodeId."); |
| } |
| |
| logActiveStackTrace(); |
| return WI.debuggerManager.resume(); |
| }) |
| .then(resolve, reject); |
| } |
| }); |
| |
| suite.addTestCase({ |
| name: `DOMBreakpoints.${name}.BreakpointDisabled`, |
| description: "Check that debugger does not pause for disabled breakpoint.", |
| teardown, |
| test(resolve, reject) { |
| const disabled = true; |
| addBreakpointForElementIdentifier(elementIdentifier, type, disabled) |
| .then(() => Promise.race([awaitEvaluateInPage(expression), rejectOnPause()])) |
| .then(() => { |
| InspectorTest.pass("Should not pause for disabled breakpoint."); |
| resolve(); |
| }) |
| .catch(reject); |
| } |
| }); |
| |
| suite.addTestCase({ |
| name: `DOMBreakpoints.${name}.DebuggerDisabled`, |
| description: "Check that debugger does not pause when the debugger is disabled.", |
| teardown, |
| test(resolve, reject) { |
| addBreakpointForElementIdentifier(elementIdentifier, type) |
| .then(() => DebuggerAgent.setBreakpointsActive(false)) |
| .then(() => Promise.race([awaitEvaluateInPage(expression), rejectOnPause()])) |
| .then(() => DebuggerAgent.setBreakpointsActive(true)) |
| .then(() => { |
| InspectorTest.pass("Should not pause for disabled breakpoint."); |
| resolve(); |
| }) |
| .catch(reject); |
| } |
| }); |
| |
| suite.addTestCase({ |
| name: `DOMBreakpoints.${name}.RemoveBreakpoint`, |
| description: "Check that debugger does not pause for removed breakpoint.", |
| teardown, |
| test(resolve, reject) { |
| addBreakpointForElementIdentifier(elementIdentifier, type) |
| .then((event) => { |
| let promise = WI.domDebuggerManager.awaitEvent(WI.DOMDebuggerManager.Event.DOMBreakpointRemoved); |
| let breakpoint = event.data.breakpoint; |
| |
| InspectorTest.log("Remove breakpoint."); |
| WI.domDebuggerManager.removeDOMBreakpoint(breakpoint); |
| return promise; |
| }) |
| .then(() => Promise.race([awaitEvaluateInPage(expression), rejectOnPause()])) |
| .then(() => { |
| InspectorTest.pass("Should not pause for removed breakpoint."); |
| resolve(); |
| }) |
| .catch(reject); |
| } |
| }); |
| } |
| |
| suite.addTestCase({ |
| name: "BasicBreakpoint", |
| description: "Check that breakpoint is in a valid initial state.", |
| teardown, |
| test(resolve, reject) { |
| addBreakpointForElementIdentifier(subtreeModifiedTestId, WI.DOMBreakpoint.Type.SubtreeModified) |
| .then((event) => { |
| let breakpoint = event.data.breakpoint; |
| InspectorTest.expectFalse(breakpoint.disabled, "Breakpoint should not be disabled."); |
| InspectorTest.expectThat(breakpoint.domNodeIdentifier, "Breakpoint should have node identifier."); |
| |
| InspectorTest.assert(WI.networkManager.mainFrame, "Missing main frame."); |
| let documentURL = WI.networkManager.mainFrame.url; |
| InspectorTest.expectEqual(breakpoint.url, documentURL, "Breakpoint URL should match document URL."); |
| }) |
| .then(resolve, reject); |
| } |
| }); |
| |
| addTestsForBreakpointType({ |
| name: "SubtreeModified", |
| elementIdentifier: subtreeModifiedTestId, |
| type: WI.DOMBreakpoint.Type.SubtreeModified, |
| expression: "subtreeModifiedTest()", |
| insertion: true, |
| shouldMatch: true, |
| }); |
| |
| addTestsForBreakpointType({ |
| name: "AttributeModified", |
| elementIdentifier: attributeModifiedTestId, |
| type: WI.DOMBreakpoint.Type.AttributeModified, |
| expression: "attributeModifiedTest()", |
| }); |
| |
| addTestsForBreakpointType({ |
| name: "NodeRemovedSelf", |
| elementIdentifier: nodeRemovedDirectTestId, |
| type: WI.DOMBreakpoint.Type.NodeRemoved, |
| expression: "nodeRemovedDirectTest()", |
| }); |
| |
| addTestsForBreakpointType({ |
| name: "NodeRemovedAncestor", |
| elementIdentifier: nodeRemovedAncestorTestId, |
| type: WI.DOMBreakpoint.Type.NodeRemoved, |
| expression: "nodeRemovedAncestorTest()", |
| shouldMatch: true, |
| }); |
| |
| suite.addTestCase({ |
| name: "RemoveAllBreakpointsForNode", |
| description: "Check that debugger does not pause for removed breakpoints on node.", |
| teardown, |
| test(resolve, reject) { |
| let breakpointTypes = Object.values(WI.DOMBreakpoint.Type); |
| let breakpointPromises = breakpointTypes.map(type => addBreakpointForElementIdentifier(multipleBreakpointsTestId, type)); |
| |
| Promise.all(breakpointPromises) |
| .then((values) => { |
| let breakpoints = values.map(event => event.data.breakpoint); |
| if (!breakpoints.length) { |
| InspectorTest.fail("No breakpoints added."); |
| reject(); |
| return; |
| } |
| |
| let node = WI.domManager.nodeForId(breakpoints[0].domNodeIdentifier); |
| if (!node) { |
| InspectorTest.fail("No DOM node associated with breakpoint."); |
| reject(); |
| return; |
| } |
| |
| let breakpointsRemoved = 0; |
| |
| function breakpointRemoved() { |
| if (++breakpointsRemoved === breakpoints.length) { |
| InspectorTest.pass(`Removed ${breakpointsRemoved} breakpoints.`); |
| |
| let nodeBreakpoints = WI.domDebuggerManager.domBreakpointsForNode(node); |
| InspectorTest.expectThat(!nodeBreakpoints.length, "DOM node should have no breakpoints."); |
| |
| WI.domDebuggerManager.removeEventListener(WI.DOMDebuggerManager.Event.DOMBreakpointRemoved, breakpointRemoved); |
| resolve(); |
| } |
| } |
| |
| WI.domDebuggerManager.addEventListener(WI.DOMDebuggerManager.Event.DOMBreakpointRemoved, breakpointRemoved); |
| WI.domDebuggerManager.removeDOMBreakpointsForNode(node); |
| }) |
| .then(resolve, reject); |
| } |
| }); |
| |
| suite.addTestCase({ |
| name: "SetBreakpointWithInvalidNodeId", |
| description: "Check that setting a breakpoint for a nonexistant node returns an error.", |
| teardown, |
| test(resolve, reject) { |
| InspectorTest.log("Attempting to set breakpoint."); |
| |
| DOMDebuggerAgent.setDOMBreakpoint(0, WI.DOMBreakpoint.Type.SubtreeModified, (error) => { |
| InspectorTest.log("Protocol result: " + error); |
| InspectorTest.expectThat(error, "Protocol should return an error.") |
| resolve(); |
| }); |
| } |
| }); |
| |
| suite.addTestCase({ |
| name: "SetBreakpointWithInvalidType", |
| description: "Check that setting a breakpoint with an invalid type returns an error.", |
| teardown, |
| test(resolve, reject) { |
| awaitQuerySelector("body") |
| .then((node) => { |
| InspectorTest.log("Attempting to set breakpoint."); |
| |
| DOMDebuggerAgent.setDOMBreakpoint(node.id, "custom-breakpoint-type", (error) => { |
| InspectorTest.log("Protocol result: " + error); |
| InspectorTest.expectThat(error, "Protocol should return an error.") |
| resolve(); |
| }); |
| }); |
| } |
| }); |
| |
| suite.addTestCase({ |
| name: "RemoveBreakpointWithInvalidNodeId", |
| description: "Check that removing a breakpoint for a nonexistant node returns an error.", |
| teardown, |
| test(resolve, reject) { |
| InspectorTest.log("Attempting to remove breakpoint."); |
| |
| DOMDebuggerAgent.removeDOMBreakpoint(0, WI.DOMBreakpoint.Type.SubtreeModified, (error) => { |
| InspectorTest.log("Protocol result: " + error); |
| InspectorTest.expectThat(error, "Protocol should return an error.") |
| resolve(); |
| }); |
| } |
| }); |
| |
| suite.addTestCase({ |
| name: "RemoveBreakpointWithInvalidType", |
| description: "Check that removing a breakpoint with an invalid type returns an error.", |
| teardown, |
| test(resolve, reject) { |
| awaitQuerySelector("body") |
| .then((node) => { |
| InspectorTest.log("Attempting to remove breakpoint."); |
| |
| DOMDebuggerAgent.removeDOMBreakpoint(node.id, "custom-breakpoint-type", (error) => { |
| InspectorTest.log("Protocol result: " + error); |
| InspectorTest.expectThat(error, "Protocol should return an error.") |
| resolve(); |
| }); |
| }); |
| } |
| }); |
| |
| suite.runTestCasesAndFinish(); |
| } |
| </script> |
| </head> |
| <body onload="runTest()"> |
| <p>Tests for DOM breakpoints.</p> |
| <div id="test-container" style="display: none"> |
| <div id="subtree-modified-test"></div> |
| <div id="attribute-modified-test"></div> |
| <div id="node-removed-direct-test"></div> |
| <div><div id="node-removed-ancestor-test"></div></div> |
| <div id="multiple-breakpoints-test"></div> |
| </div> |
| </body> |
| </html> |