Bug 1102240 - split inspector actor in smaller files;r=ochameau,pbro

MozReview-Commit-ID: LgZav4dMQRR

--HG--
rename : devtools/server/actors/inspector/inspector.js => devtools/server/actors/inspector/document-walker.js
rename : devtools/server/actors/inspector/inspector.js => devtools/server/actors/inspector/node-actor.js
rename : devtools/server/actors/inspector/inspector.js => devtools/server/actors/inspector/utils.js
rename : devtools/server/actors/inspector/inspector.js => devtools/server/actors/inspector/walker-actor.js
extra : rebase_source : b57466d2e2dfb6f332dec3a38a03b12506eb4a30
This commit is contained in:
Julian Descottes 2018-01-26 13:11:58 +01:00
Родитель 8eb0574c63
Коммит 8cca58077e
17 изменённых файлов: 3199 добавлений и 3111 удалений

Просмотреть файл

@ -11,7 +11,7 @@ const EventEmitter = require("devtools/shared/event-emitter");
const {ActorClassWithSpec, Actor, FrontClassWithSpec, Front, generateActorSpec} =
require("devtools/shared/protocol");
const {NodeActor} = require("devtools/server/actors/inspector/inspector");
const {NodeActor} = require("devtools/server/actors/inspector/node-actor");
var eventsSpec = generateActorSpec({
typeName: "eventsFormActor",

Просмотреть файл

@ -7,7 +7,7 @@
// Test editing a node's text content
const TEST_URL = URL_ROOT + "doc_markup_edit.html";
const {DEFAULT_VALUE_SUMMARY_LENGTH} = require("devtools/server/actors/inspector/inspector");
const {DEFAULT_VALUE_SUMMARY_LENGTH} = require("devtools/server/actors/inspector/walker-actor");
add_task(function* () {
let {inspector, testActor} = yield openInspectorForURL(TEST_URL);

Просмотреть файл

@ -18,7 +18,7 @@ const {
setIgnoreLayoutChanges,
getCurrentZoom,
} = require("devtools/shared/layout/utils");
const inspector = require("devtools/server/actors/inspector/inspector");
const { getNodeDisplayName } = require("devtools/server/actors/inspector/utils");
const nodeConstants = require("devtools/shared/dom-node-constants");
// Note that the order of items in this array is important because it is used
@ -676,7 +676,7 @@ class BoxModelHighlighter extends AutoRefreshHighlighter {
getBindingElementAndPseudo(this.currentNode);
// Update the tag, id, classes, pseudo-classes and dimensions
let displayName = inspector.getNodeDisplayName(node);
let displayName = getNodeDisplayName(node);
let id = node.id ? "#" + node.id : "";

Просмотреть файл

@ -0,0 +1,182 @@
/* 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 {Cc, Ci, Cu} = require("chrome");
loader.lazyRequireGetter(this, "nodeFilterConstants", "devtools/shared/dom-node-filter-constants");
loader.lazyRequireGetter(this, "standardTreeWalkerFilter", "devtools/server/actors/inspector/utils", true);
// SKIP_TO_* arguments are used with the DocumentWalker, driving the strategy to use if
// the starting node is incompatible with the filter function of the walker.
const SKIP_TO_PARENT = "SKIP_TO_PARENT";
const SKIP_TO_SIBLING = "SKIP_TO_SIBLING";
/**
* Wrapper for inDeepTreeWalker. Adds filtering to the traversal methods.
* See inDeepTreeWalker for more information about the methods.
*
* @param {DOMNode} node
* @param {Window} rootWin
* @param {Number} whatToShow
* See nodeFilterConstants / inIDeepTreeWalker for options.
* @param {Function} filter
* A custom filter function Taking in a DOMNode and returning an Int. See
* WalkerActor.nodeFilter for an example.
* @param {String} skipTo
* Either SKIP_TO_PARENT or SKIP_TO_SIBLING. If the provided node is not compatible
* with the filter function for this walker, try to find a compatible one either
* in the parents or in the siblings of the node.
*/
function DocumentWalker(node, rootWin,
whatToShow = nodeFilterConstants.SHOW_ALL,
filter = standardTreeWalkerFilter,
skipTo = SKIP_TO_PARENT) {
if (Cu.isDeadWrapper(rootWin) || !rootWin.location) {
throw new Error("Got an invalid root window in DocumentWalker");
}
this.walker = Cc["@mozilla.org/inspector/deep-tree-walker;1"]
.createInstance(Ci.inIDeepTreeWalker);
this.walker.showAnonymousContent = true;
this.walker.showSubDocuments = true;
this.walker.showDocumentsAsNodes = true;
this.walker.init(rootWin.document, whatToShow);
this.filter = filter;
// Make sure that the walker knows about the initial node (which could
// be skipped due to a filter).
this.walker.currentNode = this.getStartingNode(node, skipTo);
}
DocumentWalker.prototype = {
get whatToShow() {
return this.walker.whatToShow;
},
get currentNode() {
return this.walker.currentNode;
},
set currentNode(val) {
this.walker.currentNode = val;
},
parentNode: function () {
return this.walker.parentNode();
},
nextNode: function () {
let node = this.walker.currentNode;
if (!node) {
return null;
}
let nextNode = this.walker.nextNode();
while (nextNode && this.isSkippedNode(nextNode)) {
nextNode = this.walker.nextNode();
}
return nextNode;
},
firstChild: function () {
let node = this.walker.currentNode;
if (!node) {
return null;
}
let firstChild = this.walker.firstChild();
while (firstChild && this.isSkippedNode(firstChild)) {
firstChild = this.walker.nextSibling();
}
return firstChild;
},
lastChild: function () {
let node = this.walker.currentNode;
if (!node) {
return null;
}
let lastChild = this.walker.lastChild();
while (lastChild && this.isSkippedNode(lastChild)) {
lastChild = this.walker.previousSibling();
}
return lastChild;
},
previousSibling: function () {
let node = this.walker.previousSibling();
while (node && this.isSkippedNode(node)) {
node = this.walker.previousSibling();
}
return node;
},
nextSibling: function () {
let node = this.walker.nextSibling();
while (node && this.isSkippedNode(node)) {
node = this.walker.nextSibling();
}
return node;
},
getStartingNode: function (node, skipTo) {
// Keep a reference on the starting node in case we can't find a node compatible with
// the filter.
let startingNode = node;
if (skipTo === SKIP_TO_PARENT) {
while (node && this.isSkippedNode(node)) {
node = node.parentNode;
}
} else if (skipTo === SKIP_TO_SIBLING) {
node = this.getClosestAcceptedSibling(node);
}
return node || startingNode;
},
/**
* Loop on all of the provided node siblings until finding one that is compliant with
* the filter function.
*/
getClosestAcceptedSibling: function (node) {
if (this.filter(node) === nodeFilterConstants.FILTER_ACCEPT) {
// node is already valid, return immediately.
return node;
}
// Loop on starting node siblings.
let previous = node;
let next = node;
while (previous || next) {
previous = previous && previous.previousSibling;
next = next && next.nextSibling;
if (previous && this.filter(previous) === nodeFilterConstants.FILTER_ACCEPT) {
// A valid node was found in the previous siblings of the node.
return previous;
}
if (next && this.filter(next) === nodeFilterConstants.FILTER_ACCEPT) {
// A valid node was found in the next siblings of the node.
return next;
}
}
return null;
},
isSkippedNode: function (node) {
return this.filter(node) === nodeFilterConstants.FILTER_SKIP;
},
};
exports.DocumentWalker = DocumentWalker;
exports.SKIP_TO_PARENT = SKIP_TO_PARENT;
exports.SKIP_TO_SIBLING = SKIP_TO_SIBLING;

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -5,7 +5,11 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
DevToolsModules(
'inspector.js',
'document-walker.js',
'inspector.js',
'node-actor.js',
'utils.js',
'walker-actor.js',
)
with Files('**'):

Просмотреть файл

@ -0,0 +1,697 @@
/* 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, Cu} = require("chrome");
const protocol = require("devtools/shared/protocol");
const {nodeSpec, nodeListSpec} = require("devtools/shared/specs/node");
const InspectorUtils = require("InspectorUtils");
loader.lazyRequireGetter(this, "colorUtils", "devtools/shared/css/color", true);
loader.lazyRequireGetter(this, "getCssPath", "devtools/shared/inspector/css-logic", true);
loader.lazyRequireGetter(this, "getXPath", "devtools/shared/inspector/css-logic", true);
loader.lazyRequireGetter(this, "findCssSelector", "devtools/shared/inspector/css-logic", true);
loader.lazyRequireGetter(this, "isNativeAnonymous", "devtools/shared/layout/utils", true);
loader.lazyRequireGetter(this, "isXBLAnonymous", "devtools/shared/layout/utils", true);
loader.lazyRequireGetter(this, "isShadowAnonymous", "devtools/shared/layout/utils", true);
loader.lazyRequireGetter(this, "isAnonymous", "devtools/shared/layout/utils", true);
loader.lazyRequireGetter(this, "InspectorActorUtils", "devtools/server/actors/inspector/utils");
loader.lazyRequireGetter(this, "LongStringActor", "devtools/server/actors/string", true);
loader.lazyRequireGetter(this, "getFontPreviewData", "devtools/server/actors/styles", true);
loader.lazyRequireGetter(this, "CssLogic", "devtools/server/css-logic", true);
loader.lazyRequireGetter(this, "EventParsers", "devtools/server/event-parsers", true);
const EventEmitter = require("devtools/shared/event-emitter");
const PSEUDO_CLASSES = [":hover", ":active", ":focus"];
const FONT_FAMILY_PREVIEW_TEXT = "The quick brown fox jumps over the lazy dog";
const FONT_FAMILY_PREVIEW_TEXT_SIZE = 20;
/**
* Server side of the node actor.
*/
const NodeActor = protocol.ActorClassWithSpec(nodeSpec, {
initialize: function (walker, node) {
protocol.Actor.prototype.initialize.call(this, null);
this.walker = walker;
this.rawNode = node;
this._eventParsers = new EventParsers().parsers;
// Storing the original display of the node, to track changes when reflows
// occur
this.wasDisplayed = this.isDisplayed;
},
toString: function () {
return "[NodeActor " + this.actorID + " for " +
this.rawNode.toString() + "]";
},
/**
* Instead of storing a connection object, the NodeActor gets its connection
* from its associated walker.
*/
get conn() {
return this.walker.conn;
},
isDocumentElement: function () {
return this.rawNode.ownerDocument &&
this.rawNode.ownerDocument.documentElement === this.rawNode;
},
destroy: function () {
protocol.Actor.prototype.destroy.call(this);
if (this.mutationObserver) {
if (!Cu.isDeadWrapper(this.mutationObserver)) {
this.mutationObserver.disconnect();
}
this.mutationObserver = null;
}
this.rawNode = null;
this.walker = null;
},
// Returns the JSON representation of this object over the wire.
form: function (detail) {
if (detail === "actorid") {
return this.actorID;
}
let parentNode = this.walker.parentNode(this);
let inlineTextChild = this.walker.inlineTextChild(this);
let form = {
actor: this.actorID,
baseURI: this.rawNode.baseURI,
parent: parentNode ? parentNode.actorID : undefined,
nodeType: this.rawNode.nodeType,
namespaceURI: this.rawNode.namespaceURI,
nodeName: this.rawNode.nodeName,
nodeValue: this.rawNode.nodeValue,
displayName: InspectorActorUtils.getNodeDisplayName(this.rawNode),
numChildren: this.numChildren,
inlineTextChild: inlineTextChild ? inlineTextChild.form() : undefined,
// doctype attributes
name: this.rawNode.name,
publicId: this.rawNode.publicId,
systemId: this.rawNode.systemId,
attrs: this.writeAttrs(),
isBeforePseudoElement: this.isBeforePseudoElement,
isAfterPseudoElement: this.isAfterPseudoElement,
isAnonymous: isAnonymous(this.rawNode),
isNativeAnonymous: isNativeAnonymous(this.rawNode),
isXBLAnonymous: isXBLAnonymous(this.rawNode),
isShadowAnonymous: isShadowAnonymous(this.rawNode),
pseudoClassLocks: this.writePseudoClassLocks(),
isDisplayed: this.isDisplayed,
isInHTMLDocument: this.rawNode.ownerDocument &&
this.rawNode.ownerDocument.contentType === "text/html",
hasEventListeners: this._hasEventListeners,
};
if (this.isDocumentElement()) {
form.isDocumentElement = true;
}
// Add an extra API for custom properties added by other
// modules/extensions.
form.setFormProperty = (name, value) => {
if (!form.props) {
form.props = {};
}
form.props[name] = value;
};
// Fire an event so, other modules can create its own properties
// that should be passed to the client (within the form.props field).
EventEmitter.emit(NodeActor, "form", {
target: this,
data: form
});
return form;
},
/**
* Watch the given document node for mutations using the DOM observer
* API.
*/
watchDocument: function (callback) {
let node = this.rawNode;
// Create the observer on the node's actor. The node will make sure
// the observer is cleaned up when the actor is released.
let observer = new node.defaultView.MutationObserver(callback);
observer.mergeAttributeRecords = true;
observer.observe(node, {
nativeAnonymousChildList: true,
attributes: true,
characterData: true,
characterDataOldValue: true,
childList: true,
subtree: true
});
this.mutationObserver = observer;
},
get isBeforePseudoElement() {
return this.rawNode.nodeName === "_moz_generated_content_before";
},
get isAfterPseudoElement() {
return this.rawNode.nodeName === "_moz_generated_content_after";
},
// Estimate the number of children that the walker will return without making
// a call to children() if possible.
get numChildren() {
// For pseudo elements, childNodes.length returns 1, but the walker
// will return 0.
if (this.isBeforePseudoElement || this.isAfterPseudoElement) {
return 0;
}
let rawNode = this.rawNode;
let numChildren = rawNode.childNodes.length;
let hasAnonChildren = rawNode.nodeType === Ci.nsIDOMNode.ELEMENT_NODE &&
rawNode.ownerDocument.getAnonymousNodes(rawNode);
let hasContentDocument = rawNode.contentDocument;
let hasSVGDocument = rawNode.getSVGDocument && rawNode.getSVGDocument();
if (numChildren === 0 && (hasContentDocument || hasSVGDocument)) {
// This might be an iframe with virtual children.
numChildren = 1;
}
// Normal counting misses ::before/::after. Also, some anonymous children
// may ultimately be skipped, so we have to consult with the walker.
if (numChildren === 0 || hasAnonChildren) {
numChildren = this.walker.children(this).nodes.length;
}
return numChildren;
},
get computedStyle() {
return CssLogic.getComputedStyle(this.rawNode);
},
/**
* Is the node's display computed style value other than "none"
*/
get isDisplayed() {
// Consider all non-element nodes as displayed.
if (InspectorActorUtils.isNodeDead(this) ||
this.rawNode.nodeType !== Ci.nsIDOMNode.ELEMENT_NODE ||
this.isAfterPseudoElement ||
this.isBeforePseudoElement) {
return true;
}
let style = this.computedStyle;
if (!style) {
return true;
}
return style.display !== "none";
},
/**
* Are there event listeners that are listening on this node? This method
* uses all parsers registered via event-parsers.js.registerEventParser() to
* check if there are any event listeners.
*/
get _hasEventListeners() {
let parsers = this._eventParsers;
for (let [, {hasListeners}] of parsers) {
try {
if (hasListeners && hasListeners(this.rawNode)) {
return true;
}
} catch (e) {
// An object attached to the node looked like a listener but wasn't...
// do nothing.
}
}
return false;
},
writeAttrs: function () {
if (!this.rawNode.attributes) {
return undefined;
}
return [...this.rawNode.attributes].map(attr => {
return {namespace: attr.namespace, name: attr.name, value: attr.value };
});
},
writePseudoClassLocks: function () {
if (this.rawNode.nodeType !== Ci.nsIDOMNode.ELEMENT_NODE) {
return undefined;
}
let ret = undefined;
for (let pseudo of PSEUDO_CLASSES) {
if (InspectorUtils.hasPseudoClassLock(this.rawNode, pseudo)) {
ret = ret || [];
ret.push(pseudo);
}
}
return ret;
},
/**
* Gets event listeners and adds their information to the events array.
*
* @param {Node} node
* Node for which we are to get listeners.
*/
getEventListeners: function (node) {
let parsers = this._eventParsers;
let dbg = this.parent().tabActor.makeDebugger();
let listenerArray = [];
for (let [, {getListeners, normalizeListener}] of parsers) {
try {
let listeners = getListeners(node);
if (!listeners) {
continue;
}
for (let listener of listeners) {
if (normalizeListener) {
listener.normalizeListener = normalizeListener;
}
this.processHandlerForEvent(node, listenerArray, dbg, listener);
}
} catch (e) {
// An object attached to the node looked like a listener but wasn't...
// do nothing.
}
}
listenerArray.sort((a, b) => {
return a.type.localeCompare(b.type);
});
return listenerArray;
},
/**
* Process a handler
*
* @param {Node} node
* The node for which we want information.
* @param {Array} listenerArray
* listenerArray contains all event objects that we have gathered
* so far.
* @param {Debugger} dbg
* JSDebugger instance.
* @param {Object} eventInfo
* See event-parsers.js.registerEventParser() for a description of the
* eventInfo object.
*
* @return {Array}
* An array of objects where a typical object looks like this:
* {
* type: "click",
* handler: function() { doSomething() },
* origin: "http://www.mozilla.com",
* searchString: 'onclick="doSomething()"',
* tags: tags,
* DOM0: true,
* capturing: true,
* hide: {
* DOM0: true
* },
* native: false
* }
*/
processHandlerForEvent: function (node, listenerArray, dbg, listener) {
let { handler } = listener;
let global = Cu.getGlobalForObject(handler);
let globalDO = dbg.addDebuggee(global);
let listenerDO = globalDO.makeDebuggeeValue(handler);
let { normalizeListener } = listener;
if (normalizeListener) {
listenerDO = normalizeListener(listenerDO, listener);
}
let { capturing } = listener;
let dom0 = false;
let functionSource = handler.toString();
let hide = listener.hide || {};
let line = 0;
let native = false;
let override = listener.override || {};
let tags = listener.tags || "";
let type = listener.type || "";
let url = "";
// If the listener is an object with a 'handleEvent' method, use that.
if (listenerDO.class === "Object" || listenerDO.class === "XULElement") {
let desc;
while (!desc && listenerDO) {
desc = listenerDO.getOwnPropertyDescriptor("handleEvent");
listenerDO = listenerDO.proto;
}
if (desc && desc.value) {
listenerDO = desc.value;
}
}
// If the listener is bound to a different context then we need to switch
// to the bound function.
if (listenerDO.isBoundFunction) {
listenerDO = listenerDO.boundTargetFunction;
}
let { isArrowFunction, name, script, parameterNames } = listenerDO;
if (script) {
let scriptSource = script.source.text;
// Scripts are provided via script tags. If it wasn't provided by a
// script tag it must be a DOM0 event.
if (script.source.element) {
dom0 = script.source.element.class !== "HTMLScriptElement";
} else {
dom0 = false;
}
line = script.startLine;
url = script.url;
// Checking for the string "[native code]" is the only way at this point
// to check for native code. Even if this provides a false positive then
// grabbing the source code a second time is harmless.
if (functionSource === "[object Object]" ||
functionSource === "[object XULElement]" ||
functionSource.includes("[native code]")) {
functionSource =
scriptSource.substr(script.sourceStart, script.sourceLength);
// At this point the script looks like this:
// () { ... }
// We prefix this with "function" if it is not a fat arrow function.
if (!isArrowFunction) {
functionSource = "function " + functionSource;
}
}
} else {
// If the listener is a native one (provided by C++ code) then we have no
// access to the script. We use the native flag to prevent showing the
// debugger button because the script is not available.
native = true;
}
// Fat arrow function text always contains the parameters. Function
// parameters are often missing e.g. if Array.sort is used as a handler.
// If they are missing we provide the parameters ourselves.
if (parameterNames && parameterNames.length > 0) {
let prefix = "function " + name + "()";
let paramString = parameterNames.join(", ");
if (functionSource.startsWith(prefix)) {
functionSource = functionSource.substr(prefix.length);
functionSource = `function ${name} (${paramString})${functionSource}`;
}
}
// If the listener is native code we display the filename "[native code]."
// This is the official string and should *not* be translated.
let origin;
if (native) {
origin = "[native code]";
} else {
origin = url + ((dom0 || line === 0) ? "" : ":" + line);
}
let eventObj = {
type: override.type || type,
handler: override.handler || functionSource.trim(),
origin: override.origin || origin,
tags: override.tags || tags,
DOM0: typeof override.dom0 !== "undefined" ? override.dom0 : dom0,
capturing: typeof override.capturing !== "undefined" ?
override.capturing : capturing,
hide: typeof override.hide !== "undefined" ? override.hide : hide,
native
};
// Hide the debugger icon for DOM0 and native listeners. DOM0 listeners are
// generated dynamically from e.g. an onclick="" attribute so the script
// doesn't actually exist.
if (native || dom0) {
eventObj.hide.debugger = true;
}
listenerArray.push(eventObj);
dbg.removeDebuggee(globalDO);
},
/**
* Returns a LongStringActor with the node's value.
*/
getNodeValue: function () {
return new LongStringActor(this.conn, this.rawNode.nodeValue || "");
},
/**
* Set the node's value to a given string.
*/
setNodeValue: function (value) {
this.rawNode.nodeValue = value;
},
/**
* Get a unique selector string for this node.
*/
getUniqueSelector: function () {
if (Cu.isDeadWrapper(this.rawNode)) {
return "";
}
return findCssSelector(this.rawNode);
},
/**
* Get the full CSS path for this node.
*
* @return {String} A CSS selector with a part for the node and each of its ancestors.
*/
getCssPath: function () {
if (Cu.isDeadWrapper(this.rawNode)) {
return "";
}
return getCssPath(this.rawNode);
},
/**
* Get the XPath for this node.
*
* @return {String} The XPath for finding this node on the page.
*/
getXPath: function () {
if (Cu.isDeadWrapper(this.rawNode)) {
return "";
}
return getXPath(this.rawNode);
},
/**
* Scroll the selected node into view.
*/
scrollIntoView: function () {
this.rawNode.scrollIntoView(true);
},
/**
* Get the node's image data if any (for canvas and img nodes).
* Returns an imageData object with the actual data being a LongStringActor
* and a size json object.
* The image data is transmitted as a base64 encoded png data-uri.
* The method rejects if the node isn't an image or if the image is missing
*
* Accepts a maxDim request parameter to resize images that are larger. This
* is important as the resizing occurs server-side so that image-data being
* transfered in the longstring back to the client will be that much smaller
*/
getImageData: function (maxDim) {
return InspectorActorUtils.imageToImageData(this.rawNode, maxDim).then(imageData => {
return {
data: LongStringActor(this.conn, imageData.data),
size: imageData.size
};
});
},
/**
* Get all event listeners that are listening on this node.
*/
getEventListenerInfo: function () {
let node = this.rawNode;
if (this.rawNode.nodeName.toLowerCase() === "html") {
let winListeners = this.getEventListeners(node.ownerGlobal) || [];
let docElementListeners = this.getEventListeners(node) || [];
let docListeners = this.getEventListeners(node.parentNode) || [];
return [...winListeners, ...docElementListeners, ...docListeners];
}
return this.getEventListeners(node);
},
/**
* Modify a node's attributes. Passed an array of modifications
* similar in format to "attributes" mutations.
* {
* attributeName: <string>
* attributeNamespace: <optional string>
* newValue: <optional string> - If null or undefined, the attribute
* will be removed.
* }
*
* Returns when the modifications have been made. Mutations will
* be queued for any changes made.
*/
modifyAttributes: function (modifications) {
let rawNode = this.rawNode;
for (let change of modifications) {
if (change.newValue == null) {
if (change.attributeNamespace) {
rawNode.removeAttributeNS(change.attributeNamespace,
change.attributeName);
} else {
rawNode.removeAttribute(change.attributeName);
}
} else if (change.attributeNamespace) {
rawNode.setAttributeNS(change.attributeNamespace, change.attributeName,
change.newValue);
} else {
rawNode.setAttribute(change.attributeName, change.newValue);
}
}
},
/**
* Given the font and fill style, get the image data of a canvas with the
* preview text and font.
* Returns an imageData object with the actual data being a LongStringActor
* and the width of the text as a string.
* The image data is transmitted as a base64 encoded png data-uri.
*/
getFontFamilyDataURL: function (font, fillStyle = "black") {
let doc = this.rawNode.ownerDocument;
let options = {
previewText: FONT_FAMILY_PREVIEW_TEXT,
previewFontSize: FONT_FAMILY_PREVIEW_TEXT_SIZE,
fillStyle: fillStyle
};
let { dataURL, size } = getFontPreviewData(font, doc, options);
return { data: LongStringActor(this.conn, dataURL), size: size };
},
/**
* Finds the computed background color of the closest parent with
* a set background color.
* Returns a string with the background color of the form
* rgba(r, g, b, a). Defaults to rgba(255, 255, 255, 1) if no
* background color is found.
*/
getClosestBackgroundColor: function () {
let current = this.rawNode;
while (current) {
let computedStyle = CssLogic.getComputedStyle(current);
let currentStyle = computedStyle.getPropertyValue("background-color");
if (colorUtils.isValidCSSColor(currentStyle)) {
let currentCssColor = new colorUtils.CssColor(currentStyle);
if (!currentCssColor.isTransparent()) {
return currentCssColor.rgba;
}
}
current = current.parentNode;
}
return "rgba(255, 255, 255, 1)";
}
});
/**
* Server side of a node list as returned by querySelectorAll()
*/
const NodeListActor = protocol.ActorClassWithSpec(nodeListSpec, {
typeName: "domnodelist",
initialize: function (walker, nodeList) {
protocol.Actor.prototype.initialize.call(this);
this.walker = walker;
this.nodeList = nodeList || [];
},
destroy: function () {
protocol.Actor.prototype.destroy.call(this);
},
/**
* Instead of storing a connection object, the NodeActor gets its connection
* from its associated walker.
*/
get conn() {
return this.walker.conn;
},
/**
* Items returned by this actor should belong to the parent walker.
*/
marshallPool: function () {
return this.walker;
},
// Returns the JSON representation of this object over the wire.
form: function () {
return {
actor: this.actorID,
length: this.nodeList ? this.nodeList.length : 0
};
},
/**
* Get a single node from the node list.
*/
item: function (index) {
return this.walker.attachElement(this.nodeList[index]);
},
/**
* Get a range of the items from the node list.
*/
items: function (start = 0, end = this.nodeList.length) {
let items = Array.prototype.slice.call(this.nodeList, start, end)
.map(item => this.walker._ref(item));
return this.walker.attachElements(items);
},
release: function () {}
});
exports.NodeActor = NodeActor;
exports.NodeListActor = NodeListActor;

Просмотреть файл

@ -0,0 +1,255 @@
/* 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, Cu} = require("chrome");
const promise = require("promise");
const {Task} = require("devtools/shared/task");
loader.lazyRequireGetter(this, "AsyncUtils", "devtools/shared/async-utils");
loader.lazyRequireGetter(this, "flags", "devtools/shared/flags");
loader.lazyRequireGetter(this, "DevToolsUtils", "devtools/shared/DevToolsUtils");
loader.lazyRequireGetter(this, "nodeFilterConstants", "devtools/shared/dom-node-filter-constants");
loader.lazyRequireGetter(this, "isNativeAnonymous", "devtools/shared/layout/utils", true);
loader.lazyRequireGetter(this, "isXBLAnonymous", "devtools/shared/layout/utils", true);
const XHTML_NS = "http://www.w3.org/1999/xhtml";
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
const IMAGE_FETCHING_TIMEOUT = 500;
/**
* Returns the properly cased version of the node's tag name, which can be
* used when displaying said name in the UI.
*
* @param {Node} rawNode
* Node for which we want the display name
* @return {String}
* Properly cased version of the node tag name
*/
const getNodeDisplayName = function (rawNode) {
if (rawNode.nodeName && !rawNode.localName) {
// The localName & prefix APIs have been moved from the Node interface to the Element
// interface. Use Node.nodeName as a fallback.
return rawNode.nodeName;
}
return (rawNode.prefix ? rawNode.prefix + ":" : "") + rawNode.localName;
};
function nodeDocument(node) {
if (Cu.isDeadWrapper(node)) {
return null;
}
return node.ownerDocument ||
(node.nodeType == Ci.nsIDOMNode.DOCUMENT_NODE ? node : null);
}
function isNodeDead(node) {
return !node || !node.rawNode || Cu.isDeadWrapper(node.rawNode);
}
function isInXULDocument(el) {
let doc = nodeDocument(el);
return doc &&
doc.documentElement &&
doc.documentElement.namespaceURI === XUL_NS;
}
/**
* This DeepTreeWalker filter skips whitespace text nodes and anonymous
* content with the exception of ::before and ::after and anonymous content
* in XUL document (needed to show all elements in the browser toolbox).
*/
function standardTreeWalkerFilter(node) {
// ::before and ::after are native anonymous content, but we always
// want to show them
if (node.nodeName === "_moz_generated_content_before" ||
node.nodeName === "_moz_generated_content_after") {
return nodeFilterConstants.FILTER_ACCEPT;
}
// Ignore empty whitespace text nodes that do not impact the layout.
if (isWhitespaceTextNode(node)) {
return nodeHasSize(node)
? nodeFilterConstants.FILTER_ACCEPT
: nodeFilterConstants.FILTER_SKIP;
}
// Ignore all native and XBL anonymous content inside a non-XUL document.
// We need to do this to skip things like form controls, scrollbars,
// video controls, etc (see bug 1187482).
if (!isInXULDocument(node) && (isXBLAnonymous(node) ||
isNativeAnonymous(node))) {
return nodeFilterConstants.FILTER_SKIP;
}
return nodeFilterConstants.FILTER_ACCEPT;
}
/**
* This DeepTreeWalker filter is like standardTreeWalkerFilter except that
* it also includes all anonymous content (like internal form controls).
*/
function allAnonymousContentTreeWalkerFilter(node) {
// Ignore empty whitespace text nodes that do not impact the layout.
if (isWhitespaceTextNode(node)) {
return nodeHasSize(node)
? nodeFilterConstants.FILTER_ACCEPT
: nodeFilterConstants.FILTER_SKIP;
}
return nodeFilterConstants.FILTER_ACCEPT;
}
/**
* Is the given node a text node composed of whitespace only?
* @param {DOMNode} node
* @return {Boolean}
*/
function isWhitespaceTextNode(node) {
return node.nodeType == Ci.nsIDOMNode.TEXT_NODE && !/[^\s]/.exec(node.nodeValue);
}
/**
* Does the given node have non-0 width and height?
* @param {DOMNode} node
* @return {Boolean}
*/
function nodeHasSize(node) {
if (!node.getBoxQuads) {
return false;
}
let quads = node.getBoxQuads();
return quads.length && quads.some(quad => quad.bounds.width && quad.bounds.height);
}
/**
* Returns a promise that is settled once the given HTMLImageElement has
* finished loading.
*
* @param {HTMLImageElement} image - The image element.
* @param {Number} timeout - Maximum amount of time the image is allowed to load
* before the waiting is aborted. Ignored if flags.testing is set.
*
* @return {Promise} that is fulfilled once the image has loaded. If the image
* fails to load or the load takes too long, the promise is rejected.
*/
function ensureImageLoaded(image, timeout) {
let { HTMLImageElement } = image.ownerGlobal;
if (!(image instanceof HTMLImageElement)) {
return promise.reject("image must be an HTMLImageELement");
}
if (image.complete) {
// The image has already finished loading.
return promise.resolve();
}
// This image is still loading.
let onLoad = AsyncUtils.listenOnce(image, "load");
// Reject if loading fails.
let onError = AsyncUtils.listenOnce(image, "error").then(() => {
return promise.reject("Image '" + image.src + "' failed to load.");
});
// Don't timeout when testing. This is never settled.
let onAbort = new Promise(() => {});
if (!flags.testing) {
// Tests are not running. Reject the promise after given timeout.
onAbort = DevToolsUtils.waitForTime(timeout).then(() => {
return promise.reject("Image '" + image.src + "' took too long to load.");
});
}
// See which happens first.
return promise.race([onLoad, onError, onAbort]);
}
/**
* Given an <img> or <canvas> element, return the image data-uri. If @param node
* is an <img> element, the method waits a while for the image to load before
* the data is generated. If the image does not finish loading in a reasonable
* time (IMAGE_FETCHING_TIMEOUT milliseconds) the process aborts.
*
* @param {HTMLImageElement|HTMLCanvasElement} node - The <img> or <canvas>
* element, or Image() object. Other types cause the method to reject.
* @param {Number} maxDim - Optionally pass a maximum size you want the longest
* side of the image to be resized to before getting the image data.
* @return {Promise} A promise that is fulfilled with an object containing the
* data-uri and size-related information:
* { data: "...",
* size: {
* naturalWidth: 400,
* naturalHeight: 300,
* resized: true }
* }.
*
* If something goes wrong, the promise is rejected.
*/
const imageToImageData = Task.async(function* (node, maxDim) {
let { HTMLCanvasElement, HTMLImageElement } = node.ownerGlobal;
let isImg = node instanceof HTMLImageElement;
let isCanvas = node instanceof HTMLCanvasElement;
if (!isImg && !isCanvas) {
throw new Error("node is not a <canvas> or <img> element.");
}
if (isImg) {
// Ensure that the image is ready.
yield ensureImageLoaded(node, IMAGE_FETCHING_TIMEOUT);
}
// Get the image resize ratio if a maxDim was provided
let resizeRatio = 1;
let imgWidth = node.naturalWidth || node.width;
let imgHeight = node.naturalHeight || node.height;
let imgMax = Math.max(imgWidth, imgHeight);
if (maxDim && imgMax > maxDim) {
resizeRatio = maxDim / imgMax;
}
// Extract the image data
let imageData;
// The image may already be a data-uri, in which case, save ourselves the
// trouble of converting via the canvas.drawImage.toDataURL method, but only
// if the image doesn't need resizing
if (isImg && node.src.startsWith("data:") && resizeRatio === 1) {
imageData = node.src;
} else {
// Create a canvas to copy the rawNode into and get the imageData from
let canvas = node.ownerDocument.createElementNS(XHTML_NS, "canvas");
canvas.width = imgWidth * resizeRatio;
canvas.height = imgHeight * resizeRatio;
let ctx = canvas.getContext("2d");
// Copy the rawNode image or canvas in the new canvas and extract data
ctx.drawImage(node, 0, 0, canvas.width, canvas.height);
imageData = canvas.toDataURL("image/png");
}
return {
data: imageData,
size: {
naturalWidth: imgWidth,
naturalHeight: imgHeight,
resized: resizeRatio !== 1
}
};
});
module.exports = {
allAnonymousContentTreeWalkerFilter,
getNodeDisplayName,
imageToImageData,
isNodeDead,
nodeDocument,
standardTreeWalkerFilter,
};

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -38,7 +38,6 @@ DevToolsModules(
'heap-snapshot-file.js',
'highlighters.css',
'highlighters.js',
'inspector.js',
'layout.js',
'memory.js',
'monitor.js',

Просмотреть файл

@ -11,7 +11,7 @@ const {Ci, Cu, Cr} = require("chrome");
const {DebuggerServer} = require("devtools/server/main");
const Services = require("Services");
loader.lazyGetter(this, "NodeActor", () => require("devtools/server/actors/inspector/inspector").NodeActor, true);
loader.lazyGetter(this, "NodeActor", () => require("devtools/server/actors/inspector/node-actor").NodeActor, true);
const {
XPCOMUtils,

Просмотреть файл

@ -15,7 +15,7 @@ const Services = require("Services");
// promise is still used in tests using this helper
const promise = require("promise"); // eslint-disable-line no-unused-vars
const defer = require("devtools/shared/defer");
const {_documentWalker} = require("devtools/server/actors/inspector/inspector");
const {DocumentWalker: _documentWalker} = require("devtools/server/actors/inspector/document-walker");
// Always log packets when running tests.
Services.prefs.setBoolPref("devtools.debugger.log", true);

Просмотреть файл

@ -16,8 +16,9 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=777674
window.onload = function () {
const {InspectorFront} =
require("devtools/shared/fronts/inspector");
const {_documentWalker} =
require("devtools/server/actors/inspector/inspector");
const {DocumentWalker: _documentWalker} =
require("devtools/server/actors/inspector/document-walker");
const nodeFilterConstants =
require("devtools/shared/dom-node-filter-constants");
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";

Просмотреть файл

@ -13,7 +13,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=
<script type="application/javascript">
"use strict";
const inspector = require("devtools/server/actors/inspector/inspector");
const {DocumentWalker} = require("devtools/server/actors/inspector/document-walker");
window.onload = function () {
SimpleTest.waitForExplicitFinish();
@ -54,7 +54,7 @@ addAsyncTest(function* testRearrange() {
let nextNode = children.nodes[13];
yield gWalker.insertBefore(nodeA, longlist, nextNode);
let sibling =
new inspector._documentWalker(gInspectee.querySelector("#a"), window).nextSibling();
new DocumentWalker(gInspectee.querySelector("#a"), window).nextSibling();
is(sibling, nextNode.rawNode(), "Node should match the expected next node.");
children = yield gWalker.children(longlist);
is(nodeA, children.nodes[13], "a should be where we expect it.");

Просмотреть файл

@ -13,7 +13,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=
<script type="application/javascript">
"use strict";
const inspector = require("devtools/server/actors/inspector/inspector");
const WalkerActor = require("devtools/server/actors/inspector/walker-actor");
window.onload = function () {
SimpleTest.waitForExplicitFinish();
@ -21,9 +21,9 @@ window.onload = function () {
};
const testSummaryLength = 10;
inspector.setValueSummaryLength(testSummaryLength);
WalkerActor.setValueSummaryLength(testSummaryLength);
SimpleTest.registerCleanupFunction(function () {
inspector.setValueSummaryLength(inspector.DEFAULT_VALUE_SUMMARY_LENGTH);
WalkerActor.setValueSummaryLength(WalkerActor.DEFAULT_VALUE_SUMMARY_LENGTH);
});
let gInspectee = null;

Просмотреть файл

@ -284,11 +284,11 @@ addTest(function testFrameTraversal() {
addTest(function testLongValue() {
const testSummaryLength = 10;
const inspector = require("devtools/server/actors/inspector/inspector");
const WalkerActor = require("devtools/server/actors/inspector/walker-actor");
inspector.setValueSummaryLength(testSummaryLength);
WalkerActor.setValueSummaryLength(testSummaryLength);
SimpleTest.registerCleanupFunction(function () {
inspector.setValueSummaryLength(inspector.DEFAULT_VALUE_SUMMARY_LENGTH);
WalkerActor.setValueSummaryLength(WalkerActor.DEFAULT_VALUE_SUMMARY_LENGTH);
});
let longstringText = gInspectee.getElementById("longstring").firstChild.nodeValue;

Просмотреть файл

@ -6,7 +6,7 @@
// Test that a NodeListActor initialized with null nodelist doesn't cause
// exceptions when calling NodeListActor.form.
const { NodeListActor } = require("devtools/server/actors/inspector/inspector");
const { NodeListActor } = require("devtools/server/actors/inspector/node-actor");
function run_test() {
check_actor_for_list(null);