blob: f6a6bcd542c106bc7f0568965ae202a45618f488 [file] [log] [blame]
/*
* Copyright (C) 2009, 2010 Google Inc. All rights reserved.
* Copyright (C) 2009 Joseph Pecoraro
* Copyright (C) 2013 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
WebInspector.DOMTreeManager = class DOMTreeManager extends WebInspector.Object
{
constructor()
{
super();
this._idToDOMNode = {};
this._document = null;
this._attributeLoadNodeIds = {};
this._flows = new Map;
this._contentNodesToFlowsMap = new Map;
this._restoreSelectedNodeIsAllowed = true;
this._loadNodeAttributesTimeout = 0;
WebInspector.Frame.addEventListener(WebInspector.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this);
}
// Static
static _flowPayloadHashKey(flowPayload)
{
// Use the flow node id, to avoid collisions when we change main document id.
return flowPayload.documentNodeId + ":" + flowPayload.name;
}
// Public
requestDocument(callback)
{
if (this._document) {
callback(this._document);
return;
}
if (this._pendingDocumentRequestCallbacks) {
this._pendingDocumentRequestCallbacks.push(callback);
return;
}
this._pendingDocumentRequestCallbacks = [callback];
function onDocumentAvailable(error, root)
{
if (!error)
this._setDocument(root);
for (let callback of this._pendingDocumentRequestCallbacks)
callback(this._document);
this._pendingDocumentRequestCallbacks = null;
}
DOMAgent.getDocument(onDocumentAvailable.bind(this));
}
pushNodeToFrontend(objectId, callback)
{
this._dispatchWhenDocumentAvailable(DOMAgent.requestNode.bind(DOMAgent, objectId), callback);
}
pushNodeByPathToFrontend(path, callback)
{
this._dispatchWhenDocumentAvailable(DOMAgent.pushNodeByPathToFrontend.bind(DOMAgent, path), callback);
}
// Private
_wrapClientCallback(callback)
{
if (!callback)
return null;
return function(error, result) {
if (error)
console.error("Error during DOMAgent operation: " + error);
callback(error ? null : result);
};
}
_dispatchWhenDocumentAvailable(func, callback)
{
var callbackWrapper = this._wrapClientCallback(callback);
function onDocumentAvailable()
{
if (this._document)
func(callbackWrapper);
else {
if (callbackWrapper)
callbackWrapper("No document");
}
}
this.requestDocument(onDocumentAvailable.bind(this));
}
_attributeModified(nodeId, name, value)
{
var node = this._idToDOMNode[nodeId];
if (!node)
return;
node._setAttribute(name, value);
this.dispatchEventToListeners(WebInspector.DOMTreeManager.Event.AttributeModified, {node, name});
node.dispatchEventToListeners(WebInspector.DOMNode.Event.AttributeModified, {name});
}
_attributeRemoved(nodeId, name)
{
var node = this._idToDOMNode[nodeId];
if (!node)
return;
node._removeAttribute(name);
this.dispatchEventToListeners(WebInspector.DOMTreeManager.Event.AttributeRemoved, {node, name});
node.dispatchEventToListeners(WebInspector.DOMNode.Event.AttributeRemoved, {name});
}
_inlineStyleInvalidated(nodeIds)
{
for (var nodeId of nodeIds)
this._attributeLoadNodeIds[nodeId] = true;
if (this._loadNodeAttributesTimeout)
return;
this._loadNodeAttributesTimeout = setTimeout(this._loadNodeAttributes.bind(this), 0);
}
_loadNodeAttributes()
{
function callback(nodeId, error, attributes)
{
if (error) {
console.error("Error during DOMAgent operation: " + error);
return;
}
var node = this._idToDOMNode[nodeId];
if (node) {
node._setAttributesPayload(attributes);
this.dispatchEventToListeners(WebInspector.DOMTreeManager.Event.AttributeModified, {node, name: "style"});
node.dispatchEventToListeners(WebInspector.DOMNode.Event.AttributeModified, {name: "style"});
}
}
this._loadNodeAttributesTimeout = 0;
for (var nodeId in this._attributeLoadNodeIds) {
var nodeIdAsNumber = parseInt(nodeId);
DOMAgent.getAttributes(nodeIdAsNumber, callback.bind(this, nodeIdAsNumber));
}
this._attributeLoadNodeIds = {};
}
_characterDataModified(nodeId, newValue)
{
var node = this._idToDOMNode[nodeId];
node._nodeValue = newValue;
this.dispatchEventToListeners(WebInspector.DOMTreeManager.Event.CharacterDataModified, {node});
}
nodeForId(nodeId)
{
return this._idToDOMNode[nodeId];
}
_documentUpdated()
{
this._setDocument(null);
}
_setDocument(payload)
{
this._idToDOMNode = {};
if (payload && "nodeId" in payload)
this._document = new WebInspector.DOMNode(this, null, false, payload);
else
this._document = null;
this.dispatchEventToListeners(WebInspector.DOMTreeManager.Event.DocumentUpdated, this._document);
}
_setDetachedRoot(payload)
{
new WebInspector.DOMNode(this, null, false, payload);
}
_setChildNodes(parentId, payloads)
{
if (!parentId && payloads.length) {
this._setDetachedRoot(payloads[0]);
return;
}
var parent = this._idToDOMNode[parentId];
parent._setChildrenPayload(payloads);
}
_childNodeCountUpdated(nodeId, newValue)
{
var node = this._idToDOMNode[nodeId];
node.childNodeCount = newValue;
this.dispatchEventToListeners(WebInspector.DOMTreeManager.Event.ChildNodeCountUpdated, node);
}
_childNodeInserted(parentId, prevId, payload)
{
var parent = this._idToDOMNode[parentId];
var prev = this._idToDOMNode[prevId];
var node = parent._insertChild(prev, payload);
this._idToDOMNode[node.id] = node;
this.dispatchEventToListeners(WebInspector.DOMTreeManager.Event.NodeInserted, {node, parent});
}
_childNodeRemoved(parentId, nodeId)
{
var parent = this._idToDOMNode[parentId];
var node = this._idToDOMNode[nodeId];
parent._removeChild(node);
this._unbind(node);
this.dispatchEventToListeners(WebInspector.DOMTreeManager.Event.NodeRemoved, {node, parent});
}
_pseudoElementAdded(parentId, pseudoElement)
{
var parent = this._idToDOMNode[parentId];
if (!parent)
return;
var node = new WebInspector.DOMNode(this, parent.ownerDocument, false, pseudoElement);
node.parentNode = parent;
this._idToDOMNode[node.id] = node;
console.assert(!parent.pseudoElements().get(node.pseudoType()));
parent.pseudoElements().set(node.pseudoType(), node);
this.dispatchEventToListeners(WebInspector.DOMTreeManager.Event.NodeInserted, {node, parent});
}
_pseudoElementRemoved(parentId, pseudoElementId)
{
var pseudoElement = this._idToDOMNode[pseudoElementId];
if (!pseudoElement)
return;
var parent = pseudoElement.parentNode;
console.assert(parent);
console.assert(parent.id === parentId);
if (!parent)
return;
parent._removeChild(pseudoElement);
this._unbind(pseudoElement);
this.dispatchEventToListeners(WebInspector.DOMTreeManager.Event.NodeRemoved, {node: pseudoElement, parent});
}
_unbind(node)
{
this._removeContentNodeFromFlowIfNeeded(node);
delete this._idToDOMNode[node.id];
for (let i = 0; node.children && i < node.children.length; ++i)
this._unbind(node.children[i]);
let templateContent = node.templateContent();
if (templateContent)
this._unbind(templateContent);
for (let pseudoElement of node.pseudoElements().values())
this._unbind(pseudoElement);
// FIXME: Handle shadow roots.
}
get restoreSelectedNodeIsAllowed()
{
return this._restoreSelectedNodeIsAllowed;
}
inspectElement(nodeId)
{
var node = this._idToDOMNode[nodeId];
if (!node || !node.ownerDocument)
return;
this.dispatchEventToListeners(WebInspector.DOMTreeManager.Event.DOMNodeWasInspected, {node});
this._inspectModeEnabled = false;
this.dispatchEventToListeners(WebInspector.DOMTreeManager.Event.InspectModeStateChanged);
}
inspectNodeObject(remoteObject)
{
this._restoreSelectedNodeIsAllowed = false;
function nodeAvailable(nodeId)
{
remoteObject.release();
console.assert(nodeId);
if (!nodeId)
return;
this.inspectElement(nodeId);
// Re-resolve the node in the console's object group when adding to the console.
let domNode = this.nodeForId(nodeId);
WebInspector.RemoteObject.resolveNode(domNode, WebInspector.RuntimeManager.ConsoleObjectGroup, function(remoteObject) {
if (!remoteObject)
return;
let specialLogStyles = true;
let shouldRevealConsole = false;
WebInspector.consoleLogViewController.appendImmediateExecutionWithResult(WebInspector.UIString("Selected Element"), remoteObject, specialLogStyles, shouldRevealConsole);
});
}
remoteObject.pushNodeToFrontend(nodeAvailable.bind(this));
}
performSearch(query, searchCallback)
{
this.cancelSearch();
function callback(error, searchId, resultsCount)
{
this._searchId = searchId;
searchCallback(resultsCount);
}
DOMAgent.performSearch(query, callback.bind(this));
}
searchResult(index, callback)
{
function mycallback(error, nodeIds)
{
if (error) {
console.error(error);
callback(null);
return;
}
if (nodeIds.length !== 1)
return;
callback(this._idToDOMNode[nodeIds[0]]);
}
if (this._searchId)
DOMAgent.getSearchResults(this._searchId, index, index + 1, mycallback.bind(this));
else
callback(null);
}
cancelSearch()
{
if (this._searchId) {
DOMAgent.discardSearchResults(this._searchId);
this._searchId = undefined;
}
}
querySelector(nodeId, selectors, callback)
{
DOMAgent.querySelector(nodeId, selectors, this._wrapClientCallback(callback));
}
querySelectorAll(nodeId, selectors, callback)
{
DOMAgent.querySelectorAll(nodeId, selectors, this._wrapClientCallback(callback));
}
highlightDOMNode(nodeId, mode)
{
if (this._hideDOMNodeHighlightTimeout) {
clearTimeout(this._hideDOMNodeHighlightTimeout);
this._hideDOMNodeHighlightTimeout = undefined;
}
this._highlightedDOMNodeId = nodeId;
if (nodeId)
DOMAgent.highlightNode.invoke({nodeId, highlightConfig: this._buildHighlightConfig(mode)});
else
DOMAgent.hideHighlight();
}
highlightSelector(selectorText, frameId, mode)
{
// COMPATIBILITY (iOS 8): DOM.highlightSelector did not exist.
if (!DOMAgent.highlightSelector)
return;
DOMAgent.highlightSelector(this._buildHighlightConfig(mode), selectorText, frameId);
}
highlightRect(rect, usePageCoordinates)
{
DOMAgent.highlightRect.invoke({
x: rect.x,
y: rect.y,
width: rect.width,
height: rect.height,
color: {r: 111, g: 168, b: 220, a: 0.66},
outlineColor: {r: 255, g: 229, b: 153, a: 0.66},
usePageCoordinates
});
}
hideDOMNodeHighlight()
{
this.highlightDOMNode(0);
}
highlightDOMNodeForTwoSeconds(nodeId)
{
this.highlightDOMNode(nodeId);
this._hideDOMNodeHighlightTimeout = setTimeout(this.hideDOMNodeHighlight.bind(this), 2000);
}
get inspectModeEnabled()
{
return this._inspectModeEnabled;
}
set inspectModeEnabled(enabled)
{
if (enabled === this._inspectModeEnabled)
return;
DOMAgent.setInspectModeEnabled(enabled, this._buildHighlightConfig(), (error) => {
this._inspectModeEnabled = error ? false : enabled;
this.dispatchEventToListeners(WebInspector.DOMTreeManager.Event.InspectModeStateChanged);
});
}
_buildHighlightConfig(mode = "all")
{
let highlightConfig = {showInfo: mode === "all"};
if (mode === "all" || mode === "content")
highlightConfig.contentColor = {r: 111, g: 168, b: 220, a: 0.66};
if (mode === "all" || mode === "padding")
highlightConfig.paddingColor = {r: 147, g: 196, b: 125, a: 0.66};
if (mode === "all" || mode === "border")
highlightConfig.borderColor = {r: 255, g: 229, b: 153, a: 0.66};
if (mode === "all" || mode === "margin")
highlightConfig.marginColor = {r: 246, g: 178, b: 107, a: 0.66};
return highlightConfig;
}
_createContentFlowFromPayload(flowPayload)
{
// FIXME: Collect the regions from the payload.
var flow = new WebInspector.ContentFlow(flowPayload.documentNodeId, flowPayload.name, flowPayload.overset, flowPayload.content.map(this.nodeForId.bind(this)));
for (var contentNode of flow.contentNodes) {
console.assert(!this._contentNodesToFlowsMap.has(contentNode.id));
this._contentNodesToFlowsMap.set(contentNode.id, flow);
}
return flow;
}
_updateContentFlowFromPayload(contentFlow, flowPayload)
{
console.assert(contentFlow.contentNodes.length === flowPayload.content.length);
console.assert(contentFlow.contentNodes.every((node, i) => node.id === flowPayload.content[i]));
// FIXME: Collect the regions from the payload.
contentFlow.overset = flowPayload.overset;
}
getNamedFlowCollection(documentNodeIdentifier)
{
function onNamedFlowCollectionAvailable(error, flows)
{
if (error)
return;
this._contentNodesToFlowsMap.clear();
var contentFlows = [];
for (var i = 0; i < flows.length; ++i) {
var flowPayload = flows[i];
var flowKey = WebInspector.DOMTreeManager._flowPayloadHashKey(flowPayload);
var contentFlow = this._flows.get(flowKey);
if (contentFlow)
this._updateContentFlowFromPayload(contentFlow, flowPayload);
else {
contentFlow = this._createContentFlowFromPayload(flowPayload);
this._flows.set(flowKey, contentFlow);
}
contentFlows.push(contentFlow);
}
this.dispatchEventToListeners(WebInspector.DOMTreeManager.Event.ContentFlowListWasUpdated, {documentNodeIdentifier, flows: contentFlows});
}
if (window.CSSAgent)
CSSAgent.getNamedFlowCollection(documentNodeIdentifier, onNamedFlowCollectionAvailable.bind(this));
}
namedFlowCreated(flowPayload)
{
var flowKey = WebInspector.DOMTreeManager._flowPayloadHashKey(flowPayload);
console.assert(!this._flows.has(flowKey));
var contentFlow = this._createContentFlowFromPayload(flowPayload);
this._flows.set(flowKey, contentFlow);
this.dispatchEventToListeners(WebInspector.DOMTreeManager.Event.ContentFlowWasAdded, {flow: contentFlow});
}
namedFlowRemoved(documentNodeIdentifier, flowName)
{
var flowKey = WebInspector.DOMTreeManager._flowPayloadHashKey({documentNodeId: documentNodeIdentifier, name: flowName});
var contentFlow = this._flows.get(flowKey);
console.assert(contentFlow);
this._flows.delete(flowKey);
// Remove any back links to this flow from the content nodes.
for (var contentNode of contentFlow.contentNodes)
this._contentNodesToFlowsMap.delete(contentNode.id);
this.dispatchEventToListeners(WebInspector.DOMTreeManager.Event.ContentFlowWasRemoved, {flow: contentFlow});
}
_sendNamedFlowUpdateEvents(flowPayload)
{
var flowKey = WebInspector.DOMTreeManager._flowPayloadHashKey(flowPayload);
console.assert(this._flows.has(flowKey));
this._updateContentFlowFromPayload(this._flows.get(flowKey), flowPayload);
}
regionOversetChanged(flowPayload)
{
this._sendNamedFlowUpdateEvents(flowPayload);
}
registeredNamedFlowContentElement(documentNodeIdentifier, flowName, contentNodeId, nextContentElementNodeId)
{
var flowKey = WebInspector.DOMTreeManager._flowPayloadHashKey({documentNodeId: documentNodeIdentifier, name: flowName});
console.assert(this._flows.has(flowKey));
console.assert(!this._contentNodesToFlowsMap.has(contentNodeId));
var flow = this._flows.get(flowKey);
var contentNode = this.nodeForId(contentNodeId);
this._contentNodesToFlowsMap.set(contentNode.id, flow);
if (nextContentElementNodeId)
flow.insertContentNodeBefore(contentNode, this.nodeForId(nextContentElementNodeId));
else
flow.appendContentNode(contentNode);
}
_removeContentNodeFromFlowIfNeeded(node)
{
if (!this._contentNodesToFlowsMap.has(node.id))
return;
var flow = this._contentNodesToFlowsMap.get(node.id);
this._contentNodesToFlowsMap.delete(node.id);
flow.removeContentNode(node);
}
unregisteredNamedFlowContentElement(documentNodeIdentifier, flowName, contentNodeId)
{
console.assert(this._contentNodesToFlowsMap.has(contentNodeId));
var flow = this._contentNodesToFlowsMap.get(contentNodeId);
console.assert(flow.id === WebInspector.DOMTreeManager._flowPayloadHashKey({documentNodeId: documentNodeIdentifier, name: flowName}));
this._contentNodesToFlowsMap.delete(contentNodeId);
flow.removeContentNode(this.nodeForId(contentNodeId));
}
_coerceRemoteArrayOfDOMNodes(objectId, callback)
{
var length, nodes, received = 0, lastError = null, domTreeManager = this;
function nodeRequested(index, error, nodeId)
{
if (error)
lastError = error;
else
nodes[index] = domTreeManager._idToDOMNode[nodeId];
if (++received === length)
callback(lastError, nodes);
}
WebInspector.runtimeManager.getPropertiesForRemoteObject(objectId, function(error, properties) {
if (error) {
callback(error);
return;
}
var lengthProperty = properties.get("length");
if (!lengthProperty || lengthProperty.value.type !== "number") {
callback(null);
return;
}
length = lengthProperty.value.value;
if (!length) {
callback(null, []);
return;
}
nodes = new Array(length);
for (var i = 0; i < length; ++i) {
var nodeProperty = properties.get(String(i));
console.assert(nodeProperty.value.type === "object");
DOMAgent.requestNode(nodeProperty.value.objectId, nodeRequested.bind(null, i));
}
});
}
getNodeContentFlowInfo(domNode, resultReadyCallback)
{
DOMAgent.resolveNode(domNode.id, domNodeResolved.bind(this));
function domNodeResolved(error, remoteObject)
{
if (error) {
resultReadyCallback(error);
return;
}
// Serialize "backendFunction" and execute it in the context of the page
// passing the DOMNode as the "this" reference.
var evalParameters = {
objectId: remoteObject.objectId,
functionDeclaration: appendWebInspectorSourceURL(backendFunction.toString()),
doNotPauseOnExceptionsAndMuteConsole: true,
returnByValue: false,
generatePreview: false
};
RuntimeAgent.callFunctionOn.invoke(evalParameters, regionNodesAvailable.bind(this));
}
function regionNodesAvailable(error, remoteObject, wasThrown)
{
if (error) {
resultReadyCallback(error);
return;
}
if (wasThrown) {
// We should never get here, but having the error is useful for debugging.
console.error("Error while executing backend function:", JSON.stringify(remoteObject));
resultReadyCallback(null);
return;
}
// The backend function can never return null.
console.assert(remoteObject.type === "object");
console.assert(remoteObject.objectId);
WebInspector.runtimeManager.getPropertiesForRemoteObject(remoteObject.objectId, remoteObjectPropertiesAvailable.bind(this));
}
function remoteObjectPropertiesAvailable(error, properties) {
if (error) {
resultReadyCallback(error);
return;
}
var result = {
regionFlow: null,
contentFlow: null,
regions: null
};
var regionFlowNameProperty = properties.get("regionFlowName");
if (regionFlowNameProperty && regionFlowNameProperty.value && regionFlowNameProperty.value.value) {
console.assert(regionFlowNameProperty.value.type === "string");
var regionFlowKey = WebInspector.DOMTreeManager._flowPayloadHashKey({documentNodeId: domNode.ownerDocument.id, name: regionFlowNameProperty.value.value});
result.regionFlow = this._flows.get(regionFlowKey);
}
var contentFlowNameProperty = properties.get("contentFlowName");
if (contentFlowNameProperty && contentFlowNameProperty.value && contentFlowNameProperty.value.value) {
console.assert(contentFlowNameProperty.value.type === "string");
var contentFlowKey = WebInspector.DOMTreeManager._flowPayloadHashKey({documentNodeId: domNode.ownerDocument.id, name: contentFlowNameProperty.value.value});
result.contentFlow = this._flows.get(contentFlowKey);
}
var regionsProperty = properties.get("regions");
if (!regionsProperty || !regionsProperty.value.objectId) {
// The list of regions is null.
resultReadyCallback(null, result);
return;
}
console.assert(regionsProperty.value.type === "object");
console.assert(regionsProperty.value.subtype === "array");
this._coerceRemoteArrayOfDOMNodes(regionsProperty.value.objectId, function(error, nodes) {
result.regions = nodes;
resultReadyCallback(error, result);
});
}
// Note that "backendFunction" is serialized and executed in the context of the page.
function backendFunction()
{
function getComputedProperty(node, propertyName)
{
if (!node.ownerDocument || !node.ownerDocument.defaultView)
return null;
var computedStyle = node.ownerDocument.defaultView.getComputedStyle(node);
return computedStyle ? computedStyle[propertyName] : null;
}
function getContentFlowName(node)
{
for (; node; node = node.parentNode) {
var flowName = getComputedProperty(node, "webkitFlowInto");
if (flowName && flowName !== "none")
return flowName;
}
return null;
}
var node = this;
// Even detached nodes have an ownerDocument.
console.assert(node.ownerDocument);
var result = {
regionFlowName: getComputedProperty(node, "webkitFlowFrom"),
contentFlowName: getContentFlowName(node),
regions: null
};
if (result.contentFlowName) {
var flowThread = node.ownerDocument.webkitGetNamedFlows().namedItem(result.contentFlowName);
if (flowThread)
result.regions = flowThread.getRegionsByContent(node);
}
return result;
}
}
// Private
_mainResourceDidChange(event)
{
if (event.target.isMainFrame())
this._restoreSelectedNodeIsAllowed = true;
}
};
WebInspector.DOMTreeManager.Event = {
AttributeModified: "dom-tree-manager-attribute-modified",
AttributeRemoved: "dom-tree-manager-attribute-removed",
CharacterDataModified: "dom-tree-manager-character-data-modified",
NodeInserted: "dom-tree-manager-node-inserted",
NodeRemoved: "dom-tree-manager-node-removed",
DocumentUpdated: "dom-tree-manager-document-updated",
ChildNodeCountUpdated: "dom-tree-manager-child-node-count-updated",
DOMNodeWasInspected: "dom-tree-manager-dom-node-was-inspected",
InspectModeStateChanged: "dom-tree-manager-inspect-mode-state-changed",
ContentFlowListWasUpdated: "dom-tree-manager-content-flow-list-was-updated",
ContentFlowWasAdded: "dom-tree-manager-content-flow-was-added",
ContentFlowWasRemoved: "dom-tree-manager-content-flow-was-removed",
RegionOversetChanged: "dom-tree-manager-region-overset-changed"
};