8000 Send WillNavigate earlier during navigation startup by uthmaniv · Pull Request #37778 · servo/servo · GitHub
[go: up one dir, main page]

Skip to content

Conversation

uthmaniv
Copy link
Member
@uthmaniv uthmaniv commented Jun 29, 2025

The will-navigate message tells the devtools client to expect a navigation for a browsing context. This makes the network monitor clear any previous entries and show the requests for the new page that is loaded. In order to support this correctly, we need to send the navigation notification from the constellation instead of the script thread, otherwise we silently ignore navigations triggered by the browser URL bar.

Testing: Ran servo in devtools mode , now the requests appear for new loaded page
Fixes: #37334

uthmaniv added 2 commits June 29, 2025 19:36
Signed-off-by: Uthman Yahaya Baba <uthmanyahayababa@gmail.com>
…for WebViewId argument

Signed-off-by: Uthman Yahaya Baba <uthmanyahayababa@gmail.com>
@jdm
Copy link
Member
jdm commented Jun 30, 2025

I figured out why the requests are now missing! The change from calling resource_available(..) on the BrowsingContextActor to calling it on the NetworkEventActor means that the from field in the message now refers to an actor that the client has never seen before, so the message gets ignored.

Since the Firefox-only logs show these messages being sent from the Watcher actors, I propose the following fix:
0. move the new ResourceAvailable impl from NetworkEventActor to WatcherActor

  1. enable the network-event resource watching in
    ("network-event", false),
  2. add an empty branch for network-event to
    "console-message" | "error-message" => {},
  3. add a new watcher_name field to NetworkEventActor, and take it as an argument in DevtoolsInstance::find_network_event_actor
  4. using the new BrowsingContextId you've added to the devtools HttpRequest and HttpResponse, in handle_network_event, find the BrowsingContextActor for the given id and get the associated watcher name from the actor's public fields
  5. find the watcher actor in network_handler::handle_network_event and use it to invoke the resource_available(..) methods

The end result should move us closer to the behaviour that Firefox expects!

uthmaniv added 2 commits July 1, 2025 14:46
Signed-off-by: Uthman Yahaya Baba <uthmanyahayababa@gmail.com>
Signed-off-by: Uthman Yahaya Baba <uthmanyahayababa@gmail.com>
Copy link
Member
@jdm jdm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good start!

Comment on lines 378 to 385
let msg = WillNavigate {
browsing_context_id,
url: url.clone(),
};

for stream in self.streams.borrow_mut().values_mut() {
let _ = stream.write_json_packet(&msg);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This message is not right. We need a resource-available message that looks like https://gist.github.com/uthmaniv/611b99c3531499dc7170e8b11e19d206#file-gistfile1-txt-L463-L481 . We can use default values for innerWindowId and isFrameSwitching with a TODO about setting them correctly.

I believe we also want this message to be sent from the Watcher actor instead of the BrowsingContext actor, too.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it, I was initially contemplating to send it from th WatcherActor. Thanks for the clarification

Comment on lines 112 to 115
WillNavigate {
browsing_context_id: BrowsingContextId,
url: ServoUrl,
},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This message won't be used after my comments are addressed, so let's remove it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

.unwrap_or_else(|| global.upcast::<GlobalScope>().get_referrer());

let request = RequestBuilder::new(None, script_url, referrer)
let request = RequestBuilder::new(Some(TEST_WEBVIEW_ID), script_url, referrer)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't use TEST_WEBVIEW_ID in non-test code. Let's keep this as None.

let _ = fetch_terminated_sender.send(false);
}

let browsing_context_id = request.target_webview_id.unwrap().0;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of unwrapping this, we should pass the Option value.

meta_headers.map(Serde::into_inner),
meta_status,
pipeline_id,
browsing_context_id,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's only send the information to the devtools if there is a non-None browsing context id.

is_xhr: bool,
context: &FetchContext,
fetch_terminated: UnboundedSender<bool>,
browsing_context_id: BrowsingContextId,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's make this Option<BrowsingContextId>.

