1# Using Same-Layer Rendering 2 3In the system, applications can use the **Web** component to load web pages. If the capability or performance of non-native UI components (same-layer components) is inferior to that of native components, you can use the ArkUI component to render these components. 4 5## When to Use 6### On the Web Page 7To improve the performance of an applet, you can use the ArkUI **XComponent** component to render the map component, and use the ArkUI **TextInput** component to render the input box component. 8- On the web page, you can render the UI components (same-layer tags) such as **\<embed>** and **\<object>** at the same layer based on certain rules. For details, see [Specifications and Constraints](#specifications-and-constraints). 9 10- On the application, you can use the same-layer rendering event reporting API of the **Web** component to detect the lifecycle and input event of the HTML5 same-layer tags, and process the service logic of the same-layer rendering components. 11 12- In addition, you can use ArkUI APIs such as **NodeContainer** to construct same-layer rendering components corresponding to HTML5 same-layer tags. Common ArkUI components that support same-layer rendering: [TextInput](../reference/apis-arkui/arkui-ts/ts-basic-components-textinput.md), [XComponent](../reference/apis-arkui/arkui-ts/ts-basic-components-xcomponent.md), [Canvas](../reference/apis-arkui/arkui-ts/ts-components-canvas-canvas.md), [Video](../reference/apis-arkui/arkui-ts/ts-media-components-video.md), [Web](../reference/apis-arkweb/ts-basic-components-web.md). For details, see [Specifications and Constraints](#specifications-and-constraints). 13 14### On the Third-Party UI Framework 15Flutter provides the **PlatformView** and **Texture** abstract components that can be rendered using native components, which complete the functions of the Flutter components. Weex2.0 framework supports the **Camera**, **Video**, and **Canvas** components. 16 17- Since third-party frameworks such as Flutter and Weex are not operated in the OS, the available third-party framework UI components that can be rendered at the same layer are not listed in the following. 18 19- On the application, you can use ArkUI APIs such as **NodeContainer** to construct same-layer rendering components corresponding to third-party framework same-layer tags. Common ArkUI components that support same-layer rendering: [TextInput](../reference/apis-arkui/arkui-ts/ts-basic-components-textinput.md), [XComponent](../reference/apis-arkui/arkui-ts/ts-basic-components-xcomponent.md), [Canvas](../reference/apis-arkui/arkui-ts/ts-components-canvas-canvas.md), [Video](../reference/apis-arkui/arkui-ts/ts-media-components-video.md), [Web](../reference/apis-arkweb/ts-basic-components-web.md). For details, see [Specifications and Constraints](#specifications-and-constraints). 20 21## Overall Architecture 22The ArkWeb same-layer rendering feature supports same-layer tag lifecycle and event hit forwarding. 23 24The lifecycle of same-layer tags is associated with front-end tags (\<embed>/\<object>). Events that hit the same-layer tags are reported to you, and you should distribute them to the corresponding component tree. The following figure shows the overall framework. 25 26**Figure 1** Overall architecture of same-layer rendering 27 28 29 30## Specifications and Constraints 31### ArkUI Components That Can Be Rendered at the Same Layer 32 33The following specifications take effect in both web pages and third-party frameworks. 34 35**Supported Components**: 36 37- Basic components: [AlphabetIndexer](../reference/apis-arkui/arkui-ts/ts-container-alphabet-indexer.md), [Blank](../reference/apis-arkui/arkui-ts/ts-basic-components-blank.md), [Button](../reference/apis-arkui/arkui-ts/ts-basic-components-button.md), [CalendarPicker](../reference/apis-arkui/arkui-ts/ts-basic-components-calendarpicker.md), [Checkbox](../reference/apis-arkui/arkui-ts/ts-basic-components-checkbox.md), [CheckboxGroup](../reference/apis-arkui/arkui-ts/ts-basic-components-checkboxgroup.md), [ContainerSpan](../reference/apis-arkui/arkui-ts/ts-basic-components-containerspan.md), [DataPanel](../reference/apis-arkui/arkui-ts/ts-basic-components-datapanel.md), [DatePicker](../reference/apis-arkui/arkui-ts/ts-basic-components-datepicker.md), [Divider](../reference/apis-arkui/arkui-ts/ts-basic-components-divider.md), [Gauge](../reference/apis-arkui/arkui-ts/ts-basic-components-gauge.md), [Hyperlink](../reference/apis-arkui/arkui-ts/ts-container-hyperlink.md), [Image](../reference/apis-arkui/arkui-ts/ts-basic-components-image.md), [ImageAnimator](../reference/apis-arkui/arkui-ts/ts-basic-components-imageanimator.md), [ImageSpan](../reference/apis-arkui/arkui-ts/ts-basic-components-imagespan.md), [LoadingProgress](../reference/apis-arkui/arkui-ts/ts-basic-components-loadingprogress.md), [Marquee](../reference/apis-arkui/arkui-ts/ts-basic-components-marquee.md), [PatternLock](../reference/apis-arkui/arkui-ts/ts-basic-components-patternlock.md), [Progress](../reference/apis-arkui/arkui-ts/ts-basic-components-progress.md), [QRCode](../reference/apis-arkui/arkui-ts/ts-basic-components-qrcode.md), [Radio](../reference/apis-arkui/arkui-ts/ts-basic-components-radio.md), [Rating](../reference/apis-arkui/arkui-ts/ts-basic-components-rating.md), [Refresh](../reference/apis-arkui/arkui-ts/ts-container-refresh.md), [ScrollBar](../reference/apis-arkui/arkui-ts/ts-container-scroll.md), [Search](../reference/apis-arkui/arkui-ts/ts-basic-components-search.md), [Span](../reference/apis-arkui/arkui-ts/ts-basic-components-span.md), [Select](../reference/apis-arkui/arkui-ts/ts-basic-components-select.md), [Slider](../reference/apis-arkui/arkui-ts/ts-basic-components-slider.md), [Text](../reference/apis-arkui/arkui-ts/ts-basic-components-text.md), [TextArea](../reference/apis-arkui/arkui-ts/ts-basic-components-textarea.md), [TextClock](../reference/apis-arkui/arkui-ts/ts-basic-components-textclock.md), [TextInput](../reference/apis-arkui/arkui-ts/ts-basic-components-textinput.md), [TextPicker](../reference/apis-arkui/arkui-ts/ts-basic-components-textpicker.md), [TextTimer](../reference/apis-arkui/arkui-ts/ts-basic-components-texttimer.md), [TimePicker](../reference/apis-arkui/arkui-ts/ts-basic-components-timepicker.md), [Toggle](../reference/apis-arkui/arkui-ts/ts-basic-components-toggle.md) 38 39- Container components: [Badge](../reference/apis-arkui/arkui-ts/ts-container-badge.md), [Column](../reference/apis-arkui/arkui-ts/ts-container-column.md), [ColumnSplit](../reference/apis-arkui/arkui-ts/ts-container-columnsplit.md), [Counter](../reference/apis-arkui/arkui-ts/ts-container-counter.md), [Flex](../reference/apis-arkui/arkui-ts/ts-container-flex.md), [GridCol](../reference/apis-arkui/arkui-ts/ts-container-gridcol.md), [GridRow](../reference/apis-arkui/arkui-ts/ts-container-gridrow.md), [Grid](../reference/apis-arkui/arkui-ts/ts-container-grid.md), [GridItem](../reference/apis-arkui/arkui-ts/ts-container-griditem.md) and [List](../reference/apis-arkui/arkui-ts/ts-container-list.md), [ListItem](../reference/apis-arkui/arkui-ts/ts-container-listitem.md), [ListItemGroup](../reference/apis-arkui/arkui-ts/ts-container-listitemgroup.md), [RelativeContainer](../reference/apis-arkui/arkui-ts/ts-container-relativecontainer.md), [Row](../reference/apis-arkui/arkui-ts/ts-container-row.md), [RowSplit](../reference/apis-arkui/arkui-ts/ts-container-rowsplit.md), [Scroll](../reference/apis-arkui/arkui-ts/ts-container-scroll.md), [Stack](../reference/apis-arkui/arkui-ts/ts-container-stack.md), [Swiper](../reference/apis-arkui/arkui-ts/ts-container-swiper.md), [Tabs](../reference/apis-arkui/arkui-ts/ts-container-tabs.md), [TabContent](../reference/apis-arkui/arkui-ts/ts-container-tabcontent.md), [NodeContainer](../reference/apis-arkui/arkui-ts/ts-basic-components-nodecontainer.md), [SideBarContainer](../reference/apis-arkui/arkui-ts/ts-container-sidebarcontainer.md), [Stepper](../reference/apis-arkui/arkui-ts/ts-basic-components-stepper.md), [StepperItem](../reference/apis-arkui/arkui-ts/ts-basic-components-stepperitem.md), [WaterFlow](../reference/apis-arkui/arkui-ts/ts-container-waterflow.md), [FlowItem](../reference/apis-arkui/arkui-ts/ts-container-flowitem.md) 40 41- Self-drawing components: [XComponent](../reference/apis-arkui/arkui-ts/ts-basic-components-xcomponent.md), [Canvas](../reference/apis-arkui/arkui-ts/ts-components-canvas-canvas.md), [Video](../reference/apis-arkui/arkui-ts/ts-media-components-video.md), [Web](../reference/apis-arkweb/ts-basic-components-web.md) 42 43- Command-based custom drawing nodes: [BuilderNode](../reference/apis-arkui/js-apis-arkui-builderNode.md), [ComponentContent](../reference/apis-arkui/js-apis-arkui-ComponentContent.md), [ContentSlot](../reference/apis-arkui/arkui-ts/ts-components-contentSlot.md), [FrameNode](../reference/apis-arkui/js-apis-arkui-frameNode.md), [Graphics](../reference/apis-arkui/js-apis-arkui-graphics.md), [NodeController](../reference/apis-arkui/js-apis-arkui-nodeController.md), [RenderNode](../reference/apis-arkui/js-apis-arkui-renderNode.md), [XComponentNode](../reference/apis-arkui/js-apis-arkui-xcomponentNode.md), [AttributeUpdater](../reference/apis-arkui/js-apis-arkui-AttributeUpdater.md) and [CAPI](../reference/apis-arkui/_ark_u_i___native_module.md) (The components that support same-layer rendering are the same as that of ArkTS.) 44 45**Supported Common Component Attributes and Events**: 46 47- Common attributes that are not supported: [restoreId](../reference/apis-arkui/arkui-ts/ts-universal-attributes-restoreId.md) and [Special Effect Drawing Combination](../reference/apis-arkui/arkui-ts/ts-universal-attributes-use-effect.md). 48 49- Other attributes, events, and component capabilities that are not clearly marked as not supported are supported by default. 50 51### Same-Layer Rendering Tags of the Web Page 52This specification applies only to web pages and does not apply to third-party frameworks. 53 54If an application needs to use the same-layer rendering on a web page loaded by the **Web** component, you need to specify the **\<embed>** and **\<object>** tags on the web page as the same-layer rendering components based on the following specifications. 55 56**Supported Devices**: 57Currently, only mobile phones and tablets are supported. 58 59**Supported HTML5 Tags**: 60- **\<embed>**: After same-layer rendering is enabled, only tags whose type is prefixed with **native** can be identified as same-layer components. Attributes cannot be customized. 61 62- **\<object>**: After the same-layer rendering is enabled, the **\<object>** tag of the non-standard **MIME** type can be identified as a same-layer component and parsed based on the custom **param**/**value** attribute. 63 64- W3C standard tags (such as **\<input>** and **\<video>**) cannot be defined as same-layer tags. 65 66- The **\<object>** and **\<embed>** tags cannot be configured as the same-layer tags at the same time. 67 68- The tag types contain only English characters and are case insensitive. 69 70**Supported Attributes of Same-Layer Tags**: 71CSS attributes that comply with the W3C standard. 72 73**Lifecycle Management of Same-Layer Tags**: 74The [onNativeEmbedLifecycleChange()](../reference/apis-arkweb/ts-basic-components-web.md#onnativeembedlifecyclechange11) callback is triggered when the lifecycle of the **Embed** tag changes. 75 76- Creation, destruction, and position width and height change are supported. The visibility status change is not supported. 77 78- Web pages containing same-layer components support back-forward cache. 79 80**Distributing and Processing the Input Events of Same-Layer Tags**: 81- The **DOWN**, **UP**, **MOVE**, and **CANCEL** touch events are supported. The [onnativeembedgestureevent11](../reference/apis-arkweb/ts-basic-components-web.md#onnativeembedgestureevent11) can be configured. By default, the touch event is consumed on the application side. 82 83- The application page containing same-layer components cannot be scaled, and the scaling APIs such as [initialScale](../reference/apis-arkweb/ts-basic-components-web.md#initialscale), [zoom](../reference/apis-arkweb/js-apis-webview.md#zoom), [zoomIn](../reference/apis-arkweb/js-apis-webview.md#zoomin) and [zoomOut](../reference/apis-arkweb/js-apis-webview.md#zoomout) are not supported. 84 85- Mouse, keyboard, and touchpad events are not supported. 86 87**Constraints**: 88 89- Configure a maximum of five same-layer tags on a web page. Otherwise, the rendering performance deteriorates. 90 91- Due to GPU restrictions, the maximum height and texture size of a same-layer tag are 8000 px. 92 93- When same-layer rendering is enabled, web pages opened by the **Web** component do not support the unified [RenderMode](../reference/apis-arkweb/ts-basic-components-web.md#rendermode12). 94 95- When the non-full-screen mode is changed to the full-screen mode, the **Video** component is exported through non-texture mode and the video playback status remains unchanged. When the non-full-screen mode is restored, the **Video** component is exported through texture mode and the video playback status remains unchanged. 96 97- The **Web** component supports only same-layer rendering nesting at one layer. The input events such as swipe, tap, zoom, and long-press are supported, while drag and rotate events are not supported. 98 99- In the page layout of ArkUI components (such as **TextInput**), you are advised to use a **Stack** component to wrap the same-layer **NodeContainer** and **BuilderNode** and ensure that they are in the same position. In addition, the **NodeContainer** must be aligned with the **\<embed>**/**\<object>** tag to ensure proper component interaction. If the positions of the two components are different, the following problems may occur: The position of the text selection box attached to the **TextInput**/**TextArea** component is incorrect (as shown in the following figure). The animation start and stop of the **LoadingProgress**/**Marquee** component do not match the visibility status of the component. 100 101 **Figure 2** Misplaced **TextInput** without **Stack** 102 103  104 105 **Figure 3** Proper **TextInput** with **Stack** 106 107  108 109## Rendering Text Boxes at the Same Layer on Web Pages 110On web pages, you can render the native ArkUI **TextInput** components at the same layer. The following figure shows the effect of three text boxes that are rendered at the same layer. 111 112**Figure 4** Same-layer rendering text boxes 113 114  115 1161. Mark the HTML tags that need to be rendered at the same layer on the web page. 117 118 The **\<embed>** and **\<object>** tags support same-layer rendering, and the **type** can be specified randomly. They are case insensitive and will be converted to lowercase letters by the ArkWeb kernel. The **tag** string is matched using the entire string, and the **type** string is matched using the prefix. 119 120 If this API is not used or receives an invalid string (empty string), the ArkWeb kernel uses the default setting, that is, "embed" + "native/" prefix. If the specified **type** is the same as the W3C standard **object** or **embed** type, for example, **registerNativeEmbedRule** ("**object**," "**application**/**pdf**"), ArkWeb will comply with the W3C standard behavior and will not identify it as a same-layer tag. 121 122 - Use the \<embed> tags. 123 124 ```html 125 <!--HAP's src/main/resources/rawfile/text.html--> 126 <!DOCTYPE html> 127 <html> 128 <head> 129 <title>Same-Layer Rendering Test HTML</title> 130 <meta name="viewport"> 131 </head> 132 133 <body style="background:white"> 134 135 <embed id = "input1" type="native/view" style="width: 100%; height: 100px; margin: 30px; margin-top: 600px"/> 136 137 <embed id = "input2" type="native/view2" style="width: 100%; height: 100px; margin: 30px; margin-top: 50px"/> 138 139 <embed id = "input3" type="native/view3" style="width: 100%; height: 100px; margin: 30px; margin-top: 50px"/> 140 141 </body> 142 </html> 143 ``` 144 145 - Use the \<object> tags. 146 147 Call **registerNativeEmbedRule** to register a **\<object>** tag. 148 ```ts 149 // ... 150 Web({src: $rawfile("text.html"), controller: this.browserTabController}) 151 // Register the same-layer tag of "object" and type of "test." 152 .registerNativeEmbedRule("object", "test") 153 // ... 154 ``` 155 156 Example of using **registerNativeEmbedRule** on the frontend page, with the tag of "object" and type of "test": 157 158 ```html 159 <!--HAP's src/main/resources/rawfile/text.html--> 160 <!DOCTYPE html> 161 <html> 162 <head> 163 <title>Same-Layer Rendering Test HTML</title> 164 <meta name="viewport"> 165 </head> 166 167 <body style="background:white"> 168 169 <object id = "input1" type="test/input" style="width: 100%; height: 100px; margin: 30px; margin-top: 600px"></object> 170 171 <object id = "input2" type="test/input" style="width: 100%; height: 100px; margin: 30px; margin-top: 50px"></object> 172 173 <object id = "input3" type="test/input" style="width: 100%; height: 100px; margin: 30px; margin-top: 50px"></object> 174 175 </body> 176 </html> 177 ``` 178 1792. Use **enableNativeEmbedMode** to enable the same-layer rendering on the application, 180 181 which is disabled by default. 182 183 ```ts 184 // xxx.ets 185 import { webview } from '@kit.ArkWeb'; 186 @Entry 187 @Component 188 struct WebComponent { 189 controller: webview.WebviewController = new webview.WebviewController(); 190 191 build() { 192 Column() { 193 Web({ src: 'www.example.com', controller: this.controller }) 194 // Enable same-layer rendering. 195 .enableNativeEmbedMode(true) 196 } 197 } 198 } 199 ``` 200 2013. Create a custom component, 202 203 which is displayed as a native component in the corresponding area after the same-layer rendering is enabled. 204 205 ```ts 206 @Component 207 struct TextInputComponent { 208 @Prop params: Params 209 @State bkColor: Color = Color.White 210 211 build() { 212 Column() { 213 TextInput({text: '', placeholder: 'please input your word...'}) 214 .placeholderColor(Color.Gray) 215 .id(this.params?.elementId) 216 .placeholderFont({size: 13, weight: 400}) 217 .caretColor(Color.Gray) 218 .width(this.params?.width) 219 .height(this.params?.height) 220 .fontSize(14) 221 .fontColor(Color.Black) 222 } 223 // The width and height of the outermost custom container component must be the same as those of the tag at the same layer. 224 .width(this.params.width) 225 .height(this.params.height) 226 } 227 } 228 229 @Builder 230 function TextInputBuilder(params:Params) { 231 TextInputComponent({params: params}) 232 .width(params.width) 233 .height(params.height) 234 .backgroundColor(Color.White) 235 } 236 ``` 237 2384. Create a node controller, 239 240 which is used to control and report node behaviors of the corresponding NodeContainer. 241 242 ```ts 243 class MyNodeController extends NodeController { 244 private rootNode: BuilderNode<[Params]> | undefined | null; 245 private embedId_: string = ""; 246 private surfaceId_: string = ""; 247 private renderType_: NodeRenderType = NodeRenderType.RENDER_TYPE_DISPLAY; 248 private width_: number = 0; 249 private height_: number = 0; 250 private type_: string = ""; 251 private isDestroy_: boolean = false; 252 253 setRenderOption(params: nodeControllerParams) { 254 this.surfaceId_ = params.surfaceId; 255 this.renderType_ = params.renderType; 256 this.embedId_ = params.embedId; 257 this.width_ = params.width; 258 this.height_ = params.height; 259 this.type_ = params.type; 260 } 261 262 // Method that must be overridden. It is used to build the number of nodes and return the number of nodes that will be mounted to the corresponding NodeContainer. 263 // Called when the corresponding NodeContainer is created or called by the rebuild method. 264 makeNode(uiContext: UIContext): FrameNode | null { 265 if (this.isDestroy_) { // rootNode is null. 266 return null; 267 } 268 if (!this.rootNode) { // When rootNode is set to undefined 269 this.rootNode = new BuilderNode(uiContext, { surfaceId: this.surfaceId_, type: this.renderType_ }); 270 if(this.rootNode) { 271 this.rootNode.build(wrapBuilder(TextInputBuilder), { textOne: "myTextInput", width: this.width_, height: this.height_ }) 272 return this.rootNode.getFrameNode(); 273 }else{ 274 return null; 275 } 276 } 277 // Return the FrameNode object. 278 return this.rootNode.getFrameNode(); 279 } 280 281 setBuilderNode(rootNode: BuilderNode<Params[]> | null): void { 282 this.rootNode = rootNode; 283 } 284 285 getBuilderNode(): BuilderNode<[Params]> | undefined | null { 286 return this.rootNode; 287 } 288 289 updateNode(arg: Object): void { 290 this.rootNode?.update(arg); 291 } 292 293 getEmbedId(): string { 294 return this.embedId_; 295 } 296 297 setDestroy(isDestroy: boolean): void { 298 this.isDestroy_ = isDestroy; 299 if (this.isDestroy_) { 300 this.rootNode = null; 301 } 302 } 303 304 postEvent(event: TouchEvent | undefined): boolean { 305 return this.rootNode?.postTouchEvent(event) as boolean 306 } 307 } 308 ``` 309 3105. Call [onNativeEmbedLifecycleChange](../reference/apis-arkweb/ts-basic-components-web.md#onnativeembedlifecyclechange11) to listen for the lifecycle changes of the same-layer rendering tags. 311 312 After this function is enabled, the ArkWeb kernel triggers the callback registered by [onNativeEmbedLifecycleChange](../reference/apis-arkweb/ts-basic-components-web.md#onnativeembedlifecyclechange11) each time a same-layer rendering tag is used on a web page. 313 314 315 316 ```ts 317 build() { 318 Row() { 319 Column() { 320 Stack() { 321 ForEach(this.componentIdArr, (componentId: string) => { 322 NodeContainer(this.nodeControllerMap.get(componentId)) 323 .position(this.positionMap.get(componentId)) 324 .width(this.widthMap.get(componentId)) 325 .height(this.heightMap.get(componentId)) 326 }, (embedId: string) => embedId) 327 // Load the local text.html page. 328 Web({src: $rawfile("text.html"), controller: this.browserTabController}) 329 // Enable same-layer rendering. 330 .enableNativeEmbedMode(true) 331 // Register the same-layer tag of "object" and type of "test." 332 .registerNativeEmbedRule("object", "test") 333 // Obtain the lifecycle change data of the embed tag. 334 .onNativeEmbedLifecycleChange((embed) => { 335 console.log("NativeEmbed surfaceId" + embed.surfaceId); 336 // If embed.info.id is used as the key for mapping nodeController, explicitly specify the ID on the HTML5 page. 337 const componentId = embed.info?.id?.toString() as string 338 if (embed.status == NativeEmbedStatus.CREATE) { 339 console.log("NativeEmbed create" + JSON.stringify(embed.info)); 340 // Create a NodeController instance, set parameters, and rebuild. 341 let nodeController = new MyNodeController() 342 // The unit of embed.info.width and embed.info.height is px, which needs to be converted to the default unit vp on the eTS side. 343 nodeController.setRenderOption({surfaceId : embed.surfaceId as string, 344 type : embed.info?.type as string, 345 renderType : NodeRenderType.RENDER_TYPE_TEXTURE, 346 embedId : embed.embedId as string, 347 width : px2vp(embed.info?.width), 348 height : px2vp(embed.info?.height)}) 349 this.edges = {left: `${embed.info?.position?.x as number}px`, top: `${embed.info?.position?.y as number}px`} 350 nodeController.setDestroy(false); 351 // Save the nodeController instance to the Map, with the Id attribute of the embed tag passed in by the Web component as the key. 352 this.nodeControllerMap.set(componentId, nodeController); 353 this.widthMap.set(componentId, px2vp(embed.info?.width)); 354 this.heightMap.set(componentId, px2vp(embed.info?.height)); 355 this.positionMap.set(componentId, this.edges); 356 // Save the Id attribute of the embed tag passed in by the Web component to the @State decorated variable for dynamically creating a nodeContainer. The push action must be executed after the set action. 357 this.componentIdArr.push(componentId) 358 } else if (embed.status == NativeEmbedStatus.UPDATE) { 359 let nodeController = this.nodeControllerMap.get(componentId); 360 console.log("NativeEmbed update" + JSON.stringify(embed)); 361 this.edges = {left: `${embed.info?.position?.x as number}px`, top: `${embed.info?.position?.y as number}px`} 362 this.positionMap.set(componentId, this.edges); 363 this.widthMap.set(componentId, px2vp(embed.info?.width)); 364 this.heightMap.set(componentId, px2vp(embed.info?.height)); 365 nodeController?.updateNode({textOne: 'update', width: px2vp(embed.info?.width), height: px2vp(embed.info?.height)} as ESObject) 366 } else if (embed.status == NativeEmbedStatus.DESTROY) { 367 console.log("NativeEmbed destroy" + JSON.stringify(embed)); 368 let nodeController = this.nodeControllerMap.get(componentId); 369 nodeController?.setDestroy(true) 370 this.nodeControllerMap.clear(); 371 this.positionMap.delete(componentId); 372 this.widthMap.delete(componentId); 373 this.heightMap.delete(componentId); 374 this.componentIdArr.filter((value: string) => value != componentId) 375 } else { 376 console.log("NativeEmbed status" + embed.status); 377 } 378 }) 379 }.height("80%") 380 } 381 } 382 } 383 ``` 384 3856. Call [onNativeEmbedGestureEvent](../reference/apis-arkweb/ts-basic-components-web.md#onnativeembedgestureevent11) to listen for gesture events that are rendered at the same layer. 386 387 When gesture events are listened, the ArkWeb kernel triggers the callback registered by [onNativeEmbedGestureEvent](../reference/apis-arkweb/ts-basic-components-web.md#onnativeembedgestureevent11) each time a touch operation is performed in the same-layer rendering region. 388 389 390 391 ```ts 392 build() { 393 Row() { 394 Column() { 395 Stack() { 396 ForEach(this.componentIdArr, (componentId: string) => { 397 NodeContainer(this.nodeControllerMap.get(componentId)) 398 .position(this.positionMap.get(componentId)) 399 .width(this.widthMap.get(componentId)) 400 .height(this.heightMap.get(componentId)) 401 }, (embedId: string) => embedId) 402 // Load the local text.html page. 403 Web({src: $rawfile("text.html"), controller: this.browserTabController}) 404 // Enable same-layer rendering. 405 .enableNativeEmbedMode(true) 406 // Obtain the lifecycle change data of the embed tag. 407 .onNativeEmbedLifecycleChange((embed) => { 408 // Implement lifecycle changes. 409 }) 410 .onNativeEmbedGestureEvent((touch) => { 411 console.log("NativeEmbed onNativeEmbedGestureEvent" + JSON.stringify(touch.touchEvent)); 412 this.componentIdArr.forEach((componentId: string) => { 413 let nodeController = this.nodeControllerMap.get(componentId); 414 // Send the obtained event of the region at the same layer to the nodeController corresponding to embedId of the region. 415 if(nodeController?.getEmbedId() == touch.embedId) { 416 let ret = nodeController?.postEvent(touch.touchEvent) 417 if(ret) { 418 console.log("onNativeEmbedGestureEvent success " + componentId); 419 } else { 420 console.log("onNativeEmbedGestureEvent fail " + componentId); 421 } 422 if(touch.result) { 423 // Notify the Web component of the gesture event consumption result. 424 touch.result.setGestureEventResult(ret); 425 } 426 } 427 }) 428 }) 429 } 430 } 431 } 432 } 433 ``` 434 435**Sample Code** 436 437To start with, add the Internet permission to the **module.json5** file. For details, see [Declaring Permissions](../security/AccessToken/declare-permissions.md). 438 439 ``` 440 "requestPermissions":[ 441 { 442 "name" : "ohos.permission.INTERNET" 443 } 444 ] 445 ``` 446 447Code on the application side: 448 449 ```ts 450 // Create a NodeController instance. 451 import webview from '@ohos.web.webview'; 452 import { UIContext } from '@ohos.arkui.UIContext'; 453 import { NodeController, BuilderNode, NodeRenderType, FrameNode } from "@ohos.arkui.node"; 454 455 @Observed 456 declare class Params{ 457 elementId: string 458 textOne: string 459 textTwo: string 460 width: number 461 height: number 462 } 463 464 declare class nodeControllerParams { 465 surfaceId: string 466 type: string 467 renderType: NodeRenderType 468 embedId: string 469 width: number 470 height: number 471 } 472 473 // The NodeController instance must be used with a NodeContainer for controlling and feeding back the behavior of the nodes in the container. 474 class MyNodeController extends NodeController { 475 private rootNode: BuilderNode<[Params]> | undefined | null; 476 private embedId_: string = ""; 477 private surfaceId_: string = ""; 478 private renderType_: NodeRenderType = NodeRenderType.RENDER_TYPE_DISPLAY; 479 private width_: number = 0; 480 private height_: number = 0; 481 private type_: string = ""; 482 private isDestroy_: boolean = false; 483 484 setRenderOption(params: nodeControllerParams) { 485 this.surfaceId_ = params.surfaceId; 486 this.renderType_ = params.renderType; 487 this.embedId_ = params.embedId; 488 this.width_ = params.width; 489 this.height_ = params.height; 490 this.type_ = params.type; 491 } 492 493 // Method that must be overridden. It is used to build the number of nodes and return the number of nodes that will be mounted to the corresponding NodeContainer. 494 // Called when the corresponding NodeContainer is created or called by the rebuild method. 495 makeNode(uiContext: UIContext): FrameNode | null { 496 if (this.isDestroy_) { // rootNode is null. 497 return null; 498 } 499 if (!this.rootNode) { // When rootNode is set to undefined 500 this.rootNode = new BuilderNode(uiContext, { surfaceId: this.surfaceId_, type: this.renderType_ }); 501 if(this.rootNode) { 502 this.rootNode.build(wrapBuilder(TextInputBuilder), { textOne: "myTextInput", width: this.width_, height: this.height_ }) 503 return this.rootNode.getFrameNode(); 504 }else{ 505 return null; 506 } 507 } 508 // Return the FrameNode object. 509 return this.rootNode.getFrameNode(); 510 } 511 512 setBuilderNode(rootNode: BuilderNode<Params[]> | null): void { 513 this.rootNode = rootNode; 514 } 515 516 getBuilderNode(): BuilderNode<[Params]> | undefined | null { 517 return this.rootNode; 518 } 519 520 updateNode(arg: Object): void { 521 this.rootNode?.update(arg); 522 } 523 524 getEmbedId(): string { 525 return this.embedId_; 526 } 527 528 setDestroy(isDestroy: boolean): void { 529 this.isDestroy_ = isDestroy; 530 if (this.isDestroy_) { 531 this.rootNode = null; 532 } 533 } 534 535 postEvent(event: TouchEvent | undefined): boolean { 536 return this.rootNode?.postTouchEvent(event) as boolean 537 } 538 } 539 540 @Component 541 struct TextInputComponent { 542 @Prop params: Params 543 @State bkColor: Color = Color.White 544 545 build() { 546 Column() { 547 TextInput({text: '', placeholder: 'please input your word...'}) 548 .placeholderColor(Color.Gray) 549 .id(this.params?.elementId) 550 .placeholderFont({size: 13, weight: 400}) 551 .caretColor(Color.Gray) 552 .fontSize(14) 553 .fontColor(Color.Black) 554 } 555 // The width and height of the outermost custom container component must be the same as those of the tag at the same layer. 556 .width(this.params.width) 557 .height(this.params.height) 558 } 559 } 560 561 // In @Builder, add the specific dynamic component content. 562 @Builder 563 function TextInputBuilder(params:Params) { 564 TextInputComponent({params: params}) 565 .width(params.width) 566 .height(params.height) 567 .backgroundColor(Color.White) 568 } 569 570 @Entry 571 @Component 572 struct Page{ 573 browserTabController: WebviewController = new webview.WebviewController() 574 private nodeControllerMap: Map<string, MyNodeController> = new Map(); 575 @State componentIdArr: Array<string> = []; 576 @State posMap: Map<string, Position | undefined> = new Map(); 577 @State widthMap: Map<string, number> = new Map(); 578 @State heightMap: Map<string, number> = new Map(); 579 @State positionMap: Map<string, Edges> = new Map(); 580 @State edges: Edges = {}; 581 582 build() { 583 Row() { 584 Column() { 585 Stack() { 586 ForEach(this.componentIdArr, (componentId: string) => { 587 NodeContainer(this.nodeControllerMap.get(componentId)) 588 .position(this.positionMap.get(componentId)) 589 .width(this.widthMap.get(componentId)) 590 .height(this.heightMap.get(componentId)) 591 }, (embedId: string) => embedId) 592 // Load the local text.html page. 593 Web({src: $rawfile("text.html"), controller: this.browserTabController}) 594 // Enable same-layer rendering. 595 .enableNativeEmbedMode(true) 596 // Obtain the lifecycle change data of the embed tag. 597 .onNativeEmbedLifecycleChange((embed) => { 598 console.log("NativeEmbed surfaceId" + embed.surfaceId); 599 // If embed.info.id is used as the key for mapping nodeController, explicitly specify the ID on the HTML5 page. 600 const componentId = embed.info?.id?.toString() as string 601 if (embed.status == NativeEmbedStatus.CREATE) { 602 console.log("NativeEmbed create" + JSON.stringify(embed.info)); 603 // Create a NodeController instance, set parameters, and rebuild. 604 let nodeController = new MyNodeController() 605 // The unit of embed.info.width and embed.info.height is px, which needs to be converted to the default unit vp on the eTS side. 606 nodeController.setRenderOption({surfaceId : embed.surfaceId as string, 607 type : embed.info?.type as string, 608 renderType : NodeRenderType.RENDER_TYPE_TEXTURE, 609 embedId : embed.embedId as string, 610 width : px2vp(embed.info?.width), 611 height : px2vp(embed.info?.height)}) 612 this.edges = {left: `${embed.info?.position?.x as number}px`, top: `${embed.info?.position?.y as number}px`} 613 nodeController.setDestroy(false); 614 // Save the nodeController instance to the Map, with the Id attribute of the embed tag passed in by the Web component as the key. 615 this.nodeControllerMap.set(componentId, nodeController); 616 this.widthMap.set(componentId, px2vp(embed.info?.width)); 617 this.heightMap.set(componentId, px2vp(embed.info?.height)); 618 this.positionMap.set(componentId, this.edges); 619 // Save the Id attribute of the embed tag passed in by the Web component to the @State decorated variable for dynamically creating a nodeContainer. The push action must be executed after the set action. 620 this.componentIdArr.push(componentId) 621 } else if (embed.status == NativeEmbedStatus.UPDATE) { 622 let nodeController = this.nodeControllerMap.get(componentId); 623 console.log("NativeEmbed update" + JSON.stringify(embed)); 624 this.edges = {left: `${embed.info?.position?.x as number}px`, top: `${embed.info?.position?.y as number}px`} 625 this.positionMap.set(componentId, this.edges); 626 this.widthMap.set(componentId, px2vp(embed.info?.width)); 627 this.heightMap.set(componentId, px2vp(embed.info?.height)); 628 nodeController?.updateNode({textOne: 'update', width: px2vp(embed.info?.width), height: px2vp(embed.info?.height)} as ESObject) 629 } else if (embed.status == NativeEmbedStatus.DESTROY) { 630 console.log("NativeEmbed destroy" + JSON.stringify(embed)); 631 let nodeController = this.nodeControllerMap.get(componentId); 632 nodeController?.setDestroy(true) 633 this.nodeControllerMap.clear(); 634 this.positionMap.delete(componentId); 635 this.widthMap.delete(componentId); 636 this.heightMap.delete(componentId); 637 this.componentIdArr.filter((value: string) => value != componentId) 638 } else { 639 console.log("NativeEmbed status" + embed.status); 640 } 641 })// Obtain the touch event information of components for same-layer rendering. 642 .onNativeEmbedGestureEvent((touch) => { 643 console.log("NativeEmbed onNativeEmbedGestureEvent" + JSON.stringify(touch.touchEvent)); 644 this.componentIdArr.forEach((componentId: string) => { 645 let nodeController = this.nodeControllerMap.get(componentId); 646 // Send the obtained event of the region at the same layer to the nodeController corresponding to embedId of the region. 647 if(nodeController?.getEmbedId() == touch.embedId) { 648 let ret = nodeController?.postEvent(touch.touchEvent) 649 if(ret) { 650 console.log("onNativeEmbedGestureEvent success " + componentId); 651 } else { 652 console.log("onNativeEmbedGestureEvent fail " + componentId); 653 } 654 if(touch.result) { 655 // Notify the Web component of the gesture event consumption result. 656 touch.result.setGestureEventResult(ret); 657 } 658 } 659 }) 660 }) 661 } 662 } 663 } 664 } 665 } 666 ``` 667 668## Drawing the XComponent+AVPlayer and Button Components 669 670You can enable or disable same-layer rendering through [enableNativeEmbedMode()](../reference/apis-arkweb/ts-basic-components-web.md#enablenativeembedmode11). To use same-layer rendering, the **\<embed>** element must be explicitly used in the HTML file, and the **type** attribute of the element must start with **native/**. The background of the elements corresponding to the tags at the same layer is transparent. 671 672- Example of using same-layer rendering on the application side: 673 674 ```ts 675 // HAP's src/main/ets/pages/Index.ets 676 // Create a NodeController instance. 677 import { webview } from '@kit.ArkWeb'; 678 import { UIContext, NodeController, BuilderNode, NodeRenderType, FrameNode } from "@kit.ArkUI"; 679 import { AVPlayerDemo } from './PlayerDemo'; 680 681 @Observed 682 declare class Params { 683 textOne : string 684 textTwo : string 685 width : number 686 height : number 687 } 688 689 declare class nodeControllerParams { 690 surfaceId : string 691 type : string 692 renderType : NodeRenderType 693 embedId : string 694 width : number 695 height : number 696 } 697 698 // The NodeController instance must be used with a NodeContainer for controlling and feeding back the behavior of the nodes in the container. 699 class MyNodeController extends NodeController { 700 private rootNode: BuilderNode<[Params]> | undefined | null; 701 private embedId_ : string = ""; 702 private surfaceId_ : string = ""; 703 private renderType_ :NodeRenderType = NodeRenderType.RENDER_TYPE_DISPLAY; 704 private width_ : number = 0; 705 private height_ : number = 0; 706 private type_ : string = ""; 707 private isDestroy_ : boolean = false; 708 709 setRenderOption(params : nodeControllerParams) { 710 this.surfaceId_ = params.surfaceId; 711 this.renderType_ = params.renderType; 712 this.embedId_ = params.embedId; 713 this.width_ = params.width; 714 this.height_ = params.height; 715 this.type_ = params.type; 716 } 717 // Method that must be overridden. It is used to build the number of nodes and return the number of nodes that will be mounted to the corresponding NodeContainer. 718 // Called when the corresponding NodeContainer is created or called by the rebuild method. 719 makeNode(uiContext: UIContext): FrameNode | null{ 720 if (this.isDestroy_) { // rootNode is null. 721 return null; 722 } 723 if (!this.rootNode) { // When rootNode is set to undefined 724 this.rootNode = new BuilderNode(uiContext, { surfaceId: this.surfaceId_, type: this.renderType_}); 725 if (this.type_ === 'native/video') { 726 this.rootNode.build(wrapBuilder(VideoBuilder), {textOne: "myButton", width : this.width_, height : this.height_}); 727 } else { 728 // other 729 } 730 } 731 // Return the FrameNode object. 732 return this.rootNode.getFrameNode(); 733 } 734 735 setBuilderNode(rootNode: BuilderNode<Params[]> | null): void{ 736 this.rootNode = rootNode; 737 } 738 739 getBuilderNode(): BuilderNode<[Params]> | undefined | null{ 740 return this.rootNode; 741 } 742 743 updateNode(arg: Object): void { 744 this.rootNode?.update(arg); 745 } 746 getEmbedId() : string { 747 return this.embedId_; 748 } 749 750 setDestroy(isDestroy : boolean) : void { 751 this.isDestroy_ = isDestroy; 752 if (this.isDestroy_) { 753 this.rootNode = null; 754 } 755 } 756 757 postEvent(event: TouchEvent | undefined) : boolean { 758 return this.rootNode?.postTouchEvent(event) as boolean 759 } 760 } 761 762 @Component 763 struct VideoComponent { 764 @ObjectLink params: Params 765 @State bkColor: Color = Color.Red 766 mXComponentController: XComponentController = new XComponentController(); 767 @State player_changed: boolean = false; 768 player?: AVPlayerDemo; 769 770 build() { 771 Column() { 772 Button(this.params.textOne) 773 774 XComponent({ id: 'video_player_id', type: XComponentType.SURFACE, controller: this.mXComponentController}) 775 .border({width: 1, color: Color.Red}) 776 .onLoad(() => { 777 this.player = new AVPlayerDemo(); 778 this.player.setSurfaceID(this.mXComponentController.getXComponentSurfaceId()); 779 this.player_changed = !this.player_changed; 780 this.player.avPlayerLiveDemo() 781 }) 782 .width(300) 783 .height(200) 784 } 785 // The width and height of the outermost custom container component must be the same as those of the tag at the same layer. 786 .width(this.params.width) 787 .height(this.params.height) 788 } 789 } 790 // In @Builder, add the specific dynamic component content. 791 @Builder 792 function VideoBuilder(params: Params) { 793 VideoComponent({ params: params }) 794 .backgroundColor(Color.Gray) 795 } 796 797 @Entry 798 @Component 799 struct WebIndex { 800 browserTabController: WebviewController = new webview.WebviewController() 801 private nodeControllerMap: Map<string, MyNodeController> = new Map(); 802 @State componentIdArr: Array<string> = []; 803 804 aboutToAppear() { 805 // Enable web frontend page debugging. 806 webview.WebviewController.setWebDebuggingAccess(true); 807 } 808 809 build(){ 810 Row() { 811 Column() { 812 Stack() { 813 ForEach(this.componentIdArr, (componentId: string) => { 814 NodeContainer(this.nodeControllerMap.get(componentId)) 815 }, (embedId: string) => embedId) 816 // Load the local test.html page. 817 Web({ src: $rawfile("test.html"), controller: this.browserTabController }) 818 // Enable same-layer rendering. 819 .enableNativeEmbedMode(true) 820 // Obtain the lifecycle change data of the embed tag. 821 .onNativeEmbedLifecycleChange((embed) => { 822 console.log("NativeEmbed surfaceId" + embed.surfaceId); 823 // 1. If embed.info.id is used as the key for mapping nodeController, explicitly specify the ID on the HTML5 page. 824 const componentId = embed.info?.id?.toString() as string 825 if (embed.status == NativeEmbedStatus.CREATE) { 826 console.log("NativeEmbed create" + JSON.stringify(embed.info)) 827 // Create a NodeController instance, set parameters, and rebuild. 828 let nodeController = new MyNodeController() 829 // 1. The unit of embed.info.width and embed.info.height is px, which needs to be converted to the default unit vp on the eTS side. 830 nodeController.setRenderOption({surfaceId : embed.surfaceId as string, type : embed.info?.type as string, 831 renderType : NodeRenderType.RENDER_TYPE_TEXTURE, embedId : embed.embedId as string, 832 width : px2vp(embed.info?.width), height : px2vp(embed.info?.height)}) 833 nodeController.setDestroy(false); 834 // Save the nodeController instance to the Map, with the Id attribute of the embed tag passed in by the Web component as the key. 835 this.nodeControllerMap.set(componentId, nodeController) 836 // Save the Id attribute of the embed tag passed in by the Web component to the @State decorated variable for dynamically creating a nodeContainer. The push action must be executed after the set action. 837 this.componentIdArr.push(componentId) 838 } else if (embed.status == NativeEmbedStatus.UPDATE) { 839 let nodeController = this.nodeControllerMap.get(componentId) 840 nodeController?.updateNode({textOne: 'update', width: px2vp(embed.info?.width), height: px2vp(embed.info?.height)} as ESObject) 841 } else { 842 let nodeController = this.nodeControllerMap.get(componentId); 843 nodeController?.setDestroy(true) 844 this.nodeControllerMap.clear(); 845 this.componentIdArr.length = 0; 846 } 847 })// Obtain the touch event information of components for same-layer rendering. 848 .onNativeEmbedGestureEvent((touch) => { 849 console.log("NativeEmbed onNativeEmbedGestureEvent" + JSON.stringify(touch.touchEvent)); 850 this.componentIdArr.forEach((componentId: string) => { 851 let nodeController = this.nodeControllerMap.get(componentId) 852 // Send the obtained event of the region at the same layer to the nodeController corresponding to embedId of the region. 853 if (nodeController?.getEmbedId() === touch.embedId) { 854 let ret = nodeController?.postEvent(touch.touchEvent) 855 if (ret) { 856 console.log("onNativeEmbedGestureEvent success " + componentId) 857 } else { 858 console.log("onNativeEmbedGestureEvent fail " + componentId) 859 } 860 if (touch.result) { 861 // Notify the Web component of the gesture event consumption result. 862 touch.result.setGestureEventResult(ret); 863 } 864 } 865 }) 866 }) 867 } 868 } 869 } 870 } 871 } 872 ``` 873 874- Example of video playback code on the application side. Replace the video URL with the correct one in practice. 875 876 ```ts 877 // HAP's src/main/ets/pages/PlayerDemo.ets 878 import { media } from '@kit.MediaKit'; 879 import { BusinessError } from '@ohos.base'; 880 881 export class AVPlayerDemo { 882 private count: number = 0; 883 private surfaceID: string = ''; // The surfaceID parameter specifies the window used to display the video. Its value is obtained through XComponent. 884 private isSeek: boolean = true; // Specify whether the seek operation is supported. 885 886 setSurfaceID(surface_id: string){ 887 console.log('setSurfaceID : ' + surface_id); 888 this.surfaceID = surface_id; 889 } 890 // Set AVPlayer callback functions. 891 setAVPlayerCallback(avPlayer: media.AVPlayer) { 892 // Callback function for the seek operation. 893 avPlayer.on('seekDone', (seekDoneTime: number) => { 894 console.info(`AVPlayer seek succeeded, seek time is ${seekDoneTime}`); 895 }) 896 // Callback function for errors. If an error occurs during the operation on the AVPlayer, reset() is called to reset the AVPlayer. 897 avPlayer.on('error', (err: BusinessError) => { 898 console.error(`Invoke avPlayer failed, code is ${err.code}, message is ${err.message}`); 899 avPlayer.reset(); 900 }) 901 // Callback for state changes. 902 avPlayer.on('stateChange', async (state: string, reason: media.StateChangeReason) => { 903 switch (state) { 904 case 'idle': // This state is reported upon a successful callback of reset(). 905 console.info('AVPlayer state idle called.'); 906 avPlayer.release(); // Call release() to release the instance. 907 break; 908 case 'initialized': // This state is reported when the AVPlayer sets the playback source. 909 console.info('AVPlayer state initialized called.'); 910 avPlayer.surfaceId = this.surfaceID; // Set the window to display the video. This setting is not required when a pure audio asset is to be played. 911 avPlayer.prepare(); 912 break; 913 case 'prepared': // This state is reported upon a successful callback of prepare(). 914 console.info('AVPlayer state prepared called.'); 915 avPlayer.play(); // Call play() to start playback. 916 break; 917 case 'playing': // This state is reported upon a successful callback of play(). 918 console.info('AVPlayer state prepared called.'); 919 if(this.count !== 0) { 920 if (this.isSeek) { 921 console.info('AVPlayer start to seek.'); 922 avPlayer.seek(avPlayer.duration); // Call seek() to seek to the end of the video clip. 923 } else { 924 // When the seek operation is not supported, the playback continues until it reaches the end. 925 console.info('AVPlayer wait to play end.'); 926 } 927 } else { 928 avPlayer.pause(); // Call pause() to pause the playback. 929 } 930 this.count++; 931 break; 932 case 'paused': // This state is reported upon a successful callback of pause(). 933 console.info('AVPlayer state paused called.'); 934 avPlayer.play(); // Call play() again to start playback. 935 break; 936 case 'completed': // This state is reported upon the completion of the playback. 937 console.info('AVPlayer state paused called.'); 938 avPlayer.stop(); // Call stop() to stop the playback. 939 break; 940 case 'stopped': // This state is reported upon a successful callback of stop(). 941 console.info('AVPlayer state stopped called.'); 942 avPlayer.reset(); // Call reset() to reset the AVPlayer. 943 break; 944 case 'released': // This state is reported upon the release of the AVPlayer. 945 console.info('AVPlayer state released called.'); 946 break; 947 default: 948 break; 949 } 950 }) 951 } 952 953 // Set the live stream source through the URL. 954 async avPlayerLiveDemo(){ 955 // Create an AVPlayer instance. 956 let avPlayer: media.AVPlayer = await media.createAVPlayer(); 957 // Set a callback for state changes. 958 this.setAVPlayerCallback(avPlayer); 959 this.isSeek = false; // The seek operation is not supported. 960 // Replace the URL with the actual URL of the video source. 961 avPlayer.url = 'https://xxx.xxx/demo.mp4'; 962 } 963 } 964 ``` 965 966- Example of the frontend page: 967 968 ```html 969 <!--HAP's src/main/resources/rawfile/test.html--> 970 <!DOCTYPE html> 971 <html> 972 <head> 973 <title>Same-Layer Rendering Test HTML</title> 974 <meta name="viewport"> 975 </head> 976 <body> 977 <div> 978 <div id="bodyId"> 979 <embed id="nativeVideo" type = "native/video" width="1000" height="1500" src="test" style = "background-color:red"/> 980 </div> 981 </div> 982 </body> 983 </html> 984 ``` 985 986  987