Commit 5b5d3a74 authored by aandrey@chromium.org's avatar aandrey@chromium.org

DevTools: Support XHR event listener breakpoints on frontend.

BUG=381470
R=yurys@chromium.org, pfeldman@chromium.org, vsevik, yurys

Review URL: https://codereview.chromium.org/321113005

git-svn-id: svn://svn.chromium.org/blink/trunk@176318 bbb929c8-8fbe-4397-9dbb-9b2b20218538
parent cfaa5579
Tests event listener breakpoints on XHRs.
Set timer for test function.
Captured call stacks in no particular order:
Call stack:
0) downloadEnd (event-listener-breakpoints-xhr.html:19)
Event target: XMLHttpRequest
Call stack:
0) downloadProgress (event-listener-breakpoints-xhr.html:27)
Event target: XMLHttpRequest
Call stack:
0) loadCallback (event-listener-breakpoints-xhr.html:35)
Event target: XMLHttpRequest
Call stack:
0) uploadEnd (event-listener-breakpoints-xhr.html:23)
Event target: XMLHttpRequestUpload
Call stack:
0) uploadProgress (event-listener-breakpoints-xhr.html:31)
Event target: XMLHttpRequestUpload
Call stack:
0) xhr.onreadystatechange (event-listener-breakpoints-xhr.html:15)
1) sendXHR (event-listener-breakpoints-xhr.html:46)
2) testFunction (event-listener-breakpoints-xhr.html:9)
Event target: XMLHttpRequest
<html>
<head>
<script src="../../../http/tests/inspector/inspector-test.js"></script>
<script src="../../../http/tests/inspector/debugger-test.js"></script>
<script>
function testFunction()
{
sendXHR();
}
function sendXHR()
{
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function()
{
xhr.onreadystatechange = null;
};
function downloadEnd()
{
xhr.removeEventListener("loadend", downloadEnd, false);
}
function uploadEnd()
{
xhr.upload.removeEventListener("loadend", uploadEnd, false);
}
function downloadProgress()
{
xhr.removeEventListener("progress", downloadProgress, false);
}
function uploadProgress()
{
xhr.upload.removeEventListener("progress", uploadProgress, false);
}
function loadCallback()
{
xhr.onload = null;
xhr.onerror = null;
}
xhr.onload = loadCallback;
xhr.onerror = loadCallback;
xhr.addEventListener("loadend", downloadEnd, false);
xhr.addEventListener("progress", downloadProgress, false);
xhr.upload.addEventListener("loadend", uploadEnd, false);
xhr.upload.addEventListener("progress", uploadProgress, false);
xhr.open("POST", "http://localhost/?now=" + Date.now(), true);
xhr.send(String(sendXHR));
}
function test()
{
var pane = WebInspector.panels.sources.sidebarPanes.eventListenerBreakpoints;
var xhrTargetNames = ["XMLHttpRequest", "XMLHttpRequestUpload"];
InspectorTest.setQuiet(true);
InspectorTest.startDebuggerTest(step1);
function step1()
{
pane._setBreakpoint("listener:load");
pane._setBreakpoint("listener:error");
pane._setBreakpoint("listener:loadend", xhrTargetNames);
pane._setBreakpoint("listener:progress", xhrTargetNames);
pane._setBreakpoint("listener:readystatechange", xhrTargetNames);
InspectorTest.runTestFunctionAndWaitUntilPaused(didPause);
}
var totalBreaks = 6;
var callStacksOutput = [];
function didPause(callFrames, reason, breakpointIds, asyncStackTrace, auxData)
{
--totalBreaks;
auxData = auxData || {};
var result = InspectorTest.captureStackTraceIntoString(callFrames) + "\n";
result += "Event target: " + auxData["targetName"] + "\n";
callStacksOutput.push(result);
if (totalBreaks) {
InspectorTest.resumeExecution(InspectorTest.waitUntilPaused.bind(InspectorTest, didPause));
} else {
InspectorTest.addResult("Captured call stacks in no particular order:");
callStacksOutput.sort();
InspectorTest.addResults(callStacksOutput);
InspectorTest.completeDebuggerTest();
}
}
}
</script>
</head>
<body onload="runTest()">
<p>
Tests event listener breakpoints on XHRs.
</p>
</body>
</html>
......@@ -85,8 +85,8 @@ function test()
function testLoadBreakpointOnXHR(next)
{
DOMDebuggerAgent.setEventListenerBreakpoint("load", "xmlHTTPrequest"); // test case-insensitive match
DOMDebuggerAgent.setEventListenerBreakpoint("error", "XMLHttpRequest");
pane._setBreakpoint("listener:load", ["xmlHTTPrequest"]); // test case-insensitive match
pane._setBreakpoint("listener:error", ["XMLHttpRequest"]);
InspectorTest.waitUntilPaused(paused);
InspectorTest.evaluateInPageWithTimeout("addLoadListeners()");
......@@ -94,8 +94,8 @@ function test()
{
InspectorTest.captureStackTrace(callFrames);
printEventTargetName(auxData);
DOMDebuggerAgent.removeEventListenerBreakpoint("load", "XMLHttpRequest");
DOMDebuggerAgent.removeEventListenerBreakpoint("error", "xmlHTTPrequest");
pane._removeBreakpoint("listener:load", ["XMLHttpRequest"]);
pane._removeBreakpoint("listener:error", ["xmlHTTPrequest"]);
InspectorTest.resumeExecution(resumed);
}
......
......@@ -521,28 +521,30 @@ WebInspector.EventListenerBreakpointsSidebarPane = function()
this.categoriesTreeOutline = new TreeOutline(this.categoriesElement);
this.bodyElement.appendChild(this.categoriesElement);
this._breakpointItems = {};
this._categoryItems = [];
// FIXME: uncomment following once inspector stops being drop targer in major ports.
// Otherwise, inspector page reacts on drop event and tries to load the event data.
// this._createCategory(WebInspector.UIString("Drag"), true, ["drag", "drop", "dragstart", "dragend", "dragenter", "dragleave", "dragover"]);
this._createCategory(WebInspector.UIString("Animation"), false, ["requestAnimationFrame", "cancelAnimationFrame", "animationFrameFired"]);
this._createCategory(WebInspector.UIString("Control"), true, ["resize", "scroll", "zoom", "focus", "blur", "select", "change", "submit", "reset"]);
this._createCategory(WebInspector.UIString("Clipboard"), true, ["copy", "cut", "paste", "beforecopy", "beforecut", "beforepaste"]);
this._createCategory(WebInspector.UIString("DOM Mutation"), true, ["DOMActivate", "DOMFocusIn", "DOMFocusOut", "DOMAttrModified", "DOMCharacterDataModified", "DOMNodeInserted", "DOMNodeInsertedIntoDocument", "DOMNodeRemoved", "DOMNodeRemovedFromDocument", "DOMSubtreeModified", "DOMContentLoaded"]);
this._createCategory(WebInspector.UIString("Device"), true, ["deviceorientation", "devicemotion"]);
this._createCategory(WebInspector.UIString("Drag / drop"), true, ["dragenter", "dragover", "dragleave", "drop"]);
this._createCategory(WebInspector.UIString("Keyboard"), true, ["keydown", "keyup", "keypress", "input"]);
this._createCategory(WebInspector.UIString("Load"), true, ["load", "beforeunload", "unload", "abort", "error", "hashchange", "popstate"]);
this._createCategory(WebInspector.UIString("Mouse"), true, ["click", "dblclick", "mousedown", "mouseup", "mouseover", "mousemove", "mouseout", "mousewheel", "wheel"]);
this._createCategory(WebInspector.UIString("Timer"), false, ["setTimer", "clearTimer", "timerFired"]);
this._createCategory(WebInspector.UIString("Touch"), true, ["touchstart", "touchmove", "touchend", "touchcancel"]);
this._createCategory(WebInspector.UIString("WebGL"), false, ["webglErrorFired", "webglWarningFired"]);
// this._createCategory(WebInspector.UIString("Drag"), ["drag", "drop", "dragstart", "dragend", "dragenter", "dragleave", "dragover"]);
this._createCategory(WebInspector.UIString("Animation"), ["requestAnimationFrame", "cancelAnimationFrame", "animationFrameFired"], true);
this._createCategory(WebInspector.UIString("Control"), ["resize", "scroll", "zoom", "focus", "blur", "select", "change", "submit", "reset"]);
this._createCategory(WebInspector.UIString("Clipboard"), ["copy", "cut", "paste", "beforecopy", "beforecut", "beforepaste"]);
this._createCategory(WebInspector.UIString("DOM Mutation"), ["DOMActivate", "DOMFocusIn", "DOMFocusOut", "DOMAttrModified", "DOMCharacterDataModified", "DOMNodeInserted", "DOMNodeInsertedIntoDocument", "DOMNodeRemoved", "DOMNodeRemovedFromDocument", "DOMSubtreeModified", "DOMContentLoaded"]);
this._createCategory(WebInspector.UIString("Device"), ["deviceorientation", "devicemotion"]);
this._createCategory(WebInspector.UIString("Drag / drop"), ["dragenter", "dragover", "dragleave", "drop"]);
this._createCategory(WebInspector.UIString("Keyboard"), ["keydown", "keyup", "keypress", "input"]);
this._createCategory(WebInspector.UIString("Load"), ["load", "beforeunload", "unload", "abort", "error", "hashchange", "popstate"]);
this._createCategory(WebInspector.UIString("Mouse"), ["click", "dblclick", "mousedown", "mouseup", "mouseover", "mousemove", "mouseout", "mousewheel", "wheel"]);
this._createCategory(WebInspector.UIString("Timer"), ["setTimer", "clearTimer", "timerFired"], true);
this._createCategory(WebInspector.UIString("Touch"), ["touchstart", "touchmove", "touchend", "touchcancel"]);
this._createCategory(WebInspector.UIString("XHR"), ["readystatechange", "load", "loadstart", "loadend", "abort", "error", "progress", "timeout"], false, ["XMLHttpRequest", "XMLHttpRequestUpload"]);
this._createCategory(WebInspector.UIString("WebGL"), ["webglErrorFired", "webglWarningFired"], true);
this._restoreBreakpoints();
}
WebInspector.EventListenerBreakpointsSidebarPane.categotyListener = "listener:";
WebInspector.EventListenerBreakpointsSidebarPane.categotyInstrumentation = "instrumentation:";
WebInspector.EventListenerBreakpointsSidebarPane.categoryListener = "listener:";
WebInspector.EventListenerBreakpointsSidebarPane.categoryInstrumentation = "instrumentation:";
WebInspector.EventListenerBreakpointsSidebarPane.eventTargetAny = "*";
/**
* @param {string} eventName
......@@ -575,7 +577,13 @@ WebInspector.EventListenerBreakpointsSidebarPane.eventNameForUI = function(event
}
WebInspector.EventListenerBreakpointsSidebarPane.prototype = {
_createCategory: function(name, isDOMEvent, eventNames)
/**
* @param {string} name
* @param {!Array.<string>} eventNames
* @param {boolean=} isInstrumentationEvent
* @param {!Array.<string>=} targetNames
*/
_createCategory: function(name, eventNames, isInstrumentationEvent, targetNames)
{
var labelNode = document.createElement("label");
labelNode.textContent = name;
......@@ -589,9 +597,11 @@ WebInspector.EventListenerBreakpointsSidebarPane.prototype = {
categoryItem.checkbox = this._createCheckbox(labelNode);
categoryItem.checkbox.addEventListener("click", this._categoryCheckboxClicked.bind(this, categoryItem), true);
categoryItem.targetNames = this._stringArrayToLowerCase(targetNames || [WebInspector.EventListenerBreakpointsSidebarPane.eventTargetAny]);
categoryItem.children = {};
var category = (isInstrumentationEvent ? WebInspector.EventListenerBreakpointsSidebarPane.categoryInstrumentation : WebInspector.EventListenerBreakpointsSidebarPane.categoryListener);
for (var i = 0; i < eventNames.length; ++i) {
var eventName = (isDOMEvent ? WebInspector.EventListenerBreakpointsSidebarPane.categotyListener : WebInspector.EventListenerBreakpointsSidebarPane.categotyInstrumentation) + eventNames[i];
var eventName = category + eventNames[i];
var breakpointItem = {};
var title = WebInspector.EventListenerBreakpointsSidebarPane.eventNameForUI(eventName);
......@@ -602,21 +612,34 @@ WebInspector.EventListenerBreakpointsSidebarPane.prototype = {
breakpointItem.element = new TreeElement(labelNode);
categoryItem.element.appendChild(breakpointItem.element);
var hitMarker = document.createElement("div");
hitMarker.className = "breakpoint-hit-marker";
breakpointItem.element.listItemElement.appendChild(hitMarker);
breakpointItem.element.listItemElement.createChild("div", "breakpoint-hit-marker");
breakpointItem.element.listItemElement.classList.add("source-code");
breakpointItem.element.selectable = false;
breakpointItem.checkbox = this._createCheckbox(labelNode);
breakpointItem.checkbox.addEventListener("click", this._breakpointCheckboxClicked.bind(this, eventName), true);
breakpointItem.checkbox.addEventListener("click", this._breakpointCheckboxClicked.bind(this, eventName, categoryItem.targetNames), true);
breakpointItem.parent = categoryItem;
this._breakpointItems[eventName] = breakpointItem;
categoryItem.children[eventName] = breakpointItem;
}
this._categoryItems.push(categoryItem);
},
/**
* @param {!Array.<string>} array
* @return {!Array.<string>}
*/
_stringArrayToLowerCase: function(array)
{
return array.map(function(value) {
return value.toLowerCase();
});
},
/**
* @param {!Element} labelNode
* @return {!Element}
*/
_createCheckbox: function(labelNode)
{
var checkbox = document.createElement("input");
......@@ -635,51 +658,80 @@ WebInspector.EventListenerBreakpointsSidebarPane.prototype = {
if (breakpointItem.checkbox.checked === checked)
continue;
if (checked)
this._setBreakpoint(eventName);
this._setBreakpoint(eventName, categoryItem.targetNames);
else
this._removeBreakpoint(eventName);
this._removeBreakpoint(eventName, categoryItem.targetNames);
}
this._saveBreakpoints();
},
_breakpointCheckboxClicked: function(eventName, event)
/**
* @param {string} eventName
* @param {!Array.<string>} targetNames
* @param {?Event} event
*/
_breakpointCheckboxClicked: function(eventName, targetNames, event)
{
if (event.target.checked)
this._setBreakpoint(eventName);
this._setBreakpoint(eventName, targetNames);
else
this._removeBreakpoint(eventName);
this._removeBreakpoint(eventName, targetNames);
this._saveBreakpoints();
},
_setBreakpoint: function(eventName)
/**
* @param {string} eventName
* @param {?Array.<string>=} targetNames
*/
_setBreakpoint: function(eventName, targetNames)
{
var breakpointItem = this._breakpointItems[eventName];
targetNames = targetNames || [WebInspector.EventListenerBreakpointsSidebarPane.eventTargetAny];
for (var i = 0; i < targetNames.length; ++i) {
var targetName = targetNames[i];
var breakpointItem = this._findBreakpointItem(eventName, targetName);
if (!breakpointItem)
return;
continue;
breakpointItem.checkbox.checked = true;
if (eventName.startsWith(WebInspector.EventListenerBreakpointsSidebarPane.categotyListener))
DOMDebuggerAgent.setEventListenerBreakpoint(eventName.substring(WebInspector.EventListenerBreakpointsSidebarPane.categotyListener.length));
else if (eventName.startsWith(WebInspector.EventListenerBreakpointsSidebarPane.categotyInstrumentation))
DOMDebuggerAgent.setInstrumentationBreakpoint(eventName.substring(WebInspector.EventListenerBreakpointsSidebarPane.categotyInstrumentation.length));
this._updateCategoryCheckbox(breakpointItem.parent);
breakpointItem.parent.dirtyCheckbox = true;
if (eventName.startsWith(WebInspector.EventListenerBreakpointsSidebarPane.categoryListener))
DOMDebuggerAgent.setEventListenerBreakpoint(eventName.substring(WebInspector.EventListenerBreakpointsSidebarPane.categoryListener.length), targetName);
else if (eventName.startsWith(WebInspector.EventListenerBreakpointsSidebarPane.categoryInstrumentation))
DOMDebuggerAgent.setInstrumentationBreakpoint(eventName.substring(WebInspector.EventListenerBreakpointsSidebarPane.categoryInstrumentation.length));
}
this._updateCategoryCheckboxes();
},
_removeBreakpoint: function(eventName)
/**
* @param {string} eventName
* @param {?Array.<string>=} targetNames
*/
_removeBreakpoint: function(eventName, targetNames)
{
var breakpointItem = this._breakpointItems[eventName];
targetNames = targetNames || [WebInspector.EventListenerBreakpointsSidebarPane.eventTargetAny];
for (var i = 0; i < targetNames.length; ++i) {
var targetName = targetNames[i];
var breakpointItem = this._findBreakpointItem(eventName, targetName);
if (!breakpointItem)
return;
continue;
breakpointItem.checkbox.checked = false;
if (eventName.startsWith(WebInspector.EventListenerBreakpointsSidebarPane.categotyListener))
DOMDebuggerAgent.removeEventListenerBreakpoint(eventName.substring(WebInspector.EventListenerBreakpointsSidebarPane.categotyListener.length));
else if (eventName.startsWith(WebInspector.EventListenerBreakpointsSidebarPane.categotyInstrumentation))
DOMDebuggerAgent.removeInstrumentationBreakpoint(eventName.substring(WebInspector.EventListenerBreakpointsSidebarPane.categotyInstrumentation.length));
this._updateCategoryCheckbox(breakpointItem.parent);
breakpointItem.parent.dirtyCheckbox = true;
if (eventName.startsWith(WebInspector.EventListenerBreakpointsSidebarPane.categoryListener))
DOMDebuggerAgent.removeEventListenerBreakpoint(eventName.substring(WebInspector.EventListenerBreakpointsSidebarPane.categoryListener.length), targetName);
else if (eventName.startsWith(WebInspector.EventListenerBreakpointsSidebarPane.categoryInstrumentation))
DOMDebuggerAgent.removeInstrumentationBreakpoint(eventName.substring(WebInspector.EventListenerBreakpointsSidebarPane.categoryInstrumentation.length));
}
this._updateCategoryCheckboxes();
},
_updateCategoryCheckbox: function(categoryItem)
_updateCategoryCheckboxes: function()
{
var hasEnabled = false, hasDisabled = false;
for (var i = 0; i < this._categoryItems.length; ++i) {
var categoryItem = this._categoryItems[i];
if (!categoryItem.dirtyCheckbox)
continue;
categoryItem.dirtyCheckbox = false;
var hasEnabled = false;
var hasDisabled = false;
for (var eventName in categoryItem.children) {
var breakpointItem = categoryItem.children[eventName];
if (breakpointItem.checkbox.checked)
......@@ -689,11 +741,37 @@ WebInspector.EventListenerBreakpointsSidebarPane.prototype = {
}
categoryItem.checkbox.checked = hasEnabled;
categoryItem.checkbox.indeterminate = hasEnabled && hasDisabled;
}
},
highlightBreakpoint: function(eventName)
/**
* @param {string} eventName
* @param {string=} targetName
* @return {?Object}
*/
_findBreakpointItem: function(eventName, targetName)
{
var breakpointItem = this._breakpointItems[eventName];
targetName = (targetName || WebInspector.EventListenerBreakpointsSidebarPane.eventTargetAny).toLowerCase();
for (var i = 0; i < this._categoryItems.length; ++i) {
var categoryItem = this._categoryItems[i];
if (categoryItem.targetNames.indexOf(targetName) === -1)
continue;
var breakpointItem = categoryItem.children[eventName];
if (breakpointItem)
return breakpointItem;
}
return null;
},
/**
* @param {string} eventName
* @param {string=} targetName
*/
highlightBreakpoint: function(eventName, targetName)
{
var breakpointItem = this._findBreakpointItem(eventName, targetName);
if (!breakpointItem || !breakpointItem.checkbox.checked)
breakpointItem = this._findBreakpointItem(eventName, WebInspector.EventListenerBreakpointsSidebarPane.eventTargetAny);
if (!breakpointItem)
return;
this.expand();
......@@ -713,9 +791,13 @@ WebInspector.EventListenerBreakpointsSidebarPane.prototype = {
_saveBreakpoints: function()
{
var breakpoints = [];
for (var eventName in this._breakpointItems) {
if (this._breakpointItems[eventName].checkbox.checked)
breakpoints.push({ eventName: eventName });
for (var i = 0; i < this._categoryItems.length; ++i) {
var categoryItem = this._categoryItems[i];
for (var eventName in categoryItem.children) {
var breakpointItem = categoryItem.children[eventName];
if (breakpointItem.checkbox.checked)
breakpoints.push({ eventName: eventName, targetNames: categoryItem.targetNames });
}
}
WebInspector.settings.eventListenerBreakpoints.set(breakpoints);
},
......@@ -726,7 +808,7 @@ WebInspector.EventListenerBreakpointsSidebarPane.prototype = {
for (var i = 0; i < breakpoints.length; ++i) {
var breakpoint = breakpoints[i];
if (breakpoint && typeof breakpoint.eventName === "string")
this._setBreakpoint(breakpoint.eventName);
this._setBreakpoint(breakpoint.eventName, breakpoint.targetNames);
}
},
......
......@@ -277,15 +277,16 @@ WebInspector.SourcesPanel.prototype = {
WebInspector.domBreakpointsSidebarPane.highlightBreakpoint(details.auxData);
WebInspector.domBreakpointsSidebarPane.createBreakpointHitStatusMessage(details, didCreateBreakpointHitStatusMessage.bind(this));
} else if (details.reason === WebInspector.DebuggerModel.BreakReason.EventListener) {
var eventName = details.auxData.eventName;
this.sidebarPanes.eventListenerBreakpoints.highlightBreakpoint(details.auxData.eventName);
var eventName = details.auxData["eventName"];
var targetName = details.auxData["targetName"];
this.sidebarPanes.eventListenerBreakpoints.highlightBreakpoint(eventName, targetName);
var eventNameForUI = WebInspector.EventListenerBreakpointsSidebarPane.eventNameForUI(eventName, details.auxData);
this.sidebarPanes.callstack.setStatus(WebInspector.UIString("Paused on a \"%s\" Event Listener.", eventNameForUI));
} else if (details.reason === WebInspector.DebuggerModel.BreakReason.XHR) {
this.sidebarPanes.xhrBreakpoints.highlightBreakpoint(details.auxData["breakpointURL"]);
this.sidebarPanes.callstack.setStatus(WebInspector.UIString("Paused on a XMLHttpRequest."));
} else if (details.reason === WebInspector.DebuggerModel.BreakReason.Exception)
this.sidebarPanes.callstack.setStatus(WebInspector.UIString("Paused on exception: '%s'.", details.auxData.description));
this.sidebarPanes.callstack.setStatus(WebInspector.UIString("Paused on exception: '%s'.", details.auxData["description"]));
else if (details.reason === WebInspector.DebuggerModel.BreakReason.Assert)
this.sidebarPanes.callstack.setStatus(WebInspector.UIString("Paused on assertion."));
else if (details.reason === WebInspector.DebuggerModel.BreakReason.CSPViolation)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment