improve html api

This commit is contained in:
nyne
2024-10-18 16:50:47 +08:00
parent 5812cfc701
commit 7a6e827bf9
2 changed files with 195 additions and 131 deletions

View File

@@ -478,8 +478,8 @@ class HtmlDocument {
key: this.key, key: this.key,
query: query query: query
}) })
if(!k) return null; if(k == null) return null;
return new HtmlElement(k); return new HtmlElement(k, this.key);
} }
/** /**
@@ -494,7 +494,19 @@ class HtmlDocument {
key: this.key, key: this.key,
query: query query: query
}) })
return ks.map(k => new HtmlElement(k)); return ks.map(k => new HtmlElement(k, this.key));
}
/**
* Dispose the HTML document.
* This should be called when the document is no longer needed.
*/
dispose() {
sendMessage({
method: "html",
function: "dispose",
key: this.key
})
} }
} }
@@ -504,12 +516,16 @@ class HtmlDocument {
class HtmlElement { class HtmlElement {
key = 0; key = 0;
doc = 0;
/** /**
* Constructor for HtmlDom. * Constructor for HtmlDom.
* @param {number} k - The key of the element. * @param {number} k - The key of the element.
* @param {number} doc - The key of the document.
*/ */
constructor(k) { constructor(k, doc) {
this.key = k; this.key = k;
this.doc = doc;
} }
/** /**
@@ -520,7 +536,8 @@ class HtmlElement {
return sendMessage({ return sendMessage({
method: "html", method: "html",
function: "getText", function: "getText",
key: this.key key: this.key,
doc: this.doc,
}) })
} }
@@ -532,7 +549,8 @@ class HtmlElement {
return sendMessage({ return sendMessage({
method: "html", method: "html",
function: "getAttributes", function: "getAttributes",
key: this.key key: this.key,
doc: this.doc,
}) })
} }
@@ -546,10 +564,11 @@ class HtmlElement {
method: "html", method: "html",
function: "dom_querySelector", function: "dom_querySelector",
key: this.key, key: this.key,
query: query query: query,
doc: this.doc,
}) })
if(!k) return null; if(k == null) return null;
return new HtmlElement(k); return new HtmlElement(k, this.doc);
} }
/** /**
@@ -562,9 +581,10 @@ class HtmlElement {
method: "html", method: "html",
function: "dom_querySelectorAll", function: "dom_querySelectorAll",
key: this.key, key: this.key,
query: query query: query,
doc: this.doc,
}) })
return ks.map(k => new HtmlElement(k)); return ks.map(k => new HtmlElement(k, this.doc));
} }
/** /**
@@ -575,9 +595,10 @@ class HtmlElement {
let ks = sendMessage({ let ks = sendMessage({
method: "html", method: "html",
function: "getChildren", function: "getChildren",
key: this.key key: this.key,
doc: this.doc,
}) })
return ks.map(k => new HtmlElement(k)); return ks.map(k => new HtmlElement(k, this.doc));
} }
/** /**
@@ -588,9 +609,10 @@ class HtmlElement {
let ks = sendMessage({ let ks = sendMessage({
method: "html", method: "html",
function: "getNodes", function: "getNodes",
key: this.key key: this.key,
doc: this.doc,
}) })
return ks.map(k => new HtmlNode(k)); return ks.map(k => new HtmlNode(k, this.doc));
} }
/** /**
@@ -601,7 +623,8 @@ class HtmlElement {
return sendMessage({ return sendMessage({
method: "html", method: "html",
function: "getInnerHTML", function: "getInnerHTML",
key: this.key key: this.key,
doc: this.doc,
}) })
} }
@@ -613,9 +636,10 @@ class HtmlElement {
let k = sendMessage({ let k = sendMessage({
method: "html", method: "html",
function: "getParent", function: "getParent",
key: this.key key: this.key,
doc: this.doc,
}) })
if(!k) return null; if(k == null) return null;
return new HtmlElement(k); return new HtmlElement(k);
} }
} }
@@ -623,8 +647,11 @@ class HtmlElement {
class HtmlNode { class HtmlNode {
key = 0; key = 0;
constructor(k) { doc = 0;
constructor(k, doc) {
this.key = k; this.key = k;
this.doc = doc;
} }
/** /**
@@ -635,7 +662,8 @@ class HtmlNode {
return sendMessage({ return sendMessage({
method: "html", method: "html",
function: "node_text", function: "node_text",
key: this.key key: this.key,
doc: this.doc,
}) })
} }
@@ -647,7 +675,8 @@ class HtmlNode {
return sendMessage({ return sendMessage({
method: "html", method: "html",
function: "node_type", function: "node_type",
key: this.key key: this.key,
doc: this.doc,
}) })
} }
@@ -659,10 +688,11 @@ class HtmlNode {
let k = sendMessage({ let k = sendMessage({
method: "html", method: "html",
function: "node_toElement", function: "node_toElement",
key: this.key key: this.key,
doc: this.doc,
}) })
if(!k) return null; if(k == null) return null;
return new HtmlElement(k); return new HtmlElement(k, this.doc);
} }
} }

View File

@@ -83,7 +83,7 @@ class JsEngine with _JSEngineApi {
} }
} }
dynamic _messageReceiver(dynamic message) { Object? _messageReceiver(dynamic message) {
try { try {
if (message is Map<dynamic, dynamic>) { if (message is Map<dynamic, dynamic>) {
String method = message["method"] as String; String method = message["method"] as String;
@@ -166,6 +166,7 @@ class JsEngine with _JSEngineApi {
} }
} }
} }
return null;
} catch (e, s) { } catch (e, s) {
Log.error("Failed to handle message: $message\n$e\n$s", "JsEngine"); Log.error("Failed to handle message: $message\n$e\n$s", "JsEngine");
rethrow; rethrow;
@@ -224,130 +225,57 @@ class JsEngine with _JSEngineApi {
} }
mixin class _JSEngineApi { mixin class _JSEngineApi {
final Map<int, dom.Document> _documents = {};
final Map<int, List<int>> _documentElements = {};
final Map<int, List<int>> _documentNodes = {};
final Map<int, dom.Element> _elements = {};
final Map<int, dom.Node> _nodes = {};
CookieJarSql? _cookieJar; CookieJarSql? _cookieJar;
int _elementKey = 0; final _documents = <int, DocumentWrapper>{};
int _nodeKey = 0;
dynamic handleHtmlCallback(Map<String, dynamic> data) { Object? handleHtmlCallback(Map<String, dynamic> data) {
print(data);
switch (data["function"]) { switch (data["function"]) {
case "parse": case "parse":
_documents[data["key"]] = html.parse(data["data"]); _documents[data["key"]] = DocumentWrapper.parse(data["data"]);
_documentElements[data["key"]] = [];
_documentNodes[data["key"]] = [];
Future.delayed(const Duration(seconds: 1), () {
handleHtmlCallback({"function": "dispose", "key": data["key"]});
});
return null; return null;
case "querySelector": case "querySelector":
var res = _documents[data["key"]]!.querySelector(data["query"]); var key = data["key"];
if (res == null) return null; return _documents[key]!.querySelector(data["query"]);
_elements[_elementKey] = res;
_elementKey++;
_documentElements[data["key"]]!.add(_elementKey - 1);
return _elementKey - 1;
case "querySelectorAll": case "querySelectorAll":
var res = _documents[data["key"]]!.querySelectorAll(data["query"]); var key = data["key"];
var keys = <int>[]; return _documents[key]!.querySelectorAll(data["query"]);
for (var element in res) {
_elements[_elementKey] = element;
keys.add(_elementKey);
_documentElements[data["key"]]!.add(_elementKey);
_elementKey++;
}
return keys;
case "getText": case "getText":
return _elements[data["key"]]!.text; return _documents[data["doc"]]!.elementGetText(data["key"]);
case "getAttributes": case "getAttributes":
return _elements[data["key"]]!.attributes; var res = _documents[data["doc"]]!.elementGetAttributes(data["key"]);
return res;
case "dom_querySelector": case "dom_querySelector":
var res = _elements[data["key"]]!.querySelector(data["query"]); var doc = _documents[data["doc"]]!;
if (res == null) return null; return doc.elementQuerySelector(data["key"], data["query"]);
_elements[_elements.length] = res;
var docKey = _documentElements.keys
.firstWhere((key) => _documentElements[key]!.contains(data["key"]));
_documentElements[docKey]!.add(_elements.length - 1);
return _elements.length - 1;
case "dom_querySelectorAll": case "dom_querySelectorAll":
var res = _elements[data["key"]]!.querySelectorAll(data["query"]); var doc = _documents[data["doc"]]!;
var keys = <int>[]; return doc.elementQuerySelectorAll(data["key"], data["query"]);
var docKey = _documentElements.keys
.firstWhere((key) => _documentElements[key]!.contains(data["key"]));
for (var element in res) {
_elements[_elements.length] = element;
keys.add(_elements.length - 1);
_documentElements[docKey]!.add(_elements.length - 1);
}
return keys;
case "getChildren": case "getChildren":
var res = _elements[data["key"]]!.children; var doc = _documents[data["doc"]]!;
var keys = <int>[]; return doc.elementGetChildren(data["key"]);
var docKey = _documentElements.keys
.firstWhere((key) => _documentElements[key]!.contains(data["key"]));
for (var element in res) {
_elements[_elements.length] = element;
keys.add(_elements.length - 1);
_documentElements[docKey]!.add(_elements.length - 1);
}
return keys;
case "getNodes": case "getNodes":
var res = _elements[data["key"]]!.nodes; var doc = _documents[data["doc"]]!;
var keys = <int>[]; return doc.elementGetNodes(data["key"]);
var docKey = _documentElements.keys
.firstWhere((key) => _documentElements[key]!.contains(data["key"]));
for (var node in res) {
_nodes[_nodeKey] = node;
keys.add(_nodeKey);
_documentNodes[docKey]!.add(_nodeKey);
_nodeKey++;
}
return keys;
case "getInnerHTML": case "getInnerHTML":
return _elements[data["key"]]!.innerHtml; var doc = _documents[data["doc"]]!;
return doc.elementGetInnerHTML(data["key"]);
case "getParent": case "getParent":
var res = _elements[data["key"]]!.parent; var doc = _documents[data["doc"]]!;
if (res == null) return null; return doc.elementGetParent(data["key"]);
_elements[_elementKey] = res;
_documentElements[data["key"]]!.add(_elementKey);
return _elementKey++;
case "node_text": case "node_text":
return _nodes[data["key"]]!.text; return _documents[data["doc"]]!.nodeGetText(data["key"]);
case "node_type": case "node_type":
return switch (_nodes[data["key"]]!.nodeType) { return _documents[data["doc"]]!.nodeType(data["key"]);
dom.Node.ELEMENT_NODE => "element",
dom.Node.TEXT_NODE => "text",
dom.Node.COMMENT_NODE => "comment",
dom.Node.DOCUMENT_NODE => "document",
_ => "unknown"
};
case "node_to_element": case "node_to_element":
var node = _nodes[data["key"]]!; return _documents[data["doc"]]!.nodeToElement(data["key"]);
if (node is dom.Element) {
_elements[_elementKey] = node;
var docKey = _documentNodes.keys
.firstWhere((key) => _documentElements[key]!.contains(data["key"]));
_documentElements[docKey]!.add(_elementKey);
return _elementKey++;
}
return null;
case "dispose": case "dispose":
var docKey = data["key"]; var docKey = data["key"];
_documents.remove(docKey); _documents.remove(docKey);
for (var elementKey in _documentElements[docKey]!) {
_elements.remove(elementKey);
}
_documentElements.remove(docKey);
for (var nodeKey in _documentNodes[docKey]!) {
_nodes.remove(nodeKey);
}
_documentNodes.remove(docKey);
return null; return null;
} }
return null;
} }
dynamic handleCookieCallback(Map<String, dynamic> data) { dynamic handleCookieCallback(Map<String, dynamic> data) {
@@ -384,11 +312,7 @@ mixin class _JSEngineApi {
} }
} }
void clearHtml() { void clearHtml() {}
_documents.clear();
_elements.clear();
_nodes.clear();
}
void clearCookies(List<String> domains) async { void clearCookies(List<String> domains) async {
for (var domain in domains) { for (var domain in domains) {
@@ -398,7 +322,7 @@ mixin class _JSEngineApi {
} }
} }
dynamic _convert(Map<String, dynamic> data) { Object? _convert(Map<String, dynamic> data) {
String type = data["type"]; String type = data["type"];
var value = data["value"]; var value = data["value"];
bool isEncode = data["isEncode"]; bool isEncode = data["isEncode"];
@@ -535,3 +459,113 @@ mixin class _JSEngineApi {
return (min + (max - min) * math.Random().nextDouble()).toInt(); return (min + (max - min) * math.Random().nextDouble()).toInt();
} }
} }
class DocumentWrapper {
final dom.Document doc;
DocumentWrapper.parse(String doc) : doc = html.parse(doc);
var elements = <dom.Element>[];
var nodes = <dom.Node>[];
int? querySelector(String query) {
var element = doc.querySelector(query);
if (element == null) return null;
elements.add(element);
return elements.length - 1;
}
List<int> querySelectorAll(String query) {
var res = doc.querySelectorAll(query);
var keys = <int>[];
for (var element in res) {
elements.add(element);
keys.add(elements.length - 1);
}
return keys;
}
String? elementGetText(int key) {
return elements[key].text;
}
Map<String, String> elementGetAttributes(int key) {
return elements[key].attributes.map(
(key, value) => MapEntry(
key.toString(),
value,
),
);
}
String? elementGetInnerHTML(int key) {
return elements[key].innerHtml;
}
int? elementGetParent(int key) {
var res = elements[key].parent;
if (res == null) return null;
elements.add(res);
return elements.length - 1;
}
int? elementQuerySelector(int key, String query) {
var res = elements[key].querySelector(query);
if (res == null) return null;
elements.add(res);
return elements.length - 1;
}
List<int> elementQuerySelectorAll(int key, String query) {
var res = elements[key].querySelectorAll(query);
var keys = <int>[];
for (var element in res) {
elements.add(element);
keys.add(elements.length - 1);
}
return keys;
}
List<int> elementGetChildren(int key) {
var res = elements[key].children;
var keys = <int>[];
for (var element in res) {
elements.add(element);
keys.add(elements.length - 1);
}
return keys;
}
List<int> elementGetNodes(int key) {
var res = elements[key].nodes;
var keys = <int>[];
for (var node in res) {
nodes.add(node);
keys.add(nodes.length - 1);
}
return keys;
}
String? nodeGetText(int key) {
return nodes[key].text;
}
String nodeType(int key) {
return switch (nodes[key].nodeType) {
dom.Node.ELEMENT_NODE => "element",
dom.Node.TEXT_NODE => "text",
dom.Node.COMMENT_NODE => "comment",
dom.Node.DOCUMENT_NODE => "document",
_ => "unknown"
};
}
int? nodeToElement(int key) {
if (nodes[key] is dom.Element) {
elements.add(nodes[key] as dom.Element);
return elements.length - 1;
}
return null;
}
}