@@ -226,6 +226,8 @@ pub enum NavigationResult<C: RenderConfiguration> {
226226 meta : FetchResultMeta ,
227227 spool : crate :: net:: Spool ,
228228 } ,
229+ /// The response was 204 or 205: the navigation ends and the page stays.
230+ NoContent { nav_id : NavigationId , url : Url } ,
229231}
230232
231233// Current active navigation
@@ -375,9 +377,10 @@ pub struct TabWorker<C: RenderConfiguration> {
375377 /// the renders that follow do not fetch it again. Cleared when a navigation
376378 /// commits a new document.
377379 remote_favicon : Option < Url > ,
378- /// The history entry the shown document was committed to. A traversal
379- /// moves the cursor before its load commits, and a title the renderer
380- /// reports meanwhile is still this document's, not the cursor's.
380+ /// The history entry the shown page is on: the one its document was
381+ /// committed to, or a fragment navigation's since. A traversal moves the
382+ /// cursor before its load commits, and a title the renderer reports
383+ /// meanwhile is still this document's, not the cursor's.
381384 document_entry : Option < HistoryEntryId > ,
382385}
383386
@@ -781,6 +784,45 @@ impl<C: RenderConfiguration> TabWorker<C> {
781784 self . context . has_remote_renderer ( )
782785 }
783786
787+ /// End a navigation that leaves the current document showing. Not an error: the
788+ /// Cancelled event stops the shell's spinner and puts its address bar back.
789+ fn end_navigation_in_place ( & mut self , nav_id : NavigationId , url : Url , reason : & str ) {
790+ self . sink . security . abandon ( nav_id) ;
791+ // A superseded navigation's result: the tab's state is the newer one's.
792+ if self . active_nav . as_ref ( ) . is_some_and ( |a| a. nav_id == nav_id) {
793+ self . active_nav = None ;
794+ self . load = None ;
795+ self . is_loading = false ;
796+ self . is_error = false ;
797+ self . state = TabState :: Idle ;
798+ self . pending_url = None ;
799+ self . return_to_document_entry ( ) ;
800+ }
801+ self . send_event ( EngineEvent :: Navigation {
802+ tab_id : self . tab_id ,
803+ event : NavigationEvent :: Cancelled {
804+ nav_id,
805+ url,
806+ reason : crate :: engine:: events:: CancelReason :: Custom ( reason. into ( ) ) ,
807+ } ,
808+ } ) ;
809+ }
810+
811+ /// Undo what the start of a navigation did to the page that stays: a traversal moved the
812+ /// history cursor, and every navigation scrolled to the top. The offset is the one saved
813+ /// on the document's entry when the navigation began.
814+ fn return_to_document_entry ( & mut self ) {
815+ let Some ( entry) = self . document_entry else {
816+ return ;
817+ } ;
818+ if self . history . current ( ) != Some ( entry) && self . history . go_to ( entry) . is_some ( ) {
819+ self . emit_history_changed ( ) ;
820+ }
821+ if let Some ( ( x, y) ) = self . history . entry ( entry) . map ( |e| e. scroll ) {
822+ self . apply_scroll ( x, y) ;
823+ }
824+ }
825+
784826 fn on_nav_result ( & mut self , res : NavigationResult < C > ) {
785827 match res {
786828 NavigationResult :: Ok {
@@ -925,29 +967,16 @@ impl<C: RenderConfiguration> TabWorker<C> {
925967 self . remote_capture = false ;
926968 self . remote_focus = None ;
927969 }
970+ NavigationResult :: NoContent { nav_id, url } => {
971+ self . end_navigation_in_place ( nav_id, url, "no content" ) ;
972+ }
928973 NavigationResult :: Download { nav_id, meta, spool } => {
929- self . sink . security . abandon ( nav_id) ;
930974 // Not an error and not a page change: the tab stays on its current document
931- // and the shell gets a download offer. The Cancelled event stops spinners.
975+ // and the shell gets a download offer.
932976 // The navigation ends here, without cancelling it: its token is the fetch's
933977 // parent, so the next navigation or a CancelNavigation would stop the body
934978 // under an accepted download. The spool owns the fetch from now on.
935- if self . active_nav . as_ref ( ) . is_some_and ( |a| a. nav_id == nav_id) {
936- self . active_nav = None ;
937- self . load = None ;
938- }
939- self . is_loading = false ;
940- self . is_error = false ;
941- self . state = TabState :: Idle ;
942- self . pending_url = None ;
943- self . send_event ( EngineEvent :: Navigation {
944- tab_id : self . tab_id ,
945- event : NavigationEvent :: Cancelled {
946- nav_id,
947- url : meta. final_url . clone ( ) ,
948- reason : crate :: engine:: events:: CancelReason :: Custom ( "download" . into ( ) ) ,
949- } ,
950- } ) ;
979+ self . end_navigation_in_place ( nav_id, meta. final_url . clone ( ) , "download" ) ;
951980 let info = crate :: net:: types:: ResponseInfo :: from ( & meta) ;
952981 self . next_offer += 1 ;
953982 let pending = PendingDownload {
@@ -2070,6 +2099,7 @@ impl<C: RenderConfiguration> TabWorker<C> {
20702099 }
20712100 HistoryIntent :: Reload | HistoryIntent :: Replace | HistoryIntent :: Traverse ( _) => { }
20722101 }
2102+ self . document_entry = self . history . current ( ) ;
20732103 self . current_url = Some ( url. clone ( ) ) ;
20742104
20752105 // Layout already exists, so the target can be scrolled to right away. A traversal to
@@ -2451,6 +2481,12 @@ impl<C: RenderConfiguration> TabWorker<C> {
24512481 spool,
24522482 } ) ;
24532483 }
2484+ Ok ( RoutedOutcome :: NoContent ) => {
2485+ let _ = tx_done. send ( NavigationResult :: NoContent {
2486+ nav_id,
2487+ url : document_final_url,
2488+ } ) ;
2489+ }
24542490 Ok ( RoutedOutcome :: ViewerRendered ( _doc) ) => {
24552491 log:: warn!( "Tab[{:?}] viewer rendering not supported yet" , tab_id) ;
24562492 let _ = tx_done. send ( NavigationResult :: Err {
0 commit comments