blob: 7c5433f438b1ec79069ea12fd9f91b2d0e185fa7 [file] [log] [blame]
// Copyright 2015 Google. All rights reserved. Use of this source code is
// governed by a BSD-style license that can be found in the LICENSE file.
import 'dart:async';
import 'dart:collection';
import '../webkit_inspection_protocol.dart';
/// Implementation of the
/// https://developer.chrome.com/devtools/docs/protocol/1.1/dom
class WipDom extends WipDomain {
WipDom(WipConnection connection) : super(connection);
Future<Map<String, String>> getAttributes(int nodeId) async {
WipResponse resp =
await sendCommand('DOM.getAttributes', params: {'nodeId': nodeId});
return _attributeListToMap((resp.result!['attributes'] as List).cast());
}
Future<Node> getDocument() async =>
Node((await sendCommand('DOM.getDocument')).result!['root']
as Map<String, dynamic>);
Future<String> getOuterHtml(int nodeId) async =>
(await sendCommand('DOM.getOuterHTML', params: {'nodeId': nodeId}))
.result!['root'] as String;
Future<void> hideHighlight() => sendCommand('DOM.hideHighlight');
Future<void> highlightNode(
int nodeId, {
Rgba? borderColor,
Rgba? contentColor,
Rgba? marginColor,
Rgba? paddingColor,
bool? showInfo,
}) {
var params = <String, dynamic>{
'nodeId': nodeId,
'highlightConfig': <String, dynamic>{
if (borderColor != null) 'borderColor': borderColor,
if (contentColor != null) 'contentColor': contentColor,
if (marginColor != null) 'marginColor': marginColor,
if (paddingColor != null) 'paddingColor': paddingColor,
if (showInfo != null) 'showInfo': showInfo,
},
};
return sendCommand('DOM.highlightNode', params: params);
}
Future<void> highlightRect(int x, int y, int width, int height,
{Rgba? color, Rgba? outlineColor}) {
var params = <String, dynamic>{
'x': x,
'y': y,
'width': width,
'height': height
};
if (color != null) {
params['color'] = color;
}
if (outlineColor != null) {
params['outlineColor'] = outlineColor;
}
return sendCommand('DOM.highlightRect', params: params);
}
Future<int> moveTo(int nodeId, int targetNodeId,
{int? insertBeforeNodeId}) async {
var params = {'nodeId': nodeId, 'targetNodeId': targetNodeId};
if (insertBeforeNodeId != null) {
params['insertBeforeNodeId'] = insertBeforeNodeId;
}
var resp = await sendCommand('DOM.moveTo', params: params);
return resp.result!['nodeId'] as int;
}
Future<int> querySelector(int nodeId, String selector) async {
var resp = await sendCommand('DOM.querySelector',
params: {'nodeId': nodeId, 'selector': selector});
return resp.result!['nodeId'] as int;
}
Future<List<int>> querySelectorAll(int nodeId, String selector) async {
var resp = await sendCommand('DOM.querySelectorAll',
params: {'nodeId': nodeId, 'selector': selector});
return (resp.result!['nodeIds'] as List).cast();
}
Future<void> removeAttribute(int nodeId, String name) =>
sendCommand('DOM.removeAttribute',
params: {'nodeId': nodeId, 'name': name});
Future<void> removeNode(int nodeId) =>
sendCommand('DOM.removeNode', params: {'nodeId': nodeId});
Future<void> requestChildNodes(int nodeId) =>
sendCommand('DOM.requestChildNodes', params: {'nodeId': nodeId});
Future<int> requestNode(String objectId) async {
var resp =
await sendCommand('DOM.requestNode', params: {'objectId': objectId});
return resp.result!['nodeId'] as int;
}
Future<RemoteObject> resolveNode(int nodeId, {String? objectGroup}) async {
var params = <String, dynamic>{'nodeId': nodeId};
if (objectGroup != null) {
params['objectGroup'] = objectGroup;
}
var resp = await sendCommand('DOM.resolveNode', params: params);
return RemoteObject(resp.result!['object'] as Map<String, dynamic>);
}
Future<void> setAttributeValue(int nodeId, String name, String value) =>
sendCommand('DOM.setAttributeValue',
params: {'nodeId': nodeId, 'name': name, 'value': value});
Future<void> setAttributesAsText(int nodeId, String text, {String? name}) {
var params = {'nodeId': nodeId, 'text': text};
if (name != null) {
params['name'] = name;
}
return sendCommand('DOM.setAttributeValue', params: params);
}
Future<int> setNodeName(int nodeId, String name) async {
var resp = await sendCommand('DOM.setNodeName',
params: {'nodeId': nodeId, 'name': name});
return resp.result!['nodeId'] as int;
}
Future<void> setNodeValue(int nodeId, String value) =>
sendCommand('DOM.setNodeValue',
params: {'nodeId': nodeId, 'value': value});
Future<void> setOuterHtml(int nodeId, String outerHtml) =>
sendCommand('DOM.setOuterHTML',
params: {'nodeId': nodeId, 'outerHtml': outerHtml});
Stream<AttributeModifiedEvent> get onAttributeModified => eventStream(
'DOM.attributeModified',
(WipEvent event) => AttributeModifiedEvent(event.json));
Stream<AttributeRemovedEvent> get onAttributeRemoved => eventStream(
'DOM.attributeRemoved',
(WipEvent event) => AttributeRemovedEvent(event.json));
Stream<CharacterDataModifiedEvent> get onCharacterDataModified => eventStream(
'DOM.characterDataModified',
(WipEvent event) => CharacterDataModifiedEvent(event.json));
Stream<ChildNodeCountUpdatedEvent> get onChildNodeCountUpdated => eventStream(
'DOM.childNodeCountUpdated',
(WipEvent event) => ChildNodeCountUpdatedEvent(event.json));
Stream<ChildNodeInsertedEvent> get onChildNodeInserted => eventStream(
'DOM.childNodeInserted',
(WipEvent event) => ChildNodeInsertedEvent(event.json));
Stream<ChildNodeRemovedEvent> get onChildNodeRemoved => eventStream(
'DOM.childNodeRemoved',
(WipEvent event) => ChildNodeRemovedEvent(event.json));
Stream<DocumentUpdatedEvent> get onDocumentUpdated => eventStream(
'DOM.documentUpdated',
(WipEvent event) => DocumentUpdatedEvent(event.json));
Stream<SetChildNodesEvent> get onSetChildNodes => eventStream(
'DOM.setChildNodes', (WipEvent event) => SetChildNodesEvent(event.json));
}
class AttributeModifiedEvent extends WipEvent {
AttributeModifiedEvent(Map<String, dynamic> json) : super(json);
int get nodeId => params!['nodeId'] as int;
String get name => params!['name'] as String;
String get value => params!['value'] as String;
}
class AttributeRemovedEvent extends WipEvent {
AttributeRemovedEvent(Map<String, dynamic> json) : super(json);
int get nodeId => params!['nodeId'] as int;
String get name => params!['name'] as String;
}
class CharacterDataModifiedEvent extends WipEvent {
CharacterDataModifiedEvent(Map<String, dynamic> json) : super(json);
int get nodeId => params!['nodeId'] as int;
String get characterData => params!['characterData'] as String;
}
class ChildNodeCountUpdatedEvent extends WipEvent {
ChildNodeCountUpdatedEvent(Map<String, dynamic> json) : super(json);
int get nodeId => params!['nodeId'] as int;
int get childNodeCount => params!['childNodeCount'] as int;
}
class ChildNodeInsertedEvent extends WipEvent {
ChildNodeInsertedEvent(Map<String, dynamic> json) : super(json);
int get parentNodeId => params!['parentNodeId'] as int;
int get previousNodeId => params!['previousNodeId'] as int;
late final node = Node(params!['node'] as Map<String, dynamic>);
}
class ChildNodeRemovedEvent extends WipEvent {
ChildNodeRemovedEvent(Map<String, dynamic> json) : super(json);
int get parentNodeId => params!['parentNodeId'] as int;
int get nodeId => params!['nodeId'] as int;
}
class DocumentUpdatedEvent extends WipEvent {
DocumentUpdatedEvent(Map<String, dynamic> json) : super(json);
}
class SetChildNodesEvent extends WipEvent {
SetChildNodesEvent(Map<String, dynamic> json) : super(json);
int get nodeId => params!['parentId'] as int;
Iterable<Node> get nodes sync* {
for (Map node in params!['nodes']) {
yield Node(node as Map<String, dynamic>);
}
}
@override
String toString() => 'SetChildNodes $nodeId: $nodes';
}
/// The backend keeps track of which DOM nodes have been sent,
/// will only send each node once, and will only send events
/// for nodes that have been sent.
class Node {
final Map<String, dynamic> _map;
Node(this._map);
late final Map<String, String>? attributes = _map.containsKey('attributes')
? _attributeListToMap((_map['attributes'] as List).cast())
: null;
int? get childNodeCount => _map['childNodeCount'] as int?;
late final List<Node>? children = _map.containsKey('children')
? UnmodifiableListView((_map['children'] as List)
.map((c) => Node(c as Map<String, dynamic>)))
: null;
Node? get contentDocument {
if (_map.containsKey('contentDocument')) {
return Node(_map['contentDocument'] as Map<String, dynamic>);
}
return null;
}
String? get documentUrl => _map['documentURL'] as String?;
String? get internalSubset => _map['internalSubset'] as String?;
String get localName => _map['localName'] as String;
String? get name => _map['name'] as String?;
int get nodeId => _map['nodeId'] as int;
String get nodeName => _map['nodeName'] as String;
int get nodeType => _map['nodeType'] as int;
String get nodeValue => _map['nodeValue'] as String;
String? get publicId => _map['publicId'] as String?;
String? get systemId => _map['systemId'] as String?;
String? get value => _map['value'] as String?;
String? get xmlVersion => _map['xmlVersion'] as String?;
@override
String toString() => '$nodeName: $nodeId $attributes';
}
class Rgba {
final int? a;
final int b;
final int r;
final int g;
Rgba(this.r, this.g, this.b, [this.a]);
Map<String, int> toJson() {
var json = {'r': r, 'g': g, 'b': b};
if (a != null) {
json['a'] = a!;
}
return json;
}
}
Map<String, String> _attributeListToMap(List<String> attrList) {
var attributes = <String, String>{};
for (int i = 0; i < attrList.length; i += 2) {
attributes[attrList[i]] = attrList[i + 1];
}
return UnmodifiableMapView(attributes);
}