import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo

This commit is contained in:
Roy Tam 2018-01-19 03:59:58 +08:00
commit dcd9973243
150858 changed files with 23884658 additions and 0 deletions

View file

@ -0,0 +1,631 @@
/* 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/. */
/**
* validateManifest() warns of the following errors:
* - No manifest specified in page
* - Manifest is not utf-8
* - Manifest mimetype not text/cache-manifest
* - Manifest does not begin with "CACHE MANIFEST"
* - Page modified since appcache last changed
* - Duplicate entries
* - Conflicting entries e.g. in both CACHE and NETWORK sections or in cache
* but blocked by FALLBACK namespace
* - Detect referenced files that are not available
* - Detect referenced files that have cache-control set to no-store
* - Wildcards used in a section other than NETWORK
* - Spaces in URI not replaced with %20
* - Completely invalid URIs
* - Too many dot dot slash operators
* - SETTINGS section is valid
* - Invalid section name
* - etc.
*/
"use strict";
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
var { XPCOMUtils } = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {});
var { NetUtil } = Cu.import("resource://gre/modules/NetUtil.jsm", {});
var { LoadContextInfo } = Cu.import("resource://gre/modules/LoadContextInfo.jsm", {});
var { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
var { gDevTools } = require("devtools/client/framework/devtools");
var Services = require("Services");
var promise = require("promise");
var defer = require("devtools/shared/defer");
this.EXPORTED_SYMBOLS = ["AppCacheUtils"];
function AppCacheUtils(documentOrUri) {
this._parseManifest = this._parseManifest.bind(this);
if (documentOrUri) {
if (typeof documentOrUri == "string") {
this.uri = documentOrUri;
}
if (/HTMLDocument/.test(documentOrUri.toString())) {
this.doc = documentOrUri;
}
}
}
AppCacheUtils.prototype = {
get cachePath() {
return "";
},
validateManifest: function ACU_validateManifest() {
let deferred = defer();
this.errors = [];
// Check for missing manifest.
this._getManifestURI().then(manifestURI => {
this.manifestURI = manifestURI;
if (!this.manifestURI) {
this._addError(0, "noManifest");
deferred.resolve(this.errors);
}
this._getURIInfo(this.manifestURI).then(uriInfo => {
this._parseManifest(uriInfo).then(() => {
// Sort errors by line number.
this.errors.sort(function (a, b) {
return a.line - b.line;
});
deferred.resolve(this.errors);
});
});
});
return deferred.promise;
},
_parseManifest: function ACU__parseManifest(uriInfo) {
let deferred = defer();
let manifestName = uriInfo.name;
let manifestLastModified = new Date(uriInfo.responseHeaders["Last-Modified"]);
if (uriInfo.charset.toLowerCase() != "utf-8") {
this._addError(0, "notUTF8", uriInfo.charset);
}
if (uriInfo.mimeType != "text/cache-manifest") {
this._addError(0, "badMimeType", uriInfo.mimeType);
}
let parser = new ManifestParser(uriInfo.text, this.manifestURI);
let parsed = parser.parse();
if (parsed.errors.length > 0) {
this.errors.push.apply(this.errors, parsed.errors);
}
// Check for duplicate entries.
let dupes = {};
for (let parsedUri of parsed.uris) {
dupes[parsedUri.uri] = dupes[parsedUri.uri] || [];
dupes[parsedUri.uri].push({
line: parsedUri.line,
section: parsedUri.section,
original: parsedUri.original
});
}
for (let [uri, value] of Object.entries(dupes)) {
if (value.length > 1) {
this._addError(0, "duplicateURI", uri, JSON.stringify(value));
}
}
// Loop through network entries making sure that fallback and cache don't
// contain uris starting with the network uri.
for (let neturi of parsed.uris) {
if (neturi.section == "NETWORK") {
for (let parsedUri of parsed.uris) {
if (parsedUri.section !== "NETWORK" &&
parsedUri.uri.startsWith(neturi.uri)) {
this._addError(neturi.line, "networkBlocksURI", neturi.line,
neturi.original, parsedUri.line, parsedUri.original,
parsedUri.section);
}
}
}
}
// Loop through fallback entries making sure that fallback and cache don't
// contain uris starting with the network uri.
for (let fb of parsed.fallbacks) {
for (let parsedUri of parsed.uris) {
if (parsedUri.uri.startsWith(fb.namespace)) {
this._addError(fb.line, "fallbackBlocksURI", fb.line,
fb.original, parsedUri.line, parsedUri.original,
parsedUri.section);
}
}
}
// Check that all resources exist and that their cach-control headers are
// not set to no-store.
let current = -1;
for (let i = 0, len = parsed.uris.length; i < len; i++) {
let parsedUri = parsed.uris[i];
this._getURIInfo(parsedUri.uri).then(uriInfo => {
current++;
if (uriInfo.success) {
// Check that the resource was not modified after the manifest was last
// modified. If it was then the manifest file should be refreshed.
let resourceLastModified =
new Date(uriInfo.responseHeaders["Last-Modified"]);
if (manifestLastModified < resourceLastModified) {
this._addError(parsedUri.line, "fileChangedButNotManifest",
uriInfo.name, manifestName, parsedUri.line);
}
// If cache-control: no-store the file will not be added to the
// appCache.
if (uriInfo.nocache) {
this._addError(parsedUri.line, "cacheControlNoStore",
parsedUri.original, parsedUri.line);
}
} else if (parsedUri.original !== "*") {
this._addError(parsedUri.line, "notAvailable",
parsedUri.original, parsedUri.line);
}
if (current == len - 1) {
deferred.resolve();
}
});
}
return deferred.promise;
},
_getURIInfo: function ACU__getURIInfo(uri) {
let inputStream = Cc["@mozilla.org/scriptableinputstream;1"]
.createInstance(Ci.nsIScriptableInputStream);
let deferred = defer();
let buffer = "";
var channel = NetUtil.newChannel({
uri: uri,
loadUsingSystemPrincipal: true,
securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL
});
// Avoid the cache:
channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE;
channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
channel.asyncOpen2({
onStartRequest: function (request, context) {
// This empty method is needed in order for onDataAvailable to be
// called.
},
onDataAvailable: function (request, context, stream, offset, count) {
request.QueryInterface(Ci.nsIHttpChannel);
inputStream.init(stream);
buffer = buffer.concat(inputStream.read(count));
},
onStopRequest: function onStartRequest(request, context, statusCode) {
if (statusCode === 0) {
request.QueryInterface(Ci.nsIHttpChannel);
let result = {
name: request.name,
success: request.requestSucceeded,
status: request.responseStatus + " - " + request.responseStatusText,
charset: request.contentCharset || "utf-8",
mimeType: request.contentType,
contentLength: request.contentLength,
nocache: request.isNoCacheResponse() || request.isNoStoreResponse(),
prePath: request.URI.prePath + "/",
text: buffer
};
result.requestHeaders = {};
request.visitRequestHeaders(function (header, value) {
result.requestHeaders[header] = value;
});
result.responseHeaders = {};
request.visitResponseHeaders(function (header, value) {
result.responseHeaders[header] = value;
});
deferred.resolve(result);
} else {
deferred.resolve({
name: request.name,
success: false
});
}
}
});
return deferred.promise;
},
listEntries: function ACU_show(searchTerm) {
if (!Services.prefs.getBoolPref("browser.cache.disk.enable")) {
throw new Error(l10n.GetStringFromName("cacheDisabled"));
}
let entries = [];
let appCacheStorage = Services.cache2.appCacheStorage(LoadContextInfo.default, null);
appCacheStorage.asyncVisitStorage({
onCacheStorageInfo: function () {},
onCacheEntryInfo: function (aURI, aIdEnhance, aDataSize, aFetchCount, aLastModifiedTime, aExpirationTime) {
let lowerKey = aURI.asciiSpec.toLowerCase();
if (searchTerm && lowerKey.indexOf(searchTerm.toLowerCase()) == -1) {
return;
}
if (aIdEnhance) {
aIdEnhance += ":";
}
let entry = {
"deviceID": "offline",
"key": aIdEnhance + aURI.asciiSpec,
"fetchCount": aFetchCount,
"lastFetched": null,
"lastModified": new Date(aLastModifiedTime * 1000),
"expirationTime": new Date(aExpirationTime * 1000),
"dataSize": aDataSize
};
entries.push(entry);
return true;
}
}, true);
if (entries.length === 0) {
throw new Error(l10n.GetStringFromName("noResults"));
}
return entries;
},
viewEntry: function ACU_viewEntry(key) {
let wm = Cc["@mozilla.org/appshell/window-mediator;1"]
.getService(Ci.nsIWindowMediator);
let win = wm.getMostRecentWindow(gDevTools.chromeWindowType);
let url = "about:cache-entry?storage=appcache&context=&eid=&uri=" + key;
win.openUILinkIn(url, "tab");
},
clearAll: function ACU_clearAll() {
if (!Services.prefs.getBoolPref("browser.cache.disk.enable")) {
throw new Error(l10n.GetStringFromName("cacheDisabled"));
}
let appCacheStorage = Services.cache2.appCacheStorage(LoadContextInfo.default, null);
appCacheStorage.asyncEvictStorage({
onCacheEntryDoomed: function (result) {}
});
},
_getManifestURI: function ACU__getManifestURI() {
let deferred = defer();
let getURI = () => {
let htmlNode = this.doc.querySelector("html[manifest]");
if (htmlNode) {
let pageUri = this.doc.location ? this.doc.location.href : this.uri;
let origin = pageUri.substr(0, pageUri.lastIndexOf("/") + 1);
let manifestURI = htmlNode.getAttribute("manifest");
if (manifestURI.startsWith("/")) {
manifestURI = manifestURI.substr(1);
}
return origin + manifestURI;
}
};
if (this.doc) {
let uri = getURI();
return promise.resolve(uri);
} else {
this._getURIInfo(this.uri).then(uriInfo => {
if (uriInfo.success) {
let html = uriInfo.text;
let parser = _DOMParser;
this.doc = parser.parseFromString(html, "text/html");
let uri = getURI();
deferred.resolve(uri);
} else {
this.errors.push({
line: 0,
msg: l10n.GetStringFromName("invalidURI")
});
}
});
}
return deferred.promise;
},
_addError: function ACU__addError(line, l10nString, ...params) {
let msg;
if (params) {
msg = l10n.formatStringFromName(l10nString, params, params.length);
} else {
msg = l10n.GetStringFromName(l10nString);
}
this.errors.push({
line: line,
msg: msg
});
},
};
/**
* We use our own custom parser because we need far more detailed information
* than the system manifest parser provides.
*
* @param {String} manifestText
* The text content of the manifest file.
* @param {String} manifestURI
* The URI of the manifest file. This is used in calculating the path of
* relative URIs.
*/
function ManifestParser(manifestText, manifestURI) {
this.manifestText = manifestText;
this.origin = manifestURI.substr(0, manifestURI.lastIndexOf("/") + 1)
.replace(" ", "%20");
}
ManifestParser.prototype = {
parse: function OCIMP_parse() {
let lines = this.manifestText.split(/\r?\n/);
let fallbacks = this.fallbacks = [];
let settings = this.settings = [];
let errors = this.errors = [];
let uris = this.uris = [];
this.currSection = "CACHE";
for (let i = 0; i < lines.length; i++) {
let text = this.text = lines[i].trim();
this.currentLine = i + 1;
if (i === 0 && text !== "CACHE MANIFEST") {
this._addError(1, "firstLineMustBeCacheManifest", 1);
}
// Ignore comments
if (/^#/.test(text) || !text.length) {
continue;
}
if (text == "CACHE MANIFEST") {
if (this.currentLine != 1) {
this._addError(this.currentLine, "cacheManifestOnlyFirstLine2",
this.currentLine);
}
continue;
}
if (this._maybeUpdateSectionName()) {
continue;
}
switch (this.currSection) {
case "CACHE":
case "NETWORK":
this.parseLine();
break;
case "FALLBACK":
this.parseFallbackLine();
break;
case "SETTINGS":
this.parseSettingsLine();
break;
}
}
return {
uris: uris,
fallbacks: fallbacks,
settings: settings,
errors: errors
};
},
parseLine: function OCIMP_parseLine() {
let text = this.text;
if (text.indexOf("*") != -1) {
if (this.currSection != "NETWORK" || text.length != 1) {
this._addError(this.currentLine, "asteriskInWrongSection2",
this.currSection, this.currentLine);
return;
}
}
if (/\s/.test(text)) {
this._addError(this.currentLine, "escapeSpaces", this.currentLine);
text = text.replace(/\s/g, "%20");
}
if (text[0] == "/") {
if (text.substr(0, 4) == "/../") {
this._addError(this.currentLine, "slashDotDotSlashBad", this.currentLine);
} else {
this.uris.push(this._wrapURI(this.origin + text.substring(1)));
}
} else if (text.substr(0, 2) == "./") {
this.uris.push(this._wrapURI(this.origin + text.substring(2)));
} else if (text.substr(0, 4) == "http") {
this.uris.push(this._wrapURI(text));
} else {
let origin = this.origin;
let path = text;
while (path.substr(0, 3) == "../" && /^https?:\/\/.*?\/.*?\//.test(origin)) {
let trimIdx = origin.substr(0, origin.length - 1).lastIndexOf("/") + 1;
origin = origin.substr(0, trimIdx);
path = path.substr(3);
}
if (path.substr(0, 3) == "../") {
this._addError(this.currentLine, "tooManyDotDotSlashes", this.currentLine);
return;
}
if (/^https?:\/\//.test(path)) {
this.uris.push(this._wrapURI(path));
return;
}
this.uris.push(this._wrapURI(origin + path));
}
},
parseFallbackLine: function OCIMP_parseFallbackLine() {
let split = this.text.split(/\s+/);
let origURI = this.text;
if (split.length != 2) {
this._addError(this.currentLine, "fallbackUseSpaces", this.currentLine);
return;
}
let [ namespace, fallback ] = split;
if (namespace.indexOf("*") != -1) {
this._addError(this.currentLine, "fallbackAsterisk2", this.currentLine);
}
if (/\s/.test(namespace)) {
this._addError(this.currentLine, "escapeSpaces", this.currentLine);
namespace = namespace.replace(/\s/g, "%20");
}
if (namespace.substr(0, 4) == "/../") {
this._addError(this.currentLine, "slashDotDotSlashBad", this.currentLine);
}
if (namespace.substr(0, 2) == "./") {
namespace = this.origin + namespace.substring(2);
}
if (namespace.substr(0, 4) != "http") {
let origin = this.origin;
let path = namespace;
while (path.substr(0, 3) == "../" && /^https?:\/\/.*?\/.*?\//.test(origin)) {
let trimIdx = origin.substr(0, origin.length - 1).lastIndexOf("/") + 1;
origin = origin.substr(0, trimIdx);
path = path.substr(3);
}
if (path.substr(0, 3) == "../") {
this._addError(this.currentLine, "tooManyDotDotSlashes", this.currentLine);
}
if (/^https?:\/\//.test(path)) {
namespace = path;
} else {
if (path[0] == "/") {
path = path.substring(1);
}
namespace = origin + path;
}
}
this.text = fallback;
this.parseLine();
this.fallbacks.push({
line: this.currentLine,
original: origURI,
namespace: namespace,
fallback: fallback
});
},
parseSettingsLine: function OCIMP_parseSettingsLine() {
let text = this.text;
if (this.settings.length == 1 || !/prefer-online|fast/.test(text)) {
this._addError(this.currentLine, "settingsBadValue", this.currentLine);
return;
}
switch (text) {
case "prefer-online":
this.settings.push(this._wrapURI(text));
break;
case "fast":
this.settings.push(this._wrapURI(text));
break;
}
},
_wrapURI: function OCIMP__wrapURI(uri) {
return {
section: this.currSection,
line: this.currentLine,
uri: uri,
original: this.text
};
},
_addError: function OCIMP__addError(line, l10nString, ...params) {
let msg;
if (params) {
msg = l10n.formatStringFromName(l10nString, params, params.length);
} else {
msg = l10n.GetStringFromName(l10nString);
}
this.errors.push({
line: line,
msg: msg
});
},
_maybeUpdateSectionName: function OCIMP__maybeUpdateSectionName() {
let text = this.text;
if (text == text.toUpperCase() && text.charAt(text.length - 1) == ":") {
text = text.substr(0, text.length - 1);
switch (text) {
case "CACHE":
case "NETWORK":
case "FALLBACK":
case "SETTINGS":
this.currSection = text;
return true;
default:
this._addError(this.currentLine,
"invalidSectionName", text, this.currentLine);
return false;
}
}
},
};
XPCOMUtils.defineLazyGetter(this, "l10n", () => Services.strings
.createBundle("chrome://devtools/locale/appcacheutils.properties"));
XPCOMUtils.defineLazyGetter(this, "appcacheservice", function () {
return Cc["@mozilla.org/network/application-cache-service;1"]
.getService(Ci.nsIApplicationCacheService);
});
XPCOMUtils.defineLazyGetter(this, "_DOMParser", function () {
return Cc["@mozilla.org/xmlextras/domparser;1"].createInstance(Ci.nsIDOMParser);
});

View file

@ -0,0 +1,166 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const Ci = Components.interfaces;
const Cu = Components.utils;
const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
const { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
const nodeFilterConstants = require("devtools/shared/dom-node-filter-constants");
this.EXPORTED_SYMBOLS = ["DOMHelpers"];
/**
* DOMHelpers
* Makes DOM traversal easier. Goes through iframes.
*
* @constructor
* @param nsIDOMWindow aWindow
* The content window, owning the document to traverse.
*/
this.DOMHelpers = function DOMHelpers(aWindow) {
if (!aWindow) {
throw new Error("window can't be null or undefined");
}
this.window = aWindow;
};
DOMHelpers.prototype = {
getParentObject: function Helpers_getParentObject(node)
{
let parentNode = node ? node.parentNode : null;
if (!parentNode) {
// Documents have no parentNode; Attr, Document, DocumentFragment, Entity,
// and Notation. top level windows have no parentNode
if (node && node == this.window.Node.DOCUMENT_NODE) {
// document type
if (node.defaultView) {
let embeddingFrame = node.defaultView.frameElement;
if (embeddingFrame)
return embeddingFrame.parentNode;
}
}
// a Document object without a parentNode or window
return null; // top level has no parent
}
if (parentNode.nodeType == this.window.Node.DOCUMENT_NODE) {
if (parentNode.defaultView) {
return parentNode.defaultView.frameElement;
}
// parent is document element, but no window at defaultView.
return null;
}
if (!parentNode.localName)
return null;
return parentNode;
},
getChildObject: function Helpers_getChildObject(node, index, previousSibling,
showTextNodesWithWhitespace)
{
if (!node)
return null;
if (node.contentDocument) {
// then the node is a frame
if (index == 0) {
return node.contentDocument.documentElement; // the node's HTMLElement
}
return null;
}
if (node.getSVGDocument) {
let svgDocument = node.getSVGDocument();
if (svgDocument) {
// then the node is a frame
if (index == 0) {
return svgDocument.documentElement; // the node's SVGElement
}
return null;
}
}
let child = null;
if (previousSibling) // then we are walking
child = this.getNextSibling(previousSibling);
else
child = this.getFirstChild(node);
if (showTextNodesWithWhitespace)
return child;
for (; child; child = this.getNextSibling(child)) {
if (!this.isWhitespaceText(child))
return child;
}
return null; // we have no children worth showing.
},
getFirstChild: function Helpers_getFirstChild(node)
{
let SHOW_ALL = nodeFilterConstants.SHOW_ALL;
this.treeWalker = node.ownerDocument.createTreeWalker(node,
SHOW_ALL, null);
return this.treeWalker.firstChild();
},
getNextSibling: function Helpers_getNextSibling(node)
{
let next = this.treeWalker.nextSibling();
if (!next)
delete this.treeWalker;
return next;
},
isWhitespaceText: function Helpers_isWhitespaceText(node)
{
return node.nodeType == this.window.Node.TEXT_NODE &&
!/[^\s]/.exec(node.nodeValue);
},
destroy: function Helpers_destroy()
{
delete this.window;
delete this.treeWalker;
},
/**
* A simple way to be notified (once) when a window becomes
* interactive (DOMContentLoaded).
*
* It is based on the chromeEventHandler. This is useful when
* chrome iframes are loaded in content docshells (in Firefox
* tabs for example).
*/
onceDOMReady: function Helpers_onLocationChange(callback, targetURL) {
let window = this.window;
let docShell = window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShell);
let onReady = function (event) {
if (event.target == window.document) {
docShell.chromeEventHandler.removeEventListener("DOMContentLoaded", onReady, false);
// If in `callback` the URL of the window is changed and a listener to DOMContentLoaded
// is attached, the event we just received will be also be caught by the new listener.
// We want to avoid that so we execute the callback in the next queue.
Services.tm.mainThread.dispatch(callback, 0);
}
};
if ((window.document.readyState == "complete" ||
window.document.readyState == "interactive") &&
window.location.href == targetURL) {
Services.tm.mainThread.dispatch(callback, 0);
} else {
docShell.chromeEventHandler.addEventListener("DOMContentLoaded", onReady, false);
}
}
};

View file

@ -0,0 +1,16 @@
/* 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";
/*
* JS Beautifier. Please use require("devtools/shared/jsbeautify/beautify") instead of
* this JSM.
*/
this.EXPORTED_SYMBOLS = [ "jsBeautify" ];
const { require } = Components.utils.import("resource://devtools/shared/Loader.jsm", {});
const { beautify } = require("devtools/shared/jsbeautify/beautify");
const jsBeautify = beautify.js;

View file

@ -0,0 +1,312 @@
/* 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/. */
"use strict";
const Cu = Components.utils;
const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
const {KeyCodes} = require("devtools/client/shared/keycodes");
this.EXPORTED_SYMBOLS = ["SplitView"];
/* this must be kept in sync with CSS (ie. splitview.css) */
const LANDSCAPE_MEDIA_QUERY = "(min-width: 701px)";
var bindings = new WeakMap();
/**
* SplitView constructor
*
* Initialize the split view UI on an existing DOM element.
*
* A split view contains items, each of those having one summary and one details
* elements.
* It is adaptive as it behaves similarly to a richlistbox when there the aspect
* ratio is narrow or as a pair listbox-box otherwise.
*
* @param DOMElement aRoot
* @see appendItem
*/
this.SplitView = function SplitView(aRoot)
{
this._root = aRoot;
this._controller = aRoot.querySelector(".splitview-controller");
this._nav = aRoot.querySelector(".splitview-nav");
this._side = aRoot.querySelector(".splitview-side-details");
this._activeSummary = null;
this._mql = aRoot.ownerDocument.defaultView.matchMedia(LANDSCAPE_MEDIA_QUERY);
// items list focus and search-on-type handling
this._nav.addEventListener("keydown", (aEvent) => {
function getFocusedItemWithin(nav) {
let node = nav.ownerDocument.activeElement;
while (node && node.parentNode != nav) {
node = node.parentNode;
}
return node;
}
// do not steal focus from inside iframes or textboxes
if (aEvent.target.ownerDocument != this._nav.ownerDocument ||
aEvent.target.tagName == "input" ||
aEvent.target.tagName == "textbox" ||
aEvent.target.tagName == "textarea" ||
aEvent.target.classList.contains("textbox")) {
return false;
}
// handle keyboard navigation within the items list
let newFocusOrdinal;
if (aEvent.keyCode == KeyCodes.DOM_VK_PAGE_UP ||
aEvent.keyCode == KeyCodes.DOM_VK_HOME) {
newFocusOrdinal = 0;
} else if (aEvent.keyCode == KeyCodes.DOM_VK_PAGE_DOWN ||
aEvent.keyCode == KeyCodes.DOM_VK_END) {
newFocusOrdinal = this._nav.childNodes.length - 1;
} else if (aEvent.keyCode == KeyCodes.DOM_VK_UP) {
newFocusOrdinal = getFocusedItemWithin(this._nav).getAttribute("data-ordinal");
newFocusOrdinal--;
} else if (aEvent.keyCode == KeyCodes.DOM_VK_DOWN) {
newFocusOrdinal = getFocusedItemWithin(this._nav).getAttribute("data-ordinal");
newFocusOrdinal++;
}
if (newFocusOrdinal !== undefined) {
aEvent.stopPropagation();
let el = this.getSummaryElementByOrdinal(newFocusOrdinal);
if (el) {
el.focus();
}
return false;
}
}, false);
};
SplitView.prototype = {
/**
* Retrieve whether the UI currently has a landscape orientation.
*
* @return boolean
*/
get isLandscape()
{
return this._mql.matches;
},
/**
* Retrieve the root element.
*
* @return DOMElement
*/
get rootElement()
{
return this._root;
},
/**
* Retrieve the active item's summary element or null if there is none.
*
* @return DOMElement
*/
get activeSummary()
{
return this._activeSummary;
},
/**
* Set the active item's summary element.
*
* @param DOMElement aSummary
*/
set activeSummary(aSummary)
{
if (aSummary == this._activeSummary) {
return;
}
if (this._activeSummary) {
let binding = bindings.get(this._activeSummary);
if (binding.onHide) {
binding.onHide(this._activeSummary, binding._details, binding.data);
}
this._activeSummary.classList.remove("splitview-active");
binding._details.classList.remove("splitview-active");
}
if (!aSummary) {
return;
}
let binding = bindings.get(aSummary);
aSummary.classList.add("splitview-active");
binding._details.classList.add("splitview-active");
this._activeSummary = aSummary;
if (binding.onShow) {
binding.onShow(aSummary, binding._details, binding.data);
}
},
/**
* Retrieve the active item's details element or null if there is none.
* @return DOMElement
*/
get activeDetails()
{
let summary = this.activeSummary;
return summary ? bindings.get(summary)._details : null;
},
/**
* Retrieve the summary element for a given ordinal.
*
* @param number aOrdinal
* @return DOMElement
* Summary element with given ordinal or null if not found.
* @see appendItem
*/
getSummaryElementByOrdinal: function SEC_getSummaryElementByOrdinal(aOrdinal)
{
return this._nav.querySelector("* > li[data-ordinal='" + aOrdinal + "']");
},
/**
* Append an item to the split view.
*
* @param DOMElement aSummary
* The summary element for the item.
* @param DOMElement aDetails
* The details element for the item.
* @param object aOptions
* Optional object that defines custom behavior and data for the item.
* All properties are optional :
* - function(DOMElement summary, DOMElement details, object data) onCreate
* Called when the item has been added.
* - function(summary, details, data) onShow
* Called when the item is shown/active.
* - function(summary, details, data) onHide
* Called when the item is hidden/inactive.
* - function(summary, details, data) onDestroy
* Called when the item has been removed.
* - object data
* Object to pass to the callbacks above.
* - number ordinal
* Items with a lower ordinal are displayed before those with a
* higher ordinal.
*/
appendItem: function ASV_appendItem(aSummary, aDetails, aOptions)
{
let binding = aOptions || {};
binding._summary = aSummary;
binding._details = aDetails;
bindings.set(aSummary, binding);
this._nav.appendChild(aSummary);
aSummary.addEventListener("click", (aEvent) => {
aEvent.stopPropagation();
this.activeSummary = aSummary;
}, false);
this._side.appendChild(aDetails);
if (binding.onCreate) {
binding.onCreate(aSummary, aDetails, binding.data);
}
},
/**
* Append an item to the split view according to two template elements
* (one for the item's summary and the other for the item's details).
*
* @param string aName
* Name of the template elements to instantiate.
* Requires two (hidden) DOM elements with id "splitview-tpl-summary-"
* and "splitview-tpl-details-" suffixed with aName.
* @param object aOptions
* Optional object that defines custom behavior and data for the item.
* See appendItem for full description.
* @return object{summary:,details:}
* Object with the new DOM elements created for summary and details.
* @see appendItem
*/
appendTemplatedItem: function ASV_appendTemplatedItem(aName, aOptions)
{
aOptions = aOptions || {};
let summary = this._root.querySelector("#splitview-tpl-summary-" + aName);
let details = this._root.querySelector("#splitview-tpl-details-" + aName);
summary = summary.cloneNode(true);
summary.id = "";
if (aOptions.ordinal !== undefined) { // can be zero
summary.style.MozBoxOrdinalGroup = aOptions.ordinal;
summary.setAttribute("data-ordinal", aOptions.ordinal);
}
details = details.cloneNode(true);
details.id = "";
this.appendItem(summary, details, aOptions);
return {summary: summary, details: details};
},
/**
* Remove an item from the split view.
*
* @param DOMElement aSummary
* Summary element of the item to remove.
*/
removeItem: function ASV_removeItem(aSummary)
{
if (aSummary == this._activeSummary) {
this.activeSummary = null;
}
let binding = bindings.get(aSummary);
aSummary.parentNode.removeChild(aSummary);
binding._details.parentNode.removeChild(binding._details);
if (binding.onDestroy) {
binding.onDestroy(aSummary, binding._details, binding.data);
}
},
/**
* Remove all items from the split view.
*/
removeAll: function ASV_removeAll()
{
while (this._nav.hasChildNodes()) {
this.removeItem(this._nav.firstChild);
}
},
/**
* Set the item's CSS class name.
* This sets the class on both the summary and details elements, retaining
* any SplitView-specific classes.
*
* @param DOMElement aSummary
* Summary element of the item to set.
* @param string aClassName
* One or more space-separated CSS classes.
*/
setItemClassName: function ASV_setItemClassName(aSummary, aClassName)
{
let binding = bindings.get(aSummary);
let viewSpecific;
viewSpecific = aSummary.className.match(/(splitview\-[\w-]+)/g);
viewSpecific = viewSpecific ? viewSpecific.join(" ") : "";
aSummary.className = viewSpecific + " " + aClassName;
viewSpecific = binding._details.className.match(/(splitview\-[\w-]+)/g);
viewSpecific = viewSpecific ? viewSpecific.join(" ") : "";
binding._details.className = viewSpecific + " " + aClassName;
},
};

View file

@ -0,0 +1,599 @@
/* 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";
const HTML_NS = "http://www.w3.org/1999/xhtml";
const Services = require("Services");
const {gDevTools} = require("devtools/client/framework/devtools");
const {HTMLTooltip} = require("devtools/client/shared/widgets/tooltip/HTMLTooltip");
const EventEmitter = require("devtools/shared/event-emitter");
let itemIdCounter = 0;
/**
* Autocomplete popup UI implementation.
*
* @constructor
* @param {Document} toolboxDoc
* The toolbox document to attach the autocomplete popup panel.
* @param {Object} options
* An object consiting any of the following options:
* - listId {String} The id for the list <LI> element.
* - position {String} The position for the tooltip ("top" or "bottom").
* - theme {String} String related to the theme of the popup
* - autoSelect {Boolean} Boolean to allow the first entry of the popup
* panel to be automatically selected when the popup shows.
* - onSelect {String} Callback called when the selected index is updated.
* - onClick {String} Callback called when the autocomplete popup receives a click
* event. The selectedIndex will already be updated if need be.
*/
function AutocompletePopup(toolboxDoc, options = {}) {
EventEmitter.decorate(this);
this._document = toolboxDoc;
this.autoSelect = options.autoSelect || false;
this.position = options.position || "bottom";
let theme = options.theme || "dark";
this.onSelectCallback = options.onSelect;
this.onClickCallback = options.onClick;
// If theme is auto, use the devtools.theme pref
if (theme === "auto") {
theme = Services.prefs.getCharPref("devtools.theme");
this.autoThemeEnabled = true;
// Setup theme change listener.
this._handleThemeChange = this._handleThemeChange.bind(this);
gDevTools.on("pref-changed", this._handleThemeChange);
}
// Create HTMLTooltip instance
this._tooltip = new HTMLTooltip(this._document);
this._tooltip.panel.classList.add(
"devtools-autocomplete-popup",
"devtools-monospace",
theme + "-theme");
// Stop this appearing as an alert to accessibility.
this._tooltip.panel.setAttribute("role", "presentation");
this._list = this._document.createElementNS(HTML_NS, "ul");
this._list.setAttribute("flex", "1");
// The list clone will be inserted in the same document as the anchor, and will receive
// a copy of the main list innerHTML to allow screen readers to access the list.
this._listClone = this._document.createElementNS(HTML_NS, "ul");
this._listClone.className = "devtools-autocomplete-list-aria-clone";
if (options.listId) {
this._list.setAttribute("id", options.listId);
}
this._list.className = "devtools-autocomplete-listbox " + theme + "-theme";
this._tooltip.setContent(this._list);
this.onClick = this.onClick.bind(this);
this._list.addEventListener("click", this.onClick, false);
// Array of raw autocomplete items
this.items = [];
// Map of autocompleteItem to HTMLElement
this.elements = new WeakMap();
this.selectedIndex = -1;
}
exports.AutocompletePopup = AutocompletePopup;
AutocompletePopup.prototype = {
_document: null,
_tooltip: null,
_list: null,
onSelect: function (e) {
if (this.onSelectCallback) {
this.onSelectCallback(e);
}
},
onClick: function (e) {
let item = e.target.closest(".autocomplete-item");
if (item && typeof item.dataset.index !== "undefined") {
this.selectedIndex = parseInt(item.dataset.index, 10);
}
this.emit("popup-click");
if (this.onClickCallback) {
this.onClickCallback(e);
}
},
/**
* Open the autocomplete popup panel.
*
* @param {nsIDOMNode} anchor
* Optional node to anchor the panel to.
* @param {Number} xOffset
* Horizontal offset in pixels from the left of the node to the left
* of the popup.
* @param {Number} yOffset
* Vertical offset in pixels from the top of the node to the starting
* of the popup.
* @param {Number} index
* The position of item to select.
*/
openPopup: function (anchor, xOffset = 0, yOffset = 0, index) {
this.__maxLabelLength = -1;
this._updateSize();
// Retrieve the anchor's document active element to add accessibility metadata.
this._activeElement = anchor.ownerDocument.activeElement;
this._tooltip.show(anchor, {
x: xOffset,
y: yOffset,
position: this.position,
});
this._tooltip.once("shown", () => {
if (this.autoSelect) {
this.selectItemAtIndex(index);
}
this.emit("popup-opened");
});
},
/**
* Select item at the provided index.
*
* @param {Number} index
* The position of the item to select.
*/
selectItemAtIndex: function (index) {
if (typeof index !== "number") {
// If no index was provided, select the item closest to the input.
let isAboveInput = this.position === "top";
index = isAboveInput ? this.itemCount - 1 : 0;
}
this.selectedIndex = index;
},
/**
* Hide the autocomplete popup panel.
*/
hidePopup: function () {
this._tooltip.once("hidden", () => {
this.emit("popup-closed");
});
this._clearActiveDescendant();
this._activeElement = null;
this._tooltip.hide();
},
/**
* Check if the autocomplete popup is open.
*/
get isOpen() {
return this._tooltip && this._tooltip.isVisible();
},
/**
* Destroy the object instance. Please note that the panel DOM elements remain
* in the DOM, because they might still be in use by other instances of the
* same code. It is the responsability of the client code to perform DOM
* cleanup.
*/
destroy: function () {
if (this.isOpen) {
this.hidePopup();
}
this._list.removeEventListener("click", this.onClick, false);
if (this.autoThemeEnabled) {
gDevTools.off("pref-changed", this._handleThemeChange);
}
this._list.remove();
this._listClone.remove();
this._tooltip.destroy();
this._document = null;
this._list = null;
this._tooltip = null;
},
/**
* Get the autocomplete items array.
*
* @param {Number} index
* The index of the item what is wanted.
*
* @return {Object} The autocomplete item at index index.
*/
getItemAtIndex: function (index) {
return this.items[index];
},
/**
* Get the autocomplete items array.
*
* @return {Array} The array of autocomplete items.
*/
getItems: function () {
// Return a copy of the array to avoid side effects from the caller code.
return this.items.slice(0);
},
/**
* Set the autocomplete items list, in one go.
*
* @param {Array} items
* The list of items you want displayed in the popup list.
* @param {Number} index
* The position of the item to select.
*/
setItems: function (items, index) {
this.clearItems();
items.forEach(this.appendItem, this);
if (this.isOpen && this.autoSelect) {
this.selectItemAtIndex(index);
}
},
__maxLabelLength: -1,
get _maxLabelLength() {
if (this.__maxLabelLength !== -1) {
return this.__maxLabelLength;
}
let max = 0;
for (let {label, count} of this.items) {
if (count) {
label += count + "";
}
max = Math.max(label.length, max);
}
this.__maxLabelLength = max;
return this.__maxLabelLength;
},
/**
* Update the panel size to fit the content.
*/
_updateSize: function () {
if (!this._tooltip) {
return;
}
this._list.style.width = (this._maxLabelLength + 3) + "ch";
let selectedItem = this.selectedItem;
if (selectedItem) {
this._scrollElementIntoViewIfNeeded(this.elements.get(selectedItem));
}
},
_scrollElementIntoViewIfNeeded: function (element) {
let quads = element.getBoxQuads({relativeTo: this._tooltip.panel});
if (!quads || !quads[0]) {
return;
}
let {top, height} = quads[0].bounds;
let containerHeight = this._tooltip.panel.getBoundingClientRect().height;
if (top < 0) {
// Element is above container.
element.scrollIntoView(true);
} else if ((top + height) > containerHeight) {
// Element is beloew container.
element.scrollIntoView(false);
}
},
/**
* Clear all the items from the autocomplete list.
*/
clearItems: function () {
// Reset the selectedIndex to -1 before clearing the list
this.selectedIndex = -1;
this._list.innerHTML = "";
this.__maxLabelLength = -1;
this.items = [];
this.elements = new WeakMap();
},
/**
* Getter for the index of the selected item.
*
* @type {Number}
*/
get selectedIndex() {
return this._selectedIndex;
},
/**
* Setter for the selected index.
*
* @param {Number} index
* The number (index) of the item you want to select in the list.
*/
set selectedIndex(index) {
let previousSelected = this._list.querySelector(".autocomplete-selected");
if (previousSelected) {
previousSelected.classList.remove("autocomplete-selected");
}
let item = this.items[index];
if (this.isOpen && item) {
let element = this.elements.get(item);
element.classList.add("autocomplete-selected");
this._scrollElementIntoViewIfNeeded(element);
this._setActiveDescendant(element.id);
} else {
this._clearActiveDescendant();
}
this._selectedIndex = index;
if (this.isOpen && item && this.onSelectCallback) {
// Call the user-defined select callback if defined.
this.onSelectCallback();
}
},
/**
* Getter for the selected item.
* @type Object
*/
get selectedItem() {
return this.items[this._selectedIndex];
},
/**
* Setter for the selected item.
*
* @param {Object} item
* The object you want selected in the list.
*/
set selectedItem(item) {
let index = this.items.indexOf(item);
if (index !== -1 && this.isOpen) {
this.selectedIndex = index;
}
},
/**
* Update the aria-activedescendant attribute on the current active element for
* accessibility.
*
* @param {String} id
* The id (as in DOM id) of the currently selected autocomplete suggestion
*/
_setActiveDescendant: function (id) {
if (!this._activeElement) {
return;
}
// Make sure the list clone is in the same document as the anchor.
let anchorDoc = this._activeElement.ownerDocument;
if (!this._listClone.parentNode || this._listClone.ownerDocument !== anchorDoc) {
anchorDoc.documentElement.appendChild(this._listClone);
}
// Update the clone content to match the current list content.
this._listClone.innerHTML = this._list.innerHTML;
this._activeElement.setAttribute("aria-activedescendant", id);
},
/**
* Clear the aria-activedescendant attribute on the current active element.
*/
_clearActiveDescendant: function () {
if (!this._activeElement) {
return;
}
this._activeElement.removeAttribute("aria-activedescendant");
},
/**
* Append an item into the autocomplete list.
*
* @param {Object} item
* The item you want appended to the list.
* The item object can have the following properties:
* - label {String} Property which is used as the displayed value.
* - preLabel {String} [Optional] The String that will be displayed
* before the label indicating that this is the already
* present text in the input box, and label is the text
* that will be auto completed. When this property is
* present, |preLabel.length| starting characters will be
* removed from label.
* - count {Number} [Optional] The number to represent the count of
* autocompleted label.
*/
appendItem: function (item) {
let listItem = this._document.createElementNS(HTML_NS, "li");
// Items must have an id for accessibility.
listItem.setAttribute("id", "autocomplete-item-" + itemIdCounter++);
listItem.className = "autocomplete-item";
listItem.setAttribute("data-index", this.items.length);
if (this.direction) {
listItem.setAttribute("dir", this.direction);
}
let label = this._document.createElementNS(HTML_NS, "span");
label.textContent = item.label;
label.className = "autocomplete-value";
if (item.preLabel) {
let preDesc = this._document.createElementNS(HTML_NS, "span");
preDesc.textContent = item.preLabel;
preDesc.className = "initial-value";
listItem.appendChild(preDesc);
label.textContent = item.label.slice(item.preLabel.length);
}
listItem.appendChild(label);
if (item.count && item.count > 1) {
let countDesc = this._document.createElementNS(HTML_NS, "span");
countDesc.textContent = item.count;
countDesc.setAttribute("flex", "1");
countDesc.className = "autocomplete-count";
listItem.appendChild(countDesc);
}
this._list.appendChild(listItem);
this.items.push(item);
this.elements.set(item, listItem);
},
/**
* Remove an item from the popup list.
*
* @param {Object} item
* The item you want removed.
*/
removeItem: function (item) {
if (!this.items.includes(item)) {
return;
}
let itemIndex = this.items.indexOf(item);
let selectedIndex = this.selectedIndex;
// Remove autocomplete item.
this.items.splice(itemIndex, 1);
// Remove corresponding DOM element from the elements WeakMap and from the DOM.
let elementToRemove = this.elements.get(item);
this.elements.delete(elementToRemove);
elementToRemove.remove();
if (itemIndex <= selectedIndex) {
// If the removed item index was before or equal to the selected index, shift the
// selected index by 1.
this.selectedIndex = Math.max(0, selectedIndex - 1);
}
},
/**
* Getter for the number of items in the popup.
* @type {Number}
*/
get itemCount() {
return this.items.length;
},
/**
* Getter for the height of each item in the list.
*
* @type {Number}
*/
get _itemsPerPane() {
if (this.items.length) {
let listHeight = this._tooltip.panel.clientHeight;
let element = this.elements.get(this.items[0]);
let elementHeight = element.getBoundingClientRect().height;
return Math.floor(listHeight / elementHeight);
}
return 0;
},
/**
* Select the next item in the list.
*
* @return {Object}
* The newly selected item object.
*/
selectNextItem: function () {
if (this.selectedIndex < (this.items.length - 1)) {
this.selectedIndex++;
} else {
this.selectedIndex = 0;
}
return this.selectedItem;
},
/**
* Select the previous item in the list.
*
* @return {Object}
* The newly-selected item object.
*/
selectPreviousItem: function () {
if (this.selectedIndex > 0) {
this.selectedIndex--;
} else {
this.selectedIndex = this.items.length - 1;
}
return this.selectedItem;
},
/**
* Select the top-most item in the next page of items or
* the last item in the list.
*
* @return {Object}
* The newly-selected item object.
*/
selectNextPageItem: function () {
let nextPageIndex = this.selectedIndex + this._itemsPerPane + 1;
this.selectedIndex = Math.min(nextPageIndex, this.itemCount - 1);
return this.selectedItem;
},
/**
* Select the bottom-most item in the previous page of items,
* or the first item in the list.
*
* @return {Object}
* The newly-selected item object.
*/
selectPreviousPageItem: function () {
let prevPageIndex = this.selectedIndex - this._itemsPerPane - 1;
this.selectedIndex = Math.max(prevPageIndex, 0);
return this.selectedItem;
},
/**
* Manages theme switching for the popup based on the devtools.theme pref.
*
* @private
*
* @param {String} event
* The name of the event. In this case, "pref-changed".
* @param {Object} data
* An object passed by the emitter of the event. In this case, the
* object consists of three properties:
* - pref {String} The name of the preference that was modified.
* - newValue {Object} The new value of the preference.
* - oldValue {Object} The old value of the preference.
*/
_handleThemeChange: function (event, data) {
if (data.pref === "devtools.theme") {
this._tooltip.panel.classList.toggle(data.oldValue + "-theme", false);
this._tooltip.panel.classList.toggle(data.newValue + "-theme", true);
this._list.classList.toggle(data.oldValue + "-theme", false);
this._list.classList.toggle(data.newValue + "-theme", true);
}
},
/**
* Used by tests.
*/
get _panel() {
return this._tooltip.panel;
},
/**
* Used by tests.
*/
get _window() {
return this._document.defaultView;
},
};

View file

@ -0,0 +1,235 @@
/* 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";
var Cu = Components.utils;
const loaders = Cu.import("resource://gre/modules/commonjs/toolkit/loader.js", {});
const { devtools } = Cu.import("resource://devtools/shared/Loader.jsm", {});
const { joinURI } = devtools.require("devtools/shared/path");
const { assert } = devtools.require("devtools/shared/DevToolsUtils");
const Services = devtools.require("Services");
const { AppConstants } = devtools.require("resource://gre/modules/AppConstants.jsm");
const BROWSER_BASED_DIRS = [
"resource://devtools/client/inspector/layout",
"resource://devtools/client/jsonview",
"resource://devtools/client/shared/vendor",
"resource://devtools/client/shared/redux",
];
// Any directory that matches the following regular expression
// is also considered as browser based module directory.
// ('resource://devtools/client/.*/components/')
//
// An example:
// * `resource://devtools/client/inspector/components`
// * `resource://devtools/client/inspector/shared/components`
const browserBasedDirsRegExp =
/^resource\:\/\/devtools\/client\/\S*\/components\//;
function clearCache() {
Services.obs.notifyObservers(null, "startupcache-invalidate", null);
}
/*
* Create a loader to be used in a browser environment. This evaluates
* modules in their own environment, but sets window (the normal
* global object) as the sandbox prototype, so when a variable is not
* defined it checks `window` before throwing an error. This makes all
* browser APIs available to modules by default, like a normal browser
* environment, but modules are still evaluated in their own scope.
*
* Another very important feature of this loader is that it *only*
* deals with modules loaded from under `baseURI`. Anything loaded
* outside of that path will still be loaded from the devtools loader,
* so all system modules are still shared and cached across instances.
* An exception to this is anything under
* `devtools/client/shared/{vendor/components}`, which is where shared libraries
* and React components live that should be evaluated in a browser environment.
*
* @param string baseURI
* Base path to load modules from. If null or undefined, only
* the shared vendor/components modules are loaded with the browser
* loader.
* @param Object window
* The window instance to evaluate modules within
* @param Boolean useOnlyShared
* If true, ignores `baseURI` and only loads the shared
* BROWSER_BASED_DIRS via BrowserLoader.
* @return Object
* An object with two properties:
* - loader: the Loader instance
* - require: a function to require modules with
*/
function BrowserLoader(options) {
const browserLoaderBuilder = new BrowserLoaderBuilder(options);
return {
loader: browserLoaderBuilder.loader,
require: browserLoaderBuilder.require
};
}
/**
* Private class used to build the Loader instance and require method returned
* by BrowserLoader(baseURI, window).
*
* @param string baseURI
* Base path to load modules from.
* @param Object window
* The window instance to evaluate modules within
* @param Boolean useOnlyShared
* If true, ignores `baseURI` and only loads the shared
* BROWSER_BASED_DIRS via BrowserLoader.
*/
function BrowserLoaderBuilder({ baseURI, window, useOnlyShared }) {
assert(!!baseURI !== !!useOnlyShared,
"Cannot use both `baseURI` and `useOnlyShared`.");
const loaderOptions = devtools.require("@loader/options");
const dynamicPaths = {};
const componentProxies = new Map();
if (AppConstants.DEBUG || AppConstants.DEBUG_JS_MODULES) {
dynamicPaths["devtools/client/shared/vendor/react"] =
"resource://devtools/client/shared/vendor/react-dev";
}
const opts = {
id: "browser-loader",
sharedGlobal: true,
sandboxPrototype: window,
paths: Object.assign({}, dynamicPaths, loaderOptions.paths),
invisibleToDebugger: loaderOptions.invisibleToDebugger,
requireHook: (id, require) => {
// If |id| requires special handling, simply defer to devtools
// immediately.
if (devtools.isLoaderPluginId(id)) {
return devtools.require(id);
}
const uri = require.resolve(id);
let isBrowserDir = BROWSER_BASED_DIRS.filter(dir => {
return uri.startsWith(dir);
}).length > 0;
// If the URI doesn't match hardcoded paths try the regexp.
if (!isBrowserDir) {
isBrowserDir = uri.match(browserBasedDirsRegExp) != null;
}
if ((useOnlyShared || !uri.startsWith(baseURI)) && !isBrowserDir) {
return devtools.require(uri);
}
return require(uri);
},
globals: {
// Allow modules to use the window's console to ensure logs appear in a
// tab toolbox, if one exists, instead of just the browser console.
console: window.console,
// Make sure `define` function exists. This allows defining some modules
// in AMD format while retaining CommonJS compatibility through this hook.
// JSON Viewer needs modules in AMD format, as it currently uses RequireJS
// from a content document and can't access our usual loaders. So, any
// modules shared with the JSON Viewer should include a define wrapper:
//
// // Make this available to both AMD and CJS environments
// define(function(require, exports, module) {
// ... code ...
// });
//
// Bug 1248830 will work out a better plan here for our content module
// loading needs, especially as we head towards devtools.html.
define(factory) {
factory(this.require, this.exports, this.module);
},
// Allow modules to use the DevToolsLoader lazy loading helpers.
loader: {
lazyGetter: devtools.lazyGetter,
lazyImporter: devtools.lazyImporter,
lazyServiceGetter: devtools.lazyServiceGetter,
lazyRequireGetter: this.lazyRequireGetter.bind(this),
},
}
};
if (Services.prefs.getBoolPref("devtools.loader.hotreload")) {
opts.loadModuleHook = (module, require) => {
const { uri, exports } = module;
if (exports.prototype &&
exports.prototype.isReactComponent) {
const { createProxy, getForceUpdate } =
require("devtools/client/shared/vendor/react-proxy");
const React = require("devtools/client/shared/vendor/react");
if (!componentProxies.get(uri)) {
const proxy = createProxy(exports);
componentProxies.set(uri, proxy);
module.exports = proxy.get();
} else {
const proxy = componentProxies.get(uri);
const instances = proxy.update(exports);
instances.forEach(getForceUpdate(React));
module.exports = proxy.get();
}
}
return exports;
};
const watcher = devtools.require("devtools/client/shared/devtools-file-watcher");
let onFileChanged = (_, relativePath, path) => {
this.hotReloadFile(componentProxies, "resource://devtools/" + relativePath);
};
watcher.on("file-changed", onFileChanged);
window.addEventListener("unload", () => {
watcher.off("file-changed", onFileChanged);
});
}
const mainModule = loaders.Module(baseURI, joinURI(baseURI, "main.js"));
this.loader = loaders.Loader(opts);
this.require = loaders.Require(this.loader, mainModule);
}
BrowserLoaderBuilder.prototype = {
/**
* Define a getter property on the given object that requires the given
* module. This enables delaying importing modules until the module is
* actually used.
*
* @param Object obj
* The object to define the property on.
* @param String property
* The property name.
* @param String module
* The module path.
* @param Boolean destructure
* Pass true if the property name is a member of the module's exports.
*/
lazyRequireGetter: function (obj, property, module, destructure) {
devtools.lazyGetter(obj, property, () => {
return destructure
? this.require(module)[property]
: this.require(module || property);
});
},
hotReloadFile: function (componentProxies, fileURI) {
if (fileURI.match(/\.js$/)) {
// Test for React proxy components
const proxy = componentProxies.get(fileURI);
if (proxy) {
// Remove the old module and re-require the new one; the require
// hook in the loader will take care of the rest
delete this.loader.modules[fileURI];
clearCache();
this.require(fileURI);
}
}
}
};
this.BrowserLoader = BrowserLoader;
this.EXPORTED_SYMBOLS = ["BrowserLoader"];

View file

@ -0,0 +1,7 @@
"use strict";
module.exports = {
"globals": {
"define": true,
}
};

View file

@ -0,0 +1,239 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { DOM: dom, createClass, PropTypes } = require("devtools/client/shared/vendor/react");
const { getSourceNames, parseURL,
isScratchpadScheme, getSourceMappedFile } = require("devtools/client/shared/source-utils");
const { LocalizationHelper } = require("devtools/shared/l10n");
const l10n = new LocalizationHelper("devtools/client/locales/components.properties");
const webl10n = new LocalizationHelper("devtools/client/locales/webconsole.properties");
module.exports = createClass({
displayName: "Frame",
propTypes: {
// SavedFrame, or an object containing all the required properties.
frame: PropTypes.shape({
functionDisplayName: PropTypes.string,
source: PropTypes.string.isRequired,
line: PropTypes.oneOfType([ PropTypes.string, PropTypes.number ]),
column: PropTypes.oneOfType([ PropTypes.string, PropTypes.number ]),
}).isRequired,
// Clicking on the frame link -- probably should link to the debugger.
onClick: PropTypes.func.isRequired,
// Option to display a function name before the source link.
showFunctionName: PropTypes.bool,
// Option to display a function name even if it's anonymous.
showAnonymousFunctionName: PropTypes.bool,
// Option to display a host name after the source link.
showHost: PropTypes.bool,
// Option to display a host name if the filename is empty or just '/'
showEmptyPathAsHost: PropTypes.bool,
// Option to display a full source instead of just the filename.
showFullSourceUrl: PropTypes.bool,
// Service to enable the source map feature for console.
sourceMapService: PropTypes.object,
},
getDefaultProps() {
return {
showFunctionName: false,
showAnonymousFunctionName: false,
showHost: false,
showEmptyPathAsHost: false,
showFullSourceUrl: false,
};
},
componentWillMount() {
const sourceMapService = this.props.sourceMapService;
if (sourceMapService) {
const source = this.getSource();
sourceMapService.subscribe(source, this.onSourceUpdated);
}
},
componentWillUnmount() {
const sourceMapService = this.props.sourceMapService;
if (sourceMapService) {
const source = this.getSource();
sourceMapService.unsubscribe(source, this.onSourceUpdated);
}
},
/**
* Component method to update the FrameView when a resolved location is available
* @param event
* @param location
*/
onSourceUpdated(event, location, resolvedLocation) {
const frame = this.getFrame(resolvedLocation);
this.setState({
frame,
isSourceMapped: true,
});
},
/**
* Utility method to convert the Frame object to the
* Source Object model required by SourceMapService
* @param frame
* @returns {{url: *, line: *, column: *}}
*/
getSource(frame) {
frame = frame || this.props.frame;
const { source, line, column } = frame;
return {
url: source,
line,
column,
};
},
/**
* Utility method to convert the Source object model to the
* Frame object model required by FrameView class.
* @param source
* @returns {{source: *, line: *, column: *, functionDisplayName: *}}
*/
getFrame(source) {
const { url, line, column } = source;
return {
source: url,
line,
column,
functionDisplayName: this.props.frame.functionDisplayName,
};
},
render() {
let frame, isSourceMapped;
let {
onClick,
showFunctionName,
showAnonymousFunctionName,
showHost,
showEmptyPathAsHost,
showFullSourceUrl
} = this.props;
if (this.state && this.state.isSourceMapped) {
frame = this.state.frame;
isSourceMapped = this.state.isSourceMapped;
} else {
frame = this.props.frame;
}
let source = frame.source ? String(frame.source) : "";
let line = frame.line != void 0 ? Number(frame.line) : null;
let column = frame.column != void 0 ? Number(frame.column) : null;
const { short, long, host } = getSourceNames(source);
// Reparse the URL to determine if we should link this; `getSourceNames`
// has already cached this indirectly. We don't want to attempt to
// link to "self-hosted" and "(unknown)". However, we do want to link
// to Scratchpad URIs.
// Source mapped sources might not necessary linkable, but they
// are still valid in the debugger.
const isLinkable = !!(isScratchpadScheme(source) || parseURL(source))
|| isSourceMapped;
const elements = [];
const sourceElements = [];
let sourceEl;
let tooltip = long;
// Exclude all falsy values, including `0`, as line numbers start with 1.
if (line) {
tooltip += `:${line}`;
// Intentionally exclude 0
if (column) {
tooltip += `:${column}`;
}
}
let attributes = {
"data-url": long,
className: "frame-link",
};
if (showFunctionName) {
let functionDisplayName = frame.functionDisplayName;
if (!functionDisplayName && showAnonymousFunctionName) {
functionDisplayName = webl10n.getStr("stacktrace.anonymousFunction");
}
if (functionDisplayName) {
elements.push(
dom.span({ className: "frame-link-function-display-name" },
functionDisplayName),
" "
);
}
}
let displaySource = showFullSourceUrl ? long : short;
if (isSourceMapped) {
displaySource = getSourceMappedFile(displaySource);
} else if (showEmptyPathAsHost && (displaySource === "" || displaySource === "/")) {
displaySource = host;
}
sourceElements.push(dom.span({
className: "frame-link-filename",
}, displaySource));
// If we have a line number > 0.
if (line) {
let lineInfo = `:${line}`;
// Add `data-line` attribute for testing
attributes["data-line"] = line;
// Intentionally exclude 0
if (column) {
lineInfo += `:${column}`;
// Add `data-column` attribute for testing
attributes["data-column"] = column;
}
sourceElements.push(dom.span({ className: "frame-link-line" }, lineInfo));
}
// Inner el is useful for achieving ellipsis on the left and correct LTR/RTL
// ordering. See CSS styles for frame-link-source-[inner] and bug 1290056.
let sourceInnerEl = dom.span({
className: "frame-link-source-inner",
title: isLinkable ?
l10n.getFormatStr("frame.viewsourceindebugger", tooltip) : tooltip,
}, sourceElements);
// If source is not a URL (self-hosted, eval, etc.), don't make
// it an anchor link, as we can't link to it.
if (isLinkable) {
sourceEl = dom.a({
onClick: e => {
e.preventDefault();
onClick(this.getSource(frame));
},
href: source,
className: "frame-link-source",
draggable: false,
}, sourceInnerEl);
} else {
sourceEl = dom.span({
className: "frame-link-source",
}, sourceInnerEl);
}
elements.push(sourceEl);
if (showHost && host) {
elements.push(" ", dom.span({ className: "frame-link-host" }, host));
}
return dom.span(attributes, ...elements);
}
});

View file

@ -0,0 +1,154 @@
/* 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/. */
/* eslint-env browser */
"use strict";
// A box with a start and a end pane, separated by a dragable splitter that
// allows the user to resize the relative widths of the panes.
//
// +-----------------------+---------------------+
// | | |
// | | |
// | S |
// | Start Pane p End Pane |
// | l |
// | i |
// | t |
// | t |
// | e |
// | r |
// | | |
// | | |
// +-----------------------+---------------------+
const {
DOM: dom,
createClass,
PropTypes,
} = require("devtools/client/shared/vendor/react");
const { assert } = require("devtools/shared/DevToolsUtils");
module.exports = createClass({
displayName: "HSplitBox",
propTypes: {
// The contents of the start pane.
start: PropTypes.any.isRequired,
// The contents of the end pane.
end: PropTypes.any.isRequired,
// The relative width of the start pane, expressed as a number between 0 and
// 1. The relative width of the end pane is 1 - startWidth. For example,
// with startWidth = .5, both panes are of equal width; with startWidth =
// .25, the start panel will take up 1/4 width and the end panel will take
// up 3/4 width.
startWidth: PropTypes.number,
// A minimum css width value for the start and end panes.
minStartWidth: PropTypes.any,
minEndWidth: PropTypes.any,
// A callback fired when the user drags the splitter to resize the relative
// pane widths. The function is passed the startWidth value that would put
// the splitter underneath the users mouse.
onResize: PropTypes.func.isRequired,
},
getDefaultProps() {
return {
startWidth: 0.5,
minStartWidth: "20px",
minEndWidth: "20px",
};
},
getInitialState() {
return {
mouseDown: false
};
},
componentDidMount() {
document.defaultView.top.addEventListener("mouseup", this._onMouseUp,
false);
document.defaultView.top.addEventListener("mousemove", this._onMouseMove,
false);
},
componentWillUnmount() {
document.defaultView.top.removeEventListener("mouseup", this._onMouseUp,
false);
document.defaultView.top.removeEventListener("mousemove", this._onMouseMove,
false);
},
_onMouseDown(event) {
if (event.button !== 0) {
return;
}
this.setState({ mouseDown: true });
event.preventDefault();
},
_onMouseUp(event) {
if (event.button !== 0 || !this.state.mouseDown) {
return;
}
this.setState({ mouseDown: false });
event.preventDefault();
},
_onMouseMove(event) {
if (!this.state.mouseDown) {
return;
}
const rect = this.refs.box.getBoundingClientRect();
const { left, right } = rect;
const width = right - left;
const relative = event.clientX - left;
this.props.onResize(relative / width);
event.preventDefault();
},
render() {
/* eslint-disable no-shadow */
const { start, end, startWidth, minStartWidth, minEndWidth } = this.props;
assert(startWidth => 0 && startWidth <= 1,
"0 <= this.props.startWidth <= 1");
/* eslint-enable */
return dom.div(
{
className: "h-split-box",
ref: "box",
},
dom.div(
{
className: "h-split-box-pane",
style: { flex: startWidth, minWidth: minStartWidth },
},
start
),
dom.div({
className: "devtools-side-splitter",
onMouseDown: this._onMouseDown,
}),
dom.div(
{
className: "h-split-box-pane",
style: { flex: 1 - startWidth, minWidth: minEndWidth },
},
end
)
);
}
});

View file

@ -0,0 +1,27 @@
# -*- 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/.
DIRS += [
'reps',
'splitter',
'tabs',
'tree'
]
DevToolsModules(
'frame.js',
'h-split-box.js',
'notification-box.css',
'notification-box.js',
'search-box.js',
'sidebar-toggle.css',
'sidebar-toggle.js',
'stack-trace.js',
'tree.js',
)
MOCHITEST_CHROME_MANIFESTS += ['test/mochitest/chrome.ini']
BROWSER_CHROME_MANIFESTS += ['test/browser/browser.ini']

View file

@ -0,0 +1,95 @@
/* 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/. */
/* Layout */
.notificationbox .notificationInner {
display: flex;
flex-direction: row;
}
.notificationbox .details {
flex-grow: 1;
display: flex;
flex-direction: row;
align-items: center;
}
.notificationbox .notification-button {
text-align: right;
}
.notificationbox .messageText {
flex-grow: 1;
}
.notificationbox .details:-moz-dir(rtl)
.notificationbox .notificationInner:-moz-dir(rtl) {
flex-direction: row-reverse;
}
/* Style */
.notificationbox .notification {
background-color: InfoBackground;
text-shadow: none;
border-top: 1px solid ThreeDShadow;
border-bottom: 1px solid ThreeDShadow;
}
.notificationbox .notification[data-type="info"] {
color: -moz-DialogText;
background-color: -moz-Dialog;
}
.notificationbox .notification[data-type="critical"] {
color: white;
background-image: linear-gradient(rgb(212,0,0), rgb(152,0,0));
}
.notificationbox .messageImage {
display: inline-block;
width: 16px;
height: 16px;
margin: 6px;
}
/* Default icons for notifications */
.notificationbox .messageImage[data-type="info"] {
background-image: url("chrome://global/skin/icons/information-16.png");
}
.notificationbox .messageImage[data-type="warning"] {
background-image: url("chrome://global/skin/icons/warning-16.png");
}
.notificationbox .messageImage[data-type="critical"] {
background-image: url("chrome://global/skin/icons/error-16.png");
}
/* Close button */
.notificationbox .messageCloseButton {
width: 20px;
height: 20px;
margin: 4px;
margin-inline-end: 8px;
background-image: url("chrome://devtools/skin/images/close.svg");
background-position: center;
background-color: transparent;
background-repeat: no-repeat;
border-radius: 11px;
filter: invert(0);
}
.notificationbox .messageCloseButton:hover {
background-color: gray;
filter: invert(1);
}
.notificationbox .messageCloseButton:active {
background-color: rgba(170, 170, 170, .4); /* --toolbar-tab-hover-active */
}

View file

@ -0,0 +1,263 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const React = require("devtools/client/shared/vendor/react");
const Immutable = require("devtools/client/shared/vendor/immutable");
const { LocalizationHelper } = require("devtools/shared/l10n");
const l10n = new LocalizationHelper("devtools/client/locales/components.properties");
// Shortcuts
const { PropTypes, createClass, DOM } = React;
const { div, span, button } = DOM;
// Priority Levels
const PriorityLevels = {
PRIORITY_INFO_LOW: 1,
PRIORITY_INFO_MEDIUM: 2,
PRIORITY_INFO_HIGH: 3,
PRIORITY_WARNING_LOW: 4,
PRIORITY_WARNING_MEDIUM: 5,
PRIORITY_WARNING_HIGH: 6,
PRIORITY_CRITICAL_LOW: 7,
PRIORITY_CRITICAL_MEDIUM: 8,
PRIORITY_CRITICAL_HIGH: 9,
PRIORITY_CRITICAL_BLOCK: 10,
};
/**
* This component represents Notification Box - HTML alternative for
* <xul:notifictionbox> binding.
*
* See also MDN for more info about <xul:notificationbox>:
* https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XUL/notificationbox
*/
var NotificationBox = createClass({
displayName: "NotificationBox",
propTypes: {
// List of notifications appended into the box.
notifications: PropTypes.arrayOf(PropTypes.shape({
// label to appear on the notification.
label: PropTypes.string.isRequired,
// Value used to identify the notification
value: PropTypes.string.isRequired,
// URL of image to appear on the notification. If "" then an icon
// appropriate for the priority level is used.
image: PropTypes.string.isRequired,
// Notification priority; see Priority Levels.
priority: PropTypes.number.isRequired,
// Array of button descriptions to appear on the notification.
buttons: PropTypes.arrayOf(PropTypes.shape({
// Function to be called when the button is activated.
// This function is passed three arguments:
// 1) the NotificationBox component the button is associated with
// 2) the button description as passed to appendNotification.
// 3) the element which was the target of the button press event.
// If the return value from this function is not True, then the
// notification is closed. The notification is also not closed
// if an error is thrown.
callback: PropTypes.func.isRequired,
// The label to appear on the button.
label: PropTypes.string.isRequired,
// The accesskey attribute set on the <button> element.
accesskey: PropTypes.string,
})),
// A function to call to notify you of interesting things that happen
// with the notification box.
eventCallback: PropTypes.func,
})),
// Message that should be shown when hovering over the close button
closeButtonTooltip: PropTypes.string
},
getDefaultProps() {
return {
closeButtonTooltip: l10n.getStr("notificationBox.closeTooltip")
};
},
getInitialState() {
return {
notifications: new Immutable.OrderedMap()
};
},
/**
* Create a new notification and display it. If another notification is
* already present with a higher priority, the new notification will be
* added behind it. See `propTypes` for arguments description.
*/
appendNotification(label, value, image, priority, buttons = [],
eventCallback) {
// Priority level must be within expected interval
// (see priority levels at the top of this file).
if (priority < PriorityLevels.PRIORITY_INFO_LOW ||
priority > PriorityLevels.PRIORITY_CRITICAL_BLOCK) {
throw new Error("Invalid notification priority " + priority);
}
// Custom image URL is not supported yet.
if (image) {
throw new Error("Custom image URL is not supported yet");
}
let type = "warning";
if (priority >= PriorityLevels.PRIORITY_CRITICAL_LOW) {
type = "critical";
} else if (priority <= PriorityLevels.PRIORITY_INFO_HIGH) {
type = "info";
}
let notifications = this.state.notifications.set(value, {
label: label,
value: value,
image: image,
priority: priority,
type: type,
buttons: buttons,
eventCallback: eventCallback,
});
// High priorities must be on top.
notifications = notifications.sortBy((val, key) => {
return -val.priority;
});
this.setState({
notifications: notifications
});
},
/**
* Remove specific notification from the list.
*/
removeNotification(notification) {
this.close(this.state.notifications.get(notification.value));
},
/**
* Returns an object that represents a notification. It can be
* used to close it.
*/
getNotificationWithValue(value) {
let notification = this.state.notifications.get(value);
if (!notification) {
return null;
}
// Return an object that can be used to remove the notification
// later (using `removeNotification` method) or directly close it.
return Object.assign({}, notification, {
close: () => {
this.close(notification);
}
});
},
getCurrentNotification() {
return this.state.notifications.first();
},
/**
* Close specified notification.
*/
close(notification) {
if (!notification) {
return;
}
if (notification.eventCallback) {
notification.eventCallback("removed");
}
this.setState({
notifications: this.state.notifications.remove(notification.value)
});
},
/**
* Render a button. A notification can have a set of custom buttons.
* These are used to execute custom callback.
*/
renderButton(props, notification) {
let onClick = event => {
if (props.callback) {
let result = props.callback(this, props, event.target);
if (!result) {
this.close(notification);
}
event.stopPropagation();
}
};
return (
button({
key: props.label,
className: "notification-button",
accesskey: props.accesskey,
onClick: onClick},
props.label
)
);
},
/**
* Render a notification.
*/
renderNotification(notification) {
return (
div({
key: notification.value,
className: "notification",
"data-type": notification.type},
div({className: "notificationInner"},
div({className: "details"},
div({
className: "messageImage",
"data-type": notification.type}),
span({className: "messageText"},
notification.label
),
notification.buttons.map(props =>
this.renderButton(props, notification)
)
),
div({
className: "messageCloseButton",
title: this.props.closeButtonTooltip,
onClick: this.close.bind(this, notification)}
)
)
)
);
},
/**
* Render the top (highest priority) notification. Only one
* notification is rendered at a time.
*/
render() {
let notification = this.state.notifications.first();
let content = notification ?
this.renderNotification(notification) :
null;
return div({className: "notificationbox"},
content
);
},
});
module.exports.NotificationBox = NotificationBox;
module.exports.PriorityLevels = PriorityLevels;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,110 @@
/* 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/. */
/* global window */
"use strict";
const { DOM: dom, createClass, PropTypes } = require("devtools/client/shared/vendor/react");
const {KeyShortcuts} = require("devtools/client/shared/key-shortcuts");
/**
* A generic search box component for use across devtools
*/
module.exports = createClass({
displayName: "SearchBox",
propTypes: {
delay: PropTypes.number,
keyShortcut: PropTypes.string,
onChange: PropTypes.func,
placeholder: PropTypes.string,
type: PropTypes.string
},
getInitialState() {
return {
value: ""
};
},
componentDidMount() {
if (!this.props.keyShortcut) {
return;
}
this.shortcuts = new KeyShortcuts({
window
});
this.shortcuts.on(this.props.keyShortcut, (name, event) => {
event.preventDefault();
this.refs.input.focus();
});
},
componentWillUnmount() {
if (this.shortcuts) {
this.shortcuts.destroy();
}
// Clean up an existing timeout.
if (this.searchTimeout) {
clearTimeout(this.searchTimeout);
}
},
onChange() {
if (this.state.value !== this.refs.input.value) {
this.setState({ value: this.refs.input.value });
}
if (!this.props.delay) {
this.props.onChange(this.state.value);
return;
}
// Clean up an existing timeout before creating a new one.
if (this.searchTimeout) {
clearTimeout(this.searchTimeout);
}
// Execute the search after a timeout. It makes the UX
// smoother if the user is typing quickly.
this.searchTimeout = setTimeout(() => {
this.searchTimeout = null;
this.props.onChange(this.state.value);
}, this.props.delay);
},
onClearButtonClick() {
this.refs.input.value = "";
this.onChange();
},
render() {
let { type = "search", placeholder } = this.props;
let { value } = this.state;
let divClassList = ["devtools-searchbox", "has-clear-btn"];
let inputClassList = [`devtools-${type}input`];
if (value !== "") {
inputClassList.push("filled");
}
return dom.div(
{ className: divClassList.join(" ") },
dom.input({
className: inputClassList.join(" "),
onChange: this.onChange,
placeholder,
ref: "input",
value
}),
dom.button({
className: "devtools-searchinput-clear",
hidden: value == "",
onClick: this.onClearButtonClick
})
);
}
});

View file

@ -0,0 +1,32 @@
/* 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/. */
.sidebar-toggle {
display: block;
}
.sidebar-toggle::before,
.sidebar-toggle.pane-collapsed:dir(rtl)::before {
background-image: var(--theme-pane-collapse-image);
}
.sidebar-toggle.pane-collapsed::before,
.sidebar-toggle:dir(rtl)::before {
background-image: var(--theme-pane-expand-image);
}
/* Rotate button icon 90deg if the toolbox container is
in vertical mode (sidebar displayed under the main panel) */
@media (max-width: 700px) {
.sidebar-toggle::before {
transform: rotate(90deg);
}
/* Since RTL swaps the used images, we need to flip them
the other way round */
.sidebar-toggle:dir(rtl)::before {
transform: rotate(-90deg);
}
}

View file

@ -0,0 +1,66 @@
/* -*- 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";
const { DOM, createClass, PropTypes } = require("devtools/client/shared/vendor/react");
// Shortcuts
const { button } = DOM;
/**
* Sidebar toggle button. This button is used to exapand
* and collapse Sidebar.
*/
var SidebarToggle = createClass({
displayName: "SidebarToggle",
propTypes: {
// Set to true if collapsed.
collapsed: PropTypes.bool.isRequired,
// Tooltip text used when the button indicates expanded state.
collapsePaneTitle: PropTypes.string.isRequired,
// Tooltip text used when the button indicates collapsed state.
expandPaneTitle: PropTypes.string.isRequired,
// Click callback
onClick: PropTypes.func.isRequired,
},
getInitialState: function () {
return {
collapsed: this.props.collapsed,
};
},
// Events
onClick: function (event) {
this.props.onClick(event);
},
// Rendering
render: function () {
let title = this.state.collapsed ?
this.props.expandPaneTitle :
this.props.collapsePaneTitle;
let classNames = ["devtools-button", "sidebar-toggle"];
if (this.state.collapsed) {
classNames.push("pane-collapsed");
}
return (
button({
className: classNames.join(" "),
title: title,
onClick: this.onClick
})
);
}
});
module.exports = SidebarToggle;

View file

@ -0,0 +1,54 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const React = require("devtools/client/shared/vendor/react");
const ReactDOM = require("devtools/client/shared/vendor/react-dom");
const { DOM: dom, PropTypes } = React;
const Draggable = React.createClass({
displayName: "Draggable",
propTypes: {
onMove: PropTypes.func.isRequired,
onStart: PropTypes.func,
onStop: PropTypes.func,
style: PropTypes.object,
className: PropTypes.string
},
startDragging(ev) {
ev.preventDefault();
const doc = ReactDOM.findDOMNode(this).ownerDocument;
doc.addEventListener("mousemove", this.onMove);
doc.addEventListener("mouseup", this.onUp);
this.props.onStart && this.props.onStart();
},
onMove(ev) {
ev.preventDefault();
// Use viewport coordinates so, moving mouse over iframes
// doesn't mangle (relative) coordinates.
this.props.onMove(ev.clientX, ev.clientY);
},
onUp(ev) {
ev.preventDefault();
const doc = ReactDOM.findDOMNode(this).ownerDocument;
doc.removeEventListener("mousemove", this.onMove);
doc.removeEventListener("mouseup", this.onUp);
this.props.onStop && this.props.onStop();
},
render() {
return dom.div({
style: this.props.style,
className: this.props.className,
onMouseDown: this.startDragging
});
}
});
module.exports = Draggable;

View file

@ -0,0 +1,11 @@
# -*- 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(
'draggable.js',
'split-box.css',
'split-box.js',
)

View file

@ -0,0 +1,88 @@
/* 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/. */
.split-box {
display: flex;
flex: 1;
min-width: 0;
height: 100%;
width: 100%;
}
.split-box.vert {
flex-direction: row;
}
.split-box.horz {
flex-direction: column;
}
.split-box > .uncontrolled {
display: flex;
flex: 1;
min-width: 0;
overflow: auto;
}
.split-box > .controlled {
display: flex;
overflow: auto;
}
.split-box > .splitter {
background-image: none;
border: 0;
border-style: solid;
border-color: transparent;
background-color: var(--theme-splitter-color);
background-clip: content-box;
position: relative;
box-sizing: border-box;
/* Positive z-index positions the splitter on top of its siblings and makes
it clickable on both sides. */
z-index: 1;
}
.split-box.vert > .splitter {
min-width: calc(var(--devtools-splitter-inline-start-width) +
var(--devtools-splitter-inline-end-width) + 1px);
border-inline-start-width: var(--devtools-splitter-inline-start-width);
border-inline-end-width: var(--devtools-splitter-inline-end-width);
margin-inline-start: calc(-1 * var(--devtools-splitter-inline-start-width) - 1px);
margin-inline-end: calc(-1 * var(--devtools-splitter-inline-end-width));
cursor: ew-resize;
}
.split-box.horz > .splitter {
min-height: calc(var(--devtools-splitter-top-width) +
var(--devtools-splitter-bottom-width) + 1px);
border-top-width: var(--devtools-splitter-top-width);
border-bottom-width: var(--devtools-splitter-bottom-width);
margin-top: calc(-1 * var(--devtools-splitter-top-width) - 1px);
margin-bottom: calc(-1 * var(--devtools-splitter-bottom-width));
cursor: ns-resize;
}
.split-box.disabled {
pointer-events: none;
}
/**
* Make sure splitter panels are not processing any mouse
* events. This is good for performance during splitter
* bar dragging.
*/
.split-box.dragging > .controlled,
.split-box.dragging > .uncontrolled {
pointer-events: none;
}

View file

@ -0,0 +1,205 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const React = require("devtools/client/shared/vendor/react");
const ReactDOM = require("devtools/client/shared/vendor/react-dom");
const Draggable = React.createFactory(require("devtools/client/shared/components/splitter/draggable"));
const { DOM: dom, PropTypes } = React;
/**
* This component represents a Splitter. The splitter supports vertical
* as well as horizontal mode.
*/
const SplitBox = React.createClass({
displayName: "SplitBox",
propTypes: {
// Custom class name. You can use more names separated by a space.
className: PropTypes.string,
// Initial size of controlled panel.
initialSize: PropTypes.number,
// Left/top panel
startPanel: PropTypes.any,
// Min panel size.
minSize: PropTypes.number,
// Max panel size.
maxSize: PropTypes.number,
// Right/bottom panel
endPanel: PropTypes.any,
// True if the right/bottom panel should be controlled.
endPanelControl: PropTypes.bool,
// Size of the splitter handle bar.
splitterSize: PropTypes.number,
// True if the splitter bar is vertical (default is vertical).
vert: PropTypes.bool
},
getDefaultProps() {
return {
splitterSize: 5,
vert: true,
endPanelControl: false
};
},
/**
* The state stores the current orientation (vertical or horizontal)
* and the current size (width/height). All these values can change
* during the component's life time.
*/
getInitialState() {
return {
vert: this.props.vert,
width: this.props.initialWidth || this.props.initialSize,
height: this.props.initialHeight || this.props.initialSize
};
},
// Dragging Events
/**
* Set 'resizing' cursor on entire document during splitter dragging.
* This avoids cursor-flickering that happens when the mouse leaves
* the splitter bar area (happens frequently).
*/
onStartMove() {
const splitBox = ReactDOM.findDOMNode(this);
const doc = splitBox.ownerDocument;
let defaultCursor = doc.documentElement.style.cursor;
doc.documentElement.style.cursor = (this.state.vert ? "ew-resize" : "ns-resize");
splitBox.classList.add("dragging");
this.setState({
defaultCursor: defaultCursor
});
},
onStopMove() {
const splitBox = ReactDOM.findDOMNode(this);
const doc = splitBox.ownerDocument;
doc.documentElement.style.cursor = this.state.defaultCursor;
splitBox.classList.remove("dragging");
},
/**
* Adjust size of the controlled panel. Depending on the current
* orientation we either remember the width or height of
* the splitter box.
*/
onMove(x, y) {
const node = ReactDOM.findDOMNode(this);
const doc = node.ownerDocument;
const win = doc.defaultView;
let size;
let { endPanelControl } = this.props;
if (this.state.vert) {
// Switch the control flag in case of RTL. Note that RTL
// has impact on vertical splitter only.
let dir = win.getComputedStyle(doc.documentElement).direction;
if (dir == "rtl") {
endPanelControl = !endPanelControl;
}
size = endPanelControl ?
(node.offsetLeft + node.offsetWidth) - x :
x - node.offsetLeft;
this.setState({
width: size
});
} else {
size = endPanelControl ?
(node.offsetTop + node.offsetHeight) - y :
y - node.offsetTop;
this.setState({
height: size
});
}
},
// Rendering
render() {
const vert = this.state.vert;
const { startPanel, endPanel, endPanelControl, minSize,
maxSize, splitterSize } = this.props;
let style = Object.assign({}, this.props.style);
// Calculate class names list.
let classNames = ["split-box"];
classNames.push(vert ? "vert" : "horz");
if (this.props.className) {
classNames = classNames.concat(this.props.className.split(" "));
}
let leftPanelStyle;
let rightPanelStyle;
// Set proper size for panels depending on the current state.
if (vert) {
leftPanelStyle = {
maxWidth: endPanelControl ? null : maxSize,
minWidth: endPanelControl ? null : minSize,
width: endPanelControl ? null : this.state.width
};
rightPanelStyle = {
maxWidth: endPanelControl ? maxSize : null,
minWidth: endPanelControl ? minSize : null,
width: endPanelControl ? this.state.width : null
};
} else {
leftPanelStyle = {
maxHeight: endPanelControl ? null : maxSize,
minHeight: endPanelControl ? null : minSize,
height: endPanelControl ? null : this.state.height
};
rightPanelStyle = {
maxHeight: endPanelControl ? maxSize : null,
minHeight: endPanelControl ? minSize : null,
height: endPanelControl ? this.state.height : null
};
}
// Calculate splitter size
let splitterStyle = {
flex: "0 0 " + splitterSize + "px"
};
return (
dom.div({
className: classNames.join(" "),
style: style },
startPanel ?
dom.div({
className: endPanelControl ? "uncontrolled" : "controlled",
style: leftPanelStyle},
startPanel
) : null,
Draggable({
className: "splitter",
style: splitterStyle,
onStart: this.onStartMove,
onStop: this.onStopMove,
onMove: this.onMove
}),
endPanel ?
dom.div({
className: endPanelControl ? "controlled" : "uncontrolled",
style: rightPanelStyle},
endPanel
) : null
)
);
}
});
module.exports = SplitBox;

View file

@ -0,0 +1,68 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const React = require("devtools/client/shared/vendor/react");
const { DOM: dom, createClass, createFactory, PropTypes } = React;
const { LocalizationHelper } = require("devtools/shared/l10n");
const Frame = createFactory(require("./frame"));
const l10n = new LocalizationHelper("devtools/client/locales/webconsole.properties");
const AsyncFrame = createFactory(createClass({
displayName: "AsyncFrame",
PropTypes: {
asyncCause: PropTypes.string.isRequired
},
render() {
let { asyncCause } = this.props;
return dom.span(
{ className: "frame-link-async-cause" },
l10n.getFormatStr("stacktrace.asyncStack", asyncCause)
);
}
}));
const StackTrace = createClass({
displayName: "StackTrace",
PropTypes: {
stacktrace: PropTypes.array.isRequired,
onViewSourceInDebugger: PropTypes.func.isRequired
},
render() {
let { stacktrace, onViewSourceInDebugger } = this.props;
let frames = [];
stacktrace.forEach(s => {
if (s.asyncCause) {
frames.push("\t", AsyncFrame({
asyncCause: s.asyncCause
}), "\n");
}
frames.push("\t", Frame({
frame: {
functionDisplayName: s.functionName,
source: s.filename.split(" -> ").pop(),
line: s.lineNumber,
column: s.columnNumber,
},
showFunctionName: true,
showAnonymousFunctionName: true,
showFullSourceUrl: true,
onClick: onViewSourceInDebugger
}), "\n");
});
return dom.div({ className: "stack-trace" }, frames);
}
});
module.exports = StackTrace;

View file

@ -0,0 +1,12 @@
# -*- 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(
'tabbar.css',
'tabbar.js',
'tabs.css',
'tabs.js',
)

View file

@ -0,0 +1,53 @@
/* 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/. */
.tabs .tabs-navigation {
line-height: 15px;
}
.tabs .tabs-navigation {
height: 24px;
}
.tabs .tabs-menu-item:first-child {
border-inline-start-width: 0;
}
.tabs .tabs-navigation .tabs-menu-item:focus {
outline: var(--theme-focus-outline);
outline-offset: -2px;
}
.tabs .tabs-menu-item.is-active {
height: 23px;
}
/* Firebug theme is using slightly different height. */
.theme-firebug .tabs .tabs-navigation {
height: 24px;
}
/* The tab takes entire horizontal space and individual tabs
should stretch accordingly. Use flexbox for the behavior.
Use also `overflow: hidden` so, 'overflow' and 'underflow'
events are fired (it's utilized by the all-tabs-menu). */
.tabs .tabs-navigation .tabs-menu {
overflow: hidden;
display: flex;
}
.tabs .tabs-navigation .tabs-menu-item {
flex-grow: 1;
}
.tabs .tabs-navigation .tabs-menu-item a {
text-align: center;
}
/* Firebug theme doesn't stretch the tabs. */
.theme-firebug .tabs .tabs-navigation .tabs-menu-item {
flex-grow: 0;
}

View file

@ -0,0 +1,204 @@
/* -*- 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";
const { DOM, createClass, PropTypes, createFactory } = require("devtools/client/shared/vendor/react");
const Tabs = createFactory(require("devtools/client/shared/components/tabs/tabs").Tabs);
const Menu = require("devtools/client/framework/menu");
const MenuItem = require("devtools/client/framework/menu-item");
// Shortcuts
const { div } = DOM;
/**
* Renders Tabbar component.
*/
let Tabbar = createClass({
displayName: "Tabbar",
propTypes: {
onSelect: PropTypes.func,
showAllTabsMenu: PropTypes.bool,
toolbox: PropTypes.object,
},
getDefaultProps: function () {
return {
showAllTabsMenu: false,
};
},
getInitialState: function () {
return {
tabs: [],
activeTab: 0
};
},
// Public API
addTab: function (id, title, selected = false, panel, url) {
let tabs = this.state.tabs.slice();
tabs.push({id, title, panel, url});
let newState = Object.assign({}, this.state, {
tabs: tabs,
});
if (selected) {
newState.activeTab = tabs.length - 1;
}
this.setState(newState, () => {
if (this.props.onSelect && selected) {
this.props.onSelect(id);
}
});
},
toggleTab: function (tabId, isVisible) {
let index = this.getTabIndex(tabId);
if (index < 0) {
return;
}
let tabs = this.state.tabs.slice();
tabs[index] = Object.assign({}, tabs[index], {
isVisible: isVisible
});
this.setState(Object.assign({}, this.state, {
tabs: tabs,
}));
},
removeTab: function (tabId) {
let index = this.getTabIndex(tabId);
if (index < 0) {
return;
}
let tabs = this.state.tabs.slice();
tabs.splice(index, 1);
this.setState(Object.assign({}, this.state, {
tabs: tabs,
}));
},
select: function (tabId) {
let index = this.getTabIndex(tabId);
if (index < 0) {
return;
}
let newState = Object.assign({}, this.state, {
activeTab: index,
});
this.setState(newState, () => {
if (this.props.onSelect) {
this.props.onSelect(tabId);
}
});
},
// Helpers
getTabIndex: function (tabId) {
let tabIndex = -1;
this.state.tabs.forEach((tab, index) => {
if (tab.id == tabId) {
tabIndex = index;
}
});
return tabIndex;
},
getTabId: function (index) {
return this.state.tabs[index].id;
},
getCurrentTabId: function () {
return this.state.tabs[this.state.activeTab].id;
},
// Event Handlers
onTabChanged: function (index) {
this.setState({
activeTab: index
});
if (this.props.onSelect) {
this.props.onSelect(this.state.tabs[index].id);
}
},
onAllTabsMenuClick: function (event) {
let menu = new Menu();
let target = event.target;
// Generate list of menu items from the list of tabs.
this.state.tabs.forEach(tab => {
menu.append(new MenuItem({
label: tab.title,
type: "checkbox",
checked: this.getCurrentTabId() == tab.id,
click: () => this.select(tab.id),
}));
});
// Show a drop down menu with frames.
// XXX Missing menu API for specifying target (anchor)
// and relative position to it. See also:
// https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XUL/Method/openPopup
// https://bugzilla.mozilla.org/show_bug.cgi?id=1274551
let rect = target.getBoundingClientRect();
let screenX = target.ownerDocument.defaultView.mozInnerScreenX;
let screenY = target.ownerDocument.defaultView.mozInnerScreenY;
menu.popup(rect.left + screenX, rect.bottom + screenY, this.props.toolbox);
return menu;
},
// Rendering
renderTab: function (tab) {
if (typeof tab.panel === "function") {
return tab.panel({
key: tab.id,
title: tab.title,
id: tab.id,
url: tab.url,
});
}
return tab.panel;
},
render: function () {
let tabs = this.state.tabs.map(tab => {
return this.renderTab(tab);
});
return (
div({className: "devtools-sidebar-tabs"},
Tabs({
onAllTabsMenuClick: this.onAllTabsMenuClick,
showAllTabsMenu: this.props.showAllTabsMenu,
tabActive: this.state.activeTab,
onAfterChange: this.onTabChanged},
tabs
)
)
);
},
});
module.exports = Tabbar;

View file

@ -0,0 +1,183 @@
/* 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/. */
/* Tabs General Styles */
.tabs {
height: 100%;
}
.tabs .tabs-menu {
display: table;
list-style: none;
padding: 0;
margin: 0;
}
.tabs .tabs-menu-item {
display: inline-block;
}
.tabs .tabs-menu-item a {
display: block;
color: #A9A9A9;
padding: 4px 8px;
border: 1px solid transparent;
text-decoration: none;
white-space: nowrap;
}
.tabs .tabs-menu-item a {
cursor: default;
}
/* Make sure panel content takes entire vertical space.
(minus the height of the tab bar) */
.tabs .panels {
height: calc(100% - 24px);
}
.tabs .tab-panel {
height: 100%;
}
.tabs .all-tabs-menu {
position: absolute;
top: 0;
offset-inline-end: 0;
width: 15px;
height: 100%;
border-inline-start: 1px solid var(--theme-splitter-color);
background: url("chrome://devtools/skin/images/dropmarker.svg");
background-repeat: no-repeat;
background-position: center;
background-color: var(--theme-tab-toolbar-background);
}
/* Light Theme */
.theme-dark .tabs,
.theme-light .tabs {
background: var(--theme-body-background);
}
.theme-dark .tabs .tabs-navigation,
.theme-light .tabs .tabs-navigation {
position: relative;
border-bottom: 1px solid var(--theme-splitter-color);
background: var(--theme-tab-toolbar-background);
}
.theme-dark .tabs .tabs-menu-item,
.theme-light .tabs .tabs-menu-item {
margin: 0;
padding: 0;
border-style: solid;
border-width: 0;
border-inline-start-width: 1px;
border-color: var(--theme-splitter-color);
}
.theme-dark .tabs .tabs-menu-item:last-child,
.theme-light:not(.theme-firebug) .tabs .tabs-menu-item:last-child {
border-inline-end-width: 1px;
}
.theme-dark .tabs .tabs-menu-item a,
.theme-light .tabs .tabs-menu-item a {
color: var(--theme-content-color1);
padding: 3px 15px;
}
.theme-dark .tabs .tabs-menu-item:hover:not(.is-active),
.theme-light .tabs .tabs-menu-item:hover:not(.is-active) {
background-color: var(--toolbar-tab-hover);
}
.theme-dark .tabs .tabs-menu-item:hover:active:not(.is-active),
.theme-light .tabs .tabs-menu-item:hover:active:not(.is-active) {
background-color: var(--toolbar-tab-hover-active);
}
.theme-dark .tabs .tabs-menu-item.is-active,
.theme-light .tabs .tabs-menu-item.is-active {
background-color: var(--theme-selection-background);
}
.theme-dark .tabs .tabs-menu-item.is-active a,
.theme-light .tabs .tabs-menu-item.is-active a {
color: var(--theme-selection-color);
}
/* Dark Theme */
.theme-dark .tabs .tabs-menu-item a {
color: var(--theme-body-color-alt);
}
.theme-dark .tabs .tabs-menu-item:hover:not(.is-active) a {
color: #CED3D9;
}
.theme-dark .tabs .tabs-menu-item:hover:active a {
color: var(--theme-selection-color);
}
/* Firebug Theme */
.theme-firebug .tabs .tabs-navigation {
background-image: linear-gradient(rgba(253, 253, 253, 0.2), rgba(253, 253, 253, 0));
padding-top: 3px;
padding-left: 3px;
border-bottom: 1px solid rgb(170, 188, 207);
}
.theme-firebug .tabs .tabs-menu {
margin-bottom: -1px;
}
.theme-firebug .tabs .tabs-menu-item.is-active,
.theme-firebug .tabs .tabs-menu-item.is-active:hover {
background-color: transparent;
}
.theme-firebug .tabs .tabs-menu-item {
position: relative;
border-inline-start-width: 0;
}
.theme-firebug .tabs .tabs-menu-item a {
font-family: var(--proportional-font-family);
font-weight: bold;
color: var(--theme-body-color);
border-radius: 4px 4px 0 0;
}
.theme-firebug .tabs .tabs-menu-item:hover:not(.is-active) a {
border: 1px solid #C8C8C8;
border-bottom: 1px solid transparent;
background-color: transparent;
}
.theme-firebug .tabs .tabs-menu-item.is-active a {
background-color: rgb(247, 251, 254);
border: 1px solid rgb(170, 188, 207);
border-bottom-color: transparent;
color: var(--theme-body-color);
}
.theme-firebug .tabs .tabs-menu-item:hover:active a {
background-color: var(--toolbar-tab-hover-active);
}
.theme-firebug .tabs .tabs-menu-item.is-active:hover:active a {
background-color: var(--theme-selection-background);
color: var(--theme-selection-color);
}
.theme-firebug .tabs .tabs-menu-item a {
border: 1px solid transparent;
padding: 4px 8px;
}

View file

@ -0,0 +1,369 @@
/* -*- 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";
define(function (require, exports, module) {
const React = require("devtools/client/shared/vendor/react");
const { DOM } = React;
const { findDOMNode } = require("devtools/client/shared/vendor/react-dom");
/**
* Renders simple 'tab' widget.
*
* Based on ReactSimpleTabs component
* https://github.com/pedronauck/react-simpletabs
*
* Component markup (+CSS) example:
*
* <div class='tabs'>
* <nav class='tabs-navigation'>
* <ul class='tabs-menu'>
* <li class='tabs-menu-item is-active'>Tab #1</li>
* <li class='tabs-menu-item'>Tab #2</li>
* </ul>
* </nav>
* <div class='panels'>
* The content of active panel here
* </div>
* <div>
*/
let Tabs = React.createClass({
displayName: "Tabs",
propTypes: {
className: React.PropTypes.oneOfType([
React.PropTypes.array,
React.PropTypes.string,
React.PropTypes.object
]),
tabActive: React.PropTypes.number,
onMount: React.PropTypes.func,
onBeforeChange: React.PropTypes.func,
onAfterChange: React.PropTypes.func,
children: React.PropTypes.oneOfType([
React.PropTypes.array,
React.PropTypes.element
]).isRequired,
showAllTabsMenu: React.PropTypes.bool,
onAllTabsMenuClick: React.PropTypes.func,
},
getDefaultProps: function () {
return {
tabActive: 0,
showAllTabsMenu: false,
};
},
getInitialState: function () {
return {
tabActive: this.props.tabActive,
// This array is used to store an information whether a tab
// at specific index has already been created (e.g. selected
// at least once).
// If yes, it's rendered even if not currently selected.
// This is because in some cases we don't want to re-create
// tab content when it's being unselected/selected.
// E.g. in case of an iframe being used as a tab-content
// we want the iframe to stay in the DOM.
created: [],
// True if tabs can't fit into available horizontal space.
overflow: false,
};
},
componentDidMount: function () {
let node = findDOMNode(this);
node.addEventListener("keydown", this.onKeyDown, false);
// Register overflow listeners to manage visibility
// of all-tabs-menu. This menu is displayed when there
// is not enough h-space to render all tabs.
// It allows the user to select a tab even if it's hidden.
if (this.props.showAllTabsMenu) {
node.addEventListener("overflow", this.onOverflow, false);
node.addEventListener("underflow", this.onUnderflow, false);
}
let index = this.state.tabActive;
if (this.props.onMount) {
this.props.onMount(index);
}
},
componentWillReceiveProps: function (newProps) {
// Check type of 'tabActive' props to see if it's valid
// (it's 0-based index).
if (typeof newProps.tabActive == "number") {
let created = [...this.state.created];
created[newProps.tabActive] = true;
this.setState(Object.assign({}, this.state, {
tabActive: newProps.tabActive,
created: created,
}));
}
},
componentWillUnmount: function () {
let node = findDOMNode(this);
node.removeEventListener("keydown", this.onKeyDown, false);
if (this.props.showAllTabsMenu) {
node.removeEventListener("overflow", this.onOverflow, false);
node.removeEventListener("underflow", this.onUnderflow, false);
}
},
// DOM Events
onOverflow: function (event) {
if (event.target.classList.contains("tabs-menu")) {
this.setState({
overflow: true
});
}
},
onUnderflow: function (event) {
if (event.target.classList.contains("tabs-menu")) {
this.setState({
overflow: false
});
}
},
onKeyDown: function (event) {
// Bail out if the focus isn't on a tab.
if (!event.target.closest(".tabs-menu-item")) {
return;
}
let tabActive = this.state.tabActive;
let tabCount = this.props.children.length;
switch (event.code) {
case "ArrowRight":
tabActive = Math.min(tabCount - 1, tabActive + 1);
break;
case "ArrowLeft":
tabActive = Math.max(0, tabActive - 1);
break;
}
if (this.state.tabActive != tabActive) {
this.setActive(tabActive);
}
},
onClickTab: function (index, event) {
this.setActive(index);
event.preventDefault();
},
onAllTabsMenuClick: function (event) {
if (this.props.onAllTabsMenuClick) {
this.props.onAllTabsMenuClick(event);
}
},
// API
setActive: function (index) {
let onAfterChange = this.props.onAfterChange;
let onBeforeChange = this.props.onBeforeChange;
if (onBeforeChange) {
let cancel = onBeforeChange(index);
if (cancel) {
return;
}
}
let created = [...this.state.created];
created[index] = true;
let newState = Object.assign({}, this.state, {
tabActive: index,
created: created
});
this.setState(newState, () => {
// Properly set focus on selected tab.
let node = findDOMNode(this);
let selectedTab = node.querySelector(".is-active > a");
if (selectedTab) {
selectedTab.focus();
}
if (onAfterChange) {
onAfterChange(index);
}
});
},
// Rendering
renderMenuItems: function () {
if (!this.props.children) {
throw new Error("There must be at least one Tab");
}
if (!Array.isArray(this.props.children)) {
this.props.children = [this.props.children];
}
let tabs = this.props.children
.map(tab => {
return typeof tab === "function" ? tab() : tab;
}).filter(tab => {
return tab;
}).map((tab, index) => {
let ref = ("tab-menu-" + index);
let title = tab.props.title;
let tabClassName = tab.props.className;
let isTabSelected = this.state.tabActive === index;
let classes = [
"tabs-menu-item",
tabClassName,
isTabSelected ? "is-active" : ""
].join(" ");
// Set tabindex to -1 (except the selected tab) so, it's focusable,
// but not reachable via sequential tab-key navigation.
// Changing selected tab (and so, moving focus) is done through
// left and right arrow keys.
// See also `onKeyDown()` event handler.
return (
DOM.li({
ref: ref,
key: index,
id: "tab-" + index,
className: classes,
role: "presentation",
},
DOM.a({
tabIndex: this.state.tabActive === index ? 0 : -1,
"aria-controls": "panel-" + index,
"aria-selected": isTabSelected,
role: "tab",
onClick: this.onClickTab.bind(this, index),
},
title
)
)
);
});
// Display the menu only if there is not enough horizontal
// space for all tabs (and overflow happened).
let allTabsMenu = this.state.overflow ? (
DOM.div({
className: "all-tabs-menu",
onClick: this.props.onAllTabsMenuClick
})
) : null;
return (
DOM.nav({className: "tabs-navigation"},
DOM.ul({className: "tabs-menu", role: "tablist"},
tabs
),
allTabsMenu
)
);
},
renderPanels: function () {
if (!this.props.children) {
throw new Error("There must be at least one Tab");
}
if (!Array.isArray(this.props.children)) {
this.props.children = [this.props.children];
}
let selectedIndex = this.state.tabActive;
let panels = this.props.children
.map(tab => {
return typeof tab === "function" ? tab() : tab;
}).filter(tab => {
return tab;
}).map((tab, index) => {
let selected = selectedIndex == index;
// Use 'visibility:hidden' + 'width/height:0' for hiding
// content of non-selected tab. It's faster (not sure why)
// than display:none and visibility:collapse.
let style = {
visibility: selected ? "visible" : "hidden",
height: selected ? "100%" : "0",
width: selected ? "100%" : "0",
};
return (
DOM.div({
key: index,
id: "panel-" + index,
style: style,
className: "tab-panel-box",
role: "tabpanel",
"aria-labelledby": "tab-" + index,
},
(selected || this.state.created[index]) ? tab : null
)
);
});
return (
DOM.div({className: "panels"},
panels
)
);
},
render: function () {
let classNames = ["tabs", this.props.className].join(" ");
return (
DOM.div({className: classNames},
this.renderMenuItems(),
this.renderPanels()
)
);
},
});
/**
* Renders simple tab 'panel'.
*/
let Panel = React.createClass({
displayName: "Panel",
propTypes: {
title: React.PropTypes.string.isRequired,
children: React.PropTypes.oneOfType([
React.PropTypes.array,
React.PropTypes.element
]).isRequired
},
render: function () {
return DOM.div({className: "tab-panel"},
this.props.children
);
}
});
// Exports from this module
exports.TabPanel = Panel;
exports.Tabs = Tabs;
});

View file

@ -0,0 +1,6 @@
"use strict";
module.exports = {
// Extend from the shared list of defined globals for mochitests.
"extends": "../../../../../.eslintrc.mochitests.js",
};

View file

@ -0,0 +1,7 @@
[DEFAULT]
tags = devtools
subsuite = devtools
support-files =
!/devtools/client/framework/test/shared-head.js
[browser_notification_box_basic.js]

View file

@ -0,0 +1,36 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
/* import-globals-from ../../../../framework/test/shared-head.js */
Services.scriptloader.loadSubScript(
"chrome://mochitests/content/browser/devtools/client/framework/test/shared-head.js", this);
const TEST_URI = "data:text/html;charset=utf-8,Test page";
/**
* Basic test that checks existence of the Notification box.
*/
add_task(function* () {
info("Test Notification box basic started");
let toolbox = yield openNewTabAndToolbox(TEST_URI, "webconsole");
// Append a notification
let notificationBox = toolbox.getNotificationBox();
notificationBox.appendNotification(
"Info message",
"id1",
null,
notificationBox.PRIORITY_INFO_HIGH
);
// Verify existence of one notification.
let parentNode = toolbox.doc.getElementById("toolbox-notificationbox");
let nodes = parentNode.querySelectorAll(".notification");
is(nodes.length, 1, "There must be one notification");
});

View file

@ -0,0 +1,6 @@
"use strict";
module.exports = {
// Extend from the shared list of defined globals for mochitests.
"extends": "../../../../../.eslintrc.mochitests.js"
};

View file

@ -0,0 +1,51 @@
[DEFAULT]
support-files =
head.js
[test_frame_01.html]
[test_HSplitBox_01.html]
[test_notification_box_01.html]
[test_notification_box_02.html]
[test_notification_box_03.html]
[test_reps_array.html]
[test_reps_attribute.html]
[test_reps_comment-node.html]
[test_reps_date-time.html]
[test_reps_document.html]
[test_reps_element-node.html]
[test_reps_event.html]
[test_reps_function.html]
[test_reps_grip.html]
[test_reps_grip-array.html]
[test_reps_grip-map.html]
[test_reps_infinity.html]
[test_reps_long-string.html]
[test_reps_nan.html]
[test_reps_null.html]
[test_reps_number.html]
[test_reps_object.html]
[test_reps_object-with-text.html]
[test_reps_object-with-url.html]
[test_reps_promise.html]
[test_reps_regexp.html]
[test_reps_string.html]
[test_reps_stylesheet.html]
[test_reps_symbol.html]
[test_reps_text-node.html]
[test_reps_undefined.html]
[test_reps_window.html]
[test_sidebar_toggle.html]
[test_stack-trace.html]
[test_tabs_accessibility.html]
[test_tabs_menu.html]
[test_tree_01.html]
[test_tree_02.html]
[test_tree_03.html]
[test_tree_04.html]
[test_tree_05.html]
[test_tree_06.html]
[test_tree_07.html]
[test_tree_08.html]
[test_tree_09.html]
[test_tree_10.html]
[test_tree_11.html]

View file

@ -0,0 +1,217 @@
/* 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/. */
/* eslint no-unused-vars: [2, {"vars": "local"}] */
"use strict";
var { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
var { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
var { Assert } = require("resource://testing-common/Assert.jsm");
var { gDevTools } = require("devtools/client/framework/devtools");
var { BrowserLoader } = Cu.import("resource://devtools/client/shared/browser-loader.js", {});
var promise = require("promise");
var defer = require("devtools/shared/defer");
var Services = require("Services");
var { DebuggerServer } = require("devtools/server/main");
var { DebuggerClient } = require("devtools/shared/client/main");
var DevToolsUtils = require("devtools/shared/DevToolsUtils");
var flags = require("devtools/shared/flags");
var { Task } = require("devtools/shared/task");
var { TargetFactory } = require("devtools/client/framework/target");
var { Toolbox } = require("devtools/client/framework/toolbox");
flags.testing = true;
var { require: browserRequire } = BrowserLoader({
baseURI: "resource://devtools/client/shared/",
window
});
let ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom");
let React = browserRequire("devtools/client/shared/vendor/react");
var TestUtils = React.addons.TestUtils;
var EXAMPLE_URL = "http://example.com/browser/browser/devtools/shared/test/";
function forceRender(comp) {
return setState(comp, {})
.then(() => setState(comp, {}));
}
// All tests are asynchronous.
SimpleTest.waitForExplicitFinish();
function onNextAnimationFrame(fn) {
return () =>
requestAnimationFrame(() =>
requestAnimationFrame(fn));
}
function setState(component, newState) {
return new Promise(resolve => {
component.setState(newState, onNextAnimationFrame(resolve));
});
}
function setProps(component, newProps) {
return new Promise(resolve => {
component.setProps(newProps, onNextAnimationFrame(resolve));
});
}
function dumpn(msg) {
dump(`SHARED-COMPONENTS-TEST: ${msg}\n`);
}
/**
* Tree
*/
var TEST_TREE_INTERFACE = {
getParent: x => TEST_TREE.parent[x],
getChildren: x => TEST_TREE.children[x],
renderItem: (x, depth, focused) => "-".repeat(depth) + x + ":" + focused + "\n",
getRoots: () => ["A", "M"],
getKey: x => "key-" + x,
itemHeight: 1,
onExpand: x => TEST_TREE.expanded.add(x),
onCollapse: x => TEST_TREE.expanded.delete(x),
isExpanded: x => TEST_TREE.expanded.has(x),
};
function isRenderedTree(actual, expectedDescription, msg) {
const expected = expectedDescription.map(x => x + "\n").join("");
dumpn(`Expected tree:\n${expected}`);
dumpn(`Actual tree:\n${actual}`);
is(actual, expected, msg);
}
// Encoding of the following tree/forest:
//
// A
// |-- B
// | |-- E
// | | |-- K
// | | `-- L
// | |-- F
// | `-- G
// |-- C
// | |-- H
// | `-- I
// `-- D
// `-- J
// M
// `-- N
// `-- O
var TEST_TREE = {
children: {
A: ["B", "C", "D"],
B: ["E", "F", "G"],
C: ["H", "I"],
D: ["J"],
E: ["K", "L"],
F: [],
G: [],
H: [],
I: [],
J: [],
K: [],
L: [],
M: ["N"],
N: ["O"],
O: []
},
parent: {
A: null,
B: "A",
C: "A",
D: "A",
E: "B",
F: "B",
G: "B",
H: "C",
I: "C",
J: "D",
K: "E",
L: "E",
M: null,
N: "M",
O: "N"
},
expanded: new Set(),
};
/**
* Frame
*/
function checkFrameString({
el, file, line, column, source, functionName, shouldLink, tooltip
}) {
let $ = selector => el.querySelector(selector);
let $func = $(".frame-link-function-display-name");
let $source = $(".frame-link-source");
let $sourceInner = $(".frame-link-source-inner");
let $filename = $(".frame-link-filename");
let $line = $(".frame-link-line");
is($filename.textContent, file, "Correct filename");
is(el.getAttribute("data-line"), line ? `${line}` : null, "Expected `data-line` found");
is(el.getAttribute("data-column"),
column ? `${column}` : null, "Expected `data-column` found");
is($sourceInner.getAttribute("title"), tooltip, "Correct tooltip");
is($source.tagName, shouldLink ? "A" : "SPAN", "Correct linkable status");
if (shouldLink) {
is($source.getAttribute("href"), source, "Correct source");
}
if (line != null) {
let lineText = `:${line}`;
if (column != null) {
lineText += `:${column}`;
}
is($line.textContent, lineText, "Correct line number");
} else {
ok(!$line, "Should not have an element for `line`");
}
if (functionName != null) {
is($func.textContent, functionName, "Correct function name");
} else {
ok(!$func, "Should not have an element for `functionName`");
}
}
function renderComponent(component, props) {
const el = React.createElement(component, props, {});
// By default, renderIntoDocument() won't work for stateless components, but
// it will work if the stateless component is wrapped in a stateful one.
// See https://github.com/facebook/react/issues/4839
const wrappedEl = React.DOM.span({}, [el]);
const renderedComponent = TestUtils.renderIntoDocument(wrappedEl);
return ReactDOM.findDOMNode(renderedComponent).children[0];
}
function shallowRenderComponent(component, props) {
const el = React.createElement(component, props);
const renderer = TestUtils.createRenderer();
renderer.render(el, {});
return renderer.getRenderOutput();
}
/**
* Test that a rep renders correctly across different modes.
*/
function testRepRenderModes(modeTests, testName, componentUnderTest, gripStub) {
modeTests.forEach(({mode, expectedOutput, message}) => {
const modeString = typeof mode === "undefined" ? "no mode" : mode;
if (!message) {
message = `${testName}: ${modeString} renders correctly.`;
}
const rendered = renderComponent(componentUnderTest.rep, { object: gripStub, mode });
is(rendered.textContent, expectedOutput, message);
});
}

View file

@ -0,0 +1,126 @@
<!-- 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/. -->
<!DOCTYPE HTML>
<html>
<!--
Basic tests for the HSplitBox component.
-->
<head>
<meta charset="utf-8">
<title>Tree component test</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript "src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
<link rel="stylesheet" href="resource://devtools/client/themes/splitters.css" type="text/css"/>
<link rel="stylesheet" href="chrome://devtools/skin/components-h-split-box.css" type="text/css"/>
<style>
html {
--theme-splitter-color: black;
}
</style>
</head>
<body>
<pre id="test">
<script src="head.js" type="application/javascript;version=1.8"></script>
<script type="application/javascript;version=1.8">
const FUDGE_FACTOR = .1;
function aboutEq(a, b) {
dumpn(`Checking ${a} ~= ${b}`);
return Math.abs(a - b) < FUDGE_FACTOR;
}
window.onload = Task.async(function* () {
try {
const React = browserRequire("devtools/client/shared/vendor/react");
const ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom");
let HSplitBox = React.createFactory(browserRequire("devtools/client/shared/components/h-split-box"));
ok(HSplitBox, "Should get HSplitBox");
const newSizes = [];
const box = ReactDOM.render(HSplitBox({
start: "hello!",
end: "world!",
startWidth: .5,
onResize(newSize) {
newSizes.push(newSize);
},
}), window.document.body);
// Test that we properly rendered our two panes.
let panes = document.querySelectorAll(".h-split-box-pane");
is(panes.length, 2, "Should get two panes");
is(panes[0].style.flexGrow, "0.5", "Each pane should have .5 width");
is(panes[1].style.flexGrow, "0.5", "Each pane should have .5 width");
is(panes[0].textContent.trim(), "hello!", "First pane should be hello");
is(panes[1].textContent.trim(), "world!", "Second pane should be world");
// Now change the left width and assert that the changes are reflected.
yield setProps(box, { startWidth: .25 });
panes = document.querySelectorAll(".h-split-box-pane");
is(panes.length, 2, "Should still have two panes");
is(panes[0].style.flexGrow, "0.25", "First pane's width should be .25");
is(panes[1].style.flexGrow, "0.75", "Second pane's width should be .75");
// Mouse moves without having grabbed the splitter should have no effect.
let container = document.querySelector(".h-split-box");
ok(container, "Should get our container .h-split-box");
const { left, top, width } = container.getBoundingClientRect();
const middle = left + width / 2;
const oneQuarter = left + width / 4;
const threeQuarters = left + 3 * width / 4;
synthesizeMouse(container, middle, top, { type: "mousemove" }, window);
is(newSizes.length, 0, "Mouse moves without dragging the splitter should have no effect");
// Send a mouse down on the splitter, and then move the mouse a couple
// times. Now we should get resizes.
const splitter = document.querySelector(".devtools-side-splitter");
ok(splitter, "Should get our splitter");
synthesizeMouseAtCenter(splitter, { button: 0, type: "mousedown" }, window);
function mouseMove(clientX) {
const event = new MouseEvent("mousemove", { clientX });
document.defaultView.top.dispatchEvent(event);
}
mouseMove(middle);
is(newSizes.length, 1, "Should get 1 resize");
ok(aboutEq(newSizes[0], .5), "New size should be ~.5");
mouseMove(left);
is(newSizes.length, 2, "Should get 2 resizes");
ok(aboutEq(newSizes[1], 0), "New size should be ~0");
mouseMove(oneQuarter);
is(newSizes.length, 3, "Sould get 3 resizes");
ok(aboutEq(newSizes[2], .25), "New size should be ~.25");
mouseMove(threeQuarters);
is(newSizes.length, 4, "Should get 4 resizes");
ok(aboutEq(newSizes[3], .75), "New size should be ~.75");
synthesizeMouseAtCenter(splitter, { button: 0, type: "mouseup" }, window);
// Now that we have let go of the splitter, mouse moves should not result in resizes.
synthesizeMouse(container, middle, top, { type: "mousemove" }, window);
is(newSizes.length, 4, "Should still have 4 resizes");
} catch(e) {
ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
} finally {
SimpleTest.finish();
}
});
</script>
</pre>
</body>
</html>

View file

@ -0,0 +1,309 @@
<!-- 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/. -->
<!DOCTYPE HTML>
<html>
<!--
Test the formatting of the file name, line and columns are correct in frame components,
with optional columns, unknown and non-URL sources.
-->
<head>
<meta charset="utf-8">
<title>Frame component test</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
</head>
<body>
<pre id="test">
<script src="head.js" type="application/javascript;version=1.8"></script>
<script type="application/javascript;version=1.8">
window.onload = Task.async(function* () {
try {
let ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom");
let React = browserRequire("devtools/client/shared/vendor/react");
let Frame = React.createFactory(browserRequire("devtools/client/shared/components/frame"));
ok(Frame, "Should get Frame");
// Check when there's a column
yield checkFrameComponent({
frame: {
source: "http://myfile.com/mahscripts.js",
line: 55,
column: 10,
}
}, {
file: "mahscripts.js",
line: 55,
column: 10,
shouldLink: true,
tooltip: "View source in Debugger → http://myfile.com/mahscripts.js:55:10",
});
// Check when there's no column
yield checkFrameComponent({
frame: {
source: "http://myfile.com/mahscripts.js",
line: 55,
}
}, {
file: "mahscripts.js",
line: 55,
shouldLink: true,
tooltip: "View source in Debugger → http://myfile.com/mahscripts.js:55",
});
// Check when column === 0
yield checkFrameComponent({
frame: {
source: "http://myfile.com/mahscripts.js",
line: 55,
column: 0,
}
}, {
file: "mahscripts.js",
line: 55,
shouldLink: true,
tooltip: "View source in Debugger → http://myfile.com/mahscripts.js:55",
});
// Check when there's no parseable URL source;
// should not link but should render line/columns
yield checkFrameComponent({
frame: {
source: "self-hosted",
line: 1,
}
}, {
file: "self-hosted",
line: "1",
shouldLink: false,
tooltip: "self-hosted:1",
});
yield checkFrameComponent({
frame: {
source: "self-hosted",
line: 1,
column: 10,
}
}, {
file: "self-hosted",
line: "1",
column: "10",
shouldLink: false,
tooltip: "self-hosted:1:10",
});
// Check when there's no source;
// should not link but should render line/columns
yield checkFrameComponent({
frame: {
line: 1,
}
}, {
file: "(unknown)",
line: "1",
shouldLink: false,
tooltip: "(unknown):1",
});
yield checkFrameComponent({
frame: {
line: 1,
column: 10,
}
}, {
file: "(unknown)",
line: "1",
column: "10",
shouldLink: false,
tooltip: "(unknown):1:10",
});
// Check when there's a column, but no line;
// no line/column info should render
yield checkFrameComponent({
frame: {
source: "http://myfile.com/mahscripts.js",
column: 55,
}
}, {
file: "mahscripts.js",
shouldLink: true,
tooltip: "View source in Debugger → http://myfile.com/mahscripts.js",
});
// Check when line is 0; this should be an invalid
// line option, so don't render line/column
yield checkFrameComponent({
frame: {
source: "http://myfile.com/mahscripts.js",
line: 0,
column: 55,
}
}, {
file: "mahscripts.js",
shouldLink: true,
tooltip: "View source in Debugger → http://myfile.com/mahscripts.js",
});
// Check when source is via Scratchpad; we should render out the
// lines and columns as this is linkable.
yield checkFrameComponent({
frame: {
source: "Scratchpad/1",
line: 10,
column: 50,
}
}, {
file: "Scratchpad/1",
line: 10,
column: 50,
shouldLink: true,
tooltip: "View source in Debugger → Scratchpad/1:10:50",
});
// Check that line and column can be strings
yield checkFrameComponent({
frame: {
source: "http://myfile.com/mahscripts.js",
line: "10",
column: "55",
}
}, {
file: "mahscripts.js",
line: 10,
column: 55,
shouldLink: true,
tooltip: "View source in Debugger → http://myfile.com/mahscripts.js:10:55",
});
// Check that line and column can be strings,
// and that the `0` rendering rules apply when they are strings as well
yield checkFrameComponent({
frame: {
source: "http://myfile.com/mahscripts.js",
line: "0",
column: "55",
}
}, {
file: "mahscripts.js",
shouldLink: true,
tooltip: "View source in Debugger → http://myfile.com/mahscripts.js",
});
// Check that the showFullSourceUrl option works correctly
yield checkFrameComponent({
frame: {
source: "http://myfile.com/mahscripts.js",
line: 0,
},
showFullSourceUrl: true
}, {
file: "http://myfile.com/mahscripts.js",
shouldLink: true,
tooltip: "View source in Debugger → http://myfile.com/mahscripts.js",
});
// Check that the showFunctionName option works correctly
yield checkFrameComponent({
frame: {
functionDisplayName: "myfun",
source: "http://myfile.com/mahscripts.js",
line: 0,
}
}, {
functionName: null,
file: "mahscripts.js",
shouldLink: true,
tooltip: "View source in Debugger → http://myfile.com/mahscripts.js",
});
yield checkFrameComponent({
frame: {
functionDisplayName: "myfun",
source: "http://myfile.com/mahscripts.js",
line: 0,
},
showFunctionName: true
}, {
functionName: "myfun",
file: "mahscripts.js",
shouldLink: true,
tooltip: "View source in Debugger → http://myfile.com/mahscripts.js",
});
// Check that anonymous function name is not displayed unless explicitly enabled
yield checkFrameComponent({
frame: {
source: "http://myfile.com/mahscripts.js",
line: 0,
},
showFunctionName: true
}, {
functionName: null,
file: "mahscripts.js",
shouldLink: true,
tooltip: "View source in Debugger → http://myfile.com/mahscripts.js",
});
yield checkFrameComponent({
frame: {
source: "http://myfile.com/mahscripts.js",
line: 0,
},
showFunctionName: true,
showAnonymousFunctionName: true
}, {
functionName: "<anonymous>",
file: "mahscripts.js",
shouldLink: true,
tooltip: "View source in Debugger → http://myfile.com/mahscripts.js",
});
// Check if file is rendered with "/" for root documents when showEmptyPathAsHost is false
yield checkFrameComponent({
frame: {
source: "http://www.cnn.com/",
line: "1",
},
showEmptyPathAsHost: false,
}, {
file: "/",
line: "1",
shouldLink: true,
tooltip: "View source in Debugger → http://www.cnn.com/:1",
});
// Check if file is rendered with hostname for root documents when showEmptyPathAsHost is true
yield checkFrameComponent({
frame: {
source: "http://www.cnn.com/",
line: "1",
},
showEmptyPathAsHost: true,
}, {
file: "www.cnn.com",
line: "1",
shouldLink: true,
tooltip: "View source in Debugger → http://www.cnn.com/:1",
});
function* checkFrameComponent(input, expected) {
let props = Object.assign({ onClick: () => {} }, input);
let frame = ReactDOM.render(Frame(props), window.document.body);
yield forceRender(frame);
let el = frame.getDOMNode();
let { source } = input.frame;
checkFrameString(Object.assign({ el, source }, expected));
}
} catch (e) {
ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
} finally {
SimpleTest.finish();
}
});
</script>
</pre>
</body>
</html>

View file

@ -0,0 +1,108 @@
<!-- 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/. -->
<!DOCTYPE HTML>
<html>
<!--
Test for Notification Box. The test is checking:
* Basic rendering
* Appending a notification
* Notification priority
* Closing notification
-->
<head>
<meta charset="utf-8">
<title>Notification Box</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
</head>
<body>
<pre id="test">
<script src="head.js" type="application/javascript;version=1.8"></script>
<script type="application/javascript;version=1.8">
window.onload = Task.async(function* () {
try {
let ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom");
let React = browserRequire("devtools/client/shared/vendor/react");
let { NotificationBox, PriorityLevels } = browserRequire("devtools/client/shared/components/notification-box");
const renderedBox = shallowRenderComponent(NotificationBox, {});
is(renderedBox.type, "div", "NotificationBox is rendered as <div>");
// Test rendering
let boxElement = React.createElement(NotificationBox);
let notificationBox = TestUtils.renderIntoDocument(boxElement);
let notificationNode = ReactDOM.findDOMNode(notificationBox);
is(notificationNode.className, "notificationbox",
"NotificationBox has expected classname");
is(notificationNode.textContent, "",
"Empty NotificationBox has no text content");
checkNumberOfNotifications(notificationBox, 0);
// Append a notification
notificationBox.appendNotification(
"Info message",
"id1",
null,
PriorityLevels.PRIORITY_INFO_HIGH
);
is (notificationNode.textContent, "Info message",
"The box must display notification message");
checkNumberOfNotifications(notificationBox, 1);
// Append more important notification
notificationBox.appendNotification(
"Critical message",
"id2",
null,
PriorityLevels.PRIORITY_CRITICAL_BLOCK
);
checkNumberOfNotifications(notificationBox, 1);
is (notificationNode.textContent, "Critical message",
"The box must display more important notification message");
// Append less important notification
notificationBox.appendNotification(
"Warning message",
"id3",
null,
PriorityLevels.PRIORITY_WARNING_HIGH
);
checkNumberOfNotifications(notificationBox, 1);
is (notificationNode.textContent, "Critical message",
"The box must still display the more important notification");
ok(notificationBox.getCurrentNotification(),
"There must be current notification");
notificationBox.getNotificationWithValue("id1").close();
checkNumberOfNotifications(notificationBox, 1);
notificationBox.getNotificationWithValue("id2").close();
checkNumberOfNotifications(notificationBox, 1);
notificationBox.getNotificationWithValue("id3").close();
checkNumberOfNotifications(notificationBox, 0);
} catch(e) {
ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
} finally {
SimpleTest.finish();
}
});
function checkNumberOfNotifications(notificationBox, expected) {
is(TestUtils.scryRenderedDOMComponentsWithClass(
notificationBox, "notification").length, expected,
"The notification box must have expected number of notifications");
}
</script>
</pre>
</body>
</html>

View file

@ -0,0 +1,70 @@
<!-- 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/. -->
<!DOCTYPE HTML>
<html>
<!--
Test for Notification Box. The test is checking:
* Using custom callback in a notification
-->
<head>
<meta charset="utf-8">
<title>Notification Box</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
</head>
<body>
<pre id="test">
<script src="head.js" type="application/javascript;version=1.8"></script>
<script type="application/javascript;version=1.8">
window.onload = Task.async(function* () {
try {
let ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom");
let React = browserRequire("devtools/client/shared/vendor/react");
let { NotificationBox, PriorityLevels } = browserRequire("devtools/client/shared/components/notification-box");
// Test rendering
let boxElement = React.createElement(NotificationBox);
let notificationBox = TestUtils.renderIntoDocument(boxElement);
let notificationNode = ReactDOM.findDOMNode(notificationBox);
let callbackExecuted = false;
// Append a notification.
notificationBox.appendNotification(
"Info message",
"id1",
null,
PriorityLevels.PRIORITY_INFO_LOW,
undefined,
(reason) => {
callbackExecuted = true;
is(reason, "removed", "The reason must be expected string");
}
);
is(TestUtils.scryRenderedDOMComponentsWithClass(
notificationBox, "notification").length, 1,
"There must be one notification");
let closeButton = notificationNode.querySelector(
".messageCloseButton");
// Click the close button to close the notification.
TestUtils.Simulate.click(closeButton);
is(TestUtils.scryRenderedDOMComponentsWithClass(
notificationBox, "notification").length, 0,
"The notification box must be empty now");
ok(callbackExecuted, "Event callback must be executed.");
} catch(e) {
ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
} finally {
SimpleTest.finish();
}
});
</script>
</pre>
</body>
</html>

View file

@ -0,0 +1,84 @@
<!-- 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/. -->
<!DOCTYPE HTML>
<html>
<!--
Test for Notification Box. The test is checking:
* Using custom buttons in a notification
-->
<head>
<meta charset="utf-8">
<title>Notification Box</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
</head>
<body>
<pre id="test">
<script src="head.js" type="application/javascript;version=1.8"></script>
<script type="application/javascript;version=1.8">
window.onload = Task.async(function* () {
try {
let ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom");
let React = browserRequire("devtools/client/shared/vendor/react");
let { NotificationBox, PriorityLevels } = browserRequire("devtools/client/shared/components/notification-box");
// Test rendering
let boxElement = React.createElement(NotificationBox);
let notificationBox = TestUtils.renderIntoDocument(boxElement);
let notificationNode = ReactDOM.findDOMNode(notificationBox);
let buttonCallbackExecuted = false;
var buttons = [{
label: "Button1",
callback: () => {
buttonCallbackExecuted = true;
// Do not close the notification
return true;
},
}, {
label: "Button2",
callback: () => {
// Close the notification (return value undefined)
},
}];
// Append a notification with buttons.
notificationBox.appendNotification(
"Info message",
"id1",
null,
PriorityLevels.PRIORITY_INFO_LOW,
buttons
);
let buttonNodes = notificationNode.querySelectorAll(
".notification-button");
is(buttonNodes.length, 2, "There must be two buttons");
// Click the first button
TestUtils.Simulate.click(buttonNodes[0]);
ok(buttonCallbackExecuted, "Button callback must be executed.");
is(TestUtils.scryRenderedDOMComponentsWithClass(
notificationBox, "notification").length, 1,
"There must be one notification");
// Click the second button (closing the notification)
TestUtils.Simulate.click(buttonNodes[1]);
is(TestUtils.scryRenderedDOMComponentsWithClass(
notificationBox, "notification").length, 0,
"The notification box must be empty now");
} catch(e) {
ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
} finally {
SimpleTest.finish();
}
});
</script>
</pre>
</body>
</html>

View file

@ -0,0 +1,259 @@
<!-- 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/. -->
<!DOCTYPE HTML>
<html>
<!--
Test ArrayRep rep
-->
<head>
<meta charset="utf-8">
<title>Rep test - ArrayRep</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
</head>
<body>
<pre id="test">
<script src="head.js" type="application/javascript;version=1.8"></script>
<script type="application/javascript;version=1.8">
"use strict";
/* import-globals-from head.js */
window.onload = Task.async(function* () {
let { Rep } = browserRequire("devtools/client/shared/components/reps/rep");
let { ArrayRep } = browserRequire("devtools/client/shared/components/reps/array");
let componentUnderTest = ArrayRep;
const maxLength = {
short: 3,
long: 300
};
try {
yield testBasic();
// Test property iterator
yield testMaxProps();
yield testMoreThanShortMaxProps();
yield testMoreThanLongMaxProps();
yield testRecursiveArray();
// Test that properties are rendered as expected by ItemRep
yield testNested();
yield testArray();
} catch (e) {
ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
} finally {
SimpleTest.finish();
}
function testBasic() {
// Test that correct rep is chosen
const stub = [];
const renderedRep = shallowRenderComponent(Rep, { object: stub });
is(renderedRep.type, ArrayRep.rep,
`Rep correctly selects ${ArrayRep.rep.displayName}`);
// Test rendering
const defaultOutput = `[]`;
const modeTests = [
{
mode: undefined,
expectedOutput: defaultOutput,
},
{
mode: "tiny",
expectedOutput: `[]`,
},
{
mode: "short",
expectedOutput: defaultOutput,
},
{
mode: "long",
expectedOutput: defaultOutput,
}
];
testRepRenderModes(modeTests, "testBasic", componentUnderTest, stub);
}
function testMaxProps() {
const stub = [1, "foo", {}];
const defaultOutput = `[ 1, "foo", Object ]`;
const modeTests = [
{
mode: undefined,
expectedOutput: defaultOutput,
},
{
mode: "tiny",
expectedOutput: `[3]`,
},
{
mode: "short",
expectedOutput: defaultOutput,
},
{
mode: "long",
expectedOutput: defaultOutput,
}
];
testRepRenderModes(modeTests, "testMaxProps", componentUnderTest, stub);
}
function testMoreThanShortMaxProps() {
const stub = Array(maxLength.short + 1).fill("foo");
const defaultShortOutput = `[ ${Array(maxLength.short).fill("\"foo\"").join(", ")}, 1 more… ]`;
const modeTests = [
{
mode: undefined,
expectedOutput: defaultShortOutput,
},
{
mode: "tiny",
expectedOutput: `[${maxLength.short + 1}]`,
},
{
mode: "short",
expectedOutput: defaultShortOutput,
},
{
mode: "long",
expectedOutput: `[ ${Array(maxLength.short + 1).fill("\"foo\"").join(", ")} ]`,
}
];
testRepRenderModes(modeTests, "testMoreThanMaxProps", componentUnderTest, stub);
}
function testMoreThanLongMaxProps() {
const stub = Array(maxLength.long + 1).fill("foo");
const defaultShortOutput = `[ ${Array(maxLength.short).fill("\"foo\"").join(", ")}, ${maxLength.long + 1 - maxLength.short} more… ]`;
const defaultLongOutput = `[ ${Array(maxLength.long).fill("\"foo\"").join(", ")}, 1 more… ]`;
const modeTests = [
{
mode: undefined,
expectedOutput: defaultShortOutput,
},
{
mode: "tiny",
expectedOutput: `[${maxLength.long + 1}]`,
},
{
mode: "short",
expectedOutput: defaultShortOutput,
},
{
mode: "long",
expectedOutput: defaultLongOutput,
}
];
testRepRenderModes(modeTests, "testMoreThanMaxProps", componentUnderTest, stub);
}
function testRecursiveArray() {
let stub = [1];
stub.push(stub);
const defaultOutput = `[ 1, [2] ]`;
const modeTests = [
{
mode: undefined,
expectedOutput: defaultOutput,
},
{
mode: "tiny",
expectedOutput: `[2]`,
},
{
mode: "short",
expectedOutput: defaultOutput,
},
{
mode: "long",
expectedOutput: defaultOutput,
}
];
testRepRenderModes(modeTests, "testRecursiveArray", componentUnderTest, stub);
}
function testNested() {
let stub = [
{
p1: "s1",
p2: ["a1", "a2", "a3"],
p3: "s3",
p4: "s4"
}
];
const defaultOutput = `[ Object ]`;
const modeTests = [
{
mode: undefined,
expectedOutput: defaultOutput,
},
{
mode: "tiny",
expectedOutput: `[1]`,
},
{
mode: "short",
expectedOutput: defaultOutput,
},
{
mode: "long",
expectedOutput: defaultOutput,
}
];
testRepRenderModes(modeTests, "testNested", componentUnderTest, stub);
}
function testArray() {
let stub = [
"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m",
"n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"
];
const defaultOutput = `[ "a", "b", "c", "d", "e", "f", "g", "h", "i", "j",` +
` "k", "l", "m", "n", "o", "p", "q", "r", "s", "t",` +
` "u", "v", "w", "x", "y", "z" ]`;
const shortOutput = `[ "a", "b", "c", 23 more… ]`;
const modeTests = [
{
mode: undefined,
expectedOutput: shortOutput,
},
{
mode: "tiny",
expectedOutput: `[26]`,
},
{
mode: "short",
expectedOutput: shortOutput,
},
{
mode: "long",
expectedOutput: defaultOutput,
}
];
testRepRenderModes(modeTests, "testNested", componentUnderTest, stub);
}
});
</script>
</pre>
</body>
</html>

View file

@ -0,0 +1,56 @@
<!-- 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/. -->
<!DOCTYPE HTML>
<html>
<!--
Test Attribute rep
-->
<head>
<meta charset="utf-8">
<title>Rep test - Attribute</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
</head>
<body>
<pre id="test">
<script src="head.js" type="application/javascript;version=1.8"></script>
<script type="application/javascript;version=1.8">
window.onload = Task.async(function* () {
try {
let { Rep } = browserRequire("devtools/client/shared/components/reps/rep");
let { Attribute } = browserRequire("devtools/client/shared/components/reps/attribute");
let gripStub = {
"type": "object",
"class": "Attr",
"actor": "server1.conn19.obj65",
"extensible": true,
"frozen": false,
"sealed": false,
"ownPropertyLength": 0,
"preview": {
"kind": "DOMNode",
"nodeType": 2,
"nodeName": "class",
"value": "autocomplete-suggestions"
}
};
// Test that correct rep is chosen
const renderedRep = shallowRenderComponent(Rep, { object: gripStub });
is(renderedRep.type, Attribute.rep, `Rep correctly selects ${Attribute.rep.displayName}`);
// Test rendering
const renderedComponent = renderComponent(Attribute.rep, { object: gripStub });
is(renderedComponent.textContent, "class=\"autocomplete-suggestions\"", "Attribute rep has expected text content");
} catch(e) {
ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
} finally {
SimpleTest.finish();
}
});
</script>
</pre>
</body>
</html>

View file

@ -0,0 +1,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/. -->
<!DOCTYPE HTML>
<html>
<!--
Test comment-node rep
-->
<head>
<meta charset="utf-8">
<title>Rep test - comment-node</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
</head>
<body>
<pre id="test">
<script src="head.js" type="application/javascript;version=1.8"></script>
<script type="application/javascript;version=1.8">
"use strict";
window.onload = Task.async(function* () {
try {
let { Rep } = browserRequire("devtools/client/shared/components/reps/rep");
let { CommentNode } = browserRequire("devtools/client/shared/components/reps/comment-node");
let gripStub = {
"type": "object",
"actor": "server1.conn1.child1/obj47",
"class": "Comment",
"extensible": true,
"frozen": false,
"sealed": false,
"ownPropertyLength": 0,
"preview": {
"kind": "DOMNode",
"nodeType": 8,
"nodeName": "#comment",
"textContent": "test\nand test\nand test\nand test\nand test\nand test\nand test"
}
};
// Test that correct rep is chosen.
const renderedRep = shallowRenderComponent(Rep, { object: gripStub });
is(renderedRep.type, CommentNode.rep,
`Rep correctly selects ${CommentNode.rep.displayName}`);
// Test rendering.
const renderedComponent = renderComponent(CommentNode.rep, { object: gripStub });
is(renderedComponent.className, "objectBox theme-comment",
"CommentNode rep has expected class names");
is(renderedComponent.textContent,
`<!-- test\nand test\nand test\nan…d test\nand test\nand test -->`,
"CommentNode rep has expected text content");
// Test tiny rendering.
const tinyRenderedComponent = renderComponent(CommentNode.rep, {
object: gripStub,
mode: "tiny"
});
is(tinyRenderedComponent.textContent,
`<!-- test\\nand test\\na… test\\nand test -->`,
"CommentNode rep has expected text content in tiny mode");
// Test long rendering.
const longRenderedComponent = renderComponent(CommentNode.rep, {
object: gripStub,
mode: "long"
});
is(longRenderedComponent.textContent, `<!-- ${gripStub.preview.textContent} -->`,
"CommentNode rep has expected text content in long mode");
} catch (e) {
ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
} finally {
SimpleTest.finish();
}
});
</script>
</pre>
</body>
</html>

View file

@ -0,0 +1,79 @@
<!-- 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/. -->
<!DOCTYPE HTML>
<html>
<!--
Test DateTime rep
-->
<head>
<meta charset="utf-8">
<title>Rep test - DateTime</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
</head>
<body>
<pre id="test">
<script src="head.js" type="application/javascript;version=1.8"></script>
<script type="application/javascript;version=1.8">
window.onload = Task.async(function* () {
let { Rep } = browserRequire("devtools/client/shared/components/reps/rep");
let { DateTime } = browserRequire("devtools/client/shared/components/reps/date-time");
try {
testValid();
testInvalid();
} catch(e) {
ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
} finally {
SimpleTest.finish();
}
function testValid() {
let gripStub = {
"type": "object",
"class": "Date",
"actor": "server1.conn0.child1/obj32",
"extensible": true,
"frozen": false,
"sealed": false,
"ownPropertyLength": 0,
"preview": {
"timestamp": 1459372644859
}
};
// Test that correct rep is chosen
const renderedRep = shallowRenderComponent(Rep, { object: gripStub });
is(renderedRep.type, DateTime.rep, `Rep correctly selects ${DateTime.rep.displayName}`);
// Test rendering
const renderedComponent = renderComponent(DateTime.rep, { object: gripStub });
is(renderedComponent.textContent, "2016-03-30T21:17:24.859Z", "DateTime rep has expected text content for valid date");
}
function testInvalid() {
let gripStub = {
"type": "object",
"actor": "server1.conn0.child1/obj32",
"class": "Date",
"extensible": true,
"frozen": false,
"sealed": false,
"ownPropertyLength": 0,
"preview": {
"timestamp": {
"type": "NaN"
}
}
};
// Test rendering
const renderedComponent = renderComponent(DateTime.rep, { object: gripStub });
is(renderedComponent.textContent, "Invalid Date", "DateTime rep has expected text content for invalid date");
}
});
</script>
</pre>
</body>
</html>

View file

@ -0,0 +1,56 @@
<!-- 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/. -->
<!DOCTYPE HTML>
<html>
<!--
Test Document rep
-->
<head>
<meta charset="utf-8">
<title>Rep test - Document</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
</head>
<body>
<pre id="test">
<script src="head.js" type="application/javascript;version=1.8"></script>
<script type="application/javascript;version=1.8">
window.onload = Task.async(function* () {
let { Rep } = browserRequire("devtools/client/shared/components/reps/rep");
let { Document } = browserRequire("devtools/client/shared/components/reps/document");
try {
let gripStub = {
"type": "object",
"class": "HTMLDocument",
"actor": "server1.conn17.obj115",
"extensible": true,
"frozen": false,
"sealed": false,
"ownPropertyLength": 1,
"preview": {
"kind": "DOMNode",
"nodeType": 9,
"nodeName": "#document",
"location": "https://www.mozilla.org/en-US/firefox/new/"
}
};
// Test that correct rep is chosen
const renderedRep = shallowRenderComponent(Rep, { object: gripStub });
is(renderedRep.type, Document.rep, `Rep correctly selects ${Document.rep.displayName}`);
// Test rendering
const renderedComponent = renderComponent(Document.rep, { object: gripStub });
is(renderedComponent.textContent, "https://www.mozilla.org/en-US/firefox/new/", "Document rep has expected text content");
} catch(e) {
ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
} finally {
SimpleTest.finish();
}
});
</script>
</pre>
</body>
</html>

View file

@ -0,0 +1,341 @@
<!-- 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/. -->
<!DOCTYPE HTML>
<html>
<!--
Test Element node rep
-->
<head>
<meta charset="utf-8">
<title>Rep test - Element node</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
</head>
<body>
<pre id="test">
<script src="head.js" type="application/javascript;version=1.8"></script>
<script type="application/javascript;version=1.8">
"use strict";
window.onload = Task.async(function* () {
let { Rep } = browserRequire("devtools/client/shared/components/reps/rep");
let { ElementNode } = browserRequire("devtools/client/shared/components/reps/element-node");
try {
yield testBodyNode();
yield testDocumentElement();
yield testNode();
yield testNodeWithLeadingAndTrailingSpacesClassName();
yield testNodeWithoutAttributes();
yield testLotsOfAttributes();
yield testSvgNode();
yield testSvgNodeInXHTML();
} catch (e) {
ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
} finally {
SimpleTest.finish();
}
function testBodyNode() {
const stub = getGripStub("testBodyNode");
const renderedRep = shallowRenderComponent(Rep, { object: stub });
is(renderedRep.type, ElementNode.rep,
`Rep correctly selects ${ElementNode.rep.displayName} for body node`);
const renderedComponent = renderComponent(ElementNode.rep, { object: stub });
is(renderedComponent.textContent, `<body id="body-id" class="body-class">`,
"Element node rep has expected text content for body node");
const tinyRenderedComponent = renderComponent(
ElementNode.rep, { object: stub, mode: "tiny" });
is(tinyRenderedComponent.textContent, `body#body-id.body-class`,
"Element node rep has expected text content for body node in tiny mode");
}
function testDocumentElement() {
const stub = getGripStub("testDocumentElement");
const renderedRep = shallowRenderComponent(Rep, { object: stub });
is(renderedRep.type, ElementNode.rep,
`Rep correctly selects ${ElementNode.rep.displayName} for document element node`);
const renderedComponent = renderComponent(ElementNode.rep, { object: stub });
is(renderedComponent.textContent, `<html dir="ltr" lang="en-US">`,
"Element node rep has expected text content for document element node");
const tinyRenderedComponent = renderComponent(
ElementNode.rep, { object: stub, mode: "tiny" });
is(tinyRenderedComponent.textContent, `html`,
"Element node rep has expected text content for document element in tiny mode");
}
function testNode() {
const stub = getGripStub("testNode");
const renderedRep = shallowRenderComponent(Rep, { object: stub });
is(renderedRep.type, ElementNode.rep,
`Rep correctly selects ${ElementNode.rep.displayName} for element node`);
const renderedComponent = renderComponent(ElementNode.rep, { object: stub });
is(renderedComponent.textContent,
`<input id="newtab-customize-button" class="bar baz" dir="ltr" ` +
`title="Customize your New Tab page" value="foo" type="button">`,
"Element node rep has expected text content for element node");
const tinyRenderedComponent = renderComponent(
ElementNode.rep, { object: stub, mode: "tiny" });
is(tinyRenderedComponent.textContent,
`input#newtab-customize-button.bar.baz`,
"Element node rep has expected text content for element node in tiny mode");
}
function testNodeWithLeadingAndTrailingSpacesClassName() {
const stub = getGripStub("testNodeWithLeadingAndTrailingSpacesClassName");
const renderedRep = shallowRenderComponent(Rep, { object: stub });
is(renderedRep.type, ElementNode.rep,
`Rep correctly selects ${ElementNode.rep.displayName} for element node`);
const renderedComponent = renderComponent(ElementNode.rep, { object: stub });
is(renderedComponent.textContent,
`<body id="nightly-whatsnew" class=" html-ltr ">`,
"Element node rep output element node with the class trailing spaces");
const tinyRenderedComponent = renderComponent(
ElementNode.rep, { object: stub, mode: "tiny" });
is(tinyRenderedComponent.textContent,
`body#nightly-whatsnew.html-ltr`,
"Element node rep does not show leading nor trailing spaces " +
"on class attribute in tiny mode");
}
function testNodeWithoutAttributes() {
const stub = getGripStub("testNodeWithoutAttributes");
const renderedComponent = renderComponent(ElementNode.rep, { object: stub });
is(renderedComponent.textContent, "<p>",
"Element node rep has expected text content for element node without attributes");
const tinyRenderedComponent = renderComponent(
ElementNode.rep, { object: stub, mode: "tiny" });
is(tinyRenderedComponent.textContent, `p`,
"Element node rep has expected text content for element node without attributes");
}
function testLotsOfAttributes() {
const stub = getGripStub("testLotsOfAttributes");
const renderedComponent = renderComponent(ElementNode.rep, { object: stub });
is(renderedComponent.textContent,
'<p id="lots-of-attributes" a="" b="" c="" d="" e="" f="" g="" ' +
'h="" i="" j="" k="" l="" m="" n="">',
"Element node rep has expected text content for node with lots of attributes");
const tinyRenderedComponent = renderComponent(
ElementNode.rep, { object: stub, mode: "tiny" });
is(tinyRenderedComponent.textContent, `p#lots-of-attributes`,
"Element node rep has expected text content for node in tiny mode");
}
function testSvgNode() {
const stub = getGripStub("testSvgNode");
const renderedRep = shallowRenderComponent(Rep, { object: stub });
is(renderedRep.type, ElementNode.rep,
`Rep correctly selects ${ElementNode.rep.displayName} for SVG element node`);
const renderedComponent = renderComponent(ElementNode.rep, { object: stub });
is(renderedComponent.textContent,
'<clipPath id="clip" class="svg-element">',
"Element node rep has expected text content for SVG element node");
const tinyRenderedComponent = renderComponent(
ElementNode.rep, { object: stub, mode: "tiny" });
is(tinyRenderedComponent.textContent, `clipPath#clip.svg-element`,
"Element node rep has expected text content for SVG element node in tiny mode");
}
function testSvgNodeInXHTML() {
const stub = getGripStub("testSvgNodeInXHTML");
const renderedRep = shallowRenderComponent(Rep, { object: stub });
is(renderedRep.type, ElementNode.rep,
`Rep correctly selects ${ElementNode.rep.displayName} for XHTML SVG element node`);
const renderedComponent = renderComponent(ElementNode.rep, { object: stub });
is(renderedComponent.textContent,
'<svg:circle class="svg-element" cx="0" cy="0" r="5">',
"Element node rep has expected text content for XHTML SVG element node");
const tinyRenderedComponent = renderComponent(
ElementNode.rep, { object: stub, mode: "tiny" });
is(tinyRenderedComponent.textContent, `svg:circle.svg-element`,
"Element node rep has expected text content for XHTML SVG element in tiny mode");
}
function getGripStub(name) {
switch (name) {
case "testBodyNode":
return {
"type": "object",
"actor": "server1.conn1.child1/obj30",
"class": "HTMLBodyElement",
"ownPropertyLength": 0,
"preview": {
"kind": "DOMNode",
"nodeType": 1,
"nodeName": "body",
"attributes": {
"class": "body-class",
"id": "body-id"
},
"attributesLength": 2
}
};
case "testDocumentElement":
return {
"type": "object",
"actor": "server1.conn1.child1/obj40",
"class": "HTMLHtmlElement",
"ownPropertyLength": 0,
"preview": {
"kind": "DOMNode",
"nodeType": 1,
"nodeName": "html",
"attributes": {
"dir": "ltr",
"lang": "en-US"
},
"attributesLength": 2
}
};
case "testNode":
return {
"type": "object",
"actor": "server1.conn2.child1/obj116",
"class": "HTMLInputElement",
"extensible": true,
"frozen": false,
"sealed": false,
"ownPropertyLength": 0,
"preview": {
"kind": "DOMNode",
"nodeType": 1,
"nodeName": "input",
"attributes": {
"id": "newtab-customize-button",
"dir": "ltr",
"title": "Customize your New Tab page",
"class": "bar baz",
"value": "foo",
"type": "button"
},
"attributesLength": 6
}
};
case "testNodeWithLeadingAndTrailingSpacesClassName":
return {
"type": "object",
"actor": "server1.conn3.child1/obj59",
"class": "HTMLBodyElement",
"extensible": true,
"frozen": false,
"sealed": false,
"ownPropertyLength": 0,
"preview": {
"kind": "DOMNode",
"nodeType": 1,
"nodeName": "body",
"attributes": {
"id": "nightly-whatsnew",
"class": " html-ltr "
},
"attributesLength": 2
}
};
case "testNodeWithoutAttributes":
return {
"type": "object",
"actor": "server1.conn1.child1/obj32",
"class": "HTMLParagraphElement",
"ownPropertyLength": 0,
"preview": {
"kind": "DOMNode",
"nodeType": 1,
"nodeName": "p",
"attributes": {},
"attributesLength": 1
}
};
case "testLotsOfAttributes":
return {
"type": "object",
"actor": "server1.conn2.child1/obj30",
"class": "HTMLParagraphElement",
"ownPropertyLength": 0,
"preview": {
"kind": "DOMNode",
"nodeType": 1,
"nodeName": "p",
"attributes": {
"id": "lots-of-attributes",
"a": "",
"b": "",
"c": "",
"d": "",
"e": "",
"f": "",
"g": "",
"h": "",
"i": "",
"j": "",
"k": "",
"l": "",
"m": "",
"n": ""
},
"attributesLength": 15
}
};
case "testSvgNode":
return {
"type": "object",
"actor": "server1.conn1.child1/obj42",
"class": "SVGClipPathElement",
"ownPropertyLength": 0,
"preview": {
"kind": "DOMNode",
"nodeType": 1,
"nodeName": "clipPath",
"attributes": {
"id": "clip",
"class": "svg-element"
},
"attributesLength": 0
}
};
case "testSvgNodeInXHTML":
return {
"type": "object",
"actor": "server1.conn3.child1/obj34",
"class": "SVGCircleElement",
"ownPropertyLength": 0,
"preview": {
"kind": "DOMNode",
"nodeType": 1,
"nodeName": "svg:circle",
"attributes": {
"class": "svg-element",
"cx": "0",
"cy": "0",
"r": "5"
},
"attributesLength": 3
}
};
}
return null;
}
});
</script>
</pre>
</body>
</html>

View file

@ -0,0 +1,300 @@
<!-- 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/. -->
<!DOCTYPE HTML>
<html>
<!--
Test Event rep
-->
<head>
<meta charset="utf-8">
<title>Rep test - Event</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
</head>
<body>
<pre id="test">
<script src="head.js" type="application/javascript;version=1.8"></script>
<script type="application/javascript;version=1.8">
window.onload = Task.async(function* () {
let { Rep } = browserRequire("devtools/client/shared/components/reps/rep");
let { Event } = browserRequire("devtools/client/shared/components/reps/event");
try {
// Test that correct rep is chosen
const renderedRep = shallowRenderComponent(Rep, { object: getGripStub("testEvent") });
is(renderedRep.type, Event.rep, `Rep correctly selects ${Event.rep.displayName}`);
yield testEvent();
yield testMouseEvent();
yield testKeyboardEvent();
yield testMessageEvent();
} catch(e) {
ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
} finally {
SimpleTest.finish();
}
function testEvent() {
const renderedComponent = renderComponent(Event.rep, { object: getGripStub("testEvent") });
is(renderedComponent.textContent,
"Event { isTrusted: true, eventPhase: 2, bubbles: false, 7 more… }",
"Event rep has expected text content for an event");
}
function testMouseEvent() {
const renderedComponent = renderComponent(Event.rep, { object: getGripStub("testMouseEvent") });
is(renderedComponent.textContent,
"MouseEvent { clientX: 62, clientY: 18, layerX: 0, 2 more… }",
"Event rep has expected text content for a mouse event");
}
function testKeyboardEvent() {
const renderedComponent = renderComponent(Event.rep, { object: getGripStub("testKeyboardEvent") });
is(renderedComponent.textContent,
"KeyboardEvent { key: \"Control\", charCode: 0, keyCode: 17 }",
"Event rep has expected text content for a keyboard event");
}
function testMessageEvent() {
const renderedComponent = renderComponent(Event.rep, { object: getGripStub("testMessageEvent") });
is(renderedComponent.textContent,
"MessageEvent { isTrusted: false, data: \"test data\", origin: \"null\", 7 more… }",
"Event rep has expected text content for a message event");
}
function getGripStub(name) {
switch (name) {
case "testEvent":
return {
"type": "object",
"class": "Event",
"actor": "server1.conn23.obj35",
"extensible": true,
"frozen": false,
"sealed": false,
"ownPropertyLength": 1,
"preview": {
"kind": "DOMEvent",
"type": "beforeprint",
"properties": {
"isTrusted": true,
"currentTarget": {
"type": "object",
"class": "Window",
"actor": "server1.conn23.obj37",
"extensible": true,
"frozen": false,
"sealed": false,
"ownPropertyLength": 760,
"preview": {
"kind": "ObjectWithURL",
"url": "http://example.com"
}
},
"eventPhase": 2,
"bubbles": false,
"cancelable": false,
"defaultPrevented": false,
"timeStamp": 1466780008434005,
"originalTarget": {
"type": "object",
"class": "Window",
"actor": "server1.conn23.obj38",
"extensible": true,
"frozen": false,
"sealed": false,
"ownPropertyLength": 760,
"preview": {
"kind": "ObjectWithURL",
"url": "http://example.com"
}
},
"explicitOriginalTarget": {
"type": "object",
"class": "Window",
"actor": "server1.conn23.obj39",
"extensible": true,
"frozen": false,
"sealed": false,
"ownPropertyLength": 760,
"preview": {
"kind": "ObjectWithURL",
"url": "http://example.com"
}
},
"NONE": 0
},
"target": {
"type": "object",
"class": "Window",
"actor": "server1.conn23.obj36",
"extensible": true,
"frozen": false,
"sealed": false,
"ownPropertyLength": 760,
"preview": {
"kind": "ObjectWithURL",
"url": "http://example.com"
}
}
}
};
case "testMouseEvent":
return {
"type": "object",
"class": "MouseEvent",
"actor": "server1.conn20.obj39",
"extensible": true,
"frozen": false,
"sealed": false,
"ownPropertyLength": 1,
"preview": {
"kind": "DOMEvent",
"type": "click",
"properties": {
"buttons": 0,
"clientX": 62,
"clientY": 18,
"layerX": 0,
"layerY": 0
},
"target": {
"type": "object",
"class": "HTMLDivElement",
"actor": "server1.conn20.obj40",
"extensible": true,
"frozen": false,
"sealed": false,
"ownPropertyLength": 0,
"preview": {
"kind": "DOMNode",
"nodeType": 1,
"nodeName": "div",
"attributes": {
"id": "test"
},
"attributesLength": 1
}
}
}
};
case "testKeyboardEvent":
return {
"type": "object",
"class": "KeyboardEvent",
"actor": "server1.conn21.obj49",
"extensible": true,
"frozen": false,
"sealed": false,
"ownPropertyLength": 1,
"preview": {
"kind": "DOMEvent",
"type": "keyup",
"properties": {
"key": "Control",
"charCode": 0,
"keyCode": 17
},
"target": {
"type": "object",
"class": "HTMLBodyElement",
"actor": "server1.conn21.obj50",
"extensible": true,
"frozen": false,
"sealed": false,
"ownPropertyLength": 0,
"preview": {
"kind": "DOMNode",
"nodeType": 1,
"nodeName": "body",
"attributes": {},
"attributesLength": 0
}
},
"eventKind": "key",
"modifiers": []
}
};
case "testMessageEvent":
return {
"type": "object",
"class": "MessageEvent",
"actor": "server1.conn3.obj34",
"extensible": true,
"frozen": false,
"sealed": false,
"ownPropertyLength": 1,
"preview": {
"kind": "DOMEvent",
"type": "message",
"properties": {
"isTrusted": false,
"data": "test data",
"origin": "null",
"lastEventId": "",
"source": {
"type": "object",
"class": "Window",
"actor": "server1.conn3.obj36",
"extensible": true,
"frozen": false,
"sealed": false,
"ownPropertyLength": 760,
"preview": {
"kind": "ObjectWithURL",
"url": ""
}
},
"ports": {
"type": "object",
"class": "Array",
"actor": "server1.conn3.obj37",
"extensible": true,
"frozen": false,
"sealed": false,
"ownPropertyLength": 0
},
"currentTarget": {
"type": "object",
"class": "Window",
"actor": "server1.conn3.obj38",
"extensible": true,
"frozen": false,
"sealed": false,
"ownPropertyLength": 760,
"preview": {
"kind": "ObjectWithURL",
"url": ""
}
},
"eventPhase": 2,
"bubbles": false,
"cancelable": false
},
"target": {
"type": "object",
"class": "Window",
"actor": "server1.conn3.obj35",
"extensible": true,
"frozen": false,
"sealed": false,
"ownPropertyLength": 760,
"preview": {
"kind": "ObjectWithURL",
"url": ""
}
}
}
};
}
}
});
</script>
</pre>
</body>
</html>

View file

@ -0,0 +1,206 @@
<!-- 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/. -->
<!DOCTYPE HTML>
<html>
<!--
Test Func rep
-->
<head>
<meta charset="utf-8">
<title>Rep test - Func</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
</head>
<body>
<pre id="test">
<script src="head.js" type="application/javascript;version=1.8"></script>
<script type="application/javascript;version=1.8">
window.onload = Task.async(function* () {
let { Rep } = browserRequire("devtools/client/shared/components/reps/rep");
let { Func } = browserRequire("devtools/client/shared/components/reps/function");
const componentUnderTest = Func;
try {
// Test that correct rep is chosen
const gripStub = getGripStub("testNamed");
const renderedRep = shallowRenderComponent(Rep, { object: gripStub });
is(renderedRep.type, Func.rep, `Rep correctly selects ${Func.rep.displayName}`);
yield testNamed();
yield testVarNamed();
yield testAnon();
yield testLongName();
} catch(e) {
ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
} finally {
SimpleTest.finish();
}
function testNamed() {
// Test declaration: `function testName{ let innerVar = "foo" }`
const testName = "testNamed";
const defaultOutput = `testName()`;
const modeTests = [
{
mode: undefined,
expectedOutput: defaultOutput,
}
];
testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
}
function testUserNamed() {
// Test declaration: `function testName{ let innerVar = "foo" }`
const testName = "testUserNamed";
const defaultOutput = `testUserName()`;
const modeTests = [
{
mode: undefined,
expectedOutput: defaultOutput,
}
];
testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
}
function testVarNamed() {
// Test declaration: `let testVarName = function() { }`
const testName = "testVarNamed";
const defaultOutput = `testVarName()`;
const modeTests = [
{
mode: undefined,
expectedOutput: defaultOutput,
}
];
testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
}
function testAnon() {
// Test declaration: `() => {}`
const testName = "testAnon";
const defaultOutput = `function()`;
const modeTests = [
{
mode: undefined,
expectedOutput: defaultOutput,
}
];
testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
}
function testLongName() {
// Test declaration: `let f = function loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong() { }`
const testName = "testLongName";
const defaultOutput = `looooooooooooooooooooooooooooooooooooooooooooooooo\u2026ooooooooooooooooooooooooooooooooooooooooooooong()`;
const modeTests = [
{
mode: undefined,
expectedOutput: defaultOutput,
}
];
testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
}
function getGripStub(functionName) {
switch (functionName) {
case "testNamed":
return {
"type": "object",
"class": "Function",
"actor": "server1.conn6.obj35",
"extensible": true,
"frozen": false,
"sealed": false,
"name": "testName",
"displayName": "testName",
"location": {
"url": "debugger eval code",
"line": 1
}
};
case "testUserNamed":
return {
"type": "object",
"class": "Function",
"actor": "server1.conn6.obj35",
"extensible": true,
"frozen": false,
"sealed": false,
"name": "testName",
"userDisplayName": "testUserName",
"displayName": "testName",
"location": {
"url": "debugger eval code",
"line": 1
}
};
case "testVarNamed":
return {
"type": "object",
"class": "Function",
"actor": "server1.conn7.obj41",
"extensible": true,
"frozen": false,
"sealed": false,
"displayName": "testVarName",
"location": {
"url": "debugger eval code",
"line": 1
}
};
case "testAnon":
return {
"type": "object",
"class": "Function",
"actor": "server1.conn7.obj45",
"extensible": true,
"frozen": false,
"sealed": false,
"location": {
"url": "debugger eval code",
"line": 1
}
};
case "testLongName":
return {
"type": "object",
"class": "Function",
"actor": "server1.conn7.obj67",
"extensible": true,
"frozen": false,
"sealed": false,
"name": "loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong",
"displayName": "loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong",
"location": {
"url": "debugger eval code",
"line": 1
}
};
}
}
});
</script>
</pre>
</body>
</html>

View file

@ -0,0 +1,707 @@
<!-- 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/. -->
<!DOCTYPE HTML>
<html>
<!--
Test GripArray rep
-->
<head>
<meta charset="utf-8">
<title>Rep test - GripArray</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
</head>
<body>
<pre id="test">
<script src="head.js" type="application/javascript;version=1.8"></script>
<script type="application/javascript;version=1.8">
window.onload = Task.async(function* () {
let { Rep } = browserRequire("devtools/client/shared/components/reps/rep");
let { GripArray } = browserRequire("devtools/client/shared/components/reps/grip-array");
let componentUnderTest = GripArray;
const maxLength = {
short: 3,
long: 300
};
try {
yield testBasic();
// Test property iterator
yield testMaxProps();
yield testMoreThanShortMaxProps();
yield testMoreThanLongMaxProps();
yield testRecursiveArray();
yield testPreviewLimit();
yield testNamedNodeMap();
yield testNodeList();
yield testDocumentFragment();
} catch(e) {
ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
} finally {
SimpleTest.finish();
}
function testBasic() {
// Test array: `[]`
const testName = "testBasic";
// Test that correct rep is chosen
const gripStub = getGripStub("testBasic");
const renderedRep = shallowRenderComponent(Rep, { object: gripStub });
is(renderedRep.type, GripArray.rep, `Rep correctly selects ${GripArray.rep.displayName}`);
// Test rendering
const defaultOutput = `Array []`;
const modeTests = [
{
mode: undefined,
expectedOutput: defaultOutput,
},
{
mode: "tiny",
expectedOutput: `[]`,
},
{
mode: "short",
expectedOutput: defaultOutput,
},
{
mode: "long",
expectedOutput: defaultOutput,
}
];
testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
}
function testMaxProps() {
// Test array: `[1, "foo", {}]`;
const testName = "testMaxProps";
const defaultOutput = `Array [ 1, "foo", Object ]`;
const modeTests = [
{
mode: undefined,
expectedOutput: defaultOutput,
},
{
mode: "tiny",
expectedOutput: `[3]`,
},
{
mode: "short",
expectedOutput: defaultOutput,
},
{
mode: "long",
expectedOutput: defaultOutput,
}
];
testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
}
function testMoreThanShortMaxProps() {
// Test array = `["test string"…] //4 items`
const testName = "testMoreThanShortMaxProps";
const defaultOutput = `Array [ ${Array(maxLength.short).fill("\"test string\"").join(", ")}, 1 more… ]`;
const modeTests = [
{
mode: undefined,
expectedOutput: defaultOutput,
},
{
mode: "tiny",
expectedOutput: `[${maxLength.short + 1}]`,
},
{
mode: "short",
expectedOutput: defaultOutput,
},
{
mode: "long",
expectedOutput: `Array [ ${Array(maxLength.short + 1).fill("\"test string\"").join(", ")} ]`,
}
];
testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
}
function testMoreThanLongMaxProps() {
// Test array = `["test string"…] //301 items`
const testName = "testMoreThanLongMaxProps";
const defaultShortOutput = `Array [ ${Array(maxLength.short).fill("\"test string\"").join(", ")}, ${maxLength.long + 1 - maxLength.short} more… ]`;
const defaultLongOutput = `Array [ ${Array(maxLength.long).fill("\"test string\"").join(", ")}, 1 more… ]`;
const modeTests = [
{
mode: undefined,
expectedOutput: defaultShortOutput,
},
{
mode: "tiny",
expectedOutput: `[${maxLength.long + 1}]`,
},
{
mode: "short",
expectedOutput: defaultShortOutput,
},
{
mode: "long",
expectedOutput: defaultLongOutput
}
];
testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
}
function testRecursiveArray() {
// Test array = `let a = []; a = [a]`
const testName = "testRecursiveArray";
const defaultOutput = `Array [ [1] ]`;
const modeTests = [
{
mode: undefined,
expectedOutput: defaultOutput,
},
{
mode: "tiny",
expectedOutput: `[1]`,
},
{
mode: "short",
expectedOutput: defaultOutput,
},
{
mode: "long",
expectedOutput: defaultOutput,
}
];
testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
}
function testPreviewLimit() {
const testName = "testPreviewLimit";
const shortOutput = `Array [ 0, 1, 2, 8 more… ]`;
const defaultOutput = `Array [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 1 more… ]`;
const modeTests = [
{
mode: undefined,
expectedOutput: shortOutput,
},
{
mode: "tiny",
expectedOutput: `[11]`,
},
{
mode: "short",
expectedOutput: shortOutput,
},
{
mode: "long",
expectedOutput: defaultOutput,
}
];
testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
}
function testNamedNodeMap() {
const testName = "testNamedNodeMap";
const defaultOutput = `NamedNodeMap [ class="myclass", cellpadding="7", border="3" ]`;
const modeTests = [
{
mode: undefined,
expectedOutput: defaultOutput,
},
{
mode: "tiny",
expectedOutput: `[3]`,
},
{
mode: "short",
expectedOutput: defaultOutput,
},
{
mode: "long",
expectedOutput: defaultOutput,
}
];
testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
}
function testNodeList() {
const testName = "testNodeList";
const defaultOutput = "NodeList [ button#btn-1.btn.btn-log, " +
"button#btn-2.btn.btn-err, button#btn-3.btn.btn-count ]";
const modeTests = [
{
mode: undefined,
expectedOutput: defaultOutput,
},
{
mode: "tiny",
expectedOutput: `[3]`,
},
{
mode: "short",
expectedOutput: defaultOutput,
},
{
mode: "long",
expectedOutput: defaultOutput,
}
];
testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
}
function testDocumentFragment() {
const testName = "testDocumentFragment";
const defaultOutput = "DocumentFragment [ li#li-0.list-element, " +
"li#li-1.list-element, li#li-2.list-element, 2 more… ]";
const longOutput = "DocumentFragment [ " +
"li#li-0.list-element, li#li-1.list-element, li#li-2.list-element, " +
"li#li-3.list-element, li#li-4.list-element ]";
const modeTests = [
{
mode: undefined,
expectedOutput: defaultOutput,
},
{
mode: "tiny",
expectedOutput: `[5]`,
},
{
mode: "short",
expectedOutput: defaultOutput,
},
{
mode: "long",
expectedOutput: longOutput,
}
];
testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
}
function getGripStub(functionName) {
switch (functionName) {
case "testBasic":
return {
"type": "object",
"class": "Array",
"actor": "server1.conn0.obj35",
"extensible": true,
"frozen": false,
"sealed": false,
"ownPropertyLength": 1,
"preview": {
"kind": "ArrayLike",
"length": 0,
"items": []
}
};
case "testMaxProps":
return {
"type": "object",
"class": "Array",
"actor": "server1.conn1.obj35",
"extensible": true,
"frozen": false,
"sealed": false,
"ownPropertyLength": 4,
"preview": {
"kind": "ArrayLike",
"length": 3,
"items": [
1,
"foo",
{
"type": "object",
"class": "Object",
"actor": "server1.conn1.obj36",
"extensible": true,
"frozen": false,
"sealed": false,
"ownPropertyLength": 0
}
]
}
};
case "testMoreThanShortMaxProps":
let shortArrayGrip = {
"type": "object",
"class": "Array",
"actor": "server1.conn1.obj35",
"extensible": true,
"frozen": false,
"sealed": false,
"ownPropertyLength": 4,
"preview": {
"kind": "ArrayLike",
"length": maxLength.short + 1,
"items": []
}
};
// Generate array grip with length 4, which is more that the maximum
// limit in case of the 'short' mode.
for (let i = 0; i < maxLength.short + 1; i++) {
shortArrayGrip.preview.items.push("test string");
}
return shortArrayGrip;
case "testMoreThanLongMaxProps":
let longArrayGrip = {
"type": "object",
"class": "Array",
"actor": "server1.conn1.obj35",
"extensible": true,
"frozen": false,
"sealed": false,
"ownPropertyLength": 4,
"preview": {
"kind": "ArrayLike",
"length": maxLength.long + 1,
"items": []
}
};
// Generate array grip with length 301, which is more that the maximum
// limit in case of the 'long' mode.
for (let i = 0; i < maxLength.long + 1; i++) {
longArrayGrip.preview.items.push("test string");
}
return longArrayGrip;
case "testPreviewLimit":
return {
"type": "object",
"class": "Array",
"actor": "server1.conn1.obj31",
"extensible": true,
"frozen": false,
"sealed": false,
"ownPropertyLength": 12,
"preview": {
"kind": "ArrayLike",
"length": 11,
"items": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
}
};
case "testRecursiveArray":
return {
"type": "object",
"class": "Array",
"actor": "server1.conn3.obj42",
"extensible": true,
"frozen": false,
"sealed": false,
"ownPropertyLength": 2,
"preview": {
"kind": "ArrayLike",
"length": 1,
"items": [
{
"type": "object",
"class": "Array",
"actor": "server1.conn3.obj43",
"extensible": true,
"frozen": false,
"sealed": false,
"ownPropertyLength": 2,
"preview": {
"kind": "ArrayLike",
"length": 1
}
}
]
}
};
case "testNamedNodeMap":
return {
"type": "object",
"class": "NamedNodeMap",
"actor": "server1.conn3.obj42",
"extensible": true,
"frozen": false,
"sealed": false,
"ownPropertyLength": 6,
"preview": {
"kind": "ArrayLike",
"length": 3,
"items": [
{
"type": "object",
"class": "Attr",
"actor": "server1.conn3.obj43",
"extensible": true,
"frozen": false,
"sealed": false,
"ownPropertyLength": 0,
"preview": {
"kind": "DOMNode",
"nodeType": 2,
"nodeName": "class",
"value": "myclass"
}
},
{
"type": "object",
"class": "Attr",
"actor": "server1.conn3.obj44",
"extensible": true,
"frozen": false,
"sealed": false,
"ownPropertyLength": 0,
"preview": {
"kind": "DOMNode",
"nodeType": 2,
"nodeName": "cellpadding",
"value": "7"
}
},
{
"type": "object",
"class": "Attr",
"actor": "server1.conn3.obj44",
"extensible": true,
"frozen": false,
"sealed": false,
"ownPropertyLength": 0,
"preview": {
"kind": "DOMNode",
"nodeType": 2,
"nodeName": "border",
"value": "3"
}
}
]
}
};
case "testNodeList":
return {
"type": "object",
"actor": "server1.conn1.child1/obj51",
"class": "NodeList",
"extensible": true,
"frozen": false,
"sealed": false,
"ownPropertyLength": 3,
"preview": {
"kind": "ArrayLike",
"length": 3,
"items": [
{
"type": "object",
"actor": "server1.conn1.child1/obj52",
"class": "HTMLButtonElement",
"extensible": true,
"frozen": false,
"sealed": false,
"ownPropertyLength": 0,
"preview": {
"kind": "DOMNode",
"nodeType": 1,
"nodeName": "button",
"attributes": {
"id": "btn-1",
"class": "btn btn-log",
"type": "button"
},
"attributesLength": 3
}
},
{
"type": "object",
"actor": "server1.conn1.child1/obj53",
"class": "HTMLButtonElement",
"extensible": true,
"frozen": false,
"sealed": false,
"ownPropertyLength": 0,
"preview": {
"kind": "DOMNode",
"nodeType": 1,
"nodeName": "button",
"attributes": {
"id": "btn-2",
"class": "btn btn-err",
"type": "button"
},
"attributesLength": 3
}
},
{
"type": "object",
"actor": "server1.conn1.child1/obj54",
"class": "HTMLButtonElement",
"extensible": true,
"frozen": false,
"sealed": false,
"ownPropertyLength": 0,
"preview": {
"kind": "DOMNode",
"nodeType": 1,
"nodeName": "button",
"attributes": {
"id": "btn-3",
"class": "btn btn-count",
"type": "button"
},
"attributesLength": 3
}
}
]
}
};
case "testDocumentFragment":
return {
"type": "object",
"actor": "server1.conn1.child1/obj45",
"class": "DocumentFragment",
"extensible": true,
"frozen": false,
"sealed": false,
"ownPropertyLength": 0,
"preview": {
"kind": "DOMNode",
"nodeType": 11,
"nodeName": "#document-fragment",
"childNodesLength": 5,
"childNodes": [
{
"type": "object",
"actor": "server1.conn1.child1/obj46",
"class": "HTMLLIElement",
"extensible": true,
"frozen": false,
"sealed": false,
"ownPropertyLength": 0,
"preview": {
"kind": "DOMNode",
"nodeType": 1,
"nodeName": "li",
"attributes": {
"id": "li-0",
"class": "list-element"
},
"attributesLength": 2
}
},
{
"type": "object",
"actor": "server1.conn1.child1/obj47",
"class": "HTMLLIElement",
"extensible": true,
"frozen": false,
"sealed": false,
"ownPropertyLength": 0,
"preview": {
"kind": "DOMNode",
"nodeType": 1,
"nodeName": "li",
"attributes": {
"id": "li-1",
"class": "list-element"
},
"attributesLength": 2
}
},
{
"type": "object",
"actor": "server1.conn1.child1/obj48",
"class": "HTMLLIElement",
"extensible": true,
"frozen": false,
"sealed": false,
"ownPropertyLength": 0,
"preview": {
"kind": "DOMNode",
"nodeType": 1,
"nodeName": "li",
"attributes": {
"id": "li-2",
"class": "list-element"
},
"attributesLength": 2
}
},
{
"type": "object",
"actor": "server1.conn1.child1/obj49",
"class": "HTMLLIElement",
"extensible": true,
"frozen": false,
"sealed": false,
"ownPropertyLength": 0,
"preview": {
"kind": "DOMNode",
"nodeType": 1,
"nodeName": "li",
"attributes": {
"id": "li-3",
"class": "list-element"
},
"attributesLength": 2
}
},
{
"type": "object",
"actor": "server1.conn1.child1/obj50",
"class": "HTMLLIElement",
"extensible": true,
"frozen": false,
"sealed": false,
"ownPropertyLength": 0,
"preview": {
"kind": "DOMNode",
"nodeType": 1,
"nodeName": "li",
"attributes": {
"id": "li-4",
"class": "list-element"
},
"attributesLength": 2
}
}
]
}
};
}
return null;
}
});
</script>
</pre>
</body>
</html>

View file

@ -0,0 +1,405 @@
<!-- 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/. -->
<!DOCTYPE HTML>
<html>
<!--
Test GripMap rep
-->
<head>
<meta charset="utf-8">
<title>Rep test - GripMap</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
</head>
<body>
<pre id="test">
<script src="head.js" type="application/javascript;version=1.8"></script>
<script type="application/javascript;version=1.8">
"use strict";
window.onload = Task.async(function* () {
let { Rep } = browserRequire("devtools/client/shared/components/reps/rep");
let { GripMap } = browserRequire("devtools/client/shared/components/reps/grip-map");
const componentUnderTest = GripMap;
try {
yield testEmptyMap();
yield testSymbolKeyedMap();
yield testWeakMap();
// // Test entries iterator
yield testMaxEntries();
yield testMoreThanMaxEntries();
yield testUninterestingEntries();
} catch (e) {
ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
} finally {
SimpleTest.finish();
}
function testEmptyMap() {
// Test object: `new Map()`
const testName = "testEmptyMap";
// Test that correct rep is chosen
const gripStub = getGripStub("testEmptyMap");
const renderedRep = shallowRenderComponent(Rep, { object: gripStub });
is(renderedRep.type, GripMap.rep, `Rep correctly selects ${GripMap.rep.displayName}`);
// Test rendering
const defaultOutput = `Map { }`;
const modeTests = [
{
mode: undefined,
expectedOutput: defaultOutput,
},
{
mode: "tiny",
expectedOutput: "Map",
},
{
mode: "short",
expectedOutput: defaultOutput,
},
{
mode: "long",
expectedOutput: defaultOutput,
}
];
testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
}
function testSymbolKeyedMap() {
// Test object:
// `new Map([[Symbol("a"), "value-a"], [Symbol("b"), "value-b"]])`
const testName = "testSymbolKeyedMap";
const defaultOutput = `Map { Symbol(a): "value-a", Symbol(b): "value-b" }`;
const modeTests = [
{
mode: undefined,
expectedOutput: defaultOutput,
},
{
mode: "tiny",
expectedOutput: "Map",
},
{
mode: "short",
expectedOutput: defaultOutput,
},
{
mode: "long",
expectedOutput: defaultOutput,
}
];
testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
}
function testWeakMap() {
// Test object: `new WeakMap([[{a: "key-a"}, "value-a"]])`
const testName = "testWeakMap";
// Test that correct rep is chosen
const gripStub = getGripStub("testWeakMap");
const renderedRep = shallowRenderComponent(Rep, { object: gripStub });
is(renderedRep.type, GripMap.rep, `Rep correctly selects ${GripMap.rep.displayName}`);
// Test rendering
const defaultOutput = `WeakMap { Object: "value-a" }`;
const modeTests = [
{
mode: undefined,
expectedOutput: defaultOutput,
},
{
mode: "tiny",
expectedOutput: "WeakMap",
},
{
mode: "short",
expectedOutput: defaultOutput,
},
{
mode: "long",
expectedOutput: defaultOutput,
}
];
testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
}
function testMaxEntries() {
// Test object:
// `new Map([["key-a","value-a"], ["key-b","value-b"], ["key-c","value-c"]])`
const testName = "testMaxEntries";
const defaultOutput = `Map { key-a: "value-a", key-b: "value-b", key-c: "value-c" }`;
const modeTests = [
{
mode: undefined,
expectedOutput: defaultOutput,
},
{
mode: "tiny",
expectedOutput: "Map",
},
{
mode: "short",
expectedOutput: defaultOutput,
},
{
mode: "long",
expectedOutput: defaultOutput,
}
];
testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
}
function testMoreThanMaxEntries() {
// Test object = `new Map(
// [["key-0", "value-0"], ["key-1", "value-1"]], …, ["key-100", "value-100"]]}`
const testName = "testMoreThanMaxEntries";
const defaultOutput =
`Map { key-0: "value-0", key-1: "value-1", key-2: "value-2", 98 more… }`;
// Generate string with 101 entries, which is the max limit for 'long' mode.
let longString = Array.from({length: 100}).map((_, i) => `key-${i}: "value-${i}"`);
const longOutput = `Map { ${longString.join(", ")}, 1 more… }`;
const modeTests = [
{
mode: undefined,
expectedOutput: defaultOutput,
},
{
mode: "tiny",
expectedOutput: `Map`,
},
{
mode: "short",
expectedOutput: defaultOutput,
},
{
mode: "long",
expectedOutput: longOutput,
}
];
testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
}
function testUninterestingEntries() {
// Test object:
// `new Map([["key-a",null], ["key-b",undefined], ["key-c","value-c"], ["key-d",4]])`
const testName = "testUninterestingEntries";
const defaultOutput =
`Map { key-a: null, key-c: "value-c", key-d: 4, 1 more… }`;
const longOutput =
`Map { key-a: null, key-b: undefined, key-c: "value-c", key-d: 4 }`;
const modeTests = [
{
mode: undefined,
expectedOutput: defaultOutput,
},
{
mode: "tiny",
expectedOutput: `Map`,
},
{
mode: "short",
expectedOutput: defaultOutput,
},
{
mode: "long",
expectedOutput: longOutput,
}
];
testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
}
function getGripStub(functionName) {
switch (functionName) {
case "testEmptyMap":
return {
"type": "object",
"actor": "server1.conn1.child1/obj97",
"class": "Map",
"extensible": true,
"frozen": false,
"sealed": false,
"ownPropertyLength": 0,
"preview": {
"kind": "MapLike",
"size": 0,
"entries": []
}
};
case "testSymbolKeyedMap":
return {
"type": "object",
"actor": "server1.conn1.child1/obj118",
"class": "Map",
"extensible": true,
"frozen": false,
"sealed": false,
"ownPropertyLength": 0,
"preview": {
"kind": "MapLike",
"size": 2,
"entries": [
[
{
"type": "symbol",
"name": "a"
},
"value-a"
],
[
{
"type": "symbol",
"name": "b"
},
"value-b"
]
]
}
};
case "testWeakMap":
return {
"type": "object",
"actor": "server1.conn1.child1/obj115",
"class": "WeakMap",
"extensible": true,
"frozen": false,
"sealed": false,
"ownPropertyLength": 0,
"preview": {
"kind": "MapLike",
"size": 1,
"entries": [
[
{
"type": "object",
"actor": "server1.conn1.child1/obj116",
"class": "Object",
"extensible": true,
"frozen": false,
"sealed": false,
"ownPropertyLength": 1
},
"value-a"
]
]
}
};
case "testMaxEntries":
return {
"type": "object",
"actor": "server1.conn1.child1/obj109",
"class": "Map",
"extensible": true,
"frozen": false,
"sealed": false,
"ownPropertyLength": 0,
"preview": {
"kind": "MapLike",
"size": 3,
"entries": [
[
"key-a",
"value-a"
],
[
"key-b",
"value-b"
],
[
"key-c",
"value-c"
]
]
}
};
case "testMoreThanMaxEntries": {
let entryNb = 101;
return {
"type": "object",
"class": "Map",
"actor": "server1.conn0.obj332",
"extensible": true,
"frozen": false,
"sealed": false,
"ownPropertyLength": 0,
"preview": {
"kind": "MapLike",
"size": entryNb,
// Generate 101 entries, which is more that the maximum
// limit in case of the 'long' mode.
"entries": Array.from({length: entryNb}).map((_, i) => {
return [`key-${i}`, `value-${i}`];
})
}
};
}
case "testUninterestingEntries":
return {
"type": "object",
"actor": "server1.conn1.child1/obj111",
"class": "Map",
"extensible": true,
"frozen": false,
"sealed": false,
"ownPropertyLength": 0,
"preview": {
"kind": "MapLike",
"size": 4,
"entries": [
[
"key-a",
{
"type": "null"
}
],
[
"key-b",
{
"type": "undefined"
}
],
[
"key-c",
"value-c"
],
[
"key-d",
4
]
]
}
};
}
}
});
</script>
</pre>
</body>
</html>

View file

@ -0,0 +1,887 @@
<!-- 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/. -->
<!DOCTYPE HTML>
<html>
<!--
Test grip rep
-->
<head>
<meta charset="utf-8">
<title>Rep test - grip</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
</head>
<body>
<pre id="test">
<script src="head.js" type="application/javascript;version=1.8"></script>
<script type="application/javascript;version=1.8">
window.onload = Task.async(function* () {
let { Rep } = browserRequire("devtools/client/shared/components/reps/rep");
let { Grip } = browserRequire("devtools/client/shared/components/reps/grip");
const componentUnderTest = Grip;
try {
yield testBasic();
yield testBooleanObject();
yield testNumberObject();
yield testStringObject();
yield testProxy();
yield testArrayBuffer();
yield testSharedArrayBuffer();
// Test property iterator
yield testMaxProps();
yield testMoreThanMaxProps();
yield testUninterestingProps();
yield testNonEnumerableProps();
// Test that properties are rendered as expected by PropRep
yield testNestedObject();
yield testNestedArray();
// Test that 'more' property doesn't clobber the caption.
yield testMoreProp();
} catch(e) {
ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
} finally {
SimpleTest.finish();
}
function testBasic() {
// Test object: `{}`
const testName = "testBasic";
// Test that correct rep is chosen
const gripStub = getGripStub("testBasic");
const renderedRep = shallowRenderComponent(Rep, { object: gripStub });
is(renderedRep.type, Grip.rep, `Rep correctly selects ${Grip.rep.displayName}`);
// Test rendering
const defaultOutput = `Object { }`;
const modeTests = [
{
mode: undefined,
expectedOutput: defaultOutput,
},
{
mode: "tiny",
expectedOutput: `Object`,
},
{
mode: "short",
expectedOutput: defaultOutput,
},
{
mode: "long",
expectedOutput: defaultOutput,
}
];
testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
}
function testBooleanObject() {
// Test object: `new Boolean(true)`
const testName = "testBooleanObject";
// Test that correct rep is chosen
const gripStub = getGripStub(testName);
const renderedRep = shallowRenderComponent(Rep, { object: gripStub });
is(renderedRep.type, Grip.rep, `Rep correctly selects ${Grip.rep.displayName}`);
// Test rendering
const defaultOutput = `Boolean { true }`;
const modeTests = [
{
mode: undefined,
expectedOutput: defaultOutput,
},
{
mode: "tiny",
expectedOutput: `Boolean`,
},
{
mode: "short",
expectedOutput: defaultOutput,
},
{
mode: "long",
expectedOutput: defaultOutput,
}
];
testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
}
function testNumberObject() {
// Test object: `new Number(42)`
const testName = "testNumberObject";
// Test that correct rep is chosen
const gripStub = getGripStub(testName);
const renderedRep = shallowRenderComponent(Rep, { object: gripStub });
is(renderedRep.type, Grip.rep, `Rep correctly selects ${Grip.rep.displayName}`);
// Test rendering
const defaultOutput = `Number { 42 }`;
const modeTests = [
{
mode: undefined,
expectedOutput: defaultOutput,
},
{
mode: "tiny",
expectedOutput: `Number`,
},
{
mode: "short",
expectedOutput: defaultOutput,
},
{
mode: "long",
expectedOutput: defaultOutput,
}
];
testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
}
function testStringObject() {
// Test object: `new String("foo")`
const testName = "testStringObject";
// Test that correct rep is chosen
const gripStub = getGripStub(testName);
const renderedRep = shallowRenderComponent(Rep, { object: gripStub });
is(renderedRep.type, Grip.rep, `Rep correctly selects ${Grip.rep.displayName}`);
// Test rendering
const defaultOutput = `String { "foo" }`;
const modeTests = [
{
mode: undefined,
expectedOutput: defaultOutput,
},
{
mode: "tiny",
expectedOutput: `String`,
},
{
mode: "short",
expectedOutput: defaultOutput,
},
{
mode: "long",
expectedOutput: defaultOutput,
}
];
testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
}
function testProxy() {
// Test object: `new Proxy({a:1},[1,2,3])`
const testName = "testProxy";
// Test that correct rep is chosen
const gripStub = getGripStub(testName);
const renderedRep = shallowRenderComponent(Rep, { object: gripStub });
is(renderedRep.type, Grip.rep, `Rep correctly selects ${Grip.rep.displayName}`);
// Test rendering
const defaultOutput = `Proxy { <target>: Object, <handler>: [3] }`;
const modeTests = [
{
mode: undefined,
expectedOutput: defaultOutput,
},
{
mode: "tiny",
expectedOutput: `Proxy`,
},
{
mode: "short",
expectedOutput: defaultOutput,
},
{
mode: "long",
expectedOutput: defaultOutput,
}
];
testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
}
function testArrayBuffer() {
// Test object: `new ArrayBuffer(10)`
const testName = "testArrayBuffer";
// Test that correct rep is chosen
const gripStub = getGripStub(testName);
const renderedRep = shallowRenderComponent(Rep, { object: gripStub });
is(renderedRep.type, Grip.rep, `Rep correctly selects ${Grip.rep.displayName}`);
// Test rendering
const defaultOutput = `ArrayBuffer { byteLength: 10 }`;
const modeTests = [
{
mode: undefined,
expectedOutput: defaultOutput,
},
{
mode: "tiny",
expectedOutput: `ArrayBuffer`,
},
{
mode: "short",
expectedOutput: defaultOutput,
},
{
mode: "long",
expectedOutput: defaultOutput,
}
];
testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
}
function testSharedArrayBuffer() {
// Test object: `new SharedArrayBuffer(5)`
const testName = "testSharedArrayBuffer";
// Test that correct rep is chosen
const gripStub = getGripStub(testName);
const renderedRep = shallowRenderComponent(Rep, { object: gripStub });
is(renderedRep.type, Grip.rep, `Rep correctly selects ${Grip.rep.displayName}`);
// Test rendering
const defaultOutput = `SharedArrayBuffer { byteLength: 5 }`;
const modeTests = [
{
mode: undefined,
expectedOutput: defaultOutput,
},
{
mode: "tiny",
expectedOutput: `SharedArrayBuffer`,
},
{
mode: "short",
expectedOutput: defaultOutput,
},
{
mode: "long",
expectedOutput: defaultOutput,
}
];
testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
}
function testMaxProps() {
// Test object: `{a: "a", b: "b", c: "c"}`;
const testName = "testMaxProps";
const defaultOutput = `Object { a: "a", b: "b", c: "c" }`;
const modeTests = [
{
mode: undefined,
expectedOutput: defaultOutput,
},
{
mode: "tiny",
expectedOutput: `Object`,
},
{
mode: "short",
expectedOutput: defaultOutput,
},
{
mode: "long",
expectedOutput: defaultOutput,
}
];
testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
}
function testMoreThanMaxProps() {
// Test object = `{p0: "0", p1: "1", p2: "2", …, p100: "100"}`
const testName = "testMoreThanMaxProps";
const defaultOutput = `Object { p0: "0", p1: "1", p2: "2", 98 more… }`;
// Generate string with 100 properties, which is the max limit
// for 'long' mode.
let props = "";
for (let i = 0; i < 100; i++) {
props += "p" + i + ": \"" + i + "\", ";
}
const longOutput = `Object { ${props}1 more… }`;
const modeTests = [
{
mode: undefined,
expectedOutput: defaultOutput,
},
{
mode: "tiny",
expectedOutput: `Object`,
},
{
mode: "short",
expectedOutput: defaultOutput,
},
{
mode: "long",
expectedOutput: longOutput,
}
];
testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
}
function testUninterestingProps() {
// Test object: `{a: undefined, b: undefined, c: "c", d: 1}`
// @TODO This is not how we actually want the preview to be output.
// See https://bugzilla.mozilla.org/show_bug.cgi?id=1276376
const expectedOutput = `Object { a: undefined, b: undefined, c: "c", 1 more… }`;
}
function testNonEnumerableProps() {
// Test object: `Object.defineProperty({}, "foo", {enumerable : false});`
const testName = "testNonEnumerableProps";
// Test that correct rep is chosen
const gripStub = getGripStub("testNonEnumerableProps");
const renderedRep = shallowRenderComponent(Rep, { object: gripStub });
is(renderedRep.type, Grip.rep, `Rep correctly selects ${Grip.rep.displayName}`);
// Test rendering
const defaultOutput = `Object { }`;
const modeTests = [
{
mode: undefined,
expectedOutput: defaultOutput,
},
{
mode: "tiny",
expectedOutput: `Object`,
},
{
mode: "short",
expectedOutput: defaultOutput,
},
{
mode: "long",
expectedOutput: defaultOutput,
}
];
testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
}
function testNestedObject() {
// Test object: `{objProp: {id: 1}, strProp: "test string"}`
const testName = "testNestedObject";
const defaultOutput = `Object { objProp: Object, strProp: "test string" }`;
const modeTests = [
{
mode: undefined,
expectedOutput: defaultOutput,
},
{
mode: "tiny",
expectedOutput: `Object`,
},
{
mode: "short",
expectedOutput: defaultOutput,
},
{
mode: "long",
expectedOutput: defaultOutput,
}
];
testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
}
function testNestedArray() {
// Test object: `{arrProp: ["foo", "bar", "baz"]}`
const testName = "testNestedArray";
const defaultOutput = `Object { arrProp: [3] }`;
const modeTests = [
{
mode: undefined,
expectedOutput: defaultOutput,
},
{
mode: "tiny",
expectedOutput: `Object`,
},
{
mode: "short",
expectedOutput: defaultOutput,
},
{
mode: "long",
expectedOutput: defaultOutput,
}
];
testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
}
function testMoreProp() {
// Test object: `{a: undefined, b: 1, more: 2, d: 3}`;
const testName = "testMoreProp";
const defaultOutput = `Object { b: 1, more: 2, d: 3, 1 more… }`;
const longOutput = `Object { a: undefined, b: 1, more: 2, d: 3 }`;
const modeTests = [
{
mode: undefined,
expectedOutput: defaultOutput,
},
{
mode: "tiny",
expectedOutput: `Object`,
},
{
mode: "short",
expectedOutput: defaultOutput,
},
{
mode: "long",
expectedOutput: longOutput,
}
];
testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
}
function getGripStub(functionName) {
switch (functionName) {
case "testBasic":
return {
"type": "object",
"class": "Object",
"actor": "server1.conn0.obj304",
"extensible": true,
"frozen": false,
"sealed": false,
"ownPropertyLength": 0,
"preview": {
"kind": "Object",
"ownProperties": {},
"ownPropertiesLength": 0,
"safeGetterValues": {}
}
};
case "testMaxProps":
return {
"type": "object",
"class": "Object",
"actor": "server1.conn0.obj337",
"extensible": true,
"frozen": false,
"sealed": false,
"ownPropertyLength": 3,
"preview": {
"kind": "Object",
"ownProperties": {
"a": {
"configurable": true,
"enumerable": true,
"writable": true,
"value": "a"
},
"b": {
"configurable": true,
"enumerable": true,
"writable": true,
"value": "b"
},
"c": {
"configurable": true,
"enumerable": true,
"writable": true,
"value": "c"
}
},
"ownPropertiesLength": 3,
"safeGetterValues": {}
}
};
case "testMoreThanMaxProps": {
let grip = {
"type": "object",
"class": "Object",
"actor": "server1.conn0.obj332",
"extensible": true,
"frozen": false,
"sealed": false,
"ownPropertyLength": 101,
"preview": {
"kind": "Object",
"ownProperties": {},
"ownPropertiesLength": 101,
"safeGetterValues": {}
}
};
// Generate 101 properties, which is more that the maximum
// limit in case of the 'long' mode.
for (let i = 0; i < 101; i++) {
grip.preview.ownProperties["p" + i] = {
"configurable": true,
"enumerable": true,
"writable": true,
"value": i + ""
};
}
return grip;
}
case "testUninterestingProps":
return {
"type": "object",
"class": "Object",
"actor": "server1.conn0.obj342",
"extensible": true,
"frozen": false,
"sealed": false,
"ownPropertyLength": 4,
"preview": {
"kind": "Object",
"ownProperties": {
"a": {
"configurable": true,
"enumerable": true,
"writable": true,
"value": {
"type": "undefined"
}
},
"b": {
"configurable": true,
"enumerable": true,
"writable": true,
"value": {
"type": "undefined"
}
},
"c": {
"configurable": true,
"enumerable": true,
"writable": true,
"value": "c"
},
"d": {
"configurable": true,
"enumerable": true,
"writable": true,
"value": 1
}
},
"ownPropertiesLength": 4,
"safeGetterValues": {}
}
};
case "testNonEnumerableProps":
return {
"type": "object",
"actor": "server1.conn1.child1/obj30",
"class": "Object",
"extensible": true,
"frozen": false,
"sealed": false,
"ownPropertyLength": 1,
"preview": {
"kind": "Object",
"ownProperties": {},
"ownPropertiesLength": 1,
"safeGetterValues": {}
}
};
case "testNestedObject":
return {
"type": "object",
"class": "Object",
"actor": "server1.conn0.obj145",
"extensible": true,
"frozen": false,
"sealed": false,
"ownPropertyLength": 2,
"preview": {
"kind": "Object",
"ownProperties": {
"objProp": {
"configurable": true,
"enumerable": true,
"writable": true,
"value": {
"type": "object",
"class": "Object",
"actor": "server1.conn0.obj146",
"extensible": true,
"frozen": false,
"sealed": false,
"ownPropertyLength": 1
}
},
"strProp": {
"configurable": true,
"enumerable": true,
"writable": true,
"value": "test string"
}
},
"ownPropertiesLength": 2,
"safeGetterValues": {}
}
};
case "testNestedArray":
return {
"type": "object",
"class": "Object",
"actor": "server1.conn0.obj326",
"extensible": true,
"frozen": false,
"sealed": false,
"ownPropertyLength": 1,
"preview": {
"kind": "Object",
"ownProperties": {
"arrProp": {
"configurable": true,
"enumerable": true,
"writable": true,
"value": {
"type": "object",
"class": "Array",
"actor": "server1.conn0.obj327",
"extensible": true,
"frozen": false,
"sealed": false,
"ownPropertyLength": 4,
"preview": {
"kind": "ArrayLike",
"length": 3
}
}
}
},
"ownPropertiesLength": 1,
"safeGetterValues": {}
},
};
case "testMoreProp":
return {
"type": "object",
"class": "Object",
"actor": "server1.conn0.obj342",
"extensible": true,
"frozen": false,
"sealed": false,
"ownPropertyLength": 4,
"preview": {
"kind": "Object",
"ownProperties": {
"a": {
"configurable": true,
"enumerable": true,
"writable": true,
"value": {
"type": "undefined"
}
},
"b": {
"configurable": true,
"enumerable": true,
"writable": true,
"value": 1
},
"more": {
"configurable": true,
"enumerable": true,
"writable": true,
"value": 2
},
"d": {
"configurable": true,
"enumerable": true,
"writable": true,
"value": 3
}
},
"ownPropertiesLength": 4,
"safeGetterValues": {}
}
};
case "testBooleanObject":
return {
"type": "object",
"actor": "server1.conn1.child1/obj57",
"class": "Boolean",
"ownPropertyLength": 0,
"preview": {
"kind": "Object",
"ownProperties": {},
"ownPropertiesLength": 0,
"safeGetterValues": {},
"wrappedValue": true
}
};
case "testNumberObject":
return {
"type": "object",
"actor": "server1.conn1.child1/obj59",
"class": "Number",
"ownPropertyLength": 0,
"preview": {
"kind": "Object",
"ownProperties": {},
"ownPropertiesLength": 0,
"safeGetterValues": {},
"wrappedValue": 42
}
};
case "testStringObject":
return {
"type": "object",
"actor": "server1.conn1.child1/obj61",
"class": "String",
"ownPropertyLength": 4,
"preview": {
"kind": "Object",
"ownProperties": {},
"ownPropertiesLength": 4,
"safeGetterValues": {},
"wrappedValue": "foo"
}
};
case "testProxy":
return {
"type": "object",
"actor": "server1.conn1.child1/obj47",
"class": "Proxy",
"proxyTarget": {
"type": "object",
"actor": "server1.conn1.child1/obj48",
"class": "Object",
"ownPropertyLength": 1
},
"proxyHandler": {
"type": "object",
"actor": "server1.conn1.child1/obj49",
"class": "Array",
"ownPropertyLength": 4,
"preview": {
"kind": "ArrayLike",
"length": 3
}
},
"preview": {
"kind": "Object",
"ownProperties": {
"<target>": {
"value": {
"type": "object",
"actor": "server1.conn1.child1/obj48",
"class": "Object",
"ownPropertyLength": 1
}
},
"<handler>": {
"value": {
"type": "object",
"actor": "server1.conn1.child1/obj49",
"class": "Array",
"ownPropertyLength": 4,
"preview": {
"kind": "ArrayLike",
"length": 3
}
}
}
},
"ownPropertiesLength": 2
}
};
case "testArrayBuffer":
return {
"type": "object",
"actor": "server1.conn1.child1/obj170",
"class": "ArrayBuffer",
"extensible": true,
"frozen": false,
"sealed": false,
"ownPropertyLength": 0,
"preview": {
"kind": "Object",
"ownProperties": {},
"ownPropertiesLength": 0,
"safeGetterValues": {
"byteLength": {
"getterValue": 10,
"getterPrototypeLevel": 1,
"enumerable": false,
"writable": true
}
}
}
};
case "testSharedArrayBuffer":
return {
"type": "object",
"actor": "server1.conn1.child1/obj171",
"class": "SharedArrayBuffer",
"extensible": true,
"frozen": false,
"sealed": false,
"ownPropertyLength": 0,
"preview": {
"kind": "Object",
"ownProperties": {},
"ownPropertiesLength": 0,
"safeGetterValues": {
"byteLength": {
"getterValue": 5,
"getterPrototypeLevel": 1,
"enumerable": false,
"writable": true
}
}
}
};
}
}
});
</script>
</pre>
</body>
</html>

View file

@ -0,0 +1,73 @@
<!-- 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/. -->
<!DOCTYPE HTML>
<html>
<!--
Test Infinity rep
-->
<head>
<meta charset="utf-8">
<title>Rep test - Infinity</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
</head>
<body>
<pre id="test">
<script src="head.js" type="application/javascript;version=1.8"></script>
<script type="application/javascript;version=1.8">
"use strict";
window.onload = Task.async(function* () {
let { Rep } = browserRequire("devtools/client/shared/components/reps/rep");
let { InfinityRep } = browserRequire("devtools/client/shared/components/reps/infinity");
try {
yield testInfinity();
yield testNegativeInfinity();
} catch (e) {
ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
} finally {
SimpleTest.finish();
}
function testInfinity() {
const stub = getGripStub("testInfinity");
const renderedRep = shallowRenderComponent(Rep, { object: stub });
is(renderedRep.type, InfinityRep.rep,
`Rep correctly selects ${InfinityRep.rep.displayName} for Infinity value`);
const renderedComponent = renderComponent(InfinityRep.rep, { object: stub });
is(renderedComponent.textContent, "Infinity",
"Infinity rep has expected text content for Infinity");
}
function testNegativeInfinity() {
const stub = getGripStub("testNegativeInfinity");
const renderedRep = shallowRenderComponent(Rep, { object: stub });
is(renderedRep.type, InfinityRep.rep,
`Rep correctly selects ${InfinityRep.rep.displayName} for negative Infinity value`);
const renderedComponent = renderComponent(InfinityRep.rep, { object: stub });
is(renderedComponent.textContent, "-Infinity",
"Infinity rep has expected text content for negative Infinity");
}
function getGripStub(name) {
switch (name) {
case "testInfinity":
return {
type: "Infinity"
};
case "testNegativeInfinity":
return {
type: "-Infinity"
};
}
return null;
}
});
</script>
</pre>
</body>
</html>

View file

@ -0,0 +1,125 @@
<!-- 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/. -->
<!DOCTYPE HTML>
<html>
<!--
Test LongString rep
-->
<head>
<meta charset="utf-8">
<title>Rep test - LongString</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
</head>
<body>
<pre id="test">
<script src="head.js" type="application/javascript;version=1.8"></script>
<script type="application/javascript;version=1.8">
window.onload = Task.async(function* () {
let { Rep } = browserRequire("devtools/client/shared/components/reps/rep");
let { LongStringRep } = browserRequire("devtools/client/shared/components/reps/long-string");
try {
// Test that correct rep is chosen
const renderedRep = shallowRenderComponent(Rep, { object: getGripStub("testMultiline") });
is(renderedRep.type, LongStringRep.rep,
`Rep correctly selects ${LongStringRep.rep.displayName}`);
// Test rendering
yield testMultiline();
yield testMultilineOpen();
yield testFullText();
yield testMultilineLimit();
yield testUseQuotes();
} catch (e) {
ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
} finally {
SimpleTest.finish();
}
function testMultiline() {
const stub = getGripStub("testMultiline");
const renderedComponent = renderComponent(
LongStringRep.rep, { object: stub });
is(renderedComponent.textContent, `"${stub.initial}…"`,
"LongString rep has expected text content for multiline string");
}
function testMultilineLimit() {
const renderedComponent = renderComponent(
LongStringRep.rep, { object: getGripStub("testMultiline"), cropLimit: 20 });
is(
renderedComponent.textContent,
`"a\naaaaaaaaaaaaaaaaaa…"`,
"LongString rep has expected text content for multiline string " +
"with specified number of characters");
}
function testMultilineOpen() {
const stub = getGripStub("testMultiline");
const renderedComponent = renderComponent(
LongStringRep.rep, { object: stub, member: {open: true}, cropLimit: 20 });
is(renderedComponent.textContent, `"${stub.initial}…"`,
"LongString rep has expected text content for multiline string when open");
}
function testFullText() {
const stub = getGripStub("testFullText");
const renderedComponentOpen = renderComponent(
LongStringRep.rep, { object: stub, member: {open: true}, cropLimit: 20 });
is(renderedComponentOpen.textContent, `"${stub.fullText}"`,
"LongString rep has expected text content when grip has a fullText " +
"property and is open");
const renderedComponentNotOpen = renderComponent(
LongStringRep.rep, { object: stub, cropLimit: 20 });
is(renderedComponentNotOpen.textContent,
`"a\naaaaaaaaaaaaaaaaaa…"`,
"LongString rep has expected text content when grip has a fullText " +
"property and is not open");
}
function testUseQuotes() {
const renderedComponent = renderComponent(LongStringRep.rep,
{ object: getGripStub("testMultiline"), cropLimit: 20, useQuotes: false });
is(renderedComponent.textContent,
"a\naaaaaaaaaaaaaaaaaa…",
"LongString rep was expected to omit quotes");
}
function getGripStub(name) {
const multilineFullText = "a\n" + Array(20000).fill("a").join("");
const fullTextLength = multilineFullText.length;
const initialText = multilineFullText.substring(0, 10000);
switch (name) {
case "testMultiline":
return {
"type": "longString",
"initial": initialText,
"length": fullTextLength,
"actor": "server1.conn1.child1/longString58"
};
case "testFullText":
return {
"type": "longString",
"fullText": multilineFullText,
"initial": initialText,
"length": fullTextLength,
"actor": "server1.conn1.child1/longString58"
};
}
return null;
}
});
</script>
</pre>
</body>
</html>

View file

@ -0,0 +1,48 @@
<!-- 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/. -->
<!DOCTYPE HTML>
<html>
<!--
Test NaN rep
-->
<head>
<meta charset="utf-8">
<title>Rep test - NaN</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
</head>
<body>
<pre id="test">
<script src="head.js" type="application/javascript;version=1.8"></script>
<script type="application/javascript;version=1.8">
"use strict";
window.onload = Task.async(function* () {
let { Rep } = browserRequire("devtools/client/shared/components/reps/rep");
let { NaNRep } = browserRequire("devtools/client/shared/components/reps/nan");
try {
yield testNaN();
} catch (e) {
ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
} finally {
SimpleTest.finish();
}
function testNaN() {
const stub = {
type: "NaN"
};
const renderedRep = shallowRenderComponent(Rep, {object: stub});
is(renderedRep.type, NaNRep.rep,
`Rep correctly selects ${NaNRep.rep.displayName} for NaN value`);
const renderedComponent = renderComponent(NaNRep.rep, {object: stub});
is(renderedComponent.textContent, "NaN", "NaN rep has expected text content");
}
});
</script>
</pre>
</body>
</html>

View file

@ -0,0 +1,44 @@
<!-- 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/. -->
<!DOCTYPE HTML>
<html>
<!--
Test Null rep
-->
<head>
<meta charset="utf-8">
<title>Rep test - Null</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
</head>
<body>
<pre id="test">
<script src="head.js" type="application/javascript;version=1.8"></script>
<script type="application/javascript;version=1.8">
window.onload = Task.async(function* () {
try {
let { Rep } = browserRequire("devtools/client/shared/components/reps/rep");
let { Null } = browserRequire("devtools/client/shared/components/reps/null");
let gripStub = {
"type": "null"
};
// Test that correct rep is chosen
const renderedRep = shallowRenderComponent(Rep, { object: gripStub });
is(renderedRep.type, Null.rep, `Rep correctly selects ${Null.rep.displayName}`);
// Test rendering
const renderedComponent = renderComponent(Null.rep, { object: gripStub });
is(renderedComponent.textContent, "null", "Null rep has expected text content");
} catch(e) {
ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
} finally {
SimpleTest.finish();
}
});
</script>
</pre>
</body>
</html>

View file

@ -0,0 +1,97 @@
<!-- 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/. -->
<!DOCTYPE HTML>
<html>
<!--
Test Number rep
-->
<head>
<meta charset="utf-8">
<title>Rep test - Number</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
</head>
<body>
<pre id="test">
<script src="head.js" type="application/javascript;version=1.8"></script>
<script type="application/javascript;version=1.8">
window.onload = Task.async(function* () {
let { Rep } = browserRequire("devtools/client/shared/components/reps/rep");
let { Number } = browserRequire("devtools/client/shared/components/reps/number");
try {
yield testInt();
yield testBoolean();
yield testNegativeZero();
yield testUnsafeInt();
} catch(e) {
ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
} finally {
SimpleTest.finish();
}
function testInt() {
const renderedRep = shallowRenderComponent(Rep, { object: getGripStub("testInt") });
is(renderedRep.type, Number.rep, `Rep correctly selects ${Number.rep.displayName} for integer value`);
const renderedComponent = renderComponent(Number.rep, { object: getGripStub("testInt") });
is(renderedComponent.textContent, "5", "Number rep has expected text content for integer");
}
function testBoolean() {
const renderedRep = shallowRenderComponent(Rep, { object: getGripStub("testTrue") });
is(renderedRep.type, Number.rep, `Rep correctly selects ${Number.rep.displayName} for boolean value`);
let renderedComponent = renderComponent(Number.rep, { object: getGripStub("testTrue") });
is(renderedComponent.textContent, "true", "Number rep has expected text content for boolean true");
renderedComponent = renderComponent(Number.rep, { object: getGripStub("testFalse") });
is(renderedComponent.textContent, "false", "Number rep has expected text content for boolean false");
}
function testNegativeZero() {
const renderedRep = shallowRenderComponent(Rep, { object: getGripStub("testNegZeroGrip") });
is(renderedRep.type, Number.rep, `Rep correctly selects ${Number.rep.displayName} for negative zero value`);
let renderedComponent = renderComponent(Number.rep, { object: getGripStub("testNegZeroGrip") });
is(renderedComponent.textContent, "-0", "Number rep has expected text content for negative zero grip");
renderedComponent = renderComponent(Number.rep, { object: getGripStub("testNegZeroValue") });
is(renderedComponent.textContent, "-0", "Number rep has expected text content for negative zero value");
}
function testUnsafeInt() {
const renderedComponent = renderComponent(Number.rep, { object: getGripStub("testUnsafeInt") });
is(renderedComponent.textContent, "900719925474099100", "Number rep has expected text content for a long number");
}
function getGripStub(name) {
switch (name) {
case "testInt":
return 5;
case "testTrue":
return true;
case "testFalse":
return false;
case "testNegZeroValue":
return -0;
case "testNegZeroGrip":
return {
"type": "-0"
};
case "testUnsafeInt":
return 900719925474099122;
}
}
});
</script>
</pre>
</body>
</html>

View file

@ -0,0 +1,54 @@
<!-- 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/. -->
<!DOCTYPE HTML>
<html>
<!--
Test ObjectWithText rep
-->
<head>
<meta charset="utf-8">
<title>Rep test - ObjectWithText</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
</head>
<body>
<pre id="test">
<script src="head.js" type="application/javascript;version=1.8"></script>
<script type="application/javascript;version=1.8">
window.onload = Task.async(function* () {
try {
let { Rep } = browserRequire("devtools/client/shared/components/reps/rep");
let { ObjectWithText } = browserRequire("devtools/client/shared/components/reps/object-with-text");
let gripStub = {
"type": "object",
"class": "CSSStyleRule",
"actor": "server1.conn3.obj273",
"extensible": true,
"frozen": false,
"sealed": false,
"ownPropertyLength": 0,
"preview": {
"kind": "ObjectWithText",
"text": ".Shadow"
}
};
// Test that correct rep is chosen
const renderedRep = shallowRenderComponent(Rep, { object: gripStub });
is(renderedRep.type, ObjectWithText.rep, `Rep correctly selects ${ObjectWithText.rep.displayName}`);
// Test rendering
const renderedComponent = renderComponent(ObjectWithText.rep, { object: gripStub });
is(renderedComponent.textContent, "\".Shadow\"", "ObjectWithText rep has expected text content");
} catch(e) {
ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
} finally {
SimpleTest.finish();
}
});
</script>
</pre>
</body>
</html>

View file

@ -0,0 +1,60 @@
<!-- 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/. -->
<!DOCTYPE HTML>
<html>
<!--
Test ObjectWithURL rep
-->
<head>
<meta charset="utf-8">
<title>Rep test - ObjectWithURL</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
</head>
<body>
<pre id="test">
<script src="head.js" type="application/javascript;version=1.8"></script>
<script type="application/javascript;version=1.8">
window.onload = Task.async(function* () {
try {
let ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom");
let React = browserRequire("devtools/client/shared/vendor/react");
let { Rep } = browserRequire("devtools/client/shared/components/reps/rep");
let { ObjectWithURL } = browserRequire("devtools/client/shared/components/reps/object-with-url");
let gripStub = {
"type": "object",
"class": "Location",
"actor": "server1.conn2.obj272",
"extensible": true,
"frozen": false,
"sealed": false,
"ownPropertyLength": 15,
"preview": {
"kind": "ObjectWithURL",
"url": "https://www.mozilla.org/en-US/"
}
};
// Test that correct rep is chosen
const renderedRep = shallowRenderComponent(Rep, { object: gripStub });
is(renderedRep.type, ObjectWithURL.rep, `Rep correctly selects ${ObjectWithURL.rep.displayName}`);
// Test rendering
const renderedComponent = renderComponent(ObjectWithURL.rep, { object: gripStub });
ok(renderedComponent.className.includes("objectBox-Location"), "ObjectWithURL rep has expected class name");
const innerNode = renderedComponent.querySelector(".objectPropValue");
is(innerNode.textContent, "https://www.mozilla.org/en-US/", "ObjectWithURL rep has expected inner HTML structure and text content");
// @TODO test link once Bug 1245303 has been implemented.
} catch(e) {
ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
} finally {
SimpleTest.finish();
}
});
</script>
</pre>
</body>
</html>

View file

@ -0,0 +1,225 @@
<!-- 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/. -->
<!DOCTYPE HTML>
<html>
<!--
Test Obj rep
-->
<head>
<meta charset="utf-8">
<title>Rep test - Obj</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
</head>
<body>
<pre id="test">
<script src="head.js" type="application/javascript;version=1.8"></script>
<script type="application/javascript;version=1.8">
window.onload = Task.async(function* () {
let { Rep } = browserRequire("devtools/client/shared/components/reps/rep");
let { Obj } = browserRequire("devtools/client/shared/components/reps/object");
const componentUnderTest = Obj;
try {
yield testBasic();
// Test property iterator
yield testMaxProps();
yield testMoreThanMaxProps();
yield testUninterestingProps();
// Test that properties are rendered as expected by PropRep
yield testNested();
// Test that 'more' property doesn't clobber the caption.
yield testMoreProp();
} catch(e) {
ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
} finally {
SimpleTest.finish();
}
function testBasic() {
const stub = {};
// Test that correct rep is chosen
const renderedRep = shallowRenderComponent(Rep, { object: stub });
is(renderedRep.type, Obj.rep, `Rep correctly selects ${Obj.rep.displayName}`);
// Test rendering
const defaultOutput = `Object`;
const modeTests = [
{
mode: undefined,
expectedOutput: defaultOutput,
},
{
mode: "tiny",
expectedOutput: defaultOutput,
},
{
mode: "short",
expectedOutput: defaultOutput,
},
{
mode: "long",
expectedOutput: defaultOutput,
}
];
testRepRenderModes(modeTests, "testBasic", componentUnderTest, stub);
}
function testMaxProps() {
const testName = "testMaxProps";
const stub = {a: "a", b: "b", c: "c"};
const defaultOutput = `Object { a: "a", b: "b", c: "c" }`;
const modeTests = [
{
mode: undefined,
expectedOutput: defaultOutput,
},
{
mode: "tiny",
expectedOutput: `Object`,
},
{
mode: "short",
expectedOutput: defaultOutput,
},
{
mode: "long",
expectedOutput: defaultOutput,
}
];
testRepRenderModes(modeTests, "testMaxProps", componentUnderTest, stub);
}
function testMoreThanMaxProps() {
let stub = {};
for (let i = 0; i<100; i++) {
stub[`p${i}`] = i
}
const defaultOutput = `Object { p0: 0, p1: 1, p2: 2, 97 more… }`;
const modeTests = [
{
mode: undefined,
expectedOutput: defaultOutput,
},
{
mode: "tiny",
expectedOutput: `Object`,
},
{
mode: "short",
expectedOutput: defaultOutput,
},
{
mode: "long",
expectedOutput: defaultOutput,
}
];
testRepRenderModes(modeTests, "testMoreThanMaxProps", componentUnderTest, stub);
}
function testUninterestingProps() {
const stub = {a:undefined, b:undefined, c:"c", d:0};
const defaultOutput = `Object { c: "c", d: 0, a: undefined, 1 more… }`;
const modeTests = [
{
mode: undefined,
expectedOutput: defaultOutput,
},
{
mode: "tiny",
expectedOutput: `Object`,
},
{
mode: "short",
expectedOutput: defaultOutput,
},
{
mode: "long",
expectedOutput: defaultOutput,
}
];
testRepRenderModes(modeTests, "testUninterestingProps", componentUnderTest, stub);
}
function testNested() {
const stub = {
objProp: {
id: 1,
arr: [2]
},
strProp: "test string",
arrProp: [1]
};
const defaultOutput = `Object { strProp: "test string", objProp: Object, arrProp: [1] }`;
const modeTests = [
{
mode: undefined,
expectedOutput: defaultOutput,
},
{
mode: "tiny",
expectedOutput: `Object`,
},
{
mode: "short",
expectedOutput: defaultOutput,
},
{
mode: "long",
expectedOutput: defaultOutput,
}
];
testRepRenderModes(modeTests, "testNestedObject", componentUnderTest, stub);
}
function testMoreProp() {
const stub = {
a: undefined,
b: 1,
'more': 2,
d: 3
};
const defaultOutput = `Object { b: 1, more: 2, d: 3, 1 more… }`;
const modeTests = [
{
mode: undefined,
expectedOutput: defaultOutput,
},
{
mode: "tiny",
expectedOutput: `Object`,
},
{
mode: "short",
expectedOutput: defaultOutput,
},
{
mode: "long",
expectedOutput: defaultOutput,
}
];
testRepRenderModes(modeTests, "testMoreProp", componentUnderTest, stub);
}});
</script>
</pre>
</body>
</html>

View file

@ -0,0 +1,333 @@
<!-- 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/. -->
<!DOCTYPE HTML>
<html>
<!--
Test Promise rep
-->
<head>
<meta charset="utf-8">
<title>Rep test - Promise</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
</head>
<body>
<pre id="test">
<script src="head.js" type="application/javascript;version=1.8"></script>
<script type="application/javascript;version=1.8">
"use strict";
window.onload = Task.async(function* () {
let { Rep } = browserRequire("devtools/client/shared/components/reps/rep");
let { PromiseRep } = browserRequire("devtools/client/shared/components/reps/promise");
const componentUnderTest = PromiseRep;
try {
yield testPending();
yield testFulfilledWithNumber();
yield testFulfilledWithString();
yield testFulfilledWithObject();
yield testFulfilledWithArray();
} catch (e) {
ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
} finally {
SimpleTest.finish();
}
function testPending() {
// Test object = `new Promise((resolve, reject) => true)`
const stub = getGripStub("testPending");
// Test that correct rep is chosen.
const renderedRep = shallowRenderComponent(Rep, { object: stub });
is(renderedRep.type, PromiseRep.rep,
`Rep correctly selects ${PromiseRep.rep.displayName} for pending Promise`);
// Test rendering
const defaultOutput = `Promise { <state>: "pending" }`;
const modeTests = [
{
mode: undefined,
expectedOutput: defaultOutput,
},
{
mode: "tiny",
expectedOutput: `Promise { "pending" }`,
},
{
mode: "short",
expectedOutput: defaultOutput,
},
{
mode: "long",
expectedOutput: defaultOutput,
}
];
testRepRenderModes(modeTests, "testPending", componentUnderTest, stub);
}
function testFulfilledWithNumber() {
// Test object = `Promise.resolve(42)`
const stub = getGripStub("testFulfilledWithNumber");
// Test that correct rep is chosen.
const renderedRep = shallowRenderComponent(Rep, { object: stub });
const {displayName} = PromiseRep.rep;
is(renderedRep.type, PromiseRep.rep,
`Rep correctly selects ${displayName} for Promise fulfilled with a number`);
// Test rendering
const defaultOutput = `Promise { <state>: "fulfilled", <value>: 42 }`;
const modeTests = [
{
mode: undefined,
expectedOutput: defaultOutput,
},
{
mode: "tiny",
expectedOutput: `Promise { "fulfilled" }`,
},
{
mode: "short",
expectedOutput: defaultOutput,
},
{
mode: "long",
expectedOutput: defaultOutput,
}
];
testRepRenderModes(modeTests, "testFulfilledWithNumber", componentUnderTest, stub);
}
function testFulfilledWithString() {
// Test object = `Promise.resolve("foo")`
const stub = getGripStub("testFulfilledWithString");
// Test that correct rep is chosen.
const renderedRep = shallowRenderComponent(Rep, { object: stub });
const {displayName} = PromiseRep.rep;
is(renderedRep.type, PromiseRep.rep,
`Rep correctly selects ${displayName} for Promise fulfilled with a string`);
// Test rendering
const defaultOutput = `Promise { <state>: "fulfilled", <value>: "foo" }`;
const modeTests = [
{
mode: undefined,
expectedOutput: defaultOutput,
},
{
mode: "tiny",
expectedOutput: `Promise { "fulfilled" }`,
},
{
mode: "short",
expectedOutput: defaultOutput,
},
{
mode: "long",
expectedOutput: defaultOutput,
}
];
testRepRenderModes(modeTests, "testFulfilledWithString", componentUnderTest, stub);
}
function testFulfilledWithObject() {
// Test object = `Promise.resolve({foo: "bar", baz: "boo"})`
const stub = getGripStub("testFulfilledWithObject");
// Test that correct rep is chosen.
const renderedRep = shallowRenderComponent(Rep, { object: stub });
const {displayName} = PromiseRep.rep;
is(renderedRep.type, PromiseRep.rep,
`Rep correctly selects ${displayName} for Promise fulfilled with an object`);
// Test rendering
const defaultOutput = `Promise { <state>: "fulfilled", <value>: Object }`;
const modeTests = [
{
mode: undefined,
expectedOutput: defaultOutput,
},
{
mode: "tiny",
expectedOutput: `Promise { "fulfilled" }`,
},
{
mode: "short",
expectedOutput: defaultOutput,
},
{
mode: "long",
expectedOutput: defaultOutput,
}
];
testRepRenderModes(modeTests, "testFulfilledWithObject", componentUnderTest, stub);
}
function testFulfilledWithArray() {
// Test object = `Promise.resolve([1,2,3])`
const stub = getGripStub("testFulfilledWithArray");
// Test that correct rep is chosen.
const renderedRep = shallowRenderComponent(Rep, { object: stub });
const {displayName} = PromiseRep.rep;
is(renderedRep.type, PromiseRep.rep,
`Rep correctly selects ${displayName} for Promise fulfilled with an array`);
// Test rendering
const defaultOutput = `Promise { <state>: "fulfilled", <value>: [3] }`;
const modeTests = [
{
mode: undefined,
expectedOutput: defaultOutput,
},
{
mode: "tiny",
expectedOutput: `Promise { "fulfilled" }`,
},
{
mode: "short",
expectedOutput: defaultOutput,
},
{
mode: "long",
expectedOutput: defaultOutput,
}
];
testRepRenderModes(modeTests, "testFulfilledWithArray", componentUnderTest, stub);
}
function getGripStub(name) {
switch (name) {
case "testPending":
return {
"type": "object",
"actor": "server1.conn1.child1/obj54",
"class": "Promise",
"promiseState": {
"state": "pending",
"creationTimestamp": 1477327760242.5752
},
"ownPropertyLength": 0,
"preview": {
"kind": "Object",
"ownProperties": {},
"ownPropertiesLength": 0,
"safeGetterValues": {}
}
};
case "testFulfilledWithNumber":
return {
"type": "object",
"actor": "server1.conn1.child1/obj55",
"class": "Promise",
"promiseState": {
"state": "fulfilled",
"value": 42,
"creationTimestamp": 1477327760242.721,
"timeToSettle": 0.018497000000479602
},
"ownPropertyLength": 0,
"preview": {
"kind": "Object",
"ownProperties": {},
"ownPropertiesLength": 0,
"safeGetterValues": {}
}
};
case "testFulfilledWithString":
return {
"type": "object",
"actor": "server1.conn1.child1/obj56",
"class": "Promise",
"promiseState": {
"state": "fulfilled",
"value": "foo",
"creationTimestamp": 1477327760243.2483,
"timeToSettle": 0.0019969999998465937
},
"ownPropertyLength": 0,
"preview": {
"kind": "Object",
"ownProperties": {},
"ownPropertiesLength": 0,
"safeGetterValues": {}
}
};
case "testFulfilledWithObject":
return {
"type": "object",
"actor": "server1.conn1.child1/obj59",
"class": "Promise",
"promiseState": {
"state": "fulfilled",
"value": {
"type": "object",
"actor": "server1.conn1.child1/obj60",
"class": "Object",
"extensible": true,
"frozen": false,
"sealed": false,
"ownPropertyLength": 2
},
"creationTimestamp": 1477327760243.2214,
"timeToSettle": 0.002035999999861815
},
"ownPropertyLength": 0,
"preview": {
"kind": "Object",
"ownProperties": {},
"ownPropertiesLength": 0,
"safeGetterValues": {}
}
};
case "testFulfilledWithArray":
return {
"type": "object",
"actor": "server1.conn1.child1/obj57",
"class": "Promise",
"promiseState": {
"state": "fulfilled",
"value": {
"type": "object",
"actor": "server1.conn1.child1/obj58",
"class": "Array",
"extensible": true,
"frozen": false,
"sealed": false,
"ownPropertyLength": 4,
"preview": {
"kind": "ArrayLike",
"length": 3
}
},
"creationTimestamp": 1477327760242.9597,
"timeToSettle": 0.006158000000141328
},
"ownPropertyLength": 0,
"preview": {
"kind": "Object",
"ownProperties": {},
"ownPropertiesLength": 0,
"safeGetterValues": {}
}
};
}
return null;
}
});
</script>
</pre>
</body>
</html>

View file

@ -0,0 +1,51 @@
<!-- 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/. -->
<!DOCTYPE HTML>
<html>
<!--
Test RegExp rep
-->
<head>
<meta charset="utf-8">
<title>Rep test - RegExp</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
</head>
<body>
<pre id="test">
<script src="head.js" type="application/javascript;version=1.8"></script>
<script type="application/javascript;version=1.8">
window.onload = Task.async(function* () {
try {
let { Rep } = browserRequire("devtools/client/shared/components/reps/rep");
let { RegExp } = browserRequire("devtools/client/shared/components/reps/regexp");
let gripStub = {
"type": "object",
"class": "RegExp",
"actor": "server1.conn22.obj39",
"extensible": true,
"frozen": false,
"sealed": false,
"ownPropertyLength": 1,
"displayString": "/ab+c/i"
};
// Test that correct rep is chosen
const renderedRep = shallowRenderComponent(Rep, { object: gripStub });
is(renderedRep.type, RegExp.rep, `Rep correctly selects ${RegExp.rep.displayName}`);
// Test rendering
const renderedComponent = renderComponent(RegExp.rep, { object: gripStub });
is(renderedComponent.textContent, "/ab+c/i", "RegExp rep has expected text content");
} catch(e) {
ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
} finally {
SimpleTest.finish();
}
});
</script>
</pre>
</body>
</html>

View file

@ -0,0 +1,79 @@
<!-- 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/. -->
<!DOCTYPE HTML>
<html>
<!--
Test String rep
-->
<head>
<meta charset="utf-8">
<title>Rep test - String</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
</head>
<body>
<pre id="test">
<script src="head.js" type="application/javascript;version=1.8"></script>
<script type="application/javascript;version=1.8">
window.onload = Task.async(function* () {
let { Rep } = browserRequire("devtools/client/shared/components/reps/rep");
let { StringRep } = browserRequire("devtools/client/shared/components/reps/string");
try {
// Test that correct rep is chosen
const renderedRep = shallowRenderComponent(Rep, { object: getGripStub("testMultiline") });
is(renderedRep.type, StringRep.rep, `Rep correctly selects ${StringRep.rep.displayName}`);
// Test rendering
yield testMultiline();
yield testMultilineOpen();
yield testMultilineLimit();
yield testUseQuotes();
yield testNonPritableCharacters();
} catch(e) {
ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
} finally {
SimpleTest.finish();
}
function testMultiline() {
const renderedComponent = renderComponent(StringRep.rep, { object: getGripStub("testMultiline") });
is(renderedComponent.textContent, "\"aaaaaaaaaaaaaaaaaaaaa\nbbbbbbbbbbbbbbbbbbb\ncccccccccccccccc\n\"", "String rep has expected text content for multiline string");
}
function testMultilineLimit() {
const renderedComponent = renderComponent(StringRep.rep, { object: getGripStub("testMultiline"), cropLimit: 20 });
is(renderedComponent.textContent, "\"aaaaaaaaaa…cccccccc\n\"", "String rep has expected text content for multiline string with specified number of characters");
}
function testMultilineOpen() {
const renderedComponent = renderComponent(StringRep.rep, { object: getGripStub("testMultiline"), member: {open: true} });
is(renderedComponent.textContent, "\"aaaaaaaaaaaaaaaaaaaaa\nbbbbbbbbbbbbbbbbbbb\ncccccccccccccccc\n\"", "String rep has expected text content for multiline string when open");
}
function testUseQuotes(){
const renderedComponent = renderComponent(StringRep.rep, { object: getGripStub("testUseQuotes"), useQuotes: false });
is(renderedComponent.textContent, "abc", "String rep was expected to omit quotes");
}
function testNonPritableCharacters(){
const renderedComponent = renderComponent(StringRep.rep, { object: getGripStub("testNonPritableCharacters"), useQuotes: false });
is(renderedComponent.textContent, "a\ufffdb", "String rep was expected to omit non printable characters");
}
function getGripStub(name) {
switch (name) {
case "testMultiline":
return "aaaaaaaaaaaaaaaaaaaaa\nbbbbbbbbbbbbbbbbbbb\ncccccccccccccccc\n";
case "testUseQuotes":
return "abc";
case "testNonPritableCharacters":
return "a\x01b";
}
}
});
</script>
</pre>
</body>
</html>

View file

@ -0,0 +1,54 @@
<!-- 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/. -->
<!DOCTYPE HTML>
<html>
<!--
Test Stylesheet rep
-->
<head>
<meta charset="utf-8">
<title>Rep test - Stylesheet</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
</head>
<body>
<pre id="test">
<script src="head.js" type="application/javascript;version=1.8"></script>
<script type="application/javascript;version=1.8">
window.onload = Task.async(function* () {
try {
let { Rep } = browserRequire("devtools/client/shared/components/reps/rep");
let { StyleSheet } = browserRequire("devtools/client/shared/components/reps/stylesheet");
let gripStub = {
"type": "object",
"class": "CSSStyleSheet",
"actor": "server1.conn2.obj1067",
"extensible": true,
"frozen": false,
"sealed": false,
"ownPropertyLength": 0,
"preview": {
"kind": "ObjectWithURL",
"url": "https://example.com/styles.css"
}
};
// Test that correct rep is chosen
const renderedRep = shallowRenderComponent(Rep, { object: gripStub });
is(renderedRep.type, StyleSheet.rep, `Rep correctly selects ${StyleSheet.rep.displayName}`);
// Test rendering
const renderedComponent = renderComponent(StyleSheet.rep, { object: gripStub });
is(renderedComponent.textContent, "StyleSheet https://example.com/styles.css", "StyleSheet rep has expected text content");
} catch(e) {
ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
} finally {
SimpleTest.finish();
}
});
</script>
</pre>
</body>
</html>

View file

@ -0,0 +1,77 @@
<!-- 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/. -->
<!DOCTYPE HTML>
<html>
<!--
Test Symbol rep
-->
<head>
<meta charset="utf-8">
<title>Rep test - String</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
</head>
<body>
<pre id="test">
<script src="head.js" type="application/javascript;version=1.8"></script>
<script type="application/javascript;version=1.8">
"use strict";
/* import-globals-from head.js */
window.onload = Task.async(function* () {
let { Rep } = browserRequire("devtools/client/shared/components/reps/rep");
let { SymbolRep } = browserRequire("devtools/client/shared/components/reps/symbol");
let gripStubs = new Map();
gripStubs.set("testSymbolFoo", {
type: "symbol",
name: "foo"
});
gripStubs.set("testSymbolWithoutIdentifier", {
type: "symbol"
});
try {
// Test that correct rep is chosen
const renderedRep = shallowRenderComponent(
Rep,
{ object: gripStubs.get("testSymbolFoo")}
);
is(renderedRep.type, SymbolRep.rep,
`Rep correctly selects ${SymbolRep.rep.displayName}`);
// Test rendering
yield testSymbol();
yield testSymbolWithoutIdentifier();
} catch (e) {
ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
} finally {
SimpleTest.finish();
}
function testSymbol() {
const renderedComponent = renderComponent(
SymbolRep.rep,
{ object: gripStubs.get("testSymbolFoo") }
);
is(renderedComponent.textContent, "Symbol(foo)",
"Symbol rep has expected text content");
}
function testSymbolWithoutIdentifier() {
const renderedComponent = renderComponent(
SymbolRep.rep,
{ object: gripStubs.get("testSymbolWithoutIdentifier") }
);
is(renderedComponent.textContent, "Symbol()",
"Symbol rep without identifier has expected text content");
}
});
</script>
</pre>
</body>
</html>

View file

@ -0,0 +1,115 @@
<!-- 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/. -->
<!DOCTYPE HTML>
<html>
<!--
Test text-node rep
-->
<head>
<meta charset="utf-8">
<title>Rep test - text-node</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
</head>
<body>
<pre id="test">
<script src="head.js" type="application/javascript;version=1.8"></script>
<script type="application/javascript;version=1.8">
"use strict";
window.onload = Task.async(function* () {
let { Rep } = browserRequire("devtools/client/shared/components/reps/rep");
let { TextNode } = browserRequire("devtools/client/shared/components/reps/text-node");
let gripStubs = new Map();
gripStubs.set("testRendering", {
"class": "Text",
"actor": "server1.conn1.child1/obj50",
"preview": {
"textContent": "hello world"
}
});
gripStubs.set("testRenderingWithEOL", {
"class": "Text",
"actor": "server1.conn1.child1/obj50",
"preview": {
"textContent": "hello\nworld"
}
});
try {
// Test that correct rep is chosen
const renderedRep = shallowRenderComponent(Rep, {
object: gripStubs.get("testRendering")
});
is(renderedRep.type, TextNode.rep,
`Rep correctly selects ${TextNode.rep.displayName}`);
yield testRendering();
yield testRenderingWithEOL();
} catch (e) {
ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
} finally {
SimpleTest.finish();
}
function testRendering() {
const stub = gripStubs.get("testRendering");
const defaultShortOutput = `"hello world"`;
const defaultLongOutput = `<TextNode textContent="hello world">;`;
const modeTests = [
{
mode: undefined,
expectedOutput: defaultShortOutput,
},
{
mode: "tiny",
expectedOutput: defaultShortOutput,
},
{
mode: "short",
expectedOutput: defaultShortOutput,
},
{
mode: "long",
expectedOutput: defaultLongOutput,
}
];
testRepRenderModes(modeTests, "testRendering", TextNode, stub);
}
function testRenderingWithEOL() {
const stub = gripStubs.get("testRenderingWithEOL");
const defaultShortOutput = `"hello\nworld"`;
const defaultLongOutput = `<TextNode textContent="hello\nworld">;`;
const modeTests = [
{
mode: undefined,
expectedOutput: defaultShortOutput,
},
{
mode: "tiny",
expectedOutput: defaultShortOutput,
},
{
mode: "short",
expectedOutput: defaultShortOutput,
},
{
mode: "long",
expectedOutput: defaultLongOutput,
}
];
testRepRenderModes(modeTests, "testRenderingWithEOL", TextNode, stub);
}
});
</script>
</pre>
</body>
</html>

View file

@ -0,0 +1,47 @@
<!-- 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/. -->
<!DOCTYPE HTML>
<html>
<!--
Test undefined rep
-->
<head>
<meta charset="utf-8">
<title>Rep test - undefined</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
</head>
<body>
<pre id="test">
<script src="head.js" type="application/javascript;version=1.8"></script>
<script type="application/javascript;version=1.8">
window.onload = Task.async(function* () {
try {
let ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom");
let React = browserRequire("devtools/client/shared/vendor/react");
let { Rep } = browserRequire("devtools/client/shared/components/reps/rep");
let { Undefined } = browserRequire("devtools/client/shared/components/reps/undefined");
let gripStub = {
"type": "undefined"
};
// Test that correct rep is chosen
const renderedRep = shallowRenderComponent(Rep, { object: gripStub });
is(renderedRep.type, Undefined.rep, `Rep correctly selects ${Undefined.rep.displayName}`);
// Test rendering
const renderedComponent = renderComponent(Undefined.rep, {});
is(renderedComponent.className, "objectBox objectBox-undefined", "Undefined rep has expected class names");
is(renderedComponent.textContent, "undefined", "Undefined rep has expected text content");
} catch(e) {
ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
} finally {
SimpleTest.finish();
}
});
</script>
</pre>
</body>
</html>

View file

@ -0,0 +1,58 @@
<!-- 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/. -->
<!DOCTYPE HTML>
<html>
<!--
Test window rep
-->
<head>
<meta charset="utf-8">
<title>Rep tests - window</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
</head>
<body>
<pre id="test">
<script src="head.js" type="application/javascript;version=1.8"></script>
<script type="application/javascript;version=1.8">
window.onload = Task.async(function* () {
try {
let ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom");
let React = browserRequire("devtools/client/shared/vendor/react");
let { Rep } = browserRequire("devtools/client/shared/components/reps/rep");
let { Window } = browserRequire("devtools/client/shared/components/reps/window");
let gripStub = {
"type": "object",
"class": "Window",
"actor": "server1.conn3.obj198",
"extensible": true,
"frozen": false,
"sealed": false,
"ownPropertyLength": 887,
"preview": {
"kind": "ObjectWithURL",
"url": "about:newtab"
}
};
// Test that correct rep is chosen
const renderedRep = shallowRenderComponent(Rep, { object: gripStub });
is(renderedRep.type, Window.rep, `Rep correctly selects ${Window.rep.displayName}`);
// Test rendering
const renderedComponent = renderComponent(Window.rep, { object: gripStub });
ok(renderedComponent.className.includes("objectBox-Window"), "Window rep has expected class name");
const innerNode = renderedComponent.querySelector(".objectPropValue");
is(innerNode.textContent, "about:newtab", "Window rep has expected inner HTML structure and text content");
} catch(e) {
ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
} finally {
SimpleTest.finish();
}
});
</script>
</pre>
</body>
</html>

View file

@ -0,0 +1,56 @@
<!-- 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/. -->
<!DOCTYPE HTML>
<html>
<!--
Test sidebar toggle button
-->
<head>
<meta charset="utf-8">
<title>Sidebar toggle button test</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
</head>
<body>
<pre id="test">
<script src="head.js" type="application/javascript;version=1.8"></script>
<script type="application/javascript;version=1.8">
window.onload = Task.async(function* () {
let SidebarToggle = browserRequire("devtools/client/shared/components/sidebar-toggle.js");
try {
yield test();
} catch(e) {
ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
} finally {
SimpleTest.finish();
}
function test() {
const output1 = shallowRenderComponent(SidebarToggle, {
collapsed: false,
collapsePaneTitle: "Expand",
expandPaneTitle: "Collapse"
});
is(output1.type, "button", "Output is a button element");
is(output1.props.title, "Expand", "Proper title is set");
is(output1.props.className.indexOf("pane-collapsed"), -1,
"Proper class name is set");
const output2 = shallowRenderComponent(SidebarToggle, {
collapsed: true,
collapsePaneTitle: "Expand",
expandPaneTitle: "Collapse"
});
is(output2.props.title, "Collapse", "Proper title is set");
ok(output2.props.className.indexOf("pane-collapsed") >= 0,
"Proper class name is set");
}
});
</script>
</pre>
</body>
</html>

View file

@ -0,0 +1,102 @@
<!-- 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/. -->
<!DOCTYPE HTML>
<html>
<!--
Test the rendering of a stack trace
-->
<head>
<meta charset="utf-8">
<title>StackTrace component test</title>
<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script src="chrome://mochikit/content/tests/SimpleTest/SpawnTask.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
</head>
<body>
<script src="head.js"></script>
<script>
/* import-globals-from head.js */
"use strict";
window.onload = function () {
let ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom");
let React = browserRequire("devtools/client/shared/vendor/react");
let StackTrace = React.createFactory(
browserRequire("devtools/client/shared/components/stack-trace")
);
ok(StackTrace, "Got the StackTrace factory");
add_task(function* () {
let stacktrace = [
{
filename: "http://myfile.com/mahscripts.js",
lineNumber: 55,
columnNumber: 10
},
{
asyncCause: "because",
functionName: "loadFunc",
filename: "http://myfile.com/loader.js -> http://myfile.com/loadee.js",
lineNumber: 10
}
];
let props = {
stacktrace,
onViewSourceInDebugger: () => {}
};
let trace = ReactDOM.render(StackTrace(props), window.document.body);
yield forceRender(trace);
let traceEl = trace.getDOMNode();
ok(traceEl, "Rendered StackTrace has an element");
// Get the child nodes and filter out the text-only whitespace ones
let frameEls = Array.from(traceEl.childNodes)
.filter(n => n.className.includes("frame"));
ok(frameEls, "Rendered StackTrace has frames");
is(frameEls.length, 3, "StackTrace has 3 frames");
// Check the top frame, function name should be anonymous
checkFrameString({
el: frameEls[0],
functionName: "<anonymous>",
source: "http://myfile.com/mahscripts.js",
file: "http://myfile.com/mahscripts.js",
line: 55,
column: 10,
shouldLink: true,
tooltip: "View source in Debugger → http://myfile.com/mahscripts.js:55:10",
});
// Check the async cause node
is(frameEls[1].className, "frame-link-async-cause",
"Async cause has the right class");
is(frameEls[1].textContent, "(Async: because)", "Async cause has the right label");
// Check the third frame, the source should be parsed into a valid source URL
checkFrameString({
el: frameEls[2],
functionName: "loadFunc",
source: "http://myfile.com/loadee.js",
file: "http://myfile.com/loadee.js",
line: 10,
column: null,
shouldLink: true,
tooltip: "View source in Debugger → http://myfile.com/loadee.js:10",
});
// Check the tabs and newlines in the stack trace textContent
let traceText = traceEl.textContent;
let traceLines = traceText.split("\n");
ok(traceLines.length > 0, "There are newlines in the stack trace text");
is(traceLines.pop(), "", "There is a newline at the end of the stack trace text");
is(traceLines.length, 3, "The stack trace text has 3 lines");
ok(traceLines.every(l => l[0] == "\t"), "Every stack trace line starts with tab");
});
};
</script>
</body>
</html>

View file

@ -0,0 +1,79 @@
<!-- 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/. -->
<!DOCTYPE HTML>
<html>
<!--
Test tabs accessibility.
-->
<head>
<meta charset="utf-8">
<title>Tabs component accessibility test</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
</head>
<body>
<pre id="test">
<script src="head.js" type="application/javascript;version=1.8"></script>
<script type="application/javascript;version=1.8">
window.onload = Task.async(function* () {
try {
const ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom");
const React = browserRequire("devtools/client/shared/vendor/react");
const { Simulate } = React.addons.TestUtils;
const InspectorTabPanel = React.createFactory(browserRequire("devtools/client/inspector/components/inspector-tab-panel"));
const Tabbar = React.createFactory(browserRequire("devtools/client/shared/components/tabs/tabbar"));
const tabbar = Tabbar();
const tabbarReact = ReactDOM.render(tabbar, window.document.body);
const tabbarEl = ReactDOM.findDOMNode(tabbarReact);
// Setup for InspectorTabPanel
const tabpanels = document.createElement("div");
tabpanels.id = "tabpanels";
document.body.appendChild(tabpanels);
yield addTabWithPanel(0);
yield addTabWithPanel(1);
const tabAnchors = tabbarEl.querySelectorAll("li.tabs-menu-item a");
is(tabAnchors[0].parentElement.getAttribute("role"), "presentation", "li role is set correctly");
is(tabAnchors[0].getAttribute("role"), "tab", "Anchor role is set correctly");
is(tabAnchors[0].getAttribute("aria-selected"), "true", "Anchor aria-selected is set correctly by default");
is(tabAnchors[0].getAttribute("aria-controls"), "panel-0", "Anchor aria-controls is set correctly");
is(tabAnchors[1].parentElement.getAttribute("role"), "presentation", "li role is set correctly");
is(tabAnchors[1].getAttribute("role"), "tab", "Anchor role is set correctly");
is(tabAnchors[1].getAttribute("aria-selected"), "false", "Anchor aria-selected is set correctly by default");
is(tabAnchors[1].getAttribute("aria-controls"), "panel-1", "Anchor aria-controls is set correctly");
yield setState(tabbarReact, Object.assign({}, tabbarReact.state, {
activeTab: 1
}));
is(tabAnchors[0].getAttribute("aria-selected"), "false", "Anchor aria-selected is reset correctly");
is(tabAnchors[1].getAttribute("aria-selected"), "true", "Anchor aria-selected is reset correctly");
function addTabWithPanel(tabId) {
// Setup for InspectorTabPanel
let panel = document.createElement("div");
panel.id = `sidebar-panel-${tabId}`;
document.body.appendChild(panel);
return setState(tabbarReact, Object.assign({}, tabbarReact.state, {
tabs: tabbarReact.state.tabs.concat({
id: `sidebar-panel-${tabId}`,
title: `tab-${tabId}`,
panel: InspectorTabPanel
}),
}));
}
} catch(e) {
ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
} finally {
SimpleTest.finish();
}
});
</script>
</pre>
</body>
</html>

View file

@ -0,0 +1,81 @@
<!-- 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/. -->
<!DOCTYPE HTML>
<html class="theme-light">
<!--
Test all-tabs menu.
-->
<head>
<meta charset="utf-8">
<title>Tabs component All-tabs menu test</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
<link rel="stylesheet" type="text/css" href="resource://devtools/client/themes/variables.css">
<link rel="stylesheet" type="text/css" href="resource://devtools/client/themes/common.css">
<link rel="stylesheet" type="text/css" href="resource://devtools/client/themes/light-theme.css">
<link rel="stylesheet" type="text/css" href="resource://devtools/client/shared/components/tabs/tabs.css">
<link rel="stylesheet" type="text/css" href="resource://devtools/client/shared/components/tabs/tabbar.css">
<link rel="stylesheet" type="text/css" href="resource://devtools/client/inspector/components/side-panel.css">
<link rel="stylesheet" type="text/css" href="resource://devtools/client/inspector/components/inspector-tab-panel.css">
</head>
<body>
<pre id="test">
<script src="head.js" type="application/javascript;version=1.8"></script>
<script type="application/javascript;version=1.8">
window.onload = Task.async(function* () {
try {
const ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom");
const React = browserRequire("devtools/client/shared/vendor/react");
const Tabbar = React.createFactory(browserRequire("devtools/client/shared/components/tabs/tabbar"));
// Create container for the TabBar. Set smaller width
// to ensure that tabs won't fit and the all-tabs menu
// needs to appear.
const tabBarBox = document.createElement("div");
tabBarBox.style.width = "200px";
tabBarBox.style.height = "200px";
tabBarBox.style.border = "1px solid lightgray";
document.body.appendChild(tabBarBox);
// Render the tab-bar.
const tabbar = Tabbar({
showAllTabsMenu: true,
});
const tabbarReact = ReactDOM.render(tabbar, tabBarBox);
// Test panel.
let TabPanel = React.createFactory(React.createClass({
render: function () {
return React.DOM.div({}, "content");
}
}));
// Create a few panels.
yield addTabWithPanel(1);
yield addTabWithPanel(2);
yield addTabWithPanel(3);
yield addTabWithPanel(4);
yield addTabWithPanel(5);
// Make sure the all-tabs menu is there.
const allTabsMenu = tabBarBox.querySelector(".all-tabs-menu");
ok(allTabsMenu, "All-tabs menu must be rendered");
function addTabWithPanel(tabId) {
return setState(tabbarReact, Object.assign({}, tabbarReact.state, {
tabs: tabbarReact.state.tabs.concat({id: `${tabId}`,
title: `tab-${tabId}`, panel: TabPanel}),
}));
}
} catch(e) {
ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
} finally {
SimpleTest.finish();
}
});
</script>
</pre>
</body>
</html>

Some files were not shown because too many files have changed in this diff Show more