1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/browser/devtools/webconsole/console-output.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,2867 @@ 1.4 +/* vim: set ts=2 et sw=2 tw=80: */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +"use strict"; 1.10 + 1.11 +const {Cc, Ci, Cu} = require("chrome"); 1.12 + 1.13 +loader.lazyImporter(this, "VariablesView", "resource:///modules/devtools/VariablesView.jsm"); 1.14 +loader.lazyImporter(this, "escapeHTML", "resource:///modules/devtools/VariablesView.jsm"); 1.15 +loader.lazyImporter(this, "gDevTools", "resource:///modules/devtools/gDevTools.jsm"); 1.16 +loader.lazyImporter(this, "Task","resource://gre/modules/Task.jsm"); 1.17 + 1.18 +const Heritage = require("sdk/core/heritage"); 1.19 +const XHTML_NS = "http://www.w3.org/1999/xhtml"; 1.20 +const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; 1.21 +const STRINGS_URI = "chrome://browser/locale/devtools/webconsole.properties"; 1.22 + 1.23 +const WebConsoleUtils = require("devtools/toolkit/webconsole/utils").Utils; 1.24 +const l10n = new WebConsoleUtils.l10n(STRINGS_URI); 1.25 + 1.26 +// Constants for compatibility with the Web Console output implementation before 1.27 +// bug 778766. 1.28 +// TODO: remove these once bug 778766 is fixed. 1.29 +const COMPAT = { 1.30 + // The various categories of messages. 1.31 + CATEGORIES: { 1.32 + NETWORK: 0, 1.33 + CSS: 1, 1.34 + JS: 2, 1.35 + WEBDEV: 3, 1.36 + INPUT: 4, 1.37 + OUTPUT: 5, 1.38 + SECURITY: 6, 1.39 + }, 1.40 + 1.41 + // The possible message severities. 1.42 + SEVERITIES: { 1.43 + ERROR: 0, 1.44 + WARNING: 1, 1.45 + INFO: 2, 1.46 + LOG: 3, 1.47 + }, 1.48 + 1.49 + // The preference keys to use for each category/severity combination, indexed 1.50 + // first by category (rows) and then by severity (columns). 1.51 + // 1.52 + // Most of these rather idiosyncratic names are historical and predate the 1.53 + // division of message type into "category" and "severity". 1.54 + PREFERENCE_KEYS: [ 1.55 + // Error Warning Info Log 1.56 + [ "network", "netwarn", null, "networkinfo", ], // Network 1.57 + [ "csserror", "cssparser", null, null, ], // CSS 1.58 + [ "exception", "jswarn", null, "jslog", ], // JS 1.59 + [ "error", "warn", "info", "log", ], // Web Developer 1.60 + [ null, null, null, null, ], // Input 1.61 + [ null, null, null, null, ], // Output 1.62 + [ "secerror", "secwarn", null, null, ], // Security 1.63 + ], 1.64 + 1.65 + // The fragment of a CSS class name that identifies each category. 1.66 + CATEGORY_CLASS_FRAGMENTS: [ "network", "cssparser", "exception", "console", 1.67 + "input", "output", "security" ], 1.68 + 1.69 + // The fragment of a CSS class name that identifies each severity. 1.70 + SEVERITY_CLASS_FRAGMENTS: [ "error", "warn", "info", "log" ], 1.71 + 1.72 + // The indent of a console group in pixels. 1.73 + GROUP_INDENT: 12, 1.74 +}; 1.75 + 1.76 +// A map from the console API call levels to the Web Console severities. 1.77 +const CONSOLE_API_LEVELS_TO_SEVERITIES = { 1.78 + error: "error", 1.79 + exception: "error", 1.80 + assert: "error", 1.81 + warn: "warning", 1.82 + info: "info", 1.83 + log: "log", 1.84 + trace: "log", 1.85 + debug: "log", 1.86 + dir: "log", 1.87 + group: "log", 1.88 + groupCollapsed: "log", 1.89 + groupEnd: "log", 1.90 + time: "log", 1.91 + timeEnd: "log", 1.92 + count: "log" 1.93 +}; 1.94 + 1.95 +// Array of known message source URLs we need to hide from output. 1.96 +const IGNORED_SOURCE_URLS = ["debugger eval code", "self-hosted"]; 1.97 + 1.98 +// The maximum length of strings to be displayed by the Web Console. 1.99 +const MAX_LONG_STRING_LENGTH = 200000; 1.100 + 1.101 +// Regular expression that matches the allowed CSS property names when using 1.102 +// the `window.console` API. 1.103 +const RE_ALLOWED_STYLES = /^(?:-moz-)?(?:background|border|box|clear|color|cursor|display|float|font|line|margin|padding|text|transition|outline|white-space|word|writing|(?:min-|max-)?width|(?:min-|max-)?height)/; 1.104 + 1.105 +// Regular expressions to search and replace with 'notallowed' in the styles 1.106 +// given to the `window.console` API methods. 1.107 +const RE_CLEANUP_STYLES = [ 1.108 + // url(), -moz-element() 1.109 + /\b(?:url|(?:-moz-)?element)[\s('"]+/gi, 1.110 + 1.111 + // various URL protocols 1.112 + /['"(]*(?:chrome|resource|about|app|data|https?|ftp|file):+\/*/gi, 1.113 +]; 1.114 + 1.115 +/** 1.116 + * The ConsoleOutput object is used to manage output of messages in the Web 1.117 + * Console. 1.118 + * 1.119 + * @constructor 1.120 + * @param object owner 1.121 + * The console output owner. This usually the WebConsoleFrame instance. 1.122 + * Any other object can be used, as long as it has the following 1.123 + * properties and methods: 1.124 + * - window 1.125 + * - document 1.126 + * - outputMessage(category, methodOrNode[, methodArguments]) 1.127 + * TODO: this is needed temporarily, until bug 778766 is fixed. 1.128 + */ 1.129 +function ConsoleOutput(owner) 1.130 +{ 1.131 + this.owner = owner; 1.132 + this._onFlushOutputMessage = this._onFlushOutputMessage.bind(this); 1.133 +} 1.134 + 1.135 +ConsoleOutput.prototype = { 1.136 + _dummyElement: null, 1.137 + 1.138 + /** 1.139 + * The output container. 1.140 + * @type DOMElement 1.141 + */ 1.142 + get element() { 1.143 + return this.owner.outputNode; 1.144 + }, 1.145 + 1.146 + /** 1.147 + * The document that holds the output. 1.148 + * @type DOMDocument 1.149 + */ 1.150 + get document() { 1.151 + return this.owner ? this.owner.document : null; 1.152 + }, 1.153 + 1.154 + /** 1.155 + * The DOM window that holds the output. 1.156 + * @type Window 1.157 + */ 1.158 + get window() { 1.159 + return this.owner.window; 1.160 + }, 1.161 + 1.162 + /** 1.163 + * Getter for the debugger WebConsoleClient. 1.164 + * @type object 1.165 + */ 1.166 + get webConsoleClient() { 1.167 + return this.owner.webConsoleClient; 1.168 + }, 1.169 + 1.170 + /** 1.171 + * Getter for the current toolbox debuggee target. 1.172 + * @type Target 1.173 + */ 1.174 + get toolboxTarget() { 1.175 + return this.owner.owner.target; 1.176 + }, 1.177 + 1.178 + /** 1.179 + * Release an actor. 1.180 + * 1.181 + * @private 1.182 + * @param string actorId 1.183 + * The actor ID you want to release. 1.184 + */ 1.185 + _releaseObject: function(actorId) 1.186 + { 1.187 + this.owner._releaseObject(actorId); 1.188 + }, 1.189 + 1.190 + /** 1.191 + * Add a message to output. 1.192 + * 1.193 + * @param object ...args 1.194 + * Any number of Message objects. 1.195 + * @return this 1.196 + */ 1.197 + addMessage: function(...args) 1.198 + { 1.199 + for (let msg of args) { 1.200 + msg.init(this); 1.201 + this.owner.outputMessage(msg._categoryCompat, this._onFlushOutputMessage, 1.202 + [msg]); 1.203 + } 1.204 + return this; 1.205 + }, 1.206 + 1.207 + /** 1.208 + * Message renderer used for compatibility with the current Web Console output 1.209 + * implementation. This method is invoked for every message object that is 1.210 + * flushed to output. The message object is initialized and rendered, then it 1.211 + * is displayed. 1.212 + * 1.213 + * TODO: remove this method once bug 778766 is fixed. 1.214 + * 1.215 + * @private 1.216 + * @param object message 1.217 + * The message object to render. 1.218 + * @return DOMElement 1.219 + * The message DOM element that can be added to the console output. 1.220 + */ 1.221 + _onFlushOutputMessage: function(message) 1.222 + { 1.223 + return message.render().element; 1.224 + }, 1.225 + 1.226 + /** 1.227 + * Get an array of selected messages. This list is based on the text selection 1.228 + * start and end points. 1.229 + * 1.230 + * @param number [limit] 1.231 + * Optional limit of selected messages you want. If no value is given, 1.232 + * all of the selected messages are returned. 1.233 + * @return array 1.234 + * Array of DOM elements for each message that is currently selected. 1.235 + */ 1.236 + getSelectedMessages: function(limit) 1.237 + { 1.238 + let selection = this.window.getSelection(); 1.239 + if (selection.isCollapsed) { 1.240 + return []; 1.241 + } 1.242 + 1.243 + if (selection.containsNode(this.element, true)) { 1.244 + return Array.slice(this.element.children); 1.245 + } 1.246 + 1.247 + let anchor = this.getMessageForElement(selection.anchorNode); 1.248 + let focus = this.getMessageForElement(selection.focusNode); 1.249 + if (!anchor || !focus) { 1.250 + return []; 1.251 + } 1.252 + 1.253 + let start, end; 1.254 + if (anchor.timestamp > focus.timestamp) { 1.255 + start = focus; 1.256 + end = anchor; 1.257 + } else { 1.258 + start = anchor; 1.259 + end = focus; 1.260 + } 1.261 + 1.262 + let result = []; 1.263 + let current = start; 1.264 + while (current) { 1.265 + result.push(current); 1.266 + if (current == end || (limit && result.length == limit)) { 1.267 + break; 1.268 + } 1.269 + current = current.nextSibling; 1.270 + } 1.271 + return result; 1.272 + }, 1.273 + 1.274 + /** 1.275 + * Find the DOM element of a message for any given descendant. 1.276 + * 1.277 + * @param DOMElement elem 1.278 + * The element to start the search from. 1.279 + * @return DOMElement|null 1.280 + * The DOM element of the message, if any. 1.281 + */ 1.282 + getMessageForElement: function(elem) 1.283 + { 1.284 + while (elem && elem.parentNode) { 1.285 + if (elem.classList && elem.classList.contains("message")) { 1.286 + return elem; 1.287 + } 1.288 + elem = elem.parentNode; 1.289 + } 1.290 + return null; 1.291 + }, 1.292 + 1.293 + /** 1.294 + * Select all messages. 1.295 + */ 1.296 + selectAllMessages: function() 1.297 + { 1.298 + let selection = this.window.getSelection(); 1.299 + selection.removeAllRanges(); 1.300 + let range = this.document.createRange(); 1.301 + range.selectNodeContents(this.element); 1.302 + selection.addRange(range); 1.303 + }, 1.304 + 1.305 + /** 1.306 + * Add a message to the selection. 1.307 + * 1.308 + * @param DOMElement elem 1.309 + * The message element to select. 1.310 + */ 1.311 + selectMessage: function(elem) 1.312 + { 1.313 + let selection = this.window.getSelection(); 1.314 + selection.removeAllRanges(); 1.315 + let range = this.document.createRange(); 1.316 + range.selectNodeContents(elem); 1.317 + selection.addRange(range); 1.318 + }, 1.319 + 1.320 + /** 1.321 + * Open an URL in a new tab. 1.322 + * @see WebConsole.openLink() in hudservice.js 1.323 + */ 1.324 + openLink: function() 1.325 + { 1.326 + this.owner.owner.openLink.apply(this.owner.owner, arguments); 1.327 + }, 1.328 + 1.329 + /** 1.330 + * Open the variables view to inspect an object actor. 1.331 + * @see JSTerm.openVariablesView() in webconsole.js 1.332 + */ 1.333 + openVariablesView: function() 1.334 + { 1.335 + this.owner.jsterm.openVariablesView.apply(this.owner.jsterm, arguments); 1.336 + }, 1.337 + 1.338 + /** 1.339 + * Destroy this ConsoleOutput instance. 1.340 + */ 1.341 + destroy: function() 1.342 + { 1.343 + this._dummyElement = null; 1.344 + this.owner = null; 1.345 + }, 1.346 +}; // ConsoleOutput.prototype 1.347 + 1.348 +/** 1.349 + * Message objects container. 1.350 + * @type object 1.351 + */ 1.352 +let Messages = {}; 1.353 + 1.354 +/** 1.355 + * The BaseMessage object is used for all types of messages. Every kind of 1.356 + * message should use this object as its base. 1.357 + * 1.358 + * @constructor 1.359 + */ 1.360 +Messages.BaseMessage = function() 1.361 +{ 1.362 + this.widgets = new Set(); 1.363 + this._onClickAnchor = this._onClickAnchor.bind(this); 1.364 + this._repeatID = { uid: gSequenceId() }; 1.365 + this.textContent = ""; 1.366 +}; 1.367 + 1.368 +Messages.BaseMessage.prototype = { 1.369 + /** 1.370 + * Reference to the ConsoleOutput owner. 1.371 + * 1.372 + * @type object|null 1.373 + * This is |null| if the message is not yet initialized. 1.374 + */ 1.375 + output: null, 1.376 + 1.377 + /** 1.378 + * Reference to the parent message object, if this message is in a group or if 1.379 + * it is otherwise owned by another message. 1.380 + * 1.381 + * @type object|null 1.382 + */ 1.383 + parent: null, 1.384 + 1.385 + /** 1.386 + * Message DOM element. 1.387 + * 1.388 + * @type DOMElement|null 1.389 + * This is |null| if the message is not yet rendered. 1.390 + */ 1.391 + element: null, 1.392 + 1.393 + /** 1.394 + * Tells if this message is visible or not. 1.395 + * @type boolean 1.396 + */ 1.397 + get visible() { 1.398 + return this.element && this.element.parentNode; 1.399 + }, 1.400 + 1.401 + /** 1.402 + * The owner DOM document. 1.403 + * @type DOMElement 1.404 + */ 1.405 + get document() { 1.406 + return this.output.document; 1.407 + }, 1.408 + 1.409 + /** 1.410 + * Holds the text-only representation of the message. 1.411 + * @type string 1.412 + */ 1.413 + textContent: null, 1.414 + 1.415 + /** 1.416 + * Set of widgets included in this message. 1.417 + * @type Set 1.418 + */ 1.419 + widgets: null, 1.420 + 1.421 + // Properties that allow compatibility with the current Web Console output 1.422 + // implementation. 1.423 + _categoryCompat: null, 1.424 + _severityCompat: null, 1.425 + _categoryNameCompat: null, 1.426 + _severityNameCompat: null, 1.427 + _filterKeyCompat: null, 1.428 + 1.429 + /** 1.430 + * Object that is JSON-ified and used as a non-unique ID for tracking 1.431 + * duplicate messages. 1.432 + * @private 1.433 + * @type object 1.434 + */ 1.435 + _repeatID: null, 1.436 + 1.437 + /** 1.438 + * Initialize the message. 1.439 + * 1.440 + * @param object output 1.441 + * The ConsoleOutput owner. 1.442 + * @param object [parent=null] 1.443 + * Optional: a different message object that owns this instance. 1.444 + * @return this 1.445 + */ 1.446 + init: function(output, parent=null) 1.447 + { 1.448 + this.output = output; 1.449 + this.parent = parent; 1.450 + return this; 1.451 + }, 1.452 + 1.453 + /** 1.454 + * Non-unique ID for this message object used for tracking duplicate messages. 1.455 + * Different message kinds can identify themselves based their own criteria. 1.456 + * 1.457 + * @return string 1.458 + */ 1.459 + getRepeatID: function() 1.460 + { 1.461 + return JSON.stringify(this._repeatID); 1.462 + }, 1.463 + 1.464 + /** 1.465 + * Render the message. After this method is invoked the |element| property 1.466 + * will point to the DOM element of this message. 1.467 + * @return this 1.468 + */ 1.469 + render: function() 1.470 + { 1.471 + if (!this.element) { 1.472 + this.element = this._renderCompat(); 1.473 + } 1.474 + return this; 1.475 + }, 1.476 + 1.477 + /** 1.478 + * Prepare the message container for the Web Console, such that it is 1.479 + * compatible with the current implementation. 1.480 + * TODO: remove this once bug 778766 is fixed. 1.481 + * 1.482 + * @private 1.483 + * @return Element 1.484 + * The DOM element that wraps the message. 1.485 + */ 1.486 + _renderCompat: function() 1.487 + { 1.488 + let doc = this.output.document; 1.489 + let container = doc.createElementNS(XHTML_NS, "div"); 1.490 + container.id = "console-msg-" + gSequenceId(); 1.491 + container.className = "message"; 1.492 + container.category = this._categoryCompat; 1.493 + container.severity = this._severityCompat; 1.494 + container.setAttribute("category", this._categoryNameCompat); 1.495 + container.setAttribute("severity", this._severityNameCompat); 1.496 + container.setAttribute("filter", this._filterKeyCompat); 1.497 + container.clipboardText = this.textContent; 1.498 + container.timestamp = this.timestamp; 1.499 + container._messageObject = this; 1.500 + 1.501 + return container; 1.502 + }, 1.503 + 1.504 + /** 1.505 + * Add a click callback to a given DOM element. 1.506 + * 1.507 + * @private 1.508 + * @param Element element 1.509 + * The DOM element to which you want to add a click event handler. 1.510 + * @param function [callback=this._onClickAnchor] 1.511 + * Optional click event handler. The default event handler is 1.512 + * |this._onClickAnchor|. 1.513 + */ 1.514 + _addLinkCallback: function(element, callback = this._onClickAnchor) 1.515 + { 1.516 + // This is going into the WebConsoleFrame object instance that owns 1.517 + // the ConsoleOutput object. The WebConsoleFrame owner is the WebConsole 1.518 + // object instance from hudservice.js. 1.519 + // TODO: move _addMessageLinkCallback() into ConsoleOutput once bug 778766 1.520 + // is fixed. 1.521 + this.output.owner._addMessageLinkCallback(element, callback); 1.522 + }, 1.523 + 1.524 + /** 1.525 + * The default |click| event handler for links in the output. This function 1.526 + * opens the anchor's link in a new tab. 1.527 + * 1.528 + * @private 1.529 + * @param Event event 1.530 + * The DOM event that invoked this function. 1.531 + */ 1.532 + _onClickAnchor: function(event) 1.533 + { 1.534 + this.output.openLink(event.target.href); 1.535 + }, 1.536 + 1.537 + destroy: function() 1.538 + { 1.539 + // Destroy all widgets that have registered themselves in this.widgets 1.540 + for (let widget of this.widgets) { 1.541 + widget.destroy(); 1.542 + } 1.543 + this.widgets.clear(); 1.544 + } 1.545 +}; // Messages.BaseMessage.prototype 1.546 + 1.547 + 1.548 +/** 1.549 + * The NavigationMarker is used to show a page load event. 1.550 + * 1.551 + * @constructor 1.552 + * @extends Messages.BaseMessage 1.553 + * @param string url 1.554 + * The URL to display. 1.555 + * @param number timestamp 1.556 + * The message date and time, milliseconds elapsed since 1 January 1970 1.557 + * 00:00:00 UTC. 1.558 + */ 1.559 +Messages.NavigationMarker = function(url, timestamp) 1.560 +{ 1.561 + Messages.BaseMessage.call(this); 1.562 + this._url = url; 1.563 + this.textContent = "------ " + url; 1.564 + this.timestamp = timestamp; 1.565 +}; 1.566 + 1.567 +Messages.NavigationMarker.prototype = Heritage.extend(Messages.BaseMessage.prototype, 1.568 +{ 1.569 + /** 1.570 + * The address of the loading page. 1.571 + * @private 1.572 + * @type string 1.573 + */ 1.574 + _url: null, 1.575 + 1.576 + /** 1.577 + * Message timestamp. 1.578 + * 1.579 + * @type number 1.580 + * Milliseconds elapsed since 1 January 1970 00:00:00 UTC. 1.581 + */ 1.582 + timestamp: 0, 1.583 + 1.584 + _categoryCompat: COMPAT.CATEGORIES.NETWORK, 1.585 + _severityCompat: COMPAT.SEVERITIES.LOG, 1.586 + _categoryNameCompat: "network", 1.587 + _severityNameCompat: "info", 1.588 + _filterKeyCompat: "networkinfo", 1.589 + 1.590 + /** 1.591 + * Prepare the DOM element for this message. 1.592 + * @return this 1.593 + */ 1.594 + render: function() 1.595 + { 1.596 + if (this.element) { 1.597 + return this; 1.598 + } 1.599 + 1.600 + let url = this._url; 1.601 + let pos = url.indexOf("?"); 1.602 + if (pos > -1) { 1.603 + url = url.substr(0, pos); 1.604 + } 1.605 + 1.606 + let doc = this.output.document; 1.607 + let urlnode = doc.createElementNS(XHTML_NS, "a"); 1.608 + urlnode.className = "url"; 1.609 + urlnode.textContent = url; 1.610 + urlnode.title = this._url; 1.611 + urlnode.href = this._url; 1.612 + urlnode.draggable = false; 1.613 + this._addLinkCallback(urlnode); 1.614 + 1.615 + let render = Messages.BaseMessage.prototype.render.bind(this); 1.616 + render().element.appendChild(urlnode); 1.617 + this.element.classList.add("navigation-marker"); 1.618 + this.element.url = this._url; 1.619 + this.element.appendChild(doc.createTextNode("\n")); 1.620 + 1.621 + return this; 1.622 + }, 1.623 +}); // Messages.NavigationMarker.prototype 1.624 + 1.625 + 1.626 +/** 1.627 + * The Simple message is used to show any basic message in the Web Console. 1.628 + * 1.629 + * @constructor 1.630 + * @extends Messages.BaseMessage 1.631 + * @param string|Node|function message 1.632 + * The message to display. 1.633 + * @param object [options] 1.634 + * Options for this message: 1.635 + * - category: (string) category that this message belongs to. Defaults 1.636 + * to no category. 1.637 + * - severity: (string) severity of the message. Defaults to no severity. 1.638 + * - timestamp: (number) date and time when the message was recorded. 1.639 + * Defaults to |Date.now()|. 1.640 + * - link: (string) if provided, the message will be wrapped in an anchor 1.641 + * pointing to the given URL here. 1.642 + * - linkCallback: (function) if provided, the message will be wrapped in 1.643 + * an anchor. The |linkCallback| function will be added as click event 1.644 + * handler. 1.645 + * - location: object that tells the message source: url, line, column 1.646 + * and lineText. 1.647 + * - className: (string) additional element class names for styling 1.648 + * purposes. 1.649 + * - private: (boolean) mark this as a private message. 1.650 + * - filterDuplicates: (boolean) true if you do want this message to be 1.651 + * filtered as a potential duplicate message, false otherwise. 1.652 + */ 1.653 +Messages.Simple = function(message, options = {}) 1.654 +{ 1.655 + Messages.BaseMessage.call(this); 1.656 + 1.657 + this.category = options.category; 1.658 + this.severity = options.severity; 1.659 + this.location = options.location; 1.660 + this.timestamp = options.timestamp || Date.now(); 1.661 + this.private = !!options.private; 1.662 + 1.663 + this._message = message; 1.664 + this._className = options.className; 1.665 + this._link = options.link; 1.666 + this._linkCallback = options.linkCallback; 1.667 + this._filterDuplicates = options.filterDuplicates; 1.668 +}; 1.669 + 1.670 +Messages.Simple.prototype = Heritage.extend(Messages.BaseMessage.prototype, 1.671 +{ 1.672 + /** 1.673 + * Message category. 1.674 + * @type string 1.675 + */ 1.676 + category: null, 1.677 + 1.678 + /** 1.679 + * Message severity. 1.680 + * @type string 1.681 + */ 1.682 + severity: null, 1.683 + 1.684 + /** 1.685 + * Message source location. Properties: url, line, column, lineText. 1.686 + * @type object 1.687 + */ 1.688 + location: null, 1.689 + 1.690 + /** 1.691 + * Tells if this message comes from a private browsing context. 1.692 + * @type boolean 1.693 + */ 1.694 + private: false, 1.695 + 1.696 + /** 1.697 + * Custom class name for the DOM element of the message. 1.698 + * @private 1.699 + * @type string 1.700 + */ 1.701 + _className: null, 1.702 + 1.703 + /** 1.704 + * Message link - if this message is clicked then this URL opens in a new tab. 1.705 + * @private 1.706 + * @type string 1.707 + */ 1.708 + _link: null, 1.709 + 1.710 + /** 1.711 + * Message click event handler. 1.712 + * @private 1.713 + * @type function 1.714 + */ 1.715 + _linkCallback: null, 1.716 + 1.717 + /** 1.718 + * Tells if this message should be checked if it is a duplicate of another 1.719 + * message or not. 1.720 + */ 1.721 + _filterDuplicates: false, 1.722 + 1.723 + /** 1.724 + * The raw message displayed by this Message object. This can be a function, 1.725 + * DOM node or a string. 1.726 + * 1.727 + * @private 1.728 + * @type mixed 1.729 + */ 1.730 + _message: null, 1.731 + 1.732 + _afterMessage: null, 1.733 + _objectActors: null, 1.734 + _groupDepthCompat: 0, 1.735 + 1.736 + /** 1.737 + * Message timestamp. 1.738 + * 1.739 + * @type number 1.740 + * Milliseconds elapsed since 1 January 1970 00:00:00 UTC. 1.741 + */ 1.742 + timestamp: 0, 1.743 + 1.744 + get _categoryCompat() { 1.745 + return this.category ? 1.746 + COMPAT.CATEGORIES[this.category.toUpperCase()] : null; 1.747 + }, 1.748 + get _severityCompat() { 1.749 + return this.severity ? 1.750 + COMPAT.SEVERITIES[this.severity.toUpperCase()] : null; 1.751 + }, 1.752 + get _categoryNameCompat() { 1.753 + return this.category ? 1.754 + COMPAT.CATEGORY_CLASS_FRAGMENTS[this._categoryCompat] : null; 1.755 + }, 1.756 + get _severityNameCompat() { 1.757 + return this.severity ? 1.758 + COMPAT.SEVERITY_CLASS_FRAGMENTS[this._severityCompat] : null; 1.759 + }, 1.760 + 1.761 + get _filterKeyCompat() { 1.762 + return this._categoryCompat !== null && this._severityCompat !== null ? 1.763 + COMPAT.PREFERENCE_KEYS[this._categoryCompat][this._severityCompat] : 1.764 + null; 1.765 + }, 1.766 + 1.767 + init: function() 1.768 + { 1.769 + Messages.BaseMessage.prototype.init.apply(this, arguments); 1.770 + this._groupDepthCompat = this.output.owner.groupDepth; 1.771 + this._initRepeatID(); 1.772 + return this; 1.773 + }, 1.774 + 1.775 + _initRepeatID: function() 1.776 + { 1.777 + if (!this._filterDuplicates) { 1.778 + return; 1.779 + } 1.780 + 1.781 + // Add the properties we care about for identifying duplicate messages. 1.782 + let rid = this._repeatID; 1.783 + delete rid.uid; 1.784 + 1.785 + rid.category = this.category; 1.786 + rid.severity = this.severity; 1.787 + rid.private = this.private; 1.788 + rid.location = this.location; 1.789 + rid.link = this._link; 1.790 + rid.linkCallback = this._linkCallback + ""; 1.791 + rid.className = this._className; 1.792 + rid.groupDepth = this._groupDepthCompat; 1.793 + rid.textContent = ""; 1.794 + }, 1.795 + 1.796 + getRepeatID: function() 1.797 + { 1.798 + // No point in returning a string that includes other properties when there 1.799 + // is a unique ID. 1.800 + if (this._repeatID.uid) { 1.801 + return JSON.stringify({ uid: this._repeatID.uid }); 1.802 + } 1.803 + 1.804 + return JSON.stringify(this._repeatID); 1.805 + }, 1.806 + 1.807 + render: function() 1.808 + { 1.809 + if (this.element) { 1.810 + return this; 1.811 + } 1.812 + 1.813 + let timestamp = new Widgets.MessageTimestamp(this, this.timestamp).render(); 1.814 + 1.815 + let icon = this.document.createElementNS(XHTML_NS, "span"); 1.816 + icon.className = "icon"; 1.817 + 1.818 + // Apply the current group by indenting appropriately. 1.819 + // TODO: remove this once bug 778766 is fixed. 1.820 + let indent = this._groupDepthCompat * COMPAT.GROUP_INDENT; 1.821 + let indentNode = this.document.createElementNS(XHTML_NS, "span"); 1.822 + indentNode.className = "indent"; 1.823 + indentNode.style.width = indent + "px"; 1.824 + 1.825 + let body = this._renderBody(); 1.826 + this._repeatID.textContent += "|" + body.textContent; 1.827 + 1.828 + let repeatNode = this._renderRepeatNode(); 1.829 + let location = this._renderLocation(); 1.830 + 1.831 + Messages.BaseMessage.prototype.render.call(this); 1.832 + if (this._className) { 1.833 + this.element.className += " " + this._className; 1.834 + } 1.835 + 1.836 + this.element.appendChild(timestamp.element); 1.837 + this.element.appendChild(indentNode); 1.838 + this.element.appendChild(icon); 1.839 + this.element.appendChild(body); 1.840 + if (repeatNode) { 1.841 + this.element.appendChild(repeatNode); 1.842 + } 1.843 + if (location) { 1.844 + this.element.appendChild(location); 1.845 + } 1.846 + this.element.appendChild(this.document.createTextNode("\n")); 1.847 + 1.848 + this.element.clipboardText = this.element.textContent; 1.849 + 1.850 + if (this.private) { 1.851 + this.element.setAttribute("private", true); 1.852 + } 1.853 + 1.854 + if (this._afterMessage) { 1.855 + this.element._outputAfterNode = this._afterMessage.element; 1.856 + this._afterMessage = null; 1.857 + } 1.858 + 1.859 + // TODO: handle object releasing in a more elegant way once all console 1.860 + // messages use the new API - bug 778766. 1.861 + this.element._objectActors = this._objectActors; 1.862 + this._objectActors = null; 1.863 + 1.864 + return this; 1.865 + }, 1.866 + 1.867 + /** 1.868 + * Render the message body DOM element. 1.869 + * @private 1.870 + * @return Element 1.871 + */ 1.872 + _renderBody: function() 1.873 + { 1.874 + let body = this.document.createElementNS(XHTML_NS, "span"); 1.875 + body.className = "message-body-wrapper message-body devtools-monospace"; 1.876 + 1.877 + let anchor, container = body; 1.878 + if (this._link || this._linkCallback) { 1.879 + container = anchor = this.document.createElementNS(XHTML_NS, "a"); 1.880 + anchor.href = this._link || "#"; 1.881 + anchor.draggable = false; 1.882 + this._addLinkCallback(anchor, this._linkCallback); 1.883 + body.appendChild(anchor); 1.884 + } 1.885 + 1.886 + if (typeof this._message == "function") { 1.887 + container.appendChild(this._message(this)); 1.888 + } else if (this._message instanceof Ci.nsIDOMNode) { 1.889 + container.appendChild(this._message); 1.890 + } else { 1.891 + container.textContent = this._message; 1.892 + } 1.893 + 1.894 + return body; 1.895 + }, 1.896 + 1.897 + /** 1.898 + * Render the repeat bubble DOM element part of the message. 1.899 + * @private 1.900 + * @return Element 1.901 + */ 1.902 + _renderRepeatNode: function() 1.903 + { 1.904 + if (!this._filterDuplicates) { 1.905 + return null; 1.906 + } 1.907 + 1.908 + let repeatNode = this.document.createElementNS(XHTML_NS, "span"); 1.909 + repeatNode.setAttribute("value", "1"); 1.910 + repeatNode.className = "message-repeats"; 1.911 + repeatNode.textContent = 1; 1.912 + repeatNode._uid = this.getRepeatID(); 1.913 + return repeatNode; 1.914 + }, 1.915 + 1.916 + /** 1.917 + * Render the message source location DOM element. 1.918 + * @private 1.919 + * @return Element 1.920 + */ 1.921 + _renderLocation: function() 1.922 + { 1.923 + if (!this.location) { 1.924 + return null; 1.925 + } 1.926 + 1.927 + let {url, line} = this.location; 1.928 + if (IGNORED_SOURCE_URLS.indexOf(url) != -1) { 1.929 + return null; 1.930 + } 1.931 + 1.932 + // The ConsoleOutput owner is a WebConsoleFrame instance from webconsole.js. 1.933 + // TODO: move createLocationNode() into this file when bug 778766 is fixed. 1.934 + return this.output.owner.createLocationNode(url, line); 1.935 + }, 1.936 +}); // Messages.Simple.prototype 1.937 + 1.938 + 1.939 +/** 1.940 + * The Extended message. 1.941 + * 1.942 + * @constructor 1.943 + * @extends Messages.Simple 1.944 + * @param array messagePieces 1.945 + * The message to display given as an array of elements. Each array 1.946 + * element can be a DOM node, function, ObjectActor, LongString or 1.947 + * a string. 1.948 + * @param object [options] 1.949 + * Options for rendering this message: 1.950 + * - quoteStrings: boolean that tells if you want strings to be wrapped 1.951 + * in quotes or not. 1.952 + */ 1.953 +Messages.Extended = function(messagePieces, options = {}) 1.954 +{ 1.955 + Messages.Simple.call(this, null, options); 1.956 + 1.957 + this._messagePieces = messagePieces; 1.958 + 1.959 + if ("quoteStrings" in options) { 1.960 + this._quoteStrings = options.quoteStrings; 1.961 + } 1.962 + 1.963 + this._repeatID.quoteStrings = this._quoteStrings; 1.964 + this._repeatID.messagePieces = messagePieces + ""; 1.965 + this._repeatID.actors = new Set(); // using a set to avoid duplicates 1.966 +}; 1.967 + 1.968 +Messages.Extended.prototype = Heritage.extend(Messages.Simple.prototype, 1.969 +{ 1.970 + /** 1.971 + * The message pieces displayed by this message instance. 1.972 + * @private 1.973 + * @type array 1.974 + */ 1.975 + _messagePieces: null, 1.976 + 1.977 + /** 1.978 + * Boolean that tells if the strings displayed in this message are wrapped. 1.979 + * @private 1.980 + * @type boolean 1.981 + */ 1.982 + _quoteStrings: true, 1.983 + 1.984 + getRepeatID: function() 1.985 + { 1.986 + if (this._repeatID.uid) { 1.987 + return JSON.stringify({ uid: this._repeatID.uid }); 1.988 + } 1.989 + 1.990 + // Sets are not stringified correctly. Temporarily switching to an array. 1.991 + let actors = this._repeatID.actors; 1.992 + this._repeatID.actors = [...actors]; 1.993 + let result = JSON.stringify(this._repeatID); 1.994 + this._repeatID.actors = actors; 1.995 + return result; 1.996 + }, 1.997 + 1.998 + render: function() 1.999 + { 1.1000 + let result = this.document.createDocumentFragment(); 1.1001 + 1.1002 + for (let i = 0; i < this._messagePieces.length; i++) { 1.1003 + let separator = i > 0 ? this._renderBodyPieceSeparator() : null; 1.1004 + if (separator) { 1.1005 + result.appendChild(separator); 1.1006 + } 1.1007 + 1.1008 + let piece = this._messagePieces[i]; 1.1009 + result.appendChild(this._renderBodyPiece(piece)); 1.1010 + } 1.1011 + 1.1012 + this._message = result; 1.1013 + this._messagePieces = null; 1.1014 + return Messages.Simple.prototype.render.call(this); 1.1015 + }, 1.1016 + 1.1017 + /** 1.1018 + * Render the separator between the pieces of the message. 1.1019 + * 1.1020 + * @private 1.1021 + * @return Element 1.1022 + */ 1.1023 + _renderBodyPieceSeparator: function() { return null; }, 1.1024 + 1.1025 + /** 1.1026 + * Render one piece/element of the message array. 1.1027 + * 1.1028 + * @private 1.1029 + * @param mixed piece 1.1030 + * Message element to display - this can be a LongString, ObjectActor, 1.1031 + * DOM node or a function to invoke. 1.1032 + * @return Element 1.1033 + */ 1.1034 + _renderBodyPiece: function(piece) 1.1035 + { 1.1036 + if (piece instanceof Ci.nsIDOMNode) { 1.1037 + return piece; 1.1038 + } 1.1039 + if (typeof piece == "function") { 1.1040 + return piece(this); 1.1041 + } 1.1042 + 1.1043 + return this._renderValueGrip(piece); 1.1044 + }, 1.1045 + 1.1046 + /** 1.1047 + * Render a grip that represents a value received from the server. This method 1.1048 + * picks the appropriate widget to render the value with. 1.1049 + * 1.1050 + * @private 1.1051 + * @param object grip 1.1052 + * The value grip received from the server. 1.1053 + * @param object options 1.1054 + * Options for displaying the value. Available options: 1.1055 + * - noStringQuotes - boolean that tells the renderer to not use quotes 1.1056 + * around strings. 1.1057 + * - concise - boolean that tells the renderer to compactly display the 1.1058 + * grip. This is typically set to true when the object needs to be 1.1059 + * displayed in an array preview, or as a property value in object 1.1060 + * previews, etc. 1.1061 + * @return DOMElement 1.1062 + * The DOM element that displays the given grip. 1.1063 + */ 1.1064 + _renderValueGrip: function(grip, options = {}) 1.1065 + { 1.1066 + let isPrimitive = VariablesView.isPrimitive({ value: grip }); 1.1067 + let isActorGrip = WebConsoleUtils.isActorGrip(grip); 1.1068 + let noStringQuotes = !this._quoteStrings; 1.1069 + if ("noStringQuotes" in options) { 1.1070 + noStringQuotes = options.noStringQuotes; 1.1071 + } 1.1072 + 1.1073 + if (isActorGrip) { 1.1074 + this._repeatID.actors.add(grip.actor); 1.1075 + 1.1076 + if (!isPrimitive) { 1.1077 + return this._renderObjectActor(grip, options); 1.1078 + } 1.1079 + if (grip.type == "longString") { 1.1080 + let widget = new Widgets.LongString(this, grip, options).render(); 1.1081 + return widget.element; 1.1082 + } 1.1083 + } 1.1084 + 1.1085 + let result = this.document.createElementNS(XHTML_NS, "span"); 1.1086 + if (isPrimitive) { 1.1087 + let className = this.getClassNameForValueGrip(grip); 1.1088 + if (className) { 1.1089 + result.className = className; 1.1090 + } 1.1091 + 1.1092 + result.textContent = VariablesView.getString(grip, { 1.1093 + noStringQuotes: noStringQuotes, 1.1094 + concise: options.concise, 1.1095 + }); 1.1096 + } else { 1.1097 + result.textContent = grip; 1.1098 + } 1.1099 + 1.1100 + return result; 1.1101 + }, 1.1102 + 1.1103 + /** 1.1104 + * Get a CodeMirror-compatible class name for a given value grip. 1.1105 + * 1.1106 + * @param object grip 1.1107 + * Value grip from the server. 1.1108 + * @return string 1.1109 + * The class name for the grip. 1.1110 + */ 1.1111 + getClassNameForValueGrip: function(grip) 1.1112 + { 1.1113 + let map = { 1.1114 + "number": "cm-number", 1.1115 + "longstring": "console-string", 1.1116 + "string": "console-string", 1.1117 + "regexp": "cm-string-2", 1.1118 + "boolean": "cm-atom", 1.1119 + "-infinity": "cm-atom", 1.1120 + "infinity": "cm-atom", 1.1121 + "null": "cm-atom", 1.1122 + "undefined": "cm-comment", 1.1123 + }; 1.1124 + 1.1125 + let className = map[typeof grip]; 1.1126 + if (!className && grip && grip.type) { 1.1127 + className = map[grip.type.toLowerCase()]; 1.1128 + } 1.1129 + if (!className && grip && grip.class) { 1.1130 + className = map[grip.class.toLowerCase()]; 1.1131 + } 1.1132 + 1.1133 + return className; 1.1134 + }, 1.1135 + 1.1136 + /** 1.1137 + * Display an object actor with the appropriate renderer. 1.1138 + * 1.1139 + * @private 1.1140 + * @param object objectActor 1.1141 + * The ObjectActor to display. 1.1142 + * @param object options 1.1143 + * Options to use for displaying the ObjectActor. 1.1144 + * @see this._renderValueGrip for the available options. 1.1145 + * @return DOMElement 1.1146 + * The DOM element that displays the object actor. 1.1147 + */ 1.1148 + _renderObjectActor: function(objectActor, options = {}) 1.1149 + { 1.1150 + let widget = null; 1.1151 + let {preview} = objectActor; 1.1152 + 1.1153 + if (preview && preview.kind) { 1.1154 + widget = Widgets.ObjectRenderers.byKind[preview.kind]; 1.1155 + } 1.1156 + 1.1157 + if (!widget || (widget.canRender && !widget.canRender(objectActor))) { 1.1158 + widget = Widgets.ObjectRenderers.byClass[objectActor.class]; 1.1159 + } 1.1160 + 1.1161 + if (!widget || (widget.canRender && !widget.canRender(objectActor))) { 1.1162 + widget = Widgets.JSObject; 1.1163 + } 1.1164 + 1.1165 + let instance = new widget(this, objectActor, options).render(); 1.1166 + return instance.element; 1.1167 + }, 1.1168 +}); // Messages.Extended.prototype 1.1169 + 1.1170 + 1.1171 + 1.1172 +/** 1.1173 + * The JavaScriptEvalOutput message. 1.1174 + * 1.1175 + * @constructor 1.1176 + * @extends Messages.Extended 1.1177 + * @param object evalResponse 1.1178 + * The evaluation response packet received from the server. 1.1179 + * @param string [errorMessage] 1.1180 + * Optional error message to display. 1.1181 + */ 1.1182 +Messages.JavaScriptEvalOutput = function(evalResponse, errorMessage) 1.1183 +{ 1.1184 + let severity = "log", msg, quoteStrings = true; 1.1185 + 1.1186 + if (errorMessage) { 1.1187 + severity = "error"; 1.1188 + msg = errorMessage; 1.1189 + quoteStrings = false; 1.1190 + } else { 1.1191 + msg = evalResponse.result; 1.1192 + } 1.1193 + 1.1194 + let options = { 1.1195 + className: "cm-s-mozilla", 1.1196 + timestamp: evalResponse.timestamp, 1.1197 + category: "output", 1.1198 + severity: severity, 1.1199 + quoteStrings: quoteStrings, 1.1200 + }; 1.1201 + Messages.Extended.call(this, [msg], options); 1.1202 +}; 1.1203 + 1.1204 +Messages.JavaScriptEvalOutput.prototype = Messages.Extended.prototype; 1.1205 + 1.1206 +/** 1.1207 + * The ConsoleGeneric message is used for console API calls. 1.1208 + * 1.1209 + * @constructor 1.1210 + * @extends Messages.Extended 1.1211 + * @param object packet 1.1212 + * The Console API call packet received from the server. 1.1213 + */ 1.1214 +Messages.ConsoleGeneric = function(packet) 1.1215 +{ 1.1216 + let options = { 1.1217 + className: "cm-s-mozilla", 1.1218 + timestamp: packet.timeStamp, 1.1219 + category: "webdev", 1.1220 + severity: CONSOLE_API_LEVELS_TO_SEVERITIES[packet.level], 1.1221 + private: packet.private, 1.1222 + filterDuplicates: true, 1.1223 + location: { 1.1224 + url: packet.filename, 1.1225 + line: packet.lineNumber, 1.1226 + }, 1.1227 + }; 1.1228 + 1.1229 + switch (packet.level) { 1.1230 + case "count": { 1.1231 + let counter = packet.counter, label = counter.label; 1.1232 + if (!label) { 1.1233 + label = l10n.getStr("noCounterLabel"); 1.1234 + } 1.1235 + Messages.Extended.call(this, [label+ ": " + counter.count], options); 1.1236 + break; 1.1237 + } 1.1238 + default: 1.1239 + Messages.Extended.call(this, packet.arguments, options); 1.1240 + break; 1.1241 + } 1.1242 + 1.1243 + this._repeatID.consoleApiLevel = packet.level; 1.1244 + this._repeatID.styles = packet.styles; 1.1245 + this._stacktrace = this._repeatID.stacktrace = packet.stacktrace; 1.1246 + this._styles = packet.styles || []; 1.1247 + 1.1248 + this._onClickCollapsible = this._onClickCollapsible.bind(this); 1.1249 +}; 1.1250 + 1.1251 +Messages.ConsoleGeneric.prototype = Heritage.extend(Messages.Extended.prototype, 1.1252 +{ 1.1253 + _styles: null, 1.1254 + _stacktrace: null, 1.1255 + 1.1256 + /** 1.1257 + * Tells if the message can be expanded/collapsed. 1.1258 + * @type boolean 1.1259 + */ 1.1260 + collapsible: false, 1.1261 + 1.1262 + /** 1.1263 + * Getter that tells if this message is collapsed - no details are shown. 1.1264 + * @type boolean 1.1265 + */ 1.1266 + get collapsed() { 1.1267 + return this.collapsible && this.element && !this.element.hasAttribute("open"); 1.1268 + }, 1.1269 + 1.1270 + _renderBodyPieceSeparator: function() 1.1271 + { 1.1272 + return this.document.createTextNode(" "); 1.1273 + }, 1.1274 + 1.1275 + render: function() 1.1276 + { 1.1277 + let msg = this.document.createElementNS(XHTML_NS, "span"); 1.1278 + msg.className = "message-body devtools-monospace"; 1.1279 + 1.1280 + this._renderBodyPieces(msg); 1.1281 + 1.1282 + let repeatNode = Messages.Simple.prototype._renderRepeatNode.call(this); 1.1283 + let location = Messages.Simple.prototype._renderLocation.call(this); 1.1284 + if (location) { 1.1285 + location.target = "jsdebugger"; 1.1286 + } 1.1287 + 1.1288 + let stack = null; 1.1289 + let twisty = null; 1.1290 + if (this._stacktrace && this._stacktrace.length > 0) { 1.1291 + stack = new Widgets.Stacktrace(this, this._stacktrace).render().element; 1.1292 + 1.1293 + twisty = this.document.createElementNS(XHTML_NS, "a"); 1.1294 + twisty.className = "theme-twisty"; 1.1295 + twisty.href = "#"; 1.1296 + twisty.title = l10n.getStr("messageToggleDetails"); 1.1297 + twisty.addEventListener("click", this._onClickCollapsible); 1.1298 + } 1.1299 + 1.1300 + let flex = this.document.createElementNS(XHTML_NS, "span"); 1.1301 + flex.className = "message-flex-body"; 1.1302 + 1.1303 + if (twisty) { 1.1304 + flex.appendChild(twisty); 1.1305 + } 1.1306 + 1.1307 + flex.appendChild(msg); 1.1308 + 1.1309 + if (repeatNode) { 1.1310 + flex.appendChild(repeatNode); 1.1311 + } 1.1312 + if (location) { 1.1313 + flex.appendChild(location); 1.1314 + } 1.1315 + 1.1316 + let result = this.document.createDocumentFragment(); 1.1317 + result.appendChild(flex); 1.1318 + 1.1319 + if (stack) { 1.1320 + result.appendChild(this.document.createTextNode("\n")); 1.1321 + result.appendChild(stack); 1.1322 + } 1.1323 + 1.1324 + this._message = result; 1.1325 + this._stacktrace = null; 1.1326 + 1.1327 + Messages.Simple.prototype.render.call(this); 1.1328 + 1.1329 + if (stack) { 1.1330 + this.collapsible = true; 1.1331 + this.element.setAttribute("collapsible", true); 1.1332 + 1.1333 + let icon = this.element.querySelector(".icon"); 1.1334 + icon.addEventListener("click", this._onClickCollapsible); 1.1335 + } 1.1336 + 1.1337 + return this; 1.1338 + }, 1.1339 + 1.1340 + _renderBody: function() 1.1341 + { 1.1342 + let body = Messages.Simple.prototype._renderBody.apply(this, arguments); 1.1343 + body.classList.remove("devtools-monospace", "message-body"); 1.1344 + return body; 1.1345 + }, 1.1346 + 1.1347 + _renderBodyPieces: function(container) 1.1348 + { 1.1349 + let lastStyle = null; 1.1350 + 1.1351 + for (let i = 0; i < this._messagePieces.length; i++) { 1.1352 + let separator = i > 0 ? this._renderBodyPieceSeparator() : null; 1.1353 + if (separator) { 1.1354 + container.appendChild(separator); 1.1355 + } 1.1356 + 1.1357 + let piece = this._messagePieces[i]; 1.1358 + let style = this._styles[i]; 1.1359 + 1.1360 + // No long string support. 1.1361 + if (style && typeof style == "string" ) { 1.1362 + lastStyle = this.cleanupStyle(style); 1.1363 + } 1.1364 + 1.1365 + container.appendChild(this._renderBodyPiece(piece, lastStyle)); 1.1366 + } 1.1367 + 1.1368 + this._messagePieces = null; 1.1369 + this._styles = null; 1.1370 + }, 1.1371 + 1.1372 + _renderBodyPiece: function(piece, style) 1.1373 + { 1.1374 + let elem = Messages.Extended.prototype._renderBodyPiece.call(this, piece); 1.1375 + let result = elem; 1.1376 + 1.1377 + if (style) { 1.1378 + if (elem.nodeType == Ci.nsIDOMNode.ELEMENT_NODE) { 1.1379 + elem.style = style; 1.1380 + } else { 1.1381 + let span = this.document.createElementNS(XHTML_NS, "span"); 1.1382 + span.style = style; 1.1383 + span.appendChild(elem); 1.1384 + result = span; 1.1385 + } 1.1386 + } 1.1387 + 1.1388 + return result; 1.1389 + }, 1.1390 + 1.1391 + // no-op for the message location and .repeats elements. 1.1392 + // |this.render()| handles customized message output. 1.1393 + _renderLocation: function() { }, 1.1394 + _renderRepeatNode: function() { }, 1.1395 + 1.1396 + /** 1.1397 + * Expand/collapse message details. 1.1398 + */ 1.1399 + toggleDetails: function() 1.1400 + { 1.1401 + let twisty = this.element.querySelector(".theme-twisty"); 1.1402 + if (this.element.hasAttribute("open")) { 1.1403 + this.element.removeAttribute("open"); 1.1404 + twisty.removeAttribute("open"); 1.1405 + } else { 1.1406 + this.element.setAttribute("open", true); 1.1407 + twisty.setAttribute("open", true); 1.1408 + } 1.1409 + }, 1.1410 + 1.1411 + /** 1.1412 + * The click event handler for the message expander arrow element. This method 1.1413 + * toggles the display of message details. 1.1414 + * 1.1415 + * @private 1.1416 + * @param nsIDOMEvent ev 1.1417 + * The DOM event object. 1.1418 + * @see this.toggleDetails() 1.1419 + */ 1.1420 + _onClickCollapsible: function(ev) 1.1421 + { 1.1422 + ev.preventDefault(); 1.1423 + this.toggleDetails(); 1.1424 + }, 1.1425 + 1.1426 + /** 1.1427 + * Given a style attribute value, return a cleaned up version of the string 1.1428 + * such that: 1.1429 + * 1.1430 + * - no external URL is allowed to load. See RE_CLEANUP_STYLES. 1.1431 + * - only some of the properties are allowed, based on a whitelist. See 1.1432 + * RE_ALLOWED_STYLES. 1.1433 + * 1.1434 + * @param string style 1.1435 + * The style string to cleanup. 1.1436 + * @return string 1.1437 + * The style value after cleanup. 1.1438 + */ 1.1439 + cleanupStyle: function(style) 1.1440 + { 1.1441 + for (let r of RE_CLEANUP_STYLES) { 1.1442 + style = style.replace(r, "notallowed"); 1.1443 + } 1.1444 + 1.1445 + let dummy = this.output._dummyElement; 1.1446 + if (!dummy) { 1.1447 + dummy = this.output._dummyElement = 1.1448 + this.document.createElementNS(XHTML_NS, "div"); 1.1449 + } 1.1450 + dummy.style = style; 1.1451 + 1.1452 + let toRemove = []; 1.1453 + for (let i = 0; i < dummy.style.length; i++) { 1.1454 + let prop = dummy.style[i]; 1.1455 + if (!RE_ALLOWED_STYLES.test(prop)) { 1.1456 + toRemove.push(prop); 1.1457 + } 1.1458 + } 1.1459 + 1.1460 + for (let prop of toRemove) { 1.1461 + dummy.style.removeProperty(prop); 1.1462 + } 1.1463 + 1.1464 + style = dummy.style.cssText; 1.1465 + 1.1466 + dummy.style = ""; 1.1467 + 1.1468 + return style; 1.1469 + }, 1.1470 +}); // Messages.ConsoleGeneric.prototype 1.1471 + 1.1472 +/** 1.1473 + * The ConsoleTrace message is used for console.trace() calls. 1.1474 + * 1.1475 + * @constructor 1.1476 + * @extends Messages.Simple 1.1477 + * @param object packet 1.1478 + * The Console API call packet received from the server. 1.1479 + */ 1.1480 +Messages.ConsoleTrace = function(packet) 1.1481 +{ 1.1482 + let options = { 1.1483 + className: "cm-s-mozilla", 1.1484 + timestamp: packet.timeStamp, 1.1485 + category: "webdev", 1.1486 + severity: CONSOLE_API_LEVELS_TO_SEVERITIES[packet.level], 1.1487 + private: packet.private, 1.1488 + filterDuplicates: true, 1.1489 + location: { 1.1490 + url: packet.filename, 1.1491 + line: packet.lineNumber, 1.1492 + }, 1.1493 + }; 1.1494 + 1.1495 + this._renderStack = this._renderStack.bind(this); 1.1496 + Messages.Simple.call(this, this._renderStack, options); 1.1497 + 1.1498 + this._repeatID.consoleApiLevel = packet.level; 1.1499 + this._stacktrace = this._repeatID.stacktrace = packet.stacktrace; 1.1500 + this._arguments = packet.arguments; 1.1501 +}; 1.1502 + 1.1503 +Messages.ConsoleTrace.prototype = Heritage.extend(Messages.Simple.prototype, 1.1504 +{ 1.1505 + /** 1.1506 + * Holds the stackframes received from the server. 1.1507 + * 1.1508 + * @private 1.1509 + * @type array 1.1510 + */ 1.1511 + _stacktrace: null, 1.1512 + 1.1513 + /** 1.1514 + * Holds the arguments the content script passed to the console.trace() 1.1515 + * method. This array is cleared when the message is initialized, and 1.1516 + * associated actors are released. 1.1517 + * 1.1518 + * @private 1.1519 + * @type array 1.1520 + */ 1.1521 + _arguments: null, 1.1522 + 1.1523 + init: function() 1.1524 + { 1.1525 + let result = Messages.Simple.prototype.init.apply(this, arguments); 1.1526 + 1.1527 + // We ignore console.trace() arguments. Release object actors. 1.1528 + if (Array.isArray(this._arguments)) { 1.1529 + for (let arg of this._arguments) { 1.1530 + if (WebConsoleUtils.isActorGrip(arg)) { 1.1531 + this.output._releaseObject(arg.actor); 1.1532 + } 1.1533 + } 1.1534 + } 1.1535 + this._arguments = null; 1.1536 + 1.1537 + return result; 1.1538 + }, 1.1539 + 1.1540 + render: function() 1.1541 + { 1.1542 + Messages.Simple.prototype.render.apply(this, arguments); 1.1543 + this.element.setAttribute("open", true); 1.1544 + return this; 1.1545 + }, 1.1546 + 1.1547 + /** 1.1548 + * Render the stack frames. 1.1549 + * 1.1550 + * @private 1.1551 + * @return DOMElement 1.1552 + */ 1.1553 + _renderStack: function() 1.1554 + { 1.1555 + let cmvar = this.document.createElementNS(XHTML_NS, "span"); 1.1556 + cmvar.className = "cm-variable"; 1.1557 + cmvar.textContent = "console"; 1.1558 + 1.1559 + let cmprop = this.document.createElementNS(XHTML_NS, "span"); 1.1560 + cmprop.className = "cm-property"; 1.1561 + cmprop.textContent = "trace"; 1.1562 + 1.1563 + let title = this.document.createElementNS(XHTML_NS, "span"); 1.1564 + title.className = "message-body devtools-monospace"; 1.1565 + title.appendChild(cmvar); 1.1566 + title.appendChild(this.document.createTextNode(".")); 1.1567 + title.appendChild(cmprop); 1.1568 + title.appendChild(this.document.createTextNode("():")); 1.1569 + 1.1570 + let repeatNode = Messages.Simple.prototype._renderRepeatNode.call(this); 1.1571 + let location = Messages.Simple.prototype._renderLocation.call(this); 1.1572 + if (location) { 1.1573 + location.target = "jsdebugger"; 1.1574 + } 1.1575 + 1.1576 + let widget = new Widgets.Stacktrace(this, this._stacktrace).render(); 1.1577 + 1.1578 + let body = this.document.createElementNS(XHTML_NS, "span"); 1.1579 + body.className = "message-flex-body"; 1.1580 + body.appendChild(title); 1.1581 + if (repeatNode) { 1.1582 + body.appendChild(repeatNode); 1.1583 + } 1.1584 + if (location) { 1.1585 + body.appendChild(location); 1.1586 + } 1.1587 + body.appendChild(this.document.createTextNode("\n")); 1.1588 + 1.1589 + let frag = this.document.createDocumentFragment(); 1.1590 + frag.appendChild(body); 1.1591 + frag.appendChild(widget.element); 1.1592 + 1.1593 + return frag; 1.1594 + }, 1.1595 + 1.1596 + _renderBody: function() 1.1597 + { 1.1598 + let body = Messages.Simple.prototype._renderBody.apply(this, arguments); 1.1599 + body.classList.remove("devtools-monospace", "message-body"); 1.1600 + return body; 1.1601 + }, 1.1602 + 1.1603 + // no-op for the message location and .repeats elements. 1.1604 + // |this._renderStack| handles customized message output. 1.1605 + _renderLocation: function() { }, 1.1606 + _renderRepeatNode: function() { }, 1.1607 +}); // Messages.ConsoleTrace.prototype 1.1608 + 1.1609 +let Widgets = {}; 1.1610 + 1.1611 +/** 1.1612 + * The base widget class. 1.1613 + * 1.1614 + * @constructor 1.1615 + * @param object message 1.1616 + * The owning message. 1.1617 + */ 1.1618 +Widgets.BaseWidget = function(message) 1.1619 +{ 1.1620 + this.message = message; 1.1621 +}; 1.1622 + 1.1623 +Widgets.BaseWidget.prototype = { 1.1624 + /** 1.1625 + * The owning message object. 1.1626 + * @type object 1.1627 + */ 1.1628 + message: null, 1.1629 + 1.1630 + /** 1.1631 + * The DOM element of the rendered widget. 1.1632 + * @type Element 1.1633 + */ 1.1634 + element: null, 1.1635 + 1.1636 + /** 1.1637 + * Getter for the DOM document that holds the output. 1.1638 + * @type Document 1.1639 + */ 1.1640 + get document() { 1.1641 + return this.message.document; 1.1642 + }, 1.1643 + 1.1644 + /** 1.1645 + * The ConsoleOutput instance that owns this widget instance. 1.1646 + */ 1.1647 + get output() { 1.1648 + return this.message.output; 1.1649 + }, 1.1650 + 1.1651 + /** 1.1652 + * Render the widget DOM element. 1.1653 + * @return this 1.1654 + */ 1.1655 + render: function() { }, 1.1656 + 1.1657 + /** 1.1658 + * Destroy this widget instance. 1.1659 + */ 1.1660 + destroy: function() { }, 1.1661 + 1.1662 + /** 1.1663 + * Helper for creating DOM elements for widgets. 1.1664 + * 1.1665 + * Usage: 1.1666 + * this.el("tag#id.class.names"); // create element "tag" with ID "id" and 1.1667 + * two class names, .class and .names. 1.1668 + * 1.1669 + * this.el("span", { attr1: "value1", ... }) // second argument can be an 1.1670 + * object that holds element attributes and values for the new DOM element. 1.1671 + * 1.1672 + * this.el("p", { attr1: "value1", ... }, "text content"); // the third 1.1673 + * argument can include the default .textContent of the new DOM element. 1.1674 + * 1.1675 + * this.el("p", "text content"); // if the second argument is not an object, 1.1676 + * it will be used as .textContent for the new DOM element. 1.1677 + * 1.1678 + * @param string tagNameIdAndClasses 1.1679 + * Tag name for the new element, optionally followed by an ID and/or 1.1680 + * class names. Examples: "span", "div#fooId", "div.class.names", 1.1681 + * "p#id.class". 1.1682 + * @param string|object [attributesOrTextContent] 1.1683 + * If this argument is an object it will be used to set the attributes 1.1684 + * of the new DOM element. Otherwise, the value becomes the 1.1685 + * .textContent of the new DOM element. 1.1686 + * @param string [textContent] 1.1687 + * If this argument is provided the value is used as the textContent of 1.1688 + * the new DOM element. 1.1689 + * @return DOMElement 1.1690 + * The new DOM element. 1.1691 + */ 1.1692 + el: function(tagNameIdAndClasses) 1.1693 + { 1.1694 + let attrs, text; 1.1695 + if (typeof arguments[1] == "object") { 1.1696 + attrs = arguments[1]; 1.1697 + text = arguments[2]; 1.1698 + } else { 1.1699 + text = arguments[1]; 1.1700 + } 1.1701 + 1.1702 + let tagName = tagNameIdAndClasses.split(/#|\./)[0]; 1.1703 + 1.1704 + let elem = this.document.createElementNS(XHTML_NS, tagName); 1.1705 + for (let name of Object.keys(attrs || {})) { 1.1706 + elem.setAttribute(name, attrs[name]); 1.1707 + } 1.1708 + if (text !== undefined && text !== null) { 1.1709 + elem.textContent = text; 1.1710 + } 1.1711 + 1.1712 + let idAndClasses = tagNameIdAndClasses.match(/([#.][^#.]+)/g); 1.1713 + for (let idOrClass of (idAndClasses || [])) { 1.1714 + if (idOrClass.charAt(0) == "#") { 1.1715 + elem.id = idOrClass.substr(1); 1.1716 + } else { 1.1717 + elem.classList.add(idOrClass.substr(1)); 1.1718 + } 1.1719 + } 1.1720 + 1.1721 + return elem; 1.1722 + }, 1.1723 +}; 1.1724 + 1.1725 +/** 1.1726 + * The timestamp widget. 1.1727 + * 1.1728 + * @constructor 1.1729 + * @param object message 1.1730 + * The owning message. 1.1731 + * @param number timestamp 1.1732 + * The UNIX timestamp to display. 1.1733 + */ 1.1734 +Widgets.MessageTimestamp = function(message, timestamp) 1.1735 +{ 1.1736 + Widgets.BaseWidget.call(this, message); 1.1737 + this.timestamp = timestamp; 1.1738 +}; 1.1739 + 1.1740 +Widgets.MessageTimestamp.prototype = Heritage.extend(Widgets.BaseWidget.prototype, 1.1741 +{ 1.1742 + /** 1.1743 + * The UNIX timestamp. 1.1744 + * @type number 1.1745 + */ 1.1746 + timestamp: 0, 1.1747 + 1.1748 + render: function() 1.1749 + { 1.1750 + if (this.element) { 1.1751 + return this; 1.1752 + } 1.1753 + 1.1754 + this.element = this.document.createElementNS(XHTML_NS, "span"); 1.1755 + this.element.className = "timestamp devtools-monospace"; 1.1756 + this.element.textContent = l10n.timestampString(this.timestamp) + " "; 1.1757 + 1.1758 + return this; 1.1759 + }, 1.1760 +}); // Widgets.MessageTimestamp.prototype 1.1761 + 1.1762 + 1.1763 +/** 1.1764 + * Widget used for displaying ObjectActors that have no specialised renderers. 1.1765 + * 1.1766 + * @constructor 1.1767 + * @param object message 1.1768 + * The owning message. 1.1769 + * @param object objectActor 1.1770 + * The ObjectActor to display. 1.1771 + * @param object [options] 1.1772 + * Options for displaying the given ObjectActor. See 1.1773 + * Messages.Extended.prototype._renderValueGrip for the available 1.1774 + * options. 1.1775 + */ 1.1776 +Widgets.JSObject = function(message, objectActor, options = {}) 1.1777 +{ 1.1778 + Widgets.BaseWidget.call(this, message); 1.1779 + this.objectActor = objectActor; 1.1780 + this.options = options; 1.1781 + this._onClick = this._onClick.bind(this); 1.1782 +}; 1.1783 + 1.1784 +Widgets.JSObject.prototype = Heritage.extend(Widgets.BaseWidget.prototype, 1.1785 +{ 1.1786 + /** 1.1787 + * The ObjectActor displayed by the widget. 1.1788 + * @type object 1.1789 + */ 1.1790 + objectActor: null, 1.1791 + 1.1792 + render: function() 1.1793 + { 1.1794 + if (!this.element) { 1.1795 + this._render(); 1.1796 + } 1.1797 + 1.1798 + return this; 1.1799 + }, 1.1800 + 1.1801 + _render: function() 1.1802 + { 1.1803 + let str = VariablesView.getString(this.objectActor, this.options); 1.1804 + let className = this.message.getClassNameForValueGrip(this.objectActor); 1.1805 + if (!className && this.objectActor.class == "Object") { 1.1806 + className = "cm-variable"; 1.1807 + } 1.1808 + 1.1809 + this.element = this._anchor(str, { className: className }); 1.1810 + }, 1.1811 + 1.1812 + /** 1.1813 + * Render an anchor with a given text content and link. 1.1814 + * 1.1815 + * @private 1.1816 + * @param string text 1.1817 + * Text to show in the anchor. 1.1818 + * @param object [options] 1.1819 + * Available options: 1.1820 + * - onClick (function): "click" event handler.By default a click on 1.1821 + * the anchor opens the variables view for the current object actor 1.1822 + * (this.objectActor). 1.1823 + * - href (string): if given the string is used as a link, and clicks 1.1824 + * on the anchor open the link in a new tab. 1.1825 + * - appendTo (DOMElement): append the element to the given DOM 1.1826 + * element. If not provided, the anchor is appended to |this.element| 1.1827 + * if it is available. If |appendTo| is provided and if it is a falsy 1.1828 + * value, the anchor is not appended to any element. 1.1829 + * @return DOMElement 1.1830 + * The DOM element of the new anchor. 1.1831 + */ 1.1832 + _anchor: function(text, options = {}) 1.1833 + { 1.1834 + if (!options.onClick && !options.href) { 1.1835 + options.onClick = this._onClick; 1.1836 + } 1.1837 + 1.1838 + let anchor = this.el("a", { 1.1839 + class: options.className, 1.1840 + draggable: false, 1.1841 + href: options.href || "#", 1.1842 + }, text); 1.1843 + 1.1844 + this.message._addLinkCallback(anchor, !options.href ? options.onClick : null); 1.1845 + 1.1846 + if (options.appendTo) { 1.1847 + options.appendTo.appendChild(anchor); 1.1848 + } else if (!("appendTo" in options) && this.element) { 1.1849 + this.element.appendChild(anchor); 1.1850 + } 1.1851 + 1.1852 + return anchor; 1.1853 + }, 1.1854 + 1.1855 + /** 1.1856 + * The click event handler for objects shown inline. 1.1857 + * @private 1.1858 + */ 1.1859 + _onClick: function() 1.1860 + { 1.1861 + this.output.openVariablesView({ 1.1862 + label: VariablesView.getString(this.objectActor, { concise: true }), 1.1863 + objectActor: this.objectActor, 1.1864 + autofocus: true, 1.1865 + }); 1.1866 + }, 1.1867 + 1.1868 + /** 1.1869 + * Add a string to the message. 1.1870 + * 1.1871 + * @private 1.1872 + * @param string str 1.1873 + * String to add. 1.1874 + * @param DOMElement [target = this.element] 1.1875 + * Optional DOM element to append the string to. The default is 1.1876 + * this.element. 1.1877 + */ 1.1878 + _text: function(str, target = this.element) 1.1879 + { 1.1880 + target.appendChild(this.document.createTextNode(str)); 1.1881 + }, 1.1882 +}); // Widgets.JSObject.prototype 1.1883 + 1.1884 +Widgets.ObjectRenderers = {}; 1.1885 +Widgets.ObjectRenderers.byKind = {}; 1.1886 +Widgets.ObjectRenderers.byClass = {}; 1.1887 + 1.1888 +/** 1.1889 + * Add an object renderer. 1.1890 + * 1.1891 + * @param object obj 1.1892 + * An object that represents the renderer. Properties: 1.1893 + * - byClass (string, optional): this renderer will be used for the given 1.1894 + * object class. 1.1895 + * - byKind (string, optional): this renderer will be used for the given 1.1896 + * object kind. 1.1897 + * One of byClass or byKind must be provided. 1.1898 + * - extends (object, optional): the renderer object extends the given 1.1899 + * object. Default: Widgets.JSObject. 1.1900 + * - canRender (function, optional): this method is invoked when 1.1901 + * a candidate object needs to be displayed. The method is invoked as 1.1902 + * a static method, as such, none of the properties of the renderer 1.1903 + * object will be available. You get one argument: the object actor grip 1.1904 + * received from the server. If the method returns true, then this 1.1905 + * renderer is used for displaying the object, otherwise not. 1.1906 + * - initialize (function, optional): the constructor of the renderer 1.1907 + * widget. This function is invoked with the following arguments: the 1.1908 + * owner message object instance, the object actor grip to display, and 1.1909 + * an options object. See Messages.Extended.prototype._renderValueGrip() 1.1910 + * for details about the options object. 1.1911 + * - render (function, required): the method that displays the given 1.1912 + * object actor. 1.1913 + */ 1.1914 +Widgets.ObjectRenderers.add = function(obj) 1.1915 +{ 1.1916 + let extendObj = obj.extends || Widgets.JSObject; 1.1917 + 1.1918 + let constructor = function() { 1.1919 + if (obj.initialize) { 1.1920 + obj.initialize.apply(this, arguments); 1.1921 + } else { 1.1922 + extendObj.apply(this, arguments); 1.1923 + } 1.1924 + }; 1.1925 + 1.1926 + let proto = WebConsoleUtils.cloneObject(obj, false, function(key) { 1.1927 + if (key == "initialize" || key == "canRender" || 1.1928 + (key == "render" && extendObj === Widgets.JSObject)) { 1.1929 + return false; 1.1930 + } 1.1931 + return true; 1.1932 + }); 1.1933 + 1.1934 + if (extendObj === Widgets.JSObject) { 1.1935 + proto._render = obj.render; 1.1936 + } 1.1937 + 1.1938 + constructor.canRender = obj.canRender; 1.1939 + constructor.prototype = Heritage.extend(extendObj.prototype, proto); 1.1940 + 1.1941 + if (obj.byClass) { 1.1942 + Widgets.ObjectRenderers.byClass[obj.byClass] = constructor; 1.1943 + } else if (obj.byKind) { 1.1944 + Widgets.ObjectRenderers.byKind[obj.byKind] = constructor; 1.1945 + } else { 1.1946 + throw new Error("You are adding an object renderer without any byClass or " + 1.1947 + "byKind property."); 1.1948 + } 1.1949 +}; 1.1950 + 1.1951 + 1.1952 +/** 1.1953 + * The widget used for displaying Date objects. 1.1954 + */ 1.1955 +Widgets.ObjectRenderers.add({ 1.1956 + byClass: "Date", 1.1957 + 1.1958 + render: function() 1.1959 + { 1.1960 + let {preview} = this.objectActor; 1.1961 + this.element = this.el("span.class-" + this.objectActor.class); 1.1962 + 1.1963 + let anchorText = this.objectActor.class; 1.1964 + let anchorClass = "cm-variable"; 1.1965 + if ("timestamp" in preview && typeof preview.timestamp != "number") { 1.1966 + anchorText = new Date(preview.timestamp).toString(); // invalid date 1.1967 + anchorClass = ""; 1.1968 + } 1.1969 + 1.1970 + this._anchor(anchorText, { className: anchorClass }); 1.1971 + 1.1972 + if (!("timestamp" in preview) || typeof preview.timestamp != "number") { 1.1973 + return; 1.1974 + } 1.1975 + 1.1976 + this._text(" "); 1.1977 + 1.1978 + let elem = this.el("span.cm-string-2", new Date(preview.timestamp).toISOString()); 1.1979 + this.element.appendChild(elem); 1.1980 + }, 1.1981 +}); 1.1982 + 1.1983 +/** 1.1984 + * The widget used for displaying Function objects. 1.1985 + */ 1.1986 +Widgets.ObjectRenderers.add({ 1.1987 + byClass: "Function", 1.1988 + 1.1989 + render: function() 1.1990 + { 1.1991 + let grip = this.objectActor; 1.1992 + this.element = this.el("span.class-" + this.objectActor.class); 1.1993 + 1.1994 + // TODO: Bug 948484 - support arrow functions and ES6 generators 1.1995 + let name = grip.userDisplayName || grip.displayName || grip.name || ""; 1.1996 + name = VariablesView.getString(name, { noStringQuotes: true }); 1.1997 + 1.1998 + let str = this.options.concise ? name || "function " : "function " + name; 1.1999 + 1.2000 + if (this.options.concise) { 1.2001 + this._anchor(name || "function", { 1.2002 + className: name ? "cm-variable" : "cm-keyword", 1.2003 + }); 1.2004 + if (!name) { 1.2005 + this._text(" "); 1.2006 + } 1.2007 + } else if (name) { 1.2008 + this.element.appendChild(this.el("span.cm-keyword", "function")); 1.2009 + this._text(" "); 1.2010 + this._anchor(name, { className: "cm-variable" }); 1.2011 + } else { 1.2012 + this._anchor("function", { className: "cm-keyword" }); 1.2013 + this._text(" "); 1.2014 + } 1.2015 + 1.2016 + this._text("("); 1.2017 + 1.2018 + // TODO: Bug 948489 - Support functions with destructured parameters and 1.2019 + // rest parameters 1.2020 + let params = grip.parameterNames || []; 1.2021 + let shown = 0; 1.2022 + for (let param of params) { 1.2023 + if (shown > 0) { 1.2024 + this._text(", "); 1.2025 + } 1.2026 + this.element.appendChild(this.el("span.cm-def", param)); 1.2027 + shown++; 1.2028 + } 1.2029 + 1.2030 + this._text(")"); 1.2031 + }, 1.2032 +}); // Widgets.ObjectRenderers.byClass.Function 1.2033 + 1.2034 +/** 1.2035 + * The widget used for displaying ArrayLike objects. 1.2036 + */ 1.2037 +Widgets.ObjectRenderers.add({ 1.2038 + byKind: "ArrayLike", 1.2039 + 1.2040 + render: function() 1.2041 + { 1.2042 + let {preview} = this.objectActor; 1.2043 + let {items} = preview; 1.2044 + this.element = this.el("span.kind-" + preview.kind); 1.2045 + 1.2046 + this._anchor(this.objectActor.class, { className: "cm-variable" }); 1.2047 + 1.2048 + if (!items || this.options.concise) { 1.2049 + this._text("["); 1.2050 + this.element.appendChild(this.el("span.cm-number", preview.length)); 1.2051 + this._text("]"); 1.2052 + return this; 1.2053 + } 1.2054 + 1.2055 + this._text(" [ "); 1.2056 + 1.2057 + let shown = 0; 1.2058 + for (let item of items) { 1.2059 + if (shown > 0) { 1.2060 + this._text(", "); 1.2061 + } 1.2062 + 1.2063 + if (item !== null) { 1.2064 + let elem = this.message._renderValueGrip(item, { concise: true }); 1.2065 + this.element.appendChild(elem); 1.2066 + } else if (shown == (items.length - 1)) { 1.2067 + this._text(", "); 1.2068 + } 1.2069 + 1.2070 + shown++; 1.2071 + } 1.2072 + 1.2073 + if (shown < preview.length) { 1.2074 + this._text(", "); 1.2075 + 1.2076 + let n = preview.length - shown; 1.2077 + let str = VariablesView.stringifiers._getNMoreString(n); 1.2078 + this._anchor(str); 1.2079 + } 1.2080 + 1.2081 + this._text(" ]"); 1.2082 + }, 1.2083 +}); // Widgets.ObjectRenderers.byKind.ArrayLike 1.2084 + 1.2085 +/** 1.2086 + * The widget used for displaying MapLike objects. 1.2087 + */ 1.2088 +Widgets.ObjectRenderers.add({ 1.2089 + byKind: "MapLike", 1.2090 + 1.2091 + render: function() 1.2092 + { 1.2093 + let {preview} = this.objectActor; 1.2094 + let {entries} = preview; 1.2095 + 1.2096 + let container = this.element = this.el("span.kind-" + preview.kind); 1.2097 + this._anchor(this.objectActor.class, { className: "cm-variable" }); 1.2098 + 1.2099 + if (!entries || this.options.concise) { 1.2100 + if (typeof preview.size == "number") { 1.2101 + this._text("["); 1.2102 + container.appendChild(this.el("span.cm-number", preview.size)); 1.2103 + this._text("]"); 1.2104 + } 1.2105 + return; 1.2106 + } 1.2107 + 1.2108 + this._text(" { "); 1.2109 + 1.2110 + let shown = 0; 1.2111 + for (let [key, value] of entries) { 1.2112 + if (shown > 0) { 1.2113 + this._text(", "); 1.2114 + } 1.2115 + 1.2116 + let keyElem = this.message._renderValueGrip(key, { 1.2117 + concise: true, 1.2118 + noStringQuotes: true, 1.2119 + }); 1.2120 + 1.2121 + // Strings are property names. 1.2122 + if (keyElem.classList && keyElem.classList.contains("console-string")) { 1.2123 + keyElem.classList.remove("console-string"); 1.2124 + keyElem.classList.add("cm-property"); 1.2125 + } 1.2126 + 1.2127 + container.appendChild(keyElem); 1.2128 + 1.2129 + this._text(": "); 1.2130 + 1.2131 + let valueElem = this.message._renderValueGrip(value, { concise: true }); 1.2132 + container.appendChild(valueElem); 1.2133 + 1.2134 + shown++; 1.2135 + } 1.2136 + 1.2137 + if (typeof preview.size == "number" && shown < preview.size) { 1.2138 + this._text(", "); 1.2139 + 1.2140 + let n = preview.size - shown; 1.2141 + let str = VariablesView.stringifiers._getNMoreString(n); 1.2142 + this._anchor(str); 1.2143 + } 1.2144 + 1.2145 + this._text(" }"); 1.2146 + }, 1.2147 +}); // Widgets.ObjectRenderers.byKind.MapLike 1.2148 + 1.2149 +/** 1.2150 + * The widget used for displaying objects with a URL. 1.2151 + */ 1.2152 +Widgets.ObjectRenderers.add({ 1.2153 + byKind: "ObjectWithURL", 1.2154 + 1.2155 + render: function() 1.2156 + { 1.2157 + this.element = this._renderElement(this.objectActor, 1.2158 + this.objectActor.preview.url); 1.2159 + }, 1.2160 + 1.2161 + _renderElement: function(objectActor, url) 1.2162 + { 1.2163 + let container = this.el("span.kind-" + objectActor.preview.kind); 1.2164 + 1.2165 + this._anchor(objectActor.class, { 1.2166 + className: "cm-variable", 1.2167 + appendTo: container, 1.2168 + }); 1.2169 + 1.2170 + if (!VariablesView.isFalsy({ value: url })) { 1.2171 + this._text(" \u2192 ", container); 1.2172 + let shortUrl = WebConsoleUtils.abbreviateSourceURL(url, { 1.2173 + onlyCropQuery: !this.options.concise 1.2174 + }); 1.2175 + this._anchor(shortUrl, { href: url, appendTo: container }); 1.2176 + } 1.2177 + 1.2178 + return container; 1.2179 + }, 1.2180 +}); // Widgets.ObjectRenderers.byKind.ObjectWithURL 1.2181 + 1.2182 +/** 1.2183 + * The widget used for displaying objects with a string next to them. 1.2184 + */ 1.2185 +Widgets.ObjectRenderers.add({ 1.2186 + byKind: "ObjectWithText", 1.2187 + 1.2188 + render: function() 1.2189 + { 1.2190 + let {preview} = this.objectActor; 1.2191 + this.element = this.el("span.kind-" + preview.kind); 1.2192 + 1.2193 + this._anchor(this.objectActor.class, { className: "cm-variable" }); 1.2194 + 1.2195 + if (!this.options.concise) { 1.2196 + this._text(" "); 1.2197 + this.element.appendChild(this.el("span.console-string", 1.2198 + VariablesView.getString(preview.text))); 1.2199 + } 1.2200 + }, 1.2201 +}); 1.2202 + 1.2203 +/** 1.2204 + * The widget used for displaying DOM event previews. 1.2205 + */ 1.2206 +Widgets.ObjectRenderers.add({ 1.2207 + byKind: "DOMEvent", 1.2208 + 1.2209 + render: function() 1.2210 + { 1.2211 + let {preview} = this.objectActor; 1.2212 + 1.2213 + let container = this.element = this.el("span.kind-" + preview.kind); 1.2214 + 1.2215 + this._anchor(preview.type || this.objectActor.class, 1.2216 + { className: "cm-variable" }); 1.2217 + 1.2218 + if (this.options.concise) { 1.2219 + return; 1.2220 + } 1.2221 + 1.2222 + if (preview.eventKind == "key" && preview.modifiers && 1.2223 + preview.modifiers.length) { 1.2224 + this._text(" "); 1.2225 + 1.2226 + let mods = 0; 1.2227 + for (let mod of preview.modifiers) { 1.2228 + if (mods > 0) { 1.2229 + this._text("-"); 1.2230 + } 1.2231 + container.appendChild(this.el("span.cm-keyword", mod)); 1.2232 + mods++; 1.2233 + } 1.2234 + } 1.2235 + 1.2236 + this._text(" { "); 1.2237 + 1.2238 + let shown = 0; 1.2239 + if (preview.target) { 1.2240 + container.appendChild(this.el("span.cm-property", "target")); 1.2241 + this._text(": "); 1.2242 + let target = this.message._renderValueGrip(preview.target, { concise: true }); 1.2243 + container.appendChild(target); 1.2244 + shown++; 1.2245 + } 1.2246 + 1.2247 + for (let key of Object.keys(preview.properties || {})) { 1.2248 + if (shown > 0) { 1.2249 + this._text(", "); 1.2250 + } 1.2251 + 1.2252 + container.appendChild(this.el("span.cm-property", key)); 1.2253 + this._text(": "); 1.2254 + 1.2255 + let value = preview.properties[key]; 1.2256 + let valueElem = this.message._renderValueGrip(value, { concise: true }); 1.2257 + container.appendChild(valueElem); 1.2258 + 1.2259 + shown++; 1.2260 + } 1.2261 + 1.2262 + this._text(" }"); 1.2263 + }, 1.2264 +}); // Widgets.ObjectRenderers.byKind.DOMEvent 1.2265 + 1.2266 +/** 1.2267 + * The widget used for displaying DOM node previews. 1.2268 + */ 1.2269 +Widgets.ObjectRenderers.add({ 1.2270 + byKind: "DOMNode", 1.2271 + 1.2272 + canRender: function(objectActor) { 1.2273 + let {preview} = objectActor; 1.2274 + if (!preview) { 1.2275 + return false; 1.2276 + } 1.2277 + 1.2278 + switch (preview.nodeType) { 1.2279 + case Ci.nsIDOMNode.DOCUMENT_NODE: 1.2280 + case Ci.nsIDOMNode.ATTRIBUTE_NODE: 1.2281 + case Ci.nsIDOMNode.TEXT_NODE: 1.2282 + case Ci.nsIDOMNode.COMMENT_NODE: 1.2283 + case Ci.nsIDOMNode.DOCUMENT_FRAGMENT_NODE: 1.2284 + case Ci.nsIDOMNode.ELEMENT_NODE: 1.2285 + return true; 1.2286 + default: 1.2287 + return false; 1.2288 + } 1.2289 + }, 1.2290 + 1.2291 + render: function() 1.2292 + { 1.2293 + switch (this.objectActor.preview.nodeType) { 1.2294 + case Ci.nsIDOMNode.DOCUMENT_NODE: 1.2295 + this._renderDocumentNode(); 1.2296 + break; 1.2297 + case Ci.nsIDOMNode.ATTRIBUTE_NODE: { 1.2298 + let {preview} = this.objectActor; 1.2299 + this.element = this.el("span.attributeNode.kind-" + preview.kind); 1.2300 + let attr = this._renderAttributeNode(preview.nodeName, preview.value, true); 1.2301 + this.element.appendChild(attr); 1.2302 + break; 1.2303 + } 1.2304 + case Ci.nsIDOMNode.TEXT_NODE: 1.2305 + this._renderTextNode(); 1.2306 + break; 1.2307 + case Ci.nsIDOMNode.COMMENT_NODE: 1.2308 + this._renderCommentNode(); 1.2309 + break; 1.2310 + case Ci.nsIDOMNode.DOCUMENT_FRAGMENT_NODE: 1.2311 + this._renderDocumentFragmentNode(); 1.2312 + break; 1.2313 + case Ci.nsIDOMNode.ELEMENT_NODE: 1.2314 + this._renderElementNode(); 1.2315 + break; 1.2316 + default: 1.2317 + throw new Error("Unsupported nodeType: " + preview.nodeType); 1.2318 + } 1.2319 + }, 1.2320 + 1.2321 + _renderDocumentNode: function() 1.2322 + { 1.2323 + let fn = Widgets.ObjectRenderers.byKind.ObjectWithURL.prototype._renderElement; 1.2324 + this.element = fn.call(this, this.objectActor, 1.2325 + this.objectActor.preview.location); 1.2326 + this.element.classList.add("documentNode"); 1.2327 + }, 1.2328 + 1.2329 + _renderAttributeNode: function(nodeName, nodeValue, addLink) 1.2330 + { 1.2331 + let value = VariablesView.getString(nodeValue, { noStringQuotes: true }); 1.2332 + 1.2333 + let fragment = this.document.createDocumentFragment(); 1.2334 + if (addLink) { 1.2335 + this._anchor(nodeName, { className: "cm-attribute", appendTo: fragment }); 1.2336 + } else { 1.2337 + fragment.appendChild(this.el("span.cm-attribute", nodeName)); 1.2338 + } 1.2339 + 1.2340 + this._text("=", fragment); 1.2341 + fragment.appendChild(this.el("span.console-string", 1.2342 + '"' + escapeHTML(value) + '"')); 1.2343 + 1.2344 + return fragment; 1.2345 + }, 1.2346 + 1.2347 + _renderTextNode: function() 1.2348 + { 1.2349 + let {preview} = this.objectActor; 1.2350 + this.element = this.el("span.textNode.kind-" + preview.kind); 1.2351 + 1.2352 + this._anchor(preview.nodeName, { className: "cm-variable" }); 1.2353 + this._text(" "); 1.2354 + 1.2355 + let text = VariablesView.getString(preview.textContent); 1.2356 + this.element.appendChild(this.el("span.console-string", text)); 1.2357 + }, 1.2358 + 1.2359 + _renderCommentNode: function() 1.2360 + { 1.2361 + let {preview} = this.objectActor; 1.2362 + let comment = "<!-- " + VariablesView.getString(preview.textContent, { 1.2363 + noStringQuotes: true, 1.2364 + }) + " -->"; 1.2365 + 1.2366 + this.element = this._anchor(comment, { 1.2367 + className: "kind-" + preview.kind + " commentNode cm-comment", 1.2368 + }); 1.2369 + }, 1.2370 + 1.2371 + _renderDocumentFragmentNode: function() 1.2372 + { 1.2373 + let {preview} = this.objectActor; 1.2374 + let {childNodes} = preview; 1.2375 + let container = this.element = this.el("span.documentFragmentNode.kind-" + 1.2376 + preview.kind); 1.2377 + 1.2378 + this._anchor(this.objectActor.class, { className: "cm-variable" }); 1.2379 + 1.2380 + if (!childNodes || this.options.concise) { 1.2381 + this._text("["); 1.2382 + container.appendChild(this.el("span.cm-number", preview.childNodesLength)); 1.2383 + this._text("]"); 1.2384 + return; 1.2385 + } 1.2386 + 1.2387 + this._text(" [ "); 1.2388 + 1.2389 + let shown = 0; 1.2390 + for (let item of childNodes) { 1.2391 + if (shown > 0) { 1.2392 + this._text(", "); 1.2393 + } 1.2394 + 1.2395 + let elem = this.message._renderValueGrip(item, { concise: true }); 1.2396 + container.appendChild(elem); 1.2397 + shown++; 1.2398 + } 1.2399 + 1.2400 + if (shown < preview.childNodesLength) { 1.2401 + this._text(", "); 1.2402 + 1.2403 + let n = preview.childNodesLength - shown; 1.2404 + let str = VariablesView.stringifiers._getNMoreString(n); 1.2405 + this._anchor(str); 1.2406 + } 1.2407 + 1.2408 + this._text(" ]"); 1.2409 + }, 1.2410 + 1.2411 + _renderElementNode: function() 1.2412 + { 1.2413 + let doc = this.document; 1.2414 + let {attributes, nodeName} = this.objectActor.preview; 1.2415 + 1.2416 + this.element = this.el("span." + "kind-" + this.objectActor.preview.kind + ".elementNode"); 1.2417 + 1.2418 + let openTag = this.el("span.cm-tag"); 1.2419 + openTag.textContent = "<"; 1.2420 + this.element.appendChild(openTag); 1.2421 + 1.2422 + let tagName = this._anchor(nodeName, { 1.2423 + className: "cm-tag", 1.2424 + appendTo: openTag 1.2425 + }); 1.2426 + 1.2427 + if (this.options.concise) { 1.2428 + if (attributes.id) { 1.2429 + tagName.appendChild(this.el("span.cm-attribute", "#" + attributes.id)); 1.2430 + } 1.2431 + if (attributes.class) { 1.2432 + tagName.appendChild(this.el("span.cm-attribute", "." + attributes.class.split(/\s+/g).join("."))); 1.2433 + } 1.2434 + } else { 1.2435 + for (let name of Object.keys(attributes)) { 1.2436 + let attr = this._renderAttributeNode(" " + name, attributes[name]); 1.2437 + this.element.appendChild(attr); 1.2438 + } 1.2439 + } 1.2440 + 1.2441 + let closeTag = this.el("span.cm-tag"); 1.2442 + closeTag.textContent = ">"; 1.2443 + this.element.appendChild(closeTag); 1.2444 + 1.2445 + // Register this widget in the owner message so that it gets destroyed when 1.2446 + // the message is destroyed. 1.2447 + this.message.widgets.add(this); 1.2448 + 1.2449 + this.linkToInspector(); 1.2450 + }, 1.2451 + 1.2452 + /** 1.2453 + * If the DOMNode being rendered can be highlit in the page, this function 1.2454 + * will attach mouseover/out event listeners to do so, and the inspector icon 1.2455 + * to open the node in the inspector. 1.2456 + * @return a promise (always the same) that resolves when the node has been 1.2457 + * linked to the inspector, or rejects if it wasn't (either if no toolbox 1.2458 + * could be found to access the inspector, or if the node isn't present in the 1.2459 + * inspector, i.e. if the node is in a DocumentFragment or not part of the 1.2460 + * tree, or not of type Ci.nsIDOMNode.ELEMENT_NODE). 1.2461 + */ 1.2462 + linkToInspector: function() 1.2463 + { 1.2464 + if (this._linkedToInspector) { 1.2465 + return this._linkedToInspector; 1.2466 + } 1.2467 + 1.2468 + this._linkedToInspector = Task.spawn(function*() { 1.2469 + // Checking the node type 1.2470 + if (this.objectActor.preview.nodeType !== Ci.nsIDOMNode.ELEMENT_NODE) { 1.2471 + throw null; 1.2472 + } 1.2473 + 1.2474 + // Checking the presence of a toolbox 1.2475 + let target = this.message.output.toolboxTarget; 1.2476 + this.toolbox = gDevTools.getToolbox(target); 1.2477 + if (!this.toolbox) { 1.2478 + throw null; 1.2479 + } 1.2480 + 1.2481 + // Checking that the inspector supports the node 1.2482 + yield this.toolbox.initInspector(); 1.2483 + this._nodeFront = yield this.toolbox.walker.getNodeActorFromObjectActor(this.objectActor.actor); 1.2484 + if (!this._nodeFront) { 1.2485 + throw null; 1.2486 + } 1.2487 + 1.2488 + // At this stage, the message may have been cleared already 1.2489 + if (!this.document) { 1.2490 + throw null; 1.2491 + } 1.2492 + 1.2493 + this.highlightDomNode = this.highlightDomNode.bind(this); 1.2494 + this.element.addEventListener("mouseover", this.highlightDomNode, false); 1.2495 + this.unhighlightDomNode = this.unhighlightDomNode.bind(this); 1.2496 + this.element.addEventListener("mouseout", this.unhighlightDomNode, false); 1.2497 + 1.2498 + this._openInspectorNode = this._anchor("", { 1.2499 + className: "open-inspector", 1.2500 + onClick: this.openNodeInInspector.bind(this) 1.2501 + }); 1.2502 + this._openInspectorNode.title = l10n.getStr("openNodeInInspector"); 1.2503 + }.bind(this)); 1.2504 + 1.2505 + return this._linkedToInspector; 1.2506 + }, 1.2507 + 1.2508 + /** 1.2509 + * Highlight the DOMNode corresponding to the ObjectActor in the page. 1.2510 + * @return a promise that resolves when the node has been highlighted, or 1.2511 + * rejects if the node cannot be highlighted (detached from the DOM) 1.2512 + */ 1.2513 + highlightDomNode: function() 1.2514 + { 1.2515 + return Task.spawn(function*() { 1.2516 + yield this.linkToInspector(); 1.2517 + let isAttached = yield this.toolbox.walker.isInDOMTree(this._nodeFront); 1.2518 + if (isAttached) { 1.2519 + yield this.toolbox.highlighterUtils.highlightNodeFront(this._nodeFront); 1.2520 + } else { 1.2521 + throw null; 1.2522 + } 1.2523 + }.bind(this)); 1.2524 + }, 1.2525 + 1.2526 + /** 1.2527 + * Unhighlight a previously highlit node 1.2528 + * @see highlightDomNode 1.2529 + * @return a promise that resolves when the highlighter has been hidden 1.2530 + */ 1.2531 + unhighlightDomNode: function() 1.2532 + { 1.2533 + return this.linkToInspector().then(() => { 1.2534 + return this.toolbox.highlighterUtils.unhighlight(); 1.2535 + }); 1.2536 + }, 1.2537 + 1.2538 + /** 1.2539 + * Open the DOMNode corresponding to the ObjectActor in the inspector panel 1.2540 + * @return a promise that resolves when the inspector has been switched to 1.2541 + * and the node has been selected, or rejects if the node cannot be selected 1.2542 + * (detached from the DOM). Note that in any case, the inspector panel will 1.2543 + * be switched to. 1.2544 + */ 1.2545 + openNodeInInspector: function() 1.2546 + { 1.2547 + return Task.spawn(function*() { 1.2548 + yield this.linkToInspector(); 1.2549 + yield this.toolbox.selectTool("inspector"); 1.2550 + 1.2551 + let isAttached = yield this.toolbox.walker.isInDOMTree(this._nodeFront); 1.2552 + if (isAttached) { 1.2553 + let onReady = this.toolbox.inspector.once("inspector-updated"); 1.2554 + yield this.toolbox.selection.setNodeFront(this._nodeFront, "console"); 1.2555 + yield onReady; 1.2556 + } else { 1.2557 + throw null; 1.2558 + } 1.2559 + }.bind(this)); 1.2560 + }, 1.2561 + 1.2562 + destroy: function() 1.2563 + { 1.2564 + if (this.toolbox && this._nodeFront) { 1.2565 + this.element.removeEventListener("mouseover", this.highlightDomNode, false); 1.2566 + this.element.removeEventListener("mouseout", this.unhighlightDomNode, false); 1.2567 + this._openInspectorNode.removeEventListener("mousedown", this.openNodeInInspector, true); 1.2568 + this.toolbox = null; 1.2569 + this._nodeFront = null; 1.2570 + } 1.2571 + }, 1.2572 +}); // Widgets.ObjectRenderers.byKind.DOMNode 1.2573 + 1.2574 +/** 1.2575 + * The widget used for displaying generic JS object previews. 1.2576 + */ 1.2577 +Widgets.ObjectRenderers.add({ 1.2578 + byKind: "Object", 1.2579 + 1.2580 + render: function() 1.2581 + { 1.2582 + let {preview} = this.objectActor; 1.2583 + let {ownProperties, safeGetterValues} = preview; 1.2584 + 1.2585 + if ((!ownProperties && !safeGetterValues) || this.options.concise) { 1.2586 + this.element = this._anchor(this.objectActor.class, 1.2587 + { className: "cm-variable" }); 1.2588 + return; 1.2589 + } 1.2590 + 1.2591 + let container = this.element = this.el("span.kind-" + preview.kind); 1.2592 + this._anchor(this.objectActor.class, { className: "cm-variable" }); 1.2593 + this._text(" { "); 1.2594 + 1.2595 + let addProperty = (str) => { 1.2596 + container.appendChild(this.el("span.cm-property", str)); 1.2597 + }; 1.2598 + 1.2599 + let shown = 0; 1.2600 + for (let key of Object.keys(ownProperties || {})) { 1.2601 + if (shown > 0) { 1.2602 + this._text(", "); 1.2603 + } 1.2604 + 1.2605 + let value = ownProperties[key]; 1.2606 + 1.2607 + addProperty(key); 1.2608 + this._text(": "); 1.2609 + 1.2610 + if (value.get) { 1.2611 + addProperty("Getter"); 1.2612 + } else if (value.set) { 1.2613 + addProperty("Setter"); 1.2614 + } else { 1.2615 + let valueElem = this.message._renderValueGrip(value.value, { concise: true }); 1.2616 + container.appendChild(valueElem); 1.2617 + } 1.2618 + 1.2619 + shown++; 1.2620 + } 1.2621 + 1.2622 + let ownPropertiesShown = shown; 1.2623 + 1.2624 + for (let key of Object.keys(safeGetterValues || {})) { 1.2625 + if (shown > 0) { 1.2626 + this._text(", "); 1.2627 + } 1.2628 + 1.2629 + addProperty(key); 1.2630 + this._text(": "); 1.2631 + 1.2632 + let value = safeGetterValues[key].getterValue; 1.2633 + let valueElem = this.message._renderValueGrip(value, { concise: true }); 1.2634 + container.appendChild(valueElem); 1.2635 + 1.2636 + shown++; 1.2637 + } 1.2638 + 1.2639 + if (typeof preview.ownPropertiesLength == "number" && 1.2640 + ownPropertiesShown < preview.ownPropertiesLength) { 1.2641 + this._text(", "); 1.2642 + 1.2643 + let n = preview.ownPropertiesLength - ownPropertiesShown; 1.2644 + let str = VariablesView.stringifiers._getNMoreString(n); 1.2645 + this._anchor(str); 1.2646 + } 1.2647 + 1.2648 + this._text(" }"); 1.2649 + }, 1.2650 +}); // Widgets.ObjectRenderers.byKind.Object 1.2651 + 1.2652 +/** 1.2653 + * The long string widget. 1.2654 + * 1.2655 + * @constructor 1.2656 + * @param object message 1.2657 + * The owning message. 1.2658 + * @param object longStringActor 1.2659 + * The LongStringActor to display. 1.2660 + */ 1.2661 +Widgets.LongString = function(message, longStringActor) 1.2662 +{ 1.2663 + Widgets.BaseWidget.call(this, message); 1.2664 + this.longStringActor = longStringActor; 1.2665 + this._onClick = this._onClick.bind(this); 1.2666 + this._onSubstring = this._onSubstring.bind(this); 1.2667 +}; 1.2668 + 1.2669 +Widgets.LongString.prototype = Heritage.extend(Widgets.BaseWidget.prototype, 1.2670 +{ 1.2671 + /** 1.2672 + * The LongStringActor displayed by the widget. 1.2673 + * @type object 1.2674 + */ 1.2675 + longStringActor: null, 1.2676 + 1.2677 + render: function() 1.2678 + { 1.2679 + if (this.element) { 1.2680 + return this; 1.2681 + } 1.2682 + 1.2683 + let result = this.element = this.document.createElementNS(XHTML_NS, "span"); 1.2684 + result.className = "longString console-string"; 1.2685 + this._renderString(this.longStringActor.initial); 1.2686 + result.appendChild(this._renderEllipsis()); 1.2687 + 1.2688 + return this; 1.2689 + }, 1.2690 + 1.2691 + /** 1.2692 + * Render the long string in the widget element. 1.2693 + * @private 1.2694 + * @param string str 1.2695 + * The string to display. 1.2696 + */ 1.2697 + _renderString: function(str) 1.2698 + { 1.2699 + this.element.textContent = VariablesView.getString(str, { 1.2700 + noStringQuotes: !this.message._quoteStrings, 1.2701 + noEllipsis: true, 1.2702 + }); 1.2703 + }, 1.2704 + 1.2705 + /** 1.2706 + * Render the anchor ellipsis that allows the user to expand the long string. 1.2707 + * 1.2708 + * @private 1.2709 + * @return Element 1.2710 + */ 1.2711 + _renderEllipsis: function() 1.2712 + { 1.2713 + let ellipsis = this.document.createElementNS(XHTML_NS, "a"); 1.2714 + ellipsis.className = "longStringEllipsis"; 1.2715 + ellipsis.textContent = l10n.getStr("longStringEllipsis"); 1.2716 + ellipsis.href = "#"; 1.2717 + ellipsis.draggable = false; 1.2718 + this.message._addLinkCallback(ellipsis, this._onClick); 1.2719 + 1.2720 + return ellipsis; 1.2721 + }, 1.2722 + 1.2723 + /** 1.2724 + * The click event handler for the ellipsis shown after the short string. This 1.2725 + * function expands the element to show the full string. 1.2726 + * @private 1.2727 + */ 1.2728 + _onClick: function() 1.2729 + { 1.2730 + let longString = this.output.webConsoleClient.longString(this.longStringActor); 1.2731 + let toIndex = Math.min(longString.length, MAX_LONG_STRING_LENGTH); 1.2732 + 1.2733 + longString.substring(longString.initial.length, toIndex, this._onSubstring); 1.2734 + }, 1.2735 + 1.2736 + /** 1.2737 + * The longString substring response callback. 1.2738 + * 1.2739 + * @private 1.2740 + * @param object response 1.2741 + * Response packet. 1.2742 + */ 1.2743 + _onSubstring: function(response) 1.2744 + { 1.2745 + if (response.error) { 1.2746 + Cu.reportError("LongString substring failure: " + response.error); 1.2747 + return; 1.2748 + } 1.2749 + 1.2750 + this.element.lastChild.remove(); 1.2751 + this.element.classList.remove("longString"); 1.2752 + 1.2753 + this._renderString(this.longStringActor.initial + response.substring); 1.2754 + 1.2755 + this.output.owner.emit("messages-updated", new Set([this.message.element])); 1.2756 + 1.2757 + let toIndex = Math.min(this.longStringActor.length, MAX_LONG_STRING_LENGTH); 1.2758 + if (toIndex != this.longStringActor.length) { 1.2759 + this._logWarningAboutStringTooLong(); 1.2760 + } 1.2761 + }, 1.2762 + 1.2763 + /** 1.2764 + * Inform user that the string he tries to view is too long. 1.2765 + * @private 1.2766 + */ 1.2767 + _logWarningAboutStringTooLong: function() 1.2768 + { 1.2769 + let msg = new Messages.Simple(l10n.getStr("longStringTooLong"), { 1.2770 + category: "output", 1.2771 + severity: "warning", 1.2772 + }); 1.2773 + this.output.addMessage(msg); 1.2774 + }, 1.2775 +}); // Widgets.LongString.prototype 1.2776 + 1.2777 + 1.2778 +/** 1.2779 + * The stacktrace widget. 1.2780 + * 1.2781 + * @constructor 1.2782 + * @extends Widgets.BaseWidget 1.2783 + * @param object message 1.2784 + * The owning message. 1.2785 + * @param array stacktrace 1.2786 + * The stacktrace to display, array of frames as supplied by the server, 1.2787 + * over the remote protocol. 1.2788 + */ 1.2789 +Widgets.Stacktrace = function(message, stacktrace) 1.2790 +{ 1.2791 + Widgets.BaseWidget.call(this, message); 1.2792 + this.stacktrace = stacktrace; 1.2793 +}; 1.2794 + 1.2795 +Widgets.Stacktrace.prototype = Heritage.extend(Widgets.BaseWidget.prototype, 1.2796 +{ 1.2797 + /** 1.2798 + * The stackframes received from the server. 1.2799 + * @type array 1.2800 + */ 1.2801 + stacktrace: null, 1.2802 + 1.2803 + render: function() 1.2804 + { 1.2805 + if (this.element) { 1.2806 + return this; 1.2807 + } 1.2808 + 1.2809 + let result = this.element = this.document.createElementNS(XHTML_NS, "ul"); 1.2810 + result.className = "stacktrace devtools-monospace"; 1.2811 + 1.2812 + for (let frame of this.stacktrace) { 1.2813 + result.appendChild(this._renderFrame(frame)); 1.2814 + } 1.2815 + 1.2816 + return this; 1.2817 + }, 1.2818 + 1.2819 + /** 1.2820 + * Render a frame object received from the server. 1.2821 + * 1.2822 + * @param object frame 1.2823 + * The stack frame to display. This object should have the following 1.2824 + * properties: functionName, filename and lineNumber. 1.2825 + * @return DOMElement 1.2826 + * The DOM element to display for the given frame. 1.2827 + */ 1.2828 + _renderFrame: function(frame) 1.2829 + { 1.2830 + let fn = this.document.createElementNS(XHTML_NS, "span"); 1.2831 + fn.className = "function"; 1.2832 + if (frame.functionName) { 1.2833 + let span = this.document.createElementNS(XHTML_NS, "span"); 1.2834 + span.className = "cm-variable"; 1.2835 + span.textContent = frame.functionName; 1.2836 + fn.appendChild(span); 1.2837 + fn.appendChild(this.document.createTextNode("()")); 1.2838 + } else { 1.2839 + fn.classList.add("cm-comment"); 1.2840 + fn.textContent = l10n.getStr("stacktrace.anonymousFunction"); 1.2841 + } 1.2842 + 1.2843 + let location = this.output.owner.createLocationNode(frame.filename, 1.2844 + frame.lineNumber, 1.2845 + "jsdebugger"); 1.2846 + 1.2847 + // .devtools-monospace sets font-size to 80%, however .body already has 1.2848 + // .devtools-monospace. If we keep it here, the location would be rendered 1.2849 + // smaller. 1.2850 + location.classList.remove("devtools-monospace"); 1.2851 + 1.2852 + let elem = this.document.createElementNS(XHTML_NS, "li"); 1.2853 + elem.appendChild(fn); 1.2854 + elem.appendChild(location); 1.2855 + elem.appendChild(this.document.createTextNode("\n")); 1.2856 + 1.2857 + return elem; 1.2858 + }, 1.2859 +}); // Widgets.Stacktrace.prototype 1.2860 + 1.2861 + 1.2862 +function gSequenceId() 1.2863 +{ 1.2864 + return gSequenceId.n++; 1.2865 +} 1.2866 +gSequenceId.n = 0; 1.2867 + 1.2868 +exports.ConsoleOutput = ConsoleOutput; 1.2869 +exports.Messages = Messages; 1.2870 +exports.Widgets = Widgets;