DEV Community

Cover image for Web Cross-Origin Resources, Web Long-Press Menu, Web Request Interception, Screen Recording Prevention, Base64 Image Dimensions
kouwei qing
kouwei qing

Posted on

Web Cross-Origin Resources, Web Long-Press Menu, Web Request Interception, Screen Recording Prevention, Base64 Image Dimensions

[Daily HarmonyOS Next Knowledge] Web Cross-Origin Resources, Web Long-Press Menu, Web Request Interception, Screen Recording Prevention, Base64 Image Dimensions

1. HarmonyOS Web Component Local Resource Cross-Origin Issue

For solutions to cross-origin resource issues, refer to the official documentation: https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/web-cross-origin-V5

Method 1

To enable the Web component to access cross-origin resources, developers should use protocols like http or https instead of file or resource for loading. The replacement URL domain name should be a custom-constructed domain for personal or organizational use to avoid conflicts with actual internet domains. Meanwhile, use the Web component's onInterceptRequest method to intercept and replace local resources.

The following example demonstrates resolving cross-origin access failures for local resources. Here, index.html and js/script.js are placed in the project's rawfile directory. Accessing index.html via the resource protocol will block js/script.js due to cross-origin restrictions. The example uses https://www.example.com/ to replace the resource protocol and leverages the onInterceptRequest interface to replace resources, enabling js/script.js to load successfully and resolve cross-origin blocking.

// main/ets/pages/Index.ets
import { webview } from '@kit.ArkWeb';

@Entry
@Component
struct Index {
  @State message: string = 'Hello World';
  webviewController: webview.WebviewController = new webview.WebviewController();
  // Mapping table for constructed domain names and local files
  schemeMap = new Map([
    ["https://www.example.com/index.html", "index.html"],
    ["https://www.example.com/js/script.js", "js/script.js"],
  ])
  // Mapping table for local files and constructed response MIME types
  mimeTypeMap = new Map([
    ["index.html", 'text/html'],
    ["js/script.js", "text/javascript"]
  ])

  build() {
    Row() {
      Column() {
        // For local index.html, use http or https protocol instead of file or resource protocol, and construct a custom domain name.
        // This example uses www.example.com.
        Web({ src: "https://www.example.com/index.html", controller: this.webviewController })
          .javaScriptAccess(true)
          .fileAccess(true)
          .domStorageAccess(true)
          .geolocationAccess(true)
          .width("100%")
          .height("100%")
          .onInterceptRequest((event) => {
            if (!event) {
              return;
            }
            // Intercept and replace local offline resources to bypass cross-origin restrictions
            if (this.schemeMap.has(event.request.getRequestUrl())) {
              let rawfileName: string = this.schemeMap.get(event.request.getRequestUrl())!;
              let mimeType = this.mimeTypeMap.get(rawfileName);
              if (typeof mimeType === 'string') {
                let response = new WebResourceResponse();
                // Construct response data. If the local file is in rawfile, set it as follows
                response.setResponseData($rawfile(rawfileName));
                response.setResponseEncoding('utf-8');
                response.setResponseMimeType(mimeType);
                response.setResponseCode(200);
                response.setReasonMessage('OK');
                response.setResponseIsReady(true);
                return response;
              }
            }
            return null;
          })
      }
      .width('100%')
    }
    .height('100%')
  }
}
Enter fullscreen mode Exit fullscreen mode
<!-- main/resources/rawfile/index.html -->
<html>
<head>
    <meta name="viewport" content="width=device-width,initial-scale=1">
</head>
<body>
<script crossorigin src="./js/script.js"></script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode
// main/resources/rawfile/js/script.js
const body = document.body;
const element = document.createElement('div');
element.textContent = 'success';
body.appendChild(element);
Enter fullscreen mode Exit fullscreen mode

Method 2

