pawbrowse 0.6.4 → 0.6.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/extension/background.js +38 -9
- package/extension/manifest.json +1 -1
- package/package.json +1 -1
package/extension/background.js
CHANGED
|
@@ -865,14 +865,41 @@ const SNAPSHOT = `(function(seed, opts){
|
|
|
865
865
|
if(dropH.size) actions=actions.filter(function(a){ return !dropH.has(a); });
|
|
866
866
|
}catch(_){}
|
|
867
867
|
// The nearest ancestor text that isn't just the label itself: which row/item a control is in.
|
|
868
|
+
// avoid, when given, skips past an ancestor whose stripped text matches it — used below when
|
|
869
|
+
// that text turned out to be shared by another same-label control too (not actually distinguishing).
|
|
868
870
|
cache.ctxOf=function(el, lab){ return ctxOf(el, lab); };
|
|
869
|
-
function ctxOf(el, lab){
|
|
871
|
+
function ctxOf(el, lab, avoid){
|
|
870
872
|
for(var p=el&&el.parentElement, g=0; p && g<6 && p.tagName!=='BODY'; p=p.parentElement, g++){
|
|
871
873
|
var tx=clean(p.innerText,400); if(!tx || tx===lab) continue;
|
|
872
|
-
var rest=clean(tx.split(lab).join(' '),40); if(rest) return rest;
|
|
874
|
+
var rest=clean(tx.split(lab).join(' '),40); if(rest && rest!==avoid) return rest;
|
|
873
875
|
}
|
|
874
876
|
return '';
|
|
875
877
|
}
|
|
878
|
+
// Two same-label controls can also share their nearest row text (an identical filter chip mirrored
|
|
879
|
+
// into two named panels, e.g. Booking's "Free cancellation" appearing under both "Your previous
|
|
880
|
+
// filters" and "Popular filters"): climb one level further, past that shared text, for the colliding
|
|
881
|
+
// pair only. others is the LIVE elements of every other control in the same label group, so this
|
|
882
|
+
// gives the identical answer whether it runs now (observe) or later against a re-scanned DOM
|
|
883
|
+
// (cache.get's re-render fallback below) — the same collision is always detected the same way.
|
|
884
|
+
function ctxOfDistinct(el, lab, others){
|
|
885
|
+
var cx=ctxOf(el, lab);
|
|
886
|
+
if(!cx) return cx;
|
|
887
|
+
for(var i=0;i<others.length;i++){ if(others[i]!==el && ctxOf(others[i], lab)===cx) return ctxOf(el, lab, cx) || cx; }
|
|
888
|
+
return cx;
|
|
889
|
+
}
|
|
890
|
+
cache.ctxOfDistinct=ctxOfDistinct;
|
|
891
|
+
// Every currently-visible, currently-actionable element sharing an identity (role+label): the group
|
|
892
|
+
// ctxOfDistinct needs to detect a collision live, on demand, from just an element and its guard —
|
|
893
|
+
// used both by cache.get's re-render fallback and by resolveHit's own row-context re-check below, so
|
|
894
|
+
// the two always agree on what counts as "still the same row" for a live element.
|
|
895
|
+
function poolOf(want){
|
|
896
|
+
var cands=collect(), out=[];
|
|
897
|
+
for(var i=0;i<cands.length;i++){
|
|
898
|
+
try{ if(cands[i].el.isConnected && cache.guard(cands[i].el)===want && cache.surface(cands[i].el)) out.push(cands[i].el); }catch(_){}
|
|
899
|
+
}
|
|
900
|
+
return out;
|
|
901
|
+
}
|
|
902
|
+
cache.ctxLive=function(el, lab, want){ return ctxOfDistinct(el, lab, poolOf(want)); };
|
|
876
903
|
// Identical labels ("Delete" per row, "Edit" per user) are ambiguous to the agent: tag each with
|
|
877
904
|
// the nearest ancestor text that tells them apart (e.g. the list row it lives in).
|
|
878
905
|
try{
|
|
@@ -880,7 +907,8 @@ const SNAPSHOT = `(function(seed, opts){
|
|
|
880
907
|
for(var u=0;u<actions.length;u++){ var key=actions[u].kind+'|'+actions[u].label; (byLabel[key]=byLabel[key]||[]).push(actions[u]); }
|
|
881
908
|
Object.keys(byLabel).forEach(function(key){
|
|
882
909
|
var grp=byLabel[key]; if(grp.length<2) return;
|
|
883
|
-
grp.
|
|
910
|
+
var els=grp.map(function(a){ return cache.nodes.get(a.node); });
|
|
911
|
+
grp.forEach(function(a, ai){ var cx=ctxOfDistinct(els[ai], a.label, els); if(cx) a.ctx=cx; });
|
|
884
912
|
});
|
|
885
913
|
}catch(_){}
|
|
886
914
|
var focus=null;
|
|
@@ -929,12 +957,13 @@ const SNAPSHOT = `(function(seed, opts){
|
|
|
929
957
|
var node=cache.byId[id]; if(node==null) return null;
|
|
930
958
|
var e=cache.nodes.get(node); if(e && e.isConnected) return e;
|
|
931
959
|
var fp=cache.fps && cache.fps[id], want=cache.guards && cache.guards[id]; if(!fp || !want || unnamed(want)) return null;
|
|
932
|
-
var
|
|
933
|
-
for(var k=0;k<
|
|
934
|
-
var el=
|
|
960
|
+
var pool=poolOf(want), hit=null, n=0;
|
|
961
|
+
for(var k=0;k<pool.length && n<2;k++){
|
|
962
|
+
var el=pool[k];
|
|
935
963
|
try{
|
|
936
|
-
|
|
937
|
-
|
|
964
|
+
// Same collision-aware ctx as at observe time (see ctxOfDistinct above): a shared, non-
|
|
965
|
+
// distinguishing row text must climb past it here too, or a stored deep ctx never matches.
|
|
966
|
+
if(fp.ctx && ctxOfDistinct(el, fp.label, pool)!==fp.ctx) continue;
|
|
938
967
|
hit=el; n++;
|
|
939
968
|
}catch(_){}
|
|
940
969
|
}
|
|
@@ -1291,7 +1320,7 @@ async function resolveHit(tabId, ref, opts, _retries) {
|
|
|
1291
1320
|
if(c.guard && c.guards && c.guards[${R}]!=null && c.guard(e)!==c.guards[${R}]) return {error:'element changed since observe (observe again)'};
|
|
1292
1321
|
// Same node, same label — but a different ROW? Virtualized lists recycle row elements for other
|
|
1293
1322
|
// items: "Delete" may now belong to someone else. The row context it was listed with must hold.
|
|
1294
|
-
var fp=c.fps&&c.fps[${R}]; if(fp && fp.ctx && c.
|
|
1323
|
+
var fp=c.fps&&c.fps[${R}]; if(fp && fp.ctx && c.ctxLive){ var liveCtx=c.ctxLive(e, fp.label, c.guards[${R}]); if(liveCtx!==fp.ctx) return {error:'the row this control belongs to changed since observe (now in "'+liveCtx+'"); observe again'}; }
|
|
1295
1324
|
if(e.matches(':disabled')||e.closest('[aria-disabled="true"],[inert]')) return {error:'element is disabled'};
|
|
1296
1325
|
if(${forFill} && (e.readOnly||e.getAttribute('aria-readonly')==='true')) return {error:'field is read-only'};
|
|
1297
1326
|
if(${forFill} && !('value' in e) && !e.isContentEditable) return {error:'not an editable field (observe again)'};
|
package/extension/manifest.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifest_version": 3,
|
|
3
3
|
"name": "PawBrowse",
|
|
4
|
-
"version": "0.6.
|
|
4
|
+
"version": "0.6.5",
|
|
5
5
|
"description": "Let your AI agent (Claude Code) drive your real Chrome. Fast element-table control. Open source, no keys.",
|
|
6
6
|
"minimum_chrome_version": "125",
|
|
7
7
|
"permissions": [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pawbrowse",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.5",
|
|
4
4
|
"description": "Let Claude Code drive your real, logged-in Chrome. A zero-dependency MCP server + a Chrome MV3 extension that uses element-table perception over CDP. The calling agent is the policy: no second model, no API keys, page snapshots never leave for a third party.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|