Skip to content

Instantly share code, notes, and snippets.

@mike-clark-8192
Created September 28, 2024 07:03
Show Gist options
  • Save mike-clark-8192/af6e0d2e579045ff3f0ecbe056c5544a to your computer and use it in GitHub Desktop.
Save mike-clark-8192/af6e0d2e579045ff3f0ecbe056c5544a to your computer and use it in GitHub Desktop.
JavaScript port of Chrome Dev Tools `$x`
function $x(selector, contextNode = document) {
// Ensure the selector is provided and is a string
if (!selector || typeof selector !== 'string') {
throw new Error('$x requires a non-empty string as its first argument.');
}
// Ensure the context node is a valid DOM Node
if (!contextNode || !(contextNode instanceof Node)) {
throw new Error('The second argument to $x must be a DOM Node.');
}
// Evaluate the XPath expression
let xpathResult;
try {
xpathResult = document.evaluate(
selector,
contextNode,
null, // No custom namespace resolver
XPathResult.ANY_TYPE, // We want any result type
null // No initial result
);
} catch (e) {
// Handle XPath evaluation errors
console.error('Error in XPath evaluation:', e);
return null;
}
// Handle different result types
switch (xpathResult.resultType) {
case XPathResult.NUMBER_TYPE:
return xpathResult.numberValue;
case XPathResult.STRING_TYPE:
return xpathResult.stringValue;
case XPathResult.BOOLEAN_TYPE:
return xpathResult.booleanValue;
case XPathResult.UNORDERED_NODE_ITERATOR_TYPE:
case XPathResult.ORDERED_NODE_ITERATOR_TYPE:
// Iterate over the nodes and collect them in an array
const nodes = [];
let node = xpathResult.iterateNext();
while (node) {
nodes.push(node);
node = xpathResult.iterateNext();
}
return nodes;
case XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE:
case XPathResult.ORDERED_NODE_SNAPSHOT_TYPE:
// Collect nodes from the snapshot result into an array
const snapshotNodes = [];
for (let i = 0; i < xpathResult.snapshotLength; i++) {
snapshotNodes.push(xpathResult.snapshotItem(i));
}
return snapshotNodes;
case XPathResult.ANY_UNORDERED_NODE_TYPE:
case XPathResult.FIRST_ORDERED_NODE_TYPE:
// Return the single node if available
return xpathResult.singleNodeValue;
default:
return null; // Unexpected result type
}
}
void MainThreadDebugger::XpathSelectorCallback(
const v8::FunctionCallbackInfo<v8::Value>& info) {
if (info.Length() < 1)
return;
const String& selector =
ToCoreStringWithUndefinedOrNullCheck(info.GetIsolate(), info[0]);
if (selector.empty())
return;
Node* node = SecondArgumentAsNode(info);
if (!node || !node->IsContainerNode())
return;
ScriptState* script_state =
ScriptState::ForRelevantRealm(info.GetIsolate(), info.This());
ExceptionState exception_state(info.GetIsolate(),
v8::ExceptionContext::kOperation,
"CommandLineAPI", "$x");
XPathResult* result = XPathEvaluator::Create()->evaluate(
nullptr, selector, node, nullptr, XPathResult::kAnyType, ScriptValue(),
exception_state);
if (exception_state.HadException()) {
if (exception_state.HadException()) {
ApplyContextToException(script_state, exception_state.GetException(),
exception_state.GetContext());
}
return;
}
if (!result) {
return;
}
if (result->resultType() == XPathResult::kNumberType) {
bindings::V8SetReturnValue(info, result->numberValue(exception_state));
} else if (result->resultType() == XPathResult::kStringType) {
bindings::V8SetReturnValue(info, result->stringValue(exception_state),
info.GetIsolate(),
bindings::V8ReturnValue::kNonNullable);
} else if (result->resultType() == XPathResult::kBooleanType) {
bindings::V8SetReturnValue(info, result->booleanValue(exception_state));
} else {
v8::Isolate* isolate = info.GetIsolate();
v8::Local<v8::Context> context = isolate->GetCurrentContext();
v8::Local<v8::Array> nodes = v8::Array::New(isolate);
wtf_size_t index = 0;
while (Node* next_node = result->iterateNext(exception_state)) {
v8::Local<v8::Value> value =
ToV8Traits<Node>::ToV8(script_state, next_node);
if (!CreateDataPropertyInArray(context, nodes, index++, value)
.FromMaybe(false)) {
return;
}
}
if (exception_state.HadException()) {
ApplyContextToException(script_state, exception_state.GetException(),
exception_state.GetContext());
return;
}
info.GetReturnValue().Set(nodes);
}
}
void MainThreadDebugger::installAdditionalCommandLineAPI(
v8::Local<v8::Context> context,
v8::Local<v8::Object> object) {
ThreadDebuggerCommonImpl::installAdditionalCommandLineAPI(context, object);
CreateFunctionProperty(
context, object, "$", MainThreadDebugger::QuerySelectorCallback,
"function $(selector, [startNode]) { [Command Line API] }",
v8::SideEffectType::kHasNoSideEffect);
CreateFunctionProperty(
context, object, "$$", MainThreadDebugger::QuerySelectorAllCallback,
"function $$(selector, [startNode]) { [Command Line API] }",
v8::SideEffectType::kHasNoSideEffect);
CreateFunctionProperty(
context, object, "$x", MainThreadDebugger::XpathSelectorCallback,
"function $x(xpath, [startNode]) { [Command Line API] }",
v8::SideEffectType::kHasNoSideEffect);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment