initWithContext method
void
initWithContext(
- 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);
}
}
};
}