diff --git a/packages/compiler-core/__tests__/transforms/transformElement.spec.ts b/packages/compiler-core/__tests__/transforms/transformElement.spec.ts
index bf3510a052d..e4ab776a764 100644
--- a/packages/compiler-core/__tests__/transforms/transformElement.spec.ts
+++ b/packages/compiler-core/__tests__/transforms/transformElement.spec.ts
@@ -99,6 +99,16 @@ describe('compiler: element transform', () => {
     expect(node.tag).toBe(`$setup["Example"]`)
   })
 
+  test('resolve component from setup bindings & component', () => {
+    const { root, node } = parseWithElementTransform(`<Example/>`, {
+      bindingMetadata: {
+        Example: BindingTypes.SETUP_CONST,
+      },
+    })
+    expect(root.helpers).not.toContain(RESOLVE_COMPONENT)
+    expect(node.tag).toBe(`_resolveSetupReturned("Example", $setup)`)
+  })
+
   test('resolve component from setup bindings (inline)', () => {
     const { root, node } = parseWithElementTransform(`<Example/>`, {
       inline: true,
diff --git a/packages/compiler-core/src/ast.ts b/packages/compiler-core/src/ast.ts
index 8116f532b79..2247f2c758b 100644
--- a/packages/compiler-core/src/ast.ts
+++ b/packages/compiler-core/src/ast.ts
@@ -9,6 +9,7 @@ import {
   OPEN_BLOCK,
   type RENDER_LIST,
   type RENDER_SLOT,
+  RESOLVE_SETUP_RETURNED,
   WITH_DIRECTIVES,
   type WITH_MEMO,
 } from './runtimeHelpers'
@@ -865,6 +866,10 @@ export function getVNodeBlockHelper(
   return ssr || isComponent ? CREATE_BLOCK : CREATE_ELEMENT_BLOCK
 }
 
+export function getSetupReturnedHelper() {
+  return RESOLVE_SETUP_RETURNED
+}
+
 export function convertToBlock(
   node: VNodeCall,
   { helper, removeHelper, inSSR }: TransformContext,
diff --git a/packages/compiler-core/src/codegen.ts b/packages/compiler-core/src/codegen.ts
index 6b6f24b3a30..909fbf42c94 100644
--- a/packages/compiler-core/src/codegen.ts
+++ b/packages/compiler-core/src/codegen.ts
@@ -24,6 +24,7 @@ import {
   type TemplateLiteral,
   type TextNode,
   type VNodeCall,
+  getSetupReturnedHelper,
   getVNodeBlockHelper,
   getVNodeHelper,
   locStub,
@@ -318,6 +319,8 @@ export function generate(
   if (!__BROWSER__ && options.bindingMetadata && !options.inline) {
     // binding optimization args
     args.push('$props', '$setup', '$data', '$options')
+    // Add helper 'getSetupReturnedHelper' for $setup
+    context.helper(getSetupReturnedHelper())
   }
   const signature =
     !__BROWSER__ && options.isTS
diff --git a/packages/compiler-core/src/runtimeHelpers.ts b/packages/compiler-core/src/runtimeHelpers.ts
index 7cf3757b249..3eff4592b6e 100644
--- a/packages/compiler-core/src/runtimeHelpers.ts
+++ b/packages/compiler-core/src/runtimeHelpers.ts
@@ -26,6 +26,9 @@ export const CREATE_STATIC: unique symbol = Symbol(
 export const RESOLVE_COMPONENT: unique symbol = Symbol(
   __DEV__ ? `resolveComponent` : ``,
 )
+export const RESOLVE_SETUP_RETURNED = Symbol(
+  __DEV__ ? `resolveSetupReturned` : ``,
+)
 export const RESOLVE_DYNAMIC_COMPONENT: unique symbol = Symbol(
   __DEV__ ? `resolveDynamicComponent` : ``,
 )
@@ -98,6 +101,7 @@ export const helperNameMap: Record<symbol, string> = {
   [CREATE_TEXT]: `createTextVNode`,
   [CREATE_STATIC]: `createStaticVNode`,
   [RESOLVE_COMPONENT]: `resolveComponent`,
+  [RESOLVE_SETUP_RETURNED]: `resolveSetupReturned`,
   [RESOLVE_DYNAMIC_COMPONENT]: `resolveDynamicComponent`,
   [RESOLVE_DIRECTIVE]: `resolveDirective`,
   [RESOLVE_FILTER]: `resolveFilter`,
diff --git a/packages/compiler-core/src/transforms/transformElement.ts b/packages/compiler-core/src/transforms/transformElement.ts
index c917436ea91..471aac038f6 100644
--- a/packages/compiler-core/src/transforms/transformElement.ts
+++ b/packages/compiler-core/src/transforms/transformElement.ts
@@ -21,6 +21,7 @@ import {
   createObjectProperty,
   createSimpleExpression,
   createVNodeCall,
+  getSetupReturnedHelper,
 } from '../ast'
 import {
   PatchFlags,
@@ -344,10 +345,13 @@ function resolveSetupReference(name: string, context: TransformContext) {
     checkType(BindingTypes.SETUP_REACTIVE_CONST) ||
     checkType(BindingTypes.LITERAL_CONST)
   if (fromConst) {
+    const helper = context.helperString
     return context.inline
       ? // in inline mode, const setup bindings (e.g. imports) can be used as-is
         fromConst
-      : `$setup[${JSON.stringify(fromConst)}]`
+      : `${helper(getSetupReturnedHelper())}(${JSON.stringify(
+          fromConst,
+        )}, $setup)`
   }
 
   const fromMaybeRef =
diff --git a/packages/runtime-core/src/helpers/resolveAssets.ts b/packages/runtime-core/src/helpers/resolveAssets.ts
index aa6532c2811..a6dd122e5af 100644
--- a/packages/runtime-core/src/helpers/resolveAssets.ts
+++ b/packages/runtime-core/src/helpers/resolveAssets.ts
@@ -6,7 +6,7 @@ import {
 } from '../component'
 import { currentRenderingInstance } from '../componentRenderContext'
 import type { Directive } from '../directives'
-import { camelize, capitalize, isString } from '@vue/shared'
+import { camelize, capitalize, isLateTag, isString } from '@vue/shared'
 import { warn } from '../warning'
 import type { VNodeTypes } from '../vnode'
 
@@ -106,18 +106,26 @@ function resolveAsset(
       resolve(instance[type] || (Component as ComponentOptions)[type], name) ||
       // global registration
       resolve(instance.appContext[type], name)
-
     if (!res && maybeSelfReference) {
       // fallback to implicit self-reference
       return Component
     }
 
-    if (__DEV__ && warnMissing && !res) {
-      const extra =
-        type === COMPONENTS
-          ? `\nIf this is a native custom element, make sure to exclude it from ` +
+    if (
+      __DEV__ &&
+      warnMissing &&
+      ((!res && !isLateTag(name)) || (res && isLateTag(name)))
+    ) {
+      let extra = ''
+      if (type === COMPONENTS) {
+        if (isLateTag(name)) {
+          extra = `\nplease do not use built-in tag names as component names.`
+        } else {
+          extra =
+            `\nIf this is a native custom element, make sure to exclude it from ` +
             `component resolution via compilerOptions.isCustomElement.`
-          : ``
+        }
+      }
       warn(`Failed to resolve ${type.slice(0, -1)}: ${name}${extra}`)
     }
 
@@ -138,3 +146,16 @@ function resolve(registry: Record<string, any> | undefined, name: string) {
       registry[capitalize(camelize(name))])
   )
 }
+
+/**
+ * @private
+ */
+export function resolveSetupReturned(name: string, setupReturn: any) {
+  if (!setupReturn) return name
+  const returnValue = setupReturn[name]
+  if (returnValue && returnValue.__file && isLateTag(name as string)) {
+    const extra = `\nplease do not use built-in tag names as component names.`
+    warn(`Failed to resolve component: ${name},${extra}`)
+  }
+  return returnValue
+}
diff --git a/packages/runtime-core/src/hydrationStrategies.ts b/packages/runtime-core/src/hydrationStrategies.ts
index 51200fc1c34..791ca9e5254 100644
--- a/packages/runtime-core/src/hydrationStrategies.ts
+++ b/packages/runtime-core/src/hydrationStrategies.ts
@@ -26,6 +26,16 @@ export const hydrateOnIdle: HydrationStrategyFactory<number> =
     return () => cancelIdleCallback(id)
   }
 
+function elementIsVisibleInViewport(el: Element) {
+  const { top, left, bottom, right } = el.getBoundingClientRect()
+  // eslint-disable-next-line no-restricted-globals
+  const { innerHeight, innerWidth } = window
+  return (
+    ((top > 0 && top < innerHeight) || (bottom > 0 && bottom < innerHeight)) &&
+    ((left > 0 && left < innerWidth) || (right > 0 && right < innerWidth))
+  )
+}
+
 export const hydrateOnVisible: HydrationStrategyFactory<
   IntersectionObserverInit
 > = opts => (hydrate, forEach) => {
@@ -37,7 +47,14 @@ export const hydrateOnVisible: HydrationStrategyFactory<
       break
     }
   }, opts)
-  forEach(el => ob.observe(el))
+  forEach(el => {
+    if (elementIsVisibleInViewport(el)) {
+      hydrate()
+      ob.disconnect()
+      return false
+    }
+    ob.observe(el)
+  })
   return () => ob.disconnect()
 }
 
@@ -85,14 +102,20 @@ export const hydrateOnInteraction: HydrationStrategyFactory<
     return teardown
   }
 
-export function forEachElement(node: Node, cb: (el: Element) => void): void {
+export function forEachElement(
+  node: Node,
+  cb: (el: Element) => void | false,
+): void {
   // fragment
   if (isComment(node) && node.data === '[') {
     let depth = 1
     let next = node.nextSibling
     while (next) {
       if (next.nodeType === DOMNodeTypes.ELEMENT) {
-        cb(next as Element)
+        const result = cb(next as Element)
+        if (result === false) {
+          break
+        }
       } else if (isComment(next)) {
         if (next.data === ']') {
           if (--depth === 0) break
diff --git a/packages/runtime-core/src/index.ts b/packages/runtime-core/src/index.ts
index 7f716b5f4e8..15c915c8919 100644
--- a/packages/runtime-core/src/index.ts
+++ b/packages/runtime-core/src/index.ts
@@ -144,6 +144,7 @@ export {
   resolveComponent,
   resolveDirective,
   resolveDynamicComponent,
+  resolveSetupReturned,
 } from './helpers/resolveAssets'
 // For integration with runtime compiler
 export { registerRuntimeCompiler, isRuntimeOnly } from './component'
diff --git a/packages/shared/src/domTagConfig.ts b/packages/shared/src/domTagConfig.ts
index 7f9d198e569..9084909c4b1 100644
--- a/packages/shared/src/domTagConfig.ts
+++ b/packages/shared/src/domTagConfig.ts
@@ -14,6 +14,8 @@ const HTML_TAGS =
   'option,output,progress,select,textarea,details,dialog,menu,' +
   'summary,template,blockquote,iframe,tfoot'
 
+const LATE_ADDED_TAGS = 'search'
+
 // https://developer.mozilla.org/en-US/docs/Web/SVG/Element
 const SVG_TAGS =
   'svg,animate,animateMotion,animateTransform,circle,clipPath,color-profile,' +
@@ -62,3 +64,5 @@ export const isMathMLTag: (key: string) => boolean =
  */
 export const isVoidTag: (key: string) => boolean =
   /*@__PURE__*/ makeMap(VOID_TAGS)
+
+export const isLateTag = /*#__PURE__*/ makeMap(LATE_ADDED_TAGS)