mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-05 15:28:39 +09:00
import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo
This commit is contained in:
commit
dcd9973243
150858 changed files with 23884658 additions and 0 deletions
631
devtools/client/shared/AppCacheUtils.jsm
Normal file
631
devtools/client/shared/AppCacheUtils.jsm
Normal 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);
|
||||
});
|
||||
166
devtools/client/shared/DOMHelpers.jsm
Normal file
166
devtools/client/shared/DOMHelpers.jsm
Normal 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);
|
||||
}
|
||||
}
|
||||
};
|
||||
16
devtools/client/shared/Jsbeautify.jsm
Normal file
16
devtools/client/shared/Jsbeautify.jsm
Normal 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;
|
||||
312
devtools/client/shared/SplitView.jsm
Normal file
312
devtools/client/shared/SplitView.jsm
Normal 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;
|
||||
},
|
||||
};
|
||||
599
devtools/client/shared/autocomplete-popup.js
Normal file
599
devtools/client/shared/autocomplete-popup.js
Normal 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;
|
||||
},
|
||||
};
|
||||
235
devtools/client/shared/browser-loader.js
Normal file
235
devtools/client/shared/browser-loader.js
Normal 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"];
|
||||
7
devtools/client/shared/components/.eslintrc.js
Normal file
7
devtools/client/shared/components/.eslintrc.js
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
"use strict";
|
||||
|
||||
module.exports = {
|
||||
"globals": {
|
||||
"define": true,
|
||||
}
|
||||
};
|
||||
239
devtools/client/shared/components/frame.js
Normal file
239
devtools/client/shared/components/frame.js
Normal 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);
|
||||
}
|
||||
});
|
||||
154
devtools/client/shared/components/h-split-box.js
Normal file
154
devtools/client/shared/components/h-split-box.js
Normal 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
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
27
devtools/client/shared/components/moz.build
Normal file
27
devtools/client/shared/components/moz.build
Normal 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']
|
||||
95
devtools/client/shared/components/notification-box.css
Normal file
95
devtools/client/shared/components/notification-box.css
Normal 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 */
|
||||
}
|
||||
263
devtools/client/shared/components/notification-box.js
Normal file
263
devtools/client/shared/components/notification-box.js
Normal 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;
|
||||
186
devtools/client/shared/components/reps/array.js
Normal file
186
devtools/client/shared/components/reps/array.js
Normal 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
|
||||
};
|
||||
});
|
||||
70
devtools/client/shared/components/reps/attribute.js
Normal file
70
devtools/client/shared/components/reps/attribute.js
Normal 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
|
||||
};
|
||||
});
|
||||
31
devtools/client/shared/components/reps/caption.js
Normal file
31
devtools/client/shared/components/reps/caption.js
Normal 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;
|
||||
});
|
||||
60
devtools/client/shared/components/reps/comment-node.js
Normal file
60
devtools/client/shared/components/reps/comment-node.js
Normal 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
|
||||
};
|
||||
});
|
||||
70
devtools/client/shared/components/reps/date-time.js
Normal file
70
devtools/client/shared/components/reps/date-time.js
Normal 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
|
||||
};
|
||||
});
|
||||
78
devtools/client/shared/components/reps/document.js
Normal file
78
devtools/client/shared/components/reps/document.js
Normal 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
|
||||
};
|
||||
});
|
||||
114
devtools/client/shared/components/reps/element-node.js
Normal file
114
devtools/client/shared/components/reps/element-node.js
Normal 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
|
||||
};
|
||||
});
|
||||
81
devtools/client/shared/components/reps/event.js
Normal file
81
devtools/client/shared/components/reps/event.js
Normal 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
|
||||
};
|
||||
});
|
||||
73
devtools/client/shared/components/reps/function.js
Normal file
73
devtools/client/shared/components/reps/function.js
Normal 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
|
||||
};
|
||||
});
|
||||
198
devtools/client/shared/components/reps/grip-array.js
Normal file
198
devtools/client/shared/components/reps/grip-array.js
Normal 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
|
||||
};
|
||||
});
|
||||
193
devtools/client/shared/components/reps/grip-map.js
Normal file
193
devtools/client/shared/components/reps/grip-map.js
Normal 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
|
||||
};
|
||||
});
|
||||
247
devtools/client/shared/components/reps/grip.js
Normal file
247
devtools/client/shared/components/reps/grip.js
Normal 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;
|
||||
});
|
||||
41
devtools/client/shared/components/reps/infinity.js
Normal file
41
devtools/client/shared/components/reps/infinity.js
Normal 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
|
||||
};
|
||||
});
|
||||
71
devtools/client/shared/components/reps/long-string.js
Normal file
71
devtools/client/shared/components/reps/long-string.js
Normal 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,
|
||||
};
|
||||
});
|
||||
40
devtools/client/shared/components/reps/moz.build
Normal file
40
devtools/client/shared/components/reps/moz.build
Normal 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',
|
||||
)
|
||||
41
devtools/client/shared/components/reps/nan.js
Normal file
41
devtools/client/shared/components/reps/nan.js
Normal 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
|
||||
};
|
||||
});
|
||||
46
devtools/client/shared/components/reps/null.js
Normal file
46
devtools/client/shared/components/reps/null.js
Normal 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
|
||||
};
|
||||
});
|
||||
51
devtools/client/shared/components/reps/number.js
Normal file
51
devtools/client/shared/components/reps/number.js
Normal 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
|
||||
};
|
||||
});
|
||||
76
devtools/client/shared/components/reps/object-with-text.js
Normal file
76
devtools/client/shared/components/reps/object-with-text.js
Normal 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
|
||||
};
|
||||
});
|
||||
76
devtools/client/shared/components/reps/object-with-url.js
Normal file
76
devtools/client/shared/components/reps/object-with-url.js
Normal 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
|
||||
};
|
||||
});
|
||||
171
devtools/client/shared/components/reps/object.js
Normal file
171
devtools/client/shared/components/reps/object.js
Normal 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
|
||||
};
|
||||
});
|
||||
111
devtools/client/shared/components/reps/promise.js
Normal file
111
devtools/client/shared/components/reps/promise.js
Normal 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
|
||||
};
|
||||
});
|
||||
70
devtools/client/shared/components/reps/prop-rep.js
Normal file
70
devtools/client/shared/components/reps/prop-rep.js
Normal 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;
|
||||
});
|
||||
63
devtools/client/shared/components/reps/regexp.js
Normal file
63
devtools/client/shared/components/reps/regexp.js
Normal 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
|
||||
};
|
||||
});
|
||||
160
devtools/client/shared/components/reps/rep-utils.js
Normal file
160
devtools/client/shared/components/reps/rep-utils.js
Normal 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;
|
||||
});
|
||||
144
devtools/client/shared/components/reps/rep.js
Normal file
144
devtools/client/shared/components/reps/rep.js
Normal 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;
|
||||
});
|
||||
174
devtools/client/shared/components/reps/reps.css
Normal file
174
devtools/client/shared/components/reps/reps.css
Normal 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;
|
||||
}
|
||||
69
devtools/client/shared/components/reps/string.js
Normal file
69
devtools/client/shared/components/reps/string.js
Normal 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,
|
||||
};
|
||||
});
|
||||
77
devtools/client/shared/components/reps/stylesheet.js
Normal file
77
devtools/client/shared/components/reps/stylesheet.js
Normal 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
|
||||
};
|
||||
});
|
||||
48
devtools/client/shared/components/reps/symbol.js
Normal file
48
devtools/client/shared/components/reps/symbol.js
Normal 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,
|
||||
};
|
||||
});
|
||||
94
devtools/client/shared/components/reps/text-node.js
Normal file
94
devtools/client/shared/components/reps/text-node.js
Normal 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
|
||||
};
|
||||
});
|
||||
46
devtools/client/shared/components/reps/undefined.js
Normal file
46
devtools/client/shared/components/reps/undefined.js
Normal 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
|
||||
};
|
||||
});
|
||||
73
devtools/client/shared/components/reps/window.js
Normal file
73
devtools/client/shared/components/reps/window.js
Normal 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
|
||||
};
|
||||
});
|
||||
110
devtools/client/shared/components/search-box.js
Normal file
110
devtools/client/shared/components/search-box.js
Normal 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
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
32
devtools/client/shared/components/sidebar-toggle.css
Normal file
32
devtools/client/shared/components/sidebar-toggle.css
Normal 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);
|
||||
}
|
||||
}
|
||||
66
devtools/client/shared/components/sidebar-toggle.js
Normal file
66
devtools/client/shared/components/sidebar-toggle.js
Normal 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;
|
||||
54
devtools/client/shared/components/splitter/draggable.js
Normal file
54
devtools/client/shared/components/splitter/draggable.js
Normal 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;
|
||||
11
devtools/client/shared/components/splitter/moz.build
Normal file
11
devtools/client/shared/components/splitter/moz.build
Normal 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',
|
||||
)
|
||||
88
devtools/client/shared/components/splitter/split-box.css
Normal file
88
devtools/client/shared/components/splitter/split-box.css
Normal 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;
|
||||
}
|
||||
205
devtools/client/shared/components/splitter/split-box.js
Normal file
205
devtools/client/shared/components/splitter/split-box.js
Normal 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;
|
||||
68
devtools/client/shared/components/stack-trace.js
Normal file
68
devtools/client/shared/components/stack-trace.js
Normal 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;
|
||||
12
devtools/client/shared/components/tabs/moz.build
Normal file
12
devtools/client/shared/components/tabs/moz.build
Normal 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',
|
||||
)
|
||||
53
devtools/client/shared/components/tabs/tabbar.css
Normal file
53
devtools/client/shared/components/tabs/tabbar.css
Normal 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;
|
||||
}
|
||||
|
||||
204
devtools/client/shared/components/tabs/tabbar.js
Normal file
204
devtools/client/shared/components/tabs/tabbar.js
Normal 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;
|
||||
183
devtools/client/shared/components/tabs/tabs.css
Normal file
183
devtools/client/shared/components/tabs/tabs.css
Normal 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;
|
||||
}
|
||||
369
devtools/client/shared/components/tabs/tabs.js
Normal file
369
devtools/client/shared/components/tabs/tabs.js
Normal 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;
|
||||
});
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
"use strict";
|
||||
|
||||
module.exports = {
|
||||
// Extend from the shared list of defined globals for mochitests.
|
||||
"extends": "../../../../../.eslintrc.mochitests.js",
|
||||
};
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
[DEFAULT]
|
||||
tags = devtools
|
||||
subsuite = devtools
|
||||
support-files =
|
||||
!/devtools/client/framework/test/shared-head.js
|
||||
|
||||
[browser_notification_box_basic.js]
|
||||
|
|
@ -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");
|
||||
});
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
"use strict";
|
||||
|
||||
module.exports = {
|
||||
// Extend from the shared list of defined globals for mochitests.
|
||||
"extends": "../../../../../.eslintrc.mochitests.js"
|
||||
};
|
||||
51
devtools/client/shared/components/test/mochitest/chrome.ini
Normal file
51
devtools/client/shared/components/test/mochitest/chrome.ini
Normal 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]
|
||||
217
devtools/client/shared/components/test/mochitest/head.js
Normal file
217
devtools/client/shared/components/test/mochitest/head.js
Normal 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);
|
||||
});
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue