Web Console output rewrite, bug 778766
This document describes the ongoing work for the Web Console output rewrite / reimplementation. Feedback is requested.
WORK IN PROGRESS!
Issues currently affecting the Web Console output:
- page navigation/reload issues:
- add an option to clear messages on reload, bug 705921.
- add a marker for each reload, bug 793996.
- group / gray out / mark as obsolete output from previous reloads, bug 655700.
- customized output for different types of objects:
- DOM elements, bug 682033.
- no syntax highlighting for JS and CSS, bug 584733.
- DOM enums, bug 762321.
- other types of JS objects, bug 843004.
- you cannot select freely any text in the output, bugs 706755 (sub-line, character selection) and 760876 (drag-select multiple lines).
- add stack traces to every console API call and every script error, bug 814497.
- hard to experiment with output changes, due to an aging codebase that doesn't
take into consideration today's use cases and needs. as a class of problem, this one doesn't feel consistent with the others -- what do we need to do to fix the problem? Do you have experiments in mind? Obvious ones should include SourceEditor-based output area and a pure HTML version. Others?
- hard to add new (custom) messages using the Web Console API, bug 775607.
- technical debt, cleanups needed:
- Move methods from the HUD Service to the individual HeadsUpDisplay objects, bug 592523,
- Use attributes instead of classes for category/severity, bug 628019, and
- confusion over console node, hud box and output node, bug 643135
- keyboard accessibility, Make clickable output in the WebConsole keyboard accessible bug 588010.
- too much output:
- improve filtering:
- provide APIs for filtering existing and new messages added to output, bug 837774. current filtering logic is based on xpath and walking DOM nodes...
- highlight category button when a new message is added, if the category is off - bug 801067.
- allow grouping of messages based on custom logic.
- allow expansion of grouped repeated messages, bug 704252.
- timestamps should be expandable/collapsible, bug 722267.
- improve filtering:
- performance issues:
- layout is flushed by reading
scrollHeight
too often, bug 746870. HS_regroupOutput()
is too slow and irrelevant as it stands, bug 746871.pruneOutputIfNecessary
is too slow, bug 746872.
- layout is flushed by reading
- lack of polish and UX issues:
- links can open using any mouse button, bug 653710.
- links in errors/warnings cannot be clicked/copied, bug 663370.
- clicking in blank areas of messages opens popups/sidebar, bug 773291.
- better UX for all links in output, bug 776939.
- "No value to execute" has no value, bug 835951.
- add tooltip to the repeat bubble, bug 675487.
- the choice of fonts need to be checked again, bugs 674422 and 696385.
- wrong image in
console.css
, bug 844637. - display column number in addition to line numbers, bug 684096.
- incorrect URL shown when a hash is used, bug 724224.
window.console
API improvements and new features that depend on a more flexible output implementation:console.trace()
output should be prettier, bug 790309.console.group()
should allow collapse, bug 755534.- make
console.dir()
output collapsible, bug 792633. - add support for
%c
style formatting, bug 823097. - print custom stack traces, bug 639800.
This list is somewhat sorted by priority. Do note that keyboard accessibility and performance are overall goals that should be under our constant attention.
Suggestions to prioritize bugs are welcome.
Overall goals in no particular order:
- improve user experience by polishing the output. We have a lot of bugs about simple issues that can be fixed to improve UX.
- improve the output visual appearance.
- make it easier to manage a lot of output by improving filtering, search, message grouping and more.
- make it easier for Mozilla developers to iterate on output changes, to add new kinds of messages, and to write new tests.
- add specialized output for different kinds of objects, to improve the overall console usability.
- improve accessibility.
- improve performance.
TODO 1: Any goals we should add/remove? Are they too vague/lofty? Should we be more explicit about what we want to fix in this list of goals?
TODO 2: Any non-goals?
We haven't decided whether or not we're going to replace the XUL RichItemList with some other format, HTML or SourceEditor. We should be up front about that because it will dictate how we deal with all of the other bugs on this list.
What about being explicit about Modularizing the ConsoleOutput so it can be retargeted or reused?
The HUDService.jsm
file will continue to be the Firefox and toolbox glue for
the Web Console user interface. This file should continue to be kept as
lightweight as possible.
All output will be HTML to make it easier to integrate other pieces of output that come from outside of the Web Console.
The webconsole.js
and webconsole.xul
files will continue to hold the user
interface implementation for the Web and Browser Consoles. I would like the XUL
file to continue to hold the basic console UI as opposed to having to create
each DOM element from JavaScript during initialization.
For webconsole.js
I would like to see it become much lighter. It is currently
bloated and it includes a lot of the older Web Console's arcane legacy code.
The WebConsoleFrame
and JSTerm
objects will continue to exist, while almost
everything related to output will move into ConsoleOutput.jsm
.
The JSTerm
input and autocomplete code will be refactored later, after
the output work. That will be a simpler and quicker endeavour.
Do we want to write down a plan here for
JSTerm
input and autocomplete as well?
We will add an output
property to the WebConsoleFrame
object which will hold
an instance of ConsoleOutput
.
We will keep setFilterState()
and getFilterState()
: the Web Console UI will
continue to manage filter UI (the buttons) and state. We currently have
a rather confusing mix of things: categories, severities (log levels) and class
names.
I propose that we do not impose any strict set of filter categories, severities, etc. I think we should have each button associated to a flag name / pref key that is either on or off. Messages will have their own filter logic which can check for any of these flags. This should allow us flexibility in terms of UI: we can later change those filter buttons and menus as we want as long as we set the right filter flags. We can add/remove flags freely - it's up to the messages we implement if they react to them or not.
When the filters state change via API calls or due to UI interaction we will
invoke ConsoleOutput.onFilterStateChanged()
or
ConsoleOutput.onFilterTextChanged()
. Then it is all up to the ConsoleOutput
to do the message filtering.
Instead of calling the
onFilterStateChanged()
/onFilterTextChanged()
methods onConsoleOutput
we could emit events. Should we do that?ConsoleOutput
could listen to these events on the owningWebConsole
instance and react to such changes.
Once all messages are filtered ConsoleOutput
should emit a messages-filtered
event.
Should we include which messages? Do we care?
The flags-based approach should allow us to have the same message belonging to multiple "categories" and any severity. We can also output messages that do not belong to any category or severity (as is the case with JavaScript evaluation).
Log limits: currently we have log limits per categories and we prune messages per category. We also have undocumented preferences allowing users to change the log limit for each category. In my opinion this is over the top. I believe we should simplify to a single log limit that limits the number of messages we display, overall. This would help us simplify output queues and pruning. We can also have one documented preference for this log limit.
Is there agreement with this change?
The new ConsoleOutput.jsm
would export a reusable component for building
something like the Web Console output. This file will contain the following
objects:
-
ConsoleOutput
Output manager: how to add new messages, how output is queued, pruning, filtering, removal of messages and scrolling.
-
Messages.BaseMessage
Base object from which all of the other message types inherit. This will include minimal API to get a usable message.
-
Messages.Simple
This is going to be the simplest type of message you can output to the console: it will support HTML content, a message source link, basic filtering, along with the option of assigning the message a category and a severity. To display the message source we will use the
MessageSource
widget. TheMessageTimestamp
widget will be used to display the timestamp. -
Messages.JavaScriptInput
Will be used for displaying JavaScript input that is evaluated. The
JavaScriptSnippet
widget will be used to syntax highlight user's input. -
Messages.JavaScriptOutput
Will be used for displaying JavaScript input that is evaluated. Initial implementation will use the
JSObject
widget to make the result available for inspection. For DOM elements we will useWidgets.DOMElement
. -
Messages.NetworkRequest
-
Messages.ReloadMarker
Will be used for bug 793996. The
Separator
widget will be used here. -
Messages.ConsoleGeneric
Will be used for almost all of the
window.console
API messages. JavaScript objects and DOM elements will be rendered with the appropriate widgets (JSObject
andDOMElement
). -
Messages.ConsoleDir
This is for
console.dir()
messages. TheVariablesView
widget will be used for displaying an inlineVariablesView
instance. -
Messages.ConsoleTrace
This is for
console.trace()
messages. TheStacktrace
widget will be used here, with nicer UI than now, to fix bug 790309. -
Messages.ConsoleGroup
-
Messages.ScriptError
-
Messages.StyleError
-
Widgets.BaseWidget
Every widget inherits from this one.
-
Widgets.Separator
-
Widgets.MessageSource
used for displaying the message source for most types of messages. -
Widgets.MessageTimestamp
used to display the message timestamp. -
Widgets.JavaScriptSnippet
-
Widgets.CSSSnippet
Reusable widgets for displaying JavaScript and CSS with syntax highlighting. We can probably reuse parts from CodeMirror when that lands.
-
Widgets.Stacktrace
will be used for displaying stacktraces forconsole.trace()
. Once we fix bug 814497 we will also use it forwindow.console
API messages and for script errors. -
Widgets.JSObject
Widget that displays an
ObjectActor
and allows users to inspect the object using theVariablesView
in a sidebar. -
Widgets.DOMElement
Similar to the
JSObject
widget but for DOM elements. The object will be rendered as an HTML tag:<tag id="foo" class="class names">
. This will fix bug 682033. -
Widgets.VariablesView
Reusable widget for messages that need to display an inline
VariablesView
. -
Utils
Holds common static methods we need throughout the code.
Each message object is meant to handle its own state - render itself, filter,
destroy, etc. The current Web Console output implementation is far too
cumbersome in this regard: we have one big createMessageNode()
function that
renders all kinds of messages. There's a similar situation with message
filtering and removal.
Widgets are meant to be reusable bits of UI within message objects. For example
the separator is reusable for the reload marker. The JSObject
widget is
reusable for all of the console messages that need to display an inline object
(an ObjectActor
) that you can inspect in the sidebar with VariablesView
.
TODO: this section is probably almost complete.
The ConsoleOutput
manages the output messages: how to add new messages, how
output is queued, pruning, filtering, removal of messages and scrolling.
Proposed API:
-
constructor(aOwner, aParentElement)
aOwner
points to the owning Web Console instance.aParentElement
points to the output element in the iframe where messages will be added.
-
addMessage(aMessage...)
Allows adding multiple messages at once. Each message is added to the output queue.
-
removeMessage(aMessage...)
allows the removal of messages. -
onFilterTextChanged(aNewText)
-
onFilterStateChanged(aFilters)
Invoked when any of the filters change. The
aFilters
object holds the state of all known filters. This method goes through all message objects and callsonFilterStateChanged()
for them.
Available events:
messages-added
fires whenever messages are added after there is an output update. The event object includes the list of all new messages.messages-updated
fires when there's an output update for messages that change their content due to user interaction. The list of all updated messages is included.messages-removed
fires when messages are pruned or otherwise removed.
The first two events are already partially implemented in WebConsoleFrame
.
TODO: this section is incomplete.
figure out sorting.
figure out grouping.
figure out copy to clipboard.
Each message is responsible for:
- rendering itself when
ConsoleOutput
asks it to; - filter itself based on user's text input or based on custom flags;
- show/hide itself when needed (for example due to filtering);
- destroy itself when the message is removed.
The BaseMessage
class is not meant to be used on its own. It has almost no
logic, it cannot render itself, it has no timestamp awareness, non-working
filtering capabilities, etc. All message types extend from this class or
subclasses.
Methods:
-
constructor(....)
each type of message has its own constructor that takes whatever it needs. For examplewindow.console
API related messages will take the console message received from the server, which includes the arguments and the rest of API call information.Once constructed messages don't do almost anything and they are unowned.
-
init(aOwner, aParent)
is invoked by theConsoleOutput
instance once you dooutput.addMessage(msg)
.aOwner
will beoutput
andaParent
will be the parent message if there's any (this is given only if this is a child message in a group). This is when the message becomes owned by aConsoleOutput
. -
onRemoveFromQueue()
is invoked only byConsoleOutput
if this message object will be pruned from the output queue before it is rendered. -
render()
is invoked when this message is displayed. It returns a DOM element that is added into the document by the parent message or byConsoleOutput
. Callingrender()
multiple times gives you the same element. -
onRemove()
is invoked once this message is pruned or otherwise removed after it was once rendered. -
onFilterTextChanged(aNewText)
-
onFilterStateChanged(aFilters)
Invoked when any of the filters change. The
aFilters
object holds the state of all known filters. This method returns a boolean that tells if this message should be hidden or not.
Properties:
owner
points to the owningConsoleOutput
instance.parent
gives the parent group of messages (if any).element
gives the DOM element of this message, if it was ever rendered.rendered
boolean that tells if this message has been rendered.visible
boolean that tells if this message is visible or not.textContent
string that gives the full message content.widgets
set of widgets contained by this message.
TODO: this section is almost complete.
The Simple
message extends the BaseMessage
API making it usable for basic
message output. This is the usual base class for the rest message types in the
Web Console. The BaseMessage
class should be used as a base only if you need
a message type that has more advanced requirements related to rendering and
filtering.
Methods:
constructor(aMessage, aOptions = {})
whereaMessage
is the message you want to display. Options:html
- (boolean) tells ifaMessage
is HTML or not. Defaults tofalse
.category
- (string) category that this message belongs to. Defaults to no category.severity
- (string) severity of the message. Defaults to no severity.timestamp
- (number) time of message. Defaults toDate.now()
.location
object tells the message source:file
,line
,column
,lineText
.
Properties:
timestamp
,category
,severity
,location
.
This message object can filter itself based on category, severity and text content. The message content is not allowed to be HTML by default to prevent potential breakage. HTML output shall be enabled on a case by case basis.
Should we make
timestamp
part of theBaseMessage
API?
Notice that there's no explicit list of allowed categories or severities. That
is left at the discretion of the implementers - UI and UX designers. This also
up to each message implementation. Some types of messages are expected to force
their own category/severity (for example ConsoleDir
or NetworkRequest
).
TODO: this section is almost complete.
The BaseWidget
class is meant to be the class that other widget classes extend
from.
Methods:
constructor(aMessage)
whereaMessage
is the owning message object.render()
is invoked when the owning message is displayed. This is also when the widget needs to be displayed. This function must return a DOM element.destroy()
is invoked when the owning message is removed.
Properties:
textContent
.
TODO: almost done, perhaps.
Current plan is to take bugs from the list of known issues and go through them, sorted by technical priorities. There will be no one big patch to rewrite everything. New APIs will be written and existing code will be converted to that as we fix bugs, step by step.
This section will describe the list of main bugs where work will happen to convert filtering, pruning, output queueing, and existing message types to the new APIs.
TODO: Write down the plan!
vim:set ft=markdown: