mirror of
https://github.com/servo/servo.git
synced 2025-06-27 10:33:39 +01:00
To show the contents of inline scripts in the Sources panel, we need to send the whole HTML file from script to devtools, not just the script code. This is trickier than the external script case, but we can look to [how Firefox does it](https://servo.zulipchat.com/#narrow/channel/263398-general/topic/Getting.20the.20original.20page.20HTML.20from.20script/near/524392861) for some inspiration. The process is as follows: - when we execute a script - notify devtools to create the source actor - if it’s an external script, send the script code to the devtools server - if it’s an inline script, don’t send any source contents yet - devtools stores the contents in the source actor - while loading a new document - buffer the markup, so we can send it to devtools - when we finish loading a new document - send the buffered markup to the devtools server - devtools stores the contents in any source actors with no contents yet - when a source actor gets a `source` request - if we have the contents, send those contents to the client - if we don’t have the contents (inline script that loaded while devtools was closed) - FUTURE: try to fetch the markup out of cache - otherwise send `<!-- not available; please reload! -->` Testing: Several tests added to test the changes, also updates an existing test with correct assertion Fixes: https://github.com/servo/servo/issues/36874 --------- Signed-off-by: atbrakhi <atbrakhi@igalia.com> Signed-off-by: Delan Azabani <dazabani@igalia.com> Co-authored-by: Delan Azabani <dazabani@igalia.com>
163 lines
4.7 KiB
Rust
163 lines
4.7 KiB
Rust
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||
|
||
use std::cell::RefCell;
|
||
use std::collections::BTreeSet;
|
||
use std::net::TcpStream;
|
||
|
||
use base::id::PipelineId;
|
||
use serde::Serialize;
|
||
use serde_json::{Map, Value};
|
||
use servo_url::ServoUrl;
|
||
|
||
use crate::StreamId;
|
||
use crate::actor::{Actor, ActorMessageStatus, ActorRegistry};
|
||
use crate::protocol::JsonPacketStream;
|
||
|
||
/// A `sourceForm` as used in responses to thread `sources` requests.
|
||
///
|
||
/// For now, we also use this for sources in watcher `resource-available-array` messages,
|
||
/// but in Firefox those have extra fields.
|
||
///
|
||
/// <https://firefox-source-docs.mozilla.org/devtools/backend/protocol.html#loading-script-sources>
|
||
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Serialize)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub(crate) struct SourceForm {
|
||
pub actor: String,
|
||
/// URL of the script, or URL of the page for inline scripts.
|
||
pub url: String,
|
||
pub is_black_boxed: bool,
|
||
}
|
||
|
||
#[derive(Serialize)]
|
||
pub(crate) struct SourcesReply {
|
||
pub from: String,
|
||
pub sources: Vec<SourceForm>,
|
||
}
|
||
|
||
pub(crate) struct SourceManager {
|
||
source_actor_names: RefCell<BTreeSet<String>>,
|
||
}
|
||
|
||
#[derive(Clone, Debug)]
|
||
pub struct SourceActor {
|
||
/// Actor name.
|
||
pub name: String,
|
||
|
||
/// URL of the script, or URL of the page for inline scripts.
|
||
pub url: ServoUrl,
|
||
|
||
/// The ‘black-boxed’ flag, which tells the debugger to avoid pausing inside this script.
|
||
/// <https://firefox-source-docs.mozilla.org/devtools/backend/protocol.html#black-boxing-sources>
|
||
pub is_black_boxed: bool,
|
||
|
||
pub content: Option<String>,
|
||
pub content_type: String,
|
||
}
|
||
|
||
#[derive(Serialize)]
|
||
struct SourceContentReply {
|
||
from: String,
|
||
#[serde(rename = "contentType")]
|
||
content_type: String,
|
||
source: String,
|
||
}
|
||
|
||
impl SourceManager {
|
||
pub fn new() -> Self {
|
||
Self {
|
||
source_actor_names: RefCell::new(BTreeSet::default()),
|
||
}
|
||
}
|
||
|
||
pub fn add_source(&self, actor_name: &str) {
|
||
self.source_actor_names
|
||
.borrow_mut()
|
||
.insert(actor_name.to_owned());
|
||
}
|
||
|
||
pub fn source_forms(&self, actors: &ActorRegistry) -> Vec<SourceForm> {
|
||
self.source_actor_names
|
||
.borrow()
|
||
.iter()
|
||
.map(|actor_name| actors.find::<SourceActor>(actor_name).source_form())
|
||
.collect()
|
||
}
|
||
}
|
||
|
||
impl SourceActor {
|
||
pub fn new(
|
||
name: String,
|
||
url: ServoUrl,
|
||
content: Option<String>,
|
||
content_type: String,
|
||
) -> SourceActor {
|
||
SourceActor {
|
||
name,
|
||
url,
|
||
content,
|
||
content_type,
|
||
is_black_boxed: false,
|
||
}
|
||
}
|
||
|
||
pub fn new_registered(
|
||
actors: &mut ActorRegistry,
|
||
pipeline_id: PipelineId,
|
||
url: ServoUrl,
|
||
content: Option<String>,
|
||
content_type: String,
|
||
) -> &SourceActor {
|
||
let source_actor_name = actors.new_name("source");
|
||
|
||
let source_actor = SourceActor::new(source_actor_name.clone(), url, content, content_type);
|
||
actors.register(Box::new(source_actor));
|
||
actors.register_source_actor(pipeline_id, &source_actor_name);
|
||
|
||
actors.find(&source_actor_name)
|
||
}
|
||
|
||
pub fn source_form(&self) -> SourceForm {
|
||
SourceForm {
|
||
actor: self.name.clone(),
|
||
url: self.url.to_string(),
|
||
is_black_boxed: self.is_black_boxed,
|
||
}
|
||
}
|
||
}
|
||
|
||
impl Actor for SourceActor {
|
||
fn name(&self) -> String {
|
||
self.name.clone()
|
||
}
|
||
|
||
fn handle_message(
|
||
&self,
|
||
_registry: &ActorRegistry,
|
||
msg_type: &str,
|
||
_msg: &Map<String, Value>,
|
||
stream: &mut TcpStream,
|
||
_id: StreamId,
|
||
) -> Result<ActorMessageStatus, ()> {
|
||
Ok(match msg_type {
|
||
// Client has requested contents of the source.
|
||
"source" => {
|
||
let reply = SourceContentReply {
|
||
from: self.name(),
|
||
content_type: self.content_type.clone(),
|
||
// TODO: do we want to wait instead of giving up immediately, in cases where the content could
|
||
// become available later (e.g. after a fetch)?
|
||
source: self
|
||
.content
|
||
.as_deref()
|
||
.unwrap_or("<!-- not available; please reload! -->")
|
||
.to_owned(),
|
||
};
|
||
let _ = stream.write_json_packet(&reply);
|
||
ActorMessageStatus::Processed
|
||
},
|
||
_ => ActorMessageStatus::Ignored,
|
||
})
|
||
}
|
||
}
|