DEV Community

HarmonyOS
HarmonyOS

Posted on

Lifecycle of Web Components

Read the original article:Lifecycle of Web Components

Lifecycle of Web Components

Context

You can use Web components to load local or online web pages. The Web components provide various component lifecycle callbacks. Using these callbacks, you can detect the lifecycle changes of Web components and process related services.

The states of a Web component: binding controller to the Web component, the start, progress and end of page loading, and page to be visible. Understanding and properly implementing these lifecycle callbacks is essential for building robust web-based applications in HarmonyOS.

The Web component lifecycle encompasses critical phases from component initialization through page loading completion. This lifecycle management enables developers to:

  • Configure web debugging and security settings during component initialization
  • Control URL loading and implement custom request handling
  • Monitor page loading progress and respond to loading events
  • Handle rendering issues and component cleanup gracefully
  • Optimize performance by leveraging timing-specific callbacks

Proper lifecycle management ensures smooth user experience, efficient resource utilization, and robust error handling in web-enabled HarmonyOS applications.

Description

Web Page Loading States of the Web Component

Component Initialization Phase

aboutToAppear: When a new instance of a custom component is created, execute this function before its build function is executed. In this case, you are advised to configure setWebDebuggingAccess, customizeSchemes, and configCookie.

onControllerAttached: This callback is triggered when the controller is successfully bound to the Web component. Do not call APIs related to the Web component before this callback event. Otherwise, a js-error exception will be thrown. You are advised to inject the JS object using registerJavaScriptProxy and set the custom user agent using setCustomUserAgent in this event. You can use APIs irrelevant to web pages, such as loadUrl and getWebId, in the callback. However, the web page is not loaded when the callback function is called. Therefore, APIs for web page operation, such as zoomIn and zoomOut, cannot be used in the callback function.

URL Loading Control Phase

onLoadIntercept: This callback is triggered before the Web component loads a URL, which is used to determine whether to block the access. By default, the loading is allowed.

onOverrideUrlLoading: This callback is triggered when a URL is to be loaded to the web. By using this callback, the host application can obtain the control right. If the callback returns true, the web stops loading the URL. If the callback returns false, the web continues to load the URL. The behavior of onLoadIntercept() is different from that of the onOverrideUrlLoading() and they are triggered in different timing. Therefore, the two APIs are used in different scenarios. When LoadUrl and iframe (HTML tag, indicating the HTML inline framework element, which is used to embed another page into the current page) are loaded, onLoadIntercept() is triggered, but onOverrideUrlLoading() is not triggered when LoadUrl is loaded and when the iframe loads the HTTP(s) protocol or about:blank.

onInterceptRequest: This callback is triggered before the Web component loads the URL, which is used to intercept the URL and return response data.

Page Loading Progress Phase

onPageBegin: This callback is triggered when a web page starts to be loaded and is triggered only in the main frame (an HTML element used to display the HTML page). This callback is not triggered when the content of an iframe or frameset (an HTML tag used to include frames) is loaded. Multi-frame pages may start to be loaded at the same time. Even if the main frame is already loaded, the sub-frames may start to be loaded or continue to be loaded. This callback is not triggered for navigation (such as segment and historical status) on the same page, navigation that fails before submission, or navigation that is canceled.

onProgressChange: This callback is triggered to notify the loading progress of the page. Multi-frame pages or sub-frames may continue to be loaded while the main frame is already loaded. Therefore, this callback event may still be received after onPageEnd event.

onPageEnd: This callback is triggered only in the main frame when a web page is already loaded. Multi-frame pages may start to be loaded at the same time. Even if the main frame is already loaded, the sub-frames may start to be loaded or continue to be loaded. This callback is not triggered for navigation (such as segment and historical status) on the same page, navigation that fails before submission, or navigation that is canceled. You are advised to execute the JavaScript script loadUrl in this callback. Note that receiving this callback does not guarantee that the next frame of drawn by the Web will reflect the current DOM status.

Page Visibility and Cleanup Phase