Set a path list using setPathAllowingUniversalAccess. When accessing resources in this list via the file protocol, cross-origin access to local files is allowed. Once the path list is set, the file protocol is restricted to accessing only resources in the list (the behavior of fileAccess will be overridden by this interface). Paths in the list must conform to either of the following formats:

  1. Application file directory obtained via Context.filesDir, with subdirectory examples:

    • /data/storage/el2/base/files/example
    • /data/storage/el2/base/haps/entry/files/example
  2. Application resource directory obtained via Context.resourceDir, with subdirectory examples:

    • /data/storage/el1/bundle/entry/resource/resfile
    • /data/storage/el1/bundle/entry/resource/resfile/example

If any path in the list fails to meet the above conditions, the system will throw an exception code 401 and mark the path list setup as failed. If the path list is empty, the accessible range of the file protocol will follow the rules of fileAccess, as shown in the example below.

// main/ets/pages/Index.ets
import { webview } from '@kit.ArkWeb';
import { BusinessError } from '@kit.BasicServicesKit';

@Entry
@Component
struct WebComponent {
  controller: WebviewController = new webview.WebviewController();

  build() {
    Row() {
      Web({ src: "", controller: this.controller })
        .onControllerAttached(() => {
          try {
            // Set the path list allowing cross-origin access
            this.controller.setPathAllowingUniversalAccess([
              getContext().resourceDir,
              getContext().filesDir + "/example"
            ])
            this.controller.loadUrl("file://" + getContext().resourceDir + "/index.html")
          } catch (error) {
            console.error(`ErrorCode: ${(error as BusinessError).code},  Message: ${(error as BusinessError).message}`);
          }
        })
        .javaScriptAccess(true)
        .fileAccess(true)
        .domStorageAccess(true)
    }
  }
}
Enter fullscreen mode Exit fullscreen mode
<!-- main/resource/rawfile/index.html -->
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8">
    <title>Demo</title>
    <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no, viewport-fit=cover">
    <script>
        function getFile() {
            var file = "file:///data/storage/el1/bundle/entry/resources/resfile/js/script.js";
      // Use file protocol to access local js files across origins via XMLHttpRequest
            var xmlHttpReq = new XMLHttpRequest();
            xmlHttpReq.onreadystatechange = function(){
                console.log("readyState:" + xmlHttpReq.readyState);
                console.log("status:" + xmlHttpReq.status);
                if(xmlHttpReq.readyState == 4){
                    if (xmlHttpReq.status == 200) {
                // If the ets side correctly sets the path list, resources can be obtained here
                        const element = document.getElementById('text');
                        element.textContent = "load " + file + " success";
                    } else {
                // If the ets side does not set the path list, a CORS cross-origin check error will be triggered here
                        const element = document.getElementById('text');
                        element.textContent = "load " + file + " failed";
                    }
                }
            }
            xmlHttpReq.open("GET", file);
            xmlHttpReq.send(null);
        }
    </script>
</head>

<body>
<div class="page">
    <button id="example" onclick="getFile()">stealFile</button>
</div>
<div id="text"></div>
</body>

</html>
Enter fullscreen mode Exit fullscreen mode
// main/resources/rawfile/js/script.js
const body = document.body;
const element = document.createElement('div');
element.textContent = 'success';
body.appendChild(element);
Enter fullscreen mode Exit fullscreen mode

2. HarmonyOS Web: How to Implement Long-Press to Pop Up a Menu for Saving Images, Similar to getHitTestResult?

Refer to the onContextMenuShow event in the Web component. Long-pressing a specific element (e.g., image, link) or right-clicking the mouse will trigger the menu. For details, refer to: https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/ts-basic-components-web-V5#oncontextmenushow9

Currently, conditions for the long-press pop-up cannot be controlled. You can try to control the selection box via H5-side events and CSS.

3. HarmonyOS Web: Demo for Intercepting App Navigation and Custom Request Responses

For Web interception and custom request responses, refer to the official documentation: https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/web-resource-interception-request-mgmt-V5

