Merge m-c to inbound.

This commit is contained in:
Ryan VanderMeulen 2013-06-26 12:56:36 -04:00
commit 2d3527097e
38 changed files with 1765 additions and 527 deletions

View File

@ -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);

View File

@ -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") {

View File

@ -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) {

View File

@ -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

View File

@ -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);
});
},
}

View File

@ -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();

View File

@ -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();
}

View File

@ -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;
},
}

View File

@ -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()

View File

@ -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", { });

View File

@ -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();
});
}

View File

@ -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");

View File

@ -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() {

View File

@ -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()

View File

@ -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;

View File

@ -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);

View File

@ -250,6 +250,7 @@ TargetEventsHandler.prototype = {
case "will-navigate": {
// Reset UI.
NetMonitorView.RequestsMenu.reset();
NetMonitorView.Sidebar.reset();
NetMonitorView.NetworkDetails.reset();
// Reset global helpers cache.

View File

@ -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();

View File

@ -15,6 +15,10 @@
overflow: auto;
}
#custom-pane {
overflow: auto;
}
#timings-summary-blocked {
display: none; /* This doesn't work yet. */
}

View File

@ -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>

View File

@ -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)

View File

@ -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.");

View File

@ -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);
}

View File

@ -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

View File

@ -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);
});
}

View File

@ -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;

View File

@ -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>

View File

@ -0,0 +1,10 @@
<html>
<head>
<script>
console.log("iframe 1");
</script>
</head>
<body>
<h1>iframe 1</h1>
</body>
</html>

View File

@ -0,0 +1,11 @@
<html>
<head>
<script>
console.log("iframe 2");
blah;
</script>
</head>
<body>
<h1>iframe 2</h1>
</body>
</html>

View File

@ -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>

View File

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

View File

@ -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">

View File

@ -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 {

View File

@ -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 {

View File

@ -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 {

View File

@ -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");

View File

@ -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.
*

View File

@ -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);