Bug 793996 - Create reload marker in the Web Console; r=robcee

This commit is contained in:
Mihai Sucan 2013-08-02 20:19:23 +03:00
Родитель a1f817b16f
Коммит f94af751ab
8 изменённых файлов: 489 добавлений и 142 удалений

Просмотреть файл

@ -0,0 +1,311 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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/. */
"use strict";
const Heritage = require("sdk/core/heritage");
const XHTML_NS = "http://www.w3.org/1999/xhtml";
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
// Constants for compatibility with the Web Console output implementation before
// bug 778766.
// TODO: remove these once bug 778766 is fixed.
const COMPAT = {
// The various categories of messages.
CATEGORIES: {
NETWORK: 0,
CSS: 1,
JS: 2,
WEBDEV: 3,
INPUT: 4,
OUTPUT: 5,
SECURITY: 6,
},
// The possible message severities.
SEVERITIES: {
ERROR: 0,
WARNING: 1,
INFO: 2,
LOG: 3,
},
};
/**
* The ConsoleOutput object is used to manage output of messages in the Web
* Console.
*
* @constructor
* @param object owner
* The console output owner. This usually the WebConsoleFrame instance.
* Any other object can be used, as long as it has the following
* properties and methods:
* - window
* - document
* - outputMessage(category, methodOrNode[, methodArguments])
* TODO: this is needed temporarily, until bug 778766 is fixed.
*/
function ConsoleOutput(owner)
{
this.owner = owner;
this._onFlushOutputMessage = this._onFlushOutputMessage.bind(this);
}
ConsoleOutput.prototype = {
/**
* The document that holds the output.
* @type DOMDocument
*/
get document() this.owner.document,
/**
* The DOM window that holds the output.
* @type Window
*/
get window() this.owner.window,
/**
* Add a message to output.
*
* @param object ...args
* Any number of Message objects.
* @return this
*/
addMessage: function(...args)
{
for (let msg of args) {
msg.init(this);
this.owner.outputMessage(msg._categoryCompat, this._onFlushOutputMessage,
[msg]);
}
return this;
},
/**
* Message renderer used for compatibility with the current Web Console output
* implementation. This method is invoked for every message object that is
* flushed to output. The message object is initialized and rendered, then it
* is displayed.
*
* TODO: remove this method once bug 778766 is fixed.
*
* @private
* @param object message
* The message object to render.
* @return DOMElement
* The message DOM element that can be added to the console output.
*/
_onFlushOutputMessage: function(message)
{
return message.render().element;
},
/**
* Destroy this ConsoleOutput instance.
*/
destroy: function()
{
this.owner = null;
},
}; // ConsoleOutput.prototype
/**
* Message objects container.
* @type object
*/
let Messages = {};
/**
* The BaseMessage object is used for all types of messages. Every kind of
* message should use this object as its base.
*
* @constructor
*/
Messages.BaseMessage = function()
{
this.widgets = new Set();
};
Messages.BaseMessage.prototype = {
/**
* Reference to the ConsoleOutput owner.
*
* @type object|null
* This is |null| if the message is not yet initialized.
*/
output: null,
/**
* Reference to the parent message object, if this message is in a group or if
* it is otherwise owned by another message.
*
* @type object|null
*/
parent: null,
/**
* Message DOM element.
*
* @type DOMElement|null
* This is |null| if the message is not yet rendered.
*/
element: null,
/**
* Tells if this message is visible or not.
* @type boolean
*/
get visible() {
return this.element && this.element.parentNode;
},
/**
* Holds the text-only representation of the message.
* @type string
*/
textContent: "",
/**
* Set of widgets included in this message.
* @type Set
*/
widgets: null,
// Properties that allow compatibility with the current Web Console output
// implementation.
_elementClassCompat: "",
_categoryCompat: null,
_severityCompat: null,
/**
* Initialize the message.
*
* @param object output
* The ConsoleOutput owner.
* @param object [parent=null]
* Optional: a different message object that owns this instance.
* @return this
*/
init: function(output, parent=null)
{
this.output = output;
this.parent = parent;
return this;
},
/**
* Render the message. After this method is invoked the |element| property
* will point to the DOM element of this message.
* @return this
*/
render: function()
{
if (!this.element) {
this.element = this._renderCompat();
}
return this;
},
/**
* Prepare the message container for the Web Console, such that it is
* compatible with the current implementation.
* TODO: remove this once bug 778766.
*/
_renderCompat: function()
{
let doc = this.output.document;
let container = doc.createElementNS(XUL_NS, "richlistitem");
container.setAttribute("id", "console-msg-" + gSequenceId());
container.setAttribute("class", "hud-msg-node " + this._elementClassCompat);
container.category = this._categoryCompat;
container.severity = this._severityCompat;
container.clipboardText = this.textContent;
container.timestamp = this.timestamp;
container._messageObject = this;
let body = doc.createElementNS(XUL_NS, "description");
body.flex = 1;
body.classList.add("webconsole-msg-body");
container.appendChild(body);
return container;
},
}; // Messages.BaseMessage.prototype
/**
* The NavigationMarker is used to show a page load event.
*
* @constructor
* @extends Messages.BaseMessage
* @param string url
* The URL to display.
* @param number timestamp
* The message date and time, milliseconds elapsed since 1 January 1970
* 00:00:00 UTC.
*/
Messages.NavigationMarker = function(url, timestamp)
{
Messages.BaseMessage.apply(this, arguments);
this._url = url;
this.textContent = "------ " + url;
this.timestamp = timestamp;
};
Messages.NavigationMarker.prototype = Heritage.extend(Messages.BaseMessage.prototype,
{
/**
* Message timestamp.
*
* @type number
* Milliseconds elapsed since 1 January 1970 00:00:00 UTC.
*/
timestamp: 0,
// Class names in order: category, severity then the class for the filter.
_elementClassCompat: "webconsole-msg-network webconsole-msg-info hud-networkinfo",
_categoryCompat: COMPAT.CATEGORIES.NETWORK,
_severityCompat: COMPAT.SEVERITIES.LOG,
/**
* Prepare the DOM element for this message.
* @return this
*/
render: function()
{
if (this.element) {
return this;
}
let url = this._url;
let pos = url.indexOf("?");
if (pos > -1) {
url = url.substr(0, pos);
}
let doc = this.output.document;
let urlnode = doc.createElementNS(XHTML_NS, "span");
urlnode.className = "url";
urlnode.textContent = url;
// Add the text in the xul:description.webconsole-msg-body element.
let render = Messages.BaseMessage.prototype.render.bind(this);
render().element.firstChild.appendChild(urlnode);
this.element.classList.add("navigation-marker");
this.element.url = this._url;
return this;
},
}); // Messages.NavigationMarker.prototype
function gSequenceId()
{
return gSequenceId.n++;
}
gSequenceId.n = 0;
exports.ConsoleOutput = ConsoleOutput;
exports.Messages = Messages;