The Web component supports custom response capabilities after the app intercepts page requests. Developers can use the onInterceptRequest() interface to implement custom resource request responses, which can be applied to scenarios such as custom Web page responses and custom file resource responses.

Process: The Web page initiates a resource loading request → the app layer receives the resource request message → the app layer constructs a local resource response message and sends it to the Web kernel → the Web kernel parses the app layer's response information and loads page resources based on this response.

4. HarmonyOS: How to Prevent Screen Recording/Screenshot for a Single Page or Dialog (e.g., Security Password Keyboard in a Dialog)

To prevent screen recording and screenshot for specific pages or dialogs, set the window to privacy mode using setWindowPrivacyMode. This will display a gray overlay during screen recording, screenshot, or screen sharing (privacy windows do not allow these operations).

Reference: https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/js-apis-window-V5#setwindowprivacymode9

setWindowPrivacyMode

setWindowPrivacyMode(isPrivacyMode: boolean, callback: AsyncCallback<void>): void

Sets whether the window is in privacy mode using an asynchronous callback. A window in privacy mode cannot be screenshotted or screen-recorded, suitable for scenarios requiring protection against screenshots/screen recordings.

5. HarmonyOS: How to Obtain Original Dimensions of a Base64 Image

In image verification code scenarios, the server returns two base64-encoded images (background image and verification image). The scaling factor needs to be calculated based on the dimensions of the background and original images to determine the size and positioning of the verification image.

Use Image Kit capabilities. Reference: https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/js-apis-image-V5#imageinfo

Demo:

import util from '@ohos.util';
import { image } from '@kit.ImageKit';
@Entry
@Component
struct Index {
  @State message: string = 'getImage';
  @StorageLink('test') test: object = new Object;
  @State imageBase64: string =
    'iVBORw0KGgoAAAANSUhEUgAAADwAAAAUCAYAAADRA14pAAABN0lEQVR42mP4P8IAAy0Mjf6xAYxHnIcHo6cZaOlZYj38VbESjIech5E9SayHYZ5FxnT1cL7uFwxMbt4lxtPYPElLjzNg8ywhMWp6GOZBeiVzDA/jinFySmZSkzUpHn5oLosXk+1hYj2NXliRUnjh8hy5MYzP0wzEeIzUvEyNGCY3WZMUw5Qm61EPjzQPkwIGjYfp4VlsnianIULIs3gbHvT2LLZWFzVLZ7xNS3p7lBqAGM+CPZy6o+w/DGfvrv5ffagTjtuOT/4/8cxcOF50Zc3/5dc3wvHeh0fh+PDjk/8vv74Bx/c+PPz/8utrOP7559fg8LD/uqT/A4GpHdB7Q/XBmFBAMyBLPv70DCWWTjw7h2L42pvbUCxGdlTPqRkoji7Y24DiqdCN6f8HKnCRMcNA5bmBCmgACwohlRAJ3H4AAAAASUVORK5CYII='
  @State pixelMap: image.PixelMap | undefined = undefined;
  build() {
    Column() {
      Text(this.message)
        .id('HelloWorld')
        .fontSize(50)
        .fontWeight(FontWeight.Bold)
        .onClick(async () => {
          let helper = new util.Base64Helper();
          let buffer: ArrayBuffer = helper.decodeSync(this.imageBase64, util.Type.MIME).buffer as ArrayBuffer;
          let imageSource = image.createImageSource(buffer);
          let opts: image.DecodingOptions = { editable: true };
          this.pixelMap = await imageSource.createPixelMap(opts);
          this.pixelMap.getImageInfo().then((imageInfo: image.ImageInfo) => {
            if (imageInfo == undefined) {
              console.error(`Failed to obtain the image pixel map information.`);
            }
            let wit = imageInfo.size.width;
            let hig = imageInfo.size.height;
            console.log(`Succeeded in obtaining the image pixel map information., ${JSON.stringify(wit)}, ${JSON.stringify(hig)}`);
          })
        })
    }
    .height('100%')
    .width('100%')
  }
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)