Revision control

++ // Copyright (C) 2025 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++
++ #include "qohoswebresourceerror.h"
++ #include "qohoswebviewcontroller.h"
++ #include <QtCore/private/qnapi_p.h>
++ #include <QtCore/private/qohoslogger_p.h>
++ #include <QtCore/QByteArray>
++ #include <QtCore/QMimeDatabase>
++ #include <QtCore/QResource>
++ #include <QtCore/QUrl>
++ #include <arkui/native_node_napi.h>
++ #include <arkui/native_type.h>
++ #include <web/arkweb_net_error_list.h>
++ #include <web/arkweb_scheme_handler.h>
++ #include <cstdlib>
++
++ QT_BEGIN_NAMESPACE
++
++ #define QT_OHOS_ARKWEB_CHECK(call) \
++ do { \
++ const int32_t arkwebRc_ = (call); \
++ if (arkwebRc_ != ARKWEB_NET_OK) \
++ qOhosPrintfError("%s: %s failed with error: %d", \
++ Q_FUNC_INFO, #call, arkwebRc_); \
++ } while (false)
++
++ namespace {
++
++ class QOhosWebViewControllerImpl : public QOhosWebViewController
++ {
++ public:
++ QOhosWebViewControllerImpl();
++
++ ::ArkUI_NodeHandle createEmbeddedWebComponentNodeOrFail(
++ std::shared_ptr<QOhosWebComponentListener> webComponentListener,
++ QObject *webComponentListenerContext) override;
++
++ bool tryLoadUrl(const std::string &url) override;
++ std::string getUrl() override;
++
++ bool tryLoadHtml(const std::string &data, const std::string &mimeType,
++ const std::string &encoding, const std::string &baseUrl,
++ const std::string &historyUrl) override;
++
++ bool canGoBack() override;
++ bool canGoForward() override;
++
++ void goBack() override;
++ void goForward() override;
++
++ void refresh() override;
++ void stop() override;
++
++ std::optional<std::string> tryRunJavaScript(const std::string &script) override;
++
++ void setAttribute(QWebViewSettings::WebAttribute attribute, bool enabled) override;
++ bool testAttribute(QWebViewSettings::WebAttribute attribute) const override;
++
++ bool trySetCookie(const std::string &url, const std::string &cookie) override;
++ std::optional<std::string> tryFetchCookie(const std::string &url) override;
++ bool tryClearAllCookies() override;
++ std::vector<std::pair<std::string, std::string>> fetchAllCookies() override;
++
++ void bindQrcSchemeHandler() override;
++
++ bool trySetCustomUserAgent(const std::string &userAgent) override;
++
++ private:
++ using WebAttribute = QWebViewSettings::WebAttribute;
++
++ QNapi::Object makeWebComponentAttributes(
++ QOhosJsState &jsState,
++ const std::shared_ptr<QOhosWebComponentListener> &webComponentListener) const;
++ void applyUniversalAccessPath(QOhosJsState &jsState);
++ void setPathAllowingUniversalAccess(bool enabled);
++ void updateWebAttribute(WebAttribute attribute, bool enabled);
++
++ struct JsScopeData
++ {
++ QNapi::Reference<QNapi::Object> jsWebViewController;
++ QNapi::Reference<QNapi::Object> jsComponentContent;
++
++ QHash<WebAttribute, bool> attributes = {
++ {WebAttribute::LocalStorageEnabled, false},
++ {WebAttribute::JavaScriptEnabled, true},
++ {WebAttribute::AllowFileAccess, false},
++ {WebAttribute::LocalContentCanAccessFileUrls, false},
++ };
++ std::string universalAccessPath;
++ };
++
++ std::shared_ptr<JsScopeData> m_jsScopeData;
++ std::shared_ptr<QOhosWebComponentListener> m_webComponentListener;
++ std::shared_ptr<void> m_qrcSchemeHandlerBinding;
++ std::string m_webTag;
++ };
++
++ QNapi::Object createEmbeddedWebComponent(
++ QOhosJsState &jsState, QNapi::Object parentAbilityWindowStage,
++ QNapi::Object webComponentAttributes)
++ {
++ return jsState.eval<QNapi::Object>(
++ "QEmbeddedComponentCreator.makeNewQEmbeddedComponentCreator().createEmbeddedWebComponent(*)",
++ {
++ parentAbilityWindowStage.eval<QNapi::Object>("getMainWindowSync().getUIContext()"),
++ webComponentAttributes
++ });
++ }
++
++ std::string localDirectoryForTarget(const std::string &target)
++ {
++ if (target.empty())
++ return {};
++
++ std::string path = target;
++
++ const std::string filePrefix = "file://";
++ if (path.compare(0, filePrefix.size(), filePrefix) == 0)
++ path.erase(0, filePrefix.size());
++
++ if (path.empty() || path.front() != '/')
++ return {};
++
++ if (path.back() == '/')
++ path.pop_back();
++ else
++ path.erase(path.find_last_of('/'));
++
++ if (path.empty()) // the document sat at the filesystem root, e.g. "/index.html"
++ path = "/";
++
++ return path;
++ }
++
++ std::optional<std::string> tryGetResourceRequestUrl(const ArkWeb_ResourceRequest *resourceRequest)
++ {
++ char *rawUrl = nullptr;
++ ::OH_ArkWebResourceRequest_GetUrl(resourceRequest, &rawUrl);
++ if (!rawUrl) {
++ qOhosPrintfError("%s: OH_ArkWebResourceRequest_GetUrl returned no URL", Q_FUNC_INFO);
++ return std::nullopt;
++ }
++ std::string url = rawUrl;
++ ::OH_ArkWeb_ReleaseString(rawUrl);
++ return url;
++ }
++
++ std::shared_ptr<::ArkWeb_Response> createArkWebResponseOrNull()
++ {
++ ArkWeb_Response *response = nullptr;
++ ::OH_ArkWeb_CreateResponse(&response);
++ if (!response) {
++ qOhosPrintfError("%s: OH_ArkWeb_CreateResponse failed", Q_FUNC_INFO);
++ return nullptr;
++ }
++ return std::shared_ptr<::ArkWeb_Response>(response, ::OH_ArkWeb_DestroyResponse);
++ }
++
++ void onQrcRequestStart(const ArkWeb_SchemeHandler *,
++ ArkWeb_ResourceRequest *resourceRequest,
++ const ArkWeb_ResourceHandler *resourceHandler,
++ bool *intercept)
++ {
++ *intercept = true;
++ QT_OHOS_ARKWEB_CHECK(::OH_ArkWebResourceRequest_SetUserData(
++ resourceRequest, const_cast<ArkWeb_ResourceHandler *>(resourceHandler)));
++
++ auto optRequestUrl = tryGetResourceRequestUrl(resourceRequest);
++ if (!optRequestUrl) {
++ QT_OHOS_ARKWEB_CHECK(::OH_ArkWebResourceHandler_DidFailWithErrorV2(
++ resourceHandler, ARKWEB_ERR_FAILED, true));
++ return;
++ }
++ const QUrl url(QString::fromUtf8(*optRequestUrl));
++
++ // qrc:/path/file -> ":/path/file"
++ QResource resource(QStringLiteral(":") + url.path());
++ if (!resource.isValid()) {
++ QT_OHOS_ARKWEB_CHECK(::OH_ArkWebResourceHandler_DidFailWithErrorV2(
++ resourceHandler, ARKWEB_ERR_FILE_NOT_FOUND, true));
++ return;
++ }
++
++ const QByteArray body = resource.uncompressedData();
++ const QByteArray mimeType =
++ QMimeDatabase().mimeTypeForFileNameAndData(url.fileName(), body).name().toUtf8();
++
++ const std::shared_ptr<::ArkWeb_Response> response = createArkWebResponseOrNull();
++ if (!response) {
++ QT_OHOS_ARKWEB_CHECK(::OH_ArkWebResourceHandler_DidFailWithErrorV2(
++ resourceHandler, ARKWEB_ERR_FAILED, true));
++ return;
++ }
++ QT_OHOS_ARKWEB_CHECK(::OH_ArkWebResponse_SetStatus(response.get(), 200));
++ QT_OHOS_ARKWEB_CHECK(::OH_ArkWebResponse_SetMimeType(response.get(), mimeType.constData()));
++ QT_OHOS_ARKWEB_CHECK(::OH_ArkWebResponse_SetCharset(response.get(), "UTF-8"));
++ QT_OHOS_ARKWEB_CHECK(::OH_ArkWebResponse_SetHeaderByName(
++ response.get(), "content-length", QByteArray::number(body.size()).constData(), false));
++
++ QT_OHOS_ARKWEB_CHECK(::OH_ArkWebResourceHandler_DidReceiveResponse(resourceHandler, response.get()));
++ QT_OHOS_ARKWEB_CHECK(::OH_ArkWebResourceHandler_DidReceiveData(
++ resourceHandler, reinterpret_cast<const uint8_t *>(body.constData()), body.size()));
++ QT_OHOS_ARKWEB_CHECK(::OH_ArkWebResourceHandler_DidFinish(resourceHandler));
++ }
++
++ void onQrcRequestStop(const ArkWeb_SchemeHandler *,
++ const ArkWeb_ResourceRequest *resourceRequest)
++ {
++ auto *resourceHandler = static_cast<ArkWeb_ResourceHandler *>(
++ ::OH_ArkWebResourceRequest_GetUserData(resourceRequest));
++ if (resourceHandler)
++ QT_OHOS_ARKWEB_CHECK(::OH_ArkWebResourceHandler_Destroy(resourceHandler));
++ QT_OHOS_ARKWEB_CHECK(::OH_ArkWebResourceRequest_Destroy(resourceRequest));
++ }
++
++ std::shared_ptr<::ArkWeb_SchemeHandler> createQrcSchemeHandlerOrNull()
++ {
++ ArkWeb_SchemeHandler *schemeHandler = nullptr;
++ ::OH_ArkWeb_CreateSchemeHandler(&schemeHandler);
++ if (!schemeHandler) {
++ qOhosPrintfError("%s: OH_ArkWeb_CreateSchemeHandler failed", Q_FUNC_INFO);
++ return nullptr;
++ }
++ QT_OHOS_ARKWEB_CHECK(::OH_ArkWebSchemeHandler_SetOnRequestStart(schemeHandler, onQrcRequestStart));
++ QT_OHOS_ARKWEB_CHECK(::OH_ArkWebSchemeHandler_SetOnRequestStop(schemeHandler, onQrcRequestStop));
++ return std::shared_ptr<::ArkWeb_SchemeHandler>(schemeHandler, ::OH_ArkWeb_DestroySchemeHandler);
++ }
++
++ std::shared_ptr<::ArkWeb_SchemeHandler> sharedQrcSchemeHandlerOrNull()
++ {
++ static std::weak_ptr<::ArkWeb_SchemeHandler> weakHandler;
++ auto handler = weakHandler.lock();
++ if (!handler) {
++ handler = createQrcSchemeHandlerOrNull();
++ weakHandler = handler;
++ }
++ return handler;
++ }
++
++ std::string makeUniqueWebViewTag()
++ {
++ static unsigned s_webTagCounter = 0;
++ return "qtwebview_" + std::to_string(s_webTagCounter++);
++ }
++
++ QOhosWebViewControllerImpl::QOhosWebViewControllerImpl()
++ : QOhosWebViewController()
++ , m_webTag(makeUniqueWebViewTag())
++ {
++ m_jsScopeData = QOhosJsThreadGateway::eval(
++ [&](QOhosJsState &jsState) {
++ return QtOhos::makeProxyWithJsThreadDeleter(
++ QtOhos::moveToSharedPtr(
++ JsScopeData{
++ .jsWebViewController = QNapi::Reference<>::makePersistentFrom(
++ jsState.eval<QNapi::Object>("@ohos.web.webview.WebviewController<new>(*)", {m_webTag})),
++ .jsComponentContent = {},
++ .attributes = {},
++ .universalAccessPath = {},
++ }));
++ });
++ }
++
++ ::ArkUI_NodeHandle QOhosWebViewControllerImpl::createEmbeddedWebComponentNodeOrFail(
++ std::shared_ptr<QOhosWebComponentListener> webComponentListener,
++ QObject *webComponentListenerContext)
++ {
++ m_webComponentListener =
++ makeOhosWebComponentListenerExecutingInContextThread(
++ webComponentListener, webComponentListenerContext);
++
++ return QOhosJsThreadGateway::eval([&](QOhosJsState &jsState) {
++ auto optWindowStage = jsState.defaultWindowStageOrEmpty();
++ if (optWindowStage.IsEmpty()) {
++ qFatal("Attempting to create Web component without WindowStage object available, which is not supported. Aborting...");
++ std::abort();
++ }
++
++ auto embeddedWebComponent = createEmbeddedWebComponent(
++ jsState, optWindowStage,
++ makeWebComponentAttributes(jsState, m_webComponentListener));
++
++ m_jsScopeData->jsComponentContent =
++ QNapi::Reference<>::makePersistentFrom(embeddedWebComponent);
++
++ ::ArkUI_NodeHandle handle = nullptr;
++ auto getNodeFromNapiValueResult
++ = ::OH_ArkUI_GetNodeHandleFromNapiValue(jsState.env(), embeddedWebComponent, &handle);
++ if (getNodeFromNapiValueResult != ::ARKUI_ERROR_CODE_NO_ERROR || handle == nullptr) {
++ qFatal(
++ "Failed to get node handle from webview component, reason: %i, Aborting...",
++ getNodeFromNapiValueResult);
++ std::abort();
++ }
++
++ return handle;
++ });
++ }
++
++ bool QOhosWebViewControllerImpl::tryLoadUrl(const std::string &url)
++ {
++ return QOhosJsThreadGateway::eval([&](QOhosJsState &jsState) {
++ m_jsScopeData->universalAccessPath = localDirectoryForTarget(url);
++ applyUniversalAccessPath(jsState);
++ try {
++ m_jsScopeData->jsWebViewController.eval("loadUrl(*)", {url});
++ return true;
++ } catch (const Napi::Error &error) {
++ qOhosPrintfError(
++ "%s received error from WebController.loadUrl('%s'): %s",
++ Q_FUNC_INFO, url.c_str(), error.what());
++ return false;
++ }
++ });
++ }
++
++ std::string QOhosWebViewControllerImpl::getUrl()
++ {
++ return QOhosJsThreadGateway::eval([&](QOhosJsState &) {
++ return m_jsScopeData->jsWebViewController.eval<QNapi::String>("getUrl()").Utf8Value();
++ });
++ }
++
++ bool QOhosWebViewControllerImpl::tryLoadHtml(const std::string &data, const std::string &mimeType,
++ const std::string &encoding, const std::string &baseUrl,
++ const std::string &historyUrl)
++ {
++ return QOhosJsThreadGateway::eval([&](QOhosJsState &jsState) {
++ m_jsScopeData->universalAccessPath = localDirectoryForTarget(baseUrl);
++ applyUniversalAccessPath(jsState);
++ try {
++ if (baseUrl.empty())
++ m_jsScopeData->jsWebViewController.eval("loadData(*)", {data, mimeType, encoding});
++ else
++ m_jsScopeData->jsWebViewController.eval("loadData(*)", {data, mimeType, encoding, baseUrl, historyUrl});
++ return true;
++ } catch (const Napi::Error &error) {
++ qOhosPrintfError(
++ "%s received error from WebController.loadData: %s", Q_FUNC_INFO, error.what());
++ return false;
++ }
++ });
++ }
++
++ bool QOhosWebViewControllerImpl::canGoBack()
++ {
++ return QOhosJsThreadGateway::eval([&](QOhosJsState &) {
++ return m_jsScopeData->jsWebViewController.eval<QNapi::Boolean>("accessBackward()").Value();
++ });
++ }
++
++ bool QOhosWebViewControllerImpl::canGoForward()
++ {
++ return QOhosJsThreadGateway::eval([&](QOhosJsState &) {
++ return m_jsScopeData->jsWebViewController.eval<QNapi::Boolean>("accessForward()").Value();
++ });
++ }
++
++ void QOhosWebViewControllerImpl::goBack()
++ {
++ QOhosJsThreadGateway::runAndWait([&](QOhosJsState &) {
++ m_jsScopeData->jsWebViewController.eval("backward()");
++ });
++ }
++
++ void QOhosWebViewControllerImpl::goForward()
++ {
++ QOhosJsThreadGateway::runAndWait([&](QOhosJsState &) {
++ m_jsScopeData->jsWebViewController.eval("forward()");
++ });
++ }
++
++ void QOhosWebViewControllerImpl::refresh()
++ {
++ QOhosJsThreadGateway::runAndWait([&](QOhosJsState &) {
++ m_jsScopeData->jsWebViewController.eval("refresh()");
++ });
++ }
++
++ void QOhosWebViewControllerImpl::stop()
++ {
++ QOhosJsThreadGateway::runAndWait([&](QOhosJsState &) {
++ m_jsScopeData->jsWebViewController.eval("stop()");
++ });
++ }
++
++ std::optional<std::string> QOhosWebViewControllerImpl::tryRunJavaScript(const std::string &script)
++ {
++ return QOhosJsThreadGateway::evalWithPromise<std::optional<std::string>>(
++ [&](QOhosJsState &, auto javaScriptResultPromise) {
++ auto thenCatchPromises = std::move(javaScriptResultPromise).makeThenCatchBranches(Q_FUNC_INFO);
++ m_jsScopeData->jsWebViewController.evalToPromiseOrRejectOnThrow("runJavaScript(*)", {script})
++ .onThen(
++ [thenPromise = std::move(thenCatchPromises.first)](const QOhosCallbackInfo &cbInfo) {
++ std::string javaScriptResult = cbInfo.getFirstArg<QNapi::String>(Q_FUNC_INFO);
++ thenPromise(javaScriptResult);
++ })
++ .onCatch(
++ [catchPromise = std::move(thenCatchPromises.second)](const QOhosCallbackInfo &cbInfo) {
++ QtOhos::logJsCallbackError(cbInfo, "@ohos.web.webview.WebviewController.runJavaScript() failed");
++ catchPromise({});
++ });
++ },
++ Q_FUNC_INFO);
++ }
++
++ void QOhosWebViewControllerImpl::setAttribute(WebAttribute attribute, bool enabled)
++ {
++ if (attribute == WebAttribute::LocalContentCanAccessFileUrls) {
++ setPathAllowingUniversalAccess(enabled);
++ return;
++ }
++ updateWebAttribute(attribute, enabled);
++ }
++
++ bool QOhosWebViewControllerImpl::testAttribute(WebAttribute attribute) const
++ {
++ return QOhosJsThreadGateway::eval(
++ [&](QOhosJsState &) {
++ return m_jsScopeData->attributes.value(attribute, false);
++ });
++ }
++
++ bool QOhosWebViewControllerImpl::trySetCookie(const std::string &url, const std::string &cookie)
++ {
++ return QOhosJsThreadGateway::eval([&](QOhosJsState &jsState) {
++ try {
++ jsState.eval("@ohos.web.webview.WebCookieManager.configCookieSync(*)", {url, cookie});
++ return true;
++ } catch (const Napi::Error &error) {
++ qOhosPrintfError(
++ "%s: WebCookieManager.configCookieSync() failed for '%s': %s",
++ Q_FUNC_INFO, url.c_str(), error.what());
++ return false;
++ }
++ });
++ }
++
++ std::optional<std::string> QOhosWebViewControllerImpl::tryFetchCookie(const std::string &url)
++ {
++ return QOhosJsThreadGateway::eval([&](QOhosJsState &jsState) -> std::optional<std::string> {
++ try {
++ const std::string cookie = jsState.eval<QNapi::String>(
++ "@ohos.web.webview.WebCookieManager.fetchCookieSync(*)", {url});
++ return cookie;
++ } catch (const Napi::Error &error) {
++ qOhosPrintfError(
++ "%s: WebCookieManager.fetchCookieSync('%s') failed: %s",
++ Q_FUNC_INFO, url.c_str(), error.what());
++ return {};
++ }
++ });
++ }
++
++ bool QOhosWebViewControllerImpl::tryClearAllCookies()
++ {
++ return QOhosJsThreadGateway::eval([&](QOhosJsState &jsState) {
++ try {
++ jsState.eval("@ohos.web.webview.WebCookieManager.clearAllCookiesSync()");
++ return true;
++ } catch (const Napi::Error &error) {
++ qOhosPrintfError(
++ "%s: WebCookieManager.clearAllCookiesSync() failed: %s", Q_FUNC_INFO, error.what());
++ return false;
++ }
++ });
++ }
++
++ std::vector<std::pair<std::string, std::string>> QOhosWebViewControllerImpl::fetchAllCookies()
++ {
++ using CookieList = std::vector<std::pair<std::string, std::string>>;
++ return QOhosJsThreadGateway::evalWithPromise<CookieList>(
++ [&](QOhosJsState &jsState, auto cookieListPromise) {
++ constexpr bool incognitoMode = false;
++ auto thenCatchPromises = std::move(cookieListPromise).makeThenCatchBranches(Q_FUNC_INFO);
++ jsState.evalToPromiseOrRejectOnThrow(
++ "@ohos.web.webview.WebCookieManager.fetchAllCookies(*)", {incognitoMode})
++ .onThen(
++ [thenPromise = std::move(thenCatchPromises.first)](const QOhosCallbackInfo &cbInfo) {
++ auto cookies = cbInfo.getFirstArg<QNapi::Array>(Q_FUNC_INFO);
++ thenPromise(
++ QNapi::getArrayElements<CookieList, QNapi::Object>(
++ cookies,
++ [](const QNapi::Object &cookie) -> std::pair<std::string, std::string> {
++ return {
++ cookie.get<QNapi::String>("domain"),
++ cookie.get<QNapi::String>("name")
++ };
++ }));
++ })
++ .onCatch(
++ [catchPromise = std::move(thenCatchPromises.second)](const QOhosCallbackInfo &cbInfo) {
++ QtOhos::logJsCallbackError(
++ cbInfo, "@ohos.web.webview.WebCookieManager.fetchAllCookies() failed");
++ catchPromise({});
++ });
++ },
++ Q_FUNC_INFO);
++ }
++
++ void QOhosWebViewControllerImpl::bindQrcSchemeHandler()
++ {
++ m_qrcSchemeHandlerBinding = QOhosJsThreadGateway::eval(
++ [&](QOhosJsState &) -> std::shared_ptr<void> {
++ auto schemeHandler = sharedQrcSchemeHandlerOrNull();
++ if (!schemeHandler)
++ return nullptr;
++
++ if (!::OH_ArkWeb_SetSchemeHandler("qrc", m_webTag.c_str(), schemeHandler.get())) {
++ qOhosPrintfError(
++ "%s: OH_ArkWeb_SetSchemeHandler(\"qrc\", \"%s\") failed.",
++ Q_FUNC_INFO, m_webTag.c_str());
++ return nullptr;
++ }
++
++ return QtOhos::makeProxyWithJsThreadDeleter(
++ QtOhos::makeDestroyNotifier(
++ [webTag = m_webTag, schemeHandler] {
++ if (::OH_ArkWeb_ClearSchemeHandlers(webTag.c_str()) != 0)
++ qOhosPrintfError(
++ "%s: OH_ArkWeb_ClearSchemeHandlers(\"%s\") failed.",
++ Q_FUNC_INFO, webTag.c_str());
++ }));
++ });
++ }
++
++ bool QOhosWebViewControllerImpl::trySetCustomUserAgent(const std::string &userAgent)
++ {
++ return QOhosJsThreadGateway::eval([&](QOhosJsState &) {
++ try {
++ m_jsScopeData->jsWebViewController.eval("setCustomUserAgent(*)", {userAgent});
++ return true;
++ } catch (const Napi::Error &error) {
++ qOhosPrintfError("%s: setCustomUserAgent() failed: %s", Q_FUNC_INFO, error.what());
++ return false;
++ }
++ });
++ }
++
++ QNapi::Object QOhosWebViewControllerImpl::makeWebComponentAttributes(
++ QOhosJsState &jsState,
++ const std::shared_ptr<QOhosWebComponentListener> &webComponentListener) const
++ {
++ return QNapi::makeObject(
++ jsState.env(),
++ {
++ {"webviewController", m_jsScopeData->jsWebViewController.Value()},
++ {"domStorageAccess", m_jsScopeData->attributes.value(WebAttribute::LocalStorageEnabled, false)},
++ {"javaScriptAccess", m_jsScopeData->attributes.value(WebAttribute::JavaScriptEnabled, false)},
++ {"fileAccess", m_jsScopeData->attributes.value(WebAttribute::AllowFileAccess, false)},
++ {
++ "onErrorReceive",
++ [webComponentListener](const QOhosCallbackInfo &callbackInfo) {
++ auto onErrorReceiveEvent = callbackInfo.getFirstArg<QNapi::Object>("onErrorReceive");
++ std::string url = onErrorReceiveEvent.eval<QNapi::String>("request.getRequestUrl()");
++ auto error = onErrorReceiveEvent.get<QNapi::Object>("error");
++ webComponentListener->onErrorReceived(
++ url, WebResourceError::makeFromJsObject(error));
++ }
++ },
++ {
++ "onPageBegin",
++ [webComponentListener](const QNapi::CallbackInfo &callbackInfo) {
++ auto onPageBeginEvent = callbackInfo.getFirstArg<QNapi::Object>("onPageBegin");
++ webComponentListener->onPageBegan(
++ onPageBeginEvent.get<QNapi::String>("url"));
++ }
++ },
++ {
++ "onPageEnd",
++ [webComponentListener](const QNapi::CallbackInfo &callbackInfo) {
++ auto onPageEndEvent = callbackInfo.getFirstArg<QNapi::Object>("onPageEnd");
++ webComponentListener->onPageEnded(
++ onPageEndEvent.get<QNapi::String>("url"));
++ }
++ },
++ {
++ "onProgressChange",
++ [webComponentListener](const QNapi::CallbackInfo &callbackInfo) {
++ auto onProgressChangeEvent = callbackInfo.getFirstArg<QNapi::Object>("onProgressChange");
++ webComponentListener->onProgressChanged(
++ onProgressChangeEvent.get<QNapi::Number>("newProgress"));
++ }
++ },
++ {
++ "onTitleReceive",
++ [webComponentListener](const QNapi::CallbackInfo &callbackInfo) {
++ auto onTitleReceivedEvent = callbackInfo.getFirstArg<QNapi::Object>("onTitleReceive");
++ auto isRealTitleValue =
++ QNapi::getOptionalPropOrEmpty<QNapi::Boolean>(onTitleReceivedEvent, "isRealTitle");
++ webComponentListener->onTitleReceived(
++ onTitleReceivedEvent.get<QNapi::String>("title"),
++ !isRealTitleValue.IsEmpty() && isRealTitleValue.Value());
++ }
++ },
++ });
++ }
++
++ void QOhosWebViewControllerImpl::applyUniversalAccessPath(QOhosJsState &jsState)
++ {
++ if (!m_jsScopeData->jsComponentContent)
++ return;
++
++ std::vector<std::string> paths;
++ if (m_jsScopeData->attributes[WebAttribute::LocalContentCanAccessFileUrls] && !m_jsScopeData->universalAccessPath.empty())
++ paths.push_back(m_jsScopeData->universalAccessPath);
++
++ try {
++ m_jsScopeData->jsWebViewController.eval(
++ "setPathAllowingUniversalAccess(*)",
++ {QNapi::makeArray(jsState.env(), paths)});
++ } catch (const Napi::Error &error) {
++ qOhosPrintfError(
++ "%s: setPathAllowingUniversalAccess failed (paths must live under "
++ "filesDir or resourceDir, otherwise error 401): %s",
++ Q_FUNC_INFO, error.what());
++ }
++ }
++
++ void QOhosWebViewControllerImpl::setPathAllowingUniversalAccess(bool enabled)
++ {
++ QOhosJsThreadGateway::runAndWait([&](QOhosJsState &jsState) {
++ m_jsScopeData->attributes[WebAttribute::LocalContentCanAccessFileUrls] = enabled;
++ applyUniversalAccessPath(jsState);
++ });
++ }
++
++ void QOhosWebViewControllerImpl::updateWebAttribute(WebAttribute attribute, bool enabled)
++ {
++ QOhosJsThreadGateway::runAndWait([&](QOhosJsState &jsState) {
++ m_jsScopeData->attributes[attribute] = enabled;
++
++ if (!m_jsScopeData->jsComponentContent)
++ return;
++
++ m_jsScopeData->jsComponentContent.eval(
++ "update(*)", {makeWebComponentAttributes(jsState, m_webComponentListener)});
++ });
++ }
++
++ }
++
++ QOhosWebViewController::QOhosWebViewController() = default;
++
++ QOhosWebViewController::~QOhosWebViewController() = default;
++
++ std::shared_ptr<QOhosWebViewController> makeOhosWebViewController()
++ {
++ return std::make_shared<QOhosWebViewControllerImpl>();
++ }
++
++ void initializeOhosWebEngine()
++ {
++ QOhosJsThreadGateway::runAndWait([](QOhosJsState &jsState) {
++ const int32_t opt = ARKWEB_SCHEME_OPTION_STANDARD | ARKWEB_SCHEME_OPTION_SECURE
++ | ARKWEB_SCHEME_OPTION_CORS_ENABLED | ARKWEB_SCHEME_OPTION_FETCH_ENABLED;
++ if (::OH_ArkWeb_RegisterCustomSchemes("qrc", opt) != 0) {
++ qOhosPrintfError("%s: RegisterCustomSchemes(\"qrc\") failed", Q_FUNC_INFO);
++ return;
++ }
++ jsState.eval("@ohos.web.webview.WebviewController.initializeWebEngine()");
++ });
++ }
++
++ QT_END_NAMESPACE