onPageVisible: Web callback event, which is triggered when the body of an HTTP response starts to be loaded and a new page is about to be visible in the rendering process. In this case, the document loading is still in the early stage, so the linked resources such as online CSS and images may not be available.

onRenderExited: This callback is triggered when an application rendering process exits abnormally. You can release system resources and save data in this callback. If you want to recover the application, call the loadUrl API to reload the page.

onDisAppear: This callback is triggered when a component is uninstalled from the component tree, which is a common event.

Performance Indicators of Web Component Page Loading

Pay attention to some important performance indicators during web page loading. Such as First Contentful Paint (FCP), First Meaningful Paint (FMP), and Largest Contentful Paint (LCP). The Web component provides the following APIs:

onFirstContentfulPaint: This callback is triggered to notify the time when the web page content such as a text, non-blank Canvas, an image or SVG is rendered for the first time.

onFirstMeaningfulPaint: This callback is triggered to notify the time when the meaningful web page content is rendered for the first time.

onLargestContentfulPaint: This callback is triggered to notify the time when the largest web page content is rendered.

Solution / Approach

1. Complete Web Component Implementation

The following code demonstrates comprehensive lifecycle callback implementation:

// xxx.ets
import { webview } from '@kit.ArkWeb';
import { BusinessError } from '@kit.BasicServicesKit';
import { promptAction } from '@kit.ArkUI';

@Entry
@Component
struct WebComponent {
  controller: webview.WebviewController = new webview.WebviewController();
  responseWeb: WebResourceResponse = new WebResourceResponse();
  heads: Header[] = new Array();
  @State webData: string = "<!DOCTYPE html>\n" +
    "<html>\n" +
    "<head>\n" +
    "<title>intercept test</title>\n" +
    "</head>\n" +
    "<body>\n" +
    "<h1>intercept test</h1>\n" +
    "</body>\n" +
    "</html>";

  aboutToAppear(): void {
    try {
      webview.WebviewController.setWebDebuggingAccess(true);
    } catch (error) {
      console.error(`ErrorCode: ${(error as BusinessError).code},  Message: ${(error as BusinessError).message}`);
    }
  }

