@gjsify/devtools-browser 0.11.0 → 0.11.1

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.
@@ -0,0 +1 @@
1
+ var e=Object.defineProperty,__name=(t,n)=>e(t,`name`,{value:n,configurable:!0});export{__name};
@@ -0,0 +1,75 @@
1
+ import"./_virtual/_rolldown/runtime.js";function pageTemplate(e,t,n){return`<!DOCTYPE html>
2
+ <html><head><meta charset="utf-8"><title>${e}</title>
3
+ <style>
4
+ body { font-family: system-ui, sans-serif; padding: 24px; line-height: 1.6;
5
+ background: #fdfcfb; color: #1d1c1a; max-width: 760px; margin: 0 auto; }
6
+ h1 { color: #4a5fb8; margin-top: 0; }
7
+ a { color: #4a5fb8; }
8
+ pre { background: #efeee8; padding: 12px; border-radius: 4px; overflow-x: auto; }
9
+ code { background: #efeee8; padding: 1px 6px; border-radius: 3px; }
10
+ </style></head>
11
+ <body>
12
+ ${n}
13
+ <script>
14
+ // Announce arrival to the parent so the URL bar status line can
15
+ // confirm the postMessage roundtrip works in both variants.
16
+ window.addEventListener('DOMContentLoaded', function () {
17
+ try {
18
+ window.parent.postMessage({
19
+ type: 'page-loaded',
20
+ title: ${JSON.stringify(e)},
21
+ url: ${JSON.stringify(t)},
22
+ }, '*');
23
+ } catch (e) { /* postMessage may be unavailable in some contexts — best-effort */ }
24
+ });
25
+ <\/script>
26
+ </body></html>`}const e={welcome:{title:`Welcome`,html:pageTemplate(`Welcome`,`page:welcome`,`
27
+ <h1>Welcome to the Minimalist Browser</h1>
28
+ <p>This showcase demonstrates the <code>@gjsify/iframe</code> bridge — the same
29
+ code runs in a real browser (using a native <code>&lt;iframe&gt;</code>) and
30
+ under GJS (using <code>IFrameBridge</code> over WebKit.WebView).</p>
31
+ <p>Try the URL bar:</p>
32
+ <ul>
33
+ <li><code>page:about</code> — about this showcase</li>
34
+ <li><code>page:postmessage</code> — postMessage round-trip demo</li>
35
+ <li><code>page:adwaita</code> — Adwaita design language note</li>
36
+ <li><code>https://example.com</code> — real network URL (GJS variant: loads via WebKit)</li>
37
+ </ul>
38
+ <p>The Back / Forward / Reload buttons use an application-side history stack
39
+ maintained by the parent — the same code path works in both variants.</p>
40
+ `)},about:{title:`About`,html:pageTemplate(`About`,`page:about`,`
41
+ <h1>About this showcase</h1>
42
+ <p>The Minimalist Browser is a cross-variant validation of <code>@gjsify/iframe</code>'s
43
+ feature-completeness. The browser variant uses a real <code>&lt;iframe&gt;</code>;
44
+ the GJS variant uses <code>IFrameBridge</code> over WebKit.WebView. Both run the
45
+ <em>same</em> shared core code from <code>src/browser-demo.ts</code> — the only
46
+ platform-specific code wires the platform's UI controls to the core's API
47
+ and constructs the iframe handle.</p>
48
+ <p>postMessage between parent (URL bar) and iframe (content area) demonstrates
49
+ the bidirectional bridge:</p>
50
+ <ul>
51
+ <li><strong>Parent → iframe:</strong> the parent sends <code>{type: 'set-theme', theme}</code> when the user toggles theme (not used in this minimal demo, but reachable).</li>
52
+ <li><strong>Iframe → parent:</strong> every page announces <code>{type: 'page-loaded', title, url}</code> on DOMContentLoaded; the parent surfaces the title in the status line.</li>
53
+ </ul>
54
+ `)},postmessage:{title:`postMessage round-trip`,html:pageTemplate(`postMessage round-trip`,`page:postmessage`,`
55
+ <h1>postMessage round-trip</h1>
56
+ <p>On <code>DOMContentLoaded</code>, this page calls:</p>
57
+ <pre>window.parent.postMessage({
58
+ type: 'page-loaded',
59
+ title: 'postMessage round-trip',
60
+ url: 'page:postmessage',
61
+ }, '*');</pre>
62
+ <p>The parent's URL bar status line updates accordingly. The same code path
63
+ runs unchanged in the browser variant (where <code>window.parent</code> is
64
+ the real iframe parent) and the GJS variant (where it's the WebKit
65
+ <code>script-message-handler</code> bridge into the GJS host).</p>
66
+ `)},adwaita:{title:`Adwaita design`,html:pageTemplate(`Adwaita design`,`page:adwaita`,`
67
+ <h1>Adwaita design language</h1>
68
+ <p>The shell of this browser uses the GNOME Adwaita design language for both
69
+ variants: native Adw widgets in the GJS variant, and <code>@gjsify/adwaita-web</code>
70
+ custom-elements in the browser variant. The iframe content area is
71
+ deliberately unstyled — pages bring their own styling.</p>
72
+ `)}};var BrowserCore=class{constructor(e){this._iframe=e,this._history=[],this._historyIndex=-1,this._stateCbs=[],this._loadedCbs=[],queueMicrotask(()=>this._attachListener())}_attachListener(){let e=this._iframe.contentWindow;e&&e.addEventListener(`message`,e=>{let t=e.data;if(t&&t.type===`page-loaded`&&typeof t.title==`string`&&typeof t.url==`string`)for(let e of this._loadedCbs)e({title:t.title,url:t.url})})}navigate(e){this._history=this._history.slice(0,this._historyIndex+1),this._history.push(e),this._historyIndex=this._history.length-1,this._loadInto(e),this._emitState()}back(){this.canGoBack&&(this._historyIndex--,this._loadInto(this._history[this._historyIndex]),this._emitState())}forward(){this.canGoForward&&(this._historyIndex++,this._loadInto(this._history[this._historyIndex]),this._emitState())}reload(){this._historyIndex<0||(this._loadInto(this._history[this._historyIndex]),this._emitState())}get currentUrl(){return this._historyIndex>=0?this._history[this._historyIndex]:``}get canGoBack(){return this._historyIndex>0}get canGoForward(){return this._historyIndex<this._history.length-1}onStateChange(e){this._stateCbs.push(e)}onPageLoaded(e){this._loadedCbs.push(e)}reattachListener(){this._attachListener()}_loadInto(t){if(t.startsWith(`page:`)){let n=e[t.slice(5)];if(this._iframe.src=``,!n){this._iframe.srcdoc=pageTemplate(`Not found`,t,`
73
+ <h1>Page not found</h1>
74
+ <p>The URL <code>${t}</code> is not a built-in page.
75
+ Try <code>page:welcome</code>.</p>`);return}this._iframe.srcdoc=n.html;return}this._iframe.srcdoc=``,this._iframe.src=t}_emitState(){let e={url:this.currentUrl,canGoBack:this.canGoBack,canGoForward:this.canGoForward};for(let t of this._stateCbs)t(e)}};const t=Object.keys(e).map(e=>`page:${e}`),n=`page:welcome`;export{t as BUILTIN_PAGE_URLS,BrowserCore,n as DEFAULT_HOME_URL};
@@ -0,0 +1 @@
1
+ import"./_virtual/_rolldown/runtime.js";import{buildAccessibilityExpression as e,buildDomTreeExpression as t,buildInspectElementExpression as n,buildNetworkExpression as r}from"./inspector.js";function browserDevtoolsExtension(i){let{bridge:a,core:o}=i,s=``;return o.onPageLoaded(e=>{s=e.title}),{methodsXml:[`<method name="BrowserNavigate"><arg type="s" direction="in" name="url"/></method>`,`<method name="BrowserBack"></method>`,`<method name="BrowserForward"></method>`,`<method name="BrowserReload"></method>`,`<method name="BrowserGetPageInfo"><arg type="s" direction="out" name="info_json"/></method>`,`<method name="BrowserScreenshot"><arg type="s" direction="in" name="region"/><arg type="ay" direction="out" name="png"/></method>`,`<method name="BrowserSetViewport"><arg type="i" direction="in" name="width"/><arg type="i" direction="in" name="height"/><arg type="i" direction="out" name="w"/><arg type="i" direction="out" name="h"/></method>`,`<method name="BrowserEvalJs"><arg type="s" direction="in" name="script"/><arg type="s" direction="out" name="result_json"/></method>`,`<method name="BrowserGetLinks"><arg type="s" direction="out" name="links_json"/></method>`,`<method name="BrowserFollowLink"><arg type="s" direction="in" name="selector_or_text"/><arg type="s" direction="out" name="result_json"/></method>`,`<method name="BrowserQueryDom"><arg type="s" direction="in" name="selector"/><arg type="s" direction="out" name="elements_json"/></method>`,`<method name="BrowserWaitForLoad"><arg type="i" direction="in" name="timeout_ms"/><arg type="b" direction="out" name="ok"/></method>`,`<method name="BrowserGetConsole"><arg type="s" direction="out" name="logs_json"/></method>`,`<method name="BrowserInspectElement"><arg type="s" direction="in" name="selector"/><arg type="s" direction="out" name="element_json"/></method>`,`<method name="BrowserDomTree"><arg type="s" direction="in" name="selector"/><arg type="i" direction="in" name="max_depth"/><arg type="s" direction="out" name="tree_json"/></method>`,`<method name="BrowserGetNetwork"><arg type="s" direction="out" name="network_json"/></method>`,`<method name="BrowserGetAccessibility"><arg type="s" direction="in" name="selector"/><arg type="i" direction="in" name="max_depth"/><arg type="s" direction="out" name="a11y_json"/></method>`,`<method name="BrowserOpenInspector"></method>`,`<method name="BrowserCloseInspector"></method>`],handlers:{BrowserNavigate:e=>{o.navigate(e)},BrowserBack:()=>{o.back()},BrowserForward:()=>{o.forward()},BrowserReload:()=>{o.reload()},BrowserGetPageInfo:()=>JSON.stringify({uri:a.currentUri,appUrl:o.currentUrl,title:a.pageTitle||s,canGoBack:o.canGoBack,canGoForward:o.canGoForward,lastLoadError:a.lastLoadError},null,2),BrowserScreenshot:async e=>a.takeScreenshot(e===`visible`?`visible`:`full`),BrowserSetViewport:(e,t)=>{a.setViewportSize(e,t);let n=a.getViewportSize();return[n.width,n.height]},BrowserEvalJs:async e=>JSON.stringify(await a.evaluateJavaScript(e)??null,null,2),BrowserGetLinks:async()=>JSON.stringify(await a.getLinks(),null,2),BrowserFollowLink:async e=>{let t=a.waitForNavigation();if(!await a.clickElement(e))return JSON.stringify({clicked:!1,navigated:!1},null,2);let n=!0;try{await t}catch{n=!1}return JSON.stringify({clicked:!0,navigated:n,uri:a.currentUri},null,2)},BrowserQueryDom:async e=>JSON.stringify(await a.queryDom(e),null,2),BrowserWaitForLoad:async e=>{try{return await a.waitForNavigation(e>0?e:3e4),!0}catch{return!1}},BrowserGetConsole:()=>JSON.stringify(a.getConsoleLogs(),null,2),BrowserInspectElement:async e=>JSON.stringify(await a.evaluateJavaScript(n(e))??null,null,2),BrowserDomTree:async(e,n)=>JSON.stringify(await a.evaluateJavaScript(t(e||void 0,n>0?n:3))??null,null,2),BrowserGetNetwork:async()=>JSON.stringify(await a.evaluateJavaScript(r())??null,null,2),BrowserGetAccessibility:async(t,n)=>JSON.stringify(await a.evaluateJavaScript(e(t||void 0,n>0?n:4))??null,null,2),BrowserOpenInspector:()=>{a.get_inspector()?.show()},BrowserCloseInspector:()=>{a.get_inspector()?.close()}},methodKinds:{BrowserNavigate:`mutating`,BrowserBack:`mutating`,BrowserForward:`mutating`,BrowserReload:`mutating`,BrowserGetPageInfo:`read-only`,BrowserScreenshot:`read-only`,BrowserSetViewport:`mutating`,BrowserEvalJs:`mutating`,BrowserGetLinks:`read-only`,BrowserFollowLink:`mutating`,BrowserQueryDom:`read-only`,BrowserWaitForLoad:`read-only`,BrowserGetConsole:`read-only`,BrowserInspectElement:`read-only`,BrowserDomTree:`read-only`,BrowserGetNetwork:`read-only`,BrowserGetAccessibility:`read-only`,BrowserOpenInspector:`presence`,BrowserCloseInspector:`presence`},contributeStatus:()=>({browser:{uri:a.currentUri,title:a.pageTitle||s,canGoBack:o.canGoBack,canGoForward:o.canGoForward,lastLoadError:a.lastLoadError}})}}export{browserDevtoolsExtension};
@@ -0,0 +1 @@
1
+ import"./_virtual/_rolldown/runtime.js";import{BUILTIN_PAGE_URLS as e,BrowserCore as t}from"./browser-core.js";import n from"@girs/adw-1";import r from"@girs/gtk-4.0";import{IFrameBridge as i}from"@gjsify/iframe";var BrowserWindow=class{constructor(a,o={}){let s=o.homeUrl??`page:welcome`;this._win=new n.ApplicationWindow({application:a,default_width:1100,default_height:720,title:o.title??`Minimalist Browser`});let c=new n.ToolbarView,l=new n.HeaderBar;c.add_top_bar(l);let u=new r.Button({icon_name:`go-previous-symbolic`,tooltip_text:`Back`,sensitive:!1}),d=new r.Button({icon_name:`go-next-symbolic`,tooltip_text:`Forward`,sensitive:!1}),f=new r.Button({icon_name:`view-refresh-symbolic`,tooltip_text:`Reload`});l.pack_start(u),l.pack_start(d),l.pack_start(f);let p=new r.Entry({placeholder_text:`page:welcome or https://…`,hexpand:!0,width_chars:50});p.set_text(s),l.set_title_widget(p);let m=new r.Button({label:`Go`,css_classes:[`suggested-action`]});l.pack_end(m);let h=new r.Box({orientation:r.Orientation.VERTICAL,spacing:0}),g=new r.Box({orientation:r.Orientation.HORIZONTAL,spacing:8,margin_start:12,margin_end:12,margin_top:6,margin_bottom:6}),_=new r.Label({label:`Quick nav:`});_.add_css_class(`dim-label`),g.append(_);for(let t of e){let e=new r.Button({label:t});e.add_css_class(`flat`),e.connect(`clicked`,()=>this._core.navigate(t)),g.append(e)}h.append(g),this._iframeWidget=new i({captureConsole:!0}),this._iframeWidget.installGlobals(),this._iframeWidget.vexpand=!0,this._iframeWidget.hexpand=!0,h.append(this._iframeWidget);let v=new r.Label({halign:r.Align.START,margin_start:12,margin_end:12,margin_top:4,margin_bottom:4});v.add_css_class(`dim-label`),v.add_css_class(`monospace`),v.set_text(`Status: idle`),h.append(v),c.set_content(h),this._win.set_content(c),this._core=new t(this._iframeWidget.iframeElement),this._iframeWidget.onReady(()=>this._core.reattachListener()),this._core.onStateChange(e=>{p.set_text(e.url),u.sensitive=e.canGoBack,d.sensitive=e.canGoForward}),this._core.onPageLoaded(e=>{v.set_text(`Status: loaded "${e.title}" (${e.url})`)});let submitUrl=()=>{let e=p.get_text().trim();e&&this._core.navigate(e)};m.connect(`clicked`,submitUrl),p.connect(`activate`,submitUrl),u.connect(`clicked`,()=>this._core.back()),d.connect(`clicked`,()=>this._core.forward()),f.connect(`clicked`,()=>this._core.reload()),this._core.navigate(s)}get core(){return this._core}get window(){return this._win}get bridge(){return this._iframeWidget}navigate(e){this._core.navigate(e)}present(){this._win.present()}};export{BrowserWindow};
@@ -0,0 +1 @@
1
+ import{BUILTIN_PAGE_URLS as e,BrowserCore as t,DEFAULT_HOME_URL as n}from"./browser-core.js";import{buildAccessibilityExpression as r,buildDomTreeExpression as i,buildInspectElementExpression as a,buildNetworkExpression as o}from"./inspector.js";import{browserDevtoolsExtension as s}from"./browser-devtools-extension.js";import{BrowserWindow as c}from"./browser-window.js";import{BrowserApplication as l,runBrowserDevtools as u}from"./run.js";export{e as BUILTIN_PAGE_URLS,l as BrowserApplication,t as BrowserCore,c as BrowserWindow,n as DEFAULT_HOME_URL,s as browserDevtoolsExtension,r as buildAccessibilityExpression,i as buildDomTreeExpression,a as buildInspectElementExpression,o as buildNetworkExpression,u as runBrowserDevtools};
@@ -0,0 +1 @@
1
+ import"./_virtual/_rolldown/runtime.js";const e=`display.position.top.right.bottom.left.width.height.box-sizing.margin.padding.border.color.background-color.font-family.font-size.font-weight.line-height.text-align.flex-direction.justify-content.align-items.gap.z-index.opacity.overflow.visibility`.split(`.`);function buildInspectElementExpression(t,n=e){return`(function(){var el=document.querySelector(${JSON.stringify(t)});if(!el)return {found:false};var cs=getComputedStyle(el);var r=el.getBoundingClientRect();function px(v){return parseFloat(v)||0;}function edges(p){return {top:px(cs[p+"Top"]),right:px(cs[p+"Right"]),bottom:px(cs[p+"Bottom"]),left:px(cs[p+"Left"])};}var border={top:px(cs.borderTopWidth),right:px(cs.borderRightWidth),bottom:px(cs.borderBottomWidth),left:px(cs.borderLeftWidth)};var attrs={};for(var i=0;i<el.attributes.length;i++){attrs[el.attributes[i].name]=el.attributes[i].value;}var props=${JSON.stringify(n)};var styles={};for(var j=0;j<props.length;j++){styles[props[j]]=cs.getPropertyValue(props[j]);}return {found:true,tagName:el.tagName,id:el.id||"",className:(typeof el.className==="string"?el.className:""),attributes:attrs,text:(el.textContent||"").trim().slice(0,200),rect:{x:r.x,y:r.y,width:r.width,height:r.height},boxModel:{margin:edges("margin"),border:border,padding:edges("padding"),content:{width:el.clientWidth-px(cs.paddingLeft)-px(cs.paddingRight),height:el.clientHeight-px(cs.paddingTop)-px(cs.paddingBottom)}},computedStyle:styles};})()`}function buildDomTreeExpression(e,t=3,n=50){let r=e?`document.querySelector(${JSON.stringify(e)})`:`document.documentElement`,i=Math.max(0,Math.floor(t)),a=Math.max(1,Math.floor(n));return`(function(){var root=${r};if(!root)return null;function node(el,d){var o={tagName:el.tagName,id:el.id||"",className:(typeof el.className==="string"?el.className:""),childElementCount:el.childElementCount};if(d>0){o.children=[];var kids=el.children;for(var i=0;i<kids.length&&i<${a};i++){o.children.push(node(kids[i],d-1));}if(kids.length>${a})o.truncated=kids.length-${a};}return o;}return node(root,${i});})()`}function buildNetworkExpression(){return`(function(){var nav=performance.getEntriesByType("navigation")[0];var res=performance.getEntriesByType("resource").map(function(e){return {name:e.name,initiatorType:e.initiatorType,duration:Math.round(e.duration),transferSize:e.transferSize||0,decodedBodySize:e.decodedBodySize||0,startTime:Math.round(e.startTime)};});return {navigation:nav?{name:nav.name,duration:Math.round(nav.duration),domContentLoaded:Math.round(nav.domContentLoadedEventEnd),loadEvent:Math.round(nav.loadEventEnd)}:null,resources:res};})()`}function buildAccessibilityExpression(e,t=4,n=50){return`(function(){var root=${e?`document.querySelector(${JSON.stringify(e)})`:`document.body`};if(!root)return null;function nm(el){return el.getAttribute("aria-label")||el.getAttribute("alt")||el.getAttribute("title")||(el.textContent||"").trim().slice(0,80);}function node(el,d){var o={tagName:el.tagName,role:el.getAttribute("role")||"",name:nm(el)};var aria={};for(var i=0;i<el.attributes.length;i++){var a=el.attributes[i];if(a.name.indexOf("aria-")===0)aria[a.name]=a.value;}if(Object.keys(aria).length)o.aria=aria;if(d>0){var kids=el.children;var c=[];for(var j=0;j<kids.length&&j<${Math.max(1,Math.floor(n))};j++)c.push(node(kids[j],d-1));if(c.length)o.children=c;}return o;}return node(root,${Math.max(0,Math.floor(t))});})()`}export{buildAccessibilityExpression,buildDomTreeExpression,buildInspectElementExpression,buildNetworkExpression};
package/lib/esm/run.js ADDED
@@ -0,0 +1 @@
1
+ import"./_virtual/_rolldown/runtime.js";import{browserDevtoolsExtension as e}from"./browser-devtools-extension.js";import{BrowserWindow as t}from"./browser-window.js";import n from"@girs/adw-1";import r from"@girs/gio-2.0";import i from"@girs/gobject-2.0";import{installDevtools as a}from"@gjsify/devtools";var o=class BrowserApplication extends n.Application{static{i.registerClass({GTypeName:`GjsifyBrowserApplication`},BrowserApplication)}constructor(e){super({application_id:e.applicationId,flags:r.ApplicationFlags.DEFAULT_FLAGS}),this._window=null,this._options=e,this.connect(`activate`,()=>this._onActivate())}_onActivate(){this._window||(this._window=new t(this,{title:this._options.title,homeUrl:this._options.homeUrl}),a(this,{enabled:this._options.devtools||void 0,winActionGroup:this._window.window,extend:[e({bridge:this._window.bridge,core:this._window.core,window:this._window})]})),this._window.present()}};i.type_ensure(o.$gtype);async function runBrowserDevtools(e){return new o(e).runAsync([imports.system.programInvocationName,...ARGV])}export{o as BrowserApplication,runBrowserDevtools};
@@ -0,0 +1,87 @@
1
+ /** Abstract shape of the iframe content area. Both real `<iframe>` and
2
+ * IFrameBridge's `iframeElement` satisfy this duck-type. */
3
+ export interface IFrameHandle {
4
+ /** Either `src` (URL) or `srcdoc` (HTML) is set to navigate. */
5
+ set src(value: string);
6
+ get src(): string;
7
+ set srcdoc(value: string);
8
+ get srcdoc(): string;
9
+ /** Provides the `contentWindow` once the frame has loaded. May be null
10
+ * before first load. */
11
+ readonly contentWindow: {
12
+ addEventListener(type: 'message', listener: (event: {
13
+ data: unknown;
14
+ }) => void): void;
15
+ } | null;
16
+ }
17
+ /** Public callback surface exposed to platform-specific UI code. */
18
+ export interface BrowserHandle {
19
+ navigate(url: string): void;
20
+ back(): void;
21
+ forward(): void;
22
+ reload(): void;
23
+ /** Returns the URL currently displayed in the iframe. */
24
+ readonly currentUrl: string;
25
+ /** True when there's an entry to go back to in the application history. */
26
+ readonly canGoBack: boolean;
27
+ /** True when forward navigation is available. */
28
+ readonly canGoForward: boolean;
29
+ /** Subscribe to nav-state changes (called on every navigate / back / forward / reload). */
30
+ onStateChange(cb: (state: NavState) => void): void;
31
+ /** Subscribe to page-loaded postMessage announcements from the iframe. */
32
+ onPageLoaded(cb: (info: PageLoadedInfo) => void): void;
33
+ }
34
+ export interface NavState {
35
+ url: string;
36
+ canGoBack: boolean;
37
+ canGoForward: boolean;
38
+ }
39
+ export interface PageLoadedInfo {
40
+ title: string;
41
+ url: string;
42
+ }
43
+ export declare class BrowserCore implements BrowserHandle {
44
+ private readonly _iframe;
45
+ /** Application-side history stack. The current URL is at
46
+ * `_history[_historyIndex]`; `back()` decrements, `forward()` increments. */
47
+ private _history;
48
+ private _historyIndex;
49
+ private _stateCbs;
50
+ private _loadedCbs;
51
+ constructor(_iframe: IFrameHandle);
52
+ private _attachListener;
53
+ navigate(url: string): void;
54
+ back(): void;
55
+ forward(): void;
56
+ reload(): void;
57
+ get currentUrl(): string;
58
+ get canGoBack(): boolean;
59
+ get canGoForward(): boolean;
60
+ onStateChange(cb: (state: NavState) => void): void;
61
+ onPageLoaded(cb: (info: PageLoadedInfo) => void): void;
62
+ /**
63
+ * Public hook for the platform adapter to re-attach the listener
64
+ * after a `loadHtml` (GJS variant resets contentWindow on each
65
+ * load). Browser variant doesn't need this — the real iframe keeps
66
+ * its contentWindow across navigations.
67
+ */
68
+ reattachListener(): void;
69
+ /** Internal: route a URL into the iframe, picking `srcdoc` for the
70
+ * `page:<name>` scheme and `src` for everything else.
71
+ *
72
+ * `src` and `srcdoc` are kept mutually exclusive: per the HTML spec
73
+ * `srcdoc` takes precedence over `src`, and the iframe element dedups an
74
+ * unchanged value. So when switching to a `page:*` we clear `src`, and when
75
+ * loading a real URL we clear `srcdoc` first — otherwise navigating back to
76
+ * a real URL after a built-in page (e.g. page:welcome → https://…) would be
77
+ * a no-op (the `src` attribute never changed) and leave the stale srcdoc
78
+ * showing. Clearing to `''` only updates the attribute (the element's
79
+ * setters skip the load when the value is empty), so it triggers no extra
80
+ * navigation. */
81
+ private _loadInto;
82
+ private _emitState;
83
+ }
84
+ /** List of built-in page URLs — exported for UI dropdown / quick-nav buttons. */
85
+ export declare const BUILTIN_PAGE_URLS: string[];
86
+ /** Default URL the browser opens on launch. */
87
+ export declare const DEFAULT_HOME_URL = "page:welcome";
@@ -0,0 +1,2 @@
1
+ declare const _default: () => Promise<void>;
2
+ export default _default;
@@ -0,0 +1,15 @@
1
+ import type { DevtoolsExtension } from '@gjsify/devtools';
2
+ import type { IFrameBridge } from '@gjsify/iframe';
3
+ import type { BrowserCore } from './browser-core.js';
4
+ import type { BrowserWindow } from './browser-window.js';
5
+ export interface BrowserDevtoolsExtensionOptions {
6
+ bridge: IFrameBridge;
7
+ core: BrowserCore;
8
+ window: BrowserWindow;
9
+ }
10
+ /**
11
+ * Build the {@link DevtoolsExtension} for the browser tool. Navigation goes
12
+ * through {@link BrowserCore} (keeping the app history stack + URL bar in sync);
13
+ * inspection/screenshot/eval go straight to the {@link IFrameBridge}.
14
+ */
15
+ export declare function browserDevtoolsExtension(options: BrowserDevtoolsExtensionOptions): DevtoolsExtension;
@@ -0,0 +1,31 @@
1
+ import Adw from '@girs/adw-1';
2
+ import { IFrameBridge } from '@gjsify/iframe';
3
+ import { BrowserCore } from './browser-core.js';
4
+ export interface BrowserWindowOptions {
5
+ /** Window title (default: "Minimalist Browser"). */
6
+ title?: string;
7
+ /** URL loaded on launch (default: `page:welcome`). */
8
+ homeUrl?: string;
9
+ }
10
+ /**
11
+ * A self-contained Adwaita browser window: HeaderBar with back/forward/reload +
12
+ * URL entry, a quick-nav strip over the built-in pages, the WebKit content area,
13
+ * and a status line. The embedded {@link IFrameBridge} captures the page console
14
+ * so devtools `get_console` works.
15
+ */
16
+ export declare class BrowserWindow {
17
+ private readonly _win;
18
+ private readonly _core;
19
+ private readonly _iframeWidget;
20
+ constructor(app: Adw.Application, options?: BrowserWindowOptions);
21
+ /** The shared navigation core (history stack + page-loaded events). */
22
+ get core(): BrowserCore;
23
+ /** The Adw.ApplicationWindow. */
24
+ get window(): Adw.ApplicationWindow;
25
+ /** The IFrameBridge (WebKit.WebView) backing the content area. */
26
+ get bridge(): IFrameBridge;
27
+ /** Navigate the content area to `url` (page:* built-in or a real URL). */
28
+ navigate(url: string): void;
29
+ /** Present (show + raise) the window. */
30
+ present(): void;
31
+ }
@@ -0,0 +1,10 @@
1
+ export { BrowserCore, BUILTIN_PAGE_URLS, DEFAULT_HOME_URL } from './browser-core.js';
2
+ export type { BrowserHandle, IFrameHandle, NavState, PageLoadedInfo } from './browser-core.js';
3
+ export { BrowserWindow } from './browser-window.js';
4
+ export type { BrowserWindowOptions } from './browser-window.js';
5
+ export { browserDevtoolsExtension } from './browser-devtools-extension.js';
6
+ export type { BrowserDevtoolsExtensionOptions } from './browser-devtools-extension.js';
7
+ export { buildInspectElementExpression, buildDomTreeExpression, buildNetworkExpression, buildAccessibilityExpression, } from './inspector.js';
8
+ export type { BoxEdges, BoxModel, InspectedElement, DomNode, AccessibilityNode, NetworkEntry } from './inspector.js';
9
+ export { BrowserApplication, runBrowserDevtools } from './run.js';
10
+ export type { RunBrowserDevtoolsOptions } from './run.js';
@@ -0,0 +1,86 @@
1
+ /** Per-side edge sizes (px), as the Box Model panel shows them. */
2
+ export interface BoxEdges {
3
+ top: number;
4
+ right: number;
5
+ bottom: number;
6
+ left: number;
7
+ }
8
+ /** The Box Model panel for an element. */
9
+ export interface BoxModel {
10
+ margin: BoxEdges;
11
+ border: BoxEdges;
12
+ padding: BoxEdges;
13
+ /** Content-box size (border-box minus border + padding). */
14
+ content: {
15
+ width: number;
16
+ height: number;
17
+ };
18
+ }
19
+ /** What `inspect_element` returns — the Elements + Computed + Box Model panels. */
20
+ export interface InspectedElement {
21
+ found: boolean;
22
+ tagName?: string;
23
+ id?: string;
24
+ className?: string;
25
+ attributes?: Record<string, string>;
26
+ text?: string;
27
+ rect?: {
28
+ x: number;
29
+ y: number;
30
+ width: number;
31
+ height: number;
32
+ };
33
+ boxModel?: BoxModel;
34
+ computedStyle?: Record<string, string>;
35
+ }
36
+ /** A node in the DOM tree dump. */
37
+ export interface DomNode {
38
+ tagName: string;
39
+ id: string;
40
+ className: string;
41
+ childElementCount: number;
42
+ children?: DomNode[];
43
+ /** Number of children omitted past the cap. */
44
+ truncated?: number;
45
+ }
46
+ /** A node in the approximate accessibility tree. */
47
+ export interface AccessibilityNode {
48
+ tagName: string;
49
+ role: string;
50
+ name: string;
51
+ aria?: Record<string, string>;
52
+ children?: AccessibilityNode[];
53
+ }
54
+ /** A Resource-Timing entry (the Network panel). */
55
+ export interface NetworkEntry {
56
+ name: string;
57
+ initiatorType: string;
58
+ duration: number;
59
+ transferSize: number;
60
+ decodedBodySize: number;
61
+ startTime: number;
62
+ }
63
+ /**
64
+ * Expression: inspect the first element matching `selector` → tag/id/class +
65
+ * attributes + bounding rect + box model (margin/border/padding/content) +
66
+ * a curated set of computed styles. Returns `{found:false}` when nothing matches.
67
+ */
68
+ export declare function buildInspectElementExpression(selector: string, styleProps?: readonly string[]): string;
69
+ /**
70
+ * Expression: the DOM tree from `selector` (or `document.documentElement`) down
71
+ * to `maxDepth`, with at most `maxChildren` children per node (excess noted as
72
+ * `truncated`). Returns the Elements-tree shape.
73
+ */
74
+ export declare function buildDomTreeExpression(selector?: string, maxDepth?: number, maxChildren?: number): string;
75
+ /**
76
+ * Expression: page network activity via the Resource Timing API — the navigation
77
+ * entry + each loaded resource ({name, initiatorType, duration, sizes, startTime}).
78
+ */
79
+ export declare function buildNetworkExpression(): string;
80
+ /**
81
+ * Expression: an approximate accessibility tree from `selector` (or `body`) —
82
+ * role + accessible name + aria-* per node, down to `maxDepth`. Standard-DOM
83
+ * approximation of the inspector's Accessibility/Audit view (the true platform
84
+ * a11y tree is not exposed to page JS).
85
+ */
86
+ export declare function buildAccessibilityExpression(selector?: string, maxDepth?: number, maxChildren?: number): string;
@@ -0,0 +1,2 @@
1
+ declare const _default: () => Promise<void>;
2
+ export default _default;
@@ -0,0 +1,23 @@
1
+ import Adw from '@girs/adw-1';
2
+ import type { BrowserWindowOptions } from './browser-window.js';
3
+ export interface RunBrowserDevtoolsOptions extends BrowserWindowOptions {
4
+ /** GApplication id, e.g. `org.example.Browser`. */
5
+ applicationId: string;
6
+ /** Force-enable the devtools control plane (otherwise gated on `GJSIFY_DEVTOOLS`). */
7
+ devtools?: boolean;
8
+ }
9
+ /**
10
+ * Hosts the browser window and installs devtools on activate. Devtools is wired
11
+ * at activate time (when the application is registered, so its session-bus
12
+ * connection + object path are available — same timing as the storybook app).
13
+ */
14
+ export declare class BrowserApplication extends Adw.Application {
15
+ private _window;
16
+ private _options;
17
+ constructor(options: RunBrowserDevtoolsOptions);
18
+ private _onActivate;
19
+ }
20
+ /**
21
+ * Construct and run a browser application, resolving with its exit code.
22
+ */
23
+ export declare function runBrowserDevtools(options: RunBrowserDevtoolsOptions): Promise<number>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gjsify/devtools-browser",
3
- "version": "0.11.0",
3
+ "version": "0.11.1",
4
4
  "description": "Minimalist Adwaita web browser for GJS — shared BrowserCore + native Adw shell + a devtools extension so an agent can drive and inspect web apps over MCP",
5
5
  "type": "module",
6
6
  "module": "lib/esm/index.js",
@@ -41,12 +41,12 @@
41
41
  "@girs/gobject-2.0": "2.88.0-4.0.4",
42
42
  "@girs/gtk-4.0": "4.23.0-4.0.4",
43
43
  "@girs/webkit-6.0": "2.52.1-4.0.4",
44
- "@gjsify/devtools": "workspace:^",
45
- "@gjsify/iframe": "workspace:^"
44
+ "@gjsify/devtools": "^0.11.0",
45
+ "@gjsify/iframe": "^0.11.0"
46
46
  },
47
47
  "devDependencies": {
48
- "@gjsify/cli": "workspace:^",
49
- "@gjsify/unit": "workspace:^",
48
+ "@gjsify/cli": "^0.11.0",
49
+ "@gjsify/unit": "^0.11.0",
50
50
  "@types/node": "^25.9.2",
51
51
  "typescript": "^6.0.3"
52
52
  },