1use std::cell::{Cell, RefCell};
10use std::collections::HashMap;
11use std::net::TcpStream;
12
13use base::id::PipelineId;
14use devtools_traits::DevtoolScriptControlMsg::{
15 self, GetCssDatabase, SimulateColorScheme, WantsLiveNotifications,
16};
17use devtools_traits::{DevtoolsPageInfo, NavigationState};
18use embedder_traits::Theme;
19use ipc_channel::ipc::{self, IpcSender};
20use serde::Serialize;
21use serde_json::{Map, Value};
22
23use crate::actor::{Actor, ActorError, ActorRegistry};
24use crate::actors::inspector::InspectorActor;
25use crate::actors::inspector::accessibility::AccessibilityActor;
26use crate::actors::inspector::css_properties::CssPropertiesActor;
27use crate::actors::reflow::ReflowActor;
28use crate::actors::stylesheets::StyleSheetsActor;
29use crate::actors::tab::TabDescriptorActor;
30use crate::actors::thread::ThreadActor;
31use crate::actors::watcher::{SessionContext, SessionContextType, WatcherActor};
32use crate::id::{DevtoolsBrowserId, DevtoolsBrowsingContextId, DevtoolsOuterWindowId, IdMap};
33use crate::protocol::{ClientRequest, JsonPacketStream};
34use crate::resource::ResourceAvailable;
35use crate::{EmptyReplyMsg, StreamId};
36
37#[derive(Serialize)]
38struct ListWorkersReply {
39 from: String,
40 workers: Vec<()>,
41}
42
43#[derive(Serialize)]
44struct FrameUpdateReply {
45 from: String,
46 #[serde(rename = "type")]
47 type_: String,
48 frames: Vec<FrameUpdateMsg>,
49}
50
51#[derive(Serialize)]
52#[serde(rename_all = "camelCase")]
53struct FrameUpdateMsg {
54 id: u32,
55 is_top_level: bool,
56 url: String,
57 title: String,
58}
59
60#[derive(Serialize)]
61struct TabNavigated {
62 from: String,
63 #[serde(rename = "type")]
64 type_: String,
65 url: String,
66 title: Option<String>,
67 #[serde(rename = "nativeConsoleAPI")]
68 native_console_api: bool,
69 state: String,
70 is_frame_switching: bool,
71}
72
73#[derive(Serialize)]
74#[serde(rename_all = "camelCase")]
75struct BrowsingContextTraits {
76 frames: bool,
77 is_browsing_context: bool,
78 log_in_page: bool,
79 navigation: bool,
80 supports_top_level_target_flag: bool,
81 watchpoints: bool,
82}
83
84#[derive(Serialize)]
85#[serde(rename_all = "lowercase")]
86enum TargetType {
87 Frame,
88 }
90
91#[derive(Serialize)]
92#[serde(rename_all = "camelCase")]
93pub struct BrowsingContextActorMsg {
94 actor: String,
95 title: String,
96 url: String,
97 #[serde(rename = "browserId")]
99 browser_id: u32,
100 #[serde(rename = "outerWindowID")]
101 outer_window_id: u32,
102 #[serde(rename = "browsingContextID")]
103 browsing_context_id: u32,
104 is_top_level_target: bool,
105 traits: BrowsingContextTraits,
106 accessibility_actor: String,
108 console_actor: String,
109 css_properties_actor: String,
110 inspector_actor: String,
111 reflow_actor: String,
112 style_sheets_actor: String,
113 thread_actor: String,
114 target_type: TargetType,
115 }
130
131pub(crate) struct BrowsingContextActor {
135 pub name: String,
136 pub title: RefCell<String>,
137 pub url: RefCell<String>,
138 pub browser_id: DevtoolsBrowserId,
140 pub active_pipeline_id: Cell<PipelineId>,
141 pub active_outer_window_id: Cell<DevtoolsOuterWindowId>,
142 pub browsing_context_id: DevtoolsBrowsingContextId,
143 pub accessibility: String,
144 pub console: String,
145 pub css_properties: String,
146 pub inspector: String,
147 pub reflow: String,
148 pub style_sheets: String,
149 pub thread: String,
150 pub _tab: String,
151 pub script_chan: IpcSender<DevtoolScriptControlMsg>,
152 pub streams: RefCell<HashMap<StreamId, TcpStream>>,
153 pub watcher: String,
154}
155
156impl ResourceAvailable for BrowsingContextActor {
157 fn actor_name(&self) -> String {
158 self.name.clone()
159 }
160}
161
162impl Actor for BrowsingContextActor {
163 fn name(&self) -> String {
164 self.name.clone()
165 }
166
167 fn handle_message(
168 &self,
169 request: ClientRequest,
170 _registry: &ActorRegistry,
171 msg_type: &str,
172 _msg: &Map<String, Value>,
173 _id: StreamId,
174 ) -> Result<(), ActorError> {
175 match msg_type {
176 "listFrames" => {
177 let msg = EmptyReplyMsg { from: self.name() };
179 request.reply_final(&msg)?
180 },
181 "listWorkers" => {
182 request.reply_final(&ListWorkersReply {
183 from: self.name(),
184 workers: vec![],
186 })?
187 },
188 _ => return Err(ActorError::UnrecognizedPacketType),
189 };
190 Ok(())
191 }
192
193 fn cleanup(&self, id: StreamId) {
194 self.streams.borrow_mut().remove(&id);
195 if self.streams.borrow().is_empty() {
196 self.script_chan
197 .send(WantsLiveNotifications(self.active_pipeline_id.get(), false))
198 .unwrap();
199 }
200 }
201}
202
203impl BrowsingContextActor {
204 #[allow(clippy::too_many_arguments)]
205 pub(crate) fn new(
206 console: String,
207 browser_id: DevtoolsBrowserId,
208 browsing_context_id: DevtoolsBrowsingContextId,
209 page_info: DevtoolsPageInfo,
210 pipeline_id: PipelineId,
211 outer_window_id: DevtoolsOuterWindowId,
212 script_sender: IpcSender<DevtoolScriptControlMsg>,
213 actors: &mut ActorRegistry,
214 ) -> BrowsingContextActor {
215 let name = actors.new_name("target");
216 let DevtoolsPageInfo {
217 title,
218 url,
219 is_top_level_global,
220 } = page_info;
221
222 let accessibility = AccessibilityActor::new(actors.new_name("accessibility"));
223
224 let properties = (|| {
225 let (properties_sender, properties_receiver) = ipc::channel().ok()?;
226 script_sender.send(GetCssDatabase(properties_sender)).ok()?;
227 properties_receiver.recv().ok()
228 })()
229 .unwrap_or_default();
230 let css_properties = CssPropertiesActor::new(actors.new_name("css-properties"), properties);
231
232 let inspector = InspectorActor {
233 name: actors.new_name("inspector"),
234 walker: RefCell::new(None),
235 page_style: RefCell::new(None),
236 highlighter: RefCell::new(None),
237 script_chan: script_sender.clone(),
238 browsing_context: name.clone(),
239 };
240
241 let reflow = ReflowActor::new(actors.new_name("reflow"));
242
243 let style_sheets = StyleSheetsActor::new(actors.new_name("stylesheets"));
244
245 let tabdesc = TabDescriptorActor::new(actors, name.clone(), is_top_level_global);
246
247 let thread = ThreadActor::new(actors.new_name("thread"));
248
249 let watcher = WatcherActor::new(
250 actors,
251 name.clone(),
252 SessionContext::new(SessionContextType::BrowserElement),
253 );
254
255 let target = BrowsingContextActor {
256 name,
257 script_chan: script_sender,
258 title: RefCell::new(title),
259 url: RefCell::new(url.into_string()),
260 active_pipeline_id: Cell::new(pipeline_id),
261 active_outer_window_id: Cell::new(outer_window_id),
262 browser_id,
263 browsing_context_id,
264 accessibility: accessibility.name(),
265 console,
266 css_properties: css_properties.name(),
267 inspector: inspector.name(),
268 reflow: reflow.name(),
269 streams: RefCell::new(HashMap::new()),
270 style_sheets: style_sheets.name(),
271 _tab: tabdesc.name(),
272 thread: thread.name(),
273 watcher: watcher.name(),
274 };
275
276 actors.register(Box::new(accessibility));
277 actors.register(Box::new(css_properties));
278 actors.register(Box::new(inspector));
279 actors.register(Box::new(reflow));
280 actors.register(Box::new(style_sheets));
281 actors.register(Box::new(tabdesc));
282 actors.register(Box::new(thread));
283 actors.register(Box::new(watcher));
284
285 target
286 }
287
288 pub fn encodable(&self) -> BrowsingContextActorMsg {
289 BrowsingContextActorMsg {
290 actor: self.name(),
291 traits: BrowsingContextTraits {
292 is_browsing_context: true,
293 frames: true,
294 log_in_page: false,
295 navigation: true,
296 supports_top_level_target_flag: true,
297 watchpoints: true,
298 },
299 title: self.title.borrow().clone(),
300 url: self.url.borrow().clone(),
301 browser_id: self.browser_id.value(),
302 browsing_context_id: self.browsing_context_id.value(),
303 outer_window_id: self.active_outer_window_id.get().value(),
304 is_top_level_target: true,
305 accessibility_actor: self.accessibility.clone(),
306 console_actor: self.console.clone(),
307 css_properties_actor: self.css_properties.clone(),
308 inspector_actor: self.inspector.clone(),
309 reflow_actor: self.reflow.clone(),
310 style_sheets_actor: self.style_sheets.clone(),
311 thread_actor: self.thread.clone(),
312 target_type: TargetType::Frame,
313 }
314 }
315
316 pub(crate) fn navigate(&self, state: NavigationState, id_map: &mut IdMap) {
317 let (pipeline_id, title, url, state) = match state {
318 NavigationState::Start(url) => (None, None, url, "start"),
319 NavigationState::Stop(pipeline, info) => {
320 (Some(pipeline), Some(info.title), info.url, "stop")
321 },
322 };
323 if let Some(pipeline_id) = pipeline_id {
324 let outer_window_id = id_map.outer_window_id(pipeline_id);
325 self.active_outer_window_id.set(outer_window_id);
326 self.active_pipeline_id.set(pipeline_id);
327 }
328 url.as_str().clone_into(&mut self.url.borrow_mut());
329 if let Some(ref t) = title {
330 self.title.borrow_mut().clone_from(t);
331 }
332
333 let msg = TabNavigated {
334 from: self.name(),
335 type_: "tabNavigated".to_owned(),
336 url: url.as_str().to_owned(),
337 title,
338 native_console_api: true,
339 state: state.to_owned(),
340 is_frame_switching: false,
341 };
342
343 for stream in self.streams.borrow_mut().values_mut() {
344 let _ = stream.write_json_packet(&msg);
345 }
346 }
347
348 pub(crate) fn title_changed(&self, pipeline_id: PipelineId, title: String) {
349 if pipeline_id != self.active_pipeline_id.get() {
350 return;
351 }
352 *self.title.borrow_mut() = title;
353 }
354
355 pub(crate) fn frame_update(&self, request: &mut ClientRequest) {
356 let _ = request.write_json_packet(&FrameUpdateReply {
357 from: self.name(),
358 type_: "frameUpdate".into(),
359 frames: vec![FrameUpdateMsg {
360 id: self.browsing_context_id.value(),
361 is_top_level: true,
362 title: self.title.borrow().clone(),
363 url: self.url.borrow().clone(),
364 }],
365 });
366 }
367
368 pub fn simulate_color_scheme(&self, theme: Theme) -> Result<(), ()> {
369 self.script_chan
370 .send(SimulateColorScheme(self.active_pipeline_id.get(), theme))
371 .map_err(|_| ())
372 }
373}