mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-08-26 03:01:51 +08:00
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:
File diff suppressed because it is too large
Load Diff
@ -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;
|
||||
|
30
tns-core-modules/debugger/devtools-elements.android.ts
Normal file
30
tns-core-modules/debugger/devtools-elements.android.ts
Normal 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
|
||||
}
|
76
tns-core-modules/debugger/devtools-elements.common.ts
Normal file
76
tns-core-modules/debugger/devtools-elements.common.ts
Normal 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();
|
||||
}
|
||||
}
|
23
tns-core-modules/debugger/devtools-elements.d.ts
vendored
Normal file
23
tns-core-modules/debugger/devtools-elements.d.ts
vendored
Normal 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);
|
23
tns-core-modules/debugger/devtools-elements.ios.ts
Normal file
23
tns-core-modules/debugger/devtools-elements.ios.ts
Normal 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;
|
||||
}
|
@ -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)
|
||||
}
|
@ -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,
|
||||
|
79
tns-core-modules/debugger/webinspector-css.ios.ts
Normal file
79
tns-core-modules/debugger/webinspector-css.ios.ts
Normal 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;
|
||||
}
|
||||
}
|
95
tns-core-modules/debugger/webinspector-dom.ios.ts
Normal file
95
tns-core-modules/debugger/webinspector-dom.ios.ts
Normal 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;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user