siyuan-note/siyuan · error

empty IFrame block

Error message

empty IFrame block

What it means

htmlAssetIFrameBlockDOM converts a generated iframe markdown snippet into a block DOM tree via Lute and expects the tree to contain a root with a first child block. If Lute produced an empty tree (nil root or no first child), parsing failed silently and this error reports it instead of returning a bogus block ID.

Source

Thrown at kernel/mcp/tools/asset.go:189

		name = "component.html"
	}
	name = filepath.Base(name)
	ext := strings.ToLower(filepath.Ext(name))
	if ext != ".html" && ext != ".htm" {
		return "", fmt.Errorf("name must end in .html or .htm")
	}
	return name, nil
}

func htmlAssetIFrameBlockDOM(assetPath string) (dom, blockID string, err error) {
	src := html.EscapeString(model.HTMLAssetIFrameSrc(assetPath))
	dom, err = markdownToBlockDOM(`<iframe sandbox="allow-scripts" src="` + src + `" border="0" frameborder="no" framespacing="0" allowfullscreen="true"></iframe>`)
	if err != nil {
		return
	}
	tree := util.NewLute().BlockDOM2Tree(dom)
	if tree == nil || tree.Root == nil || tree.Root.FirstChild == nil {
		return "", "", fmt.Errorf("empty IFrame block")
	}
	blockID = tree.Root.FirstChild.ID
	return
}

func newAssetUploadToolResult(succeeded []model.AssetUploadSuccess, failed []model.AssetUploadFailure) CallToolResult {
	var sb strings.Builder
	sb.WriteString(fmt.Sprintf("Uploaded %d file(s):\n", len(succeeded)))
	for _, result := range succeeded {
		sb.WriteString(fmt.Sprintf("- %s -> %s\n", result.Name, result.Path))
	}
	if 0 < len(failed) {
		sb.WriteString(fmt.Sprintf("\nFailed %d file(s):\n", len(failed)))
		for _, result := range failed {
			sb.WriteString(fmt.Sprintf("- %s: %s\n", result.Name, result.Error))
		}
	}
	return CallToolResult{Content: []ContentItem{{Type: "text", Text: sb.String()}}, IsError: 0 < len(failed)}

View on GitHub (pinned to 8641553a1f)

Solutions

  1. Check whether markdownToBlockDOM returned an empty string or error and propagate it before calling BlockDOM2Tree
  2. Verify the Lute version parses raw HTML iframe blocks (test with util.NewLute().BlockDOM2Tree on a minimal iframe snippet)
  3. If a sanitizer/filter strips iframe, adjust the parsing options so the iframe markdown is preserved

Example fix

// before
dom, err = markdownToBlockDOM(iframeMD)
if err != nil { return } // dom may be empty, error surfaces later
// after
dom, err = markdownToBlockDOM(iframeMD)
if err != nil { return }
if dom == "" { return "", "", fmt.Errorf("empty DOM for iframe block") }
Defensive patterns

Strategy: validation

Validate before calling

tree := util.NewLute().BlockDOM2Tree(dom)
if tree == nil || tree.Root == nil || tree.Root.FirstChild == nil { return errors.New("parsed DOM is empty") }

Type guard

func hasFirstChildBlock(tree *parse.Tree) bool { return tree != nil && tree.Root != nil && tree.Root.FirstChild != nil }

Try / catch

blockID, dom, err := htmlAssetIFrameBlockDOM(assetPath)
if err != nil && strings.Contains(err.Error(), "empty IFrame block") {
    // re-render the DOM or upgrade/inspect the Lute parsing path
}

Prevention

When it happens

Trigger: markdownToBlockDOM rendered the iframe snippet into an empty/invalid DOM — e.g. Lute configuration stripped the raw HTML iframe, markdownToBlockDOM returned "", or BlockDOM2Tree failed to parse the produced DOM string.

Common situations: A Lute version change altering raw-HTML/iframe parsing; markdownToBlockDOM returning empty output due to an upstream error that wasn't propagated; unusual sanitizer settings filtering iframe tags.

Understand the failure class

Background: EmptyResultError / "no results found": when an API or scraper succeeds but returns zero rows — this error's family across 9 libraries.

Related errors


AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11). Data as JSON: /api/errors/edaa956d9e614d17. Report an issue: GitHub.