tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

debugger.rs (15502B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 use crate::{DebugCommand, RenderApi, ApiMsg};
      6 use crate::profiler::{Profiler, RenderCommandLog};
      7 use crate::composite::CompositeState;
      8 use std::collections::HashMap;
      9 use std::convert::Infallible;
     10 use api::crossbeam_channel;
     11 use api::channel::{Sender, unbounded_channel};
     12 use api::{DebugFlags, TextureCacheCategory};
     13 use api::debugger::{DebuggerMessage, SetDebugFlagsMessage, ProfileCounterDescriptor};
     14 use api::debugger::{FrameLogMessage, InitProfileCountersMessage, ProfileCounterId};
     15 use api::debugger::{CompositorDebugInfo, CompositorDebugTile};
     16 use std::thread;
     17 use base64::prelude::*;
     18 use sha1::{Sha1, Digest};
     19 use hyper::{Request, Response, Body, service::{make_service_fn, service_fn}, Server};
     20 use tokio::io::AsyncWriteExt;
     21 
     22 /// A minimal wrapper around RenderApi's channel that can be cloned.
     23 #[derive(Clone)]
     24 struct DebugRenderApi {
     25    api_sender: Sender<ApiMsg>,
     26 }
     27 
     28 impl DebugRenderApi {
     29    fn new(api: &RenderApi) -> Self {
     30        Self {
     31            api_sender: api.get_api_sender(),
     32        }
     33    }
     34 
     35    fn get_debug_flags(&self) -> DebugFlags {
     36        let (tx, rx) = unbounded_channel();
     37        let msg = ApiMsg::DebugCommand(DebugCommand::GetDebugFlags(tx));
     38        self.api_sender.send(msg).unwrap();
     39        rx.recv().unwrap()
     40    }
     41 
     42    fn send_debug_cmd(&self, cmd: DebugCommand) {
     43        let msg = ApiMsg::DebugCommand(cmd);
     44        self.api_sender.send(msg).unwrap();
     45    }
     46 }
     47 
     48 /// Implements the WR remote debugger interface, that the `wrshell` application
     49 /// can connect to when the cargo feature `debugger` is enabled. There are two
     50 /// communication channels available. First, a simple HTTP server that listens
     51 /// for commands and can act on those and/or return query results about WR
     52 /// internal state. Second, a client can optionally connect to the /debugger-socket
     53 /// endpoint for real time updates. This will be upgraded to a websocket connection
     54 /// allowing the WR instance to stream information to client(s) as appropriate.
     55 
     56 /// Details about the type of debug query being requested
     57 #[derive(Clone)]
     58 pub enum DebugQueryKind {
     59    /// Query the current spatial tree
     60    SpatialTree {},
     61    /// Query the compositing config
     62    CompositorConfig {},
     63    /// Query the compositing view
     64    CompositorView {},
     65    /// Query the content of GPU textures
     66    Textures { category: Option<TextureCacheCategory> },
     67 }
     68 
     69 /// Details about the debug query being requested
     70 #[derive(Clone)]
     71 pub struct DebugQuery {
     72    /// Kind of debug query (filters etc)
     73    pub kind: DebugQueryKind,
     74    /// Where result should be sent
     75    pub result: Sender<String>,
     76 }
     77 
     78 /// A remote debugging client. These are stored with a stream that can publish
     79 /// realtime events to (such as debug flag changes, profile counter updates etc).
     80 pub struct DebuggerClient {
     81    tx: tokio::sync::mpsc::UnboundedSender<Vec<u8>>,
     82 }
     83 
     84 impl DebuggerClient {
     85    /// Send a debugger message to this client
     86    fn send_msg(
     87        &mut self,
     88        msg: DebuggerMessage,
     89    ) -> bool {
     90        let data = serde_json::to_string(&msg).expect("bug");
     91        let data = construct_server_ws_frame(&data);
     92 
     93        self.tx.send(data).is_ok()
     94    }
     95 }
     96 
     97 /// The main debugger interface that exists in a WR instance
     98 pub struct Debugger {
     99    /// List of currently connected debug clients
    100    clients: Vec<DebuggerClient>,
    101 }
    102 
    103 impl Debugger {
    104    pub fn new() -> Self {
    105        Debugger {
    106            clients: Vec::new(),
    107        }
    108    }
    109 
    110    /// Add a newly connected client
    111    pub fn add_client(
    112        &mut self,
    113        mut client: DebuggerClient,
    114        debug_flags: DebugFlags,
    115        profiler: &Profiler,
    116    ) {
    117        // Send initial state to client
    118        let msg = SetDebugFlagsMessage {
    119            flags: debug_flags,
    120        };
    121        if client.send_msg(DebuggerMessage::SetDebugFlags(msg)) {
    122            let mut counters = Vec::new();
    123            for (id, counter) in profiler.counters().iter().enumerate() {
    124                counters.push(ProfileCounterDescriptor {
    125                    id: ProfileCounterId(id),
    126                    name: counter.name.into(),
    127                });
    128            }
    129            let msg = InitProfileCountersMessage {
    130                counters
    131            };
    132            if client.send_msg(DebuggerMessage::InitProfileCounters(msg)) {
    133                // Successful initial connection, add to list for per-frame updates
    134                self.clients.push(client);
    135            }
    136        }
    137    }
    138 
    139    /// Per-frame update. Stream any important updates to connected debug clients.
    140    /// On error, the client is dropped from the active connections.
    141    pub fn update(
    142        &mut self,
    143        debug_flags: DebugFlags,
    144        profiler: &Profiler,
    145        command_log: &Option<RenderCommandLog>,
    146    ) {
    147        let mut clients_to_keep = Vec::new();
    148 
    149        for mut client in self.clients.drain(..) {
    150            let msg = SetDebugFlagsMessage {
    151                flags: debug_flags,
    152            };
    153            let profile_counters = if client.send_msg(DebuggerMessage::SetDebugFlags(msg)) {
    154                Some(profiler.collect_updates_for_debugger())
    155            } else {
    156                None
    157            };
    158 
    159            let render_commands = command_log.as_ref().map(|dc| { dc.get().to_vec() });
    160 
    161            let msg = FrameLogMessage {
    162                profile_counters,
    163                render_commands,
    164            };
    165 
    166            if client.send_msg(DebuggerMessage::UpdateFrameLog(msg)) {
    167                clients_to_keep.push(client);
    168            }
    169        }
    170 
    171        self.clients = clients_to_keep;
    172    }
    173 }
    174 
    175 /// Start the debugger thread that listens for requests from clients.
    176 pub fn start(api: RenderApi) {
    177    let address = "127.0.0.1:3583";
    178 
    179    println!("Start WebRender debugger server on http://{}", address);
    180 
    181    let api = DebugRenderApi::new(&api);
    182 
    183    thread::spawn(move || {
    184        let runtime = match tokio::runtime::Runtime::new() {
    185            Ok(rt) => rt,
    186            Err(e) => {
    187                println!("\tUnable to create tokio runtime for the webrender debugger: {}", e);
    188                return;
    189            }
    190        };
    191 
    192        runtime.block_on(async {
    193            let make_svc = make_service_fn(move |_conn| {
    194                let api = api.clone();
    195                async move {
    196                    Ok::<_, Infallible>(service_fn(move |req| {
    197                        handle_request(req, api.clone())
    198                    }))
    199                }
    200            });
    201 
    202            let addr = address.parse().unwrap();
    203            let server = match Server::try_bind(&addr) {
    204                Ok(s) => s,
    205                Err(e) => {
    206                    eprintln!("WebRender debugger could not bind: {addr}: {e:?}");
    207                    return;
    208                }
    209            };
    210 
    211            if let Err(e) = server.serve(make_svc).await {
    212                eprintln!("WebRender debugger error: {:?}", e);
    213            }
    214        });
    215    });
    216 }
    217 
    218 async fn request_to_string(request: Request<Body>) -> Result<String, hyper::Error> {
    219    let body_bytes = hyper::body::to_bytes(request.into_body()).await?;
    220    Ok(String::from_utf8_lossy(&body_bytes).to_string())
    221 }
    222 
    223 fn string_response<S: Into<String>>(string: S) -> Response<Body> {
    224    Response::new(Body::from(string.into()))
    225 }
    226 
    227 fn status_response(status: u16) -> Response<Body> {
    228    Response::builder().status(status).body(Body::from("")).unwrap()
    229 }
    230 
    231 async fn handle_request(
    232    request: Request<Body>,
    233    api: DebugRenderApi,
    234 ) -> Result<Response<Body>, Infallible> {
    235    let path = request.uri().path();
    236    let query = request.uri().query().unwrap_or("");
    237    let args: HashMap<String, String> = url::form_urlencoded::parse(query.as_bytes())
    238        .into_owned()
    239        .collect();
    240 
    241    match path {
    242        "/ping" => {
    243            // Client can check if server is online and accepting connections
    244            Ok(string_response("pong"))
    245        }
    246        "/debug-flags" => {
    247            // Get or set the current debug flags
    248            match request.method() {
    249                &hyper::Method::GET => {
    250                    let debug_flags = api.get_debug_flags();
    251                    let result = serde_json::to_string(&debug_flags).unwrap();
    252                    Ok(string_response(result))
    253                }
    254                &hyper::Method::POST => {
    255                    let content = request_to_string(request).await.unwrap();
    256                    let flags = serde_json::from_str(&content).expect("bug");
    257                    api.send_debug_cmd(
    258                        DebugCommand::SetFlags(flags)
    259                    );
    260                    api.send_debug_cmd(
    261                        DebugCommand::GenerateFrame
    262                    );
    263                    Ok(string_response(format!("flags = {:?}", flags)))
    264                }
    265                _ => {
    266                    Ok(status_response(403))
    267                }
    268            }
    269        }
    270        "/render-cmd-log" => {
    271            match request.method() {
    272                &hyper::Method::POST => {
    273                    let content = request_to_string(request).await.unwrap();
    274                    let enabled = serde_json::from_str(&content).expect("bug");
    275                    api.send_debug_cmd(
    276                        DebugCommand::SetRenderCommandLog(enabled)
    277                    );
    278                    Ok(string_response(format!("{:?}", enabled)))
    279                }
    280                _ => {
    281                    Ok(status_response(403))
    282                }
    283            }
    284        }
    285        "/generate-frame" => {
    286            // Force generate a frame-build and composite
    287            api.send_debug_cmd(
    288                DebugCommand::GenerateFrame
    289            );
    290            Ok(status_response(200))
    291        }
    292        "/query" => {
    293            // Query internal state about WR.
    294            let (tx, rx) = crossbeam_channel::unbounded();
    295            let kind = match args.get("type").map(|s| s.as_str()) {
    296                Some("spatial-tree") => DebugQueryKind::SpatialTree {},
    297                Some("composite-view") => DebugQueryKind::CompositorView {},
    298                Some("composite-config") => DebugQueryKind::CompositorConfig {},
    299                Some("textures") => DebugQueryKind::Textures { category: None },
    300                Some("atlas-textures") => DebugQueryKind::Textures { category: Some(TextureCacheCategory::Atlas) },
    301                Some("target-textures") => DebugQueryKind::Textures { category: Some(TextureCacheCategory::RenderTarget) },
    302                Some("tile-textures") => DebugQueryKind::Textures { category: Some(TextureCacheCategory::PictureTile) },
    303                Some("standalone-textures") => DebugQueryKind::Textures { category: Some(TextureCacheCategory::Standalone) },
    304                _ => {
    305                    return Ok(string_response("Unknown query"));
    306                }
    307            };
    308 
    309            let query = DebugQuery {
    310                result: tx,
    311                kind,
    312            };
    313            api.send_debug_cmd(
    314                DebugCommand::Query(query)
    315            );
    316            let result = match rx.recv() {
    317                Ok(result) => result,
    318                Err(..) => "No response received from WR".into(),
    319            };
    320            Ok(string_response(result))
    321        }
    322        "/debugger-socket" => {
    323            // Connect to a realtime stream of events from WR. This is handled
    324            // by upgrading the HTTP request to a websocket.
    325 
    326            let upgrade_header = request.headers().get("upgrade");
    327            if upgrade_header.is_none() || upgrade_header.unwrap() != "websocket" {
    328                return Ok(status_response(404));
    329            }
    330 
    331            let key = match request.headers().get("sec-websocket-key") {
    332                Some(k) => k.to_str().unwrap_or(""),
    333                None => {
    334                    return Ok(status_response(400));
    335                }
    336            };
    337 
    338            let accept_key = convert_ws_key(key);
    339 
    340            tokio::spawn(async move {
    341                match hyper::upgrade::on(request).await {
    342                    Ok(upgraded) => {
    343                        let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel::<Vec<u8>>();
    344 
    345                        // Spawn a task to handle writing to the WebSocket stream
    346                        tokio::spawn(async move {
    347                            let mut stream = upgraded;
    348                            while let Some(data) = rx.recv().await {
    349                                if stream.write_all(&data).await.is_err() {
    350                                    break;
    351                                }
    352                                if stream.flush().await.is_err() {
    353                                    break;
    354                                }
    355                            }
    356                        });
    357 
    358                        api.send_debug_cmd(
    359                            DebugCommand::AddDebugClient(DebuggerClient {
    360                                tx,
    361                            })
    362                        );
    363                    }
    364                    Err(e) => eprintln!("Upgrade error: {}", e),
    365                }
    366            });
    367 
    368            Ok(Response::builder()
    369                .status(101)
    370                .header("upgrade", "websocket")
    371                .header("connection", "upgrade")
    372                .header("sec-websocket-accept", accept_key)
    373                .body(Body::from(""))
    374                .unwrap())
    375        }
    376        _ => {
    377            Ok(status_response(404))
    378        }
    379    }
    380 }
    381 
    382 /// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Sec-WebSocket-Key
    383 /// See https://datatracker.ietf.org/doc/html/rfc6455#section-11.3.1
    384 fn convert_ws_key(input: &str) -> String {
    385    let mut input = input.to_string().into_bytes();
    386    let mut bytes = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
    387        .to_string()
    388        .into_bytes();
    389    input.append(&mut bytes);
    390 
    391    let sha1 = Sha1::digest(&input);
    392    BASE64_STANDARD.encode(sha1)
    393 }
    394 
    395 /// Convert a string to a websocket text frame
    396 /// See https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_servers#exchanging_data_frames
    397 pub fn construct_server_ws_frame(payload: &str) -> Vec<u8> {
    398    let payload_bytes = payload.as_bytes();
    399    let payload_len = payload_bytes.len();
    400    let mut frame = Vec::new();
    401 
    402    frame.push(0x81);
    403 
    404    if payload_len <= 125 {
    405        frame.push(payload_len as u8);
    406    } else if payload_len <= 65535 {
    407        frame.push(126 as u8);
    408        frame.extend_from_slice(&(payload_len as u16).to_be_bytes());
    409    } else {
    410        frame.push(127 as u8);
    411        frame.extend_from_slice(&(payload_len as u64).to_be_bytes());
    412    }
    413 
    414    frame.extend_from_slice(payload_bytes);
    415 
    416    frame
    417 }
    418 
    419 impl From<&CompositeState> for CompositorDebugInfo {
    420    fn from(state: &CompositeState) -> Self {
    421        let tiles = state.tiles
    422            .iter()
    423            .map(|tile| {
    424                CompositorDebugTile {
    425                    local_rect: tile.local_rect,
    426                    clip_rect: tile.device_clip_rect,
    427                    device_rect: state.get_device_rect(
    428                        &tile.local_rect,
    429                        tile.transform_index,
    430                    ),
    431                    z_id: tile.z_id.0,
    432                }
    433            })
    434            .collect();
    435 
    436        CompositorDebugInfo {
    437            enabled_z_layers: !0,
    438            tiles,
    439        }
    440    }
    441 }