initWithContext method

void initWithContext(
  1. DebuggingContext context
)

Initializes the DevTools service with a debugging context.

Sets up the inspector server and UI inspector, enabling Chrome DevTools to connect to and debug the WebF content.

@param context The DebuggingContext instance to enable debugging for

Implementation

void initWithContext(DebuggingContext context) {
  _contextDevToolMap[context.contextId] = this;
  _context = context;
  _uiInspector = UIInspector(this);
  if (DebugFlags.enableDevToolsLogs) {
    devToolsLogger.fine('[DevTools] initWithContext: ctx=${context.contextId} url=${context.url ?? ''}');
  }
  // Legacy full refresh callback
  context.debugDOMTreeChanged = () => uiInspector!.onDOMTreeChanged();
  // Incremental mutation callbacks (only used by new DOM incremental update logic).
  context.debugChildNodeInserted = (parent, node, previousSibling) {
    if (this is ChromeDevToolsService) {
      bool didSeed = false;
      // Skip whitespace-only text nodes to keep Elements clean
      try {
        if (node is TextNode && node.data.trim().isEmpty) {
          if (DebugFlags.enableDevToolsProtocolLogs) {
            final pId = context.forDevtoolsNodeId(parent);
            devToolsProtocolLogger.finer(
                '[DevTools] (skip) DOM.childNodeInserted whitespace-only text under parent=$pId');
          }
          // Even if skipping the insertion event, ensure the parent is seeded
          // so that any subsequent characterDataModified for this text node
          // references a node already known by the frontend.
          try {
            final pId = context.forDevtoolsNodeId(parent);
            if (!ChromeDevToolsService.unifiedService._isParentSeeded(pId)) {
              final children = <Map>[];
              for (final c in parent.childNodes) {
                if (c is Element || (c is TextNode && c.data.trim().isNotEmpty)) {
                  children.add(InspectorNode(c).toJson());
                }
              }
              ChromeDevToolsService.unifiedService.sendEventToFrontend(
                  DOMSetChildNodesEvent(parentId: pId, nodes: children));
              if (DebugFlags.enableDevToolsProtocolLogs) {
                try {
                  final ids = children.map((m) => m['nodeId']).toList();
                  devToolsProtocolLogger.finer(
                      '[DevTools] -> DOM.setChildNodes parent=$pId count=${children.length} (seed) ids=$ids');
                } catch (_) {
                  devToolsProtocolLogger.finer(
                      '[DevTools] -> DOM.setChildNodes parent=$pId count=${children.length} (seed)');
                }
              }
              ChromeDevToolsService.unifiedService._markParentSeeded(pId);
              didSeed = true;
            }
          } catch (_) {}
          return;
        }
      } catch (_) {}
      // Ensure the parent has an established children list in DevTools.
      try {
        final pId = context.forDevtoolsNodeId(parent);
        if (!ChromeDevToolsService.unifiedService._isParentSeeded(pId)) {
          final children = <Map>[];
          for (final c in parent.childNodes) {
            if (c is Element || (c is TextNode && c.data.trim().isNotEmpty)) {
              children.add(InspectorNode(c).toJson());
            }
          }
          ChromeDevToolsService.unifiedService.sendEventToFrontend(
              DOMSetChildNodesEvent(parentId: pId, nodes: children));
          if (DebugFlags.enableDevToolsProtocolLogs) {
            try {
              final ids = children.map((m) => m['nodeId']).toList();
              devToolsProtocolLogger.finer(
                  '[DevTools] -> DOM.setChildNodes parent=$pId count=${children.length} (seed) ids=$ids');
            } catch (_) {
              devToolsProtocolLogger
                  .finer('[DevTools] -> DOM.setChildNodes parent=$pId count=${children.length} (seed)');
            }
          }
          ChromeDevToolsService.unifiedService._markParentSeeded(pId);
          didSeed = true;
        }
      } catch (_) {}

      // If we just seeded the children list, do not also emit an insert for the same node.
      if (didSeed) {
        // Still update child count since structure changed
        try {
          final pId = context.forDevtoolsNodeId(parent);
          final count = parent.childNodes
              .where((c) => c is Element || (c is TextNode && c.data.trim().isNotEmpty))
              .length;
          ChromeDevToolsService.unifiedService.sendEventToFrontend(
              DOMChildNodeCountUpdatedEvent(node: parent, childNodeCount: count));
        } catch (_) {}
        return;
      }
      if (DebugFlags.enableDevToolsProtocolLogs) {
        try {
          final pId = context.forDevtoolsNodeId(parent);
          final nId = context.forDevtoolsNodeId(node);
          final prevId = previousSibling != null ? context.forDevtoolsNodeId(previousSibling) : 0;
          final name = node.nodeName;
          devToolsProtocolLogger.finer(
              '[DevTools] -> DOM.childNodeInserted parent=$pId prev=$prevId node=$nId name=$name');
        } catch (_) {}
      }
      ChromeDevToolsService.unifiedService
          .sendEventToFrontend(DOMChildNodeInsertedEvent(
        parent: parent,
        node: node,
        previousSibling: previousSibling,
      ));
      // Update child count for the parent
      try {
        final count = parent.childNodes
            .where((c) => c is Element || (c is TextNode && c.data.trim().isNotEmpty))
            .length;
        ChromeDevToolsService.unifiedService.sendEventToFrontend(
            DOMChildNodeCountUpdatedEvent(node: parent, childNodeCount: count));
      } catch (_) {}
    }
  };
  context.debugChildNodeRemoved = (parent, node) {
    if (this is ChromeDevToolsService) {
      bool didSeed = false;
      // Skip whitespace-only text nodes (never sent to frontend)
      try {
        if (node is TextNode && node.data.trim().isEmpty) {
          if (DebugFlags.enableDevToolsProtocolLogs) {
            final pId = context.forDevtoolsNodeId(parent);
            devToolsProtocolLogger.finer(
                '[DevTools] (skip) DOM.childNodeRemoved whitespace-only text under parent=$pId');
          }
          return;
        }
      } catch (_) {}
      // Ensure the parent has an established children list in DevTools.
      try {
        final pId = context.forDevtoolsNodeId(parent);
        if (!ChromeDevToolsService.unifiedService._isParentSeeded(pId)) {
          final children = <Map>[];
          for (final c in parent.childNodes) {
            if (c is Element || (c is TextNode && c.data.trim().isNotEmpty)) {
              children.add(InspectorNode(c).toJson());
            }
          }
          ChromeDevToolsService.unifiedService.sendEventToFrontend(
              DOMSetChildNodesEvent(parentId: pId, nodes: children));
          if (DebugFlags.enableDevToolsProtocolLogs) {
            try {
              final ids = children.map((m) => m['nodeId']).toList();
              devToolsProtocolLogger.finer(
                  '[DevTools] -> DOM.setChildNodes parent=$pId count=${children.length} (seed) ids=$ids');
            } catch (_) {
              devToolsProtocolLogger
                  .finer('[DevTools] -> DOM.setChildNodes parent=$pId count=${children.length} (seed)');
            }
          }
          ChromeDevToolsService.unifiedService._markParentSeeded(pId);
          didSeed = true;
        }
      } catch (_) {}
      // If we just seeded with the current children (which no longer includes the removed node),
      // still update the count and skip removal event for unknown node.
      if (didSeed) {
        try {
          final count = parent.childNodes
              .where((c) => c is Element || (c is TextNode && c.data.trim().isNotEmpty))
              .length;
          ChromeDevToolsService.unifiedService.sendEventToFrontend(
              DOMChildNodeCountUpdatedEvent(node: parent, childNodeCount: count));
        } catch (_) {}
        return;
      }
      if (DebugFlags.enableDevToolsProtocolLogs) {
        try {
          final pId = context.forDevtoolsNodeId(parent);
          final nId = context.forDevtoolsNodeId(node);
          final name = node.nodeName;
          devToolsProtocolLogger
              .finer('[DevTools] -> DOM.childNodeRemoved parent=$pId node=$nId name=$name');
        } catch (_) {}
      }
      ChromeDevToolsService.unifiedService
          .sendEventToFrontend(DOMChildNodeRemovedEvent(
        parent: parent,
        node: node,
      ));
      // Update child count for the parent
      try {
        final count = parent.childNodes
            .where((c) => c is Element || (c is TextNode && c.data.trim().isNotEmpty))
            .length;
        ChromeDevToolsService.unifiedService.sendEventToFrontend(
            DOMChildNodeCountUpdatedEvent(node: parent, childNodeCount: count));
      } catch (_) {}
    }
  };
  context.debugAttributeModified = (element, name, value) {
    if (this is ChromeDevToolsService) {
      if (DebugFlags.enableDevToolsProtocolLogs) {
        try {
          final id = context.forDevtoolsNodeId(element);
          devToolsProtocolLogger
              .finer('[DevTools] -> DOM.attributeModified node=$id name=$name value=${value ?? ''}');
        } catch (_) {}
      }
      ChromeDevToolsService.unifiedService
          .sendEventToFrontend(DOMAttributeModifiedEvent(
        element: element,
        name: name,
        value: value,
      ));
      // Also notify CSS module tracking that the element's computed style may be updated
      final cssModule = uiInspector?.moduleRegistrar['CSS'];
      if (cssModule is InspectCSSModule) {
        final nodeId = context.forDevtoolsNodeId(element);
        cssModule.markComputedStyleDirtyByNodeId(nodeId);
      }
    }
  };
  context.debugAttributeRemoved = (element, name) {
    if (this is ChromeDevToolsService) {
      ChromeDevToolsService.unifiedService
          .sendEventToFrontend(DOMAttributeRemovedEvent(
        element: element,
        name: name,
      ));
      if (DebugFlags.enableDevToolsProtocolLogs) {
        devToolsProtocolLogger.finer('[DevTools] -> DOM.attributeRemoved name=$name');
      }
      final cssModule = uiInspector?.moduleRegistrar['CSS'];
      if (cssModule is InspectCSSModule) {
        final nodeId = context.forDevtoolsNodeId(element);
        cssModule.markComputedStyleDirtyByNodeId(nodeId);
      }
    }
  };
  context.debugCharacterDataModified = (textNode) {
    if (this is ChromeDevToolsService) {
      // Ignore modifications that make a text node whitespace-only
      try {
        if (textNode.data.trim().isEmpty) {
          if (DebugFlags.enableDevToolsProtocolLogs) {
            final id = context.forDevtoolsNodeId(textNode);
            devToolsProtocolLogger
                .finer('[DevTools] (skip) DOM.characterDataModified node=$id (whitespace-only)');
          }
          return;
        }
      } catch (_) {}
      // Ensure parent is seeded so frontend knows the text node
      try {
        final parent = textNode.parentNode;
        if (parent != null) {
          final pId = context.forDevtoolsNodeId(parent);
          if (!ChromeDevToolsService.unifiedService._isParentSeeded(pId)) {
            final children = <Map>[];
            for (final c in parent.childNodes) {
              if (c is Element || (c is TextNode && c.data.trim().isNotEmpty)) {
                children.add(InspectorNode(c).toJson());
              }
            }
            ChromeDevToolsService.unifiedService.sendEventToFrontend(
                DOMSetChildNodesEvent(parentId: pId, nodes: children));
            if (DebugFlags.enableDevToolsProtocolLogs) {
              try {
                final ids = children.map((m) => m['nodeId']).toList();
                devToolsProtocolLogger.finer(
                    '[DevTools] -> DOM.setChildNodes parent=$pId count=${children.length} (seed) ids=$ids');
              } catch (_) {
                devToolsProtocolLogger.finer(
                    '[DevTools] -> DOM.setChildNodes parent=$pId count=${children.length} (seed)');
              }
            }
            ChromeDevToolsService.unifiedService._markParentSeeded(pId);
          }
          // If the text node hasn't been announced yet (e.g., was whitespace when inserted),
          // send an insertion now before characterDataModified so the frontend can track it.
          try {
            final nId = context.forDevtoolsNodeId(textNode);
            if (!ChromeDevToolsService.unifiedService._isNodeKnown(nId)) {
              Node? prev = textNode.previousSibling;
              Node? chosenPrev;
              while (prev != null) {
                try {
                  final pid = context.forDevtoolsNodeId(prev);
                  if (ChromeDevToolsService.unifiedService._isNodeKnown(pid)) {
                    chosenPrev = prev;
                    break;
                  }
                } catch (_) {}
                prev = prev.previousSibling;
              }
              ChromeDevToolsService.unifiedService.sendEventToFrontend(
                  DOMChildNodeInsertedEvent(
                      parent: parent,
                      node: textNode,
                      previousSibling: chosenPrev));
              if (DebugFlags.enableDevToolsProtocolLogs) {
                try {
                  final prevId = chosenPrev != null ? context.forDevtoolsNodeId(chosenPrev) : 0;
                  devToolsProtocolLogger.finer(
                      '[DevTools] -> DOM.childNodeInserted parent=$pId prev=$prevId node=$nId name=#text');
                } catch (_) {}
              }
            }
          } catch (_) {}
        }
      } catch (_) {}
      if (DebugFlags.enableDevToolsProtocolLogs) {
        try {
          final id = context.forDevtoolsNodeId(textNode);
          final preview = textNode.data.length > 30
              ? textNode.data.substring(0, 30) + '…'
              : textNode.data;
          devToolsProtocolLogger
              .finer('[DevTools] -> DOM.characterDataModified node=$id data="$preview"');
        } catch (_) {}
      }
      ChromeDevToolsService.unifiedService
          .sendEventToFrontend(DOMCharacterDataModifiedEvent(
        node: textNode,
      ));
      final cssModule = uiInspector?.moduleRegistrar['CSS'];
      if (cssModule is InspectCSSModule) {
        final nodeId = context.forDevtoolsNodeId(textNode);
        cssModule.markComputedStyleDirtyByNodeId(nodeId);
      }
    }
  };
}