mut connections: Vec<TcpStream>,
network_event: NetworkEvent,
browsing_context_actor_name: String,
watcher_name: String,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need to pass this here; it's already stored as a field in NetworkEventActor.

Comment on lines 495 to 496
NetworkEvent::HttpRequest(req) => Some(req.browsing_context_id),
NetworkEvent::HttpResponse(resp) => Some(resp.browsing_context_id),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
NetworkEvent::HttpRequest(req) => Some(req.browsing_context_id),
NetworkEvent::HttpResponse(resp) => Some(resp.browsing_context_id),
NetworkEvent::HttpRequest(req) => req.browsing_context_id,
NetworkEvent::HttpResponse(resp) => resp.browsing_context_id,

Comment on lines 499 to 510
let watcher_name = browsing_context_id
.and_then(|id| self.browsing_contexts.get(&id))
.map(|actor_name| {
self.actors
.lock()
.unwrap()
.find::<BrowsingContextActor>(actor_name)
.watcher
.clone()
});

if watcher_name.is_none() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's do:

let Some(browsing_context_actor_name) = self.browsing_contexts.get(&browsing_context_id) else { return };
let watcher_name = self.actors
    .lock()
    .unwrap()
    .find::<BrowsingContextActor>(browsing_context_actor_name)
    .watcher
   .clone();

Then there are no unwraps required and it's clear where any failure needs to be handled.

Comment on lines 324 to 325
let binding = self.actors.lock().unwrap();
let actor = binding
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
let binding = self.actors.lock().unwrap();
let actor = binding
let actors = self.actors.lock().unwrap();
let actor = actors

Signed-off-by: Uthman Yahaya Baba <uthmanyahayababa@gmail.com>
Copy link
Member
@jdm jdm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really close now! Have you tested the changes? Does navigating work as expected now?

