§
    ›Çj(<  ã                  óÂ   — d Z ddlmZ ddlZddlZddlZddlZddlZddlm	Z	m
Z
mZ ddlmZ ddlmZ dd„Zd d„Zd!d„Zd"d„Z G d„ de¦  «        ZdZdZdZd#d„Zd$d„ZdS )%u`	  The seam where Node stops being necessary.

â›” THIS IS THE KEYSTONE OF THE WHOLE DETACHMENT, and its shape is the one
decision that makes the rest small. `_pw/_impl` is not replaced - 799 of its
methods never touch the channel at all, and throwing them away would mean
rewriting working logic for nothing. What is replaced is the ONE thing under
them: `PipeTransport`, which spawns `node cli.js run-driver` and speaks the
Playwright protocol over a pipe.

The seam is `Transport`, an abstract class with five methods. Everything above
it - channels, guids, `ChannelOwner`, the sync and async facades, the 823
generated API methods - keeps working unchanged, because from up there nothing
can tell whether the answer crossed a pipe or came from the next object in the
same process.

**THE PROTOCOL, MEASURED AND NOT DEDUCED.** What crosses this seam is recorded
by `scripts/capture_protocol.py` against the real Node driver, and the server is
written against that recording. Reading `_impl` tells you which initializer
fields are CONSUMED; it cannot tell you which are SENT, and the gap between
those two sets is exactly where a future code path breaks.

Four message shapes, in both directions:

    ->  {"id": N, "guid": G, "method": M, "params": P, "metadata": {...}}
    <-  {"id": N, "result": R}                     an answer
    <-  {"id": N, "error": {"error": {...}}}       a failure
    <-  {"guid": G, "method": "__create__",
         "params": {"type": T, "guid": G2, "initializer": I}}
    <-  {"guid": G, "method": M, "params": P}      an event

â›” **THREADS, AND WHY THIS IS NOT OPTIONAL.** `send()` is called ON THE ASYNCIO
LOOP. Our Juggler operations block: `goto` waits for a load state, `click` waits
for actionability. Running them inline would block the loop, and under the sync
facade - which drives the loop from a greenlet - that is a deadlock, not a
slowdown. So `send()` only enqueues; a small pool of worker threads runs the
operations and posts every reply back with `loop.call_soon_threadsafe`, which is
the only way to touch the loop from another thread.

â›” **AND ORDERING IS NOT GUARANTEED, WHICH IS CORRECT.** Replies are matched by
`id`, never by arrival order, so a slow `goto` cannot hold up a `title()` on
another page. What must stay ordered is `__create__` before any message that
names the guid it creates, and that is the dispatcher's job, not the pool's.
é    )ÚannotationsN)ÚAnyÚDictÚOptional)Úparse_error)Ú	TransportÚfailureÚBaseExceptionÚreturnúDict[str, str]c           	     óö   — t          | ¦  «        j        t          | ¦  «        pt          | ¦  «        j        d                     t	          j        t          | ¦  «        | | j        ¦  «        ¦  «        dd…         dœS )z²The `{name, message, stack}` shape both an error REPLY and a
    directly-translated exception are built from - one function, so the two
    do not drift the way rule 16 forbids.Ú i`ðÿÿN)ÚnameÚmessageÚstack)ÚtypeÚ__name__ÚstrÚjoinÚ	tracebackÚformat_exceptionÚ__traceback__©r	   s    úXC:\Projects\Historical\venv\Lib\site-packages\invisible_playwright/_juggler/transport.pyÚ_error_payloadr   9   ss   € õ
 �W‘”Ô&Ý�w‘<”<Ð9¥4¨¡=¤=Ô#9Ø—’�Ô3Ý�‰MŒM˜7 GÔ$9ñ;ô ;ñ <ô <Ø<A¸F¸FôDðð ð ó    c                ó:   — t          t          | ¦  «        ¦  «        S )zºA `_juggler`-side exception, reshaped into the fork's own class - the
    same translation `reply_error` puts on the wire, taken directly instead of
    round-tripping through a message.)r   r   r   s    r   Ú_translate_exceptionr   E   s   € õ •~ gÑ.Ô.Ñ/Ô/Ð/r   Úfutureúasyncio.FutureÚresultr   ÚNonec                ó\   — |                       ¦   «         s|                      |¦  «         dS dS )uá   â›” Guarded: a future the caller already cancelled must not be resolved
    again - `asyncio.Future.set_result` on a cancelled future raises, and this
    runs on the loop where that exception would have nowhere useful to go.N)ÚdoneÚ
set_result)r   r!   s     r   Ú_settle_resultr&   L   s8   € ð �;Š;‰=Œ=ð "Ø×Ò˜&Ñ!Ô!Ð!Ð!Ð!ð"ð "r   c                ó\   — |                       ¦   «         s|                      |¦  «         d S d S ©N)r$   Úset_exception)r   r	   s     r   Ú_settle_exceptionr*   T   s6   € Ø�;Š;‰=Œ=ð &Ø×Ò˜WÑ%Ô%Ð%Ð%Ð%ð&ð &r   c                  ó�   ‡ — e Zd ZdZdZd$ˆ fd	„Zd%d„Zd&d„Zd'd„Zd(d„Z	d(d„Z
d(d„Zd(d„Zd(d„Zd)d„Zd(d„Zd)d„Zd*d „Zd+d#„Zˆ xZS ),ÚInProcessTransportzCA `Transport` whose other end is a Python object, not a subprocess.é   Úloopúasyncio.AbstractEventLoopÚserverr   r   r"   c                ó"  •— t          ¦   «                              |¦  «         || _        t          j        ¦   «         | _        t          j        ¦   «         | _        | 	                    ¦   «         | _
        g | _        |                     | ¦  «         d S r(   )ÚsuperÚ__init__Ú_serverÚqueueÚQueueÚ_queueÚ	threadingÚEventÚ_stoppedÚcreate_futureÚ_stopped_futureÚ_threadsÚattach)Úselfr.   r0   Ú	__class__s      €r   r3   zInProcessTransport.__init__a   sq   ø€ Ý‰Œ×Ò˜ÑÔÐØˆŒÝ#(¤;¡=¤=ˆŒÝ!œÑ)Ô)ˆŒØ/3×/AÒ/AÑ/CÔ/CˆÔØ ˆŒØ�Š�dÑÔÐÐÐr   ÚobjectsúDict[str, Any]Údeliver_eventc                ó<   — | j                              ||¦  «         dS )u  Give the server a live view of the CLIENT's guid registry, and its
        way to deliver an event under the correct execution context.

        â›” THE ONLY BRIDGE BETWEEN THE TWO GRAPHS. `objects` is a REFERENCE,
        not a copy: the client mutates it in place as it creates and disposes
        things, so this always sees the CURRENT state with nothing to go
        stale. It exists because a FUSED type - one with no `__create__` and no
        guid of its own - still needs to reach the live impl-side object a
        SIBLING guid corresponds to (a Dialog needs its Page's twin, to find
        the BrowserContext to notify).

        â›” AND `deliver_event` IS NOT OPTIONAL EITHER, found by an actual
        dialog hanging rather than by the unit suite. `Connection.dispatch`
        wraps every wire event in an `EventGreenlet` under the sync facade, so
        a handler that calls an async method (`dialog.accept()`) can suspend
        into it and resume. A fused type delivers its event directly, never
        through `dispatch`, so without this it never gets that wrapping - and
        the handler hangs with no exception, because it suspended into a fiber
        with nobody on the other end of the switch. Dialog is the first user
        of both, 2026-08-29.
        N)r4   Ú
bind_twins)r?   rA   rC   s      r   Úbind_impl_objectsz$InProcessTransport.bind_impl_objectsk   s"   € ð. 	Œ×Ò ¨Ñ7Ô7Ð7Ð7Ð7r   ÚfnÚargsr    c                ó†   ‡ ‡‡‡— ‰ j                              ¦   «         Šdˆˆˆˆ fd„}‰ j                             |¦  «         ‰S )uþ  Run `fn(*args)` on this transport's OWN worker pool; return an
        awaitable that resolves on the LOOP once it is done.

        â›” THE SAME POOL AS EVERY CHANNEL MESSAGE, on purpose: a fused type's
        blocking call and a channel-routed call must never be able to starve
        each other by running on separate pools with separate limits. `_work`
        below accepts a plain callable exactly as it accepts a message dict,
        from the SAME queue, serviced by the SAME four threads.

        â›” AND THE EXCEPTION IS TRANSLATED, not passed through raw. `fn` runs
        `_juggler` code and can raise `_juggler.dispatcher.TargetClosedError` -
        a DIFFERENT CLASS than `_pw._impl._errors.TargetClosedError` with the
        SAME NAME. A fused type's own exception handling
        (`is_target_closed_error`) checks the FORK's class by `isinstance`, so
        a raw `_juggler` exception reaching it would look like an ordinary
        error and never be swallowed - reproducing, through this new path, the
        exact regression fixed on 2026-08-29 in `Page.close()`. The
        translation below goes through `parse_error`, the same one the
        message-envelope path already uses: not a second way of doing it, the
        one way, reused.
        r   r"   c                 óæ   •— 	  ‰‰Ž } ‰j                              t          ‰| ¦  «         d S # t          $ r;}t	          |¦  «        }‰j                              t
          ‰|¦  «         Y d }~d S d }~ww xY wr(   )Ú_loopÚcall_soon_threadsafer&   Ú	Exceptionr   r*   )r!   r	   Ú
translatedrH   rG   r   r?   s      €€€€r   Újobz,InProcessTransport.run_blocking.<locals>.jobœ   s¤   ø€ ðPØ˜˜T˜�ð ”
×/Ò/µÀÈÑOÔOÐOÐOÐOøõ ð <ð <ð <Ý1°'Ñ:Ô:�
Ø”
×/Ò/Õ0AÀ6Ø0:ñ<ô <ð <ð <ð <ð <ð <ð <ð <øøøøð<øøøs   ƒ+ «
A0µ0A+Á+A0©r   r"   )rK   r;   r7   Úput)r?   rG   rH   rO   r   s   ``` @r   Úrun_blockingzInProcessTransport.run_blocking„   sl   øøøø€ ð, "&¤×!9Ò!9Ñ!;Ô!;ˆð	Pð 	Pð 	Pð 	Pð 	Pð 	Pð 	Pð 	Pð 	Pð 	Œ�Š˜ÑÔÐØˆr   c                ó†   — | j                              ¦   «         rdS 	  | j        j        |g|¢R Ž  dS # t          $ r Y dS w xY w)að  Hand a call to the LOOP thread, fire-and-forget - the
        construction half of what `run_blocking` does for a call whose
        RESULT matters. A fused type's incoming EVENT (a dialog that just
        opened, built on the connection's read-loop thread) needs this to
        reach the code that must run on the asyncio loop, exactly like every
        message this transport already delivers via `emit_message` below -
        just carrying a real object instead of a wire message.
        N)r:   Úis_setrK   rL   ÚRuntimeError)r?   rG   rH   s      r   Ú	call_soonzInProcessTransport.call_soon©   si   € ð Œ=×ÒÑ!Ô!ð 	ØˆFð	Ø+ˆDŒJÔ+¨BÐ6°Ð6Ð6Ð6Ð6Ð6Ð6øÝð 	ð 	ð 	ØˆDˆDð	øøøs   �2 ²