Просмотреть файл

@ -17,7 +17,6 @@ MOCHITEST_BROWSER_FILES = \
browser_webconsole_basic_net_logging.js \
browser_webconsole_bug_579412_input_focus.js \
browser_webconsole_bug_580001_closing_after_completion.js \
browser_webconsole_bug_580400_groups.js \
browser_webconsole_bug_588730_text_node_insertion.js \
browser_webconsole_bug_601667_filter_buttons.js \
browser_webconsole_bug_597136_external_script_errors.js \
@ -145,6 +144,7 @@ MOCHITEST_BROWSER_FILES = \
browser_console_variables_view_while_debugging_and_inspecting.js \
browser_webconsole_bug_686937_autocomplete_JSTerm_helpers.js \
browser_webconsole_cached_autocomplete.js \
browser_console_navigation_marker.js \
head.js \
$(NULL)

Просмотреть файл

@ -0,0 +1,81 @@
/*
* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
// Check that the navigation marker shows on page reload - bug 793996.
function test()
{
const PREF = "devtools.webconsole.persistlog";
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console.html";
let hud = null;
let Messages = require("devtools/webconsole/console-output").Messages;
Services.prefs.setBoolPref(PREF, true);
registerCleanupFunction(() => Services.prefs.clearUserPref(PREF));
addTab(TEST_URI);
browser.addEventListener("load", function onLoad() {
browser.removeEventListener("load", onLoad, true);
openConsole(null, consoleOpened);
}, true);
function consoleOpened(aHud)
{
hud = aHud;
ok(hud, "Web Console opened");
hud.jsterm.clearOutput();
content.console.log("foobarz1");
waitForMessages({
webconsole: hud,
messages: [{
text: "foobarz1",
category: CATEGORY_WEBDEV,
severity: SEVERITY_LOG,
}],
}).then(onConsoleMessage);
}
function onConsoleMessage()
{
browser.addEventListener("load", onReload, true);
content.location.reload();
}
function onReload()
{
browser.removeEventListener("load", onReload, true);
content.console.log("foobarz2");
waitForMessages({
webconsole: hud,
messages: [{
name: "page reload",
text: "test-console.html",
category: CATEGORY_NETWORK,
severity: SEVERITY_LOG,
},
{
text: "foobarz2",
category: CATEGORY_WEBDEV,
severity: SEVERITY_LOG,
},
{
name: "navigation marker",
text: "test-console.html",
type: Messages.NavigationMarker,
}],
}).then(onConsoleMessageAfterReload);
}
function onConsoleMessageAfterReload()
{
isnot(hud.outputNode.textContent.indexOf("foobarz1"), -1,
"foobarz1 is still in the output");
finishTest();
}
}

Просмотреть файл

@ -1,78 +0,0 @@
/* vim:set ts=2 sw=2 sts=2 et: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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/. */
// Tests that console groups behave properly.
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console.html";
function test() {
addTab(TEST_URI);
browser.addEventListener("load", function onLoad() {
browser.removeEventListener("load", onLoad, true);
openConsole(null, testGroups);
}, true);
}
function testGroups(HUD) {
let jsterm = HUD.jsterm;
let outputNode = HUD.outputNode;
jsterm.clearOutput();
// We test for one group by testing for zero "new" groups. The
// "webconsole-new-group" class creates a divider. Thus one group is
// indicated by zero new groups, two groups are indicated by one new group,
// and so on.
let waitForSecondMessage = {
name: "second console message",
validatorFn: function()
{
return outputNode.querySelectorAll(".webconsole-msg-output").length == 2;
},
successFn: function()
{
let timestamp1 = Date.now();
if (timestamp1 - timestamp0 < 5000) {
is(outputNode.querySelectorAll(".webconsole-new-group").length, 0,
"no group dividers exist after the second console message");
}
for (let i = 0; i < outputNode.itemCount; i++) {
outputNode.getItemAtIndex(i).timestamp = 0; // a "far past" value
}
jsterm.execute("2");
waitForSuccess(waitForThirdMessage);
},
failureFn: finishTest,
};
let waitForThirdMessage = {
name: "one group divider exists after the third console message",
validatorFn: function()
{
return outputNode.querySelectorAll(".webconsole-new-group").length == 1;
},
successFn: finishTest,
failureFn: finishTest,
};
let timestamp0 = Date.now();
jsterm.execute("0");
waitForSuccess({
name: "no group dividers exist after the first console message",
validatorFn: function()
{
return outputNode.querySelectorAll(".webconsole-new-group").length == 0;
},
successFn: function()
{
jsterm.execute("1");
waitForSuccess(waitForSecondMessage);
},
failureFn: finishTest,
});
}

