Chrome devtools elements tab support for Android (#4351)

* Enable chrome-devtools elemets tab

* Trigger updates when property is chaned form native

* Tslint fixes

* Don't run dom-elemet tests in IOS

* fix tests

* Create package.json

* Update package.json

* domNode changed to field for performance
This commit is contained in:
Alexander Vakrilov
2017-06-12 16:48:27 +03:00
committed by GitHub
parent b7c61cad96
commit f2462158fb
16 changed files with 896 additions and 8 deletions

View File

@ -0,0 +1,13 @@
.btn1 {
background-color: lightgreen;
color: coral;
font-size: 20;
font-family: monospace;
}
.btn2 {
background-color: coral;
color: lightgreen;
font-size: 24;
font-family: serif;
}

View File

@ -0,0 +1,6 @@
import * as application from "tns-core-modules/application";
// Needed only for build infrastructure
application.setCssFileName("devtools-app/app.css");
application.start({ moduleName: "devtools-app/main-page" });

View File

@ -0,0 +1,36 @@
import * as frame from "tns-core-modules/ui/frame";
import { Label } from "tns-core-modules/ui/label";
import { StackLayout } from "tns-core-modules/ui/layouts/stack-layout";
// import { DOMNode } from "tns-core-modules/debugger/dom-node";
export function print(args) {
// const node = new DOMNode(frame.topmost());
// console.dir(node.toJSON());
// const btn = args.object.page.getViewById("btn");
// btn.ensureDomNode();
// console.dir(btn.domNode.getComputedProperties());
}
let i = 0;
export function add(args) {
const container = args.object.page.getViewById("container");
const lbl = new Label();
lbl.text = "label " + i++;
container.addChild(lbl);
}
export function remove(args) {
const container = <StackLayout>args.object.page.getViewById("container");
const lbl = container.getChildAt(container.getChildrenCount() - 1);
container.removeChild(lbl);
}
export function navigate() {
frame.topmost().navigate("gallery-app/main-page");
}
export function change(args){
args.object.text = "hi " + Math.random();
}

View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Page navigatedTo="print">
<GridLayout rows="* * *" columns="* * *">
<Button id="btn"
customProp="custom"
text="hi there"
fontSize="30"
row="1"
col="1"
color="red"
backgroundColor="lightgreen"
class="btn"
tap="change"/>
<Button text="class: btn1" class="btn1" row="1" col="0"/>
<Button text="class: btn2" class="btn2" row="1" col="2"/>
<Button text="add" tap="add" />
<Button text="remove" tap="remove" col="2" />
<Button text="move beach" tap="navigate" col="1" />
<StackLayout colSpan="3" row="2" id="container">
<TextField hint="hint" text="123" />
<TextField hint="hint" text="456" />
</StackLayout>
</GridLayout>
</Page>

View File

@ -0,0 +1,334 @@
import { assert, assertEqual } from "../TKUnit";
import { DOMNode } from "tns-core-modules/debugger/dom-node";
import { attachInspectorCallbacks } from "tns-core-modules/debugger/devtools-elements";
import { Inspector } from "tns-core-modules/debugger/devtools-elements";
import { unsetValue } from "tns-core-modules/ui/core/properties";
import { Button } from "tns-core-modules/ui/button";
import { Slider } from "tns-core-modules/ui/slider";
import { Label } from "tns-core-modules/ui/label";
import { textProperty } from "tns-core-modules/ui/text-base";
import { TextView } from "tns-core-modules/ui/text-view";
import { StackLayout } from "tns-core-modules/ui/layouts/stack-layout";
let originalInspectorGlobal: Inspector;
let currentInspector: Inspector;
function getTestInspector(): Inspector {
let inspector = {
getDocument(): string { return ""; },
removeNode(nodeId: number): void { /* */ },
getComputedStylesForNode(nodeId: number): string { return ""; },
setAttributeAsText(nodeId: number, text: string, name: string): void { /* */},
childNodeInserted(parentId: number, lastId: number, nodeStr: string): void { /* to be replaced */ },
childNodeRemoved(parentId: number, nodeId: number): void { /* to be replaced */ },
attributeModified(nodeId: number, attrName: string, attrValue: string) { /* to be replaced */ },
attributeRemoved(nodeId: number, attrName: string) { /* to be replaced */ }
}
attachInspectorCallbacks(inspector);
return inspector;
}
export function setUp(): void {
originalInspectorGlobal = global.__inspector;
currentInspector = getTestInspector();
global.__inspector = currentInspector;
}
export function tearDown(): void {
global.__inspector = originalInspectorGlobal;
}
function assertAttribute(domNode: DOMNode, name: string, value: any) {
const propIdx = domNode.attributes.indexOf(name);
assert(propIdx >= 0, `Attribute ${name} not found`);
assertEqual(domNode.attributes[propIdx + 1], value);
}
export function test_custom_attribute_is_reported_in_dom_node() {
const btn = new Button();
btn["test_prop"] = "test_value";
btn.ensureDomNode();
const domNode = btn.domNode;
assertAttribute(domNode, "test_prop", "test_value");
}
export function test_custom__falsy_attribute_is_reported_in_dom_node() {
const btn = new Button();
btn["test_prop_null"] = null;
btn["test_prop_0"] = 0;
btn["test_prop_undefined"] = undefined;
btn["test_prop_empty_string"] = "";
btn.ensureDomNode();
const domNode = btn.domNode;
assertAttribute(domNode, "test_prop_null", null + "");
assertAttribute(domNode, "test_prop_0", 0 + "");
assertAttribute(domNode, "test_prop_undefined", undefined + "");
assertAttribute(domNode, "test_prop_empty_string", "");
}
export function test_property_is_reported_in_dom_node() {
const btn = new Button();
btn.text = "test_value";
btn.ensureDomNode();
const domNode = btn.domNode;
assertAttribute(domNode, "text", "test_value");
}
export function test_childNodeInserted_in_dom_node() {
let childNodeInsertedCalled = false;
let actualParentId = 0;
let expectedParentId = 0;
currentInspector.childNodeInserted = (parentId, lastNodeId, node) => {
childNodeInsertedCalled = true;
actualParentId = parentId;
}
const stack = new StackLayout();
stack.ensureDomNode();
expectedParentId = stack._domId;
const btn1 = new Button();
btn1.text = "button1";
stack.addChild(btn1);
assert(childNodeInsertedCalled, "global.__inspector.childNodeInserted not called.");
assertEqual(actualParentId, expectedParentId);
}
export function test_childNodeInserted_at_index_in_dom_node() {
const stack = new StackLayout();
stack.ensureDomNode();
// child index 0
const btn1 = new Button();
btn1.text = "button1";
stack.addChild(btn1);
// child index 1
const btn2 = new Button();
btn2.text = "button2";
stack.addChild(btn2);
// child index 2
const btn3 = new Button();
btn3.text = "button3";
stack.addChild(btn3);
const lbl = new Label();
lbl.text = "label me this";
let called = false;
currentInspector.childNodeInserted = (parentId, lastNodeId, node) => {
assertEqual(lastNodeId, btn1._domId, "Child inserted at index 1's previous sibling does not match.");
assertEqual(JSON.parse(node).nodeId, lbl._domId, "Child id doesn't match");
called = true;
}
stack.insertChild(lbl, 1);
assert(called, "childNodeInserted not called");
}
export function test_childNodeRemoved_in_dom_node() {
let childNodeRemovedCalled = false;
let actualRemovedNodeId = 0;
let expectedRemovedNodeId = 0;
currentInspector.childNodeRemoved = (parentId, nodeId) => {
childNodeRemovedCalled = true;
actualRemovedNodeId = nodeId;
}
const stack = new StackLayout();
stack.ensureDomNode();
const btn1 = new Button();
btn1.text = "button1";
expectedRemovedNodeId = btn1._domId;
stack.addChild(btn1);
const btn2 = new Button();
btn2.text = "button2";
stack.addChild(btn2);
stack.removeChild(btn1);
console.log("btn2: " + btn2);
assert(childNodeRemovedCalled, "global.__inspector.childNodeRemoved not called.");
assertEqual(actualRemovedNodeId, expectedRemovedNodeId);
}
export function test_falsy_property_is_reported_in_dom_node() {
const btn = new Button();
btn.text = null;
btn.ensureDomNode();
const domNode = btn.domNode;
assertAttribute(domNode, "text", "null");
btn.text = undefined;
domNode.loadAttributes();
assertAttribute(domNode, "text", "undefined");
}
export function test_property_change_calls_attributeModified() {
const btn = new Button();
btn.ensureDomNode();
const domNode = btn.domNode;
let callbackCalled = false;
currentInspector.attributeModified = (nodeId: number, attrName: string, attrValue: string) => {
assertEqual(nodeId, domNode.nodeId, "nodeId");
assertEqual(attrName, "text", "attrName");
assertEqual(attrValue, "new value", "attrValue");
callbackCalled = true;
}
btn.text = "new value";
assert(callbackCalled, "attributeModified not called");
}
export function test_property_change_from_native_calls_attributeModified() {
const tv = new TextView();
tv.ensureDomNode();
const domNode = tv.domNode;
let callbackCalled = false;
currentInspector.attributeModified = (nodeId: number, attrName: string, attrValue: string) => {
assertEqual(nodeId, domNode.nodeId, "nodeId");
assertEqual(attrName, "text", "attrName");
assertEqual(attrValue, "new value", "attrValue");
callbackCalled = true;
}
textProperty.nativeValueChange(tv, "new value");
assert(callbackCalled, "attributeModified not called");
}
export function test_property_reset_calls_attributeRemoved() {
const btn = new Button();
btn.text = "some value";
btn.ensureDomNode();
const domNode = btn.domNode;
let callbackCalled = false;
currentInspector.attributeRemoved = (nodeId: number, attrName: string) => {
assertEqual(nodeId, domNode.nodeId, "nodeId");
assertEqual(attrName, "text", "attrName");
callbackCalled = true;
}
btn.text = unsetValue;
assert(callbackCalled, "attributeRemoved not called");
}
export function test_coercible_property_change_calls_attributeModified() {
const slider = new Slider();
slider.ensureDomNode();
const domNode = slider.domNode;
let callbackCalled = false;
currentInspector.attributeModified = (nodeId: number, attrName: string, attrValue: string) => {
assertEqual(nodeId, domNode.nodeId, "nodeId");
assertEqual(attrName, "value", "attrName");
assertEqual(attrValue, "10", "attrValue");
callbackCalled = true;
}
slider.value = 10;
assert(callbackCalled, "attributeModified not called");
}
export function test_coercible_property_reset_calls_attributeRemoved() {
const slider = new Slider();
slider.value = 10;
slider.ensureDomNode();
const domNode = slider.domNode;
let callbackCalled = false;
currentInspector.attributeRemoved = (nodeId: number, attrName: string) => {
assertEqual(nodeId, domNode.nodeId, "nodeId");
assertEqual(attrName, "value", "attrName");
callbackCalled = true;
}
slider.value = unsetValue;
assert(callbackCalled, "attributeRemoved not called");
}
export function test_inspector_ui_setAttributeAsText_set_existing_property() {
// Arrange
const label = new Label();
label.text = "original label";
const expectedValue = "updated label";
label.ensureDomNode();
// Act
// simulate call from the inspector UI
currentInspector.setAttributeAsText(label.domNode.nodeId, "text='" + expectedValue + "'", "text");
// Assert
assertEqual(label.text, expectedValue);
}
export function test_inspector_ui_setAttributeAsText_remove_existing_property() {
// Arrange
const label = new Label();
label.text = "original label";
label.ensureDomNode();
// Act
// simulate call from the inspector UI
currentInspector.setAttributeAsText(label.domNode.nodeId, "" /* empty value - removes the attribute */, "text");
// Assert
assertEqual(label.text, "");
}
export function test_inspector_ui_setAttributeAsText_set_new_property() {
// Arrange
const label = new Label();
const expectedValue = "custom";
label.ensureDomNode();
// Act
// simulate call from the inspector UI
currentInspector.setAttributeAsText(label.domNode.nodeId, "data-attr='" + expectedValue + "'" /* data-attr="custom" */, " " /* empty attr name initially */);
// Assert
assertEqual(label["data-attr"], expectedValue);
}
export function test_inspector_ui_removeNode() {
let childNodeRemovedCalled = false;
let stack = new StackLayout();
let label = new Label();
stack.addChild(label);
stack.ensureDomNode();
label.ensureDomNode();
let expectedParentId = stack.domNode.nodeId;
let expectedNodeId = label.domNode.nodeId;
currentInspector.childNodeRemoved = (parentId, nodeId) => {
childNodeRemovedCalled = true;
assertEqual(parentId, expectedParentId);
assertEqual(nodeId, expectedNodeId);
}
currentInspector.removeNode(label.domNode.nodeId);
assert(childNodeRemovedCalled, "childNodeRemoved callback not called.");
}

View File

@ -31,6 +31,9 @@ export function isRunningOnEmulator(): boolean {
export var allTests = {};
import * as domNodeTest from "./debugger/dom-node-tests";
allTests["DOM-NODE"] = domNodeTest;
import * as profilingTests from "./profiling/profiling-tests";
allTests["PROFILING"] = profilingTests;

View File

@ -0,0 +1,81 @@
/*
On element select in the inspector the following are requested:
- Inline styles -> CSSStyle
- Attributes styles -> CSSStyle (Appears as 'Stacklayout[Attributes style]` - unsure of its purpose) irrelevant?
- Style matches -> RuleMatch[]
- Inherited styles -> InheritedStyleEntry[]
- Pseudo Element matches -> PseudoElementMatches[]
- Computed Styles for node -> CSSComputedStyleProperty[]
- Element Fonts -> PlatformFontUsage
*/
export interface CSSProperty {
name: string
value: string
disabled: boolean // strikes out the disabled property
}
export interface ShorthandEntry { // seems irrelevant - feel free to leave empty for now
name: string
value: string
}
export interface CSSStyle {
cssProperties: CSSProperty[]
shorthandEntries: ShorthandEntry[] // doesn't seem to display anywhere
cssText?: string
}
export interface Value {
text: string
}
export interface SelectorList { // e.g. [".btn", "Button", "Label"]
selectors: Value[]
text: string // doesn't seem to display anywhere
}
export interface CSSRule {
selectorList: SelectorList
origin: string // a constant - "regular"
style: CSSStyle
styleSheetId?: string // associated stylesheet
}
export interface RuleMatch {
rule: CSSRule
matchingSelectors: number[] // index-based - the index of the selector that the node currently inspected matches
}
export interface InheritedStyleEntry {
matchedCSSRules: RuleMatch[]
inlineStyle?: CSSStyle
}
export interface CSSComputedStyleProperty {
name: string
value: string
}
export interface PlatformFontUsage {
familyName: string
glyphCount: number // number of characters in text of element
isCustomFont: boolean
}
export interface CSSStyleSheetHeader {
styleSheetId: string // a unique identifier - file name/path should do
frameId: string // constant
sourceUrl: string
origin: string // constant
title: string // the same as the id?
disabled: boolean // false - if the css has been invalidated/disabled
isInLine: boolean // false
startLine: number // constant - 1
startColumn: number // constant - 1
}
export interface PseudoElementMatches {
pseudoType: string // e.g. last-child
matches: RuleMatch[]
}

View File

@ -0,0 +1,97 @@
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

@ -0,0 +1,197 @@
import { getSetProperties, getComputedCssValues } from "../ui/core/properties";
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";
const registeredDomNodes = {};
const ELEMENT_NODE_TYPE = 1;
const ROOT_NODE_TYPE = 9;
const propertyBlacklist = [
"effectivePaddingLeft",
"effectivePaddingBottom",
"effectivePaddingRight",
"effectivePaddingTop",
"effectiveBorderTopWidth",
"effectiveBorderRightWidth",
"effectiveBorderBottomWidth",
"effectiveBorderLeftWidth",
"effectiveMinWidth",
"nodeName",
"nodeType",
"decodeWidth",
"decodeHeight"
]
function notifyInspector(callback: (inspector: Inspector) => void) {
const ins = (<any>global).__inspector
if (ins) {
callback(ins);
}
}
function valueToString(value: any): string {
if (typeof value === "undefined" || value === null) {
return "";
} else if (value instanceof Color) {
return value.toString()
} else if (typeof value === "object") {
return PercentLength.convertToString(value)
} else {
return value + "";
}
}
function propertyFilter([name, value]: [string, any]): boolean {
if (name[0] === "_") {
return false;
}
if (value !== null && typeof value === "object") {
return false;
}
if (propertyBlacklist.indexOf(name) >= 0) {
return false;
}
return true;
}
function registerNode(domNode: DOMNode) {
registeredDomNodes[domNode.nodeId] = domNode;
}
function unregisterNode(domNode: DOMNode) {
delete registeredDomNodes[domNode.nodeId];
}
export function getNodeById(id: number): DOMNode {
return registeredDomNodes[id];
}
export class DOMNode {
nodeId;
nodeType;
nodeName;
localName;
nodeValue = '';
attributes: string[] = [];
viewRef: WeakRef<ViewBase>;
constructor(view: ViewBase) {
this.viewRef = new WeakRef(view);
this.nodeType = view.typeName === "Frame" ? ROOT_NODE_TYPE : ELEMENT_NODE_TYPE;
this.nodeId = view._domId;
this.nodeName = view.typeName;
this.localName = this.nodeName;
// Load all attributes
this.loadAttributes();
registerNode(this);
}
public loadAttributes() {
this.attributes = [];
getSetProperties(this.viewRef.get())
.filter(propertyFilter)
.forEach(pair => this.attributes.push(pair[0], pair[1] + ""));
}
get children(): DOMNode[] {
const view = this.viewRef.get();
if (!view) {
return [];
}
const res = [];
view.eachChild((child) => {
child.ensureDomNode();
res.push(child.domNode);
return true;
});
return res;
}
onChildAdded(childView: ViewBase): void {
notifyInspector((ins) => {
const view = this.viewRef.get();
childView.ensureDomNode();
let previousChild: ViewBase;
view.eachChild((child) => {
if (child === childView) {
return false;
}
previousChild = child;
return true;
});
const index = !!previousChild ? previousChild._domId : 0;
ins.childNodeInserted(this.nodeId, index, childView.domNode.toJSON());
});
}
onChildRemoved(view: ViewBase): void {
notifyInspector((ins) => {
ins.childNodeRemoved(this.nodeId, view.domNode.nodeId);
});
}
attributeModified(name: string, value: any) {
notifyInspector((ins) => {
ins.attributeModified(this.nodeId, name, valueToString(value));
});
}
attributeRemoved(name: string) {
notifyInspector((ins) => {
ins.attributeRemoved(this.nodeId, name);
});
}
getComputedProperties(): CSSComputedStyleProperty[] {
const view = this.viewRef.get();
if (!view) {
return [];
}
const result = getComputedCssValues(view)
.filter(pair => pair[0][0] !== "_")
.map((pair) => {
return {
name: pair[0],
value: valueToString(pair[1])
};
});
return result;
}
dispose() {
unregisterNode(this);
this.viewRef.clear();
}
public toJSON() {
return JSON.stringify(this.toObject());
}
private toObject() {
return {
nodeId: this.nodeId,
nodeType: this.nodeType,
nodeName: this.nodeName,
localName: this.localName,
nodeValue: this.nodeValue,
children: this.children.map(c => c.toObject()),
attributes: this.attributes
};
};
}

View File

@ -108,7 +108,7 @@ export class CssAnimationProperty<T extends Style, U> {
public register(cls: { prototype: T }): void;
public isSet(instance: T): boolean;
public _valueConverter?: (value: string) => any;
public static _getByCssName(name: string): CssAnimationProperty<any, any>;
}
@ -121,4 +121,7 @@ export function propagateInheritableCssProperties(parentStyle: Style, childStyle
export function clearInheritedProperties(view: ViewBase): void;
export function makeValidator<T>(...values: T[]): (value: any) => value is T;
export function makeParser<T>(isValid: (value: any) => boolean): (value: any) => T;
export function makeParser<T>(isValid: (value: any) => boolean): (value: any) => T;
export function getSetProperties(view: ViewBase): [string, any][];
export function getComputedCssValues(view: ViewBase): [string, any][];

View File

@ -12,6 +12,7 @@ export { Style };
export const unsetValue: any = new Object();
let cssPropertyNames: string[] = [];
let symbolPropertyMap = {};
let cssSymbolPropertyMap = {};
@ -152,6 +153,14 @@ export class Property<T extends ViewBase, U> implements TypedPropertyDescriptor<
if (affectsLayout) {
this.requestLayout();
}
if (this.domNode) {
if (reset) {
this.domNode.attributeRemoved(propertyName);
} else {
this.domNode.attributeModified(propertyName, value);
}
}
}
};
@ -179,6 +188,10 @@ export class Property<T extends ViewBase, U> implements TypedPropertyDescriptor<
if (affectsLayout) {
owner.requestLayout();
}
if (owner.domNode) {
owner.domNode.attributeModified(propertyName, value);
}
}
};
@ -299,6 +312,14 @@ export class CoercibleProperty<T extends ViewBase, U> extends Property<T, U> imp
if (affectsLayout) {
this.requestLayout();
}
if (this.domNode) {
if (reset) {
this.domNode.attributeRemoved(propertyName);
} else {
this.domNode.attributeModified(propertyName, value);
}
}
}
}
}
@ -399,6 +420,8 @@ export class CssProperty<T extends Style, U> implements definitions.CssProperty<
const propertyName = options.name;
this.name = propertyName;
cssPropertyNames.push(options.cssName);
this.cssName = `css:${options.cssName}`;
this.cssLocalName = options.cssName;
@ -635,6 +658,8 @@ export class CssAnimationProperty<T extends Style, U> {
const propertyName = options.name;
this.name = propertyName;
cssPropertyNames.push(options.cssName);
CssAnimationProperty.properties[propertyName] = this;
if (options.cssName && options.cssName !== propertyName) {
CssAnimationProperty.properties[options.cssName] = this;
@ -1206,4 +1231,34 @@ export function makeParser<T>(isValid: (value: any) => boolean): (value: any) =>
throw new Error("Invalid value: " + value);
}
};
}
export function getSetProperties(view: ViewBase): [string, any][] {
const result = [];
Object.getOwnPropertyNames(view).forEach(prop => {
result.push([prop, view[prop]]);
});
let symbols = Object.getOwnPropertySymbols(view);
for (let symbol of symbols) {
const property = symbolPropertyMap[symbol];
if (!property) {
continue;
}
const value = view[property.key];
result.push([property.name, value]);
}
return result;
}
export function getComputedCssValues(view: ViewBase): [string, any][] {
const result = [];
const style = view.style;
for (var prop of cssPropertyNames) {
result.push([prop, style[prop]]);
}
return result;
}

