Elements tab support for iOS Chrome DevTools (#4930)

* add initial implementation of the dom agent for ios

* add implementation of the css agent for ios

* refactor devtools inspector methods to somewhat accomodate ios and android

patch elements dom-node tests to test for ios too

* fix android dom-node test

* fix ios dom-node-tests to mock the ios devtools inspector properly

* fix: minor changes
This commit is contained in:
Peter Kanev
2017-10-12 11:32:31 +03:00
committed by GitHub
parent 15f0a025c1
commit cb029225c3
16 changed files with 1545 additions and 2391 deletions

File diff suppressed because it is too large Load Diff

View File

@ -45,6 +45,26 @@ export function setNetwork(newNetwork: domains.network.NetworkDomainDebugger) {
network = newNetwork;
}
var dom;
export function getDOM():any {
return dom;
}
export function setDOM(newDOM) {
dom = newDOM;
}
var css;
export function getCSS(): any {
return css;
}
export function setCSS(newCSS) {
css = newCSS;
}
export namespace NetworkAgent {
export interface Request {
url: string;

View File

@ -0,0 +1,30 @@
import { InspectorEvents, InspectorCommands } from "./devtools-elements";
import { getDocument, getComputedStylesForNode, removeNode, setAttributeAsText } from "./devtools-elements.common";
import { registerInspectorEvents, DOMNode } from "./dom-node";
export function attachDOMInspectorEventCallbacks(DOMDomainFrontend: InspectorEvents) {
registerInspectorEvents(DOMDomainFrontend);
const originalChildNodeInserted: (parentId: number, lastId: number, node: string | DOMNode) => void = DOMDomainFrontend.childNodeInserted;
DOMDomainFrontend.childNodeInserted = (parentId: number, lastId: number, node: DOMNode) => {
originalChildNodeInserted(parentId, lastId, JSON.stringify(node.toObject()));
}
}
export function attachDOMInspectorCommandCallbacks(DOMDomainBackend: InspectorCommands) {
DOMDomainBackend.getDocument = () => {
return JSON.stringify(getDocument());
};
DOMDomainBackend.getComputedStylesForNode = (nodeId) => {
return JSON.stringify(getComputedStylesForNode(nodeId));
};
DOMDomainBackend.removeNode = removeNode;
DOMDomainBackend.setAttributeAsText = setAttributeAsText;
}
export function attachCSSInspectorCommandCallbacks(CSSDomainFrontend: InspectorCommands) {
// no op
}

View File

@ -0,0 +1,76 @@
import { unsetValue } from "../ui/core/properties";
import { ViewBase } from "../ui/core/view-base";
import { topmost } from "../ui/frame";
import { getNodeById } from "./dom-node";
function getViewById(nodeId: number): ViewBase {
const node = getNodeById(nodeId);
let view;
if (node) {
view = node.viewRef.get();
}
return view;
}
export function getDocument() {
const topMostFrame = topmost();
topMostFrame.ensureDomNode();
return topMostFrame.domNode.toObject();
}
export function getComputedStylesForNode(nodeId): Array<{ name: string, value: string}> {
const view = getViewById(nodeId);
if (view) {
return view.domNode.getComputedProperties();
}
return [];
}
export function removeNode(nodeId) {
const view = getViewById(nodeId);
if (view) {
// Avoid importing layout and content view
let parent = <any>view.parent;
if (parent.removeChild) {
parent.removeChild(view);
} else if (parent.content === view) {
parent.content = null;
}
else {
console.log("Can't remove child from " + parent);
}
}
}
export function setAttributeAsText(nodeId, text, name) {
const view = getViewById(nodeId);
if (view) {
// attribute is registered for the view instance
let hasOriginalAttribute = !!name.trim();
if (text) {
let textParts = text.split("=");
if (textParts.length === 2) {
let attrName = textParts[0];
let attrValue = textParts[1].replace(/['"]+/g, '');
// if attr name is being replaced with another
if (name !== attrName && hasOriginalAttribute) {
view[name] = unsetValue;
view[attrName] = attrValue;
} else {
view[hasOriginalAttribute ? name : attrName] = attrValue;
}
}
} else {
// delete attribute
view[name] = unsetValue;
}
view.domNode.loadAttributes();
}
}

View File

@ -0,0 +1,23 @@
import { DOMNode } from "./dom-node";
export interface InspectorCommands {
// DevTools -> Application communication. Methods that devtools calls when needed.
getDocument(): string | DOMNode;
removeNode(nodeId: number): void;
getComputedStylesForNode(nodeId: number): string | Array<{ name: string, value: string }>;
setAttributeAsText(nodeId: number, text: string, name: string): void;
}
export interface InspectorEvents {
// Application -> DevTools communication. Methods that the app should call when needed.
childNodeInserted(parentId: number, lastId: number, node: DOMNode): void;
childNodeRemoved(parentId: number, nodeId: number): void;
attributeModified(nodeId: number, attrName: string, attrValue: string): void;
attributeRemoved(nodeId: number, attrName: string): void;
}
export function attachDOMInspectorEventCallbacks(inspector: InspectorEvents);
export function attachDOMInspectorCommandCallbacks(inspector: InspectorCommands);
export function attachCSSInspectorCommandCallbacks(inspector: InspectorCommands);

View File

@ -0,0 +1,23 @@
import { InspectorEvents, InspectorCommands } from "./devtools-elements";
import { getDocument, getComputedStylesForNode, removeNode, setAttributeAsText } from "./devtools-elements.common";
import { registerInspectorEvents, DOMNode } from "./dom-node";
export function attachDOMInspectorEventCallbacks(DOMDomainFrontend: InspectorEvents) {
registerInspectorEvents(DOMDomainFrontend);
const originalChildNodeInserted: (parentId: number, lastId: number, node: string | DOMNode) => void = DOMDomainFrontend.childNodeInserted;
DOMDomainFrontend.childNodeInserted = (parentId: number, lastId: number, node: DOMNode) => {
originalChildNodeInserted(parentId, lastId, node.toObject());
}
}
export function attachDOMInspectorCommandCallbacks(DOMDomainBackend: InspectorCommands) {
DOMDomainBackend.getDocument = getDocument;
DOMDomainBackend.removeNode = removeNode;
DOMDomainBackend.setAttributeAsText = setAttributeAsText;
}
export function attachCSSInspectorCommandCallbacks(CSSDomainBackend: InspectorCommands) {
CSSDomainBackend.getComputedStylesForNode = getComputedStylesForNode;
}

View File

@ -1,97 +0,0 @@
import { unsetValue } from "../ui/core/properties";
import { ViewBase } from "../ui/core/view-base";
import { topmost } from "../ui/frame";
import { getNodeById } from "./dom-node";
export interface Inspector {
// DevTools -> Application communication. Methods that devtools calls when needed.
getDocument(): string;
removeNode(nodeId: number): void;
getComputedStylesForNode(nodeId: number): string;
setAttributeAsText(nodeId: number, text: string, name: string): void;
// Application -> DevTools communication. Methods that the app should call when needed.
childNodeInserted(parentId: number, lastId: number, nodeStr: string): void;
childNodeRemoved(parentId: number, nodeId: number): void;
attributeModified(nodeId: number, attrName: string, attrValue: string): void;
attributeRemoved(nodeId: number, attrName: string): void;
}
function getViewById(nodeId: number): ViewBase {
const node = getNodeById(nodeId);
let view;
if (node) {
view = node.viewRef.get();
}
return view;
}
export function attachInspectorCallbacks(inspector: Inspector) {
inspector.getDocument = function () {
const topMostFrame = topmost();
topMostFrame.ensureDomNode();
return topMostFrame.domNode.toJSON();
}
inspector.getComputedStylesForNode = function (nodeId) {
const view = getViewById(nodeId);
if (view) {
return JSON.stringify(view.domNode.getComputedProperties());
}
return "[]";
}
inspector.removeNode = function (nodeId) {
const view = getViewById(nodeId);
if (view) {
// Avoid importing layout and content view
let parent = <any>view.parent;
if (parent.removeChild) {
parent.removeChild(view);
} else if (parent.content === view) {
parent.content = null;
}
else {
console.log("Can't remove child from " + parent);
}
}
}
inspector.setAttributeAsText = function (nodeId, text, name) {
const view = getViewById(nodeId);
if (view) {
// attribute is registered for the view instance
let hasOriginalAttribute = !!name.trim();
if (text) {
let textParts = text.split("=");
if (textParts.length === 2) {
let attrName = textParts[0];
let attrValue = textParts[1].replace(/['"]+/g, '');
// if attr name is being replaced with another
if (name !== attrName && hasOriginalAttribute) {
view[name] = unsetValue;
view[attrName] = attrValue;
} else {
view[hasOriginalAttribute ? name : attrName] = attrValue;
}
}
} else {
// delete attribute
view[name] = unsetValue;
}
view.domNode.loadAttributes();
}
}
}
// Automatically attach callbacks if there is __inspector
if (global && global.__inspector) {
attachInspectorCallbacks(global.__inspector)
}

View File

@ -3,7 +3,7 @@ import { PercentLength } from "../ui/styling/style-properties";
import { ViewBase } from "../ui/core/view";
import { Color } from "../color";
import { CSSComputedStyleProperty } from "./css-agent";
import { Inspector } from "./devtools-elements";
import { InspectorEvents } from "./devtools-elements";
const registeredDomNodes = {};
const ELEMENT_NODE_TYPE = 1;
@ -19,6 +19,12 @@ const propertyBlacklist = [
"effectiveBorderLeftWidth",
"effectiveMinWidth",
"effectiveMinHeight",
"effectiveWidth",
"effectiveHeight",
"effectiveMarginLeft",
"effectiveMarginTop",
"effectiveMarginRight",
"effectiveMarginBottom",
"nodeName",
"nodeType",
"decodeWidth",
@ -30,10 +36,15 @@ const propertyBlacklist = [
"nativeView"
];
function notifyInspector(callback: (inspector: Inspector) => void) {
const ins = (<any>global).__inspector;
if (ins) {
callback(ins);
let inspectorFrontendInstance: any;
export function registerInspectorEvents(inspector: InspectorEvents) {
inspectorFrontendInstance = inspector;
}
function notifyInspector(callback: (inspector: InspectorEvents) => void) {
if (inspectorFrontendInstance) {
callback(inspectorFrontendInstance);
}
}
@ -141,7 +152,7 @@ export class DOMNode {
const index = !!previousChild ? previousChild._domId : 0;
childView.ensureDomNode();
ins.childNodeInserted(this.nodeId, index, childView.domNode.toJSON());
ins.childNodeInserted(this.nodeId, index, childView.domNode);
});
}
@ -187,11 +198,7 @@ export class DOMNode {
this.viewRef.clear();
}
public toJSON() {
return JSON.stringify(this.toObject());
}
private toObject() {
public toObject() {
return {
nodeId: this.nodeId,
nodeType: this.nodeType,

View File

@ -0,0 +1,79 @@
import * as inspectorCommandTypes from "./InspectorBackendCommands.ios";
var inspectorCommands: typeof inspectorCommandTypes = require("./InspectorBackendCommands");
import * as debuggerDomains from "./debugger";
import * as devToolsElements from "./devtools-elements";
declare var __inspectorSendEvent;
import { attachCSSInspectorCommandCallbacks } from "./devtools-elements";
@inspectorCommands.DomainDispatcher("CSS")
export class CSSDomainDebugger implements inspectorCommandTypes.CSSDomain.CSSDomainDispatcher {
private _enabled: boolean;
public events: inspectorCommandTypes.CSSDomain.CSSFrontend;
public commands: any;
constructor() {
this.events = new inspectorCommands.CSSDomain.CSSFrontend();
this.commands = {};
attachCSSInspectorCommandCallbacks(this.commands);
}
get enabled(): boolean {
return this._enabled;
}
enable(): void {
if (debuggerDomains.getCSS()) {
throw new Error("One CSSDomainDebugger may be enabled at a time.");
} else {
debuggerDomains.setCSS(this);
}
this._enabled = true;
}
/**
* Disables network tracking, prevents network events from being sent to the client.
*/
disable(): void {
if (debuggerDomains.getCSS() === this) {
debuggerDomains.setCSS(null);
}
this._enabled = false;
}
getMatchedStylesForNode(params: inspectorCommandTypes.CSSDomain.GetMatchedStylesForNodeMethodArguments): { inlineStyle?: inspectorCommandTypes.CSSDomain.CSSStyle, attributesStyle?: inspectorCommandTypes.CSSDomain.CSSStyle, matchedCSSRules?: inspectorCommandTypes.CSSDomain.RuleMatch[], pseudoElements?: inspectorCommandTypes.CSSDomain.PseudoElementMatches[], inherited?: inspectorCommandTypes.CSSDomain.InheritedStyleEntry[], cssKeyframesRules?: inspectorCommandTypes.CSSDomain.CSSKeyframesRule[] } {
return {};
}
// Returns the styles defined inline (explicitly in the "style" attribute and implicitly, using DOM attributes) for a DOM node identified by <code>nodeId</code>.
getInlineStylesForNode(params: inspectorCommandTypes.CSSDomain.GetInlineStylesForNodeMethodArguments): { inlineStyle?: inspectorCommandTypes.CSSDomain.CSSStyle, attributesStyle?: inspectorCommandTypes.CSSDomain.CSSStyle } {
return {};
}
// Returns the computed style for a DOM node identified by <code>nodeId</code>.
getComputedStyleForNode(params: inspectorCommandTypes.CSSDomain.GetComputedStyleForNodeMethodArguments): { computedStyle: inspectorCommandTypes.CSSDomain.CSSComputedStyleProperty[] } {
return { computedStyle: this.commands.getComputedStylesForNode(params.nodeId) };
}
// Requests information about platform fonts which we used to render child TextNodes in the given node.
getPlatformFontsForNode(params: inspectorCommandTypes.CSSDomain.GetPlatformFontsForNodeMethodArguments): { fonts: inspectorCommandTypes.CSSDomain.PlatformFontUsage[] } {
return {
fonts: [
{
// Font's family name reported by platform.
familyName: "Standard Font",
// Indicates if the font was downloaded or resolved locally.
isCustomFont: false,
// Amount of glyphs that were rendered with this font.
glyphCount: 0
}
]
}
}
// Returns the current textual content and the URL for a stylesheet.
getStyleSheetText(params: inspectorCommandTypes.CSSDomain.GetStyleSheetTextMethodArguments): { text: string } {
return null;
}
}

View File

@ -0,0 +1,95 @@
import * as inspectorCommandTypes from "./InspectorBackendCommands.ios";
var inspectorCommands: typeof inspectorCommandTypes = require("./InspectorBackendCommands");
// var inspectorCommandTypes: any = inspectorCommands;
import * as debuggerDomains from "./debugger";
import * as devToolsElements from "./devtools-elements";
declare var __inspectorSendEvent;
import { attachDOMInspectorEventCallbacks, attachDOMInspectorCommandCallbacks } from "./devtools-elements";
@inspectorCommands.DomainDispatcher("DOM")
export class DOMDomainDebugger implements inspectorCommandTypes.DOMDomain.DOMDomainDispatcher {
private _enabled: boolean;
public events: inspectorCommandTypes.DOMDomain.DOMFrontend;
public commands: any;
constructor() {
this.events = new inspectorCommands.DOMDomain.DOMFrontend();
this.commands = {};
attachDOMInspectorEventCallbacks(this.events);
attachDOMInspectorCommandCallbacks(this.commands);
}
get enabled(): boolean {
return this._enabled;
}
enable(): void {
if (debuggerDomains.getDOM()) {
throw new Error("One DOMDomainDebugger may be enabled at a time.");
} else {
debuggerDomains.setDOM(this);
}
this._enabled = true;
}
/**
* Disables network tracking, prevents network events from being sent to the client.
*/
disable(): void {
if (debuggerDomains.getDOM() === this) {
debuggerDomains.setDOM(null);
}
this._enabled = false;
}
getDocument(): { root: inspectorCommandTypes.DOMDomain.Node } {
const domNode = this.commands.getDocument();
return { root: domNode };
}
removeNode(params: inspectorCommandTypes.DOMDomain.RemoveNodeMethodArguments): void {
this.commands.removeNode(params.nodeId);
}
setAttributeValue(params: inspectorCommandTypes.DOMDomain.SetAttributeValueMethodArguments): void {
throw new Error("Method not implemented.");
}
setAttributesAsText(params: inspectorCommandTypes.DOMDomain.SetAttributesAsTextMethodArguments): void {
this.commands.setAttributeAsText(params.nodeId, params.text, params.name);
}
removeAttribute(params: inspectorCommandTypes.DOMDomain.RemoveAttributeMethodArguments): void {
throw new Error("Method not implemented.");
}
performSearch(params: inspectorCommandTypes.DOMDomain.PerformSearchMethodArguments): { searchId: string, resultCount: number } {
return null;
}
getSearchResults(params: inspectorCommandTypes.DOMDomain.GetSearchResultsMethodArguments): { nodeIds: inspectorCommandTypes.DOMDomain.NodeId[] } {
return null;
}
discardSearchResults(params: inspectorCommandTypes.DOMDomain.DiscardSearchResultsMethodArguments): void {
return;
}
highlightNode(params: inspectorCommandTypes.DOMDomain.HighlightNodeMethodArguments): void {
return;
}
hideHighlight(): void {
return;
}
resolveNode(params: inspectorCommandTypes.DOMDomain.ResolveNodeMethodArguments): { object: inspectorCommandTypes.RuntimeDomain.RemoteObject } {
return null;
}
}