1#![allow(dead_code)]
12
13use crate::kernel::draw;
14use crate::os_lib::web_engine::{draw_border, url_encode, WebEngine};
15use alloc::string::String;
16use alloc::vec::Vec;
17
18const CONTROL_BAR_H: i32 = 40;
21
22struct TabSlot {
28 title: String,
29 engine: Option<WebEngine>,
30}
31
32static SVG_BACK: &[u8] = b"<svg width=\"20\" height=\"20\" viewBox=\"0 0 20 20\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M12.5 16.5L6 10L12.5 3.5\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></svg>";
33
34static SVG_FORWARD: &[u8] = b"<svg width=\"20\" height=\"20\" viewBox=\"0 0 20 20\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M7.5 3.5L14 10L7.5 16.5\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></svg>";
35
36static SVG_HOME: &[u8] = b"<svg width=\"20\" height=\"20\" viewBox=\"0 0 20 20\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M10 2.5L3.5 8V16.5C3.5 17 4 17.5 4.5 17.5H7.5V12.5H12.5V17.5H15.5C16 17.5 16.5 17 16.5 16.5V8L10 2.5Z\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linejoin=\"round\"/></svg>";
37
38static SVG_SHIELD_SECURE: &[u8] = b"<svg width=\"20\" height=\"20\" viewBox=\"0 0 20 20\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M10 2.5C13 3.5 16.5 3.5 16.5 3.5V8.5C16.5 13 13 16.5 10 17.5C7 16.5 3.5 13 3.5 8.5V3.5C3.5 3.5 7 3.5 10 2.5Z\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linejoin=\"round\"/><path d=\"M7 10L9 12L13 8\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></svg>";
39
40static SVG_SHIELD_WARN: &[u8] = b"<svg width=\"20\" height=\"20\" viewBox=\"0 0 20 20\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M10 2.5C13 3.5 16.5 3.5 16.5 3.5V8.5C16.5 13 13 16.5 10 17.5C7 16.5 3.5 13 3.5 8.5V3.5C3.5 3.5 7 3.5 10 2.5Z\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linejoin=\"round\"/><path d=\"M4.5 4.5L15.5 15.5\" stroke=\"currentColor\" stroke-width=\"2\"/></svg>";
41
42static SVG_RELOAD: &[u8] = b"<svg width=\"20\" height=\"20\" viewBox=\"0 0 20 20\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M17 10C17 13.866 13.866 17 10 17C6.13401 17 3 13.866 3 10C3 6.13401 6.13401 3 10 3C12.186 3 14.136 4.003 15.42 5.58M17 3V6H14\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></svg>";
43
44fn render_home(engine: &mut WebEngine, top: i32) {
46 engine.current_is_https = false;
47 engine.load_html(crate::os_lib::web_engine::HOME_HTML, "home", top);
48 engine.title = String::from("AtmOS Browser");
49}
50
51pub struct WebBrowser {
53 pub engine: WebEngine,
55 pub address_bar_focused: bool,
56 pub address_box: crate::kernel::text_box::TextBox,
57 pub active_tab_idx: usize,
58 tabs: Vec<TabSlot>,
59}
60
61static REDRAW_LOADING: core::sync::atomic::AtomicUsize = core::sync::atomic::AtomicUsize::new(0);
64static REDRAW_DIRTY: core::sync::atomic::AtomicUsize = core::sync::atomic::AtomicUsize::new(0);
65static REDRAW_VIDEO: core::sync::atomic::AtomicUsize = core::sync::atomic::AtomicUsize::new(0);
66static REDRAW_IMG: core::sync::atomic::AtomicUsize = core::sync::atomic::AtomicUsize::new(0);
67
68pub fn log_redraw_reasons() {
70}
71
72impl WebBrowser {
73 pub fn top_offset(&self) -> i32 {
76 CONTROL_BAR_H
77 }
78
79 fn sync_active_title(&mut self) {
81 let title = if self.engine.title.is_empty() {
82 self.engine.current_host.clone()
83 } else {
84 self.engine.title.clone()
85 };
86 if let Some(slot) = self.tabs.get_mut(self.active_tab_idx) {
87 slot.title = title;
88 }
89 }
90
91 fn activate(&mut self, idx: usize) {
94 if idx == self.active_tab_idx || idx >= self.tabs.len() {
95 return;
96 }
97 self.sync_active_title();
98 if let Some(mut next) = self.tabs[idx].engine.take() {
100 core::mem::swap(&mut self.engine, &mut next);
102 self.tabs[self.active_tab_idx].engine = Some(next);
104 self.active_tab_idx = idx;
105 }
106 self.engine.active = true;
107 self.engine.dirty = true;
108 self.engine.dirty_reason = "mod.rs:105";
109 self.address_box.set_text(&self.engine.get_url());
110 self.address_bar_focused = false;
111 }
112}
113
114impl Default for WebBrowser {
115 fn default() -> Self {
116 Self::new()
117 }
118}
119
120impl WebBrowser {
121 pub fn new() -> Self {
122 crate::os_lib::web_engine::clear_browser_cache();
124 let mut engine = WebEngine::new();
125 engine.win_w = 800;
126 engine.win_h = 600;
127 render_home(&mut engine, CONTROL_BAR_H);
129 let initial_url = engine.get_url();
130 let initial_slot = TabSlot {
132 title: String::from("AtmOS Browser"),
133 engine: None,
134 };
135 Self {
136 engine,
137 address_bar_focused: false,
138 address_box: crate::kernel::text_box::TextBox::with_text(&initial_url),
139 active_tab_idx: 0,
140 tabs: alloc::vec![initial_slot],
141 }
142 }
143}
144
145static mut GLOBAL_BROWSER: Option<WebBrowser> = None;
147
148pub fn init() {
150 unsafe {
151 GLOBAL_BROWSER = Some(WebBrowser::new());
152 }
153}
154
155pub fn get_instance() -> &'static mut WebBrowser {
156 unsafe { GLOBAL_BROWSER.get_or_insert_with(WebBrowser::new) }
158}
159
160pub fn is_active() -> bool {
161 unsafe {
162 match &GLOBAL_BROWSER {
163 Some(b) => b.engine.active,
164 None => false,
165 }
166 }
167}
168
169pub fn hovered_cursor_is_text() -> bool {
171 unsafe {
172 match &GLOBAL_BROWSER {
173 Some(b) => b.engine.active && b.engine.hovered_cursor_is_text,
174 None => false,
175 }
176 }
177}
178
179pub fn needs_render() -> bool {
180 unsafe {
181 match &GLOBAL_BROWSER {
182 Some(b) => {
183 let r = b.engine.active && (b.engine.loading || b.engine.dirty);
188 if r {
189 if b.engine.loading {
190 RENDER_LOADING.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
191 }
192 if b.engine.dirty {
193 RENDER_DIRTY.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
194 }
195 }
196 r
197 }
198 None => false,
199 }
200 }
201}
202
203static RENDER_LOADING: core::sync::atomic::AtomicUsize = core::sync::atomic::AtomicUsize::new(0);
205static RENDER_DIRTY: core::sync::atomic::AtomicUsize = core::sync::atomic::AtomicUsize::new(0);
206
207impl crate::kernel::window_mgr::App for WebBrowser {
209 fn name(&self) -> &str {
210 "browser"
211 }
212
213 fn instance_info(&self) -> alloc::string::String {
214 self.engine.title.clone()
215 }
216
217 fn on_resize(&mut self, width: u32, height: u32) {
218 let top = self.top_offset();
219 self.engine.win_w = width;
220 self.engine.win_h = height;
221 let html = self.engine.last_html.clone();
222 self.engine.parse_and_layout(&html, None, top, width, height);
223 self.engine.js_runtime.fire_resize(width as i32, height as i32);
224 self.engine.dirty = true;
225 self.engine.dirty_reason = "mod.rs:202";
226 }
227
228 fn needs_timer_redraw(&self) -> bool {
229 let loading = self.engine.loading;
235 let dirty = self.engine.dirty;
236 let video = self.engine.video_playing;
237 let img = crate::os_lib::web_engine::image_results_ready();
238 let canvas = crate::os_lib::canvas2d::canvas_dirty();
241 let font_wait = crate::os_lib::web_engine::font_pending() > 0;
255 if loading {
256 REDRAW_LOADING.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
257 }
258 if dirty {
259 REDRAW_DIRTY.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
260 }
262 if video {
263 REDRAW_VIDEO.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
264 }
265 if img {
266 REDRAW_IMG.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
267 }
268 let showing_placeholder = loading && self.engine.elements.is_empty();
282 showing_placeholder || dirty || video || img || canvas || font_wait
283 }
284
285 fn draw(
287 &mut self,
288 screen: &crate::kernel::draw::Screen,
289 win_x: u32,
290 win_y: u32,
291 win_w: u32,
292 win_h: u32,
293 ) {
294 crate::os_lib::web_engine::set_draw_stage(1);
295 let _ = crate::os_lib::canvas2d::take_canvas_dirty();
298 let top = self.top_offset();
299 if self.engine.win_w != win_w || self.engine.win_h != win_h || self.engine.top_offset != top
301 {
302 let size_changed = self.engine.win_w != win_w || self.engine.win_h != win_h;
304 self.engine.win_w = win_w;
305 self.engine.win_h = win_h;
306 let html = self.engine.last_html.clone();
307 self.engine.parse_and_layout(&html, None, top, win_w, win_h);
308 if size_changed {
310 self.engine
311 .js_runtime
312 .fire_resize(win_w as i32, win_h as i32);
313 }
314 }
315 let theme = crate::kernel::config::get_config().theme;
316 screen.boxfill(
317 win_x,
318 win_y,
319 win_x + win_w,
320 win_y + win_h,
321 crate::kernel::draw::Color(theme.app_bg),
322 );
323
324 self.engine.draw(screen, win_x, win_y, win_w, win_h, top);
328
329 screen.boxfill(
331 win_x,
332 win_y,
333 win_x + win_w,
334 win_y + CONTROL_BAR_H as u32,
335 crate::kernel::draw::Color(theme.tab_area_bg),
336 );
337
338 let can_back = self.engine.can_go_back();
340 let back_fg = if can_back { theme.button_fg } else { theme.non_active_window_border };
341 screen.boxfill(
342 win_x + 10,
343 win_y + 8,
344 win_x + 42,
345 win_y + 32,
346 draw::Color(theme.button_bg),
347 );
348 draw_border(
349 screen,
350 (win_x + 10) as i32,
351 (win_y + 8) as i32,
352 (win_x + 42) as i32,
353 (win_y + 32) as i32,
354 theme.non_active_window_border,
355 );
356 screen.draw_svg_icon(win_x + 16, win_y + 10, SVG_BACK, 20, draw::Color(back_fg));
357
358 let can_fwd = self.engine.can_go_forward();
360 let fwd_fg = if can_fwd { theme.button_fg } else { theme.non_active_window_border };
361 screen.boxfill(
362 win_x + 48,
363 win_y + 8,
364 win_x + 80,
365 win_y + 32,
366 draw::Color(theme.button_bg),
367 );
368 draw_border(
369 screen,
370 (win_x + 48) as i32,
371 (win_y + 8) as i32,
372 (win_x + 80) as i32,
373 (win_y + 32) as i32,
374 theme.non_active_window_border,
375 );
376 screen.draw_svg_icon(win_x + 54, win_y + 10, SVG_FORWARD, 20, draw::Color(fwd_fg));
377
378 screen.boxfill(
380 win_x + 86,
381 win_y + 8,
382 win_x + 118,
383 win_y + 32,
384 draw::Color(theme.button_bg),
385 );
386 draw_border(
387 screen,
388 (win_x + 86) as i32,
389 (win_y + 8) as i32,
390 (win_x + 118) as i32,
391 (win_y + 32) as i32,
392 theme.non_active_window_border,
393 );
394 screen.draw_svg_icon(
395 win_x + 92,
396 win_y + 10,
397 SVG_HOME,
398 20,
399 draw::Color(theme.button_fg),
400 );
401
402 screen.boxfill(
404 win_x + 124,
405 win_y + 8,
406 win_x + 156,
407 win_y + 32,
408 draw::Color(theme.button_bg),
409 );
410 draw_border(
411 screen,
412 (win_x + 124) as i32,
413 (win_y + 8) as i32,
414 (win_x + 156) as i32,
415 (win_y + 32) as i32,
416 theme.non_active_window_border,
417 );
418 screen.draw_svg_icon(
419 win_x + 130,
420 win_y + 10,
421 SVG_RELOAD,
422 20,
423 draw::Color(theme.button_fg),
424 );
425
426 let is_https = self.engine.current_is_https;
428 let badge_w = if is_https { 95 } else { 125 };
429 let badge_bg = if is_https { 0xFF1B4D3E } else { 0xFF4C1D1D };
430 let badge_border = if is_https { 0xFF50FA7B } else { 0xFFFF5555 };
431 let badge_lbl = if is_https { "Secure" } else { "Not Secure" };
432 let badge_fg = if is_https { 0xFFECFDF5 } else { 0xFFFEE2E2 };
433 let badge_svg = if is_https {
434 SVG_SHIELD_SECURE
435 } else {
436 SVG_SHIELD_WARN
437 };
438
439 screen.boxfill(
440 win_x + 162,
441 win_y + 8,
442 win_x + 162 + badge_w,
443 win_y + 32,
444 draw::Color(badge_bg),
445 );
446 draw_border(
447 screen,
448 (win_x + 162) as i32,
449 (win_y + 8) as i32,
450 (win_x + 162 + badge_w) as i32,
451 (win_y + 32) as i32,
452 badge_border,
453 );
454 screen.draw_svg_icon(
455 win_x + 164,
456 win_y + 10,
457 badge_svg,
458 20,
459 draw::Color(badge_border),
460 );
461 screen.draw_string_vector(
462 win_x + 189,
463 win_y + 13,
464 badge_lbl,
465 draw::Color(badge_fg),
466 11,
467 );
468
469 let badge_end = 162 + badge_w;
470
471 let bar_right = (win_x + win_w).saturating_sub(10);
473 let bar_left = win_x + badge_end + 6;
474 if bar_right > bar_left {
475 let addr_bg = if self.address_bar_focused {
476 theme.terminal_bg
477 } else {
478 theme.tab_area_bg
479 };
480 let addr_border = if self.address_bar_focused {
481 theme.focus_highlight
482 } else {
483 theme.non_active_window_border
484 };
485 screen.boxfill(
486 bar_left,
487 win_y + 8,
488 bar_right,
489 win_y + 32,
490 draw::Color(addr_bg),
491 );
492 draw_border(
493 screen,
494 bar_left as i32,
495 (win_y + 8) as i32,
496 bar_right as i32,
497 (win_y + 32) as i32,
498 addr_border,
499 );
500
501 let is_placeholder = self.address_box.text.is_empty() && !self.address_bar_focused;
502 let display_text = if is_placeholder {
503 String::from("Search or enter address...")
504 } else {
505 self.address_box.text.clone()
506 };
507 let text_color = if is_placeholder {
508 theme.non_active_window_border
509 } else {
510 theme.terminal_fg
511 };
512 screen.draw_string_vector(
513 bar_left + 10,
514 win_y + 12,
515 &display_text,
516 draw::Color(text_color),
517 12,
518 );
519
520 if self.address_bar_focused {
522 let prefix: String = self
523 .address_box
524 .text
525 .chars()
526 .take(self.address_box.cursor_pos)
527 .collect();
528 let pw = crate::kernel::vector_font::get_vector_string_width(&prefix, 12);
529 let cursor_x = bar_left + 10 + pw;
530 if cursor_x < bar_right {
531 screen.boxfill(
532 cursor_x,
533 win_y + 11,
534 cursor_x + 1,
535 win_y + 28,
536 draw::Color(crate::kernel::ime::cursor_color()),
537 );
538 }
539 }
540 }
541
542 screen.boxfill(
543 win_x,
544 win_y + 39,
545 win_x + win_w,
546 win_y + 40,
547 crate::kernel::draw::Color(theme.tab_area_border),
548 );
549
550 if !self.address_bar_focused && self.engine.dirty {
552 self.address_box.set_text(&self.engine.get_url());
553 }
554 if !self.engine.title.is_empty() {
555 if let Some(slot) = self.tabs.get_mut(self.active_tab_idx) {
556 slot.title = self.engine.title.clone();
557 }
558 }
559 crate::os_lib::web_engine::set_draw_stage(0);
560 }
561
562 fn on_mouse(
563 &mut self,
564 local_x: i32,
565 local_y: i32,
566 btn_left: bool,
567 btn_right: bool,
568 wheel: i32,
569 ) {
570 let top = self.top_offset();
571
572 if btn_left && (0..CONTROL_BAR_H).contains(&local_y) {
573 if (8..=32).contains(&local_y) {
574 if (10..=42).contains(&local_x) {
575 self.engine.go_back(top);
576 self.address_bar_focused = false;
577 self.address_box.set_text(&self.engine.get_url());
578 self.engine.dirty = true;
579 self.engine.dirty_reason = "mod.rs:532";
580 return;
581 }
582 if (48..=80).contains(&local_x) {
583 self.engine.go_forward(top);
584 self.address_bar_focused = false;
585 self.address_box.set_text(&self.engine.get_url());
586 self.engine.dirty = true;
587 self.engine.dirty_reason = "mod.rs:539";
588 return;
589 }
590 if (86..=118).contains(&local_x) {
591 render_home(&mut self.engine, top);
593 self.address_bar_focused = false;
594 self.address_box.set_text(&self.engine.get_url());
595 self.engine.dirty = true;
596 self.engine.dirty_reason = "mod.rs:547";
597 return;
598 }
599 if (124..=156).contains(&local_x) {
600 if self.engine.current_host == "localhost" {
602 let path = self.engine.current_path.clone();
603 if path == "/home" {
604 render_home(&mut self.engine, top);
606 } else {
607 let loaded = {
611 let _fs_guard = crate::kernel::fs::FS_LOCK.lock();
612 crate::kernel::fs::get_fs().read_file(&path)
613 };
614 if let Some((_meta, content)) = loaded {
615 if let Ok(html) = core::str::from_utf8(&content) {
616 self.engine.load_html(
617 html,
618 path.strip_prefix('/').unwrap_or(&path),
619 top,
620 );
621 }
622 }
623 }
624 } else {
625 self.engine.load_page(
626 self.engine.current_is_https,
627 &self.engine.current_host.clone(),
628 &self.engine.current_path.clone(),
629 false,
630 top,
631 );
632 }
633 self.address_bar_focused = false;
634 self.address_box.set_text(&self.engine.get_url());
635 return;
636 }
637 let badge_end = 162
639 + if self.engine.current_is_https {
640 95
641 } else {
642 125
643 };
644 let bar_left = badge_end + 6;
645 if local_x >= bar_left {
646 self.address_bar_focused = true;
647 self.address_box.set_text(&self.engine.get_url());
648 self.engine.dirty = true;
649 self.engine.dirty_reason = "mod.rs:593";
650 return;
651 }
652 }
653 self.address_bar_focused = false;
654 self.engine.dirty = true;
655 self.engine.dirty_reason = "mod.rs:598";
656 return;
657 }
658
659 if btn_left {
660 self.address_bar_focused = false;
661 self.engine.dirty = true;
662 self.engine.dirty_reason = "mod.rs:604";
663 }
664
665 self.engine
666 .on_mouse(local_x, local_y, btn_left, btn_right, wheel, top);
667 }
668
669 fn on_key(&mut self, keycode: u8, pressed: bool, ascii: Option<char>) {
670 if !pressed {
671 return;
672 }
673
674 if self.address_bar_focused {
675 if keycode == 0x1C {
676 let trimmed = self.address_box.text.trim();
678 if !trimmed.is_empty() {
679 let input = String::from(trimmed);
680 let top = self.top_offset();
681 if let Some(rest) = input.strip_prefix('/') {
682 let loaded = {
685 let _fs_guard = crate::kernel::fs::FS_LOCK.lock();
686 crate::kernel::fs::get_fs().read_file(&input)
687 };
688 if let Some((_meta, content)) = loaded {
689 if let Ok(html) = core::str::from_utf8(&content) {
690 self.engine.load_html(html, rest, top);
691 }
692 }
693 } else if input.starts_with("http://") || input.starts_with("https://") {
694 let is_https = input.starts_with("https://");
696 let clean = if is_https {
697 input.get(8..).unwrap_or("")
698 } else {
699 input.get(7..).unwrap_or("")
700 };
701 let parts: Vec<&str> = clean.splitn(2, '/').collect();
703 let host = parts[0];
704 let path = if parts.len() > 1 {
705 alloc::format!("/{}", parts[1])
706 } else {
707 String::from("/")
708 };
709 self.engine.load_page(is_https, host, &path, true, top);
710 } else if input.contains('.') && !input.contains(' ') {
711 let parts: Vec<&str> = input.splitn(2, '/').collect();
714 let host = parts[0];
715 let path = if parts.len() > 1 {
716 alloc::format!("/{}", parts[1])
717 } else {
718 String::from("/")
719 };
720 let is_https = !host.starts_with("localhost");
721 self.engine.load_page(is_https, host, &path, true, top);
722 } else {
723 let query = url_encode(&input);
724 let path = alloc::format!("/lite/?q={}", query);
725 self.engine
726 .load_page(true, "lite.duckduckgo.com", &path, true, top);
727 }
728 }
729 self.address_bar_focused = false;
730 self.address_box.set_text(&self.engine.get_url());
731 self.engine.dirty = true;
732 self.engine.dirty_reason = "mod.rs:669";
733 } else {
734 if self.address_box.handle_key(keycode, ascii) {
735 self.engine.dirty = true;
736 self.engine.dirty_reason = "mod.rs:672";
737 }
738 }
739 return;
740 }
741
742 self.engine.on_key(keycode, pressed, ascii);
743
744 if keycode == 0x0E && self.engine.focused_id.is_none() {
745 let top = self.top_offset();
746 self.engine.go_back(top);
747 self.address_box.set_text(&self.engine.get_url());
748 }
749
750 if self.engine.focused_id.is_none() && !self.address_bar_focused {
760 let page = (self.engine.win_h as i32 - self.top_offset()).max(80);
762 let dy = match keycode {
763 0x48 => Some(-60), 0x50 => Some(60), 0x49 => Some(-page), 0x51 => Some(page), 0x47 => Some(-self.engine.max_scroll_y), 0x4F => Some(self.engine.max_scroll_y), _ => None,
770 };
771 if let Some(dy) = dy {
772 self.engine.scroll(dy);
773 self.engine.dirty = true;
774 self.engine.dirty_reason = "browser/mod.rs:key_scroll";
775 }
776 }
777 }
778
779 fn tabs(&self) -> alloc::vec::Vec<alloc::string::String> {
780 self.tabs
782 .iter()
783 .enumerate()
784 .map(|(i, t)| {
785 if i == self.active_tab_idx {
786 if self.engine.title.is_empty() {
787 self.engine.current_host.clone()
788 } else {
789 self.engine.title.clone()
790 }
791 } else {
792 t.title.clone()
793 }
794 })
795 .collect()
796 }
797
798 fn active_tab(&self) -> usize {
799 self.active_tab_idx
800 }
801
802 fn close_tab(&mut self, idx: usize) -> bool {
803 if idx >= self.tabs.len() {
804 return false;
805 }
806
807 let was_active = idx == self.active_tab_idx;
808 self.tabs.remove(idx);
809
810 if self.tabs.is_empty() {
811 self.engine.active = false;
812 crate::os_lib::web_engine::clear_browser_cache();
813 return true;
814 }
815
816 if was_active {
817 let new_idx = idx.min(self.tabs.len() - 1);
819 if let Some(mut next) = self.tabs[new_idx].engine.take() {
820 core::mem::swap(&mut self.engine, &mut next);
821 }
823 self.active_tab_idx = new_idx;
824 self.engine.active = true;
825 self.engine.dirty = true;
826 self.engine.dirty_reason = "mod.rs:733";
827 self.address_box.set_text(&self.engine.get_url());
828 self.address_bar_focused = false;
829 } else if idx < self.active_tab_idx {
830 self.active_tab_idx -= 1;
832 }
833 false
834 }
835
836 fn switch_tab(&mut self, idx: usize) {
837 self.activate(idx);
838 }
839
840 fn add_tab(&mut self) {
841 if self.tabs.len() >= 16 {
842 return;
843 }
844 self.sync_active_title();
846 let old_active = self.active_tab_idx;
847 let w = self.engine.win_w;
848 let h = self.engine.win_h;
849
850 let mut new_engine = WebEngine::new();
852 new_engine.win_w = w;
853 new_engine.win_h = h;
854 new_engine.current_host = String::from("newtab");
855 new_engine.current_path = String::from("/");
856 new_engine.title = String::from("New Tab");
857 new_engine.active = true;
858 let top = self.top_offset();
859 new_engine.parse_and_layout(
860 "<h1>New Tab</h1><p>Enter URL in the address bar.</p>",
861 None,
862 top,
863 w,
864 h,
865 );
866
867 let prev = core::mem::replace(&mut self.engine, new_engine);
869 self.tabs[old_active].engine = Some(prev);
870
871 self.tabs.push(TabSlot {
872 title: String::from("New Tab"),
873 engine: None,
874 });
875 self.active_tab_idx = self.tabs.len() - 1;
876
877 self.address_box.set_text("");
878 self.address_bar_focused = true;
879 self.engine.dirty = true;
880 self.engine.dirty_reason = "mod.rs:786";
881 }
882
883 fn get_dom_value(&self, id: &str) -> Option<alloc::string::String> {
884 self.engine.form_values.get(id).cloned()
885 }
886
887 fn set_dom_value(&mut self, id: &str, val: &str) {
888 self.engine.form_values.insert(
889 alloc::string::String::from(id),
890 alloc::string::String::from(val),
891 );
892 self.engine.dirty = true;
893 self.engine.dirty_reason = "mod.rs:798";
894 }
895
896 fn get_dom_content(&self, id: &str) -> Option<alloc::string::String> {
897 self.engine.dom_contents.get(id).cloned()
898 }
899
900 fn set_dom_content(&mut self, id: &str, val: &str) {
901 self.engine.set_dom_content(id, val);
902 }
903}