1# Application Window Development (Stage Model) 2 3 4## Basic Concepts 5 6- Immersive window: a window display mode where the system windows (generally the status bar and navigation bar) are hidden to allow users to fully engage with the content. 7 8 The immersive window feature is applicable only to the main window of an application in full-screen mode. It does not apply to a main window in freeform window mode or a subwindow (for example, a dialog box or a floating window). 9 10- Floating window: a special application window that can still be displayed in the foreground when the main window and corresponding ability are running in the background. 11 12 The floating window can be used to continue playing a video after the application is switched to the background, or offer a quick entry (for example, bubbles) to the application. Before creating a floating window, an application must apply for the required permission. 13 14 15## When to Use 16 17In the stage model, you can perform the following operations during application window development: 18 19- Setting the properties and content of the main window of an application 20 21- Setting the properties and content of the subwindow of an application 22 23- Experiencing the immersive window feature 24 25- Setting a floating window 26 27- Listening for interactive and non-interactive window events 28 29## Available APIs 30 31The table below lists the common APIs used for application window development. For details about more APIs, see [Window](../reference/apis-arkui/js-apis-window.md). 32 33| Instance | API | Description | 34| -------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | 35| WindowStage | getMainWindow(callback: AsyncCallback<Window>): void | Obtains the main window of this window stage.<br>This API can be used only in the stage model.| 36| WindowStage | loadContent(path: string, callback: AsyncCallback<void>): void | Loads content to the main window in this window stage.<br>**path**: path of the page from which the content will be loaded. The path is configured in the **main_pages.json** file of the project.<br>This API can be used only in the stage model.| 37| WindowStage | createSubWindow(name: string, callback: AsyncCallback<Window>): void | Creates a subwindow.<br>This API can be used only in the stage model. | 38| WindowStage | on(type: 'windowStageEvent', callback: Callback<WindowStageEventType>): void | Subscribes to window stage lifecycle change events.<br>This API can be used only in the stage model.| 39| Window static method| createWindow(config: Configuration, callback: AsyncCallback\<Window>): void | Creates a subwindow or system window.<br>**config**: parameters used for creating the window. | 40| Window | setUIContent(path: string, callback: AsyncCallback<void>): void | Loads the content of a page, with its path in the current project specified, to this window.<br>**path**: path of the page from which the content will be loaded. The path is configured in the **main_pages.json** file of the project in the stage model. | 41| Window | setWindowBrightness(brightness: number, callback: AsyncCallback<void>): void | Sets the brightness for this window. | 42| Window | setWindowTouchable(isTouchable: boolean, callback: AsyncCallback<void>): void | Sets whether this window is touchable. | 43| Window | moveWindowTo(x: number, y: number, callback: AsyncCallback<void>): void | Moves this window. | 44| Window | resize(width: number, height: number, callback: AsyncCallback<void>): void | Changes the window size. | 45| Window | setWindowLayoutFullScreen(isLayoutFullScreen: boolean): Promise<void> | Sets whether to enable the full-screen mode for the window layout. | 46| Window | setWindowSystemBarEnable(names: Array<'status'\|'navigation'>): Promise<void> | Sets whether to display the status bar and navigation bar in this window. | 47| Window | setWindowSystemBarProperties(systemBarProperties: SystemBarProperties): Promise<void> | Sets the properties of the status bar and navigation bar in this window.<br>**systemBarProperties**: properties of the status bar and navigation bar.| 48| Window | showWindow(callback: AsyncCallback\<void>): void | Shows this window. | 49| Window | on(type: 'touchOutside', callback: Callback<void>): void | Subscribes to touch events outside this window. | 50| Window | destroyWindow(callback: AsyncCallback<void>): void | Destroys this window. | 51 52 53## Setting the Main Window of an Application 54 55In the stage model, the main window of an application is created and maintained by a **UIAbility** instance. In the **onWindowStageCreate** callback of the **UIAbility** instance, use **WindowStage** to obtain the main window of the application and set its properties. You can also set the properties (for example, **maxWindowWidth**) in the [abilities tag of the module.json5 file](../quick-start/module-configuration-file.md#abilities). 56 57### How to Develop 58 591. Obtain the main window. 60 61 Call **getMainWindow** to obtain the main window of the application. 62 632. Set the properties of the main window. 64 65 You can set multiple properties of the main window, such as the background color, brightness, and whether the main window is touchable. The code snippet below uses the **touchable** property as an example. 66 673. Load content to the main window. 68 69 Call **loadContent** to load content to the main window. 70 71```ts 72import { UIAbility } from '@kit.AbilityKit'; 73import { window } from '@kit.ArkUI'; 74import { BusinessError } from '@kit.BasicServicesKit'; 75 76export default class EntryAbility extends UIAbility { 77 onWindowStageCreate(windowStage: window.WindowStage) { 78 // 1. Obtain the main window of the application. 79 let windowClass: window.Window | null = null; 80 windowStage.getMainWindow((err: BusinessError, data) => { 81 let errCode: number = err.code; 82 if (errCode) { 83 console.error('Failed to obtain the main window. Cause: ' + JSON.stringify(err)); 84 return; 85 } 86 windowClass = data; 87 console.info('Succeeded in obtaining the main window. Data: ' + JSON.stringify(data)); 88 // 2. Set the touchable property of the main window. 89 let isTouchable: boolean = true; 90 windowClass.setWindowTouchable(isTouchable, (err: BusinessError) => { 91 let errCode: number = err.code; 92 if (errCode) { 93 console.error('Failed to set the window to be touchable. Cause:' + JSON.stringify(err)); 94 return; 95 } 96 console.info('Succeeded in setting the window to be touchable.'); 97 }) 98 }) 99 // 3. Load content to the main window. 100 windowStage.loadContent("pages/page2", (err: BusinessError) => { 101 let errCode: number = err.code; 102 if (errCode) { 103 console.error('Failed to load the content. Cause:' + JSON.stringify(err)); 104 return; 105 } 106 console.info('Succeeded in loading the content.'); 107 }); 108 } 109}; 110``` 111 112## Setting a Subwindow of an Application 113 114You can create an application subwindow, such as a dialog box, and set its properties. 115 116> **NOTE** 117> 118> Due to the following limitations, using subwindows is not recommended in mobile device scenarios. Instead, you are advised to use the [overlay](../reference/apis-arkui/arkui-ts/ts-universal-attributes-overlay.md) capability of components. 119> - Subwindows on mobile devices are constrained within the main window's boundaries, mirroring the limitations of components. 120> - In split-screen or freeform window mode, components, when compared with subwindows, offer better real-time adaptability to changes in the main window's position and size. 121> - On certain platforms, system configurations may restrict subwindows to default system animations and rounded shadows, offering no customization options for applications and thereby limiting their versatility. 122 123### How to Develop 124 1251. Create a subwindow. 126 127 Call **createSubWindow** to create a subwindow. 128 1292. Set the properties of the subwindow. 130 131 After the subwindow is created, you can set its properties, such as the size, position, background color, and brightness. 132 1333. Load content to and show the subwindow. 134 135 Call **setUIContent** to load content to the subwindow and **showWindow** to show the subwindow. 136 1374. Destroy the subwindow. 138 139 When the subwindow is no longer needed, you can call **destroyWindow** to destroy it. 140 141The code snippet for creating a subwindow in **onWindowStageCreate** is as follows: 142 143```ts 144import { UIAbility } from '@kit.AbilityKit'; 145import { window } from '@kit.ArkUI'; 146import { BusinessError } from '@kit.BasicServicesKit'; 147 148let windowStage_: window.WindowStage | null = null; 149let sub_windowClass: window.Window | null = null; 150 151export default class EntryAbility extends UIAbility { 152 showSubWindow() { 153 // 1. Create a subwindow. 154 if (windowStage_ == null) { 155 console.error('Failed to create the subwindow. Cause: windowStage_ is null'); 156 } 157 else { 158 windowStage_.createSubWindow("mySubWindow", (err: BusinessError, data) => { 159 let errCode: number = err.code; 160 if (errCode) { 161 console.error('Failed to create the subwindow. Cause: ' + JSON.stringify(err)); 162 return; 163 } 164 sub_windowClass = data; 165 console.info('Succeeded in creating the subwindow. Data: ' + JSON.stringify(data)); 166 // 2. Set the position, size, and other properties of the subwindow. 167 sub_windowClass.moveWindowTo(300, 300, (err: BusinessError) => { 168 let errCode: number = err.code; 169 if (errCode) { 170 console.error('Failed to move the window. Cause:' + JSON.stringify(err)); 171 return; 172 } 173 console.info('Succeeded in moving the window.'); 174 }); 175 sub_windowClass.resize(500, 500, (err: BusinessError) => { 176 let errCode: number = err.code; 177 if (errCode) { 178 console.error('Failed to change the window size. Cause:' + JSON.stringify(err)); 179 return; 180 } 181 console.info('Succeeded in changing the window size.'); 182 }); 183 // 3.1 Load content to the subwindow. 184 sub_windowClass.setUIContent("pages/page3", (err: BusinessError) => { 185 let errCode: number = err.code; 186 if (errCode) { 187 console.error('Failed to load the content. Cause:' + JSON.stringify(err)); 188 return; 189 } 190 console.info('Succeeded in loading the content.'); 191 // 3.2 Show the subwindow. 192 (sub_windowClass as window.Window).showWindow((err: BusinessError) => { 193 let errCode: number = err.code; 194 if (errCode) { 195 console.error('Failed to show the window. Cause: ' + JSON.stringify(err)); 196 return; 197 } 198 console.info('Succeeded in showing the window.'); 199 }); 200 }); 201 }) 202 } 203 } 204 205 destroySubWindow() { 206 // 4. Destroy the subwindow when it is no longer needed (depending on the service logic). 207 (sub_windowClass as window.Window).destroyWindow((err: BusinessError) => { 208 let errCode: number = err.code; 209 if (errCode) { 210 console.error('Failed to destroy the window. Cause: ' + JSON.stringify(err)); 211 return; 212 } 213 console.info('Succeeded in destroying the window.'); 214 }); 215 } 216 217 onWindowStageCreate(windowStage: window.WindowStage) { 218 windowStage_ = windowStage; 219 // Create a subwindow when it is needed, for example, when a touch event occurs in the main window. Calling onWindowStageCreate is not always necessary. The code here is for reference only. 220 this.showSubWindow(); 221 } 222 223 onWindowStageDestroy() { 224 // Destroy the subwindow when it is no longer needed, for example, when the Close button in the subwindow is touched. Calling onWindowStageDestroy is not always necessary. The code here is for reference only. 225 this.destroySubWindow(); 226 } 227}; 228``` 229 230You can also click a button on a page to create a subwindow. The code snippet is as follows: 231 232```ts 233// EntryAbility.ets 234onWindowStageCreate(windowStage: window.WindowStage) { 235 windowStage.loadContent('pages/Index', (err) => { 236 if (err.code) { 237 console.error('Failed to load the content. Cause:' + JSON.stringify(err)); 238 return; 239 } 240 console.info('Succeeded in loading the content.'); 241 }) 242 243 // Transfer the window stage to the Index page. 244 AppStorage.setOrCreate('windowStage', windowStage); 245} 246``` 247 248```ts 249// Index.ets 250import { window } from '@kit.ArkUI'; 251import { BusinessError } from '@kit.BasicServicesKit'; 252 253let windowStage_: window.WindowStage | undefined = undefined; 254let sub_windowClass: window.Window | undefined = undefined; 255@Entry 256@Component 257struct Index { 258 @State message: string = 'Hello World'; 259 private CreateSubWindow(){ 260 // Obtain the window stage. 261 windowStage_ = AppStorage.get('windowStage'); 262 // 1. Create a subwindow. 263 if (windowStage_ == null) { 264 console.error('Failed to create the subwindow. Cause: windowStage_ is null'); 265 } 266 else { 267 windowStage_.createSubWindow("mySubWindow", (err: BusinessError, data) => { 268 let errCode: number = err.code; 269 if (errCode) { 270 console.error('Failed to create the subwindow. Cause: ' + JSON.stringify(err)); 271 return; 272 } 273 sub_windowClass = data; 274 console.info('Succeeded in creating the subwindow. Data: ' + JSON.stringify(data)); 275 // 2. Set the position, size, and other properties of the subwindow. 276 sub_windowClass.moveWindowTo(300, 300, (err: BusinessError) => { 277 let errCode: number = err.code; 278 if (errCode) { 279 console.error('Failed to move the window. Cause:' + JSON.stringify(err)); 280 return; 281 } 282 console.info('Succeeded in moving the window.'); 283 }); 284 sub_windowClass.resize(500, 500, (err: BusinessError) => { 285 let errCode: number = err.code; 286 if (errCode) { 287 console.error('Failed to change the window size. Cause:' + JSON.stringify(err)); 288 return; 289 } 290 console.info('Succeeded in changing the window size.'); 291 }); 292 // 3. Load content to the subwindow. 293 sub_windowClass.setUIContent("pages/subWindow", (err: BusinessError) => { 294 let errCode: number = err.code; 295 if (errCode) { 296 console.error('Failed to load the content. Cause:' + JSON.stringify(err)); 297 return; 298 } 299 console.info('Succeeded in loading the content.'); 300 // 3. Show the subwindow. 301 (sub_windowClass as window.Window).showWindow((err: BusinessError) => { 302 let errCode: number = err.code; 303 if (errCode) { 304 console.error('Failed to show the window. Cause: ' + JSON.stringify(err)); 305 return; 306 } 307 console.info('Succeeded in showing the window.'); 308 }); 309 }); 310 }) 311 } 312 } 313 private destroySubWindow(){ 314 // 4. Destroy the subwindow when it is no longer needed (depending on the service logic). 315 (sub_windowClass as window.Window).destroyWindow((err: BusinessError) => { 316 let errCode: number = err.code; 317 if (errCode) { 318 console.error('Failed to destroy the window. Cause: ' + JSON.stringify(err)); 319 return; 320 } 321 console.info('Succeeded in destroying the window.'); 322 }); 323 } 324 build() { 325 Row() { 326 Column() { 327 Text(this.message) 328 .fontSize(50) 329 .fontWeight(FontWeight.Bold) 330 Button(){ 331 Text('CreateSubWindow') 332 .fontSize(24) 333 .fontWeight(FontWeight.Normal) 334 }.width(220).height(68) 335 .margin({left:10, top:60}) 336 .onClick(() => { 337 this.CreateSubWindow() 338 }) 339 Button(){ 340 Text('destroySubWindow') 341 .fontSize(24) 342 .fontWeight(FontWeight.Normal) 343 }.width(220).height(68) 344 .margin({left:10, top:60}) 345 .onClick(() => { 346 this.destroySubWindow() 347 }) 348 } 349 .width('100%') 350 } 351 .height('100%') 352 } 353} 354``` 355 356```ts 357// subWindow.ets 358@Entry 359@Component 360struct SubWindow { 361 @State message: string = 'Hello subWindow'; 362 build() { 363 Row() { 364 Column() { 365 Text(this.message) 366 .fontSize(50) 367 .fontWeight(FontWeight.Bold) 368 } 369 .width('100%') 370 } 371 .height('100%') 372 } 373} 374``` 375 376## Experiencing the Immersive Window Feature 377 378To create a better video watching and gaming experience, you can use the immersive window feature to hide the status bar and navigation bar. This feature is available only for the main window of an application. Since API version 10, the immersive window has the same size as the full screen by default; its layout is controlled by the component module; the background color of its status bar and navigation bar is transparent, and the text color is black. When an application window calls **setWindowLayoutFullScreen**, with **true** passed in, an immersive window layout is used. If **false** is passed in, a non-immersive window layout is used. 379 380> **NOTE** 381> 382> Currently, immersive UI development supports window-level configuration, but not page-level configuration. If page redirection is required, you can set the immersive mode at the beginning of the page lifecycle, for example, in the **onPageShow** callback, and then restore the default settings when the page exits, for example, in the **onPageHide** callback. 383 384### How to Develop 385 3861. Obtain the main window. 387 388 Call **getMainWindow** to obtain the main window of the application. 389 3902. Implement the immersive effect. You can use either of the following methods: 391 392 - Method 1: When the main window of the application is a full-screen window, call **setWindowSystemBarEnable** to hide the status bar and navigation bar. 393 394 - Method 2: Call **setWindowLayoutFullScreen** to enable the full-screen mode for the main window layout. Call **setWindowSystemBarProperties** to set the opacity, background color, text color, and highlighted icon of the status bar and navigation bar to create a display effect consistent with that of the main window. 395 3963. Load content to the immersive window. 397 398 Call **loadContent** to load content to the immersive window. 399 400```ts 401import { UIAbility } from '@kit.AbilityKit'; 402import { window } from '@kit.ArkUI'; 403import { BusinessError } from '@kit.BasicServicesKit'; 404 405export default class EntryAbility extends UIAbility { 406 onWindowStageCreate(windowStage: window.WindowStage) { 407 // 1. Obtain the main window of the application. 408 let windowClass: window.Window | null = null; 409 windowStage.getMainWindow((err: BusinessError, data) => { 410 let errCode: number = err.code; 411 if (errCode) { 412 console.error('Failed to obtain the main window. Cause: ' + JSON.stringify(err)); 413 return; 414 } 415 windowClass = data; 416 console.info('Succeeded in obtaining the main window. Data: ' + JSON.stringify(data)); 417 418 // 2. Implement the immersive effect by hiding the status bar and navigation bar. 419 let names: Array<'status' | 'navigation'> = []; 420 windowClass.setWindowSystemBarEnable(names) 421 .then(() => { 422 console.info('Succeeded in setting the system bar to be visible.'); 423 }) 424 .catch((err: BusinessError) => { 425 console.error('Failed to set the system bar to be visible. Cause:' + JSON.stringify(err)); 426 }); 427 // 2. Alternatively, implement the immersive effect by setting the properties of the status bar and navigation bar. 428 let isLayoutFullScreen = true; 429 windowClass.setWindowLayoutFullScreen(isLayoutFullScreen) 430 .then(() => { 431 console.info('Succeeded in setting the window layout to full-screen mode.'); 432 }) 433 .catch((err: BusinessError) => { 434 console.error('Failed to set the window layout to full-screen mode. Cause:' + JSON.stringify(err)); 435 }); 436 let sysBarProps: window.SystemBarProperties = { 437 statusBarColor: '#ff00ff', 438 navigationBarColor: '#00ff00', 439 // The following properties are supported since API version 8. 440 statusBarContentColor: '#ffffff', 441 navigationBarContentColor: '#ffffff' 442 }; 443 windowClass.setWindowSystemBarProperties(sysBarProps) 444 .then(() => { 445 console.info('Succeeded in setting the system bar properties.'); 446 }) 447 .catch((err: BusinessError) => { 448 console.error('Failed to set the system bar properties. Cause: ' + JSON.stringify(err)); 449 }); 450 }) 451 // 3. Load content to the immersive window. 452 windowStage.loadContent("pages/page2", (err: BusinessError) => { 453 let errCode: number = err.code; 454 if (errCode) { 455 console.error('Failed to load the content. Cause:' + JSON.stringify(err)); 456 return; 457 } 458 console.info('Succeeded in loading the content.'); 459 }); 460 } 461}; 462``` 463 464<!--RP2--> 465## Setting a Floating Window<!--RP2End--> 466 467A floating window is created based on an existing task. It is always displayed in the foreground, even if the task used for creating the floating window is switched to the background. Generally, the floating window is above all application windows. You can create a floating window and set its properties. 468 469 470### How to Develop 471 472<!--RP1--> 473**Prerequisites**: To create a floating window (a window of the type **WindowType.TYPE_FLOAT**), you must request the **ohos.permission.SYSTEM_FLOAT_WINDOW** permission. For details, see [Requesting Permissions for system_basic Applications](../security/AccessToken/determine-application-mode.md#requesting-permissions-for-system_basic-applications). 474<!--RP1End--> 475 4761. Create a floating window. 477 478 Call **window.createWindow** to create a floating window. 479 4802. Set properties of the floating window. 481 482 After the floating window is created, you can set its properties, such as the size, position, background color, and brightness. 483 4843. Load content to and show the floating window. 485 486 Call **setUIContent** to load content to the floating window and **showWindow** to show the window. 487 4884. Destroy the floating window. 489 490 When the floating window is no longer needed, you can call **destroyWindow** to destroy it. 491 492```ts 493import { UIAbility } from '@kit.AbilityKit'; 494import { window } from '@kit.ArkUI'; 495import { BusinessError } from '@kit.BasicServicesKit'; 496 497export default class EntryAbility extends UIAbility { 498 onWindowStageCreate(windowStage: window.WindowStage) { 499 // 1. Create a floating window. 500 let windowClass: window.Window | null = null; 501 let config: window.Configuration = { 502 name: "floatWindow", windowType: window.WindowType.TYPE_FLOAT, ctx: this.context 503 }; 504 window.createWindow(config, (err: BusinessError, data) => { 505 let errCode: number = err.code; 506 if (errCode) { 507 console.error('Failed to create the floatWindow. Cause: ' + JSON.stringify(err)); 508 return; 509 } 510 console.info('Succeeded in creating the floatWindow. Data: ' + JSON.stringify(data)); 511 windowClass = data; 512 // 2. Set the position, size, and other properties of the floating window. 513 windowClass.moveWindowTo(300, 300, (err: BusinessError) => { 514 let errCode: number = err.code; 515 if (errCode) { 516 console.error('Failed to move the window. Cause:' + JSON.stringify(err)); 517 return; 518 } 519 console.info('Succeeded in moving the window.'); 520 }); 521 windowClass.resize(500, 500, (err: BusinessError) => { 522 let errCode: number = err.code; 523 if (errCode) { 524 console.error('Failed to change the window size. Cause:' + JSON.stringify(err)); 525 return; 526 } 527 console.info('Succeeded in changing the window size.'); 528 }); 529 // 3.1 Load content to the floating window. 530 windowClass.setUIContent("pages/page4", (err: BusinessError) => { 531 let errCode: number = err.code; 532 if (errCode) { 533 console.error('Failed to load the content. Cause:' + JSON.stringify(err)); 534 return; 535 } 536 console.info('Succeeded in loading the content.'); 537 // 3.2 Show the floating window. 538 (windowClass as window.Window).showWindow((err: BusinessError) => { 539 let errCode: number = err.code; 540 if (errCode) { 541 console.error('Failed to show the window. Cause: ' + JSON.stringify(err)); 542 return; 543 } 544 console.info('Succeeded in showing the window.'); 545 }); 546 }); 547 // 4. Destroy the floating window when it is no longer needed (depending on the service logic). 548 windowClass.destroyWindow((err: BusinessError) => { 549 let errCode: number = err.code; 550 if (errCode) { 551 console.error('Failed to destroy the window. Cause: ' + JSON.stringify(err)); 552 return; 553 } 554 console.info('Succeeded in destroying the window.'); 555 }); 556 }); 557 } 558}; 559``` 560 561## Listening for Interactive and Non-Interactive Window Events 562 563When running in the foreground, an application may switch between interactive and non-interactive states and process services depending on the state. For example, when the user opens the **Recents** screen, an application becomes non-interactive and pauses the service interaction with the user, such as video playback or camera preview; when the user switched back to the foreground, the application becomes interactive again, and the paused service needs to be resumed. 564 565### How to Develop 566 567After a **WindowStage** object is created, the application can listen for the **'windowStageEvent'** event to obtain window stage lifecycle changes, for example, whether the window stage is interactive or non-interactive in the foreground. The application can process services based on the reported event status. 568 569```ts 570import { UIAbility } from '@kit.AbilityKit'; 571import { window } from '@kit.ArkUI'; 572 573export default class EntryAbility extends UIAbility { 574 onWindowStageCreate(windowStage: window.WindowStage) { 575 try { 576 windowStage.on('windowStageEvent', (data) => { 577 console.info('Succeeded in enabling the listener for window stage event changes. Data: ' + 578 JSON.stringify(data)); 579 580 // Process services based on the event status. 581 if (data == window.WindowStageEventType.SHOWN) { 582 console.info('current window stage event is SHOWN'); 583 // The application enters the foreground and is interactive by default. 584 // ... 585 } else if (data == window.WindowStageEventType.HIDDEN) { 586 console.info('current window stage event is HIDDEN'); 587 // The application enters the background and is non-interactive by default. 588 // ... 589 } else if (data == window.WindowStageEventType.PAUSED) { 590 console.info('current window stage event is PAUSED'); 591 // The user opens the Recents screen when the application is running in the foreground, and the application becomes non-interactive. 592 // ... 593 } else if (data == window.WindowStageEventType.RESUMED) { 594 console.info('current window stage event is RESUMED'); 595 // The user switches back from the Recents screen to the application, and the application becomes interactive. 596 // ... 597 } 598 599 // ... 600 }); 601 } catch (exception) { 602 console.error('Failed to enable the listener for window stage event changes. Cause:' + 603 JSON.stringify(exception)); 604 } 605 } 606} 607``` 608