diff options
Diffstat (limited to 'devtools/client/shared/components/reps')
33 files changed, 3143 insertions, 0 deletions
diff --git a/devtools/client/shared/components/reps/array.js b/devtools/client/shared/components/reps/array.js new file mode 100644 index 000000000..8ec1443e1 --- /dev/null +++ b/devtools/client/shared/components/reps/array.js @@ -0,0 +1,186 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +// Make this available to both AMD and CJS environments +define(function (require, exports, module) { + // Dependencies + const React = require("devtools/client/shared/vendor/react"); + const { createFactories } = require("./rep-utils"); + const { Caption } = createFactories(require("./caption")); + + // Shortcuts + const DOM = React.DOM; + + /** + * Renders an array. The array is enclosed by left and right bracket + * and the max number of rendered items depends on the current mode. + */ + let ArrayRep = React.createClass({ + displayName: "ArrayRep", + + getTitle: function (object, context) { + return "[" + object.length + "]"; + }, + + arrayIterator: function (array, max) { + let items = []; + let delim; + + for (let i = 0; i < array.length && i < max; i++) { + try { + let value = array[i]; + + delim = (i == array.length - 1 ? "" : ", "); + + items.push(ItemRep({ + object: value, + // Hardcode tiny mode to avoid recursive handling. + mode: "tiny", + delim: delim + })); + } catch (exc) { + items.push(ItemRep({ + object: exc, + mode: "tiny", + delim: delim + })); + } + } + + if (array.length > max) { + let objectLink = this.props.objectLink || DOM.span; + items.push(Caption({ + object: objectLink({ + object: this.props.object + }, (array.length - max) + " more…") + })); + } + + return items; + }, + + /** + * Returns true if the passed object is an array with additional (custom) + * properties, otherwise returns false. Custom properties should be + * displayed in extra expandable section. + * + * Example array with a custom property. + * let arr = [0, 1]; + * arr.myProp = "Hello"; + * + * @param {Array} array The array object. + */ + hasSpecialProperties: function (array) { + function isInteger(x) { + let y = parseInt(x, 10); + if (isNaN(y)) { + return false; + } + return x === y.toString(); + } + + let props = Object.getOwnPropertyNames(array); + for (let i = 0; i < props.length; i++) { + let p = props[i]; + + // Valid indexes are skipped + if (isInteger(p)) { + continue; + } + + // Ignore standard 'length' property, anything else is custom. + if (p != "length") { + return true; + } + } + + return false; + }, + + // Event Handlers + + onToggleProperties: function (event) { + }, + + onClickBracket: function (event) { + }, + + render: function () { + let mode = this.props.mode || "short"; + let object = this.props.object; + let items; + let brackets; + let needSpace = function (space) { + return space ? { left: "[ ", right: " ]"} : { left: "[", right: "]"}; + }; + + if (mode == "tiny") { + let isEmpty = object.length === 0; + items = [DOM.span({className: "length"}, isEmpty ? "" : object.length)]; + brackets = needSpace(false); + } else { + let max = (mode == "short") ? 3 : 300; + items = this.arrayIterator(object, max); + brackets = needSpace(items.length > 0); + } + + let objectLink = this.props.objectLink || DOM.span; + + return ( + DOM.span({ + className: "objectBox objectBox-array"}, + objectLink({ + className: "arrayLeftBracket", + object: object + }, brackets.left), + ...items, + objectLink({ + className: "arrayRightBracket", + object: object + }, brackets.right), + DOM.span({ + className: "arrayProperties", + role: "group"} + ) + ) + ); + }, + }); + + /** + * Renders array item. Individual values are separated by a comma. + */ + let ItemRep = React.createFactory(React.createClass({ + displayName: "ItemRep", + + render: function () { + const { Rep } = createFactories(require("./rep")); + + let object = this.props.object; + let delim = this.props.delim; + let mode = this.props.mode; + return ( + DOM.span({}, + Rep({object: object, mode: mode}), + delim + ) + ); + } + })); + + function supportsObject(object, type) { + return Array.isArray(object) || + Object.prototype.toString.call(object) === "[object Arguments]"; + } + + // Exports from this module + exports.ArrayRep = { + rep: ArrayRep, + supportsObject: supportsObject + }; +}); diff --git a/devtools/client/shared/components/reps/attribute.js b/devtools/client/shared/components/reps/attribute.js new file mode 100644 index 000000000..f57ed0380 --- /dev/null +++ b/devtools/client/shared/components/reps/attribute.js @@ -0,0 +1,70 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; + +// Make this available to both AMD and CJS environments +define(function (require, exports, module) { + // ReactJS + const React = require("devtools/client/shared/vendor/react"); + + // Reps + const { createFactories, isGrip } = require("./rep-utils"); + const { StringRep } = require("./string"); + + // Shortcuts + const { span } = React.DOM; + const { rep: StringRepFactory } = createFactories(StringRep); + + /** + * Renders DOM attribute + */ + let Attribute = React.createClass({ + displayName: "Attr", + + propTypes: { + object: React.PropTypes.object.isRequired + }, + + getTitle: function (grip) { + return grip.preview.nodeName; + }, + + render: function () { + let grip = this.props.object; + let value = grip.preview.value; + let objectLink = this.props.objectLink || span; + + return ( + objectLink({className: "objectLink-Attr"}, + span({}, + span({className: "attrTitle"}, + this.getTitle(grip) + ), + span({className: "attrEqual"}, + "=" + ), + StringRepFactory({object: value}) + ) + ) + ); + }, + }); + + // Registration + + function supportsObject(grip, type) { + if (!isGrip(grip)) { + return false; + } + + return (type == "Attr" && grip.preview); + } + + exports.Attribute = { + rep: Attribute, + supportsObject: supportsObject + }; +}); diff --git a/devtools/client/shared/components/reps/caption.js b/devtools/client/shared/components/reps/caption.js new file mode 100644 index 000000000..7f00b01e8 --- /dev/null +++ b/devtools/client/shared/components/reps/caption.js @@ -0,0 +1,31 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +// Make this available to both AMD and CJS environments +define(function (require, exports, module) { + // Dependencies + const React = require("devtools/client/shared/vendor/react"); + const DOM = React.DOM; + + /** + * Renders a caption. This template is used by other components + * that needs to distinguish between a simple text/value and a label. + */ + const Caption = React.createClass({ + displayName: "Caption", + + render: function () { + return ( + DOM.span({"className": "caption"}, this.props.object) + ); + }, + }); + + // Exports from this module + exports.Caption = Caption; +}); diff --git a/devtools/client/shared/components/reps/comment-node.js b/devtools/client/shared/components/reps/comment-node.js new file mode 100644 index 000000000..2c69c1414 --- /dev/null +++ b/devtools/client/shared/components/reps/comment-node.js @@ -0,0 +1,60 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; + +// Make this available to both AMD and CJS environments +define(function (require, exports, module) { + // ReactJS + const React = require("devtools/client/shared/vendor/react"); + const { isGrip, cropString, cropMultipleLines } = require("./rep-utils"); + + // Utils + const nodeConstants = require("devtools/shared/dom-node-constants"); + + // Shortcuts + const { span } = React.DOM; + + /** + * Renders DOM comment node. + */ + const CommentNode = React.createClass({ + displayName: "CommentNode", + + propTypes: { + object: React.PropTypes.object.isRequired, + mode: React.PropTypes.string, + }, + + render: function () { + let {object} = this.props; + + let mode = this.props.mode || "short"; + + let {textContent} = object.preview; + if (mode === "tiny") { + textContent = cropMultipleLines(textContent, 30); + } else if (mode === "short") { + textContent = cropString(textContent, 50); + } + + return span({className: "objectBox theme-comment"}, `<!-- ${textContent} -->`); + }, + }); + + // Registration + function supportsObject(object, type) { + if (!isGrip(object)) { + return false; + } + return object.preview && object.preview.nodeType === nodeConstants.COMMENT_NODE; + } + + // Exports from this module + exports.CommentNode = { + rep: CommentNode, + supportsObject: supportsObject + }; +}); diff --git a/devtools/client/shared/components/reps/date-time.js b/devtools/client/shared/components/reps/date-time.js new file mode 100644 index 000000000..55dfb7d2d --- /dev/null +++ b/devtools/client/shared/components/reps/date-time.js @@ -0,0 +1,70 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; + +// Make this available to both AMD and CJS environments +define(function (require, exports, module) { + // ReactJS + const React = require("devtools/client/shared/vendor/react"); + + // Reps + const { isGrip } = require("./rep-utils"); + + // Shortcuts + const { span } = React.DOM; + + /** + * Used to render JS built-in Date() object. + */ + let DateTime = React.createClass({ + displayName: "Date", + + propTypes: { + object: React.PropTypes.object.isRequired + }, + + getTitle: function (grip) { + if (this.props.objectLink) { + return this.props.objectLink({ + object: grip + }, grip.class + " "); + } + return ""; + }, + + render: function () { + let grip = this.props.object; + let date; + try { + date = span({className: "objectBox"}, + this.getTitle(grip), + span({className: "Date"}, + new Date(grip.preview.timestamp).toISOString() + ) + ); + } catch (e) { + date = span({className: "objectBox"}, "Invalid Date"); + } + return date; + }, + }); + + // Registration + + function supportsObject(grip, type) { + if (!isGrip(grip)) { + return false; + } + + return (type == "Date" && grip.preview); + } + + // Exports from this module + exports.DateTime = { + rep: DateTime, + supportsObject: supportsObject + }; +}); diff --git a/devtools/client/shared/components/reps/document.js b/devtools/client/shared/components/reps/document.js new file mode 100644 index 000000000..25e42609f --- /dev/null +++ b/devtools/client/shared/components/reps/document.js @@ -0,0 +1,78 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; + +// Make this available to both AMD and CJS environments +define(function (require, exports, module) { + // ReactJS + const React = require("devtools/client/shared/vendor/react"); + + // Reps + const { isGrip, getURLDisplayString } = require("./rep-utils"); + + // Shortcuts + const { span } = React.DOM; + + /** + * Renders DOM document object. + */ + let Document = React.createClass({ + displayName: "Document", + + propTypes: { + object: React.PropTypes.object.isRequired + }, + + getLocation: function (grip) { + let location = grip.preview.location; + return location ? getURLDisplayString(location) : ""; + }, + + getTitle: function (grip) { + if (this.props.objectLink) { + return span({className: "objectBox"}, + this.props.objectLink({ + object: grip + }, grip.class + " ") + ); + } + return ""; + }, + + getTooltip: function (doc) { + return doc.location.href; + }, + + render: function () { + let grip = this.props.object; + + return ( + span({className: "objectBox objectBox-object"}, + this.getTitle(grip), + span({className: "objectPropValue"}, + this.getLocation(grip) + ) + ) + ); + }, + }); + + // Registration + + function supportsObject(object, type) { + if (!isGrip(object)) { + return false; + } + + return (object.preview && type == "HTMLDocument"); + } + + // Exports from this module + exports.Document = { + rep: Document, + supportsObject: supportsObject + }; +}); diff --git a/devtools/client/shared/components/reps/element-node.js b/devtools/client/shared/components/reps/element-node.js new file mode 100644 index 000000000..6315fb5b1 --- /dev/null +++ b/devtools/client/shared/components/reps/element-node.js @@ -0,0 +1,114 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; + +// Make this available to both AMD and CJS environments +define(function (require, exports, module) { + // ReactJS + const React = require("devtools/client/shared/vendor/react"); + const { isGrip } = require("./rep-utils"); + + // Utils + const nodeConstants = require("devtools/shared/dom-node-constants"); + + // Shortcuts + const { span } = React.DOM; + + /** + * Renders DOM element node. + */ + const ElementNode = React.createClass({ + displayName: "ElementNode", + + propTypes: { + object: React.PropTypes.object.isRequired, + mode: React.PropTypes.string, + }, + + getElements: function (grip, mode) { + let {attributes, nodeName} = grip.preview; + const nodeNameElement = span({ + className: "tag-name theme-fg-color3" + }, nodeName); + + if (mode === "tiny") { + let elements = [nodeNameElement]; + if (attributes.id) { + elements.push( + span({className: "attr-name theme-fg-color2"}, `#${attributes.id}`)); + } + if (attributes.class) { + elements.push( + span({className: "attr-name theme-fg-color2"}, + attributes.class + .replace(/(^\s+)|(\s+$)/g, "") + .split(" ") + .map(cls => `.${cls}`) + .join("") + ) + ); + } + return elements; + } + let attributeElements = Object.keys(attributes) + .sort(function getIdAndClassFirst(a1, a2) { + if ([a1, a2].includes("id")) { + return 3 * (a1 === "id" ? -1 : 1); + } + if ([a1, a2].includes("class")) { + return 2 * (a1 === "class" ? -1 : 1); + } + + // `id` and `class` excepted, + // we want to keep the same order that in `attributes`. + return 0; + }) + .reduce((arr, name, i, keys) => { + let value = attributes[name]; + let attribute = span({}, + span({className: "attr-name theme-fg-color2"}, `${name}`), + `="`, + span({className: "attr-value theme-fg-color6"}, `${value}`), + `"` + ); + + return arr.concat([" ", attribute]); + }, []); + + return [ + "<", + nodeNameElement, + ...attributeElements, + ">", + ]; + }, + + render: function () { + let {object, mode} = this.props; + let elements = this.getElements(object, mode); + const baseElement = span({className: "objectBox"}, ...elements); + + if (this.props.objectLink) { + return this.props.objectLink({object}, baseElement); + } + return baseElement; + }, + }); + + // Registration + function supportsObject(object, type) { + if (!isGrip(object)) { + return false; + } + return object.preview && object.preview.nodeType === nodeConstants.ELEMENT_NODE; + } + + // Exports from this module + exports.ElementNode = { + rep: ElementNode, + supportsObject: supportsObject + }; +}); diff --git a/devtools/client/shared/components/reps/event.js b/devtools/client/shared/components/reps/event.js new file mode 100644 index 000000000..1d37e0150 --- /dev/null +++ b/devtools/client/shared/components/reps/event.js @@ -0,0 +1,81 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; + +// Make this available to both AMD and CJS environments +define(function (require, exports, module) { + // ReactJS + const React = require("devtools/client/shared/vendor/react"); + + // Reps + const { createFactories, isGrip } = require("./rep-utils"); + const { rep } = createFactories(require("./grip").Grip); + + /** + * Renders DOM event objects. + */ + let Event = React.createClass({ + displayName: "event", + + propTypes: { + object: React.PropTypes.object.isRequired + }, + + render: function () { + // Use `Object.assign` to keep `this.props` without changes because: + // 1. JSON.stringify/JSON.parse is slow. + // 2. Immutable.js is planned for the future. + let props = Object.assign({}, this.props); + props.object = Object.assign({}, this.props.object); + props.object.preview = Object.assign({}, this.props.object.preview); + props.object.preview.ownProperties = props.object.preview.properties; + delete props.object.preview.properties; + props.object.ownPropertyLength = + Object.keys(props.object.preview.ownProperties).length; + + switch (props.object.class) { + case "MouseEvent": + props.isInterestingProp = (type, value, name) => { + return (name == "clientX" || + name == "clientY" || + name == "layerX" || + name == "layerY"); + }; + break; + case "KeyboardEvent": + props.isInterestingProp = (type, value, name) => { + return (name == "key" || + name == "charCode" || + name == "keyCode"); + }; + break; + case "MessageEvent": + props.isInterestingProp = (type, value, name) => { + return (name == "isTrusted" || + name == "data"); + }; + break; + } + return rep(props); + } + }); + + // Registration + + function supportsObject(grip, type) { + if (!isGrip(grip)) { + return false; + } + + return (grip.preview && grip.preview.kind == "DOMEvent"); + } + + // Exports from this module + exports.Event = { + rep: Event, + supportsObject: supportsObject + }; +}); diff --git a/devtools/client/shared/components/reps/function.js b/devtools/client/shared/components/reps/function.js new file mode 100644 index 000000000..fd20dc318 --- /dev/null +++ b/devtools/client/shared/components/reps/function.js @@ -0,0 +1,73 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; + +// Make this available to both AMD and CJS environments +define(function (require, exports, module) { + // ReactJS + const React = require("devtools/client/shared/vendor/react"); + + // Reps + const { isGrip, cropString } = require("./rep-utils"); + + // Shortcuts + const { span } = React.DOM; + + /** + * This component represents a template for Function objects. + */ + let Func = React.createClass({ + displayName: "Func", + + propTypes: { + object: React.PropTypes.object.isRequired + }, + + getTitle: function (grip) { + if (this.props.objectLink) { + return this.props.objectLink({ + object: grip + }, "function "); + } + return ""; + }, + + summarizeFunction: function (grip) { + let name = grip.userDisplayName || grip.displayName || grip.name || "function"; + return cropString(name + "()", 100); + }, + + render: function () { + let grip = this.props.object; + + return ( + // Set dir="ltr" to prevent function parentheses from + // appearing in the wrong direction + span({dir: "ltr", className: "objectBox objectBox-function"}, + this.getTitle(grip), + this.summarizeFunction(grip) + ) + ); + }, + }); + + // Registration + + function supportsObject(grip, type) { + if (!isGrip(grip)) { + return (type == "function"); + } + + return (type == "Function"); + } + + // Exports from this module + + exports.Func = { + rep: Func, + supportsObject: supportsObject + }; +}); diff --git a/devtools/client/shared/components/reps/grip-array.js b/devtools/client/shared/components/reps/grip-array.js new file mode 100644 index 000000000..04a5603bb --- /dev/null +++ b/devtools/client/shared/components/reps/grip-array.js @@ -0,0 +1,198 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +// Make this available to both AMD and CJS environments +define(function (require, exports, module) { + // Dependencies + const React = require("devtools/client/shared/vendor/react"); + const { createFactories, isGrip } = require("./rep-utils"); + const { Caption } = createFactories(require("./caption")); + + // Shortcuts + const { span } = React.DOM; + + /** + * Renders an array. The array is enclosed by left and right bracket + * and the max number of rendered items depends on the current mode. + */ + let GripArray = React.createClass({ + displayName: "GripArray", + + propTypes: { + object: React.PropTypes.object.isRequired, + mode: React.PropTypes.string, + provider: React.PropTypes.object, + }, + + getLength: function (grip) { + if (!grip.preview) { + return 0; + } + + return grip.preview.length || grip.preview.childNodesLength || 0; + }, + + getTitle: function (object, context) { + let objectLink = this.props.objectLink || span; + if (this.props.mode != "tiny") { + return objectLink({ + object: object + }, object.class + " "); + } + return ""; + }, + + getPreviewItems: function (grip) { + if (!grip.preview) { + return null; + } + + return grip.preview.items || grip.preview.childNodes || null; + }, + + arrayIterator: function (grip, max) { + let items = []; + const gripLength = this.getLength(grip); + + if (!gripLength) { + return items; + } + + const previewItems = this.getPreviewItems(grip); + if (!previewItems) { + return items; + } + + let delim; + // number of grip preview items is limited to 10, but we may have more + // items in grip-array. + let delimMax = gripLength > previewItems.length ? + previewItems.length : previewItems.length - 1; + let provider = this.props.provider; + + for (let i = 0; i < previewItems.length && i < max; i++) { + try { + let itemGrip = previewItems[i]; + let value = provider ? provider.getValue(itemGrip) : itemGrip; + + delim = (i == delimMax ? "" : ", "); + + items.push(GripArrayItem(Object.assign({}, this.props, { + object: value, + delim: delim + }))); + } catch (exc) { + items.push(GripArrayItem(Object.assign({}, this.props, { + object: exc, + delim: delim + }))); + } + } + if (previewItems.length > max || gripLength > previewItems.length) { + let objectLink = this.props.objectLink || span; + let leftItemNum = gripLength - max > 0 ? + gripLength - max : gripLength - previewItems.length; + items.push(Caption({ + object: objectLink({ + object: this.props.object + }, leftItemNum + " more…") + })); + } + + return items; + }, + + render: function () { + let mode = this.props.mode || "short"; + let object = this.props.object; + + let items; + let brackets; + let needSpace = function (space) { + return space ? { left: "[ ", right: " ]"} : { left: "[", right: "]"}; + }; + + if (mode == "tiny") { + let objectLength = this.getLength(object); + let isEmpty = objectLength === 0; + items = [span({className: "length"}, isEmpty ? "" : objectLength)]; + brackets = needSpace(false); + } else { + let max = (mode == "short") ? 3 : 300; + items = this.arrayIterator(object, max); + brackets = needSpace(items.length > 0); + } + + let objectLink = this.props.objectLink || span; + let title = this.getTitle(object); + + return ( + span({ + className: "objectBox objectBox-array"}, + title, + objectLink({ + className: "arrayLeftBracket", + object: object + }, brackets.left), + ...items, + objectLink({ + className: "arrayRightBracket", + object: object + }, brackets.right), + span({ + className: "arrayProperties", + role: "group"} + ) + ) + ); + }, + }); + + /** + * Renders array item. Individual values are separated by + * a delimiter (a comma by default). + */ + let GripArrayItem = React.createFactory(React.createClass({ + displayName: "GripArrayItem", + + propTypes: { + delim: React.PropTypes.string, + }, + + render: function () { + let { Rep } = createFactories(require("./rep")); + + return ( + span({}, + Rep(Object.assign({}, this.props, { + mode: "tiny" + })), + this.props.delim + ) + ); + } + })); + + function supportsObject(grip, type) { + if (!isGrip(grip)) { + return false; + } + + return (grip.preview && ( + grip.preview.kind == "ArrayLike" || + type === "DocumentFragment" + ) + ); + } + + // Exports from this module + exports.GripArray = { + rep: GripArray, + supportsObject: supportsObject + }; +}); diff --git a/devtools/client/shared/components/reps/grip-map.js b/devtools/client/shared/components/reps/grip-map.js new file mode 100644 index 000000000..df673d005 --- /dev/null +++ b/devtools/client/shared/components/reps/grip-map.js @@ -0,0 +1,193 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; +// Make this available to both AMD and CJS environments +define(function (require, exports, module) { + // Dependencies + const React = require("devtools/client/shared/vendor/react"); + const { createFactories, isGrip } = require("./rep-utils"); + const { Caption } = createFactories(require("./caption")); + const { PropRep } = createFactories(require("./prop-rep")); + + // Shortcuts + const { span } = React.DOM; + /** + * Renders an map. A map is represented by a list of its + * entries enclosed in curly brackets. + */ + const GripMap = React.createClass({ + displayName: "GripMap", + + propTypes: { + object: React.PropTypes.object, + mode: React.PropTypes.string, + }, + + getTitle: function (object) { + let title = object && object.class ? object.class : "Map"; + if (this.props.objectLink) { + return this.props.objectLink({ + object: object + }, title); + } + return title; + }, + + safeEntriesIterator: function (object, max) { + max = (typeof max === "undefined") ? 3 : max; + try { + return this.entriesIterator(object, max); + } catch (err) { + console.error(err); + } + return []; + }, + + entriesIterator: function (object, max) { + // Entry filter. Show only interesting entries to the user. + let isInterestingEntry = this.props.isInterestingEntry || ((type, value) => { + return ( + type == "boolean" || + type == "number" || + (type == "string" && value.length != 0) + ); + }); + + let mapEntries = object.preview && object.preview.entries + ? object.preview.entries : []; + + let indexes = this.getEntriesIndexes(mapEntries, max, isInterestingEntry); + if (indexes.length < max && indexes.length < mapEntries.length) { + // There are not enough entries yet, so we add uninteresting entries. + indexes = indexes.concat( + this.getEntriesIndexes(mapEntries, max - indexes.length, (t, value, name) => { + return !isInterestingEntry(t, value, name); + }) + ); + } + + let entries = this.getEntries(mapEntries, indexes); + if (entries.length < mapEntries.length) { + // There are some undisplayed entries. Then display "more…". + let objectLink = this.props.objectLink || span; + + entries.push(Caption({ + key: "more", + object: objectLink({ + object: object + }, `${mapEntries.length - max} more…`) + })); + } + + return entries; + }, + + /** + * Get entries ordered by index. + * + * @param {Array} entries Entries array. + * @param {Array} indexes Indexes of entries. + * @return {Array} Array of PropRep. + */ + getEntries: function (entries, indexes) { + // Make indexes ordered by ascending. + indexes.sort(function (a, b) { + return a - b; + }); + + return indexes.map((index, i) => { + let [key, entryValue] = entries[index]; + let value = entryValue.value !== undefined ? entryValue.value : entryValue; + + return PropRep({ + // key, + name: key, + equal: ": ", + object: value, + // Do not add a trailing comma on the last entry + // if there won't be a "more..." item. + delim: (i < indexes.length - 1 || indexes.length < entries.length) ? ", " : "", + mode: "tiny", + objectLink: this.props.objectLink, + }); + }); + }, + + /** + * Get the indexes of entries in the map. + * + * @param {Array} entries Entries array. + * @param {Number} max The maximum length of indexes array. + * @param {Function} filter Filter the entry you want. + * @return {Array} Indexes of filtered entries in the map. + */ + getEntriesIndexes: function (entries, max, filter) { + return entries + .reduce((indexes, [key, entry], i) => { + if (indexes.length < max) { + let value = (entry && entry.value !== undefined) ? entry.value : entry; + // Type is specified in grip's "class" field and for primitive + // values use typeof. + let type = (value && value.class ? value.class : typeof value).toLowerCase(); + + if (filter(type, value, key)) { + indexes.push(i); + } + } + + return indexes; + }, []); + }, + + render: function () { + let object = this.props.object; + let props = this.safeEntriesIterator(object, + (this.props.mode == "long") ? 100 : 3); + + let objectLink = this.props.objectLink || span; + if (this.props.mode == "tiny") { + return ( + span({className: "objectBox objectBox-object"}, + this.getTitle(object), + objectLink({ + className: "objectLeftBrace", + object: object + }, "") + ) + ); + } + + return ( + span({className: "objectBox objectBox-object"}, + this.getTitle(object), + objectLink({ + className: "objectLeftBrace", + object: object + }, " { "), + props, + objectLink({ + className: "objectRightBrace", + object: object + }, " }") + ) + ); + }, + }); + + function supportsObject(grip, type) { + if (!isGrip(grip)) { + return false; + } + return (grip.preview && grip.preview.kind == "MapLike"); + } + + // Exports from this module + exports.GripMap = { + rep: GripMap, + supportsObject: supportsObject + }; +}); diff --git a/devtools/client/shared/components/reps/grip.js b/devtools/client/shared/components/reps/grip.js new file mode 100644 index 000000000..c63ee19f3 --- /dev/null +++ b/devtools/client/shared/components/reps/grip.js @@ -0,0 +1,247 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; + +// Make this available to both AMD and CJS environments +define(function (require, exports, module) { + // ReactJS + const React = require("devtools/client/shared/vendor/react"); + // Dependencies + const { createFactories, isGrip } = require("./rep-utils"); + const { Caption } = createFactories(require("./caption")); + const { PropRep } = createFactories(require("./prop-rep")); + // Shortcuts + const { span } = React.DOM; + + /** + * Renders generic grip. Grip is client representation + * of remote JS object and is used as an input object + * for this rep component. + */ + const GripRep = React.createClass({ + displayName: "Grip", + + propTypes: { + object: React.PropTypes.object.isRequired, + mode: React.PropTypes.string, + isInterestingProp: React.PropTypes.func + }, + + getTitle: function (object) { + if (this.props.objectLink) { + return this.props.objectLink({ + object: object + }, object.class); + } + return object.class || "Object"; + }, + + safePropIterator: function (object, max) { + max = (typeof max === "undefined") ? 3 : max; + try { + return this.propIterator(object, max); + } catch (err) { + console.error(err); + } + return []; + }, + + propIterator: function (object, max) { + if (object.preview && Object.keys(object.preview).includes("wrappedValue")) { + const { Rep } = createFactories(require("./rep")); + + return [Rep({ + object: object.preview.wrappedValue, + mode: this.props.mode || "tiny", + defaultRep: Grip, + })]; + } + + // Property filter. Show only interesting properties to the user. + let isInterestingProp = this.props.isInterestingProp || ((type, value) => { + return ( + type == "boolean" || + type == "number" || + (type == "string" && value.length != 0) + ); + }); + + let properties = object.preview + ? object.preview.ownProperties + : {}; + let propertiesLength = object.preview && object.preview.ownPropertiesLength + ? object.preview.ownPropertiesLength + : object.ownPropertyLength; + + if (object.preview && object.preview.safeGetterValues) { + properties = Object.assign({}, properties, object.preview.safeGetterValues); + propertiesLength += Object.keys(object.preview.safeGetterValues).length; + } + + let indexes = this.getPropIndexes(properties, max, isInterestingProp); + if (indexes.length < max && indexes.length < propertiesLength) { + // There are not enough props yet. Then add uninteresting props to display them. + indexes = indexes.concat( + this.getPropIndexes(properties, max - indexes.length, (t, value, name) => { + return !isInterestingProp(t, value, name); + }) + ); + } + + const truncate = Object.keys(properties).length > max; + let props = this.getProps(properties, indexes, truncate); + if (truncate) { + // There are some undisplayed props. Then display "more...". + let objectLink = this.props.objectLink || span; + + props.push(Caption({ + object: objectLink({ + object: object + }, `${object.ownPropertyLength - max} more…`) + })); + } + + return props; + }, + + /** + * Get props ordered by index. + * + * @param {Object} properties Props object. + * @param {Array} indexes Indexes of props. + * @param {Boolean} truncate true if the grip will be truncated. + * @return {Array} Props. + */ + getProps: function (properties, indexes, truncate) { + let props = []; + + // Make indexes ordered by ascending. + indexes.sort(function (a, b) { + return a - b; + }); + + indexes.forEach((i) => { + let name = Object.keys(properties)[i]; + let value = this.getPropValue(properties[name]); + + props.push(PropRep(Object.assign({}, this.props, { + mode: "tiny", + name: name, + object: value, + equal: ": ", + delim: i !== indexes.length - 1 || truncate ? ", " : "", + defaultRep: Grip + }))); + }); + + return props; + }, + + /** + * Get the indexes of props in the object. + * + * @param {Object} properties Props object. + * @param {Number} max The maximum length of indexes array. + * @param {Function} filter Filter the props you want. + * @return {Array} Indexes of interesting props in the object. + */ + getPropIndexes: function (properties, max, filter) { + let indexes = []; + + try { + let i = 0; + for (let name in properties) { + if (indexes.length >= max) { + return indexes; + } + + // Type is specified in grip's "class" field and for primitive + // values use typeof. + let value = this.getPropValue(properties[name]); + let type = (value.class || typeof value); + type = type.toLowerCase(); + + if (filter(type, value, name)) { + indexes.push(i); + } + i++; + } + } catch (err) { + console.error(err); + } + return indexes; + }, + + /** + * Get the actual value of a property. + * + * @param {Object} property + * @return {Object} Value of the property. + */ + getPropValue: function (property) { + let value = property; + if (typeof property === "object") { + let keys = Object.keys(property); + if (keys.includes("value")) { + value = property.value; + } else if (keys.includes("getterValue")) { + value = property.getterValue; + } + } + return value; + }, + + render: function () { + let object = this.props.object; + let props = this.safePropIterator(object, + (this.props.mode == "long") ? 100 : 3); + + let objectLink = this.props.objectLink || span; + if (this.props.mode == "tiny") { + return ( + span({className: "objectBox objectBox-object"}, + this.getTitle(object), + objectLink({ + className: "objectLeftBrace", + object: object + }, "") + ) + ); + } + + return ( + span({className: "objectBox objectBox-object"}, + this.getTitle(object), + objectLink({ + className: "objectLeftBrace", + object: object + }, " { "), + ...props, + objectLink({ + className: "objectRightBrace", + object: object + }, " }") + ) + ); + }, + }); + + // Registration + function supportsObject(object, type) { + if (!isGrip(object)) { + return false; + } + return (object.preview && object.preview.ownProperties); + } + + let Grip = { + rep: GripRep, + supportsObject: supportsObject + }; + + // Exports from this module + exports.Grip = Grip; +}); diff --git a/devtools/client/shared/components/reps/infinity.js b/devtools/client/shared/components/reps/infinity.js new file mode 100644 index 000000000..604e31f06 --- /dev/null +++ b/devtools/client/shared/components/reps/infinity.js @@ -0,0 +1,41 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +// Make this available to both AMD and CJS environments +define(function (require, exports, module) { + // Dependencies + const React = require("devtools/client/shared/vendor/react"); + + // Shortcuts + const { span } = React.DOM; + + /** + * Renders a Infinity object + */ + const InfinityRep = React.createClass({ + displayName: "Infinity", + + render: function () { + return ( + span({className: "objectBox objectBox-number"}, + this.props.object.type + ) + ); + } + }); + + function supportsObject(object, type) { + return type == "Infinity" || type == "-Infinity"; + } + + // Exports from this module + exports.InfinityRep = { + rep: InfinityRep, + supportsObject: supportsObject + }; +}); diff --git a/devtools/client/shared/components/reps/long-string.js b/devtools/client/shared/components/reps/long-string.js new file mode 100644 index 000000000..f19f020dc --- /dev/null +++ b/devtools/client/shared/components/reps/long-string.js @@ -0,0 +1,71 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +// Make this available to both AMD and CJS environments +define(function (require, exports, module) { + // Dependencies + const React = require("devtools/client/shared/vendor/react"); + const { sanitizeString, isGrip } = require("./rep-utils"); + // Shortcuts + const { span } = React.DOM; + + /** + * Renders a long string grip. + */ + const LongStringRep = React.createClass({ + displayName: "LongStringRep", + + propTypes: { + useQuotes: React.PropTypes.bool, + style: React.PropTypes.object, + }, + + getDefaultProps: function () { + return { + useQuotes: true, + }; + }, + + render: function () { + let { + cropLimit, + member, + object, + style, + useQuotes + } = this.props; + let {fullText, initial, length} = object; + + let config = {className: "objectBox objectBox-string"}; + if (style) { + config.style = style; + } + + let string = member && member.open + ? fullText || initial + : initial.substring(0, cropLimit); + + if (string.length < length) { + string += "\u2026"; + } + let formattedString = useQuotes ? `"${string}"` : string; + return span(config, sanitizeString(formattedString)); + }, + }); + + function supportsObject(object, type) { + if (!isGrip(object)) { + return false; + } + return object.type === "longString"; + } + + // Exports from this module + exports.LongStringRep = { + rep: LongStringRep, + supportsObject: supportsObject, + }; +}); diff --git a/devtools/client/shared/components/reps/moz.build b/devtools/client/shared/components/reps/moz.build new file mode 100644 index 000000000..f5df589f7 --- /dev/null +++ b/devtools/client/shared/components/reps/moz.build @@ -0,0 +1,40 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +DevToolsModules( + 'array.js', + 'attribute.js', + 'caption.js', + 'comment-node.js', + 'date-time.js', + 'document.js', + 'element-node.js', + 'event.js', + 'function.js', + 'grip-array.js', + 'grip-map.js', + 'grip.js', + 'infinity.js', + 'long-string.js', + 'nan.js', + 'null.js', + 'number.js', + 'object-with-text.js', + 'object-with-url.js', + 'object.js', + 'promise.js', + 'prop-rep.js', + 'regexp.js', + 'rep-utils.js', + 'rep.js', + 'reps.css', + 'string.js', + 'stylesheet.js', + 'symbol.js', + 'text-node.js', + 'undefined.js', + 'window.js', +) diff --git a/devtools/client/shared/components/reps/nan.js b/devtools/client/shared/components/reps/nan.js new file mode 100644 index 000000000..b76a5cfd3 --- /dev/null +++ b/devtools/client/shared/components/reps/nan.js @@ -0,0 +1,41 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +// Make this available to both AMD and CJS environments +define(function (require, exports, module) { + // Dependencies + const React = require("devtools/client/shared/vendor/react"); + + // Shortcuts + const { span } = React.DOM; + + /** + * Renders a NaN object + */ + const NaNRep = React.createClass({ + displayName: "NaN", + + render: function () { + return ( + span({className: "objectBox objectBox-nan"}, + "NaN" + ) + ); + } + }); + + function supportsObject(object, type) { + return type == "NaN"; + } + + // Exports from this module + exports.NaNRep = { + rep: NaNRep, + supportsObject: supportsObject + }; +}); diff --git a/devtools/client/shared/components/reps/null.js b/devtools/client/shared/components/reps/null.js new file mode 100644 index 000000000..5de00f026 --- /dev/null +++ b/devtools/client/shared/components/reps/null.js @@ -0,0 +1,46 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +// Make this available to both AMD and CJS environments +define(function (require, exports, module) { + // Dependencies + const React = require("devtools/client/shared/vendor/react"); + + // Shortcuts + const { span } = React.DOM; + + /** + * Renders null value + */ + const Null = React.createClass({ + displayName: "NullRep", + + render: function () { + return ( + span({className: "objectBox objectBox-null"}, + "null" + ) + ); + }, + }); + + function supportsObject(object, type) { + if (object && object.type && object.type == "null") { + return true; + } + + return (object == null); + } + + // Exports from this module + + exports.Null = { + rep: Null, + supportsObject: supportsObject + }; +}); diff --git a/devtools/client/shared/components/reps/number.js b/devtools/client/shared/components/reps/number.js new file mode 100644 index 000000000..31be3009b --- /dev/null +++ b/devtools/client/shared/components/reps/number.js @@ -0,0 +1,51 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +// Make this available to both AMD and CJS environments +define(function (require, exports, module) { + // Dependencies + const React = require("devtools/client/shared/vendor/react"); + + // Shortcuts + const { span } = React.DOM; + + /** + * Renders a number + */ + const Number = React.createClass({ + displayName: "Number", + + stringify: function (object) { + let isNegativeZero = Object.is(object, -0) || + (object.type && object.type == "-0"); + + return (isNegativeZero ? "-0" : String(object)); + }, + + render: function () { + let value = this.props.object; + + return ( + span({className: "objectBox objectBox-number"}, + this.stringify(value) + ) + ); + } + }); + + function supportsObject(object, type) { + return ["boolean", "number", "-0"].includes(type); + } + + // Exports from this module + + exports.Number = { + rep: Number, + supportsObject: supportsObject + }; +}); diff --git a/devtools/client/shared/components/reps/object-with-text.js b/devtools/client/shared/components/reps/object-with-text.js new file mode 100644 index 000000000..85168ce78 --- /dev/null +++ b/devtools/client/shared/components/reps/object-with-text.js @@ -0,0 +1,76 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; + +// Make this available to both AMD and CJS environments +define(function (require, exports, module) { + // ReactJS + const React = require("devtools/client/shared/vendor/react"); + + // Reps + const { isGrip } = require("./rep-utils"); + + // Shortcuts + const { span } = React.DOM; + + /** + * Renders a grip object with textual data. + */ + let ObjectWithText = React.createClass({ + displayName: "ObjectWithText", + + propTypes: { + object: React.PropTypes.object.isRequired, + }, + + getTitle: function (grip) { + if (this.props.objectLink) { + return span({className: "objectBox"}, + this.props.objectLink({ + object: grip + }, this.getType(grip) + " ") + ); + } + return ""; + }, + + getType: function (grip) { + return grip.class; + }, + + getDescription: function (grip) { + return "\"" + grip.preview.text + "\""; + }, + + render: function () { + let grip = this.props.object; + return ( + span({className: "objectBox objectBox-" + this.getType(grip)}, + this.getTitle(grip), + span({className: "objectPropValue"}, + this.getDescription(grip) + ) + ) + ); + }, + }); + + // Registration + + function supportsObject(grip, type) { + if (!isGrip(grip)) { + return false; + } + + return (grip.preview && grip.preview.kind == "ObjectWithText"); + } + + // Exports from this module + exports.ObjectWithText = { + rep: ObjectWithText, + supportsObject: supportsObject + }; +}); diff --git a/devtools/client/shared/components/reps/object-with-url.js b/devtools/client/shared/components/reps/object-with-url.js new file mode 100644 index 000000000..9c4b9a229 --- /dev/null +++ b/devtools/client/shared/components/reps/object-with-url.js @@ -0,0 +1,76 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; + +// Make this available to both AMD and CJS environments +define(function (require, exports, module) { + // ReactJS + const React = require("devtools/client/shared/vendor/react"); + + // Reps + const { isGrip, getURLDisplayString } = require("./rep-utils"); + + // Shortcuts + const { span } = React.DOM; + + /** + * Renders a grip object with URL data. + */ + let ObjectWithURL = React.createClass({ + displayName: "ObjectWithURL", + + propTypes: { + object: React.PropTypes.object.isRequired, + }, + + getTitle: function (grip) { + if (this.props.objectLink) { + return span({className: "objectBox"}, + this.props.objectLink({ + object: grip + }, this.getType(grip) + " ") + ); + } + return ""; + }, + + getType: function (grip) { + return grip.class; + }, + + getDescription: function (grip) { + return getURLDisplayString(grip.preview.url); + }, + + render: function () { + let grip = this.props.object; + return ( + span({className: "objectBox objectBox-" + this.getType(grip)}, + this.getTitle(grip), + span({className: "objectPropValue"}, + this.getDescription(grip) + ) + ) + ); + }, + }); + + // Registration + + function supportsObject(grip, type) { + if (!isGrip(grip)) { + return false; + } + + return (grip.preview && grip.preview.kind == "ObjectWithURL"); + } + + // Exports from this module + exports.ObjectWithURL = { + rep: ObjectWithURL, + supportsObject: supportsObject + }; +}); diff --git a/devtools/client/shared/components/reps/object.js b/devtools/client/shared/components/reps/object.js new file mode 100644 index 000000000..ffb1d1525 --- /dev/null +++ b/devtools/client/shared/components/reps/object.js @@ -0,0 +1,171 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; +// Make this available to both AMD and CJS environments +define(function (require, exports, module) { + // Dependencies + const React = require("devtools/client/shared/vendor/react"); + const { createFactories } = require("./rep-utils"); + const { Caption } = createFactories(require("./caption")); + const { PropRep } = createFactories(require("./prop-rep")); + // Shortcuts + const { span } = React.DOM; + /** + * Renders an object. An object is represented by a list of its + * properties enclosed in curly brackets. + */ + const Obj = React.createClass({ + displayName: "Obj", + + propTypes: { + object: React.PropTypes.object, + mode: React.PropTypes.string, + }, + + getTitle: function (object) { + let className = object && object.class ? object.class : "Object"; + if (this.props.objectLink) { + return this.props.objectLink({ + object: object + }, className); + } + return className; + }, + + safePropIterator: function (object, max) { + max = (typeof max === "undefined") ? 3 : max; + try { + return this.propIterator(object, max); + } catch (err) { + console.error(err); + } + return []; + }, + + propIterator: function (object, max) { + let isInterestingProp = (t, value) => { + // Do not pick objects, it could cause recursion. + return (t == "boolean" || t == "number" || (t == "string" && value)); + }; + + // Work around https://bugzilla.mozilla.org/show_bug.cgi?id=945377 + if (Object.prototype.toString.call(object) === "[object Generator]") { + object = Object.getPrototypeOf(object); + } + + // Object members with non-empty values are preferred since it gives the + // user a better overview of the object. + let props = this.getProps(object, max, isInterestingProp); + + if (props.length <= max) { + // There are not enough props yet (or at least, not enough props to + // be able to know whether we should print "more…" or not). + // Let's display also empty members and functions. + props = props.concat(this.getProps(object, max, (t, value) => { + return !isInterestingProp(t, value); + })); + } + + if (props.length > max) { + props.pop(); + let objectLink = this.props.objectLink || span; + + props.push(Caption({ + object: objectLink({ + object: object + }, (Object.keys(object).length - max) + " more…") + })); + } else if (props.length > 0) { + // Remove the last comma. + props[props.length - 1] = React.cloneElement( + props[props.length - 1], { delim: "" }); + } + + return props; + }, + + getProps: function (object, max, filter) { + let props = []; + + max = max || 3; + if (!object) { + return props; + } + + // Hardcode tiny mode to avoid recursive handling. + let mode = "tiny"; + + try { + for (let name in object) { + if (props.length > max) { + return props; + } + + let value; + try { + value = object[name]; + } catch (exc) { + continue; + } + + let t = typeof value; + if (filter(t, value)) { + props.push(PropRep({ + mode: mode, + name: name, + object: value, + equal: ": ", + delim: ", ", + })); + } + } + } catch (err) { + console.error(err); + } + + return props; + }, + + render: function () { + let object = this.props.object; + let props = this.safePropIterator(object); + let objectLink = this.props.objectLink || span; + + if (this.props.mode == "tiny" || !props.length) { + return ( + span({className: "objectBox objectBox-object"}, + objectLink({className: "objectTitle"}, this.getTitle(object)) + ) + ); + } + + return ( + span({className: "objectBox objectBox-object"}, + this.getTitle(object), + objectLink({ + className: "objectLeftBrace", + object: object + }, " { "), + ...props, + objectLink({ + className: "objectRightBrace", + object: object + }, " }") + ) + ); + }, + }); + function supportsObject(object, type) { + return true; + } + + // Exports from this module + exports.Obj = { + rep: Obj, + supportsObject: supportsObject + }; +}); diff --git a/devtools/client/shared/components/reps/promise.js b/devtools/client/shared/components/reps/promise.js new file mode 100644 index 000000000..0a903d366 --- /dev/null +++ b/devtools/client/shared/components/reps/promise.js @@ -0,0 +1,111 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; + +// Make this available to both AMD and CJS environments +define(function (require, exports, module) { + // ReactJS + const React = require("devtools/client/shared/vendor/react"); + // Dependencies + const { createFactories, isGrip } = require("./rep-utils"); + const { PropRep } = createFactories(require("./prop-rep")); + // Shortcuts + const { span } = React.DOM; + + /** + * Renders a DOM Promise object. + */ + const PromiseRep = React.createClass({ + displayName: "Promise", + + propTypes: { + object: React.PropTypes.object.isRequired, + mode: React.PropTypes.string, + }, + + getTitle: function (object) { + const title = object.class; + if (this.props.objectLink) { + return this.props.objectLink({ + object: object + }, title); + } + return title; + }, + + getProps: function (promiseState) { + const keys = ["state"]; + if (Object.keys(promiseState).includes("value")) { + keys.push("value"); + } + + return keys.map((key, i) => { + return PropRep(Object.assign({}, this.props, { + mode: "tiny", + name: `<${key}>`, + object: promiseState[key], + equal: ": ", + delim: i < keys.length - 1 ? ", " : "" + })); + }); + }, + + render: function () { + const object = this.props.object; + const {promiseState} = object; + let objectLink = this.props.objectLink || span; + + if (this.props.mode == "tiny") { + let { Rep } = createFactories(require("./rep")); + + return ( + span({className: "objectBox objectBox-object"}, + this.getTitle(object), + objectLink({ + className: "objectLeftBrace", + object: object + }, " { "), + Rep({object: promiseState.state}), + objectLink({ + className: "objectRightBrace", + object: object + }, " }") + ) + ); + } + + const props = this.getProps(promiseState); + return ( + span({className: "objectBox objectBox-object"}, + this.getTitle(object), + objectLink({ + className: "objectLeftBrace", + object: object + }, " { "), + ...props, + objectLink({ + className: "objectRightBrace", + object: object + }, " }") + ) + ); + }, + }); + + // Registration + function supportsObject(object, type) { + if (!isGrip(object)) { + return false; + } + return type === "Promise"; + } + + // Exports from this module + exports.PromiseRep = { + rep: PromiseRep, + supportsObject: supportsObject + }; +}); diff --git a/devtools/client/shared/components/reps/prop-rep.js b/devtools/client/shared/components/reps/prop-rep.js new file mode 100644 index 000000000..775dfea2b --- /dev/null +++ b/devtools/client/shared/components/reps/prop-rep.js @@ -0,0 +1,70 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; + +// Make this available to both AMD and CJS environments +define(function (require, exports, module) { + const React = require("devtools/client/shared/vendor/react"); + const { createFactories } = require("./rep-utils"); + const { span } = React.DOM; + + /** + * Property for Obj (local JS objects), Grip (remote JS objects) + * and GripMap (remote JS maps and weakmaps) reps. + * It's used to render object properties. + */ + let PropRep = React.createFactory(React.createClass({ + displayName: "PropRep", + + propTypes: { + // Property name. + name: React.PropTypes.oneOfType([ + React.PropTypes.string, + React.PropTypes.object, + ]).isRequired, + // Equal character rendered between property name and value. + equal: React.PropTypes.string, + // Delimiter character used to separate individual properties. + delim: React.PropTypes.string, + mode: React.PropTypes.string, + }, + + render: function () { + const { Grip } = require("./grip"); + let { Rep } = createFactories(require("./rep")); + + let key; + // The key can be a simple string, for plain objects, + // or another object for maps and weakmaps. + if (typeof this.props.name === "string") { + key = span({"className": "nodeName"}, this.props.name); + } else { + key = Rep({ + object: this.props.name, + mode: this.props.mode || "tiny", + defaultRep: Grip, + objectLink: this.props.objectLink, + }); + } + + return ( + span({}, + key, + span({ + "className": "objectEqual" + }, this.props.equal), + Rep(this.props), + span({ + "className": "objectComma" + }, this.props.delim) + ) + ); + } + })); + + // Exports from this module + exports.PropRep = PropRep; +}); diff --git a/devtools/client/shared/components/reps/regexp.js b/devtools/client/shared/components/reps/regexp.js new file mode 100644 index 000000000..2f9212658 --- /dev/null +++ b/devtools/client/shared/components/reps/regexp.js @@ -0,0 +1,63 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; + +// Make this available to both AMD and CJS environments +define(function (require, exports, module) { + // ReactJS + const React = require("devtools/client/shared/vendor/react"); + + // Reps + const { isGrip } = require("./rep-utils"); + + // Shortcuts + const { span } = React.DOM; + + /** + * Renders a grip object with regular expression. + */ + let RegExp = React.createClass({ + displayName: "regexp", + + propTypes: { + object: React.PropTypes.object.isRequired, + }, + + getSource: function (grip) { + return grip.displayString; + }, + + render: function () { + let grip = this.props.object; + let objectLink = this.props.objectLink || span; + + return ( + span({className: "objectBox objectBox-regexp"}, + objectLink({ + object: grip, + className: "regexpSource" + }, this.getSource(grip)) + ) + ); + }, + }); + + // Registration + + function supportsObject(object, type) { + if (!isGrip(object)) { + return false; + } + + return (type == "RegExp"); + } + + // Exports from this module + exports.RegExp = { + rep: RegExp, + supportsObject: supportsObject + }; +}); diff --git a/devtools/client/shared/components/reps/rep-utils.js b/devtools/client/shared/components/reps/rep-utils.js new file mode 100644 index 000000000..d9580ac8d --- /dev/null +++ b/devtools/client/shared/components/reps/rep-utils.js @@ -0,0 +1,160 @@ +/* globals URLSearchParams */ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +// Make this available to both AMD and CJS environments +define(function (require, exports, module) { + // Dependencies + const React = require("devtools/client/shared/vendor/react"); + + /** + * Create React factories for given arguments. + * Example: + * const { Rep } = createFactories(require("./rep")); + */ + function createFactories(args) { + let result = {}; + for (let p in args) { + result[p] = React.createFactory(args[p]); + } + return result; + } + + /** + * Returns true if the given object is a grip (see RDP protocol) + */ + function isGrip(object) { + return object && object.actor; + } + + function escapeNewLines(value) { + return value.replace(/\r/gm, "\\r").replace(/\n/gm, "\\n"); + } + + function cropMultipleLines(text, limit) { + return escapeNewLines(cropString(text, limit)); + } + + function cropString(text, limit, alternativeText) { + if (!alternativeText) { + alternativeText = "\u2026"; + } + + // Make sure it's a string and sanitize it. + text = sanitizeString(text + ""); + + // Crop the string only if a limit is actually specified. + if (!limit || limit <= 0) { + return text; + } + + // Set the limit at least to the length of the alternative text + // plus one character of the original text. + if (limit <= alternativeText.length) { + limit = alternativeText.length + 1; + } + + let halfLimit = (limit - alternativeText.length) / 2; + + if (text.length > limit) { + return text.substr(0, Math.ceil(halfLimit)) + alternativeText + + text.substr(text.length - Math.floor(halfLimit)); + } + + return text; + } + + function sanitizeString(text) { + // Replace all non-printable characters, except of + // (horizontal) tab (HT: \x09) and newline (LF: \x0A, CR: \x0D), + // with unicode replacement character (u+fffd). + // eslint-disable-next-line no-control-regex + let re = new RegExp("[\x00-\x08\x0B\x0C\x0E-\x1F\x80-\x9F]", "g"); + return text.replace(re, "\ufffd"); + } + + function parseURLParams(url) { + url = new URL(url); + return parseURLEncodedText(url.searchParams); + } + + function parseURLEncodedText(text) { + let params = []; + + // In case the text is empty just return the empty parameters + if (text == "") { + return params; + } + + let searchParams = new URLSearchParams(text); + let entries = [...searchParams.entries()]; + return entries.map(entry => { + return { + name: entry[0], + value: entry[1] + }; + }); + } + + function getFileName(url) { + let split = splitURLBase(url); + return split.name; + } + + function splitURLBase(url) { + if (!isDataURL(url)) { + return splitURLTrue(url); + } + return {}; + } + + function getURLDisplayString(url) { + return cropString(url); + } + + function isDataURL(url) { + return (url && url.substr(0, 5) == "data:"); + } + + function splitURLTrue(url) { + const reSplitFile = /(.*?):\/{2,3}([^\/]*)(.*?)([^\/]*?)($|\?.*)/; + let m = reSplitFile.exec(url); + + if (!m) { + return { + name: url, + path: url + }; + } else if (m[4] == "" && m[5] == "") { + return { + protocol: m[1], + domain: m[2], + path: m[3], + name: m[3] != "/" ? m[3] : m[2] + }; + } + + return { + protocol: m[1], + domain: m[2], + path: m[2] + m[3], + name: m[4] + m[5] + }; + } + + // Exports from this module + exports.createFactories = createFactories; + exports.isGrip = isGrip; + exports.cropString = cropString; + exports.cropMultipleLines = cropMultipleLines; + exports.parseURLParams = parseURLParams; + exports.parseURLEncodedText = parseURLEncodedText; + exports.getFileName = getFileName; + exports.getURLDisplayString = getURLDisplayString; + exports.sanitizeString = sanitizeString; +}); diff --git a/devtools/client/shared/components/reps/rep.js b/devtools/client/shared/components/reps/rep.js new file mode 100644 index 000000000..0891fe0ce --- /dev/null +++ b/devtools/client/shared/components/reps/rep.js @@ -0,0 +1,144 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +// Make this available to both AMD and CJS environments +define(function (require, exports, module) { + // Dependencies + const React = require("devtools/client/shared/vendor/react"); + + const { isGrip } = require("./rep-utils"); + + // Load all existing rep templates + const { Undefined } = require("./undefined"); + const { Null } = require("./null"); + const { StringRep } = require("./string"); + const { LongStringRep } = require("./long-string"); + const { Number } = require("./number"); + const { ArrayRep } = require("./array"); + const { Obj } = require("./object"); + const { SymbolRep } = require("./symbol"); + const { InfinityRep } = require("./infinity"); + const { NaNRep } = require("./nan"); + + // DOM types (grips) + const { Attribute } = require("./attribute"); + const { DateTime } = require("./date-time"); + const { Document } = require("./document"); + const { Event } = require("./event"); + const { Func } = require("./function"); + const { PromiseRep } = require("./promise"); + const { RegExp } = require("./regexp"); + const { StyleSheet } = require("./stylesheet"); + const { CommentNode } = require("./comment-node"); + const { ElementNode } = require("./element-node"); + const { TextNode } = require("./text-node"); + const { Window } = require("./window"); + const { ObjectWithText } = require("./object-with-text"); + const { ObjectWithURL } = require("./object-with-url"); + const { GripArray } = require("./grip-array"); + const { GripMap } = require("./grip-map"); + const { Grip } = require("./grip"); + + // List of all registered template. + // XXX there should be a way for extensions to register a new + // or modify an existing rep. + let reps = [ + RegExp, + StyleSheet, + Event, + DateTime, + CommentNode, + ElementNode, + TextNode, + Attribute, + LongStringRep, + Func, + PromiseRep, + ArrayRep, + Document, + Window, + ObjectWithText, + ObjectWithURL, + GripArray, + GripMap, + Grip, + Undefined, + Null, + StringRep, + Number, + SymbolRep, + InfinityRep, + NaNRep, + ]; + + /** + * Generic rep that is using for rendering native JS types or an object. + * The right template used for rendering is picked automatically according + * to the current value type. The value must be passed is as 'object' + * property. + */ + const Rep = React.createClass({ + displayName: "Rep", + + propTypes: { + object: React.PropTypes.any, + defaultRep: React.PropTypes.object, + mode: React.PropTypes.string + }, + + render: function () { + let rep = getRep(this.props.object, this.props.defaultRep); + return rep(this.props); + }, + }); + + // Helpers + + /** + * Return a rep object that is responsible for rendering given + * object. + * + * @param object {Object} Object to be rendered in the UI. This + * can be generic JS object as well as a grip (handle to a remote + * debuggee object). + * + * @param defaultObject {React.Component} The default template + * that should be used to render given object if none is found. + */ + function getRep(object, defaultRep = Obj) { + let type = typeof object; + if (type == "object" && object instanceof String) { + type = "string"; + } else if (object && type == "object" && object.type) { + type = object.type; + } + + if (isGrip(object)) { + type = object.class; + } + + for (let i = 0; i < reps.length; i++) { + let rep = reps[i]; + try { + // supportsObject could return weight (not only true/false + // but a number), which would allow to priorities templates and + // support better extensibility. + if (rep.supportsObject(object, type)) { + return React.createFactory(rep.rep); + } + } catch (err) { + console.error(err); + } + } + + return React.createFactory(defaultRep.rep); + } + + // Exports from this module + exports.Rep = Rep; +}); diff --git a/devtools/client/shared/components/reps/reps.css b/devtools/client/shared/components/reps/reps.css new file mode 100644 index 000000000..61e5e3dac --- /dev/null +++ b/devtools/client/shared/components/reps/reps.css @@ -0,0 +1,174 @@ +/* vim:set ts=2 sw=2 sts=2 et: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +.theme-dark, +.theme-light { + --number-color: var(--theme-highlight-green); + --string-color: var(--theme-highlight-orange); + --null-color: var(--theme-comment); + --object-color: var(--theme-body-color); + --caption-color: var(--theme-highlight-blue); + --location-color: var(--theme-content-color1); + --source-link-color: var(--theme-highlight-blue); + --node-color: var(--theme-highlight-bluegrey); + --reference-color: var(--theme-highlight-purple); +} + +.theme-firebug { + --number-color: #000088; + --string-color: #FF0000; + --null-color: #787878; + --object-color: DarkGreen; + --caption-color: #444444; + --location-color: #555555; + --source-link-color: blue; + --node-color: rgb(0, 0, 136); + --reference-color: rgb(102, 102, 255); +} + +/******************************************************************************/ + +.objectLink:hover { + cursor: pointer; + text-decoration: underline; +} + +.inline { + display: inline; + white-space: normal; +} + +.objectBox-object { + font-weight: bold; + color: var(--object-color); + white-space: pre-wrap; +} + +.objectBox-string, +.objectBox-symbol, +.objectBox-text, +.objectLink-textNode, +.objectBox-table { + white-space: pre-wrap; +} + +.objectBox-number, +.objectLink-styleRule, +.objectLink-element, +.objectLink-textNode, +.objectBox-array > .length { + color: var(--number-color); +} + +.objectBox-textNode, +.objectBox-string, +.objectBox-symbol { + color: var(--string-color); +} + +.objectLink-function, +.objectBox-stackTrace, +.objectLink-profile { + color: var(--object-color); +} + +.objectLink-Location { + font-style: italic; + color: var(--location-color); +} + +.objectBox-null, +.objectBox-undefined, +.objectBox-hint, +.logRowHint { + font-style: italic; + color: var(--null-color); +} + +.objectLink-sourceLink { + position: absolute; + right: 4px; + top: 2px; + padding-left: 8px; + font-weight: bold; + color: var(--source-link-color); +} + +/******************************************************************************/ + +.objectLink-event, +.objectLink-eventLog, +.objectLink-regexp, +.objectLink-object, +.objectLink-Date { + font-weight: bold; + color: var(--object-color); + white-space: pre-wrap; +} + +/******************************************************************************/ + +.objectLink-object .nodeName, +.objectLink-NamedNodeMap .nodeName, +.objectLink-NamedNodeMap .objectEqual, +.objectLink-NamedNodeMap .arrayLeftBracket, +.objectLink-NamedNodeMap .arrayRightBracket, +.objectLink-Attr .attrEqual, +.objectLink-Attr .attrTitle { + color: var(--node-color); +} + +.objectLink-object .nodeName { + font-weight: normal; +} + +/******************************************************************************/ + +.objectLeftBrace, +.objectRightBrace, +.arrayLeftBracket, +.arrayRightBracket { + cursor: pointer; + font-weight: bold; +} + +/******************************************************************************/ +/* Cycle reference*/ + +.objectLink-Reference { + font-weight: bold; + color: var(--reference-color); +} + +.objectBox-array > .objectTitle { + font-weight: bold; + color: var(--object-color); +} + +.caption { + font-weight: bold; + color: var(--caption-color); +} + +/******************************************************************************/ +/* Themes */ + +.theme-dark .objectBox-null, +.theme-dark .objectBox-undefined, +.theme-light .objectBox-null, +.theme-light .objectBox-undefined { + font-style: normal; +} + +.theme-dark .objectBox-object, +.theme-light .objectBox-object { + font-weight: normal; + white-space: pre-wrap; +} + +.theme-dark .caption, +.theme-light .caption { + font-weight: normal; +} diff --git a/devtools/client/shared/components/reps/string.js b/devtools/client/shared/components/reps/string.js new file mode 100644 index 000000000..f8b4b1986 --- /dev/null +++ b/devtools/client/shared/components/reps/string.js @@ -0,0 +1,69 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +// Make this available to both AMD and CJS environments +define(function (require, exports, module) { + // Dependencies + const React = require("devtools/client/shared/vendor/react"); + const { cropString } = require("./rep-utils"); + + // Shortcuts + const { span } = React.DOM; + + /** + * Renders a string. String value is enclosed within quotes. + */ + const StringRep = React.createClass({ + displayName: "StringRep", + + propTypes: { + useQuotes: React.PropTypes.bool, + style: React.PropTypes.object, + }, + + getDefaultProps: function () { + return { + useQuotes: true, + }; + }, + + render: function () { + let text = this.props.object; + let member = this.props.member; + let style = this.props.style; + + let config = {className: "objectBox objectBox-string"}; + if (style) { + config.style = style; + } + + if (member && member.open) { + return span(config, "\"" + text + "\""); + } + + let croppedString = this.props.cropLimit ? + cropString(text, this.props.cropLimit) : cropString(text); + + let formattedString = this.props.useQuotes ? + "\"" + croppedString + "\"" : croppedString; + + return span(config, formattedString); + }, + }); + + function supportsObject(object, type) { + return (type == "string"); + } + + // Exports from this module + + exports.StringRep = { + rep: StringRep, + supportsObject: supportsObject, + }; +}); diff --git a/devtools/client/shared/components/reps/stylesheet.js b/devtools/client/shared/components/reps/stylesheet.js new file mode 100644 index 000000000..c1fc7f1be --- /dev/null +++ b/devtools/client/shared/components/reps/stylesheet.js @@ -0,0 +1,77 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; + +// Make this available to both AMD and CJS environments +define(function (require, exports, module) { + // ReactJS + const React = require("devtools/client/shared/vendor/react"); + + // Reps + const { isGrip, getURLDisplayString } = require("./rep-utils"); + + // Shortcuts + const DOM = React.DOM; + + /** + * Renders a grip representing CSSStyleSheet + */ + let StyleSheet = React.createClass({ + displayName: "object", + + propTypes: { + object: React.PropTypes.object.isRequired, + }, + + getTitle: function (grip) { + let title = "StyleSheet "; + if (this.props.objectLink) { + return DOM.span({className: "objectBox"}, + this.props.objectLink({ + object: grip + }, title) + ); + } + return title; + }, + + getLocation: function (grip) { + // Embedded stylesheets don't have URL and so, no preview. + let url = grip.preview ? grip.preview.url : ""; + return url ? getURLDisplayString(url) : ""; + }, + + render: function () { + let grip = this.props.object; + + return ( + DOM.span({className: "objectBox objectBox-object"}, + this.getTitle(grip), + DOM.span({className: "objectPropValue"}, + this.getLocation(grip) + ) + ) + ); + }, + }); + + // Registration + + function supportsObject(object, type) { + if (!isGrip(object)) { + return false; + } + + return (type == "CSSStyleSheet"); + } + + // Exports from this module + + exports.StyleSheet = { + rep: StyleSheet, + supportsObject: supportsObject + }; +}); diff --git a/devtools/client/shared/components/reps/symbol.js b/devtools/client/shared/components/reps/symbol.js new file mode 100644 index 000000000..111794008 --- /dev/null +++ b/devtools/client/shared/components/reps/symbol.js @@ -0,0 +1,48 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +// Make this available to both AMD and CJS environments +define(function (require, exports, module) { + // Dependencies + const React = require("devtools/client/shared/vendor/react"); + + // Shortcuts + const { span } = React.DOM; + + /** + * Renders a symbol. + */ + const SymbolRep = React.createClass({ + displayName: "SymbolRep", + + propTypes: { + object: React.PropTypes.object.isRequired + }, + + render: function () { + let {object} = this.props; + let {name} = object; + + return ( + span({className: "objectBox objectBox-symbol"}, + `Symbol(${name || ""})` + ) + ); + }, + }); + + function supportsObject(object, type) { + return (type == "symbol"); + } + + // Exports from this module + exports.SymbolRep = { + rep: SymbolRep, + supportsObject: supportsObject, + }; +}); diff --git a/devtools/client/shared/components/reps/text-node.js b/devtools/client/shared/components/reps/text-node.js new file mode 100644 index 000000000..d80545cea --- /dev/null +++ b/devtools/client/shared/components/reps/text-node.js @@ -0,0 +1,94 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; + +// Make this available to both AMD and CJS environments +define(function (require, exports, module) { + // ReactJS + const React = require("devtools/client/shared/vendor/react"); + + // Reps + const { isGrip, cropString } = require("./rep-utils"); + + // Shortcuts + const DOM = React.DOM; + + /** + * Renders DOM #text node. + */ + let TextNode = React.createClass({ + displayName: "TextNode", + + propTypes: { + object: React.PropTypes.object.isRequired, + mode: React.PropTypes.string, + }, + + getTextContent: function (grip) { + return cropString(grip.preview.textContent); + }, + + getTitle: function (grip) { + if (this.props.objectLink) { + return this.props.objectLink({ + object: grip + }, "#text "); + } + return ""; + }, + + render: function () { + let grip = this.props.object; + let mode = this.props.mode || "short"; + + if (mode == "short" || mode == "tiny") { + return ( + DOM.span({className: "objectBox objectBox-textNode"}, + this.getTitle(grip), + DOM.span({className: "nodeValue"}, + "\"" + this.getTextContent(grip) + "\"" + ) + ) + ); + } + + let objectLink = this.props.objectLink || DOM.span; + return ( + DOM.span({className: "objectBox objectBox-textNode"}, + this.getTitle(grip), + objectLink({ + object: grip + }, "<"), + DOM.span({className: "nodeTag"}, "TextNode"), + " textContent=\"", + DOM.span({className: "nodeValue"}, + this.getTextContent(grip) + ), + "\"", + objectLink({ + object: grip + }, ">;") + ) + ); + }, + }); + + // Registration + + function supportsObject(grip, type) { + if (!isGrip(grip)) { + return false; + } + + return (grip.preview && grip.class == "Text"); + } + + // Exports from this module + exports.TextNode = { + rep: TextNode, + supportsObject: supportsObject + }; +}); diff --git a/devtools/client/shared/components/reps/undefined.js b/devtools/client/shared/components/reps/undefined.js new file mode 100644 index 000000000..c4e64a12c --- /dev/null +++ b/devtools/client/shared/components/reps/undefined.js @@ -0,0 +1,46 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +// Make this available to both AMD and CJS environments +define(function (require, exports, module) { + // Dependencies + const React = require("devtools/client/shared/vendor/react"); + + // Shortcuts + const { span } = React.DOM; + + /** + * Renders undefined value + */ + const Undefined = React.createClass({ + displayName: "UndefinedRep", + + render: function () { + return ( + span({className: "objectBox objectBox-undefined"}, + "undefined" + ) + ); + }, + }); + + function supportsObject(object, type) { + if (object && object.type && object.type == "undefined") { + return true; + } + + return (type == "undefined"); + } + + // Exports from this module + + exports.Undefined = { + rep: Undefined, + supportsObject: supportsObject + }; +}); diff --git a/devtools/client/shared/components/reps/window.js b/devtools/client/shared/components/reps/window.js new file mode 100644 index 000000000..628d69562 --- /dev/null +++ b/devtools/client/shared/components/reps/window.js @@ -0,0 +1,73 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; + +// Make this available to both AMD and CJS environments +define(function (require, exports, module) { + // ReactJS + const React = require("devtools/client/shared/vendor/react"); + + // Reps + const { isGrip, getURLDisplayString } = require("./rep-utils"); + + // Shortcuts + const DOM = React.DOM; + + /** + * Renders a grip representing a window. + */ + let Window = React.createClass({ + displayName: "Window", + + propTypes: { + object: React.PropTypes.object.isRequired, + }, + + getTitle: function (grip) { + if (this.props.objectLink) { + return DOM.span({className: "objectBox"}, + this.props.objectLink({ + object: grip + }, grip.class + " ") + ); + } + return ""; + }, + + getLocation: function (grip) { + return getURLDisplayString(grip.preview.url); + }, + + render: function () { + let grip = this.props.object; + + return ( + DOM.span({className: "objectBox objectBox-Window"}, + this.getTitle(grip), + DOM.span({className: "objectPropValue"}, + this.getLocation(grip) + ) + ) + ); + }, + }); + + // Registration + + function supportsObject(object, type) { + if (!isGrip(object)) { + return false; + } + + return (object.preview && type == "Window"); + } + + // Exports from this module + exports.Window = { + rep: Window, + supportsObject: supportsObject + }; +}); |