@@ -150,8 +150,9 @@ async function routeCommand(
150150 const { url } = command . params ;
151151 assertSafeUrl ( url ) ;
152152 await browser . tabs . update ( targetTabId , { url } ) ;
153- // Wait for navigation to complete
153+ // Wait for navigation to complete and content script to be ready
154154 await waitForTabLoad ( targetTabId ) ;
155+ await waitForContentScriptReady ( targetTabId ) ;
155156 const tab = await browser . tabs . get ( targetTabId ) ;
156157 return { url : tab . url , title : tab . title } ;
157158 }
@@ -162,6 +163,7 @@ async function routeCommand(
162163 func : ( ) => history . back ( ) ,
163164 } ) ;
164165 await waitForUrlChange ( targetTabId , beforeBack . url || '' ) ;
166+ await waitForContentScriptReady ( targetTabId ) ;
165167 const tab = await browser . tabs . get ( targetTabId ) ;
166168 return { url : tab . url } ;
167169 }
@@ -172,12 +174,14 @@ async function routeCommand(
172174 func : ( ) => history . forward ( ) ,
173175 } ) ;
174176 await waitForUrlChange ( targetTabId , beforeFwd . url || '' ) ;
177+ await waitForContentScriptReady ( targetTabId ) ;
175178 const tab = await browser . tabs . get ( targetTabId ) ;
176179 return { url : tab . url } ;
177180 }
178181 case 'reload' : {
179182 await browser . tabs . reload ( targetTabId ) ;
180183 await waitForTabLoad ( targetTabId ) ;
184+ await waitForContentScriptReady ( targetTabId ) ;
181185 const tab = await browser . tabs . get ( targetTabId ) ;
182186 return { url : tab . url , title : tab . title } ;
183187 }
@@ -198,6 +202,10 @@ async function routeCommand(
198202 if ( container ) {
199203 if ( ! import . meta. env . FIREFOX ) {
200204 const tab = await browser . tabs . create ( { url : url || 'about:blank' } ) ;
205+ if ( url && tab . id ) {
206+ await waitForTabLoad ( tab . id ) ;
207+ await waitForContentScriptReady ( tab . id ) ;
208+ }
201209 const result : Record < string , unknown > = {
202210 tabId : tab . id ,
203211 url : tab . url || url || 'about:blank' ,
@@ -225,6 +233,10 @@ async function routeCommand(
225233 url : url || 'about:blank' ,
226234 ...( ( cookieStoreId != null ? { cookieStoreId } : { } ) as Record < string , unknown > ) ,
227235 } as Browser . tabs . CreateProperties ) ;
236+ if ( url && tab . id ) {
237+ await waitForTabLoad ( tab . id ) ;
238+ await waitForContentScriptReady ( tab . id ) ;
239+ }
228240 const result : Record < string , unknown > = {
229241 tabId : tab . id ,
230242 url : tab . url || url || 'about:blank' ,
@@ -1097,6 +1109,38 @@ function waitForUrlChange(tabId: number, previousUrl: string, timeoutMs = 15_000
10971109 } ) ;
10981110}
10991111
1112+ /**
1113+ * Ping the content script to verify it's ready to receive commands.
1114+ * Used after navigation to ensure the new page's content script is injected.
1115+ * Resolves (never rejects) — special pages (chrome://, about:, PDF) may never
1116+ * have a content script, so we don't want to block the command flow.
1117+ */
1118+ function waitForContentScriptReady ( tabId : number , timeoutMs = 5_000 ) : Promise < void > {
1119+ const POLL_INTERVAL = 200 ;
1120+ return new Promise ( ( resolve ) => {
1121+ const deadline = Date . now ( ) + timeoutMs ;
1122+ const attempt = ( ) => {
1123+ if ( Date . now ( ) > deadline ) {
1124+ resolve ( ) ;
1125+ return ;
1126+ }
1127+ browser . tabs
1128+ . sendMessage ( tabId , { type : 'browser-cli-ping' } , { frameId : 0 } )
1129+ . then ( ( response : unknown ) => {
1130+ if ( response && ( response as { ready ?: boolean } ) . ready ) {
1131+ resolve ( ) ;
1132+ } else {
1133+ setTimeout ( attempt , POLL_INTERVAL ) ;
1134+ }
1135+ } )
1136+ . catch ( ( ) => {
1137+ setTimeout ( attempt , POLL_INTERVAL ) ;
1138+ } ) ;
1139+ } ;
1140+ attempt ( ) ;
1141+ } ) ;
1142+ }
1143+
11001144function waitForTabLoad ( tabId : number , timeoutMs = 15_000 ) : Promise < void > {
11011145 return new Promise ( ( resolve ) => {
11021146 const timer = setTimeout ( ( ) => {
0 commit comments