A ¿A c              ƒ  óÒ   K  — t          | j        ¦  «        D ]O}t          j        | j        d|z  d¬¦  «        }|                     ¦   «          | j                             |¦  «         ŒPd S )Nz
juggler-%dT)Útargetr   Údaemon)ÚrangeÚWORKERSr8   ÚThreadÚ_workÚstartr=   Úappend)r?   ÚiÚts      r   ÚconnectzInProcessTransport.connectº   st   è è € Ý�t”|Ñ$Ô$ð 	$ð 	$ˆAÝÔ ¨¬
¸ÈÑ9IØ(,ð.ñ .ô .ˆAà�GŠG‰IŒIˆIØŒM× Ò  Ñ#Ô#Ð#Ð#ð		$ð 	$r   c              ƒ  ó$   K  — | j         ƒ d{V —† dS )uˆ   â›” Must not return until the transport is stopped: `Connection.run`
        awaits this and treats its return as the connection ending.N)r<   ©r?   s    r   ÚrunzInProcessTransport.runÁ   s)   è è € ð Ô"Ð"Ð"Ð"Ð"Ð"Ð"Ð"Ð"Ð"r   c                ó€  — | j                              ¦   «         rd S | j                              ¦   «          | j        D ]}| j                             d ¦  «         Œ	 | j                             ¦   «          n# t          $ r Y nw xY w| j	         
                    ¦   «         s!| j                             | j        ¦  «         d S d S r(   )r:   rT   Úsetr=   r7   rQ   r4   ÚshutdownrM   r<   r$   rK   rL   Ú_settle)r?   Ú_s     r   Úrequest_stopzInProcessTransport.request_stopÆ   sÖ   € ØŒ=×ÒÑ!Ô!ð 	ØˆFØŒ×ÒÑÔÐð ”ð 	"ð 	"ˆAØŒK�OŠO˜DÑ!Ô!Ð!Ð!ð	ØŒL×!Ò!Ñ#Ô#Ð#Ð#øÝð 	ð 	ð 	ØˆDð	øøøàÔ#×(Ò(Ñ*Ô*ð 	:ØŒJ×+Ò+¨D¬LÑ9Ô9Ð9Ð9Ð9ð	:ð 	:s   ÁA4 Á4
