diff --git a/e2e/dev-tools/app/home/home-items-page.xml b/e2e/dev-tools/app/home/home-items-page.xml index fc36dd3ca..6066dd34b 100644 --- a/e2e/dev-tools/app/home/home-items-page.xml +++ b/e2e/dev-tools/app/home/home-items-page.xml @@ -11,11 +11,15 @@ Feel free to customize layouts and components to change how the tab view looks. - - - - - - - + + + + + + + + + + + \ No newline at end of file diff --git a/e2e/dev-tools/package.json b/e2e/dev-tools/package.json index 510a92380..cdc8843aa 100644 --- a/e2e/dev-tools/package.json +++ b/e2e/dev-tools/package.json @@ -1,11 +1,11 @@ { "nativescript": { "id": "org.nativescript.devtools", - "tns-android": { - "version": "6.1.2" - }, "tns-ios": { "version": "6.1.0" + }, + "tns-android": { + "version": "6.3.0" } }, "description": "NativeScript Application", diff --git a/nativescript-core/debugger/InspectorBackendCommands.ios.ts b/nativescript-core/debugger/InspectorBackendCommands.ios.ts index 13c44027f..9a989334c 100644 --- a/nativescript-core/debugger/InspectorBackendCommands.ios.ts +++ b/nativescript-core/debugger/InspectorBackendCommands.ios.ts @@ -1460,6 +1460,15 @@ export namespace DOMDomain { pseudoElementRemoved(parentId: NodeId, pseudoElementId: NodeId): void { __inspectorSendEvent(JSON.stringify({ "method": "DOM.pseudoElementRemoved", "params": { "parentId": parentId, "pseudoElementId": pseudoElementId } })); } + + // TODO: Move this into own domain + nodeHighlightRequested(nodeId: NodeId): void { + __inspectorSendEvent(JSON.stringify({ "method": "Overlay.nodeHighlightRequested", "params": { "nodeId": nodeId } })); + } + inspectModeCanceled(): void { + __inspectorSendEvent(JSON.stringify({ "method": "Overlay.inspectModeCanceled", "params": {} })); + } + } } diff --git a/nativescript-core/debugger/debugger.ts b/nativescript-core/debugger/debugger.ts index 1b61fe02f..7cea2c8ef 100644 --- a/nativescript-core/debugger/debugger.ts +++ b/nativescript-core/debugger/debugger.ts @@ -65,6 +65,16 @@ export function setCSS(newCSS) { css = newCSS; } +let overlay; + +export function getOverlay(): any { + return overlay; +} + +export function setOverlay(newOverlay) { + overlay = newOverlay; +} + export namespace NetworkAgent { export interface Request { url: string; diff --git a/nativescript-core/debugger/devtools-elements.android.ts b/nativescript-core/debugger/devtools-elements.android.ts index be0753b3f..a705a83ac 100644 --- a/nativescript-core/debugger/devtools-elements.android.ts +++ b/nativescript-core/debugger/devtools-elements.android.ts @@ -1,5 +1,13 @@ import { InspectorEvents, InspectorCommands } from "./devtools-elements"; -import { getDocument, getComputedStylesForNode, removeNode, setAttributeAsText } from "./devtools-elements.common"; +import { + getDocument, + getComputedStylesForNode, + removeNode, + setAttributeAsText, + highlightNode, + hideHighlight, + setInspectMode +} from "./devtools-elements.common"; import { registerInspectorEvents, DOMNode } from "./dom-node"; export function attachDOMInspectorEventCallbacks(DOMDomainFrontend: InspectorEvents) { @@ -23,8 +31,13 @@ export function attachDOMInspectorCommandCallbacks(DOMDomainBackend: InspectorCo DOMDomainBackend.removeNode = removeNode; DOMDomainBackend.setAttributeAsText = setAttributeAsText; + + DOMDomainBackend.highlightNode = highlightNode; + DOMDomainBackend.hideHighlight = hideHighlight; + DOMDomainBackend.setInspectMode = setInspectMode; } export function attachCSSInspectorCommandCallbacks(CSSDomainFrontend: InspectorCommands) { // no op } + diff --git a/nativescript-core/debugger/devtools-elements.common.ts b/nativescript-core/debugger/devtools-elements.common.ts index 71473ef5a..ad25600b4 100644 --- a/nativescript-core/debugger/devtools-elements.common.ts +++ b/nativescript-core/debugger/devtools-elements.common.ts @@ -1,11 +1,18 @@ -import { getNodeById } from "./dom-node"; +import { getNodeById, DOMNode } from "./dom-node"; +import { Color } from "../color"; // Needed for typings only -import { ViewBase } from "../ui/core/view-base"; -import { mainThreadify } from "../utils/utils"; +import { View, ViewBase } from "../ui/core/view"; + + + +import { isIOS, isAndroid } from "../platform"; + +import { mainThreadify, layout } from "../utils/utils"; +import { TouchGestureEventData, GestureTypes } from "../ui/gestures"; // Use lazy requires for core modules -const getAppRootView = () => require("../application").getRootView(); +const getAppRootView: () => View = () => require("../application").getRootView(); let unsetValue; function unsetViewValue(view, name) { @@ -96,3 +103,178 @@ export const setAttributeAsText = mainThreadify(function setAttributeAsText(node view.domNode.loadAttributes(); } }); + +interface HighlightedView { + view: ViewBase; + cachedValue: any; +}; + + +const highlightColor = new Color("#A86FA8DC"); +const highlightedViews: HighlightedView[] = []; + +export const highlightNode = mainThreadify((nodeId: number, selector?) => { + console.log("[overlay]: highlightNode", nodeId, selector); + + hideHighlight(); + + const view = getViewById(nodeId); + if (!view) { + return; + } + + const adView = view.android; + if (adView && adView.setForeground) { + const highlightForeground = new android.graphics.drawable.ColorDrawable(highlightColor.android); + highlightedViews.push({ + view: view, + cachedValue: adView.getForeground() + }); + adView.setForeground(highlightForeground); + } + + const iosView: UIView = view.ios; + if (iosView && iosView.addSubview) { + const maskView = UIView.alloc().initWithFrame(iosView.bounds); + maskView.backgroundColor = highlightColor.ios; + maskView.userInteractionEnabled = false; + iosView.addSubview(maskView); + + highlightedViews.push({ + view: view, + cachedValue: maskView + }); + } + +}); + +export const hideHighlight = mainThreadify(() => { + console.log("[overlay]: hideHighlight"); + + while (highlightedViews.length > 0) { + const hv = highlightedViews.pop(); + + const adView = hv.view.android; + if (adView && adView.setForeground) { + adView.setForeground(hv.cachedValue); + } + + const iosView = hv.view.ios; + if (iosView && hv.cachedValue) { + (hv.cachedValue).removeFromSuperview(); + } + } +}) + + +function eachDescendant(view: View, callback: (child: View) => boolean) { + if (!callback || !view) { + return; + } + + let traverseChildren: boolean; + let localCallback = function (child: View): boolean { + traverseChildren = callback(child); + if (traverseChildren) { + child.eachChildView(localCallback); + } + + return true; + }; + + view.eachChildView(localCallback); +} + +function rootViewTouched(event: TouchGestureEventData) { + if (event.action === "up" || event.action === "cancel") { + DOMNode.requestCancelInspect(); + } else { + handleSearch(event.view, event.getX(), event.getY()); + } +} + +function handleSearch(root: View, x: number, y: number) { + console.log("rootViewTouched", x, y); + + const stack: View[] = []; + eachDescendant(root, (child: View) => { + const point = child.getLocationInWindow(); + const size = child.getActualSize(); + + // console.log("Child: ", child, point, size) + + if (point && size && + point.x < x && x < (point.x + size.width) && + point.y < y && y < (point.y + size.height)) { + + stack.push(child); + return true; + } + return false; + }) + + console.log(stack.join(", ")); + + if (stack.length) { + const topView = stack[stack.length - 1]; + const domNode = topView.domNode + + highlightNode(domNode.nodeId); + domNode.requestHighligh(); + } + +} + + +let overlayContentView: org.nativescript.widgets.ContentLayout; +export const setInspectMode = mainThreadify((mode: "none" | "searchForNode") => { + console.log("[overlay]: setInspectMode", mode) + + const root = getAppRootView(); + if (root) { + if (isIOS) { + if (mode === "searchForNode") { + console.log("start listening for root view touches"); + root.on(GestureTypes.touch, rootViewTouched); + root.eachChildView(cv => { + cv.isUserInteractionEnabled = false; + return true; + }) + } else { + console.log("stop listening for root view touches"); + root.off(GestureTypes.touch, rootViewTouched); + root.eachChildView(cv => { + cv.isUserInteractionEnabled = true; + return true; + }) + } + } + + if (isAndroid) { + if (mode === "searchForNode") { + const activity: android.app.Activity = root._context; + if (!overlayContentView) { + overlayContentView = new org.nativescript.widgets.ContentLayout(activity); + + overlayContentView.setOnTouchListener(new android.view.View.OnTouchListener({ + onTouch: (v: android.view.View, event: android.view.MotionEvent): boolean => { + if (event.getAction() == android.view.MotionEvent.ACTION_UP) { + console.log("Canceling touch listener"); + + DOMNode.requestCancelInspect(); + } else { + handleSearch(root, event.getX() / layout.getDisplayDensity(), event.getY() / layout.getDisplayDensity()) + } + return true; + } + })) + } + activity.addContentView(overlayContentView, new org.nativescript.widgets.CommonLayoutParams()); + } else { + if (overlayContentView) { + (overlayContentView.getParent()).removeView(overlayContentView); + } + } + } + } +}) \ No newline at end of file diff --git a/nativescript-core/debugger/devtools-elements.d.ts b/nativescript-core/debugger/devtools-elements.d.ts index 6a188afd7..98530ffea 100644 --- a/nativescript-core/debugger/devtools-elements.d.ts +++ b/nativescript-core/debugger/devtools-elements.d.ts @@ -6,6 +6,10 @@ export interface InspectorCommands { removeNode(nodeId: number): void; getComputedStylesForNode(nodeId: number): string | Array<{ name: string, value: string }>; setAttributeAsText(nodeId: number, text: string, name: string): void; + + highlightNode(nodeId: number, selector?: string); + hideHighlight(); + setInspectMode(mode: "none" | "searchForNode"); } export interface InspectorEvents { @@ -14,6 +18,9 @@ export interface InspectorEvents { childNodeRemoved(parentId: number, nodeId: number): void; attributeModified(nodeId: number, attrName: string, attrValue: string): void; attributeRemoved(nodeId: number, attrName: string): void; + + nodeHighlightRequested(nodeId: number): void; + inspectModeCanceled(): void; } export function attachDOMInspectorEventCallbacks(inspector: InspectorEvents); diff --git a/nativescript-core/debugger/devtools-elements.ios.ts b/nativescript-core/debugger/devtools-elements.ios.ts index 2d7046639..3fadf8526 100644 --- a/nativescript-core/debugger/devtools-elements.ios.ts +++ b/nativescript-core/debugger/devtools-elements.ios.ts @@ -1,5 +1,8 @@ import { InspectorEvents, InspectorCommands } from "./devtools-elements"; -import { getDocument, getComputedStylesForNode, removeNode, setAttributeAsText } from "./devtools-elements.common"; +import { + getDocument, getComputedStylesForNode, removeNode, + setAttributeAsText, highlightNode, hideHighlight, setInspectMode +} from "./devtools-elements.common"; import { registerInspectorEvents, DOMNode } from "./dom-node"; export function attachDOMInspectorEventCallbacks(DOMDomainFrontend: InspectorEvents) { @@ -16,6 +19,10 @@ export function attachDOMInspectorCommandCallbacks(DOMDomainBackend: InspectorCo DOMDomainBackend.getDocument = getDocument; DOMDomainBackend.removeNode = removeNode; DOMDomainBackend.setAttributeAsText = setAttributeAsText; + + DOMDomainBackend.highlightNode = highlightNode; + DOMDomainBackend.hideHighlight = hideHighlight; + DOMDomainBackend.setInspectMode = setInspectMode; } export function attachCSSInspectorCommandCallbacks(CSSDomainBackend: InspectorCommands) { diff --git a/nativescript-core/debugger/dom-node.d.ts b/nativescript-core/debugger/dom-node.d.ts index dfc287be1..5f1fb299e 100644 --- a/nativescript-core/debugger/dom-node.d.ts +++ b/nativescript-core/debugger/dom-node.d.ts @@ -19,4 +19,7 @@ export declare class DOMNode { getComputedProperties(): CSSComputedStyleProperty[]; dispose(): void; toJSON(): string; + + requestHighligh(): void; + static requestCancelInspect(): void; } diff --git a/nativescript-core/debugger/dom-node.ts b/nativescript-core/debugger/dom-node.ts index f8ca58288..17f7419fe 100644 --- a/nativescript-core/debugger/dom-node.ts +++ b/nativescript-core/debugger/dom-node.ts @@ -200,6 +200,18 @@ export class DOMNode { return result; } + requestHighligh() { + notifyInspector((ins) => { + ins.nodeHighlightRequested(this.nodeId); + }); + } + + static requestCancelInspect() { + notifyInspector((ins) => { + ins.inspectModeCanceled(); + }); + } + dispose() { unregisterNode(this); this.viewRef.clear(); diff --git a/nativescript-core/debugger/webinspector-overlay.ios.ts b/nativescript-core/debugger/webinspector-overlay.ios.ts new file mode 100644 index 000000000..9f0873b38 --- /dev/null +++ b/nativescript-core/debugger/webinspector-overlay.ios.ts @@ -0,0 +1,61 @@ +import * as inspectorCommandTypes from "./InspectorBackendCommands.ios"; +const inspectorCommands: typeof inspectorCommandTypes = require("./InspectorBackendCommands"); + +import * as debuggerDomains from "./debugger"; + +import { attachDOMInspectorCommandCallbacks, InspectorCommands } from "./devtools-elements"; + +@inspectorCommands.DomainDispatcher("Overlay") +export class OverlayDomainDebugger { + + private _enabled: boolean; + public commands: InspectorCommands; + + constructor() { + + this.commands = ({}); + + attachDOMInspectorCommandCallbacks(this.commands); + + // By default start enabled because we can miss the "enable event when + // running with `--debug-brk` -- the frontend will send it before we've been created + this.enable(); + } + + get enabled(): boolean { + return this._enabled; + } + + enable(): void { + if (debuggerDomains.getOverlay()) { + throw new Error("One OverlayDebugger may be enabled at a time."); + } else { + debuggerDomains.setOverlay(this); + } + this._enabled = true; + } + + disable(): void { + if (debuggerDomains.getOverlay() === this) { + debuggerDomains.setOverlay(null); + } + this._enabled = false; + } + + highlightNode(params: any): void { + // console.log("---> highlightNode", params.nodeId); + // console.dir(params); + this.commands.highlightNode(params.nodeId); + } + + hideHighlight(): void { + // console.log("---> hideHighlight"); + this.commands.hideHighlight(); + } + + setInspectMode(params: any): void { + // console.log("---> setInspectMode", params.mode) + // console.dir(params); + this.commands.setInspectMode(params.mode); + } +} \ No newline at end of file diff --git a/nativescript-core/inspector_modules.ios.ts b/nativescript-core/inspector_modules.ios.ts index 6b61ed93e..4a8b66ba9 100644 --- a/nativescript-core/inspector_modules.ios.ts +++ b/nativescript-core/inspector_modules.ios.ts @@ -3,4 +3,5 @@ require("./globals/ts-helpers"); require("./debugger/webinspector-network"); require("./debugger/webinspector-dom"); require("./debugger/webinspector-css"); +require("./debugger/webinspector-overlay"); console.log("Finished loading inspector modules.");