зеркало из https://github.com/mozilla/gecko-dev.git
Merge mozilla-central and fx-team
This commit is contained in:
Коммит
45ce5fd943
|
@ -1058,6 +1058,7 @@ pref("devtools.toolbox.sideEnabled", true);
|
|||
pref("devtools.inspector.enabled", true);
|
||||
pref("devtools.inspector.activeSidebar", "ruleview");
|
||||
pref("devtools.inspector.markupPreview", false);
|
||||
pref("devtools.inspector.remote", false);
|
||||
|
||||
// Enable the Layout View
|
||||
pref("devtools.layoutview.enabled", true);
|
||||
|
|
|
@ -54,6 +54,7 @@ FontInspector.prototype = {
|
|||
*/
|
||||
onNewNode: function FI_onNewNode() {
|
||||
if (this.isActive() &&
|
||||
this.inspector.selection.isLocal() &&
|
||||
this.inspector.selection.isConnected() &&
|
||||
this.inspector.selection.isElementNode() &&
|
||||
this.inspector.selection.reason != "highlighter") {
|
||||
|
|
|
@ -148,6 +148,11 @@ ToolSidebar.prototype = {
|
|||
*/
|
||||
handleEvent: function ToolSidebar_eventHandler(event) {
|
||||
if (event.type == "select") {
|
||||
if (this._currentTool == this.getCurrentTabID()) {
|
||||
// Tool hasn't changed.
|
||||
return;
|
||||
}
|
||||
|
||||
let previousTool = this._currentTool;
|
||||
this._currentTool = this.getCurrentTabID();
|
||||
if (previousTool) {
|
||||
|
|
|
@ -15,6 +15,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "DebuggerServer",
|
|||
XPCOMUtils.defineLazyModuleGetter(this, "DebuggerClient",
|
||||
"resource://gre/modules/devtools/dbg-client.jsm");
|
||||
|
||||
loader.lazyGetter(this, "InspectorFront", () => require("devtools/server/actors/inspector").InspectorFront);
|
||||
|
||||
const targets = new WeakMap();
|
||||
const promiseTargets = new WeakMap();
|
||||
|
||||
|
@ -250,6 +252,17 @@ TabTarget.prototype = {
|
|||
return !!this._isThreadPaused;
|
||||
},
|
||||
|
||||
get inspector() {
|
||||
if (!this.form) {
|
||||
throw new Error("Target.inspector requires an initialized remote actor.");
|
||||
}
|
||||
if (this._inspector) {
|
||||
return this._inspector;
|
||||
}
|
||||
this._inspector = InspectorFront(this.client, this.form);
|
||||
return this._inspector;
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds remote protocol capabilities to the target, so that it can be used
|
||||
* for tools that support the Remote Debugging Protocol even for local
|
||||
|
|
|
@ -12,6 +12,9 @@ const ENSURE_SELECTION_VISIBLE_DELAY = 50; // ms
|
|||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource:///modules/devtools/DOMHelpers.jsm");
|
||||
Cu.import("resource:///modules/devtools/LayoutHelpers.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
let promise = require("sdk/core/promise");
|
||||
|
||||
const LOW_PRIORITY_ELEMENTS = {
|
||||
"HEAD": true,
|
||||
|
@ -25,6 +28,18 @@ const LOW_PRIORITY_ELEMENTS = {
|
|||
"TITLE": true,
|
||||
};
|
||||
|
||||
function resolveNextTick(value) {
|
||||
let deferred = promise.defer();
|
||||
Services.tm.mainThread.dispatch(() => {
|
||||
try {
|
||||
deferred.resolve(value);
|
||||
} catch(ex) {
|
||||
console.error(ex);
|
||||
}
|
||||
}, Ci.nsIThread.DISPATCH_NORMAL);
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
//// HTML Breadcrumbs
|
||||
|
||||
|
@ -53,6 +68,8 @@ function HTMLBreadcrumbs(aInspector)
|
|||
exports.HTMLBreadcrumbs = HTMLBreadcrumbs;
|
||||
|
||||
HTMLBreadcrumbs.prototype = {
|
||||
get walker() this.inspector.walker,
|
||||
|
||||
_init: function BC__init()
|
||||
{
|
||||
this.container = this.chromeDoc.getElementById("inspector-breadcrumbs");
|
||||
|
@ -83,12 +100,37 @@ HTMLBreadcrumbs.prototype = {
|
|||
|
||||
this.update = this.update.bind(this);
|
||||
this.updateSelectors = this.updateSelectors.bind(this);
|
||||
this.selection.on("new-node", this.update);
|
||||
this.selection.on("new-node-front", this.update);
|
||||
this.selection.on("pseudoclass", this.updateSelectors);
|
||||
this.selection.on("attribute-changed", this.updateSelectors);
|
||||
this.update();
|
||||
},
|
||||
|
||||
/**
|
||||
* Include in a promise's then() chain to reject the chain
|
||||
* when the breadcrumbs' selection has changed while the promise
|
||||
* was outstanding.
|
||||
*/
|
||||
selectionGuard: function() {
|
||||
let selection = this.selection.nodeFront;
|
||||
return (result) => {
|
||||
if (selection != this.selection.nodeFront) {
|
||||
return promise.reject("selection-changed");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Print any errors (except selection guard errors).
|
||||
*/
|
||||
selectionGuardEnd: function(err) {
|
||||
if (err != "selection-changed") {
|
||||
console.error(err);
|
||||
}
|
||||
promise.reject(err);
|
||||
},
|
||||
|
||||
/**
|
||||
* Build a string that represents the node: tagName#id.class1.class2.
|
||||
*
|
||||
|
@ -101,12 +143,19 @@ HTMLBreadcrumbs.prototype = {
|
|||
if (aNode.id) {
|
||||
text += "#" + aNode.id;
|
||||
}
|
||||
for (let i = 0; i < aNode.classList.length; i++) {
|
||||
text += "." + aNode.classList[i];
|
||||
|
||||
if (aNode.className) {
|
||||
let classList = aNode.className.split(/\s+/);
|
||||
for (let i = 0; i < classList.length; i++) {
|
||||
text += "." + classList[i];
|
||||
}
|
||||
}
|
||||
|
||||
// XXX: needs updating when pseudoclass-lock is remotable
|
||||
let rawNode = aNode.rawNode();
|
||||
for (let i = 0; i < PSEUDO_CLASSES.length; i++) {
|
||||
let pseudo = PSEUDO_CLASSES[i];
|
||||
if (DOMUtils.hasPseudoClassLock(aNode, pseudo)) {
|
||||
if (DOMUtils.hasPseudoClassLock(rawNode, pseudo)) {
|
||||
text += pseudo;
|
||||
}
|
||||
}
|
||||
|
@ -143,14 +192,20 @@ HTMLBreadcrumbs.prototype = {
|
|||
tagLabel.textContent = aNode.tagName.toLowerCase();
|
||||
idLabel.textContent = aNode.id ? ("#" + aNode.id) : "";
|
||||
|
||||
let classesText = "";
|
||||
for (let i = 0; i < aNode.classList.length; i++) {
|
||||
classesText += "." + aNode.classList[i];
|
||||
if (aNode.className) {
|
||||
let classesText = "";
|
||||
let classList = aNode.className.split(/\s+/);
|
||||
for (let i = 0; i < classList.length; i++) {
|
||||
classesText += "." + classList[i];
|
||||
}
|
||||
classesLabel.textContent = classesText;
|
||||
}
|
||||
classesLabel.textContent = classesText;
|
||||
|
||||
// XXX: Until we have pseudoclass lock in the node.
|
||||
let rawNode = aNode.rawNode();
|
||||
|
||||
let pseudos = PSEUDO_CLASSES.filter(function(pseudo) {
|
||||
return DOMUtils.hasPseudoClassLock(aNode, pseudo);
|
||||
return DOMUtils.hasPseudoClassLock(rawNode, pseudo);
|
||||
}, this);
|
||||
pseudosLabel.textContent = pseudos.join("");
|
||||
|
||||
|
@ -173,7 +228,7 @@ HTMLBreadcrumbs.prototype = {
|
|||
// We make sure that the targeted node is selected
|
||||
// because we want to use the nodemenu that only works
|
||||
// for inspector.selection
|
||||
this.selection.setNode(aNode, "breadcrumbs");
|
||||
this.selection.setNodeFront(aNode, "breadcrumbs");
|
||||
|
||||
let title = this.chromeDoc.createElement("menuitem");
|
||||
title.setAttribute("label", this.inspector.strings.GetStringFromName("breadcrumbs.siblings"));
|
||||
|
@ -183,9 +238,11 @@ HTMLBreadcrumbs.prototype = {
|
|||
|
||||
let items = [title, separator];
|
||||
|
||||
let nodes = aNode.parentNode.childNodes;
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
if (nodes[i].nodeType == aNode.ELEMENT_NODE) {
|
||||
this.walker.siblings(aNode, {
|
||||
whatToShow: Ci.nsIDOMNodeFilter.SHOW_ELEMENT
|
||||
}).then(siblings => {
|
||||
let nodes = siblings.nodes;
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
let item = this.chromeDoc.createElement("menuitem");
|
||||
if (nodes[i] === aNode) {
|
||||
item.setAttribute("disabled", "true");
|
||||
|
@ -198,14 +255,14 @@ HTMLBreadcrumbs.prototype = {
|
|||
let selection = this.selection;
|
||||
item.onmouseup = (function(aNode) {
|
||||
return function() {
|
||||
selection.setNode(aNode, "breadcrumbs");
|
||||
selection.setNodeFront(aNode, "breadcrumbs");
|
||||
}
|
||||
})(nodes[i]);
|
||||
|
||||
items.push(item);
|
||||
this.inspector.showNodeMenu(aButton, "before_start", items);
|
||||
}
|
||||
}
|
||||
this.inspector.showNodeMenu(aButton, "before_start", items);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -252,33 +309,40 @@ HTMLBreadcrumbs.prototype = {
|
|||
|
||||
if (event.type == "keypress" && this.selection.isElementNode()) {
|
||||
let node = null;
|
||||
switch (event.keyCode) {
|
||||
case this.chromeWin.KeyEvent.DOM_VK_LEFT:
|
||||
if (this.currentIndex != 0) {
|
||||
node = this.nodeHierarchy[this.currentIndex - 1].node;
|
||||
|
||||
|
||||
this._keyPromise = this._keyPromise || promise.resolve(null);
|
||||
|
||||
this._keyPromise = (this._keyPromise || promise.resolve(null)).then(() => {
|
||||
switch (event.keyCode) {
|
||||
case this.chromeWin.KeyEvent.DOM_VK_LEFT:
|
||||
if (this.currentIndex != 0) {
|
||||
node = promise.resolve(this.nodeHierarchy[this.currentIndex - 1].node);
|
||||
}
|
||||
break;
|
||||
case this.chromeWin.KeyEvent.DOM_VK_RIGHT:
|
||||
if (this.currentIndex < this.nodeHierarchy.length - 1) {
|
||||
node = promise.resolve(this.nodeHierarchy[this.currentIndex + 1].node);
|
||||
}
|
||||
break;
|
||||
case this.chromeWin.KeyEvent.DOM_VK_UP:
|
||||
node = this.walker.previousSibling(this.selection.nodeFront, {
|
||||
whatToShow: Ci.nsIDOMNodeFilter.SHOW_ELEMENT
|
||||
});
|
||||
break;
|
||||
case this.chromeWin.KeyEvent.DOM_VK_DOWN:
|
||||
node = this.walker.nextSibling(this.selection.nodeFront, {
|
||||
whatToShow: Ci.nsIDOMNodeFilter.SHOW_ELEMENT
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
return node.then((node) => {
|
||||
if (node) {
|
||||
this.selection.setNodeFront(node, "breadcrumbs");
|
||||
}
|
||||
break;
|
||||
case this.chromeWin.KeyEvent.DOM_VK_RIGHT:
|
||||
if (this.currentIndex < this.nodeHierarchy.length - 1) {
|
||||
node = this.nodeHierarchy[this.currentIndex + 1].node;
|
||||
}
|
||||
break;
|
||||
case this.chromeWin.KeyEvent.DOM_VK_UP:
|
||||
node = this.selection.node.previousSibling;
|
||||
while (node && (node.nodeType != node.ELEMENT_NODE)) {
|
||||
node = node.previousSibling;
|
||||
}
|
||||
break;
|
||||
case this.chromeWin.KeyEvent.DOM_VK_DOWN:
|
||||
node = this.selection.node.nextSibling;
|
||||
while (node && (node.nodeType != node.ELEMENT_NODE)) {
|
||||
node = node.nextSibling;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (node) {
|
||||
this.selection.setNode(node, "breadcrumbs");
|
||||
}
|
||||
});
|
||||
});
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
@ -290,12 +354,18 @@ HTMLBreadcrumbs.prototype = {
|
|||
destroy: function BC_destroy()
|
||||
{
|
||||
this.nodeHierarchy.forEach(function(crumb) {
|
||||
if (LayoutHelpers.isNodeConnected(crumb.node)) {
|
||||
DOMUtils.clearPseudoClassLocks(crumb.node);
|
||||
// This node might have already been destroyed during
|
||||
// shutdown. Will clean this up when pseudo-class lock
|
||||
// is ported to the walker.
|
||||
if (crumb.node.actorID) {
|
||||
let rawNode = crumb.node.rawNode();
|
||||
if (LayoutHelpers.isNodeConnected(rawNode)) {
|
||||
DOMUtils.clearPseudoClassLocks(rawNode);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.selection.off("new-node", this.update);
|
||||
this.selection.off("new-node-front", this.update);
|
||||
this.selection.off("pseudoclass", this.updateSelectors);
|
||||
this.selection.off("attribute-changed", this.updateSelectors);
|
||||
|
||||
|
@ -401,7 +471,7 @@ HTMLBreadcrumbs.prototype = {
|
|||
}
|
||||
|
||||
button.onBreadcrumbsClick = function onBreadcrumbsClick() {
|
||||
this.selection.setNode(aNode, "breadcrumbs");
|
||||
this.selection.setNodeFront(aNode, "breadcrumbs");
|
||||
}.bind(this);
|
||||
|
||||
button.onclick = (function _onBreadcrumbsRightClick(event) {
|
||||
|
@ -437,7 +507,7 @@ HTMLBreadcrumbs.prototype = {
|
|||
fragment.insertBefore(button, lastButtonInserted);
|
||||
lastButtonInserted = button;
|
||||
this.nodeHierarchy.splice(originalLength, 0, {node: toAppend, button: button});
|
||||
toAppend = this.DOMHelpers.getParentObject(toAppend);
|
||||
toAppend = toAppend.parentNode();
|
||||
}
|
||||
this.container.appendChild(fragment, this.container.firstChild);
|
||||
},
|
||||
|
@ -451,24 +521,37 @@ HTMLBreadcrumbs.prototype = {
|
|||
*/
|
||||
getInterestingFirstNode: function BC_getInterestingFirstNode(aNode)
|
||||
{
|
||||
let nextChild = this.DOMHelpers.getChildObject(aNode, 0);
|
||||
let fallback = null;
|
||||
let deferred = promise.defer();
|
||||
|
||||
while (nextChild) {
|
||||
if (nextChild.nodeType == aNode.ELEMENT_NODE) {
|
||||
if (!(nextChild.tagName in LOW_PRIORITY_ELEMENTS)) {
|
||||
return nextChild;
|
||||
var fallback = null;
|
||||
|
||||
var moreChildren = () => {
|
||||
this.walker.children(aNode, {
|
||||
start: fallback,
|
||||
maxNodes: 10,
|
||||
whatToShow: Ci.nsIDOMNodeFilter.SHOW_ELEMENT
|
||||
}).then(this.selectionGuard()).then(response => {
|
||||
for (let node of response.nodes) {
|
||||
if (!(node.tagName in LOW_PRIORITY_ELEMENTS)) {
|
||||
deferred.resolve(node);
|
||||
return;
|
||||
}
|
||||
if (!fallback) {
|
||||
fallback = node;
|
||||
}
|
||||
}
|
||||
if (!fallback) {
|
||||
fallback = nextChild;
|
||||
if (response.hasLast) {
|
||||
deferred.resolve(fallback);
|
||||
return;
|
||||
} else {
|
||||
moreChildren();
|
||||
}
|
||||
}
|
||||
nextChild = this.DOMHelpers.getNextSibling(nextChild);
|
||||
}).then(null, this.selectionGuardEnd);
|
||||
}
|
||||
return fallback;
|
||||
moreChildren();
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Find the "youngest" ancestor of a node which is already in the breadcrumbs.
|
||||
*
|
||||
|
@ -483,7 +566,7 @@ HTMLBreadcrumbs.prototype = {
|
|||
if (idx > -1) {
|
||||
return idx;
|
||||
} else {
|
||||
node = this.DOMHelpers.getParentObject(node);
|
||||
node = node.parentNode();
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
|
@ -498,13 +581,16 @@ HTMLBreadcrumbs.prototype = {
|
|||
// If the last displayed node is the selected node
|
||||
if (this.currentIndex == this.nodeHierarchy.length - 1) {
|
||||
let node = this.nodeHierarchy[this.currentIndex].node;
|
||||
let child = this.getInterestingFirstNode(node);
|
||||
// If the node has a child
|
||||
if (child) {
|
||||
// Show this child
|
||||
this.expand(child);
|
||||
}
|
||||
return this.getInterestingFirstNode(node).then(child => {
|
||||
// If the node has a child
|
||||
if (child) {
|
||||
// Show this child
|
||||
this.expand(child);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return resolveNextTick(true);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -560,7 +646,7 @@ HTMLBreadcrumbs.prototype = {
|
|||
return;
|
||||
}
|
||||
|
||||
let idx = this.indexOf(this.selection.node);
|
||||
let idx = this.indexOf(this.selection.nodeFront);
|
||||
|
||||
// Is the node already displayed in the breadcrumbs?
|
||||
if (idx > -1) {
|
||||
|
@ -571,24 +657,32 @@ HTMLBreadcrumbs.prototype = {
|
|||
if (this.nodeHierarchy.length > 0) {
|
||||
// No. We drop all the element that are not direct ancestors
|
||||
// of the selection
|
||||
let parent = this.DOMHelpers.getParentObject(this.selection.node);
|
||||
let parent = this.selection.nodeFront.parentNode();
|
||||
let idx = this.getCommonAncestor(parent);
|
||||
this.cutAfter(idx);
|
||||
}
|
||||
// we append the missing button between the end of the breadcrumbs display
|
||||
// and the current node.
|
||||
this.expand(this.selection.node);
|
||||
this.expand(this.selection.nodeFront);
|
||||
|
||||
// we select the current node button
|
||||
idx = this.indexOf(this.selection.node);
|
||||
idx = this.indexOf(this.selection.nodeFront);
|
||||
this.setCursor(idx);
|
||||
}
|
||||
// Add the first child of the very last node of the breadcrumbs if possible.
|
||||
this.ensureFirstChild();
|
||||
this.updateSelectors();
|
||||
|
||||
// Make sure the selected node and its neighbours are visible.
|
||||
this.scroll();
|
||||
let doneUpdating = this.inspector.updating("breadcrumbs");
|
||||
// Add the first child of the very last node of the breadcrumbs if possible.
|
||||
this.ensureFirstChild().then(this.selectionGuard()).then(() => {
|
||||
this.updateSelectors();
|
||||
|
||||
// Make sure the selected node and its neighbours are visible.
|
||||
this.scroll();
|
||||
this.inspector.emit("breadcrumbs-updated", this.selection.nodeFront);
|
||||
doneUpdating();
|
||||
}).then(null, err => {
|
||||
doneUpdating(this.selection.nodeFront);
|
||||
this.selectionGuardEnd(err);
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -228,6 +228,15 @@ Highlighter.prototype = {
|
|||
*/
|
||||
invalidateSize: function Highlighter_invalidateSize()
|
||||
{
|
||||
// The highlighter runs locally while the selection runs remotely,
|
||||
// so we can't quite trust the selection's isConnected to protect us
|
||||
// here, do the check manually.
|
||||
if (!this.selection.node ||
|
||||
!this.selection.node.ownerDocument ||
|
||||
!this.selection.node.ownerDocument.defaultView) {
|
||||
return;
|
||||
}
|
||||
|
||||
let canHiglightNode = this.selection.isNode() &&
|
||||
this.selection.isConnected() &&
|
||||
this.selection.isElementNode();
|
||||
|
|
|
@ -13,7 +13,7 @@ let EventEmitter = require("devtools/shared/event-emitter");
|
|||
let {CssLogic} = require("devtools/styleinspector/css-logic");
|
||||
|
||||
loader.lazyGetter(this, "MarkupView", () => require("devtools/markupview/markup-view").MarkupView);
|
||||
loader.lazyGetter(this, "Selection", () => require ("devtools/inspector/selection").Selection);
|
||||
loader.lazyGetter(this, "Selection", () => require("devtools/inspector/selection").Selection);
|
||||
loader.lazyGetter(this, "HTMLBreadcrumbs", () => require("devtools/inspector/breadcrumbs").HTMLBreadcrumbs);
|
||||
loader.lazyGetter(this, "Highlighter", () => require("devtools/inspector/highlighter").Highlighter);
|
||||
loader.lazyGetter(this, "ToolSidebar", () => require("devtools/framework/sidebar").ToolSidebar);
|
||||
|
@ -44,6 +44,20 @@ InspectorPanel.prototype = {
|
|||
* open is effectively an asynchronous constructor
|
||||
*/
|
||||
open: function InspectorPanel_open() {
|
||||
return this.target.makeRemote().then(() => {
|
||||
return this.target.inspector.getWalker();
|
||||
}).then(walker => {
|
||||
if (this._destroyPromise) {
|
||||
walker.release().then(null, console.error);
|
||||
}
|
||||
this.walker = walker;
|
||||
return this._getDefaultNodeForSelection();
|
||||
}).then(defaultSelection => {
|
||||
return this._deferredOpen(defaultSelection);
|
||||
}).then(null, console.error);
|
||||
},
|
||||
|
||||
_deferredOpen: function(defaultSelection) {
|
||||
let deferred = Promise.defer();
|
||||
|
||||
this.onNavigatedAway = this.onNavigatedAway.bind(this);
|
||||
|
@ -57,13 +71,13 @@ InspectorPanel.prototype = {
|
|||
this.nodemenu.addEventListener("popuphiding", this._resetNodeMenu, true);
|
||||
|
||||
// Create an empty selection
|
||||
this._selection = new Selection();
|
||||
this._selection = new Selection(this.walker);
|
||||
this.onNewSelection = this.onNewSelection.bind(this);
|
||||
this.selection.on("new-node", this.onNewSelection);
|
||||
this.selection.on("new-node-front", this.onNewSelection);
|
||||
this.onBeforeNewSelection = this.onBeforeNewSelection.bind(this);
|
||||
this.selection.on("before-new-node", this.onBeforeNewSelection);
|
||||
this.selection.on("before-new-node-front", this.onBeforeNewSelection);
|
||||
this.onDetached = this.onDetached.bind(this);
|
||||
this.selection.on("detached", this.onDetached);
|
||||
this.selection.on("detached-front", this.onDetached);
|
||||
|
||||
this.breadcrumbs = new HTMLBreadcrumbs(this);
|
||||
|
||||
|
@ -121,13 +135,7 @@ InspectorPanel.prototype = {
|
|||
this.isReady = true;
|
||||
|
||||
// All the components are initialized. Let's select a node.
|
||||
if (this.target.isLocalTab) {
|
||||
this._selection.setNode(
|
||||
this._getDefaultNodeForSelection(this.browser.contentDocument));
|
||||
} else if (this.target.window) {
|
||||
this._selection.setNode(
|
||||
this._getDefaultNodeForSelection(this.target.window.document));
|
||||
}
|
||||
this._selection.setNodeFront(defaultSelection);
|
||||
|
||||
if (this.highlighter) {
|
||||
this.highlighter.unlock();
|
||||
|
@ -146,13 +154,17 @@ InspectorPanel.prototype = {
|
|||
},
|
||||
|
||||
/**
|
||||
* Select node for default selection
|
||||
* Return a promise that will resolve to the default node for selection.
|
||||
*/
|
||||
_getDefaultNodeForSelection : function(document) {
|
||||
_getDefaultNodeForSelection : function() {
|
||||
// if available set body node as default selected node
|
||||
// else set documentElement
|
||||
var defaultNode = document.body || document.documentElement;
|
||||
return defaultNode;
|
||||
return this.walker.querySelector(this.walker.rootNode, "body").then(front => {
|
||||
if (front) {
|
||||
return front;
|
||||
}
|
||||
return this.walker.documentElement(this.walker.rootNode);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -262,35 +274,34 @@ InspectorPanel.prototype = {
|
|||
*/
|
||||
onNavigatedAway: function InspectorPanel_onNavigatedAway(event, payload) {
|
||||
let newWindow = payload._navPayload || payload;
|
||||
this.selection.setNode(null);
|
||||
this.walker.release().then(null, console.error);
|
||||
this.walker = null;
|
||||
this.selection.setNodeFront(null);
|
||||
this.selection.setWalker(null);
|
||||
this._destroyMarkup();
|
||||
this.isDirty = false;
|
||||
|
||||
let onDOMReady = function() {
|
||||
newWindow.removeEventListener("DOMContentLoaded", onDOMReady, true);
|
||||
|
||||
if (this._destroyed) {
|
||||
this.target.inspector.getWalker().then(walker => {
|
||||
if (this._destroyPromise) {
|
||||
walker.release().then(null, console.error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.selection.node) {
|
||||
let defaultNode = this._getDefaultNodeForSelection(newWindow.document);
|
||||
this.selection.setNode(defaultNode, "navigateaway");
|
||||
}
|
||||
this._initMarkup();
|
||||
this.walker = walker;
|
||||
this.selection.setWalker(walker);
|
||||
this._getDefaultNodeForSelection().then(defaultNode => {
|
||||
if (this._destroyPromise) {
|
||||
return;
|
||||
}
|
||||
this.selection.setNodeFront(defaultNode, "navigateaway");
|
||||
|
||||
this.once("markuploaded", () => {
|
||||
this.markup.expandNode(this.selection.node);
|
||||
this._initMarkup();
|
||||
this.once("markuploaded", () => {
|
||||
this.markup.expandNode(this.selection.node);
|
||||
this.setupSearchBox();
|
||||
});
|
||||
});
|
||||
|
||||
this.setupSearchBox();
|
||||
}.bind(this);
|
||||
|
||||
if (newWindow.document.readyState == "loading") {
|
||||
newWindow.addEventListener("DOMContentLoaded", onDOMReady, true);
|
||||
} else {
|
||||
onDOMReady();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -298,6 +309,69 @@ InspectorPanel.prototype = {
|
|||
*/
|
||||
onNewSelection: function InspectorPanel_onNewSelection() {
|
||||
this.cancelLayoutChange();
|
||||
|
||||
// Wait for all the known tools to finish updating and then let the
|
||||
// client know.
|
||||
let selection = this.selection.nodeFront;
|
||||
let selfUpdate = this.updating("inspector-panel");
|
||||
Services.tm.mainThread.dispatch(() => {
|
||||
try {
|
||||
selfUpdate(selection);
|
||||
} catch(ex) {
|
||||
console.error(ex);
|
||||
}
|
||||
}, Ci.nsIThread.DISPATCH_NORMAL);
|
||||
},
|
||||
|
||||
/**
|
||||
* Delay the "inspector-updated" notification while a tool
|
||||
* is updating itself. Returns a function that must be
|
||||
* invoked when the tool is done updating with the node
|
||||
* that the tool is viewing.
|
||||
*/
|
||||
updating: function(name) {
|
||||
if (this._updateProgress && this._updateProgress.node != this.selection.nodeFront) {
|
||||
this.cancelUpdate();
|
||||
}
|
||||
|
||||
if (!this._updateProgress) {
|
||||
// Start an update in progress.
|
||||
var self = this;
|
||||
this._updateProgress = {
|
||||
node: this.selection.nodeFront,
|
||||
outstanding: new Set(),
|
||||
checkDone: function() {
|
||||
if (this !== self._updateProgress) {
|
||||
return;
|
||||
}
|
||||
if (this.node !== self.selection.nodeFront) {
|
||||
self.cancelUpdate();
|
||||
return;
|
||||
}
|
||||
if (this.outstanding.size !== 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
self._updateProgress = null;
|
||||
self.emit("inspector-updated");
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
let progress = this._updateProgress;
|
||||
let done = function() {
|
||||
progress.outstanding.delete(done);
|
||||
progress.checkDone();
|
||||
};
|
||||
progress.outstanding.add(done);
|
||||
return done;
|
||||
},
|
||||
|
||||
/**
|
||||
* Cancel notification of inspector updates.
|
||||
*/
|
||||
cancelUpdate: function() {
|
||||
this._updateProgress = null;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -317,18 +391,24 @@ InspectorPanel.prototype = {
|
|||
onDetached: function InspectorPanel_onDetached(event, parentNode) {
|
||||
this.cancelLayoutChange();
|
||||
this.breadcrumbs.cutAfter(this.breadcrumbs.indexOf(parentNode));
|
||||
this.selection.setNode(parentNode, "detached");
|
||||
this.selection.setNodeFront(parentNode, "detached");
|
||||
},
|
||||
|
||||
/**
|
||||
* Destroy the inspector.
|
||||
*/
|
||||
destroy: function InspectorPanel__destroy() {
|
||||
if (this._destroyed) {
|
||||
return Promise.resolve(null);
|
||||
if (this._destroyPromise) {
|
||||
return this._destroyPromise;
|
||||
}
|
||||
if (this.walker) {
|
||||
this._destroyPromise = this.walker.release().then(null, console.error);
|
||||
delete this.walker;
|
||||
} else {
|
||||
this._destroyPromise = Promise.resolve(null);
|
||||
}
|
||||
this._destroyed = true;
|
||||
|
||||
this.cancelUpdate();
|
||||
this.cancelLayoutChange();
|
||||
|
||||
if (this.browser) {
|
||||
|
@ -358,9 +438,10 @@ InspectorPanel.prototype = {
|
|||
this.nodemenu.removeEventListener("popuphiding", this._resetNodeMenu, true);
|
||||
this.breadcrumbs.destroy();
|
||||
this.searchSuggestions.destroy();
|
||||
this.selection.off("new-node", this.onNewSelection);
|
||||
this.selection.off("new-node-front", this.onNewSelection);
|
||||
this.selection.off("before-new-node", this.onBeforeNewSelection);
|
||||
this.selection.off("detached", this.onDetached);
|
||||
this.selection.off("before-new-node-front", this.onBeforeNewSelection);
|
||||
this.selection.off("detached-front", this.onDetached);
|
||||
this._destroyMarkup();
|
||||
this._selection.destroy();
|
||||
this._selection = null;
|
||||
|
@ -374,7 +455,7 @@ InspectorPanel.prototype = {
|
|||
this.nodemenu = null;
|
||||
this.highlighter = null;
|
||||
|
||||
return Promise.resolve(null);
|
||||
return this._destroyPromise;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -501,7 +582,7 @@ InspectorPanel.prototype = {
|
|||
if (this.selection.isElementNode()) {
|
||||
if (DOMUtils.hasPseudoClassLock(this.selection.node, aPseudo)) {
|
||||
this.breadcrumbs.nodeHierarchy.forEach(function(crumb) {
|
||||
DOMUtils.removePseudoClassLock(crumb.node, aPseudo);
|
||||
DOMUtils.removePseudoClassLock(crumb.node.rawNode(), aPseudo);
|
||||
});
|
||||
} else {
|
||||
let hierarchical = aPseudo == ":hover" || aPseudo == ":active";
|
||||
|
@ -522,7 +603,7 @@ InspectorPanel.prototype = {
|
|||
clearPseudoClasses: function InspectorPanel_clearPseudoClasses() {
|
||||
this.breadcrumbs.nodeHierarchy.forEach(function(crumb) {
|
||||
try {
|
||||
DOMUtils.clearPseudoClassLocks(crumb.node);
|
||||
DOMUtils.clearPseudoClassLocks(crumb.node.rawNode());
|
||||
} catch(e) {
|
||||
// Ignore dead nodes after navigation.
|
||||
}
|
||||
|
@ -534,6 +615,9 @@ InspectorPanel.prototype = {
|
|||
*/
|
||||
toggleHighlighter: function InspectorPanel_toggleHighlighter(event)
|
||||
{
|
||||
if (!this.highlighter) {
|
||||
return;
|
||||
}
|
||||
if (event.type == "mouseover") {
|
||||
this.highlighter.hide();
|
||||
}
|
||||
|
|
|
@ -4,13 +4,13 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const {Cu} = require("chrome");
|
||||
const {Cu, Ci} = require("chrome");
|
||||
let EventEmitter = require("devtools/shared/event-emitter");
|
||||
|
||||
/**
|
||||
* API
|
||||
*
|
||||
* new Selection(node=null, track={attributes,detached});
|
||||
* new Selection(walker=null, node=null, track={attributes,detached});
|
||||
* destroy()
|
||||
* node (readonly)
|
||||
* setNode(node, origin="unknown")
|
||||
|
@ -53,19 +53,22 @@ let EventEmitter = require("devtools/shared/event-emitter");
|
|||
* @param node Inner node.
|
||||
* Can be null. Can be (un)set in the future via the "node" property;
|
||||
* @param trackAttribute Tell if events should be fired when the attributes of
|
||||
* the ndoe change.
|
||||
* the node change.
|
||||
*
|
||||
*/
|
||||
function Selection(node=null, track={attributes:true,detached:true}) {
|
||||
function Selection(walker, node=null, track={attributes:true,detached:true}) {
|
||||
EventEmitter.decorate(this);
|
||||
|
||||
this._onMutations = this._onMutations.bind(this);
|
||||
this.track = track;
|
||||
this.setWalker(walker);
|
||||
this.setNode(node);
|
||||
}
|
||||
|
||||
exports.Selection = Selection;
|
||||
|
||||
Selection.prototype = {
|
||||
_walker: null,
|
||||
_node: null,
|
||||
|
||||
_onMutations: function(mutations) {
|
||||
|
@ -86,60 +89,41 @@ Selection.prototype = {
|
|||
|
||||
if (attributeChange)
|
||||
this.emit("attribute-changed");
|
||||
if (detached)
|
||||
this.emit("detached", parentNode);
|
||||
},
|
||||
|
||||
_attachEvents: function SN__attachEvents() {
|
||||
if (!this.window || !this.isNode() || !this.track) {
|
||||
return;
|
||||
if (detached) {
|
||||
this.emit("detached", parentNode ? parentNode.rawNode() : null);
|
||||
this.emit("detached-front", parentNode);
|
||||
}
|
||||
|
||||
if (this.track.attributes) {
|
||||
this._nodeObserver = new this.window.MutationObserver(this._onMutations);
|
||||
this._nodeObserver.observe(this.node, {attributes: true});
|
||||
}
|
||||
|
||||
if (this.track.detached) {
|
||||
this._docObserver = new this.window.MutationObserver(this._onMutations);
|
||||
this._docObserver.observe(this.document.documentElement, {childList: true, subtree: true});
|
||||
}
|
||||
},
|
||||
|
||||
_detachEvents: function SN__detachEvents() {
|
||||
// `disconnect` fail if node's document has
|
||||
// been deleted.
|
||||
try {
|
||||
if (this._nodeObserver)
|
||||
this._nodeObserver.disconnect();
|
||||
} catch(e) {}
|
||||
try {
|
||||
if (this._docObserver)
|
||||
this._docObserver.disconnect();
|
||||
} catch(e) {}
|
||||
},
|
||||
|
||||
destroy: function SN_destroy() {
|
||||
this._detachEvents();
|
||||
this.setNode(null);
|
||||
this.setWalker(null);
|
||||
},
|
||||
|
||||
setNode: function SN_setNode(value, reason="unknown") {
|
||||
this.reason = reason;
|
||||
if (value !== this._node) {
|
||||
this.emit("before-new-node", value, reason);
|
||||
let previousNode = this._node;
|
||||
this._detachEvents();
|
||||
this._node = value;
|
||||
this._attachEvents();
|
||||
this.emit("new-node", previousNode, this.reason);
|
||||
setWalker: function(walker) {
|
||||
if (this._walker) {
|
||||
this._walker.off("mutations", this._onMutations);
|
||||
}
|
||||
this._walker = walker;
|
||||
if (this._walker) {
|
||||
this._walker.on("mutations", this._onMutations);
|
||||
}
|
||||
},
|
||||
|
||||
// Not remote-safe
|
||||
setNode: function SN_setNode(value, reason="unknown") {
|
||||
if (value) {
|
||||
value = this._walker.frontForRawNode(value);
|
||||
}
|
||||
this.setNodeFront(value, reason);
|
||||
},
|
||||
|
||||
// Not remote-safe
|
||||
get node() {
|
||||
return this._node;
|
||||
},
|
||||
|
||||
// Not remote-safe
|
||||
get window() {
|
||||
if (this.isNode()) {
|
||||
return this.node.ownerDocument.defaultView;
|
||||
|
@ -147,6 +131,7 @@ Selection.prototype = {
|
|||
return null;
|
||||
},
|
||||
|
||||
// Not remote-safe
|
||||
get document() {
|
||||
if (this.isNode()) {
|
||||
return this.node.ownerDocument;
|
||||
|
@ -154,28 +139,79 @@ Selection.prototype = {
|
|||
return null;
|
||||
},
|
||||
|
||||
setNodeFront: function(value, reason="unknown") {
|
||||
this.reason = reason;
|
||||
if (value !== this._nodeFront) {
|
||||
let rawValue = value ? value.rawNode() : value;
|
||||
this.emit("before-new-node", rawValue, reason);
|
||||
this.emit("before-new-node-front", value, reason);
|
||||
let previousNode = this._node;
|
||||
let previousFront = this._nodeFront;
|
||||
this._node = rawValue;
|
||||
this._nodeFront = value;
|
||||
this.emit("new-node", previousNode, this.reason);
|
||||
this.emit("new-node-front", value, this.reason);
|
||||
}
|
||||
},
|
||||
|
||||
get documentFront() {
|
||||
return this._walker.document(this._nodeFront);
|
||||
},
|
||||
|
||||
get nodeFront() {
|
||||
return this._nodeFront;
|
||||
},
|
||||
|
||||
isRoot: function SN_isRootNode() {
|
||||
return this.isNode() &&
|
||||
this.isConnected() &&
|
||||
this.node.ownerDocument.documentElement === this.node;
|
||||
this._nodeFront.isDocumentElement;
|
||||
},
|
||||
|
||||
isNode: function SN_isNode() {
|
||||
return (this.node &&
|
||||
!Cu.isDeadWrapper(this.node) &&
|
||||
this.node.ownerDocument &&
|
||||
this.node.ownerDocument.defaultView &&
|
||||
this.node instanceof this.node.ownerDocument.defaultView.Node);
|
||||
if (!this._nodeFront) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// As long as tools are still accessing node.rawNode(),
|
||||
// this needs to stay here.
|
||||
if (this._node && Cu.isDeadWrapper(this._node)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
isLocal: function SN_nsLocal() {
|
||||
return !!this._node;
|
||||
},
|
||||
|
||||
isConnected: function SN_isConnected() {
|
||||
try {
|
||||
let doc = this.document;
|
||||
return doc && doc.defaultView && doc.documentElement.contains(this.node);
|
||||
} catch (e) {
|
||||
// "can't access dead object" error
|
||||
let node = this._nodeFront;
|
||||
if (!node || !node.actorID) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// As long as there are still tools going around
|
||||
// accessing node.rawNode, this needs to stay.
|
||||
let rawNode = node.rawNode();
|
||||
if (rawNode) {
|
||||
try {
|
||||
let doc = this.document;
|
||||
return (doc && doc.defaultView && doc.documentElement.contains(rawNode));
|
||||
} catch (e) {
|
||||
// "can't access dead object" error
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
while(node) {
|
||||
if (node === this._walker.rootNode) {
|
||||
return true;
|
||||
}
|
||||
node = node.parentNode();
|
||||
};
|
||||
return false;
|
||||
},
|
||||
|
||||
isHTMLNode: function SN_isHTMLNode() {
|
||||
|
@ -186,50 +222,50 @@ Selection.prototype = {
|
|||
// Node type
|
||||
|
||||
isElementNode: function SN_isElementNode() {
|
||||
return this.isNode() && this.node.nodeType == this.window.Node.ELEMENT_NODE;
|
||||
return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.ELEMENT_NODE;
|
||||
},
|
||||
|
||||
isAttributeNode: function SN_isAttributeNode() {
|
||||
return this.isNode() && this.node.nodeType == this.window.Node.ATTRIBUTE_NODE;
|
||||
return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.ATTRIBUTE_NODE;
|
||||
},
|
||||
|
||||
isTextNode: function SN_isTextNode() {
|
||||
return this.isNode() && this.node.nodeType == this.window.Node.TEXT_NODE;
|
||||
return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.TEXT_NODE;
|
||||
},
|
||||
|
||||
isCDATANode: function SN_isCDATANode() {
|
||||
return this.isNode() && this.node.nodeType == this.window.Node.CDATA_SECTION_NODE;
|
||||
return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.CDATA_SECTION_NODE;
|
||||
},
|
||||
|
||||
isEntityRefNode: function SN_isEntityRefNode() {
|
||||
return this.isNode() && this.node.nodeType == this.window.Node.ENTITY_REFERENCE_NODE;
|
||||
return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.ENTITY_REFERENCE_NODE;
|
||||
},
|
||||
|
||||
isEntityNode: function SN_isEntityNode() {
|
||||
return this.isNode() && this.node.nodeType == this.window.Node.ENTITY_NODE;
|
||||
return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.ENTITY_NODE;
|
||||
},
|
||||
|
||||
isProcessingInstructionNode: function SN_isProcessingInstructionNode() {
|
||||
return this.isNode() && this.node.nodeType == this.window.Node.PROCESSING_INSTRUCTION_NODE;
|
||||
return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.PROCESSING_INSTRUCTION_NODE;
|
||||
},
|
||||
|
||||
isCommentNode: function SN_isCommentNode() {
|
||||
return this.isNode() && this.node.nodeType == this.window.Node.PROCESSING_INSTRUCTION_NODE;
|
||||
return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.PROCESSING_INSTRUCTION_NODE;
|
||||
},
|
||||
|
||||
isDocumentNode: function SN_isDocumentNode() {
|
||||
return this.isNode() && this.node.nodeType == this.window.Node.DOCUMENT_NODE;
|
||||
return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.DOCUMENT_NODE;
|
||||
},
|
||||
|
||||
isDocumentTypeNode: function SN_isDocumentTypeNode() {
|
||||
return this.isNode() && this.node.nodeType ==this.window. Node.DOCUMENT_TYPE_NODE;
|
||||
return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.DOCUMENT_TYPE_NODE;
|
||||
},
|
||||
|
||||
isDocumentFragmentNode: function SN_isDocumentFragmentNode() {
|
||||
return this.isNode() && this.node.nodeType == this.window.Node.DOCUMENT_FRAGMENT_NODE;
|
||||
return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.DOCUMENT_FRAGMENT_NODE;
|
||||
},
|
||||
|
||||
isNotationNode: function SN_isNotationNode() {
|
||||
return this.isNode() && this.node.nodeType == this.window.Node.NOTATION_NODE;
|
||||
return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.NOTATION_NODE;
|
||||
},
|
||||
}
|
||||
|
|
|
@ -44,7 +44,7 @@ function test()
|
|||
{
|
||||
inspector = aInspector;
|
||||
cursor = 0;
|
||||
inspector.selection.on("new-node", nodeSelected);
|
||||
inspector.on("breadcrumbs-updated", nodeSelected);
|
||||
executeSoon(function() {
|
||||
inspector.selection.setNode(nodes[0].node);
|
||||
});
|
||||
|
@ -52,17 +52,16 @@ function test()
|
|||
|
||||
function nodeSelected()
|
||||
{
|
||||
executeSoon(function() {
|
||||
performTest();
|
||||
cursor++;
|
||||
if (cursor >= nodes.length) {
|
||||
inspector.selection.off("new-node", nodeSelected);
|
||||
finishUp();
|
||||
} else {
|
||||
let node = nodes[cursor].node;
|
||||
inspector.selection.setNode(node);
|
||||
}
|
||||
});
|
||||
performTest();
|
||||
cursor++;
|
||||
|
||||
if (cursor >= nodes.length) {
|
||||
inspector.off("breadcrumbs-updated", nodeSelected);
|
||||
finishUp();
|
||||
} else {
|
||||
let node = nodes[cursor].node;
|
||||
inspector.selection.setNode(node);
|
||||
}
|
||||
}
|
||||
|
||||
function performTest()
|
||||
|
|
|
@ -34,11 +34,12 @@ function test()
|
|||
{
|
||||
inspector = aInspector;
|
||||
|
||||
executeSoon(function() {
|
||||
inspector.selection.once("new-node", highlightHeaderNode);
|
||||
// Test that navigating around without a selected node gets us to the
|
||||
// head element.
|
||||
node = doc.querySelector("h1");
|
||||
// Make sure the body element is selected initially.
|
||||
node = doc.querySelector("body");
|
||||
inspector.once("inspector-updated", () => {
|
||||
is(inspector.selection.node, node, "Body should be selected initially.");
|
||||
node = doc.querySelector("h1")
|
||||
inspector.once("inspector-updated", highlightHeaderNode);
|
||||
let bc = inspector.breadcrumbs;
|
||||
bc.nodeHierarchy[bc.currentIndex].button.focus();
|
||||
EventUtils.synthesizeKey("VK_RIGHT", { });
|
||||
|
@ -50,7 +51,7 @@ function test()
|
|||
is(inspector.selection.node, node, "selected h1 element");
|
||||
|
||||
executeSoon(function() {
|
||||
inspector.selection.once("new-node", highlightParagraphNode);
|
||||
inspector.once("inspector-updated", highlightParagraphNode);
|
||||
// Test that moving to the next sibling works.
|
||||
node = doc.querySelector("p");
|
||||
EventUtils.synthesizeKey("VK_DOWN", { });
|
||||
|
@ -62,7 +63,7 @@ function test()
|
|||
is(inspector.selection.node, node, "selected p element");
|
||||
|
||||
executeSoon(function() {
|
||||
inspector.selection.once("new-node", highlightHeaderNodeAgain);
|
||||
inspector.once("inspector-updated", highlightHeaderNodeAgain);
|
||||
// Test that moving to the previous sibling works.
|
||||
node = doc.querySelector("h1");
|
||||
EventUtils.synthesizeKey("VK_UP", { });
|
||||
|
@ -74,7 +75,7 @@ function test()
|
|||
is(inspector.selection.node, node, "selected h1 element");
|
||||
|
||||
executeSoon(function() {
|
||||
inspector.selection.once("new-node", highlightParentNode);
|
||||
inspector.once("inspector-updated", highlightParentNode);
|
||||
// Test that moving to the parent works.
|
||||
node = doc.querySelector("body");
|
||||
EventUtils.synthesizeKey("VK_LEFT", { });
|
||||
|
|
|
@ -34,9 +34,10 @@ function test()
|
|||
let tmp = {};
|
||||
Cu.import("resource:///modules/devtools/LayoutHelpers.jsm", tmp);
|
||||
ok(!tmp.LayoutHelpers.isNodeConnected(node), "Node considered as disconnected.");
|
||||
executeSoon(function() {
|
||||
is(inspector.selection.node, parentNode, "parent of selection got selected");
|
||||
|
||||
// Wait for the selection to process the mutation
|
||||
inspector.walker.on("mutations", () => {
|
||||
is(inspector.selection.node, parentNode, "parent of selection got selected");
|
||||
finishUp();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -40,27 +40,29 @@ function startInspectorTests(toolbox)
|
|||
let p = doc.querySelector("p");
|
||||
|
||||
inspector.selection.setNode(p);
|
||||
inspector.once("inspector-updated", () => {
|
||||
testHighlighter(p);
|
||||
testMarkupView(p);
|
||||
testBreadcrumbs(p);
|
||||
|
||||
testHighlighter(p);
|
||||
testMarkupView(p);
|
||||
testBreadcrumbs(p);
|
||||
let span = doc.querySelector("span");
|
||||
span.scrollIntoView();
|
||||
|
||||
let span = doc.querySelector("span");
|
||||
span.scrollIntoView();
|
||||
inspector.selection.setNode(span);
|
||||
inspector.once("inspector-updated", () => {
|
||||
testHighlighter(span);
|
||||
testMarkupView(span);
|
||||
testBreadcrumbs(span);
|
||||
|
||||
inspector.selection.setNode(span);
|
||||
|
||||
testHighlighter(span);
|
||||
testMarkupView(span);
|
||||
testBreadcrumbs(span);
|
||||
|
||||
toolbox.once("destroyed", function() {
|
||||
ok("true", "'destroyed' notification received.");
|
||||
let target = TargetFactory.forTab(gBrowser.selectedTab);
|
||||
ok(!gDevTools.getToolbox(target), "Toolbox destroyed.");
|
||||
executeSoon(runContextMenuTest);
|
||||
toolbox.once("destroyed", function() {
|
||||
ok("true", "'destroyed' notification received.");
|
||||
let target = TargetFactory.forTab(gBrowser.selectedTab);
|
||||
ok(!gDevTools.getToolbox(target), "Toolbox destroyed.");
|
||||
executeSoon(runContextMenuTest);
|
||||
});
|
||||
toolbox.destroy();
|
||||
});
|
||||
});
|
||||
toolbox.destroy();
|
||||
}
|
||||
|
||||
|
||||
|
@ -79,7 +81,7 @@ function testMarkupView(node)
|
|||
function testBreadcrumbs(node)
|
||||
{
|
||||
let b = getActiveInspector().breadcrumbs;
|
||||
let expectedText = b.prettyPrintNodeAsText(node);
|
||||
let expectedText = b.prettyPrintNodeAsText(getNodeFront(node));
|
||||
let button = b.container.querySelector("button[checked=true]");
|
||||
ok(button, "A crumbs is checked=true");
|
||||
is(button.getAttribute("tooltiptext"), expectedText, "Crumb refers to the right node");
|
||||
|
|
|
@ -31,21 +31,23 @@ function test() {
|
|||
function checkDocTypeMenuItems() {
|
||||
info("Checking context menu entries for doctype node");
|
||||
inspector.selection.setNode(doc.doctype);
|
||||
let docTypeNode = getMarkupTagNodeContaining("<!DOCTYPE html>");
|
||||
inspector.once("inspector-updated", () => {
|
||||
let docTypeNode = getMarkupTagNodeContaining("<!DOCTYPE html>");
|
||||
|
||||
// Right-click doctype tag
|
||||
contextMenuClick(docTypeNode);
|
||||
// Right-click doctype tag
|
||||
contextMenuClick(docTypeNode);
|
||||
|
||||
checkDisabled("node-menu-copyinner");
|
||||
checkDisabled("node-menu-copyouter");
|
||||
checkDisabled("node-menu-copyuniqueselector");
|
||||
checkDisabled("node-menu-delete");
|
||||
checkDisabled("node-menu-copyinner");
|
||||
checkDisabled("node-menu-copyouter");
|
||||
checkDisabled("node-menu-copyuniqueselector");
|
||||
checkDisabled("node-menu-delete");
|
||||
|
||||
for (let name of ["hover", "active", "focus"]) {
|
||||
checkDisabled("node-menu-pseudo-" + name);
|
||||
}
|
||||
for (let name of ["hover", "active", "focus"]) {
|
||||
checkDisabled("node-menu-pseudo-" + name);
|
||||
}
|
||||
|
||||
checkElementMenuItems();
|
||||
checkElementMenuItems();
|
||||
});
|
||||
}
|
||||
|
||||
function checkElementMenuItems() {
|
||||
|
|
|
@ -51,11 +51,11 @@ function createDocument()
|
|||
function selectNode(aInspector)
|
||||
{
|
||||
inspector = aInspector;
|
||||
inspector.selection.setNode(div);
|
||||
inspector.sidebar.once("ruleview-ready", function() {
|
||||
ruleview = inspector.sidebar.getWindowForTab("ruleview").ruleview.view;
|
||||
inspector.sidebar.select("ruleview");
|
||||
performTests();
|
||||
inspector.selection.setNode(div);
|
||||
inspector.once("inspector-updated", performTests);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -75,29 +75,37 @@ function performTests()
|
|||
// toggle it back on
|
||||
inspector.togglePseudoClass(pseudo);
|
||||
|
||||
testNavigate();
|
||||
|
||||
// close the inspector
|
||||
finishUp();
|
||||
testNavigate(() => {
|
||||
// close the inspector
|
||||
finishUp();
|
||||
});
|
||||
}
|
||||
|
||||
function testNavigate()
|
||||
function testNavigate(callback)
|
||||
{
|
||||
inspector.selection.setNode(parentDiv);
|
||||
inspector.once("inspector-updated", () => {
|
||||
|
||||
// make sure it's still on after naving to parent
|
||||
is(DOMUtils.hasPseudoClassLock(div, pseudo), true,
|
||||
"pseudo-class lock is still applied after inspecting ancestor");
|
||||
// make sure it's still on after naving to parent
|
||||
is(DOMUtils.hasPseudoClassLock(div, pseudo), true,
|
||||
"pseudo-class lock is still applied after inspecting ancestor");
|
||||
|
||||
inspector.selection.setNode(div2);
|
||||
inspector.selection.setNode(div2);
|
||||
|
||||
// make sure it's removed after naving to a non-hierarchy node
|
||||
is(DOMUtils.hasPseudoClassLock(div, pseudo), false,
|
||||
"pseudo-class lock is removed after inspecting sibling node");
|
||||
inspector.once("inspector-updated", () => {
|
||||
|
||||
// toggle it back on
|
||||
inspector.selection.setNode(div);
|
||||
inspector.togglePseudoClass(pseudo);
|
||||
// make sure it's removed after naving to a non-hierarchy node
|
||||
is(DOMUtils.hasPseudoClassLock(div, pseudo), false,
|
||||
"pseudo-class lock is removed after inspecting sibling node");
|
||||
|
||||
// toggle it back on
|
||||
inspector.selection.setNode(div);
|
||||
inspector.once("inspector-updated", () => {
|
||||
inspector.togglePseudoClass(pseudo);
|
||||
callback();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function testAdded()
|
||||
|
|
|
@ -5,6 +5,13 @@
|
|||
const Cu = Components.utils;
|
||||
const Ci = Components.interfaces;
|
||||
const Cc = Components.classes;
|
||||
|
||||
Services.prefs.setBoolPref("devtools.debugger.log", true);
|
||||
SimpleTest.registerCleanupFunction(() => {
|
||||
Services.prefs.clearUserPref("devtools.debugger.log");
|
||||
});
|
||||
|
||||
|
||||
let tempScope = {};
|
||||
Cu.import("resource:///modules/devtools/LayoutHelpers.jsm", tempScope);
|
||||
let LayoutHelpers = tempScope.LayoutHelpers;
|
||||
|
@ -19,6 +26,12 @@ let console = tempScope.console;
|
|||
let testDir = gTestPath.substr(0, gTestPath.lastIndexOf("/"));
|
||||
Services.scriptloader.loadSubScript(testDir + "../../../commandline/test/helpers.js", this);
|
||||
|
||||
SimpleTest.registerCleanupFunction(() => {
|
||||
console.error("Here we are\n")
|
||||
let {DebuggerServer} = Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {});
|
||||
console.error("DebuggerServer open connections: " + Object.getOwnPropertyNames(DebuggerServer._connections).length);
|
||||
});
|
||||
|
||||
function openInspector(callback)
|
||||
{
|
||||
let target = TargetFactory.forTab(gBrowser.selectedTab);
|
||||
|
@ -33,6 +46,12 @@ function getActiveInspector()
|
|||
return gDevTools.getToolbox(target).getPanel("inspector");
|
||||
}
|
||||
|
||||
function getNodeFront(node)
|
||||
{
|
||||
let inspector = getActiveInspector();
|
||||
return inspector.walker.frontForRawNode(node);
|
||||
}
|
||||
|
||||
function isHighlighting()
|
||||
{
|
||||
let outline = getActiveInspector().highlighter.outline;
|
||||
|
|
|
@ -307,8 +307,11 @@ function test() {
|
|||
id: "node18",
|
||||
});
|
||||
|
||||
is(inspector.highlighter.nodeInfo.classesBox.textContent, "",
|
||||
"No classes in the infobar before edit.");
|
||||
/**
|
||||
* XXX: disabled until the remote markup view is enabled
|
||||
* is(inspector.highlighter.nodeInfo.classesBox.textContent, "",
|
||||
* "No classes in the infobar before edit.");
|
||||
*/
|
||||
},
|
||||
execute: function(after) {
|
||||
inspector.once("markupmutation", function() {
|
||||
|
@ -326,8 +329,12 @@ function test() {
|
|||
class: "newclass",
|
||||
style: "color:green"
|
||||
});
|
||||
is(inspector.highlighter.nodeInfo.classesBox.textContent, ".newclass",
|
||||
"Correct classes in the infobar after edit.");
|
||||
|
||||
/**
|
||||
* XXX: disabled until the remote markup view is enabled
|
||||
*is(inspector.highlighter.nodeInfo.classesBox.textContent, ".newclass",
|
||||
* "Correct classes in the infobar after edit.");
|
||||
*/
|
||||
}
|
||||
};
|
||||
testAsyncSetup(test, editTagName);
|
||||
|
|
|
@ -250,6 +250,7 @@ TargetEventsHandler.prototype = {
|
|||
case "will-navigate": {
|
||||
// Reset UI.
|
||||
NetMonitorView.RequestsMenu.reset();
|
||||
NetMonitorView.Sidebar.reset();
|
||||
NetMonitorView.NetworkDetails.reset();
|
||||
|
||||
// Reset global helpers cache.
|
||||
|
|
|
@ -165,7 +165,7 @@ let NetMonitorView = {
|
|||
}
|
||||
|
||||
if (aTabIndex !== undefined) {
|
||||
$("#details-pane").selectedIndex = aTabIndex;
|
||||
$("#event-details-pane").selectedIndex = aTabIndex;
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -351,8 +351,63 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
|
|||
|
||||
this.refreshSummary();
|
||||
this.refreshZebra();
|
||||
|
||||
if (aId == this._preferredItemId) {
|
||||
this.selectedItem = requestItem;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a new custom request form populated with the data from
|
||||
* the currently selected request.
|
||||
*/
|
||||
cloneSelectedRequest: function() {
|
||||
let selected = this.selectedItem.attachment;
|
||||
|
||||
// Create the element node for the network request item.
|
||||
let menuView = this._createMenuView(selected.method, selected.url);
|
||||
|
||||
let newItem = this.push([menuView], {
|
||||
attachment: Object.create(selected, {
|
||||
isCustom: { value: true }
|
||||
})
|
||||
});
|
||||
|
||||
// Immediately switch to new request pane.
|
||||
this.selectedItem = newItem;
|
||||
},
|
||||
|
||||
/**
|
||||
* Send a new HTTP request using the data in the custom request form.
|
||||
*/
|
||||
sendCustomRequest: function() {
|
||||
let selected = this.selectedItem.attachment;
|
||||
|
||||
let data = Object.create(selected, {
|
||||
headers: { value: selected.requestHeaders.headers }
|
||||
});
|
||||
|
||||
if (selected.requestPostData) {
|
||||
data.body = selected.requestPostData.postData.text;
|
||||
}
|
||||
|
||||
NetMonitorController.webConsoleClient.sendHTTPRequest(data, (response) => {
|
||||
let id = response.eventActor.actor;
|
||||
this._preferredItemId = id;
|
||||
});
|
||||
|
||||
this.closeCustomRequest();
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove the currently selected custom request.
|
||||
*/
|
||||
closeCustomRequest: function() {
|
||||
this.remove(this.selectedItem);
|
||||
|
||||
NetMonitorView.Sidebar.toggle(false);
|
||||
},
|
||||
|
||||
/**
|
||||
* Filters all network requests in this container by a specified type.
|
||||
*
|
||||
|
@ -690,11 +745,11 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
|
|||
break;
|
||||
case "status":
|
||||
requestItem.attachment.status = value;
|
||||
this._updateMenuView(requestItem, key, value);
|
||||
this.updateMenuView(requestItem, key, value);
|
||||
break;
|
||||
case "statusText":
|
||||
requestItem.attachment.statusText = value;
|
||||
this._updateMenuView(requestItem, key,
|
||||
this.updateMenuView(requestItem, key,
|
||||
requestItem.attachment.status + " " +
|
||||
requestItem.attachment.statusText);
|
||||
break;
|
||||
|
@ -703,11 +758,11 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
|
|||
break;
|
||||
case "contentSize":
|
||||
requestItem.attachment.contentSize = value;
|
||||
this._updateMenuView(requestItem, key, value);
|
||||
this.updateMenuView(requestItem, key, value);
|
||||
break;
|
||||
case "mimeType":
|
||||
requestItem.attachment.mimeType = value;
|
||||
this._updateMenuView(requestItem, key, value);
|
||||
this.updateMenuView(requestItem, key, value);
|
||||
break;
|
||||
case "responseContent":
|
||||
requestItem.attachment.responseContent = value;
|
||||
|
@ -715,7 +770,7 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
|
|||
case "totalTime":
|
||||
requestItem.attachment.totalTime = value;
|
||||
requestItem.attachment.endedMillis = requestItem.attachment.startedMillis + value;
|
||||
this._updateMenuView(requestItem, key, value);
|
||||
this.updateMenuView(requestItem, key, value);
|
||||
this._registerLastRequestEnd(requestItem.attachment.endedMillis);
|
||||
break;
|
||||
case "eventTimings":
|
||||
|
@ -757,23 +812,11 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
|
|||
* The network request view.
|
||||
*/
|
||||
_createMenuView: function(aMethod, aUrl) {
|
||||
let uri = nsIURL(aUrl);
|
||||
let nameWithQuery = this._getUriNameWithQuery(uri);
|
||||
let hostPort = this._getUriHostPort(uri);
|
||||
|
||||
let template = $("#requests-menu-item-template");
|
||||
let fragment = document.createDocumentFragment();
|
||||
|
||||
let method = $(".requests-menu-method", template);
|
||||
method.setAttribute("value", aMethod);
|
||||
|
||||
let file = $(".requests-menu-file", template);
|
||||
file.setAttribute("value", nameWithQuery);
|
||||
file.setAttribute("tooltiptext", nameWithQuery);
|
||||
|
||||
let domain = $(".requests-menu-domain", template);
|
||||
domain.setAttribute("value", hostPort);
|
||||
domain.setAttribute("tooltiptext", hostPort);
|
||||
this.updateMenuView(template, 'method', aMethod);
|
||||
this.updateMenuView(template, 'url', aUrl);
|
||||
|
||||
let waterfall = $(".requests-menu-waterfall", template);
|
||||
waterfall.style.backgroundImage = this._cachedWaterfallBackground;
|
||||
|
@ -796,22 +839,48 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
|
|||
* @param any aValue
|
||||
* The new value to be shown.
|
||||
*/
|
||||
_updateMenuView: function(aItem, aKey, aValue) {
|
||||
updateMenuView: function(aItem, aKey, aValue) {
|
||||
let target = aItem.target || aItem;
|
||||
|
||||
switch (aKey) {
|
||||
case "method": {
|
||||
let node = $(".requests-menu-method", target);
|
||||
node.setAttribute("value", aValue);
|
||||
break;
|
||||
}
|
||||
case "url": {
|
||||
let uri;
|
||||
try {
|
||||
uri = nsIURL(aValue);
|
||||
} catch(e) {
|
||||
break; // User input may not make a well-formed url yet.
|
||||
}
|
||||
let nameWithQuery = this._getUriNameWithQuery(uri);
|
||||
let hostPort = this._getUriHostPort(uri);
|
||||
|
||||
let node = $(".requests-menu-file", target);
|
||||
node.setAttribute("value", nameWithQuery);
|
||||
node.setAttribute("tooltiptext", nameWithQuery);
|
||||
|
||||
let domain = $(".requests-menu-domain", target);
|
||||
domain.setAttribute("value", hostPort);
|
||||
domain.setAttribute("tooltiptext", hostPort);
|
||||
break;
|
||||
}
|
||||
case "status": {
|
||||
let node = $(".requests-menu-status", aItem.target);
|
||||
let node = $(".requests-menu-status", target);
|
||||
node.setAttribute("code", aValue);
|
||||
break;
|
||||
}
|
||||
case "statusText": {
|
||||
let node = $(".requests-menu-status-and-method", aItem.target);
|
||||
let node = $(".requests-menu-status-and-method", target);
|
||||
node.setAttribute("tooltiptext", aValue);
|
||||
break;
|
||||
}
|
||||
case "contentSize": {
|
||||
let kb = aValue / 1024;
|
||||
let size = L10N.numberWithDecimals(kb, CONTENT_SIZE_DECIMALS);
|
||||
let node = $(".requests-menu-size", aItem.target);
|
||||
let node = $(".requests-menu-size", target);
|
||||
let text = L10N.getFormatStr("networkMenu.sizeKB", size);
|
||||
node.setAttribute("value", text);
|
||||
node.setAttribute("tooltiptext", text);
|
||||
|
@ -819,14 +888,14 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
|
|||
}
|
||||
case "mimeType": {
|
||||
let type = this._getAbbreviatedMimeType(aValue);
|
||||
let node = $(".requests-menu-type", aItem.target);
|
||||
let node = $(".requests-menu-type", target);
|
||||
let text = CONTENT_MIME_TYPE_ABBREVIATIONS[type] || type;
|
||||
node.setAttribute("value", text);
|
||||
node.setAttribute("tooltiptext", aValue);
|
||||
break;
|
||||
}
|
||||
case "totalTime": {
|
||||
let node = $(".requests-menu-timings-total", aItem.target);
|
||||
let node = $(".requests-menu-timings-total", target);
|
||||
let text = L10N.getFormatStr("networkMenu.totalMS", aValue); // integer
|
||||
node.setAttribute("value", text);
|
||||
node.setAttribute("tooltiptext", text);
|
||||
|
@ -1089,10 +1158,10 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
|
|||
*/
|
||||
_onSelect: function({ detail: item }) {
|
||||
if (item) {
|
||||
NetMonitorView.NetworkDetails.populate(item.attachment);
|
||||
NetMonitorView.NetworkDetails.toggle(true);
|
||||
NetMonitorView.Sidebar.populate(item.attachment);
|
||||
NetMonitorView.Sidebar.toggle(true);
|
||||
} else {
|
||||
NetMonitorView.NetworkDetails.toggle(false);
|
||||
NetMonitorView.Sidebar.toggle(false);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -1104,6 +1173,14 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
|
|||
drain("resize-events", RESIZE_REFRESH_RATE, () => this._flushWaterfallViews(true));
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle the context menu opening. Hide items if no request is selected.
|
||||
*/
|
||||
_onContextShowing: function() {
|
||||
let element = $("#request-menu-context-resend");
|
||||
element.hidden = !this.selectedItem || this.selectedItem.attachment.isCustom;
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks if the specified unix time is the first one to be known of,
|
||||
* and saves it if so.
|
||||
|
@ -1239,6 +1316,162 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
|
|||
_resizeTimeout: null
|
||||
});
|
||||
|
||||
/**
|
||||
* Functions handling the sidebar details view.
|
||||
*/
|
||||
function SidebarView() {
|
||||
dumpn("SidebarView was instantiated");
|
||||
}
|
||||
|
||||
SidebarView.prototype = {
|
||||
/**
|
||||
* Sets this view hidden or visible. It's visible by default.
|
||||
*
|
||||
* @param boolean aVisibleFlag
|
||||
* Specifies the intended visibility.
|
||||
*/
|
||||
toggle: function(aVisibleFlag) {
|
||||
NetMonitorView.toggleDetailsPane({ visible: aVisibleFlag });
|
||||
NetMonitorView.RequestsMenu._flushWaterfallViews(true);
|
||||
},
|
||||
|
||||
/**
|
||||
* Populates this view with the specified data.
|
||||
*
|
||||
* @param object aData
|
||||
* The data source (this should be the attachment of a request item).
|
||||
*/
|
||||
populate: function(aData) {
|
||||
if (aData.isCustom) {
|
||||
NetMonitorView.CustomRequest.populate(aData);
|
||||
$("#details-pane").selectedIndex = 0;
|
||||
} else {
|
||||
NetMonitorView.NetworkDetails.populate(aData);
|
||||
$("#details-pane").selectedIndex = 1;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Hides this container.
|
||||
*/
|
||||
reset: function() {
|
||||
this.toggle(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Functions handling the custom request view.
|
||||
*/
|
||||
function CustomRequestView() {
|
||||
dumpn("CustomRequestView was instantiated");
|
||||
}
|
||||
|
||||
CustomRequestView.prototype = {
|
||||
/**
|
||||
* Populates this view with the specified data.
|
||||
*
|
||||
* @param object aData
|
||||
* The data source (this should be the attachment of a request item).
|
||||
*/
|
||||
populate: function(aData) {
|
||||
$("#custom-url-value").value = aData.url;
|
||||
$("#custom-method-value").value = aData.method;
|
||||
$("#custom-headers-value").value =
|
||||
writeHeaderText(aData.requestHeaders.headers);
|
||||
|
||||
if (aData.requestPostData) {
|
||||
let body = aData.requestPostData.postData.text;
|
||||
|
||||
gNetwork.getString(body).then((aString) => {
|
||||
$("#custom-postdata-value").value = aString;
|
||||
});
|
||||
}
|
||||
|
||||
this.updateCustomQuery(aData.url);
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle user input in the custom request form.
|
||||
*
|
||||
* @param object aField
|
||||
* the field that the user updated.
|
||||
*/
|
||||
onUpdate: function(aField) {
|
||||
let selectedItem = NetMonitorView.RequestsMenu.selectedItem;
|
||||
let field = aField;
|
||||
let value;
|
||||
|
||||
switch(aField) {
|
||||
case 'method':
|
||||
value = $("#custom-method-value").value.trim();
|
||||
selectedItem.attachment.method = value;
|
||||
break;
|
||||
case 'url':
|
||||
value = $("#custom-url-value").value;
|
||||
this.updateCustomQuery(value);
|
||||
selectedItem.attachment.url = value;
|
||||
break;
|
||||
case 'query':
|
||||
let query = $("#custom-query-value").value;
|
||||
this.updateCustomUrl(query);
|
||||
field = 'url';
|
||||
value = $("#custom-url-value").value
|
||||
selectedItem.attachment.url = value;
|
||||
break;
|
||||
case 'body':
|
||||
value = $("#custom-postdata-value").value;
|
||||
selectedItem.attachment.requestPostData = {
|
||||
postData: {
|
||||
text: value
|
||||
}
|
||||
};
|
||||
break;
|
||||
case 'headers':
|
||||
let headersText = $("#custom-headers-value").value;
|
||||
value = parseHeaderText(headersText);
|
||||
selectedItem.attachment.requestHeaders = {
|
||||
headers: value
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
NetMonitorView.RequestsMenu.updateMenuView(selectedItem, field, value);
|
||||
},
|
||||
|
||||
/**
|
||||
* Update the query string field based on the url.
|
||||
*
|
||||
* @param object aUrl
|
||||
* url to extract query string from.
|
||||
*/
|
||||
updateCustomQuery: function(aUrl) {
|
||||
let paramsArray = parseQueryString(nsIURL(aUrl).query);
|
||||
if (!paramsArray) {
|
||||
$("#custom-query").hidden = true;
|
||||
return;
|
||||
}
|
||||
$("#custom-query").hidden = false;
|
||||
$("#custom-query-value").value = writeQueryText(paramsArray);
|
||||
},
|
||||
|
||||
/**
|
||||
* Update the url based on the query string field.
|
||||
*
|
||||
* @param object aQueryText
|
||||
* contents of the query string field.
|
||||
*/
|
||||
updateCustomUrl: function(aQueryText) {
|
||||
let params = parseQueryText(aQueryText);
|
||||
let queryString = writeQueryString(params);
|
||||
|
||||
let url = $("#custom-url-value").value;
|
||||
let oldQuery = nsIURL(url).query;
|
||||
let path = url.replace(oldQuery, queryString);
|
||||
|
||||
$("#custom-url-value").value = path;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Functions handling the requests details view.
|
||||
*/
|
||||
|
@ -1253,9 +1486,9 @@ NetworkDetailsView.prototype = {
|
|||
* Initialization function, called when the network monitor is started.
|
||||
*/
|
||||
initialize: function() {
|
||||
dumpn("Initializing the RequestsMenuView");
|
||||
dumpn("Initializing the NetworkDetailsView");
|
||||
|
||||
this.widget = $("#details-pane");
|
||||
this.widget = $("#event-details-pane");
|
||||
|
||||
this._headers = new VariablesView($("#all-headers"),
|
||||
Heritage.extend(GENERIC_VARIABLES_VIEW_SETTINGS, {
|
||||
|
@ -1292,25 +1525,13 @@ NetworkDetailsView.prototype = {
|
|||
* Destruction function, called when the network monitor is closed.
|
||||
*/
|
||||
destroy: function() {
|
||||
dumpn("Destroying the SourcesView");
|
||||
dumpn("Destroying the NetworkDetailsView");
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets this view hidden or visible. It's visible by default.
|
||||
*
|
||||
* @param boolean aVisibleFlag
|
||||
* Specifies the intended visibility.
|
||||
*/
|
||||
toggle: function(aVisibleFlag) {
|
||||
NetMonitorView.toggleDetailsPane({ visible: aVisibleFlag });
|
||||
NetMonitorView.RequestsMenu._flushWaterfallViews(true);
|
||||
},
|
||||
|
||||
/**
|
||||
* Hides and resets this container (removes all the networking information).
|
||||
* Resets this container (removes all the networking information).
|
||||
*/
|
||||
reset: function() {
|
||||
this.toggle(false);
|
||||
this._dataSrc = null;
|
||||
},
|
||||
|
||||
|
@ -1581,21 +1802,14 @@ NetworkDetailsView.prototype = {
|
|||
*
|
||||
* @param string aName
|
||||
* The type of params to populate (get or post).
|
||||
* @param string aParams
|
||||
* @param string aQueryString
|
||||
* A query string of params (e.g. "?foo=bar&baz=42").
|
||||
*/
|
||||
_addParams: function(aName, aParams) {
|
||||
// Make sure there's at least one param available.
|
||||
if (!aParams || !aParams.contains("=")) {
|
||||
_addParams: function(aName, aQueryString) {
|
||||
let paramsArray = parseQueryString(aQueryString);
|
||||
if (!paramsArray) {
|
||||
return;
|
||||
}
|
||||
// Turn the params string into an array containing { name: value } tuples.
|
||||
let paramsArray = aParams.replace(/^[?&]/, "").split("&").map((e) =>
|
||||
let (param = e.split("=")) {
|
||||
name: NetworkHelper.convertToUnicode(unescape(param[0])),
|
||||
value: NetworkHelper.convertToUnicode(unescape(param[1]))
|
||||
});
|
||||
|
||||
let paramsScope = this._params.addScope(aName);
|
||||
paramsScope.expanded = true;
|
||||
|
||||
|
@ -1807,6 +2021,110 @@ function nsIURL(aUrl, aStore = nsIURL.store) {
|
|||
}
|
||||
nsIURL.store = new Map();
|
||||
|
||||
/**
|
||||
* Parse a url's query string into its components
|
||||
*
|
||||
* @param string aQueryString
|
||||
* The query part of a url
|
||||
* @return array
|
||||
* Array of query params {name, value}
|
||||
*/
|
||||
function parseQueryString(aQueryString) {
|
||||
// Make sure there's at least one param available.
|
||||
if (!aQueryString || !aQueryString.contains("=")) {
|
||||
return;
|
||||
}
|
||||
// Turn the params string into an array containing { name: value } tuples.
|
||||
let paramsArray = aQueryString.replace(/^[?&]/, "").split("&").map((e) =>
|
||||
let (param = e.split("=")) {
|
||||
name: NetworkHelper.convertToUnicode(unescape(param[0])),
|
||||
value: NetworkHelper.convertToUnicode(unescape(param[1]))
|
||||
});
|
||||
return paramsArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse text representation of HTTP headers.
|
||||
*
|
||||
* @param string aText
|
||||
* Text of headers
|
||||
* @return array
|
||||
* Array of headers info {name, value}
|
||||
*/
|
||||
function parseHeaderText(aText) {
|
||||
return parseRequestText(aText, ":");
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse readable text list of a query string.
|
||||
*
|
||||
* @param string aText
|
||||
* Text of query string represetation
|
||||
* @return array
|
||||
* Array of query params {name, value}
|
||||
*/
|
||||
function parseQueryText(aText) {
|
||||
return parseRequestText(aText, "=");
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a text representation of a name:value list with
|
||||
* the given name:value divider character.
|
||||
*
|
||||
* @param string aText
|
||||
* Text of list
|
||||
* @return array
|
||||
* Array of headers info {name, value}
|
||||
*/
|
||||
function parseRequestText(aText, aDivider) {
|
||||
let regex = new RegExp("(.+?)\\" + aDivider + "\\s*(.+)");
|
||||
let pairs = [];
|
||||
for (let line of aText.split("\n")) {
|
||||
let matches;
|
||||
if (matches = regex.exec(line)) {
|
||||
let [, name, value] = matches;
|
||||
pairs.push({name: name, value: value});
|
||||
}
|
||||
}
|
||||
return pairs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write out a list of headers into a chunk of text
|
||||
*
|
||||
* @param array aHeaders
|
||||
* Array of headers info {name, value}
|
||||
* @return string aText
|
||||
* List of headers in text format
|
||||
*/
|
||||
function writeHeaderText(aHeaders) {
|
||||
return [(name + ": " + value) for ({name, value} of aHeaders)].join("\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Write out a list of query params into a chunk of text
|
||||
*
|
||||
* @param array aParams
|
||||
* Array of query params {name, value}
|
||||
* @return string
|
||||
* List of query params in text format
|
||||
*/
|
||||
function writeQueryText(aParams) {
|
||||
return [(name + "=" + value) for ({name, value} of aParams)].join("\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Write out a list of query params into a query string
|
||||
*
|
||||
* @param array aParams
|
||||
* Array of query params {name, value}
|
||||
* @return string
|
||||
* Query string that can be appended to a url.
|
||||
*/
|
||||
function writeQueryString(aParams) {
|
||||
return [(name + "=" + value) for ({name, value} of aParams)].join("&");
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for draining a rapid succession of events and invoking a callback
|
||||
* once everything settles down.
|
||||
|
@ -1822,4 +2140,6 @@ drain.store = new Map();
|
|||
*/
|
||||
NetMonitorView.Toolbar = new ToolbarView();
|
||||
NetMonitorView.RequestsMenu = new RequestsMenuView();
|
||||
NetMonitorView.Sidebar = new SidebarView();
|
||||
NetMonitorView.CustomRequest = new CustomRequestView();
|
||||
NetMonitorView.NetworkDetails = new NetworkDetailsView();
|
||||
|
|
|
@ -15,6 +15,10 @@
|
|||
overflow: auto;
|
||||
}
|
||||
|
||||
#custom-pane {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
#timings-summary-blocked {
|
||||
display: none; /* This doesn't work yet. */
|
||||
}
|
||||
|
|
|
@ -17,6 +17,16 @@
|
|||
<script type="text/javascript" src="netmonitor-controller.js"/>
|
||||
<script type="text/javascript" src="netmonitor-view.js"/>
|
||||
|
||||
<popupset id="networkPopupSet">
|
||||
<menupopup id="network-request-popup"
|
||||
onpopupshowing="NetMonitorView.RequestsMenu._onContextShowing(event);">
|
||||
<menuitem id="request-menu-context-resend"
|
||||
label="&netmonitorUI.summary.resend;"
|
||||
accesskey="&netmonitorUI.summary.resend.accesskey;"
|
||||
oncommand="NetMonitorView.RequestsMenu.cloneSelectedRequest();"/>
|
||||
</menupopup>
|
||||
</popupset>
|
||||
|
||||
<box id="body"
|
||||
class="devtools-responsive-container"
|
||||
flex="1">
|
||||
|
@ -103,7 +113,7 @@
|
|||
</toolbar>
|
||||
<label id="requests-menu-empty-notice"
|
||||
value="&netmonitorUI.emptyNotice2;"/>
|
||||
<vbox id="requests-menu-contents" flex="1">
|
||||
<vbox id="requests-menu-contents" flex="1" context="network-request-popup">
|
||||
<hbox id="requests-menu-item-template" hidden="true">
|
||||
<hbox class="requests-menu-subitem requests-menu-status-and-method"
|
||||
align="center">
|
||||
|
@ -194,184 +204,251 @@
|
|||
|
||||
<splitter class="devtools-side-splitter"/>
|
||||
|
||||
<tabbox id="details-pane"
|
||||
class="devtools-sidebar-tabs"
|
||||
hidden="true">
|
||||
<tabs>
|
||||
<tab label="&netmonitorUI.tab.headers;"/>
|
||||
<tab label="&netmonitorUI.tab.cookies;"/>
|
||||
<tab label="&netmonitorUI.tab.params;"/>
|
||||
<tab label="&netmonitorUI.tab.response;"/>
|
||||
<tab label="&netmonitorUI.tab.timings;"/>
|
||||
</tabs>
|
||||
<tabpanels flex="1">
|
||||
<tabpanel id="headers-tabppanel"
|
||||
class="tabpanel-content">
|
||||
<vbox flex="1">
|
||||
<hbox id="headers-summary-url"
|
||||
class="tabpanel-summary-container"
|
||||
align="center">
|
||||
<label class="plain tabpanel-summary-label"
|
||||
value="&netmonitorUI.summary.url;"/>
|
||||
<label id="headers-summary-url-value"
|
||||
class="plain tabpanel-summary-value"
|
||||
crop="end"
|
||||
flex="1"/>
|
||||
</hbox>
|
||||
<hbox id="headers-summary-method"
|
||||
class="tabpanel-summary-container"
|
||||
align="center">
|
||||
<label class="plain tabpanel-summary-label"
|
||||
value="&netmonitorUI.summary.method;"/>
|
||||
<label id="headers-summary-method-value"
|
||||
class="plain tabpanel-summary-value"
|
||||
crop="end"
|
||||
flex="1"/>
|
||||
</hbox>
|
||||
<hbox id="headers-summary-status"
|
||||
class="tabpanel-summary-container"
|
||||
align="center">
|
||||
<label class="plain tabpanel-summary-label"
|
||||
value="&netmonitorUI.summary.status;"/>
|
||||
<box id="headers-summary-status-circle"
|
||||
class="requests-menu-status"/>
|
||||
<label id="headers-summary-status-value"
|
||||
class="plain tabpanel-summary-value"
|
||||
crop="end"
|
||||
flex="1"/>
|
||||
</hbox>
|
||||
<hbox id="headers-summary-version"
|
||||
class="tabpanel-summary-container"
|
||||
align="center">
|
||||
<label class="plain tabpanel-summary-label"
|
||||
value="&netmonitorUI.summary.version;"/>
|
||||
<label id="headers-summary-version-value"
|
||||
class="plain tabpanel-summary-value"
|
||||
crop="end"
|
||||
flex="1"/>
|
||||
</hbox>
|
||||
<vbox id="all-headers" flex="1"/>
|
||||
</vbox>
|
||||
</tabpanel>
|
||||
<tabpanel id="cookies-tabpanel"
|
||||
class="tabpanel-content">
|
||||
<vbox flex="1">
|
||||
<vbox id="all-cookies" flex="1"/>
|
||||
</vbox>
|
||||
</tabpanel>
|
||||
<tabpanel id="params-tabpanel"
|
||||
class="tabpanel-content">
|
||||
<vbox flex="1">
|
||||
<vbox id="request-params-box" flex="1" hidden="true">
|
||||
<vbox id="request-params" flex="1"/>
|
||||
</vbox>
|
||||
<vbox id="request-post-data-textarea-box" flex="1" hidden="true">
|
||||
<vbox id="request-post-data-textarea" flex="1"/>
|
||||
</vbox>
|
||||
</vbox>
|
||||
</tabpanel>
|
||||
<tabpanel id="response-tabpanel"
|
||||
class="tabpanel-content">
|
||||
<vbox flex="1">
|
||||
<label id="response-content-info-header"/>
|
||||
<vbox id="response-content-json-box" flex="1" hidden="true">
|
||||
<vbox id="response-content-json" flex="1"/>
|
||||
</vbox>
|
||||
<vbox id="response-content-textarea-box" flex="1" hidden="true">
|
||||
<vbox id="response-content-textarea" flex="1"/>
|
||||
</vbox>
|
||||
<vbox id="response-content-image-box" flex="1" hidden="true">
|
||||
<image id="response-content-image"/>
|
||||
<hbox>
|
||||
<deck id="details-pane"
|
||||
hidden="true">
|
||||
<vbox id="custom-pane"
|
||||
class="tabpanel-content">
|
||||
<hbox align="baseline">
|
||||
<label value="&netmonitorUI.custom.newRequest;"
|
||||
class="plain tabpanel-summary-label
|
||||
custom-header"/>
|
||||
<hbox flex="1" pack="end">
|
||||
<button class="devtools-toolbarbutton"
|
||||
label="&netmonitorUI.custom.send;"
|
||||
onclick="NetMonitorView.RequestsMenu.sendCustomRequest();"/>
|
||||
<button class="devtools-toolbarbutton"
|
||||
label="&netmonitorUI.custom.cancel;"
|
||||
onclick="NetMonitorView.RequestsMenu.closeCustomRequest();"/>
|
||||
</hbox>
|
||||
</hbox>
|
||||
<hbox id="custom-method-and-url"
|
||||
class="tabpanel-summary-container"
|
||||
align="center">
|
||||
<textbox id="custom-method-value"
|
||||
oninput="NetMonitorView.CustomRequest.onUpdate('method');"
|
||||
multiline="true"
|
||||
cols="6"
|
||||
rows="1"/>
|
||||
<textbox id="custom-url-value"
|
||||
flex="1"
|
||||
oninput="NetMonitorView.CustomRequest.onUpdate('url');"/>
|
||||
</hbox>
|
||||
<vbox id="custom-query"
|
||||
class="tabpanel-summary-container custom-section">
|
||||
<label class="plain tabpanel-summary-label"
|
||||
value="&netmonitorUI.custom.query;"/>
|
||||
<textbox id="custom-query-value"
|
||||
class="tabpanel-summary-input"
|
||||
multiline="true"
|
||||
rows="4"
|
||||
wrap="off"
|
||||
oninput="NetMonitorView.CustomRequest.onUpdate('query');"/>
|
||||
</vbox>
|
||||
<vbox id="custom-headers"
|
||||
class="tabpanel-summary-container custom-section">
|
||||
<label class="plain tabpanel-summary-label"
|
||||
value="&netmonitorUI.custom.headers;"/>
|
||||
<textbox id="custom-headers-value"
|
||||
class="tabpanel-summary-input"
|
||||
multiline="true"
|
||||
rows="6"
|
||||
wrap="off"
|
||||
oninput="NetMonitorView.CustomRequest.onUpdate('headers');"/>
|
||||
</vbox>
|
||||
<vbox id="custom-postdata"
|
||||
class="tabpanel-summary-container custom-section">
|
||||
<label class="plain tabpanel-summary-label"
|
||||
value="&netmonitorUI.custom.postData;"/>
|
||||
<textbox id="custom-postdata-value"
|
||||
class="tabpanel-summary-input"
|
||||
multiline="true"
|
||||
rows="6"
|
||||
wrap="off"
|
||||
oninput="NetMonitorView.CustomRequest.onUpdate('body');"/>
|
||||
</vbox>
|
||||
</vbox>
|
||||
<tabbox id="event-details-pane"
|
||||
class="devtools-sidebar-tabs">
|
||||
<tabs>
|
||||
<tab label="&netmonitorUI.tab.headers;"/>
|
||||
<tab label="&netmonitorUI.tab.cookies;"/>
|
||||
<tab label="&netmonitorUI.tab.params;"/>
|
||||
<tab label="&netmonitorUI.tab.response;"/>
|
||||
<tab label="&netmonitorUI.tab.timings;"/>
|
||||
</tabs>
|
||||
<tabpanels flex="1">
|
||||
<tabpanel id="headers-tabppanel"
|
||||
class="tabpanel-content">
|
||||
<vbox flex="1">
|
||||
<hbox id="headers-summary-url"
|
||||
class="tabpanel-summary-container"
|
||||
align="center">
|
||||
<label class="plain tabpanel-summary-label"
|
||||
value="&netmonitorUI.response.name;"/>
|
||||
<label id="response-content-image-name-value"
|
||||
value="&netmonitorUI.summary.url;"/>
|
||||
<label id="headers-summary-url-value"
|
||||
class="plain tabpanel-summary-value"
|
||||
crop="end"
|
||||
flex="1"/>
|
||||
</hbox>
|
||||
<hbox>
|
||||
<hbox id="headers-summary-method"
|
||||
class="tabpanel-summary-container"
|
||||
align="center">
|
||||
<label class="plain tabpanel-summary-label"
|
||||
value="&netmonitorUI.response.dimensions;"/>
|
||||
<label id="response-content-image-dimensions-value"
|
||||
value="&netmonitorUI.summary.method;"/>
|
||||
<label id="headers-summary-method-value"
|
||||
class="plain tabpanel-summary-value"
|
||||
crop="end"
|
||||
flex="1"/>
|
||||
</hbox>
|
||||
<hbox>
|
||||
<hbox id="headers-summary-status"
|
||||
class="tabpanel-summary-container"
|
||||
align="center">
|
||||
<label class="plain tabpanel-summary-label"
|
||||
value="&netmonitorUI.response.mime;"/>
|
||||
<label id="response-content-image-mime-value"
|
||||
value="&netmonitorUI.summary.status;"/>
|
||||
<box id="headers-summary-status-circle"
|
||||
class="requests-menu-status"/>
|
||||
<label id="headers-summary-status-value"
|
||||
class="plain tabpanel-summary-value"
|
||||
crop="end"
|
||||
flex="1"/>
|
||||
<button id="headers-summary-resend"
|
||||
label="&netmonitorUI.summary.resend;"
|
||||
class="devtools-toolbarbutton"
|
||||
onclick="NetMonitorView.RequestsMenu.cloneSelectedRequest();"/>
|
||||
</hbox>
|
||||
<hbox id="headers-summary-version"
|
||||
class="tabpanel-summary-container"
|
||||
align="center">
|
||||
<label class="plain tabpanel-summary-label"
|
||||
value="&netmonitorUI.summary.version;"/>
|
||||
<label id="headers-summary-version-value"
|
||||
class="plain tabpanel-summary-value"
|
||||
crop="end"
|
||||
flex="1"/>
|
||||
</hbox>
|
||||
<hbox>
|
||||
<vbox id="all-headers" flex="1"/>
|
||||
</vbox>
|
||||
</tabpanel>
|
||||
<tabpanel id="cookies-tabpanel"
|
||||
class="tabpanel-content">
|
||||
<vbox flex="1">
|
||||
<vbox id="all-cookies" flex="1"/>
|
||||
</vbox>
|
||||
</tabpanel>
|
||||
<tabpanel id="params-tabpanel"
|
||||
class="tabpanel-content">
|
||||
<vbox flex="1">
|
||||
<vbox id="request-params-box" flex="1" hidden="true">
|
||||
<vbox id="request-params" flex="1"/>
|
||||
</vbox>
|
||||
<vbox id="request-post-data-textarea-box" flex="1" hidden="true">
|
||||
<vbox id="request-post-data-textarea" flex="1"/>
|
||||
</vbox>
|
||||
</vbox>
|
||||
</tabpanel>
|
||||
<tabpanel id="response-tabpanel"
|
||||
class="tabpanel-content">
|
||||
<vbox flex="1">
|
||||
<label id="response-content-info-header"/>
|
||||
<vbox id="response-content-json-box" flex="1" hidden="true">
|
||||
<vbox id="response-content-json" flex="1"/>
|
||||
</vbox>
|
||||
<vbox id="response-content-textarea-box" flex="1" hidden="true">
|
||||
<vbox id="response-content-textarea" flex="1"/>
|
||||
</vbox>
|
||||
<vbox id="response-content-image-box" flex="1" hidden="true">
|
||||
<image id="response-content-image"/>
|
||||
<hbox>
|
||||
<label class="plain tabpanel-summary-label"
|
||||
value="&netmonitorUI.response.name;"/>
|
||||
<label id="response-content-image-name-value"
|
||||
class="plain tabpanel-summary-value"
|
||||
crop="end"
|
||||
flex="1"/>
|
||||
</hbox>
|
||||
<hbox>
|
||||
<label class="plain tabpanel-summary-label"
|
||||
value="&netmonitorUI.response.dimensions;"/>
|
||||
<label id="response-content-image-dimensions-value"
|
||||
class="plain tabpanel-summary-value"
|
||||
crop="end"
|
||||
flex="1"/>
|
||||
</hbox>
|
||||
<hbox>
|
||||
<label class="plain tabpanel-summary-label"
|
||||
value="&netmonitorUI.response.mime;"/>
|
||||
<label id="response-content-image-mime-value"
|
||||
class="plain tabpanel-summary-value"
|
||||
crop="end"
|
||||
flex="1"/>
|
||||
</hbox>
|
||||
<hbox>
|
||||
<label class="plain tabpanel-summary-label"
|
||||
value="&netmonitorUI.response.encoding;"/>
|
||||
<label id="response-content-image-encoding-value"
|
||||
class="plain tabpanel-summary-value"
|
||||
crop="end"
|
||||
flex="1"/>
|
||||
</hbox>
|
||||
</vbox>
|
||||
</vbox>
|
||||
</tabpanel>
|
||||
<tabpanel id="timings-tabpanel"
|
||||
class="tabpanel-content">
|
||||
<vbox flex="1">
|
||||
<hbox id="timings-summary-blocked"
|
||||
class="tabpanel-summary-container"
|
||||
align="center">
|
||||
<label class="plain tabpanel-summary-label"
|
||||
value="&netmonitorUI.response.encoding;"/>
|
||||
<label id="response-content-image-encoding-value"
|
||||
class="plain tabpanel-summary-value"
|
||||
crop="end"
|
||||
flex="1"/>
|
||||
value="&netmonitorUI.timings.blocked;"/>
|
||||
<hbox class="requests-menu-timings-box blocked"/>
|
||||
<label class="plain requests-menu-timings-total"/>
|
||||
</hbox>
|
||||
<hbox id="timings-summary-dns"
|
||||
class="tabpanel-summary-container"
|
||||
align="center">
|
||||
<label class="plain tabpanel-summary-label"
|
||||
value="&netmonitorUI.timings.dns;"/>
|
||||
<hbox class="requests-menu-timings-box dns"/>
|
||||
<label class="plain requests-menu-timings-total"/>
|
||||
</hbox>
|
||||
<hbox id="timings-summary-connect"
|
||||
class="tabpanel-summary-container"
|
||||
align="center">
|
||||
<label class="plain tabpanel-summary-label"
|
||||
value="&netmonitorUI.timings.connect;"/>
|
||||
<hbox class="requests-menu-timings-box connect"/>
|
||||
<label class="plain requests-menu-timings-total"/>
|
||||
</hbox>
|
||||
<hbox id="timings-summary-send"
|
||||
class="tabpanel-summary-container"
|
||||
align="center">
|
||||
<label class="plain tabpanel-summary-label"
|
||||
value="&netmonitorUI.timings.send;"/>
|
||||
<hbox class="requests-menu-timings-box send"/>
|
||||
<label class="plain requests-menu-timings-total"/>
|
||||
</hbox>
|
||||
<hbox id="timings-summary-wait"
|
||||
class="tabpanel-summary-container"
|
||||
align="center">
|
||||
<label class="plain tabpanel-summary-label"
|
||||
value="&netmonitorUI.timings.wait;"/>
|
||||
<hbox class="requests-menu-timings-box wait"/>
|
||||
<label class="plain requests-menu-timings-total"/>
|
||||
</hbox>
|
||||
<hbox id="timings-summary-receive"
|
||||
class="tabpanel-summary-container"
|
||||
align="center">
|
||||
<label class="plain tabpanel-summary-label"
|
||||
value="&netmonitorUI.timings.receive;"/>
|
||||
<hbox class="requests-menu-timings-box receive"/>
|
||||
<label class="plain requests-menu-timings-total"/>
|
||||
</hbox>
|
||||
</vbox>
|
||||
</vbox>
|
||||
</tabpanel>
|
||||
<tabpanel id="timings-tabpanel"
|
||||
class="tabpanel-content">
|
||||
<vbox flex="1">
|
||||
<hbox id="timings-summary-blocked"
|
||||
class="tabpanel-summary-container"
|
||||
align="center">
|
||||
<label class="plain tabpanel-summary-label"
|
||||
value="&netmonitorUI.timings.blocked;"/>
|
||||
<hbox class="requests-menu-timings-box blocked"/>
|
||||
<label class="plain requests-menu-timings-total"/>
|
||||
</hbox>
|
||||
<hbox id="timings-summary-dns"
|
||||
class="tabpanel-summary-container"
|
||||
align="center">
|
||||
<label class="plain tabpanel-summary-label"
|
||||
value="&netmonitorUI.timings.dns;"/>
|
||||
<hbox class="requests-menu-timings-box dns"/>
|
||||
<label class="plain requests-menu-timings-total"/>
|
||||
</hbox>
|
||||
<hbox id="timings-summary-connect"
|
||||
class="tabpanel-summary-container"
|
||||
align="center">
|
||||
<label class="plain tabpanel-summary-label"
|
||||
value="&netmonitorUI.timings.connect;"/>
|
||||
<hbox class="requests-menu-timings-box connect"/>
|
||||
<label class="plain requests-menu-timings-total"/>
|
||||
</hbox>
|
||||
<hbox id="timings-summary-send"
|
||||
class="tabpanel-summary-container"
|
||||
align="center">
|
||||
<label class="plain tabpanel-summary-label"
|
||||
value="&netmonitorUI.timings.send;"/>
|
||||
<hbox class="requests-menu-timings-box send"/>
|
||||
<label class="plain requests-menu-timings-total"/>
|
||||
</hbox>
|
||||
<hbox id="timings-summary-wait"
|
||||
class="tabpanel-summary-container"
|
||||
align="center">
|
||||
<label class="plain tabpanel-summary-label"
|
||||
value="&netmonitorUI.timings.wait;"/>
|
||||
<hbox class="requests-menu-timings-box wait"/>
|
||||
<label class="plain requests-menu-timings-total"/>
|
||||
</hbox>
|
||||
<hbox id="timings-summary-receive"
|
||||
class="tabpanel-summary-container"
|
||||
align="center">
|
||||
<label class="plain tabpanel-summary-label"
|
||||
value="&netmonitorUI.timings.receive;"/>
|
||||
<hbox class="requests-menu-timings-box receive"/>
|
||||
<label class="plain requests-menu-timings-total"/>
|
||||
</hbox>
|
||||
</vbox>
|
||||
</tabpanel>
|
||||
</tabpanels>
|
||||
</tabbox>
|
||||
</tabpanel>
|
||||
</tabpanels>
|
||||
</tabbox>
|
||||
</deck>
|
||||
</box>
|
||||
|
||||
</window>
|
||||
|
|
|
@ -42,6 +42,7 @@ MOCHITEST_BROWSER_TESTS = \
|
|||
browser_net_accessibility-01.js \
|
||||
browser_net_accessibility-02.js \
|
||||
browser_net_footer-summary.js \
|
||||
browser_net_resend.js \
|
||||
head.js \
|
||||
$(NULL)
|
||||
|
||||
|
|
|
@ -20,8 +20,8 @@ function test() {
|
|||
NetMonitorView.toggleDetailsPane({ visible: true }, 2)
|
||||
RequestsMenu.selectedIndex = 0;
|
||||
|
||||
let tab = document.querySelectorAll("#details-pane tab")[2];
|
||||
let tabpanel = document.querySelectorAll("#details-pane tabpanel")[2];
|
||||
let tab = document.querySelectorAll("#event-details-pane tab")[2];
|
||||
let tabpanel = document.querySelectorAll("#event-details-pane tabpanel")[2];
|
||||
|
||||
is(tab.getAttribute("selected"), "true",
|
||||
"The params tab in the network details pane should be selected.");
|
||||
|
|
|
@ -0,0 +1,167 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
let gPanelWin;
|
||||
let gPanelDoc;
|
||||
|
||||
const ADD_QUERY = "t1=t2";
|
||||
const ADD_HEADER = "Test-header: true";
|
||||
const ADD_POSTDATA = "t3=t4";
|
||||
|
||||
/**
|
||||
* Tests if resending a request works.
|
||||
*/
|
||||
|
||||
function test() {
|
||||
initNetMonitor(POST_DATA_URL).then(([aTab, aDebuggee, aMonitor]) => {
|
||||
info("Starting test... ");
|
||||
|
||||
gPanelWin = aMonitor.panelWin;
|
||||
gPanelDoc = gPanelWin.document;
|
||||
|
||||
let { NetMonitorView } = gPanelWin;
|
||||
let { RequestsMenu } = NetMonitorView;
|
||||
|
||||
RequestsMenu.lazyUpdate = false;
|
||||
|
||||
waitForNetworkEvents(aMonitor, 0, 2).then(() => {
|
||||
let origItem = RequestsMenu.getItemAtIndex(0);
|
||||
RequestsMenu.selectedItem = origItem;
|
||||
|
||||
// add a new custom request cloned from selected request
|
||||
RequestsMenu.cloneSelectedRequest();
|
||||
testCustomForm(origItem.attachment);
|
||||
|
||||
let customItem = RequestsMenu.selectedItem;
|
||||
testCustomItem(customItem, origItem);
|
||||
|
||||
// edit the custom request
|
||||
editCustomForm(() => {
|
||||
testCustomItemChanged(customItem, origItem);
|
||||
|
||||
waitForNetworkEvents(aMonitor, 0, 1).then(() => {
|
||||
let sentItem = RequestsMenu.selectedItem;
|
||||
testSentRequest(sentItem.attachment, origItem.attachment);
|
||||
finishUp(aMonitor);
|
||||
});
|
||||
// send the new request
|
||||
RequestsMenu.sendCustomRequest();
|
||||
});
|
||||
});
|
||||
|
||||
aDebuggee.performRequests();
|
||||
});
|
||||
}
|
||||
|
||||
function testCustomItem(aItem, aOrigItem) {
|
||||
let method = aItem.target.querySelector(".requests-menu-method").value;
|
||||
let origMethod = aOrigItem.target.querySelector(".requests-menu-method").value;
|
||||
is(method, origMethod, "menu item is showing the same method as original request");
|
||||
|
||||
let file = aItem.target.querySelector(".requests-menu-file").value;
|
||||
let origFile = aOrigItem.target.querySelector(".requests-menu-file").value;
|
||||
is(file, origFile, "menu item is showing the same file name as original request");
|
||||
|
||||
let domain = aItem.target.querySelector(".requests-menu-domain").value;
|
||||
let origDomain = aOrigItem.target.querySelector(".requests-menu-domain").value;
|
||||
is(domain, origDomain, "menu item is showing the same domain as original request");
|
||||
}
|
||||
|
||||
function testCustomItemChanged(aItem, aOrigItem) {
|
||||
let file = aItem.target.querySelector(".requests-menu-file").value;
|
||||
let expectedFile = aOrigItem.target.querySelector(".requests-menu-file").value + "&" + ADD_QUERY;
|
||||
|
||||
is(file, expectedFile, "menu item is updated to reflect url entered in form");
|
||||
}
|
||||
|
||||
/*
|
||||
* Test that the New Request form was populated correctly
|
||||
*/
|
||||
function testCustomForm(aData) {
|
||||
is(gPanelDoc.getElementById("custom-method-value").value, aData.method,
|
||||
"new request form showing correct method");
|
||||
|
||||
is(gPanelDoc.getElementById("custom-url-value").value, aData.url,
|
||||
"new request form showing correct url");
|
||||
|
||||
let query = gPanelDoc.getElementById("custom-query-value");
|
||||
is(query.value, "foo=bar\nbaz=42\ntype=urlencoded",
|
||||
"new request form showing correct query string");
|
||||
|
||||
let headers = gPanelDoc.getElementById("custom-headers-value").value.split("\n");
|
||||
for (let {name, value} of aData.requestHeaders.headers) {
|
||||
ok(headers.indexOf(name + ": " + value) >= 0, "form contains header from request");
|
||||
}
|
||||
|
||||
let postData = gPanelDoc.getElementById("custom-postdata-value");
|
||||
is(postData.value, aData.requestPostData.postData.text,
|
||||
"new request form showing correct post data");
|
||||
}
|
||||
|
||||
/*
|
||||
* Add some params and headers to the request form
|
||||
*/
|
||||
function editCustomForm(callback) {
|
||||
gPanelWin.focus();
|
||||
|
||||
let query = gPanelDoc.getElementById("custom-query-value");
|
||||
query.addEventListener("focus", function onFocus() {
|
||||
query.removeEventListener("focus", onFocus, false);
|
||||
|
||||
// add params to url query string field
|
||||
type(["VK_RETURN"]);
|
||||
type(ADD_QUERY);
|
||||
|
||||
let headers = gPanelDoc.getElementById("custom-headers-value");
|
||||
headers.addEventListener("focus", function onFocus() {
|
||||
headers.removeEventListener("focus", onFocus, false);
|
||||
|
||||
// add a header
|
||||
type(["VK_RETURN"]);
|
||||
type(ADD_HEADER);
|
||||
|
||||
let postData = gPanelDoc.getElementById("custom-postdata-value");
|
||||
postData.addEventListener("focus", function onFocus() {
|
||||
postData.removeEventListener("focus", onFocus, false);
|
||||
|
||||
// add to POST data
|
||||
type(ADD_POSTDATA);
|
||||
callback();
|
||||
}, false);
|
||||
postData.focus();
|
||||
}, false);
|
||||
headers.focus();
|
||||
}, false);
|
||||
query.focus();
|
||||
}
|
||||
|
||||
/*
|
||||
* Make sure newly created event matches expected request
|
||||
*/
|
||||
function testSentRequest(aData, aOrigData) {
|
||||
is(aData.method, aOrigData.method, "correct method in sent request");
|
||||
is(aData.url, aOrigData.url + "&" + ADD_QUERY, "correct url in sent request");
|
||||
|
||||
let hasHeader = aData.requestHeaders.headers.some((header) => {
|
||||
return (header.name + ": " + header.value) == ADD_HEADER;
|
||||
})
|
||||
ok(hasHeader, "new header added to sent request");
|
||||
|
||||
is(aData.requestPostData.postData.text,
|
||||
aOrigData.requestPostData.postData.text + ADD_POSTDATA,
|
||||
"post data added to sent request");
|
||||
}
|
||||
|
||||
|
||||
function type(aString) {
|
||||
for (let ch of aString) {
|
||||
EventUtils.synthesizeKey(ch, {}, gPanelWin);
|
||||
}
|
||||
}
|
||||
|
||||
function finishUp(aMonitor) {
|
||||
gPanelWin = null;
|
||||
gPanelDoc = null;
|
||||
|
||||
teardown(aMonitor).then(finish);
|
||||
}
|
|
@ -140,6 +140,7 @@ MOCHITEST_BROWSER_FILES = \
|
|||
browser_console_keyboard_accessibility.js \
|
||||
browser_console_filters.js \
|
||||
browser_console_dead_objects.js \
|
||||
browser_console_iframe_messages.js \
|
||||
head.js \
|
||||
$(NULL)
|
||||
|
||||
|
@ -239,6 +240,10 @@ MOCHITEST_BROWSER_FILES += \
|
|||
test-bug-837351-security-errors.html \
|
||||
test-bug-869003-top-window.html \
|
||||
test-bug-869003-iframe.html \
|
||||
test-consoleiframes.html \
|
||||
test-iframe1.html \
|
||||
test-iframe2.html \
|
||||
test-iframe3.html \
|
||||
$(NULL)
|
||||
|
||||
include $(topsrcdir)/config/rules.mk
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*/
|
||||
|
||||
// Check that cached messages from nested iframes are displayed in the
|
||||
// Web/Browser Console.
|
||||
|
||||
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-consoleiframes.html";
|
||||
|
||||
function test()
|
||||
{
|
||||
expectUncaughtException();
|
||||
addTab(TEST_URI);
|
||||
browser.addEventListener("load", function onLoad() {
|
||||
browser.removeEventListener("load", onLoad, true);
|
||||
|
||||
// Test for cached nsIConsoleMessages.
|
||||
Services.console.logStringMessage("test1 for bug859756");
|
||||
|
||||
info("open web console");
|
||||
openConsole(null, consoleOpened);
|
||||
}, true);
|
||||
}
|
||||
|
||||
function consoleOpened(hud)
|
||||
{
|
||||
ok(hud, "web console opened");
|
||||
|
||||
waitForMessages({
|
||||
webconsole: hud,
|
||||
messages: [
|
||||
{
|
||||
text: "main file",
|
||||
category: CATEGORY_WEBDEV,
|
||||
severity: SEVERITY_LOG,
|
||||
},
|
||||
{
|
||||
text: "blah",
|
||||
category: CATEGORY_JS,
|
||||
severity: SEVERITY_ERROR
|
||||
},
|
||||
{
|
||||
text: "iframe 1",
|
||||
category: CATEGORY_WEBDEV,
|
||||
severity: SEVERITY_LOG,
|
||||
count: 2
|
||||
},
|
||||
{
|
||||
text: "iframe 2",
|
||||
category: CATEGORY_WEBDEV,
|
||||
severity: SEVERITY_LOG
|
||||
}
|
||||
],
|
||||
}).then(() => {
|
||||
closeConsole(null, onWebConsoleClose);
|
||||
});
|
||||
}
|
||||
|
||||
function onWebConsoleClose()
|
||||
{
|
||||
info("web console closed");
|
||||
HUDConsoleUI.toggleBrowserConsole().then(onBrowserConsoleOpen);
|
||||
}
|
||||
|
||||
function onBrowserConsoleOpen(hud)
|
||||
{
|
||||
ok(hud, "browser console opened");
|
||||
Services.console.logStringMessage("test2 for bug859756");
|
||||
|
||||
waitForMessages({
|
||||
webconsole: hud,
|
||||
messages: [
|
||||
{
|
||||
text: "main file",
|
||||
category: CATEGORY_WEBDEV,
|
||||
severity: SEVERITY_LOG,
|
||||
},
|
||||
{
|
||||
text: "blah",
|
||||
category: CATEGORY_JS,
|
||||
severity: SEVERITY_ERROR
|
||||
},
|
||||
{
|
||||
text: "iframe 1",
|
||||
category: CATEGORY_WEBDEV,
|
||||
severity: SEVERITY_LOG,
|
||||
count: 2
|
||||
},
|
||||
{
|
||||
text: "iframe 2",
|
||||
category: CATEGORY_WEBDEV,
|
||||
severity: SEVERITY_LOG
|
||||
}
|
||||
],
|
||||
}).then(() => {
|
||||
closeConsole(null, finishTest);
|
||||
});
|
||||
}
|
|
@ -15,6 +15,7 @@ let TargetFactory = tempScope.devtools.TargetFactory;
|
|||
Components.utils.import("resource://gre/modules/devtools/Console.jsm", tempScope);
|
||||
let console = tempScope.console;
|
||||
let Promise = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js", {}).Promise;
|
||||
// Promise._reportErrors = true; // please never leave me.
|
||||
|
||||
let gPendingOutputTest = 0;
|
||||
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
<html>
|
||||
<head>
|
||||
<script>
|
||||
console.log("main file");
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<h1>iframe console test</h1>
|
||||
<iframe src="test-iframe1.html"></iframe>
|
||||
<iframe src="test-iframe2.html"></iframe>
|
||||
<iframe src="test-iframe3.html"></iframe>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,10 @@
|
|||
<html>
|
||||
<head>
|
||||
<script>
|
||||
console.log("iframe 1");
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<h1>iframe 1</h1>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,11 @@
|
|||
<html>
|
||||
<head>
|
||||
<script>
|
||||
console.log("iframe 2");
|
||||
blah;
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<h1>iframe 2</h1>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,11 @@
|
|||
<html>
|
||||
<head>
|
||||
<script>
|
||||
console.log("iframe 3");
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<h1>iframe 3</h1>
|
||||
<iframe src="test-iframe1.html"></iframe>
|
||||
</body>
|
||||
</html>
|
|
@ -383,18 +383,12 @@ WebConsoleFrame.prototype = {
|
|||
}.bind(this));
|
||||
},
|
||||
|
||||
_persistLog: null,
|
||||
|
||||
/**
|
||||
* Getter for the persistent logging preference. This value is cached per
|
||||
* instance to avoid reading the pref too often.
|
||||
* Getter for the persistent logging preference.
|
||||
* @type boolean
|
||||
*/
|
||||
get persistLog() {
|
||||
if (this._persistLog === null) {
|
||||
this._persistLog = Services.prefs.getBoolPref(PREF_PERSISTLOG);
|
||||
}
|
||||
return this._persistLog;
|
||||
return Services.prefs.getBoolPref(PREF_PERSISTLOG);
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -168,3 +168,36 @@
|
|||
- in the network details timings tab identifying the amount of time spent
|
||||
- in a "receive" state. -->
|
||||
<!ENTITY netmonitorUI.timings.receive "Receiving:">
|
||||
|
||||
<!-- LOCALIZATION NOTE (debuggerUI.custom.headers): This is the label displayed
|
||||
- on the button in the headers tab that opens a form to resend the currently
|
||||
displayed request -->
|
||||
<!ENTITY netmonitorUI.summary.resend "Resend">
|
||||
|
||||
<!-- LOCALIZATION NOTE (debuggerUI.custom.headers): This is the access key
|
||||
- for the Resend menu item displayed in the context menu for a request -->
|
||||
<!ENTITY netmonitorUI.summary.resend.accesskey "R">
|
||||
|
||||
<!-- LOCALIZATION NOTE (debuggerUI.custom.newRequest): This is the label displayed
|
||||
- as the title of the new custom request form -->
|
||||
<!ENTITY netmonitorUI.custom.newRequest "New Request">
|
||||
|
||||
<!-- LOCALIZATION NOTE (debuggerUI.custom.query): This is the label displayed
|
||||
- above the query string entry in the custom request form -->
|
||||
<!ENTITY netmonitorUI.custom.query "Query String:">
|
||||
|
||||
<!-- LOCALIZATION NOTE (debuggerUI.custom.headers): This is the label displayed
|
||||
- above the request headers entry in the custom request form -->
|
||||
<!ENTITY netmonitorUI.custom.headers "Request Headers:">
|
||||
|
||||
<!-- LOCALIZATION NOTE (debuggerUI.custom.headers): This is the label displayed
|
||||
- above the request body entry in the custom request form -->
|
||||
<!ENTITY netmonitorUI.custom.postData "Request Body:">
|
||||
|
||||
<!-- LOCALIZATION NOTE (debuggerUI.custom.headers): This is the label displayed
|
||||
- on the button which sends the custom request -->
|
||||
<!ENTITY netmonitorUI.custom.send "Send">
|
||||
|
||||
<!-- LOCALIZATION NOTE (debuggerUI.custom.headers): This is the label displayed
|
||||
- on the button which cancels and closes the custom request form -->
|
||||
<!ENTITY netmonitorUI.custom.cancel "Cancel">
|
||||
|
|
|
@ -383,6 +383,11 @@ box.requests-menu-status[code^="5"] {
|
|||
padding-top: 2px;
|
||||
}
|
||||
|
||||
#headers-summary-resend {
|
||||
margin: 0 6px;
|
||||
min-height: 20px;
|
||||
}
|
||||
|
||||
/* Response tabpanel */
|
||||
|
||||
#response-content-info-header {
|
||||
|
@ -422,6 +427,20 @@ box.requests-menu-status[code^="5"] {
|
|||
transition: transform 0.2s ease-out;
|
||||
}
|
||||
|
||||
/* Custom request form */
|
||||
|
||||
#custom-pane {
|
||||
padding: 0.6em 0.5em;
|
||||
}
|
||||
|
||||
.custom-header {
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.custom-section {
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
|
||||
#requests-menu-footer {
|
||||
|
|
|
@ -383,6 +383,11 @@ box.requests-menu-status[code^="5"] {
|
|||
padding-top: 2px;
|
||||
}
|
||||
|
||||
#headers-summary-resend {
|
||||
margin: 0 6px;
|
||||
min-height: 20px;
|
||||
}
|
||||
|
||||
/* Response tabpanel */
|
||||
|
||||
#response-content-info-header {
|
||||
|
@ -422,6 +427,20 @@ box.requests-menu-status[code^="5"] {
|
|||
transition: transform 0.2s ease-out;
|
||||
}
|
||||
|
||||
/* Custom request form */
|
||||
|
||||
#custom-pane {
|
||||
padding: 0.6em 0.5em;
|
||||
}
|
||||
|
||||
.custom-header {
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.custom-section {
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
|
||||
#requests-menu-footer {
|
||||
|
|
|
@ -383,6 +383,11 @@ box.requests-menu-status[code^="5"] {
|
|||
padding-top: 2px;
|
||||
}
|
||||
|
||||
#headers-summary-resend {
|
||||
margin: 0 6px;
|
||||
min-height: 20px;
|
||||
}
|
||||
|
||||
/* Response tabpanel */
|
||||
|
||||
#response-content-info-header {
|
||||
|
@ -422,6 +427,20 @@ box.requests-menu-status[code^="5"] {
|
|||
transition: transform 0.2s ease-out;
|
||||
}
|
||||
|
||||
/* Custom request form */
|
||||
|
||||
#custom-pane {
|
||||
padding: 0.6em 0.5em;
|
||||
}
|
||||
|
||||
.custom-header {
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.custom-section {
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
|
||||
#requests-menu-footer {
|
||||
|
|
|
@ -89,6 +89,7 @@ function WebConsoleActor(aConnection, aParentActor)
|
|||
|
||||
this._protoChains = new Map();
|
||||
this._dbgGlobals = new Map();
|
||||
this._netEvents = new Map();
|
||||
this._getDebuggerGlobal(this.window);
|
||||
|
||||
this._onObserverNotification = this._onObserverNotification.bind(this);
|
||||
|
@ -147,6 +148,15 @@ WebConsoleActor.prototype =
|
|||
*/
|
||||
_dbgGlobals: null,
|
||||
|
||||
/**
|
||||
* Holds a map between nsIChannel objects and NetworkEventActors for requests
|
||||
* created with sendHTTPRequest.
|
||||
*
|
||||
* @private
|
||||
* @type Map
|
||||
*/
|
||||
_netEvents: null,
|
||||
|
||||
/**
|
||||
* Object that holds the JSTerm API, the helper functions, for the default
|
||||
* window object.
|
||||
|
@ -252,6 +262,8 @@ WebConsoleActor.prototype =
|
|||
"last-pb-context-exited");
|
||||
}
|
||||
this._actorPool = null;
|
||||
|
||||
this._netEvents.clear();
|
||||
this._protoChains.clear();
|
||||
this._dbgGlobals.clear();
|
||||
this._jstermHelpers = null;
|
||||
|
@ -551,7 +563,7 @@ WebConsoleActor.prototype =
|
|||
else {
|
||||
message = {
|
||||
_type: "LogMessage",
|
||||
message: aMessage.message,
|
||||
message: this._createStringGrip(aMessage.message),
|
||||
timeStamp: aMessage.timeStamp,
|
||||
};
|
||||
}
|
||||
|
@ -948,10 +960,15 @@ WebConsoleActor.prototype =
|
|||
*/
|
||||
preparePageErrorForRemote: function WCA_preparePageErrorForRemote(aPageError)
|
||||
{
|
||||
let lineText = aPageError.sourceLine;
|
||||
if (lineText && lineText.length > DebuggerServer.LONG_STRING_INITIAL_LENGTH) {
|
||||
lineText = lineText.substr(0, DebuggerServer.LONG_STRING_INITIAL_LENGTH);
|
||||
}
|
||||
|
||||
return {
|
||||
errorMessage: this._createStringGrip(aPageError.errorMessage),
|
||||
sourceName: aPageError.sourceName,
|
||||
lineText: this._createStringGrip(aPageError.sourceLine),
|
||||
lineText: lineText,
|
||||
lineNumber: aPageError.lineNumber,
|
||||
columnNumber: aPageError.columnNumber,
|
||||
category: aPageError.category,
|
||||
|
@ -995,10 +1012,10 @@ WebConsoleActor.prototype =
|
|||
* A new NetworkEventActor is returned. This is used for tracking the
|
||||
* network request and response.
|
||||
*/
|
||||
onNetworkEvent: function WCA_onNetworkEvent(aEvent)
|
||||
onNetworkEvent: function WCA_onNetworkEvent(aEvent, aChannel)
|
||||
{
|
||||
let actor = new NetworkEventActor(aEvent, this);
|
||||
this._actorPool.addActor(actor);
|
||||
let actor = this.getNetworkEventActor(aChannel);
|
||||
actor.init(aEvent);
|
||||
|
||||
let packet = {
|
||||
from: this.actorID,
|
||||
|
@ -1011,6 +1028,57 @@ WebConsoleActor.prototype =
|
|||
return actor;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the NetworkEventActor for a nsIChannel, if it exists,
|
||||
* otherwise create a new one.
|
||||
*
|
||||
* @param object aChannel
|
||||
* The channel for the network event.
|
||||
*/
|
||||
getNetworkEventActor: function WCA_getNetworkEventActor(aChannel) {
|
||||
let actor = this._netEvents.get(aChannel);
|
||||
if (actor) {
|
||||
// delete from map as we should only need to do this check once
|
||||
this._netEvents.delete(aChannel);
|
||||
actor.channel = null;
|
||||
return actor;
|
||||
}
|
||||
|
||||
actor = new NetworkEventActor(aChannel, this);
|
||||
this._actorPool.addActor(actor);
|
||||
return actor;
|
||||
},
|
||||
|
||||
/**
|
||||
* Send a new HTTP request from the target's window.
|
||||
*
|
||||
* @param object aMessage
|
||||
* Object with 'request' - the HTTP request details.
|
||||
*/
|
||||
onSendHTTPRequest: function WCA_onSendHTTPRequest(aMessage)
|
||||
{
|
||||
let details = aMessage.request;
|
||||
|
||||
// send request from target's window
|
||||
let request = new this._window.XMLHttpRequest();
|
||||
request.open(details.method, details.url, true);
|
||||
|
||||
for (let {name, value} of details.headers) {
|
||||
request.setRequestHeader(name, value);
|
||||
}
|
||||
request.send(details.body);
|
||||
|
||||
let actor = this.getNetworkEventActor(request.channel);
|
||||
|
||||
// map channel to actor so we can associate future events with it
|
||||
this._netEvents.set(request.channel, actor);
|
||||
|
||||
return {
|
||||
from: this.actorID,
|
||||
eventActor: actor.grip()
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Handler for file activity. This method sends the file request information
|
||||
* to the remote Web Console client.
|
||||
|
@ -1108,7 +1176,7 @@ WebConsoleActor.prototype =
|
|||
});
|
||||
break;
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
WebConsoleActor.prototype.requestTypes =
|
||||
|
@ -1120,32 +1188,31 @@ WebConsoleActor.prototype.requestTypes =
|
|||
autocomplete: WebConsoleActor.prototype.onAutocomplete,
|
||||
clearMessagesCache: WebConsoleActor.prototype.onClearMessagesCache,
|
||||
setPreferences: WebConsoleActor.prototype.onSetPreferences,
|
||||
sendHTTPRequest: WebConsoleActor.prototype.onSendHTTPRequest
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates an actor for a network event.
|
||||
*
|
||||
* @constructor
|
||||
* @param object aNetworkEvent
|
||||
* The network event you want to use the actor for.
|
||||
* @param object aChannel
|
||||
* The nsIChannel associated with this event.
|
||||
* @param object aWebConsoleActor
|
||||
* The parent WebConsoleActor instance for this object.
|
||||
*/
|
||||
function NetworkEventActor(aNetworkEvent, aWebConsoleActor)
|
||||
function NetworkEventActor(aChannel, aWebConsoleActor)
|
||||
{
|
||||
this.parent = aWebConsoleActor;
|
||||
this.conn = this.parent.conn;
|
||||
|
||||
this._startedDateTime = aNetworkEvent.startedDateTime;
|
||||
this._isXHR = aNetworkEvent.isXHR;
|
||||
this.channel = aChannel;
|
||||
|
||||
this._request = {
|
||||
method: aNetworkEvent.method,
|
||||
url: aNetworkEvent.url,
|
||||
httpVersion: aNetworkEvent.httpVersion,
|
||||
method: null,
|
||||
url: null,
|
||||
httpVersion: null,
|
||||
headers: [],
|
||||
cookies: [],
|
||||
headersSize: aNetworkEvent.headersSize,
|
||||
headersSize: null,
|
||||
postData: {},
|
||||
};
|
||||
|
||||
|
@ -1159,10 +1226,6 @@ function NetworkEventActor(aNetworkEvent, aWebConsoleActor)
|
|||
|
||||
// Keep track of LongStringActors owned by this NetworkEventActor.
|
||||
this._longStringActors = new Set();
|
||||
|
||||
this._discardRequestBody = aNetworkEvent.discardRequestBody;
|
||||
this._discardResponseBody = aNetworkEvent.discardResponseBody;
|
||||
this._private = aNetworkEvent.private;
|
||||
}
|
||||
|
||||
NetworkEventActor.prototype =
|
||||
|
@ -1201,6 +1264,10 @@ NetworkEventActor.prototype =
|
|||
}
|
||||
}
|
||||
this._longStringActors = new Set();
|
||||
|
||||
if (this.channel) {
|
||||
this.parent._netEvents.delete(this.channel);
|
||||
}
|
||||
this.parent.releaseActor(this);
|
||||
},
|
||||
|
||||
|
@ -1213,6 +1280,27 @@ NetworkEventActor.prototype =
|
|||
return {};
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the properties of this actor based on it's corresponding
|
||||
* network event.
|
||||
*
|
||||
* @param object aNetworkEvent
|
||||
* The network event associated with this actor.
|
||||
*/
|
||||
init: function NEA_init(aNetworkEvent)
|
||||
{
|
||||
this._startedDateTime = aNetworkEvent.startedDateTime;
|
||||
this._isXHR = aNetworkEvent.isXHR;
|
||||
|
||||
for (let prop of ['method', 'url', 'httpVersion', 'headersSize']) {
|
||||
this._request[prop] = aNetworkEvent[prop];
|
||||
}
|
||||
|
||||
this._discardRequestBody = aNetworkEvent.discardRequestBody;
|
||||
this._discardResponseBody = aNetworkEvent.discardResponseBody;
|
||||
this._private = aNetworkEvent.private;
|
||||
},
|
||||
|
||||
/**
|
||||
* The "getRequestHeaders" packet type handler.
|
||||
*
|
||||
|
@ -1540,4 +1628,3 @@ NetworkEventActor.prototype.requestTypes =
|
|||
DebuggerServer.addTabActor(WebConsoleActor, "consoleActor");
|
||||
DebuggerServer.addGlobalActor(WebConsoleActor, "consoleActor");
|
||||
|
||||
|
||||
|
|
|
@ -284,6 +284,23 @@ WebConsoleClient.prototype = {
|
|||
this._client.request(packet, aOnResponse);
|
||||
},
|
||||
|
||||
/**
|
||||
* Send a HTTP request with the given data.
|
||||
*
|
||||
* @param string aData
|
||||
* The details of the HTTP request.
|
||||
* @param function aOnResponse
|
||||
* The function invoked when the response is received.
|
||||
*/
|
||||
sendHTTPRequest: function WCC_sendHTTPRequest(aData, aOnResponse) {
|
||||
let packet = {
|
||||
to: this._actor,
|
||||
type: "sendHTTPRequest",
|
||||
request: aData
|
||||
};
|
||||
this._client.request(packet, aOnResponse);
|
||||
},
|
||||
|
||||
/**
|
||||
* Start the given Web Console listeners.
|
||||
*
|
||||
|
|
|
@ -144,9 +144,33 @@ this.WebConsoleUtils = {
|
|||
getInnerWindowId: function WCU_getInnerWindowId(aWindow)
|
||||
{
|
||||
return aWindow.QueryInterface(Ci.nsIInterfaceRequestor).
|
||||
getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID;
|
||||
getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID;
|
||||
},
|
||||
|
||||
/**
|
||||
* Recursively gather a list of inner window ids given a
|
||||
* top level window.
|
||||
*
|
||||
* @param nsIDOMWindow aWindow
|
||||
* @return Array
|
||||
* list of inner window ids.
|
||||
*/
|
||||
getInnerWindowIDsForFrames: function WCU_getInnerWindowIDsForFrames(aWindow)
|
||||
{
|
||||
let innerWindowID = this.getInnerWindowId(aWindow);
|
||||
let ids = [innerWindowID];
|
||||
|
||||
if (aWindow.frames) {
|
||||
for (let i = 0; i < aWindow.frames.length; i++) {
|
||||
let frame = aWindow.frames[i];
|
||||
ids = ids.concat(this.getInnerWindowIDsForFrames(frame));
|
||||
}
|
||||
}
|
||||
|
||||
return ids;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Gets the ID of the outer window of this DOM window.
|
||||
*
|
||||
|
@ -986,7 +1010,7 @@ ConsoleServiceListener.prototype =
|
|||
},
|
||||
|
||||
/**
|
||||
* Get the cached page errors for the current inner window.
|
||||
* Get the cached page errors for the current inner window and its (i)frames.
|
||||
*
|
||||
* @param boolean [aIncludePrivate=false]
|
||||
* Tells if you want to also retrieve messages coming from private
|
||||
|
@ -997,22 +1021,36 @@ ConsoleServiceListener.prototype =
|
|||
*/
|
||||
getCachedMessages: function CSL_getCachedMessages(aIncludePrivate = false)
|
||||
{
|
||||
let innerWindowID = this.window ?
|
||||
WebConsoleUtils.getInnerWindowId(this.window) : null;
|
||||
let errors = Services.console.getMessageArray() || [];
|
||||
|
||||
// if !this.window, we're in a browser console. Still need to filter
|
||||
// private messages.
|
||||
if (!this.window) {
|
||||
return errors.filter((aError) => {
|
||||
if (aError instanceof Ci.nsIScriptError) {
|
||||
if (!aIncludePrivate && aError.isFromPrivateWindow) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
let ids = WebConsoleUtils.getInnerWindowIDsForFrames(this.window);
|
||||
|
||||
return errors.filter((aError) => {
|
||||
if (aError instanceof Ci.nsIScriptError) {
|
||||
if (!aIncludePrivate && aError.isFromPrivateWindow) {
|
||||
return false;
|
||||
}
|
||||
if (innerWindowID &&
|
||||
(aError.innerWindowID != innerWindowID ||
|
||||
if (ids &&
|
||||
(ids.indexOf(aError.innerWindowID) == -1 ||
|
||||
!this.isCategoryAllowed(aError.category))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (innerWindowID) {
|
||||
else if (ids && ids[0]) {
|
||||
// If this is not an nsIScriptError and we need to do window-based
|
||||
// filtering we skip this message.
|
||||
return false;
|
||||
|
@ -1115,7 +1153,7 @@ ConsoleAPIListener.prototype =
|
|||
},
|
||||
|
||||
/**
|
||||
* Get the cached messages for the current inner window.
|
||||
* Get the cached messages for the current inner window and its (i)frames.
|
||||
*
|
||||
* @param boolean [aIncludePrivate=false]
|
||||
* Tells if you want to also retrieve messages coming from private
|
||||
|
@ -1125,14 +1163,24 @@ ConsoleAPIListener.prototype =
|
|||
*/
|
||||
getCachedMessages: function CAL_getCachedMessages(aIncludePrivate = false)
|
||||
{
|
||||
let innerWindowId = this.window ?
|
||||
WebConsoleUtils.getInnerWindowId(this.window) : null;
|
||||
let events = ConsoleAPIStorage.getEvents(innerWindowId);
|
||||
if (aIncludePrivate) {
|
||||
return events;
|
||||
let messages = [];
|
||||
|
||||
// if !this.window, we're in a browser console. Retrieve all events
|
||||
// for filtering based on privacy.
|
||||
if (!this.window) {
|
||||
messages = ConsoleAPIStorage.getEvents();
|
||||
} else {
|
||||
let ids = WebConsoleUtils.getInnerWindowIDsForFrames(this.window);
|
||||
ids.forEach((id) => {
|
||||
messages = messages.concat(ConsoleAPIStorage.getEvents(id));
|
||||
});
|
||||
}
|
||||
|
||||
return events.filter((m) => !m.private);
|
||||
if (aIncludePrivate) {
|
||||
return messages;
|
||||
}
|
||||
|
||||
return messages.filter((m) => !m.private);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -1700,10 +1748,10 @@ NetworkResponseListener.prototype = {
|
|||
* window is given, all browser network requests are logged.
|
||||
* @param object aOwner
|
||||
* The network monitor owner. This object needs to hold:
|
||||
* - onNetworkEvent(aRequestInfo). This method is invoked once for every
|
||||
* new network request and it is given one arguments: the initial network
|
||||
* request information. onNetworkEvent() must return an object which
|
||||
* holds several add*() methods which are used to add further network
|
||||
* - onNetworkEvent(aRequestInfo, aChannel). This method is invoked once for
|
||||
* every new network request and it is given two arguments: the initial network
|
||||
* request information, and the channel. onNetworkEvent() must return an object
|
||||
* which holds several add*() methods which are used to add further network
|
||||
* request/response information.
|
||||
* - saveRequestAndResponseBodies property which tells if you want to log
|
||||
* request and response bodies.
|
||||
|
@ -2004,7 +2052,7 @@ NetworkMonitor.prototype = {
|
|||
cookies = NetworkHelper.parseCookieHeader(cookieHeader);
|
||||
}
|
||||
|
||||
httpActivity.owner = this.owner.onNetworkEvent(event);
|
||||
httpActivity.owner = this.owner.onNetworkEvent(event, aChannel);
|
||||
|
||||
this._setupResponseListener(httpActivity);
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче