[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%')
}
}
<!-- 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>
// main/resources/rawfile/js/script.js
const body = document.body;
const element = document.createElement('div');
element.textContent = 'success';
body.appendChild(element);
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:
-
Application file directory obtained via
Context.filesDir
, with subdirectory examples:- /data/storage/el2/base/files/example
- /data/storage/el2/base/haps/entry/files/example
-
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)
}
}
}
<!-- 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>
// main/resources/rawfile/js/script.js
const body = document.body;
const element = document.createElement('div');
element.textContent = 'success';
body.appendChild(element);
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).
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%')
}
}
Top comments (0)