BÂ Bc                óp   — | j                              ¦   «         s| j                              d ¦  «         d S d S r(   )r<   r$   r%   rd   s    r   ri   zInProcessTransport._settleÕ   s@   € ØÔ#×(Ò(Ñ*Ô*ð 	2ØÔ ×+Ò+¨DÑ1Ô1Ð1Ð1Ð1ð	2ð 	2r   c              ƒ  ód   K  — | j         ƒ d {V —† | j        D ]}|                     d¬¦  «         Œd S )Né   )Útimeout)r<   r=   r   )r?   ra   s     r   Úwait_until_stoppedz%InProcessTransport.wait_until_stoppedÙ   sS   è è € ØÔ"Ð"Ð"Ð"Ð"Ð"Ð"Ð"Ø”ð 	ð 	ˆAØ�FŠF˜1ˆFÑÔÐÐð	ð 	r   r   r   c                ó:   — | j                              |¦  «         dS )ux   â›” ENQUEUE ONLY. Running the operation here would run it on the
        event loop, and every Juggler operation blocks.N)r7   rQ   ©r?   r   s     r   ÚsendzInProcessTransport.sendÞ   s   € ð 	Œ�Š˜Ñ Ô Ð Ð Ð r   c                ó‚  — 	 | j                              ¦   «         }|€d S t          |¦  «        r |¦   «          Œ8|}	 | j                             |¦  «        }|                      |                     d¦  «        |¦  «         n@# t          $ r3}|                      |                     d¦  «        |¦  «         Y d }~nd }~ww xY wŒ¿)NTÚid)r7   ÚgetÚcallabler4   ÚhandleÚreplyrM   Úreply_error)r?   Úitemr   r!   r	   s        r   r]   zInProcessTransport._workä   sÛ   € ð	6Ø”;—?’?Ñ$Ô$ˆDØˆ|Ø�õ
 ˜‰~Œ~ð Ø�‘”�ØØˆGð6Øœ×,Ò,¨WÑ5Ô5�ð —