9E81
(send_end - send_start).unsigned_abs(),
is_xhr,
browsing_context_id,
browsing_context_id.unwrap(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need another if let around prepare_devtools_request instead of using unwrap here.

url: ServoUrl,
connections: &mut Vec<TcpStream>,
) {
let msg = serde_json::json!({
Copy link
Member
@jdm jdm Jul 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we create a new struct for this message and its fields instead of using the json macro?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, sure

connections: &mut Vec<TcpStream>,
) {
let msg = serde_json::json!({
"browsingContextID": browsing_context_id,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We will need to use browsing_context_id.0 to get the id number instead of the string representation.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

@uthmaniv
Copy link
Member Author
uthmaniv commented Jul 3, 2025

Really close now! Have you tested the changes? Does navigating work as expected now?

Yeah I did test it but it still doesn't work as expected, there are some changes from the previous commits though.
Previous commit:

  • On refresh, it shows a single new request for the refreshed url
  • The single new request is included in the already present requests (does not clear the previous requests)
    Current Commit:
  • No new request is shown for a refresh but clears previous request from the network panel

@jdm
Copy link
Member
jdm commented Jul 3, 2025

I suspect fixing the browsing context id value in the message will show an improvement.

@uthmaniv
Copy link
Member Author
uthmaniv commented Jul 3, 2025

I suspect fixing the browsing context id value in the message will show an improvement.

Alright , we are getting closer :)

Signed-off-by: Uthman Yahaya Baba <uthmanyahayababa@gmail.com>
Copy link
Member
@jdm jdm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think one final set of changes will bring us success!

#[derive(Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct WillNavigateMessage {
browsing_context_id: DevtoolsBrowsingContextId,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
browsing_context_id: DevtoolsBrowsingContextId,
#[serde(rename = "browsingContextID")]
browsing_context_id: u32,

"newURI": url,
});
is_frame_switching: false, // TODO: Implement frame switching
new_URI: url,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
new_URI: url,
new_uri: url,

"name": "will-navigate",
"time": SystemTime::now()
let msg = WillNavigateMessage {
browsing_context_id: id_map.browsing_context_id(browsing_context_id),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
browsing_context_id: id_map.browsing_context_id(browsing_context_id),
browsing_context_id: id_map.browsing_context_id(browsing_context_id).value(),

pub(crate) struct DevtoolsBrowserId(u32);

#[derive(Clone, Copy, Debug, PartialEq)]
#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
#[derive(Clone, Copy, Debug, PartialEq)]

use std::coll 6D3F ections::HashMap;

use base::id::{BrowsingContextId, PipelineId, WebViewId};
use serde::Serialize;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
use serde::Serialize;

actor.navigate(state, &mut self.id_map.lock().expect("Mutex poisoned"));

let mut id_map = self.id_map.lock().expect("Mutex poisoned");
watcher_actor.emit_will_navigate(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to ensure we only send the will-navigate message when the state is NavigationState::Start.

@jdm
Copy link
Member
jdm commented Jul 4, 2025

Besides the changes from my latest review comments, I experimented with your branch locally and have the following diff:

diff --git a/components/constellation/constellation.rs b/components/constellation/constellation.rs
index 29d3c656b5..adf7e63181 100644
--- a/components/constellation/constellation.rs
+++ b/components/constellation/constellation.rs
@@ -3612,6 +3612,13 @@ where
                 },
             };

+        if let Some(ref chan) = self.devtools_sender {
+            let state = NavigationState::Start(load_data.url.clone());
+            let _ = chan.send(DevtoolsControlMsg::FromScript(
+                ScriptToDevtoolsControlMsg::Navigate(browsing_context_id, state),
+            ));
+        }
+
         match parent_pipeline_id {
             Some(parent_pipeline_id) => {
                 // Find the script thread for the pipeline containing the iframe
diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs
index d109e95616..f2b53e4cd2 100644
--- a/components/script/script_thread.rs
+++ b/components/script/script_thread.rs
@@ -642,13 +642,6 @@ impl ScriptThread {
                     .dom_manipulation_task_source()
                     .queue(task);
             } else {
-                if let Some(ref sender) = script_thread.senders.devtools_server_sender {
-                    let _ = sender.send(ScriptToDevtoolsControlMsg::Navigate(
-                        browsing_context,
-                        NavigationState::Start(load_data.url.clone()),
-                    ));
-                }
-
                 script_thread
                     .senders
                     .pipeline_to_constellation_sender

This change makes us send the NavigationState::Start message to devtools consistently for all navigations, including typing in the URL bar. Previously, we only sent it when initiating a navigation from within the script thread by clicking links, submitting forms, using window.location = some_url, etc.

This does not address the issue with refreshing a page. I have filed #37869 about that after some investigation. I think with the change applied from the diff (and subsequent warnings fixed), this PR is good enough to merge!

uthmaniv added 2 commits July 4, 2025 15:26
Signed-off-by: Uthman Yahaya Baba <uthmanyahayababa@gmail.com>
…ruction in net/tests/

Signed-off-by: Uthman Yahaya Baba <uthmanyahayababa@gmail.com>
Copy link
Member
@jdm jdm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good! Can you update this PR's description so it reflects the latest status of these changes? Also, please move the PR out of draft mode :)

@uthmaniv uthmaniv marked this pull request as ready for review July 4, 2025 15:22
@uthmaniv uthmaniv requested a review from gterzian as a code owner July 4, 2025 15:22
Signed-off-by: Uthman Yahaya Baba <uthmanyahayababa@gmail.com>
@jdm jdm enabled auto-merge July 4, 2025 16:02
Signed-off-by: Uthman Yahaya Baba <uthmanyahayababa@gmail.com>
auto-merge was automatically disabled July 5, 2025 08:25

Head branch was pushed to by a user without write access

@jdm jdm added this pull request to the merge queue Jul 5, 2025
Merged via the queue into servo:main with commit 2ad5b24 Jul 5, 2025
21 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

DevTools network panel does not show requests when refreshing a page or navigating to subsequent URLs in the same tab.
2 participants
0