  build() {
    Column() {
      Web({ src: $rawfile('index.html'), controller: this.controller })
        .onControllerAttached(() => {
          // You are advised to use **loadUrl**, set a custom user agent, and inject a JS object.
          console.log('onControllerAttached execute')
        })
        .onLoadIntercept((event) => {
          if (event) {
            console.log('onLoadIntercept url:' + event.data.getRequestUrl())
            console.log('url:' + event.data.getRequestUrl())
            console.log('isMainFrame:' + event.data.isMainFrame())
            console.log('isRedirect:' + event.data.isRedirect())
            console.log('isRequestGesture:' + event.data.isRequestGesture())
          }
          // If true is returned, the loading is blocked. Otherwise, the loading is allowed.
          return true
        })
        .onOverrideUrlLoading((webResourceRequest: WebResourceRequest) => {
          if (webResourceRequest && webResourceRequest.getRequestUrl() == "about:blank") {
            return true;
          }
          return false;
        })
        .onInterceptRequest((event) => {
          if (event) {
            console.log('url:' + event.request.getRequestUrl());
          }
          let head1: Header = {
            headerKey: "Connection",
            headerValue: "keep-alive"
          }
          let head2: Header = {
            headerKey: "Cache-Control",
            headerValue: "no-cache"
          }
          let length = this.heads.push(head1);
          length = this.heads.push(head2);
          this.responseWeb.setResponseHeader(this.heads);
          this.responseWeb.setResponseData(this.webData);
          this.responseWeb.setResponseEncoding('utf-8');
          this.responseWeb.setResponseMimeType('text/html');
          this.responseWeb.setResponseCode(200);
          this.responseWeb.setReasonMessage('OK');
          // If response data is returned, the data is loaded based on the response data. If no response data is returned, null is returned, indicating that the data is loaded in the original mode.
          return this.responseWeb;
        })
        .onPageBegin((event) => {
          if (event) {
            console.log('onPageBegin url:' + event.url);
          }
        })
        .onFirstContentfulPaint(event => {
          if (event) {
            console.log("onFirstContentfulPaint:" + "[navigationStartTick]:" +
            event.navigationStartTick + ", [firstContentfulPaintMs]:" +
            event.firstContentfulPaintMs);
          }
        })
        .onProgressChange((event) => {
          if (event) {
            console.log('newProgress:' + event.newProgress);
          }
        })
        .onPageEnd((event) => {
          // You are advised to execute the JavaScript script in this event.
          if (event) {
            console.log('onPageEnd url:' + event.url);
          }
        })
        .onPageVisible((event) => {
          console.log('onPageVisible url:' + event.url);
        })
        .onRenderExited((event) => {
          if (event) {
            console.log('onRenderExited reason:' + event.renderExitReason);
          }
        })
        .onDisAppear(() => {
          promptAction.showToast({
            message: 'The web is hidden',
            duration: 2000
          })
        })
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

2. Frontend HTML Implementation

<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
</head>
<body>
<h1>Hello, ArkWeb</h1>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

3. Advanced Lifecycle Management Patterns

Custom Request Interception
class WebRequestManager {
  private customHeaders: Header[] = [];

  setupCustomHeaders(): void {
    this.customHeaders = [
      { headerKey: "Custom-App", headerValue: "HarmonyOS" },
      { headerKey: "User-Agent", headerValue: "HarmonyOS-WebView/1.0" }
    ];
  }

  handleInterceptRequest(event: any): WebResourceResponse | null {
    if (event && event.request) {
      const url = event.request.getRequestUrl();
      console.log('Intercepting request:', url);

      // Custom logic for specific URLs
      if (url.includes('api/custom')) {
        const response = new WebResourceResponse();
        response.setResponseHeader(this.customHeaders);
        response.setResponseData('{"status":"success","message":"Custom response"}');
        response.setResponseEncoding('utf-8');
        response.setResponseMimeType('application/json');
        response.setResponseCode(200);
        response.setReasonMessage('OK');
        return response;
      }
    }
    return null; // Continue with normal loading
  }
}
Enter fullscreen mode Exit fullscreen mode
Performance Monitoring Integration
class WebPerformanceMonitor {
  private performanceMetrics: PerformanceMetrics = {
    navigationStart: 0,
    firstContentfulPaint: 0,
    firstMeaningfulPaint: 0,
    largestContentfulPaint: 0
  };

  onFirstContentfulPaint(event: any): void {
    if (event) {
      this.performanceMetrics.navigationStart = event.navigationStartTick;
      this.performanceMetrics.firstContentfulPaint = event.firstContentfulPaintMs;

      console.log("Performance Metrics:");
      console.log("Navigation Start:", this.performanceMetrics.navigationStart);
      console.log("First Contentful Paint:", this.performanceMetrics.firstContentfulPaint);

      this.reportMetrics();
    }
  }

  onFirstMeaningfulPaint(event: any): void {
    if (event) {
      this.performanceMetrics.firstMeaningfulPaint = event.firstMeaningfulPaintMs;
      console.log("First Meaningful Paint:", this.performanceMetrics.firstMeaningfulPaint);
    }
  }

  onLargestContentfulPaint(event: any): void {
    if (event) {
      this.performanceMetrics.largestContentfulPaint = event.largestContentfulPaintMs;
      console.log("Largest Contentful Paint:", this.performanceMetrics.largestContentfulPaint);
    }
  }

  private reportMetrics(): void {
    // Send metrics to analytics service
    console.log("Reporting performance metrics:", this.performanceMetrics);
  }
}

interface PerformanceMetrics {
  navigationStart: number;
  firstContentfulPaint: number;
  firstMeaningfulPaint: number;
  largestContentfulPaint: number;
}
Enter fullscreen mode Exit fullscreen mode
Error Recovery and Resource Management
class WebComponentManager {
  private controller: webview.WebviewController;
  private isRecovering: boolean = false;

  constructor(controller: webview.WebviewController) {
    this.controller = controller;
  }

  handleRenderExit(event: any): void {
    if (event && !this.isRecovering) {
      console.log('Render process exited:', event.renderExitReason);
      this.isRecovering = true;

      // Save current state
      this.saveApplicationState();

      // Attempt recovery
      setTimeout(() => {
        this.attemptRecovery();
      }, 1000);
    }
  }

  private saveApplicationState(): void {
    // Save current application data
    console.log('Saving application state before recovery');
  }

  private attemptRecovery(): void {
    try {
      // Reload the page to recover
      this.controller.loadUrl('file:///android_asset/rawfile/index.html');
      this.isRecovering = false;
      console.log('Web component recovery attempted');
    } catch (error) {
      console.error('Recovery failed:', error);
      this.isRecovering = false;
    }
  }

  cleanup(): void {
    // Release resources when component is destroyed
    console.log('Cleaning up web component resources');
  }
}
Enter fullscreen mode Exit fullscreen mode

Key Takeaways

1. Lifecycle Callback Timing and Usage

  • aboutToAppear: Configure web debugging, custom schemes, and cookies before component build
  • onControllerAttached: Inject JavaScript objects, set user agents, and prepare web APIs (avoid web page operations)
  • onLoadIntercept vs onOverrideUrlLoading: Different timing and scenarios - onLoadIntercept for URL blocking, onOverrideUrlLoading for URL handling control
  • onPageBegin/onPageEnd: Main frame only events, ideal for JavaScript execution timing
  • onPageVisible: Early visibility notification, resources may not be fully loaded

2. Request and Response Management

  • Custom Headers: Use onInterceptRequest to add custom headers and modify responses
  • Response Interception: Return custom WebResourceResponse objects for specific URLs
  • URL Control: Implement sophisticated URL handling logic using onLoadIntercept and onOverrideUrlLoading
  • Resource Management: Properly configure response encoding, MIME types, and status codes

3. Performance Monitoring Best Practices

  • FCP (First Contentful Paint): Track initial content rendering performance
  • FMP (First Meaningful Paint): Monitor meaningful content loading times
  • LCP (Largest Contentful Paint): Measure largest content element rendering
  • Progress Tracking: Use onProgressChange for loading progress indication
  • Metrics Collection: Implement performance monitoring for optimization insights

4. Error Handling and Recovery Strategies

  • Render Process Recovery: Handle onRenderExited events with automatic recovery mechanisms
  • State Preservation: Save application state before attempting recovery
  • Resource Cleanup: Implement proper cleanup in onDisAppear callback
  • Exception Handling: Wrap Web API calls in try-catch blocks with BusinessError handling

5. Multi-Frame and Navigation Considerations

  • Main Frame Focus: onPageBegin and onPageEnd only trigger for main frame loading
  • Sub-Frame Behavior: Sub-frames may continue loading after main frame completion
  • Navigation Exclusions: Same-page navigation, failed submissions, and canceled navigation don't trigger page events
  • iFrame Handling: Different callback behavior for iFrame vs main frame loading

6. Development and Debugging Guidelines

  • Debug Access: Enable setWebDebuggingAccess in aboutToAppear for development
  • API Timing: Respect callback timing - avoid web page operations before proper initialization
  • Console Logging: Implement comprehensive logging for lifecycle event tracking
  • Error Boundaries: Use proper error handling for all web component interactions

7. Security and Configuration

  • Custom Schemes: Configure custom URL schemes during component initialization
  • User Agent: Set custom user agents in onControllerAttached callback
  • Cookie Management: Handle cookie configuration in aboutToAppear phase
  • Request Validation: Implement request validation in interception callbacks

The effective implementation of Web component lifecycle callbacks enables developers to create robust, performant, and secure web-enabled applications in HarmonyOS while providing excellent user experience through proper error handling and recovery mechanisms.

Written by Mucahid Kincir

Top comments (0)