From 56e4401db4fae0c31a72bda384a4e69238fa35ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Arrufat?= Date: Thu, 18 Jun 2026 17:46:06 +0200 Subject: [PATCH] extract: resolve href/src attributes to absolute URLs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The schema walker returned raw attribute values, so `attr: "href"` / `attr: "src"` yielded whatever the markup contained — usually a relative URL. That is inconsistent with the links and structuredData tools (which resolve against the document base) and with the DOM .href/.src properties, and it makes the common extract-then-goto pattern fail on sites with relative links, since goto needs an absolute URL. Resolve href/src via `new URL(raw, document.baseURI)`, falling back to the raw value on parse failure. Other attributes pass through unchanged; absolute URLs are unaffected. --- src/browser/tools.zig | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/browser/tools.zig b/src/browser/tools.zig index 7d7e67082..8da9530c3 100644 --- a/src/browser/tools.zig +++ b/src/browser/tools.zig @@ -398,7 +398,7 @@ pub const Tool = enum { \\Extract structured data from the current page (navigate first). `schema` is a JSON object (passed as a string) mapping output field names to CSS-selector specs. It is NOT a JSON Schema — no "type"/"properties" wrappers; the keys ARE your output fields. Value shapes: \\ "" → first match's text (trimmed; null if no match) \\ [""] → every match's text (string[]) - \\ {"selector":"","attr":""} → first match's attribute value + \\ {"selector":"","attr":""} → first match's attribute value (href/src resolved to absolute URLs) \\ [{"selector":"","attr":""}] → every match's attribute (string[]) \\ [{"selector":"","fields":{…}}] → one object per match; field selectors resolve relative to that match and accept any shape above ("" = the match's own text; nest arrays for per-item sub-lists) \\Add "limit": N inside any array's object spec to cap matches. @@ -409,7 +409,7 @@ pub const Tool = enum { \\ {"karma": "#karma"} → {"karma":"42"} \\ {"items": [".story .title"]} → {"items":["Title 1","Title 2"]} \\ {"top3": [{"selector":".story .title","limit":3}]} → {"top3":["A","B","C"]} - \\ {"links": [{"selector":"a.title","attr":"href"}]} → {"links":["/a","/b"]} + \\ {"links": [{"selector":"a.title","attr":"href"}]} → {"links":["https://site/a","https://site/b"]} \\ {"stories": [{"selector":".athing","fields":{"title":".titleline","rank":".rank"}}]} → {"stories":[{"title":"Foo","rank":"1"}]} , .summary = "Extract structured data via a CSS-selector schema", @@ -884,7 +884,15 @@ const schema_walker_prefix = \\ for (const k in inner.fields) r[k] = ext(m, inner.fields[k]); \\ return r; \\ } - \\ if (inner.attr) return m.getAttribute(inner.attr); + \\ if (inner.attr) { + \\ const raw = m.getAttribute(inner.attr); + \\ // Resolve href/src like the DOM .href/.src properties do, so extracted + \\ // URLs are usable by goto. + \\ if (raw !== null && (inner.attr === 'href' || inner.attr === 'src')) { + \\ try { return new URL(raw, document.baseURI).href; } catch (e) { return raw; } + \\ } + \\ return raw; + \\ } \\ return m.textContent.trim(); \\ } \\ function ext(el, v){