///|
/// HTML Parser Integration with Scheduler
/// HTMLパーサとスケジューラの統合
// =============================================================================
// Resource Discovery
// =============================================================================
///|
/// HTMLパース結果から発見されたリソース
pub(all) struct DiscoveredResource {
url : String
resource_type : ResourceType
/// parser-blocking かどうか (同期スクリプトなど)
blocking : Bool
/// defer属性があるか
is_defer : Bool
/// async属性があるか
is_async : Bool
}
///|
/// リソース発見結果
pub(all) struct ResourceDiscoveryResult {
/// 発見されたリソース
resources : Array[DiscoveredResource]
/// インラインスタイル
inline_styles : Array[String]
/// インラインスクリプト
inline_scripts : Array[InlineScript]
}
///|
/// インラインスクリプト情報
pub(all) struct InlineScript {
source : String
/// parser-blocking かどうか
blocking : Bool
}
///|
pub fn ResourceDiscoveryResult::new() -> ResourceDiscoveryResult {
{ resources: [], inline_styles: [], inline_scripts: [] }
}
// =============================================================================
// Element Traversal and Resource Discovery
// =============================================================================
///|
/// Elementからリソースを発見
pub fn discover_resources(element : @html.Element) -> ResourceDiscoveryResult {
let result = ResourceDiscoveryResult::new()
discover_resources_recursive(element, result)
result
}
///|
fn discover_resources_recursive(
element : @html.Element,
result : ResourceDiscoveryResult,
) -> Unit {
let tag = element.tag.to_lower()
// img要素
if tag == "img" {
match element.attributes.get("src") {
Some(src) =>
if !src.is_empty() {
result.resources.push({
url: src,
resource_type: Image,
blocking: false,
is_defer: false,
is_async: false,
})
}
None => ()
}
}
// link要素 (stylesheet)
if tag == "link" {
let rel = element.attributes.get("rel").unwrap_or("").to_lower()
if rel == "stylesheet" {
match element.attributes.get("href") {
Some(href) =>
if !href.is_empty() {
result.resources.push({
url: href,
resource_type: Stylesheet,
// スタイルシートはレンダリングブロッキング
blocking: true,
is_defer: false,
is_async: false,
})
}
None => ()
}
}
}
// script要素
if tag == "script" {
let has_src = element.attributes.get("src")
let has_async = element.attributes.contains("async")
let has_defer = element.attributes.contains("defer")
let type_attr = element.attributes.get("type").unwrap_or("")
// type="module" は defer と同様の動作
let is_module = type_attr == "module"
match has_src {
Some(src) =>
if !src.is_empty() {
// 外部スクリプト
result.resources.push({
url: src,
resource_type: Script,
// async/defer/module でなければ parser-blocking
blocking: !has_async && !has_defer && !is_module,
is_defer: has_defer || is_module,
is_async: has_async,
})
}
None => {
// インラインスクリプト
let script_content = extract_text_content(element)
if !script_content.is_empty() {
result.inline_scripts.push({
source: script_content,
// インラインスクリプトは常に parser-blocking (async/defer は外部のみ)
blocking: !has_async && !is_module,
})
}
}
}
}
// style要素 (インラインスタイル)
if tag == "style" {
let style_content = extract_text_content(element)
if !style_content.is_empty() {
result.inline_styles.push(style_content)
}
}
// 子要素を再帰的に処理
for child in element.children {
match child {
@html.Element(child_elem) =>
discover_resources_recursive(child_elem, result)
@html.Text(_) => ()
}
}
}
///|
/// 要素からテキストコンテンツを抽出
fn extract_text_content(element : @html.Element) -> String {
let sb = StringBuilder::new()
for child in element.children {
match child {
@html.Text(text) => sb.write_string(text)
@html.Element(_) => ()
}
}
sb.to_string()
}
// =============================================================================
// Document Parser (Scheduler Integration)
// =============================================================================
///|
/// ドキュメントパーサ - スケジューラと連携してHTMLをパース
pub struct DocumentParser {
scheduler : Scheduler
/// パース済みのルート要素
mut parsed_root : @html.Element?
/// 発見されたリソース
mut discovered : ResourceDiscoveryResult?
/// ドキュメントのbase URL
base_url : String
}
///|
pub fn DocumentParser::new(
scheduler : Scheduler,
base_url? : String = "",
) -> DocumentParser {
{ scheduler, parsed_root: None, discovered: None, base_url }
}
///|
/// HTMLパースタスクを開始
pub fn DocumentParser::start_parse(
self : DocumentParser,
html : String,
) -> TaskId {
self.scheduler.enqueue(ParseHTMLChunk(html~), MainThreadOnly)
}
///|
/// パース完了後の処理 - リソース発見とタスク生成
/// 外部から ParseHTMLChunk タスク完了時に呼ばれる
pub fn DocumentParser::on_parse_complete(
self : DocumentParser,
html : String,
) -> ParseCompleteResult {
// HTMLをパース
let root = @html.parse_fragment_v2(html)
self.parsed_root = Some(root)
// リソースを発見
let discovered = discover_resources(root)
self.discovered = Some(discovered)
// リソースフェッチタスクを生成
let fetch_tasks : Array[TaskId] = []
let blocking_tasks : Array[TaskId] = []
for resource in discovered.resources {
let constraint = if resource.blocking {
// blocking リソースは他のタスクをブロック
Blocking([DOM, Styling])
} else if resource.is_async {
// async は並列実行可能
Parallel
} else {
MainThreadOnly
}
let task_id = self.scheduler.enqueue(
FetchResource(url=resource.url, resource_type=resource.resource_type),
constraint,
)
fetch_tasks.push(task_id)
if resource.blocking {
blocking_tasks.push(task_id)
}
}
// インラインスタイルのパースタスク
let style_tasks : Array[TaskId] = []
for style_source in discovered.inline_styles {
let task_id = self.scheduler.enqueue(
ParseCSS(source=style_source),
MainThreadOnly,
)
style_tasks.push(task_id)
}
// インラインスクリプトの実行タスク
let script_tasks : Array[TaskId] = []
for script in discovered.inline_scripts {
let constraint = if script.blocking {
Blocking([DOM])
} else {
MainThreadOnly
}
let task_id = self.scheduler.enqueue(
ExecuteScript(source=script.source),
constraint,
)
script_tasks.push(task_id)
}
{
root,
fetch_tasks,
style_tasks,
script_tasks,
blocking_tasks,
resource_count: discovered.resources.length(),
inline_style_count: discovered.inline_styles.length(),
inline_script_count: discovered.inline_scripts.length(),
}
}
///|
/// パース済みのルート要素を取得
pub fn DocumentParser::get_root(self : DocumentParser) -> @html.Element? {
self.parsed_root
}
///|
/// 発見されたリソースを取得
pub fn DocumentParser::get_discovered_resources(
self : DocumentParser,
) -> ResourceDiscoveryResult? {
self.discovered
}
// =============================================================================
// Parse Complete Result
// =============================================================================
///|
/// パース完了時の結果
pub(all) struct ParseCompleteResult {
/// パース済みのルート要素
root : @html.Element
/// 生成されたフェッチタスク
fetch_tasks : Array[TaskId]
/// 生成されたスタイルパースタスク
style_tasks : Array[TaskId]
/// 生成されたスクリプト実行タスク
script_tasks : Array[TaskId]
/// blocking タスク
blocking_tasks : Array[TaskId]
/// 発見されたリソース数
resource_count : Int
/// インラインスタイル数
inline_style_count : Int
/// インラインスクリプト数
inline_script_count : Int
}
// =============================================================================
// Convenience Functions
// =============================================================================
///|
/// HTMLを解析してすべてのタスクを生成する便利関数
pub fn parse_and_schedule(
scheduler : Scheduler,
html : String,
base_url? : String = "",
) -> ParseCompleteResult {
let parser = DocumentParser::new(scheduler, base_url~)
parser.on_parse_complete(html)
}
///|
/// 発見されたリソースのURLリストを取得
pub fn ResourceDiscoveryResult::get_urls(
self : ResourceDiscoveryResult,
) -> Array[String] {
self.resources.map(fn(r) { r.url })
}
///|
/// 特定タイプのリソースをフィルタ
pub fn ResourceDiscoveryResult::filter_by_type(
self : ResourceDiscoveryResult,
resource_type : ResourceType,
) -> Array[DiscoveredResource] {
self.resources.filter(fn(r) { r.resource_type == resource_type })
}
///|
/// blocking リソースのみ取得
pub fn ResourceDiscoveryResult::get_blocking(
self : ResourceDiscoveryResult,
) -> Array[DiscoveredResource] {
self.resources.filter(fn(r) { r.blocking })
}