’
˜7Ÿ;š; tÑ,Ô,¨fÑ5Ô5Ð5Ð5øõ ð =ð =ð =Ø× Ò  §¢¨TÑ!2Ô!2°GÑ<Ô<Ð<Ð<Ð<Ð<Ð<Ð<øøøøð=øøøð	6s   ¼B  Â 
B=Â
)B8Â8B=c                ó    — | j                              ¦   «         rdS 	 | j                             | j        |¦  «         dS # t
          $ r Y dS w xY w)u#  Hand one protocol message up to `Connection.dispatch`.

        â›” `call_soon_threadsafe` and never a direct call: `on_message` walks
        into asyncio futures, and touching those from a worker thread corrupts
        the loop in ways that surface much later and somewhere else.
        N)r:   rT   rK   rL   Ú
on_messagerU   rr   s     r   Úemit_messagezInProcessTransport.emit_messageù   sh   € ð Œ=×ÒÑ!Ô!ð 	ØˆFð	ØŒJ×+Ò+¨D¬O¸WÑEÔEÐEÐEÐEøÝð 	ð 	ð 	ð ˆDˆDð	øøøs   � ? ¿
AÁAÚmsg_idúOptional[int]r!   c                óN   — |€d S d|i}|�||d<   |                       |¦  «         d S )Nru   r!   )r~   )r?   r   r!   Úouts       r   ry   zInProcessTransport.reply	  s>   € Øˆ>ØˆFØ# V˜nˆØÐØ"ˆC�‰MØ×Ò˜#ÑÔÐÐÐr   r	   r
   c                ó\   — |€dS |                       |dt          |¦  «        idœ¦  «         dS )u  â›” The shape matters: `Connection.dispatch` reads
        `msg["error"]["error"]["message"]`, and a flatter error is silently
        turned into a generic one, which is how a precise server-side reason
        becomes "Protocol error" by the time a user sees it.NÚerror)ru   r„   )r~   r   )r?   r   r	   s      r   rz   zInProcessTransport.reply_error  sM   € ð
 ˆ>ØˆFØ×ÒØØ�~¨gÑ6Ô6Ð7ð
