1# UIExtensionAbility 2 3## Overview 4 5[UIExtensionAbility](../reference/apis-ability-kit/js-apis-app-ability-uiExtensionAbility.md) is an ExtensionAbility component of the UI type. It is usually used in modular development scenarios where process isolation is required, for example, system dialog boxes, status bars, and capsules. There are two forms: embedded and system pop-ups. 6- The UIExtensionAbility in embedded mode must be used together with the [UIExtensionComponent](../reference/apis-arkui/arkui-ts/ts-container-ui-extension-component-sys.md). Specifically, with the UIExtensionComponent, you can embed the UI provided by the UIExtensionAbility of another application into a UIAbility of your application. The UIExtensionAbility runs in a process independent of the UIAbility for UI layout and rendering. 7- To start the UIExtensionAbility in system pop-up mode, call [requestModalUIExtensionAbility](../reference/apis-ability-kit/js-apis-inner-application-serviceExtensionContext-sys.md#serviceextensioncontextrequestmodaluiextension11) or the specified interface encapsulated in the application. 8 9## Constraints 10- Currently, the UIExtensionAbility of the **sys/commonUI**, **sysDialog**, and **sysPicker** types can be used only by system applications. For details about the UIExtensionAbility types and corresponding permission control, see the [module.json5 file](../quick-start/module-configuration-file.md). 11- The UIExtensionAbility can be started only by applications that are running in the foreground. 12 13## Lifecycle 14The [UIExtensionAbility](../reference/apis-ability-kit/js-apis-app-ability-uiExtensionAbility.md) class provides the lifecycle callbacks [onCreate](../reference/apis-ability-kit/js-apis-app-ability-uiExtensionAbility.md#oncreate), [onSessionCreate](../reference/apis-ability-kit/js-apis-app-ability-uiExtensionAbility.md#onsessioncreate), [onSessionDestroy](../reference/apis-ability-kit/js-apis-app-ability-uiExtensionAbility.md#onsessiondestroy), [onForeground](../reference/apis-ability-kit/js-apis-app-ability-uiExtensionAbility.md#onforeground), [onBackground](../reference/apis-ability-kit/js-apis-app-ability-uiExtensionAbility.md#onbackground), and [onDestroy](../reference/apis-ability-kit/js-apis-app-ability-uiExtensionAbility.md#ondestroy). You must override them as required. 15 16- **onCreate**: called to initialize the service logic when a UIExtensionAbility is created. 17- **onSessionCreate**: called when a UIExtensionContentSession instance is created for the UIExtensionAbility. 18- **onSessionDestroy**: called when a UIExtensionContentSession instance is destroyed for the UIExtensionAbility. 19- **onForeground**: called when the UIExtensionAbility is switched from the background to the foreground. 20- **onBackground**: called when the UIExtensionAbility is switched from the foreground to the background. 21- **onDestroy**: called to clear resources when the UIExtensionAbility is destroyed. 22 23## Selecting a Proper Process Model for the UIExtensionAbility 24The [UIExtensionAbility](../reference/apis-ability-kit/js-apis-app-ability-uiExtensionAbility.md) supports the multiton pattern. Each embedded UI corresponds to a UIExtensionAbility instance. In the multiton scenario, the multi-process model is used by default. 25 26When multiple UIExtensionAbility instances exist in an application, these instances can run in independent processes or share one process. They can also be grouped, and each group share one process. You can select a process model based on the **extensionProcessMode** field in the [module.json5](../quick-start/module-configuration-file.md) file. The table below describes the comparison between the process models. 27| Process Model| extensionProcessMode Field Value| Description| 28| --------| --------| --------| 29| One process for all UIExtensionAbility instances in the same bundle|bundle| The UIExtensionAbility instances do not need to communicate with each other across IPCs. Their statuses are dependent and affect each other.| 30| One process for all UIExtensionAbility instances with the same name| type |The UIExtensionAbility instances of the same type are configured in the same process so that your application can manage them by type.| 31| One process for each UIExtensionAbility instance| instance | The UIExtensionAbility instances communicate with each other only across IPCs. Their statuses do not affect each other, which is more secure.| 32### One Process for All UIExtensionAbility Instances in the Bundle 33The [UIExtensionAbility](../reference/apis-ability-kit/js-apis-app-ability-uiExtensionAbility.md) instances of the same bundle are configured in a process. This makes communication between multiple instances easier. However, the status of these instances affects each other. When an instance in the process exits abnormally, all instances in the process exit. 34 35**Figure 1** Bundle-based process model configuration 36 37 38 39The sample code of **Index.ets** is as follows: 40```ts 41import { BusinessError } from '@kit.BasicServicesKit'; 42 43@Entry 44@Component 45struct Index { 46 @State message: string = 'UIExtension UserA'; 47 private myProxy: UIExtensionProxy | undefined = undefined; 48 49 build() { 50 Row() { 51 Column() { 52 Text(this.message) 53 .fontSize(30) 54 .size({ width: '100%', height: '50' }) 55 .fontWeight(FontWeight.Bold) 56 .textAlign(TextAlign.Center) 57 58 UIExtensionComponent( 59 { 60 bundleName: 'com.samples.uiextensionability', 61 abilityName: 'UIExtensionProvider', 62 moduleName: 'entry', 63 parameters: { 64 'ability.want.params.uiExtensionType': 'sys/commonUI', 65 } 66 }) 67 .onRemoteReady((proxy) => { 68 this.myProxy = proxy; 69 }) 70 .onReceive((data) => { 71 this.message = JSON.stringify(data); 72 }) 73 .onTerminated((terminateInfo: TerminationInfo) => { 74 // This callback is triggered when the started UIExtensionAbility is terminated by calling terminateSelfWithResult or terminateSelf. 75 // It returns the result of the UIExtensionAbility's normal exit, including the result code and Want data. 76 this.message = `terminate code: ${terminateInfo.code}, want: ${terminateInfo.want}`; 77 }) 78 .onError((error: BusinessError) => { 79 // This callback is invoked when an error occurs during the running of the started UIExtensionAbility. 80 // It returns the error code and error message when the UIExtensionAbility encounters an exception. 81 this.message = `error code: ${error.code}, error msg: ${error.message}`; 82 }) 83 .offset({ x: 0, y: 10 }) 84 .size({ width: 300, height: 300 }) 85 .border({ 86 width: 5, 87 color: 0x317AF7, 88 radius: 10, 89 style: BorderStyle.Dotted 90 }) 91 92 UIExtensionComponent( 93 { 94 bundleName: 'com.samples.uiextension2', 95 abilityName: 'UIExtensionProviderB', 96 moduleName: 'entry', 97 parameters: { 98 'ability.want.params.uiExtensionType': 'sys/commonUI', 99 } 100 }) 101 .onRemoteReady((proxy) => { 102 this.myProxy = proxy; 103 }) 104 .onReceive((data) => { 105 this.message = JSON.stringify(data); 106 }) 107 .onTerminated((terminateInfo: TerminationInfo) => { 108 // This callback is triggered when the started UIExtensionAbility is terminated by calling terminateSelfWithResult or terminateSelf. 109 // It returns the result of the UIExtensionAbility's normal exit, including the result code and Want data. 110 this.message = `terminate code: ${terminateInfo.code}, want: ${terminateInfo.want}`; 111 }) 112 .onError((error: BusinessError) => { 113 // This callback is invoked when an error occurs during the running of the started UIExtensionAbility. 114 // It returns the error code and error message when the UIExtensionAbility encounters an exception. 115 this.message = `error code: ${error.code}, error msg: ${error.message}`; 116 }) 117 .offset({ x: 0, y: 50 }) 118 .size({ width: 300, height: 300 }) 119 .border({ 120 width: 5, 121 color: 0x317AF7, 122 radius: 10, 123 style: BorderStyle.Dotted 124 }) 125 } 126 .width('100%') 127 } 128 .height('100%') 129 } 130} 131``` 132**Figure 2** Index page generated based on the preceding code 133 134 135 136If this process model is used, the process name format is as follows: 137 138process name [{bundleName}:{UIExtensionAbility type}] 139 140Example: process name [com.ohos.intentexecutedemo:xxx] 141 142**Figure 3** Bundle-based process model 143 144 145 146### One Process for All UIExtensionAbility Instances of the Same Type 147Processes are allocated based on the [UIExtensionAbility](../reference/apis-ability-kit/js-apis-app-ability-uiExtensionAbility.md) type. Multiple UIExtensionAbility instances of the same type are configured in the same process so that your application can manage the instances by type. 148 149**Figure 4** Type-based process model configuration 150 151 152 153The sample code of **Index.ets** is as follows: 154```ts 155import { BusinessError } from '@kit.BasicServicesKit'; 156 157@Entry 158@Component 159struct Index { 160 @State message: string = 'UIExtension User'; 161 private myProxy: UIExtensionProxy | undefined = undefined; 162 163 build() { 164 Row() { 165 Column() { 166 Text(this.message) 167 .fontSize(30) 168 .size({ width: '100%', height: '50' }) 169 .fontWeight(FontWeight.Bold) 170 .textAlign(TextAlign.Center) 171 172 UIExtensionComponent( 173 { 174 bundleName: 'com.samples.uiextensionability', 175 abilityName: 'UIExtensionProviderA', 176 moduleName: 'entry', 177 parameters: { 178 'ability.want.params.uiExtensionType': 'sys/commonUI', 179 } 180 }) 181 .onRemoteReady((proxy) => { 182 this.myProxy = proxy; 183 }) 184 .onReceive((data) => { 185 this.message = JSON.stringify(data); 186 }) 187 .onTerminated((terminateInfo: TerminationInfo) => { 188 // This callback is triggered when the started UIExtensionAbility is terminated by calling terminateSelfWithResult or terminateSelf. 189 // It returns the result of the UIExtensionAbility's normal exit, including the result code and Want data. 190 this.message = `terminate code: ${terminateInfo.code}, want: ${terminateInfo.want}`; 191 }) 192 .onError((error: BusinessError) => { 193 // This callback is invoked when an error occurs during the running of the started UIExtensionAbility. 194 // It returns the error code and error message when the UIExtensionAbility encounters an exception. 195 this.message = `error code: ${error.code}, error msg: ${error.message}`; 196 }) 197 .offset({ x: 0, y: 10 }) 198 .size({ width: 300, height: 300 }) 199 .border({ 200 width: 5, 201 color: 0x317AF7, 202 radius: 10, 203 style: BorderStyle.Dotted 204 }) 205 206 UIExtensionComponent( 207 { 208 bundleName: 'com.samples.uiextensionability', 209 abilityName: 'UIExtensionProviderB', 210 moduleName: 'entry', 211 parameters: { 212 'ability.want.params.uiExtensionType': 'sys/commonUI', 213 } 214 }) 215 .onRemoteReady((proxy) => { 216 this.myProxy = proxy; 217 }) 218 .onReceive((data) => { 219 this.message = JSON.stringify(data); 220 }) 221 .onTerminated((terminateInfo: TerminationInfo) => { 222 // This callback is triggered when the started UIExtensionAbility is terminated by calling terminateSelfWithResult or terminateSelf. 223 // It returns the result of the UIExtensionAbility's normal exit, including the result code and Want data. 224 this.message = `terminate code: ${terminateInfo.code}, want: ${terminateInfo.want}`; 225 }) 226 .onError((error: BusinessError) => { 227 // This callback is invoked when an error occurs during the running of the started UIExtensionAbility. 228 // It returns the error code and error message when the UIExtensionAbility encounters an exception. 229 this.message = `error code: ${error.code}, error msg: ${error.message}`; 230 }) 231 .offset({ x: 0, y: 50 }) 232 .size({ width: 300, height: 300 }) 233 .border({ 234 width: 5, 235 color: 0x317AF7, 236 radius: 10, 237 style: BorderStyle.Dotted 238 }) 239 } 240 .width('100%') 241 } 242 .height('100%') 243 } 244} 245``` 246**Figure 5** Index page generated based on the preceding code 247 248 249 250If this process model is used, the process name format is as follows: 251 252process name [{bundleName}:{UIExtensionAbility name}] 253 254Example: process name [com.ohos.intentexecutedemo:xxx] 255 256**Figure 6** Type-based process model 257 258 259 260### One Process for Each UIExtensionAbility Instance 261Processes are allocated based on the [UIExtensionAbility](../reference/apis-ability-kit/js-apis-app-ability-uiExtensionAbility.md) instance. That is, each UIExtensionAbility instance has an independent process. In this mode, UIExtensionAbility instances can communicate with each other only through IPCs. However, their statuses do not affect each other, improving security. 262 263**Figure 7** Instance-specific process model configuration 264 265 266 267 268The sample code of **Index.ets** is as follows: 269```ts 270import { BusinessError } from '@kit.BasicServicesKit'; 271 272@Entry 273@Component 274struct Index { 275 @State message: string = 'UIExtension User' 276 private myProxy: UIExtensionProxy | undefined = undefined; 277 278 build() { 279 Row() { 280 Column() { 281 Text(this.message) 282 .fontSize(30) 283 .size({ width: '100%', height: '50' }) 284 .fontWeight(FontWeight.Bold) 285 .textAlign(TextAlign.Center) 286 287 UIExtensionComponent( 288 { 289 bundleName: 'com.samples.uiextensionability', 290 abilityName: 'UIExtensionProvider', 291 moduleName: 'entry', 292 parameters: { 293 'ability.want.params.uiExtensionType': 'sys/commonUI', 294 } 295 }) 296 .onRemoteReady((proxy) => { 297 this.myProxy = proxy; 298 }) 299 .onReceive((data) => { 300 this.message = JSON.stringify(data); 301 }) 302 .onTerminated((terminateInfo: TerminationInfo) => { 303 // This callback is triggered when the started UIExtensionAbility is terminated by calling terminateSelfWithResult or terminateSelf. 304 // It returns the result of the UIExtensionAbility's normal exit, including the result code and Want data. 305 this.message = `terminate code: ${terminateInfo.code}, want: ${terminateInfo.want}`; 306 }) 307 .onError((error: BusinessError) => { 308 // This callback is invoked when an error occurs during the running of the started UIExtensionAbility. 309 // It returns the error code and error message when the UIExtensionAbility encounters an exception. 310 this.message = `error code: ${error.code}, error msg: ${error.message}`; 311 }) 312 .offset({ x: 0, y: 10 }) 313 .size({ width: 300, height: 300 }) 314 .border({ 315 width: 5, 316 color: 0x317AF7, 317 radius: 10, 318 style: BorderStyle.Dotted 319 }) 320 321 UIExtensionComponent( 322 { 323 bundleName: 'com.samples.uiextensionability', 324 abilityName: 'UIExtensionProvider', 325 moduleName: 'entry', 326 parameters: { 327 'ability.want.params.uiExtensionType': 'sys/commonUI', 328 } 329 }) 330 .onRemoteReady((proxy) => { 331 this.myProxy = proxy; 332 }) 333 .onReceive((data) => { 334 this.message = JSON.stringify(data); 335 }) 336 .onTerminated((terminateInfo: TerminationInfo) => { 337 // This callback is triggered when the started UIExtensionAbility is terminated by calling terminateSelfWithResult or terminateSelf. 338 // It returns the result of the UIExtensionAbility's normal exit, including the result code and Want data. 339 this.message = `terminate code: ${terminateInfo.code}, want: ${terminateInfo.want}`; 340 }) 341 .onError((error: BusinessError) => { 342 // This callback is invoked when an error occurs during the running of the started UIExtensionAbility. 343 // It returns the error code and error message when the UIExtensionAbility encounters an exception. 344 this.message = `error code: ${error.code}, error msg: ${error.message}`; 345 }) 346 .offset({ x: 0, y: 50 }) 347 .size({ width: 300, height: 300 }) 348 .border({ 349 width: 5, 350 color: 0x317AF7, 351 radius: 10, 352 style: BorderStyle.Dotted 353 }) 354 } 355 .width('100%') 356 } 357 .height('100%') 358 } 359} 360``` 361**Figure 8** Index page generated based on the preceding code 362 363 364 365If this process model is used, the process name format is as follows: 366 367process name [{bundleName}: {UIExtensionAbility type}: {instance suffix}] 368 369Example: process name [com.ohos.intentexecutedemo:xxx:n] 370 371**Figure 9** Instance-specific process model 372 373 374 375The UIExtensionAbility provides related capabilities through the [UIExtensionContext](../reference/apis-ability-kit/js-apis-inner-application-uiExtensionContext.md) and [UIExtensionContentSession](../reference/apis-ability-kit/js-apis-app-ability-uiExtensionContentSession.md). In this document, the started UIExtensionAbility is called the provider, and the [UIExtensionComponent](../reference/apis-arkui/arkui-ts/ts-container-ui-extension-component-sys.md) that starts the UIExtensionAbility is called the client. 376 377## How to Develop 378 379 For details about how to develop a system dialog box, see [requestModalUIExtension](../reference/apis-ability-kit/js-apis-inner-application-serviceExtensionContext-sys.md#serviceextensioncontextrequestmodaluiextension11). 380 381### Developing the UIExtensionAbility Provider 382 383To implement a provider, create a [UIExtensionAbility](../reference/apis-ability-kit/js-apis-app-ability-uiExtensionAbility.md) in DevEco Studio as follows: 384 3851. In the **ets** directory of the **Module** project, right-click and choose **New > Directory** to create a directory named **uiextensionability**. 386 3872. Right-click the **uiextensionability** directory, and choose **New > File** to create a file named **UIExtensionAbility.ets**. 388 3893. Open the **UIExtensionAbility.ets** file and import its dependencies. Customize a class that inherits from **UIExtensionAbility** and implement the lifecycle callbacks [onCreate](../reference/apis-ability-kit/js-apis-app-ability-uiExtensionAbility.md#oncreate), [onSessionCreate](../reference/apis-ability-kit/js-apis-app-ability-uiExtensionAbility.md#onsessioncreate), [onSessionDestroy](../reference/apis-ability-kit/js-apis-app-ability-uiExtensionAbility.md#onsessiondestroy), [onForeground](../reference/apis-ability-kit/js-apis-app-ability-uiExtensionAbility.md#onforeground), [onBackground](../reference/apis-ability-kit/js-apis-app-ability-uiExtensionAbility.md#onbackground), and [onDestroy](../reference/apis-ability-kit/js-apis-app-ability-uiExtensionAbility.md#ondestroy). 390 391 ```ts 392 import { Want, UIExtensionAbility, UIExtensionContentSession } from '@kit.AbilityKit'; 393 394 const TAG: string = '[testTag] UIExtAbility'; 395 396 export default class UIExtAbility extends UIExtensionAbility { 397 onCreate() { 398 console.log(TAG, `onCreate`); 399 } 400 401 onForeground() { 402 console.log(TAG, `onForeground`); 403 } 404 405 onBackground() { 406 console.log(TAG, `onBackground`); 407 } 408 409 onDestroy() { 410 console.log(TAG, `onDestroy`); 411 } 412 413 onSessionCreate(want: Want, session: UIExtensionContentSession) { 414 console.log(TAG, `onSessionCreate, want: ${JSON.stringify(want)}}`); 415 let storage: LocalStorage = new LocalStorage(); 416 storage.setOrCreate('session', session); 417 session.loadContent('pages/Extension', storage); 418 } 419 420 onSessionDestroy(session: UIExtensionContentSession) { 421 console.log(TAG, `onSessionDestroy`); 422 } 423 } 424 ``` 425 4264. Write the entry page file **pages/extension.ets**, which will be loaded in [onSessionCreate](../reference/apis-ability-kit/js-apis-app-ability-uiExtensionAbility.md#onsessioncreate) of the [UIExtensionAbility](../reference/apis-ability-kit/js-apis-app-ability-uiExtensionAbility.md), and declare "pages/Extension" in the **entry\src\main\resources\base\profile\main_pages.json** file. The content of **extension.ets** is as follows: 427 428 ```ts 429 import { UIExtensionContentSession } from '@kit.AbilityKit'; 430 431 const TAG: string = `[testTag] ExtensionPage`; 432 433 @Entry() 434 @Component 435 struct Extension { 436 @State message: string = `UIExtension provider`; 437 localStorage: LocalStorage | undefined = this.getUIContext().getSharedLocalStorage(); 438 private session: UIExtensionContentSession | undefined = this.localStorage?.get<UIExtensionContentSession>('session'); 439 440 onPageShow() { 441 console.info(TAG, 'show'); 442 } 443 444 build() { 445 Row() { 446 Column() { 447 Text(this.message) 448 .fontSize(30) 449 .fontWeight(FontWeight.Bold) 450 .textAlign(TextAlign.Center) 451 452 Button("send data") 453 .width('80%') 454 .type(ButtonType.Capsule) 455 .margin({ top: 20 }) 456 .onClick(() => { 457 this.session?.sendData({ "data": 543321 }); 458 }) 459 460 Button("terminate self") 461 .width('80%') 462 .type(ButtonType.Capsule) 463 .margin({ top: 20 }) 464 .onClick(() => { 465 this.session?.terminateSelf(); 466 this.localStorage?.clear(); 467 }) 468 469 Button("terminate self with result") 470 .width('80%') 471 .type(ButtonType.Capsule) 472 .margin({ top: 20 }) 473 .onClick(() => { 474 this.session?.terminateSelfWithResult({ 475 resultCode: 0, 476 want: { 477 bundleName: "com.example.uiextensiondemo", 478 parameters: { "result": 123456 } 479 } 480 }) 481 }) 482 } 483 } 484 .height('100%') 485 } 486 } 487 ``` 488 4895. Register the [UIExtensionAbility](../reference/apis-ability-kit/js-apis-app-ability-uiExtensionAbility.md) in the [module.json5 file](../quick-start/module-configuration-file.md) of the module. Set **type** to that configured for the UIExtensionAbility and **srcEntry** to the code path of the UIExtensionAbility. The **extensionProcessMode** field identifies the multiton process model. Here, **bundle** is used as an example. 490 491 ```json 492 { 493 "module": { 494 "extensionAbilities": [ 495 { 496 "name": "UIExtensionProvider", 497 "srcEntry": "./ets/uiextensionability/UIExtensionAbility.ets", 498 "description": "UIExtensionAbility", 499 "type": "sys/commonUI", 500 "exported": true, 501 "extensionProcessMode": "bundle" 502 }, 503 ] 504 } 505 } 506 ``` 507## Developing the UIExtensionAbility Client 508 509You can load the [UIExtensionAbility](../reference/apis-ability-kit/js-apis-app-ability-uiExtensionAbility.md) in the application through the [UIExtensionComponent](../reference/apis-arkui/arkui-ts/ts-container-ui-extension-component-sys.md) on the [UIAbility](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md) page. For example, add the following content to the home page file **pages/Index.ets**: 510 511```ts 512import { BusinessError } from '@kit.BasicServicesKit'; 513 514@Entry 515@Component 516struct Index { 517 @State message: string = 'UIExtension User'; 518 private myProxy: UIExtensionProxy | undefined = undefined; 519 520 build() { 521 Row() { 522 Column() { 523 Text(this.message) 524 .fontSize(30) 525 .size({ width: '100%', height: '50' }) 526 .fontWeight(FontWeight.Bold) 527 .textAlign(TextAlign.Center) 528 529 UIExtensionComponent( 530 { 531 bundleName: 'com.example.uiextensiondemo', 532 abilityName: 'UIExtensionProvider', 533 moduleName: 'entry', 534 parameters: { 535 'ability.want.params.uiExtensionType': 'sys/commonUI', 536 } 537 }) 538 .onRemoteReady((proxy) => { 539 this.myProxy = proxy; 540 }) 541 .onReceive((data) => { 542 this.message = JSON.stringify(data); 543 }) 544 .onTerminated((terminateInfo: TerminationInfo) => { 545 // This callback is triggered when the started UIExtensionAbility is terminated by calling terminateSelfWithResult or terminateSelf. 546 // It returns the result of the UIExtensionAbility's normal exit, including the result code and Want data. 547 this.message = `terminate code: ${terminateInfo.code}, want: ${terminateInfo.want}`; 548 }) 549 .onError((error: BusinessError) => { 550 // This callback is invoked when an error occurs during the running of the started UIExtensionAbility. 551 // It returns the error code and error message when the UIExtensionAbility encounters an exception. 552 this.message = `error code: ${error.code}, error msg: ${error.message}`; 553 }) 554 .offset({ x: 0, y: 30 }) 555 .size({ width: 300, height: 300 }) 556 .border({ 557 width: 5, 558 color: 0x317AF7, 559 radius: 10, 560 style: BorderStyle.Dotted 561 }) 562 563 Button("sendData") 564 .type(ButtonType.Capsule) 565 .offset({ x: 0, y: 60 }) 566 .width('80%') 567 .type(ButtonType.Capsule) 568 .margin({ 569 top: 20 570 }) 571 .onClick(() => { 572 this.myProxy?.send({ 573 "data": 123456, 574 "message": "data from component" 575 }) 576 }) 577 } 578 .width('100%') 579 } 580 .height('100%') 581 } 582} 583``` 584