Просмотреть файл

@ -124,12 +124,20 @@ function testFormSubmission()
// There should be 3 network requests pointing to the HTML file.
waitForMessages({
webconsole: hud,
messages: [{
text: "test-network-request.html",
category: CATEGORY_NETWORK,
severity: SEVERITY_LOG,
count: 3,
}],
messages: [
{
text: "test-network-request.html",
category: CATEGORY_NETWORK,
severity: SEVERITY_LOG,
count: 3,
},
{
text: "test-data.json",
category: CATEGORY_NETWORK,
severity: SEVERITY_LOG,
count: 2,
},
],
}).then(testLiveFilteringOnSearchStrings);
};

Просмотреть файл

@ -3,7 +3,7 @@
* 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/. */
let WebConsoleUtils, gDevTools, TargetFactory, console, promise;
let WebConsoleUtils, gDevTools, TargetFactory, console, promise, require;
(() => {
gDevTools = Cu.import("resource:///modules/devtools/gDevTools.jsm", {}).gDevTools;
@ -14,6 +14,7 @@ let WebConsoleUtils, gDevTools, TargetFactory, console, promise;
let utils = tools.require("devtools/toolkit/webconsole/utils");
TargetFactory = tools.TargetFactory;
WebConsoleUtils = utils.Utils;
require = tools.require;
})();
// promise._reportErrors = true; // please never leave me.
@ -884,6 +885,9 @@ function getMessageElementText(aElement)
* message.
* - longString: boolean, set to |true} to match long strings in the
* message.
* - type: match messages that are instances of the given object. For
* example, you can point to Messages.NavigationMarker to match any
* such message.
* - objects: boolean, set to |true| if you expect inspectable
* objects in the message.
* - source: object that can hold one property: url. This is used to
@ -1063,8 +1067,25 @@ function waitForMessages(aOptions)
return false;
}
if (aRule.type) {
// The rule tries to match the newer types of messages, based on their
// object constructor.
if (!aElement._messageObject ||
!(aElement._messageObject instanceof aRule.type)) {
return false;
}
}
else if (aElement._messageObject) {
// If the message element holds a reference to its object, it means this
// is a newer message type. All of the older waitForMessages() rules do
// not expect this kind of messages. We return false here.
// TODO: we keep this behavior until bug 778766 is fixed. After that we
// will not require |type| to match newer types of messages.
return false;
}
let partialMatch = !!(aRule.consoleTrace || aRule.consoleTime ||
aRule.consoleTimeEnd);
aRule.consoleTimeEnd || aRule.type);
if (aRule.category && aElement.category != aRule.category) {
if (partialMatch) {

Просмотреть файл

@ -22,6 +22,10 @@ loader.lazyGetter(this, "ToolSidebar",
() => require("devtools/framework/sidebar").ToolSidebar);
loader.lazyGetter(this, "NetworkPanel",
() => require("devtools/webconsole/network-panel").NetworkPanel);
loader.lazyGetter(this, "ConsoleOutput",
() => require("devtools/webconsole/console-output").ConsoleOutput);
loader.lazyGetter(this, "Messages",
() => require("devtools/webconsole/console-output").Messages);
loader.lazyImporter(this, "GripClient", "resource://gre/modules/devtools/dbg-client.jsm");
loader.lazyImporter(this, "VariablesView", "resource:///modules/devtools/VariablesView.jsm");
loader.lazyImporter(this, "VariablesViewController", "resource:///modules/devtools/VariablesViewController.jsm");
@ -187,6 +191,8 @@ function WebConsoleFrame(aWebConsoleOwner)
this._networkRequests = {};
this.filterPrefs = {};
this.output = new ConsoleOutput(this);
this._toggleFilter = this._toggleFilter.bind(this);
this._flushMessageQueue = this._flushMessageQueue.bind(this);
@ -324,6 +330,12 @@ WebConsoleFrame.prototype = {
*/
outputNode: null,
/**
* The ConsoleOutput instance that manages all output.
* @type object
*/
output: null,
/**
* The input element that allows the user to filter messages by string.
* @type nsIDOMElement
@ -830,8 +842,6 @@ WebConsoleFrame.prototype = {
node.classList.add("hud-filtered-by-type");
}
}
this.regroupOutput();
},
/**
@ -858,8 +868,6 @@ WebConsoleFrame.prototype = {
node.classList.add("hud-filtered-by-string");
}
}
this.regroupOutput();
},
/**
@ -1750,6 +1758,35 @@ WebConsoleFrame.prototype = {
}
},
/**
* Handler for the tabNavigated notification.
*
* @param string aEvent
* Event name.
* @param object aPacket
* Notification packet received from the server.
*/
handleTabNavigated: function WCF_handleTabNavigated(aEvent, aPacket)
{
if (aEvent == "will-navigate") {
if (this.persistLog) {
let marker = new Messages.NavigationMarker(aPacket.url, Date.now());
this.output.addMessage(marker);
}
else {
this.jsterm.clearOutput();
}
}
if (aPacket.url) {
this.onLocationChange(aPacket.url, aPacket.title);
}
if (aEvent == "navigate" && !aPacket.nativeConsoleAPI) {
this.logWarningAboutReplacedAPI();
}
},
/**
* Output a message node. This filters a node appropriately, then sends it to
* the output, regrouping and pruning output as necessary.
@ -1863,11 +1900,6 @@ WebConsoleFrame.prototype = {
this._pruneCategoriesQueue = {};
}
// Regroup messages at the end of the queue.
if (!this._outputQueue.length) {
this.regroupOutput();
}
let isInputOutput = lastVisibleNode &&
(lastVisibleNode.classList.contains("webconsole-msg-input") ||
lastVisibleNode.classList.contains("webconsole-msg-output"));
@ -2136,29 +2168,6 @@ WebConsoleFrame.prototype = {
}
},
/**
* Splits the given console messages into groups based on their timestamps.
*/
regroupOutput: function WCF_regroupOutput()
{
// Go through the nodes and adjust the placement of "webconsole-new-group"
// classes.
let nodes = this.outputNode.querySelectorAll(".hud-msg-node" +
":not(.hud-filtered-by-string):not(.hud-filtered-by-type)");
let lastTimestamp;
for (let i = 0, n = nodes.length; i < n; i++) {
let thisTimestamp = nodes[i].timestamp;
if (lastTimestamp != null &&
thisTimestamp >= lastTimestamp + NEW_GROUP_DELAY) {
nodes[i].classList.add("webconsole-new-group");
}
else {
nodes[i].classList.remove("webconsole-new-group");
}
lastTimestamp = thisTimestamp;
}
},
/**
* Given a category and message body, creates a DOM node to represent an
* incoming message. The timestamp is automatically added.
@ -2622,7 +2631,6 @@ WebConsoleFrame.prototype = {
// Gather up the selected items and concatenate their clipboard text.
let strings = [];
let newGroup = false;
let children = this.outputNode.children;
@ -2632,21 +2640,10 @@ WebConsoleFrame.prototype = {
continue;
}
// Add dashes between groups so that group boundaries show up in the
// copied output.
if (i > 0 && item.classList.contains("webconsole-new-group")) {
newGroup = true;
}
// Ensure the selected item hasn't been filtered by type or string.
if (!item.classList.contains("hud-filtered-by-type") &&
!item.classList.contains("hud-filtered-by-string")) {
let timestampString = l10n.timestampString(item.timestamp);
if (newGroup) {
strings.push("--");
newGroup = false;
}
if (aOptions.linkOnly) {
strings.push(item.url);
}
@ -2741,6 +2738,9 @@ WebConsoleFrame.prototype = {
this.jsterm.destroy();
this.jsterm = null;
}
this.output.destroy();
this.output = null;
if (this._contextMenuHandler) {
this._contextMenuHandler.destroy();
this._contextMenuHandler = null;
@ -4981,17 +4981,7 @@ WebConsoleConnectionProxy.prototype = {
return;
}
if (aEvent == "will-navigate" && !this.owner.persistLog) {
this.owner.jsterm.clearOutput();
}
if (aPacket.url) {
this.owner.onLocationChange(aPacket.url, aPacket.title);
}
if (aEvent == "navigate" && !aPacket.nativeConsoleAPI) {
this.owner.logWarningAboutReplacedAPI();
}
this.owner.handleTabNavigated(aEvent, aPacket);
},
/**

Просмотреть файл

@ -272,3 +272,17 @@
.webconsole-msg-security.webconsole-msg-warn {
-moz-image-region: rect(32px, 24px, 40px, 16px);
}
.navigation-marker {
color: #aaa;
background: linear-gradient(#fff, #bbb, #fff) no-repeat left 50%;
background-size: 100% 2px;
-moz-margin-start: 3px;
-moz-margin-end: 6px;
font-size: 0.9em;
}
.navigation-marker .url {
background: #fff;
-moz-padding-end: 6px;
}