ð 
ñ 	ô 	ð 	ð 	ð 	r   )r.   r/   r0   r   r   r"   )rA   rB   rC   r   r   r"   )rG   r   rH   r   r   r    )rG   r   rH   r   r   r"   rP   )r   r   r   r"   )r   r€   r!   r   r   r"   )r   r€   r	   r
   r   r"   )r   Ú
__module__Ú__qualname__Ú__doc__r[   r3   rF   rR   rV   rb   re   rk   ri   rp   rs   r]   r~   ry   rz   Ú__classcell__)r@   s   @r   r,   r,   Y   sO  ø€ € € € € ØMÐMð
 €Gðð ð ð ð ð ð8ð 8ð 8ð 8ð2#ð #ð #ð #ðJð ð ð ð"$ð $ð $ð $ð#ð #ð #ð #ð
:ð :ð :ð :ð2ð 2ð 2ð 2ðð ð ð ð
!ð !ð !ð !ð6ð 6ð 6ð 6ð*ð ð ð ð ð ð ð ð
ð 
ð 
ð 
ð 
ð 
ð 
ð 
r   r,   ÚINVPW_TRANSPORTÚdriverÚjugglerr   c                 ó    — t           j                             t          ¦  «        pt                               ¦   «                              ¦   «         S )zDWhich transport this process asked for. Anything but `driver` is us.)ÚosÚenvironrv   Ú
CHOICE_ENVÚJUGGLERÚstripÚlower© r   r   Úchosenr”   3  s3   € åŒJ�NŠN�:Ñ&Ô&Ð1­'×8Ò8Ñ:Ô:×@Ò@ÑBÔBÐBr   r.   r/   ú'InProcessTransport'c                óš   — t          ¦   «         t          k    rt          dt          z  ¦  «        ‚ddlm} t          |  |¦   «         ¦  «        S )z2The transport a `Connection` should speak through.aÒ  the Node driver was removed on 2026-08-28: this package no longer ships `_driver/` and no longer downloads node. What replaced it is the in-process Python server, which is now the only transport - unset %s and it runs. To get the old arm back for a COMPARISON (the only thing it was still for): check out the last commit that carried it into a git worktree and point INVPW_DRIVER_TREE at it. Both `judge_both_transports.py` and `diff_protocol.py` read that variable.é   )ÚJugglerServer)r”   ÚDRIVERrU   r�   r0   r˜   r,   )r.   r˜   s     r   Úmake_transportrš   8  sa   € å�x„x•6ÒÐÝð5õ 8BñBñCô Cð 	Cð &Ð%Ð%Ð%Ð%Ð%Ý˜d M M¡O¤OÑ4Ô4Ð4r   )r	   r
   r   r   )r	   r
   r   r
   )r   r    r!   r   r   r"   )r   r    r	   r
   r   r"   )r   r   )r.   r/   r   r•   )r‡   Ú
__future__r   Úasyncior�   r5   r8   r   Útypingr   r   r   Ú&invisible_playwright._pw._impl._helperr   Ú)invisible_playwright._pw._impl._transportr   r   r   r&   r*   r,   r�   r™   r�   r”   rš   r“   r   r   ú<module>r       sc  ðð*ð *ðV #Ð "Ð "Ð "Ð "Ð "à €€€Ø 	€	€	€	Ø €€€Ø Ð Ð Ð Ø Ð Ð Ð Ø &Ð &Ð &Ð &Ð &Ð &Ð &Ð &Ð &Ð &à >Ð >Ð >Ð >Ð >Ð >Ø ?Ð ?Ð ?Ð ?Ð ?Ð ?ð	ð 	ð 	ð 	ð0ð 0ð 0ð 0ð"ð "ð "ð "ð&ð &ð &ð &ð
Bð Bð Bð Bð B˜ñ Bô Bð Bðj €
Ø	€Ø
€ðCð Cð Cð Cð
5ð 5ð 5ð 5ð 5ð 5r   