View File

@ -15,6 +15,7 @@ import { layout } from "../../../utils/utils";
import { Color } from "../../../color";
import { Order, FlexGrow, FlexShrink, FlexWrapBefore, AlignSelf } from "../../layouts/flexbox-layout";
import { Length } from "../../styling/style-properties";
import { DOMNode } from "../../../debugger/dom-node";
export { isIOS, isAndroid, layout, Color };
@ -57,6 +58,7 @@ export abstract class ViewBase extends Observable {
col: number;
rowSpan: number;
colSpan: number;
domNode: DOMNode;
order: Order;
flexGrow: FlexGrow;
@ -277,7 +279,7 @@ export abstract class ViewBase extends Observable {
/**
* Performs the core logic of adding a child view to the native visual tree. Returns true if the view's native representation has been successfully added, false otherwise.
*/
*/
_addViewToNativeVisualTree(view: ViewBase, atIndex?: number): boolean;
_removeViewFromNativeVisualTree(view: ViewBase): void;
_childIndexToNativeChildIndex(index?: number): number;
@ -296,6 +298,12 @@ export abstract class ViewBase extends Observable {
*/
public deletePseudoClass(name: string): void;
/**
* @unstable
* Ensures a dom-node for this view.
*/
public ensureDomNode();
//@private
public _styleScope: any;

View File

@ -11,6 +11,7 @@ import { Binding, BindingOptions, Observable, WrappedValue, PropertyChangeData,
import { isIOS, isAndroid } from "../../../platform";
import { layout } from "../../../utils/utils";
import { Length, paddingTopProperty, paddingRightProperty, paddingBottomProperty, paddingLeftProperty } from "../../styling/style-properties";
import { DOMNode } from "../../../debugger/dom-node";
// TODO: Remove this import!
import * as types from "../../../utils/types";
@ -142,6 +143,7 @@ export abstract class ViewBase extends Observable implements ViewBaseDefinition
private _visualState: string;
private _inlineStyleSelector: SelectorCore;
private __nativeView: any;
public domNode: DOMNode;
public bindingContext: any;
public nativeView: any;
@ -259,7 +261,13 @@ export abstract class ViewBase extends Observable implements ViewBaseDefinition
return null;
}
// Overriden so we don't raise `poropertyChange`
public ensureDomNode() {
if (!this.domNode) {
this.domNode = new DOMNode(this);
}
}
// Overridden so we don't raise `poropertyChange`
// The property will raise its own event.
public set(name: string, value: any) {
this[name] = WrappedValue.unwrap(value);
@ -558,6 +566,10 @@ export abstract class ViewBase extends Observable implements ViewBaseDefinition
view.parent = this;
this._addViewCore(view, atIndex);
view._parentChanged(null);
if (this.domNode) {
this.domNode.onChildAdded(view);
}
}
@profile
@ -590,7 +602,7 @@ export abstract class ViewBase extends Observable implements ViewBaseDefinition
}
/**
* Core logic for removing a child view from this instance. Used by the framework to handle lifecycle events more centralized. Do not outside the UI Stack implementation.
* Core logic for removing a child view from this instance. Used by the framework to handle lifecycle events more centralized. Do not use outside the UI Stack implementation.
*/
public _removeView(view: ViewBase) {
if (traceEnabled()) {
@ -601,6 +613,10 @@ export abstract class ViewBase extends Observable implements ViewBaseDefinition
throw new Error("View not added to this instance. View: " + view + " CurrentParent: " + view.parent + " ExpectedParent: " + this);
}
if (this.domNode) {
this.domNode.onChildRemoved(view);
}
this._removeViewCore(view);
view.parent = undefined;
view._parentChanged(this);
@ -785,6 +801,12 @@ export abstract class ViewBase extends Observable implements ViewBaseDefinition
// this._iosView = null;
this._context = null;
if (this.domNode) {
this.domNode.dispose();
this.domNode = undefined;
}
traceNotifyEvent(this, "_onContextChanged");
traceNotifyEvent(this, "_tearDownUI");
}

View File

@ -34,6 +34,10 @@ function onLivesync(args: EventData): void {
}
application.on("livesync", onLivesync);
if (global && global.__inspector) {
require("tns-core-modules/debugger/devtools-elements");
}
let frameStack: Array<FrameBase> = [];
function buildEntryFromArgs(arg: any): NavigationEntry {

View File

@ -38,12 +38,12 @@ export class Slider extends View {
/**
* Represents the observable property backing the value property of each Slider instance.
*/
export const valueProperty: Property<Slider, number>;
export const valueProperty: CoercibleProperty<Slider, number>;
/**
* Represents the observable property backing the minValue property of each Slider instance.
*/
export const minValueProperty: CoercibleProperty<Slider, number>;
export const minValueProperty: Property<Slider, number>;
/**
* Represents the observable property backing the maxValue property of each Slider instance.

View File

@ -129,7 +129,7 @@ export class TextBase extends TextBaseCommon {
}
}
// Overriden in TextField becasue setSingleLine(false) will remove methodTransformation.
// Overridden in TextField because setSingleLine(false) will remove methodTransformation.
// and we don't want to allow TextField to be multiline
[whiteSpaceProperty.setNative](value: WhiteSpace) {
const nativeView = this.nativeView;