Skip to main content

atmos/os_lib/web_engine/
frames.rs

1//! iframe の入れ子の文書(子フレーム)。
2//!
3//! 【2026-10-17】従来の iframe はプレビュー(OGP カード)を描くだけだった。iframe ごとに
4//! 独立した `WebEngine`(文書・JS・レイアウト)を持たせ、iframe の矩形を窓として
5//! `WebEngine::draw` を呼ぶ。読み込み・スクリプト・タイマーは子の `draw` が自分で進める
6//! (最上位のタブと同じ経路)。文書の要求には埋め込み元ページの `Referer` を付ける
7//! (`referrer::referrer_for`。YouTube の埋め込みはこれが無いとエラー 153 になる)。
8
9use alloc::boxed::Box;
10use alloc::collections::BTreeMap;
11use alloc::format;
12use alloc::string::String;
13
14use super::WebEngine;
15use crate::kernel::draw;
16
17/// 入れ子の深さの上限(iframe の中の iframe の中の…)。
18pub(crate) const MAX_FRAME_DEPTH: u32 = 2;
19/// 1 つの文書が持つ子フレーム数の上限(それぞれが JS とレイアウトを持つため)。
20pub(crate) const MAX_FRAMES_PER_DOC: usize = 4;
21/// これ以下の幅・高さの iframe は読み込まない(追跡用の 1px iframe 等)。
22pub(crate) const MIN_FRAME_SIZE: i32 = 8;
23
24/// 埋め込み元ページ(子フレームを持つ側)の情報。
25pub(crate) struct FrameParent<'a> {
26    pub is_https: bool,
27    pub host: &'a str,
28    pub path: &'a str,
29    pub depth: u32,
30}
31
32/// iframe の `src` を子フレームで読み込む絶対 URL にする。読み込まないもの
33/// (空・`about:`・`javascript:`・`data:` 等)は None。
34pub(crate) fn frame_url(parent: &FrameParent<'_>, src: &str) -> Option<String> {
35    let src = src.trim();
36    if src.is_empty() {
37        return None;
38    }
39    if src.starts_with("https://") || src.starts_with("http://") {
40        return Some(String::from(src));
41    }
42    let scheme = if parent.is_https { "https:" } else { "http:" };
43    if let Some(rest) = src.strip_prefix("//") {
44        return Some(format!("{}//{}", scheme, rest));
45    }
46    if src.contains(':') && !src.starts_with('/') {
47        // about:blank / javascript: / data: など(相対パス中の ':' は '/' より後ろにしか来ない想定)。
48        let before_slash = src.split('/').next().unwrap_or("");
49        if before_slash.contains(':') {
50            return None;
51        }
52    }
53    let base = format!("{}//{}{}", scheme, parent.host, parent.path);
54    Some(super::url_resolve::resolve_url(&base, src))
55}
56
57/// 子フレームのキー(文書内で何番目の iframe か+URL。src が変われば別の文書)。
58/// 要素 id は再レイアウトで変わり得る(自動採番)ので使わない。
59pub(crate) fn frame_key(ordinal: usize, url: &str) -> String {
60    format!("{}|{}", ordinal, url)
61}
62
63/// 子フレームを(無ければ作って読み込みを始め)iframe の矩形 `(x, y, w, h)` に描く。
64/// 描けなかった(上限・小さすぎる)ときは false(呼び出し側はプレビューを描く)。
65#[allow(clippy::too_many_arguments)]
66pub(crate) fn draw_child_frame(
67    frames: &mut BTreeMap<String, Box<WebEngine>>,
68    parent: &FrameParent<'_>,
69    key: &str,
70    url: &str,
71    screen: &draw::Screen,
72    x: i32,
73    y: i32,
74    w: i32,
75    h: i32,
76) -> bool {
77    if parent.depth >= MAX_FRAME_DEPTH || w < MIN_FRAME_SIZE || h < MIN_FRAME_SIZE {
78        return false;
79    }
80    if !frames.contains_key(key) {
81        if frames.len() >= MAX_FRAMES_PER_DOC {
82            return false;
83        }
84        let mut child = Box::new(WebEngine::new());
85        child.frame_depth = parent.depth + 1;
86        child.js_runtime.set_frame_depth(child.frame_depth);
87        child.win_w = w as u32;
88        child.win_h = h as u32;
89        child.top_offset = 0;
90        let (to_https, to_host) = split_scheme_host(url);
91        let referer = super::referrer::referrer_for(parent.is_https, parent.host, parent.path, to_https, to_host);
92        child.load_frame(url, referer);
93        frames.insert(String::from(key), child);
94    }
95    let Some(child) = frames.get_mut(key) else { return false };
96    // iframe の大きさが変わったら子の文書を組み直す(最上位タブのウィンドウ変更と同じ)。
97    if child.win_w != w as u32 || child.win_h != h as u32 {
98        child.win_w = w as u32;
99        child.win_h = h as u32;
100        let html = child.last_html.clone();
101        if !html.is_empty() {
102            child.parse_and_layout(&html, None, 0, w as u32, h as u32);
103        }
104    }
105    // 画面の外(左・上)へはみ出した iframe は今は描かない(窓の原点が負になれない)。
106    if x < 0 || y < 0 {
107        return true;
108    }
109    let (x, y, w, h) = (x as u32, y as u32, w as u32, h as u32);
110    screen.push_clip(x, y, x + w, y + h);
111    child.draw(screen, x, y, w, h, 0);
112    screen.pop_clip();
113    true
114}
115
116/// `https://host/...` → `(true, "host")`。
117fn split_scheme_host(url: &str) -> (bool, &str) {
118    let (https, rest) = if let Some(r) = url.strip_prefix("https://") {
119        (true, r)
120    } else {
121        (false, url.strip_prefix("http://").unwrap_or(url))
122    };
123    let end = rest.find(['/', '?', '#']).unwrap_or(rest.len());
124    (https, rest.get(..end).unwrap_or(rest))
125}