* Copyright (c) 2025 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "web_scheme_handler_request.h"
#include <uv.h>
#include "log.h"
#include "napi_parse_utils.h"
#include "napi_web_scheme_handler_request.h"
#include "web_errors.h"
namespace OHOS::Plugin {
struct RequestStopParam {
napi_env env_;
napi_ref callbackRef_;
WebSchemeHandlerRequest* request_;
const ArkWeb_ResourceRequest* arkWebRequest_;
};
static void RequestStopAfterWorkCb(uv_work_t* work, int status);
std::unordered_map<WebSchemeHandler*, ArkWeb_SchemeHandler*> g_web_scheme_handler_map;
std::unordered_map<ArkWeb_SchemeHandler*, WebSchemeHandler*> g_arkui_web_scheme_handler_map;
std::mutex g_mutexForHandlerMap;
void OnRequestStart(ArkWeb_SchemeHandler* schemeHandler, ArkWeb_ResourceRequest* resourceRequest,
ArkWeb_ResourceHandler* resourceHandler, bool* intercept)
{
WebSchemeHandler* handler = WebSchemeHandler::GetWebSchemeHandler(schemeHandler);
if (handler == nullptr) {
return;
}
handler->RequestStart(resourceRequest, resourceHandler, intercept);
}
void OnRequestStop(ArkWeb_SchemeHandler* schemeHandler, ArkWeb_ResourceRequest* resourceRequest)
{
WebSchemeHandler* handler = WebSchemeHandler::GetWebSchemeHandler(schemeHandler);
if (handler == nullptr) {
return;
}
handler->RequestStop(resourceRequest);
}
WebResourceHandler::WebResourceHandler(napi_env env) : env_(env), handler_(nullptr) {}
WebResourceHandler::WebResourceHandler(napi_env env, const ArkWeb_ResourceHandler* handler)
: env_(env), handler_(const_cast<ArkWeb_ResourceHandler*>(handler))
{}
WebResourceHandler::~WebResourceHandler() {}
int32_t WebResourceHandler::DidReceiveResponse(const ArkWeb_Response* response)
{
if (isFinished_) {
return ARKWEB_ERROR_UNKNOWN;
}
if (!response || !handler_) {
return ARKWEB_INVALID_PARAM;
}
if (handler_->DidReceiveResponse(response) != NWebError::NO_ERROR) {
return ARKWEB_INVALID_PARAM;
}
return ARKWEB_NET_OK;
}
int32_t WebResourceHandler::DidReceiveResponseBody(const uint8_t* webResourceHandlerbuffer, int64_t bufferLen)
{
if (isFinished_) {
LOGI("WebResourceHandler::DidReceiveResponseBody already finished");
return ARKWEB_ERROR_UNKNOWN;
}
if (!handler_) {
LOGE("WebResourceHandler::DidReceiveResponseBody handler_ is null");
return ARKWEB_INVALID_PARAM;
}
if (handler_->DidReceiveData(webResourceHandlerbuffer, bufferLen) != NWebError::NO_ERROR) {
return ARKWEB_INVALID_PARAM;
}
return ARKWEB_NET_OK;
}
int32_t WebResourceHandler::DidFinish()
{
if (isFinished_) {
return ARKWEB_ERROR_UNKNOWN;
}
if (!handler_) {
return ARKWEB_INVALID_PARAM;
}
if (handler_->DidFinish() != NWebError::NO_ERROR) {
return ARKWEB_INVALID_PARAM;
}
isFinished_ = true;
return ARKWEB_NET_OK;
}
int32_t WebResourceHandler::DidFailWithError(
ArkWeb_NetError errorCode, const std::string errorDescription, bool completeIfNoResponse)
{
if (isFinished_) {
return ArkWeb_ErrorCode::ARKWEB_ERROR_UNKNOWN;
}
if (handler_->DidFailWithError(errorCode, errorDescription, completeIfNoResponse) != NWebError::NO_ERROR) {
return ARKWEB_INVALID_PARAM;
}
isFinished_ = true;
return ARKWEB_NET_OK;
}
void WebResourceHandler::DestroyArkWebResourceHandler()
{
if (handler_) {
handler_->DestroyArkWebResourceHandler();
handler_ = nullptr;
}
}
WebSchemeHandlerRequest::WebSchemeHandlerRequest(napi_env env) : env_(env) {}
WebSchemeHandlerRequest::WebSchemeHandlerRequest(napi_env env, const ArkWeb_ResourceRequest* request)
{
env_ = env;
if (request) {
url_ = request->url_;
method_ = request->method_;
referrer_ = request->referrer_;
isRedirect_ = request->isRedirect_;
isMainFrame_ = request->isMainFrame_;
hasGesture_ = request->hasGesture_;
headerList_ = request->headerList_;
requestResourceType_ = request->requestResourceType_;
frameUrl_ = request->frameUrl_;
}
}
WebSchemeHandlerRequest::~WebSchemeHandlerRequest() {}
std::string WebSchemeHandlerRequest::GetUrl() const
{
return url_;
}
std::string WebSchemeHandlerRequest::GetMethod() const
{
return method_;
}
std::string WebSchemeHandlerRequest::GetReferrer() const
{
return referrer_;
}
bool WebSchemeHandlerRequest::GetIsRedirect() const
{
return isRedirect_;
}
bool WebSchemeHandlerRequest::GetIsMainFrame() const
{
return isMainFrame_;
}
bool WebSchemeHandlerRequest::GetHasGesture() const
{
return hasGesture_;
}
WebHeaderList WebSchemeHandlerRequest::GetHeaderList() const
{
return headerList_;
}
int32_t WebSchemeHandlerRequest::GetRequestResourceType() const
{
return requestResourceType_;
}
std::string WebSchemeHandlerRequest::GetFrameUrl() const
{
return frameUrl_;
}
void WebSchemeHandlerRequest::SetUrl(const std::string& url)
{
url_ = url;
}
void WebSchemeHandlerRequest::SetMethod(const std::string& method)
{
method_ = method;
}
void WebSchemeHandlerRequest::SetReferrer(const std::string& referrer)
{
referrer_ = referrer;
}
void WebSchemeHandlerRequest::SetIsRedirect(bool isRedirect)
{
isRedirect_ = isRedirect;
}
void WebSchemeHandlerRequest::SetIsMainFrame(bool isMainFrame)
{
isMainFrame_ = isMainFrame;
}
void WebSchemeHandlerRequest::SetHasGesture(bool hasGesture)
{
hasGesture_ = hasGesture;
}
void WebSchemeHandlerRequest::SetHeaderList(const WebHeaderList& headerList)
{
headerList_ = headerList;
}
void WebSchemeHandlerRequest::SetRequestResourceType(int32_t requestResourceType)
{
requestResourceType_ = requestResourceType;
}
void WebSchemeHandlerRequest::SetFrameUrl(const std::string& frameUrl)
{
frameUrl_ = frameUrl;
}
WebSchemeHandlerResponse::WebSchemeHandlerResponse(napi_env env) : env_(env)
{
if (response_ == nullptr) {
response_ = new ArkWeb_Response();
}
}
WebSchemeHandlerResponse::WebSchemeHandlerResponse(napi_env env, ArkWeb_Response* response)
: env_(env), response_(response)
{}
WebSchemeHandlerResponse::~WebSchemeHandlerResponse()
{
if (response_ != nullptr) {
delete response_;
response_ = nullptr;
}
}
std::string WebSchemeHandlerResponse::GetUrl() const
{
if (!response_) {
return "";
}
return response_->url_;
}
int32_t WebSchemeHandlerResponse::SetUrl(const std::string url)
{
if (!response_ || url.empty()) {
return ARKWEB_INVALID_PARAM;
}
response_->url_ = url;
return ARKWEB_NET_OK;
}
int32_t WebSchemeHandlerResponse::GetStatus() const
{
if (!response_) {
return ARKWEB_INVALID_PARAM;
}
return response_->status_;
}
int32_t WebSchemeHandlerResponse::SetStatus(int32_t status)
{
if (!response_) {
return ARKWEB_INVALID_PARAM;
}
response_->status_ = status;
return ARKWEB_NET_OK;
}
std::string WebSchemeHandlerResponse::GetStatusText() const
{
if (!response_) {
return "";
}
return response_->statusText_;
}
int32_t WebSchemeHandlerResponse::SetStatusText(const std::string statusText)
{
if (!response_ || statusText.empty()) {
return ARKWEB_INVALID_PARAM;
}
response_->statusText_ = statusText;
return ARKWEB_NET_OK;
}
std::string WebSchemeHandlerResponse::GetMimeType() const
{
if (!response_) {
return "";
}
return response_->mimeType_;
}
int32_t WebSchemeHandlerResponse::SetMimeType(const std::string mimeType)
{
if (!response_ || mimeType.empty()) {
return ARKWEB_INVALID_PARAM;
}
response_->mimeType_ = mimeType;
return ARKWEB_NET_OK;
}
std::string WebSchemeHandlerResponse::GetEncoding() const
{
if (!response_) {
return "";
}
return response_->encoding_;
}
int32_t WebSchemeHandlerResponse::SetEncoding(const std::string encoding)
{
if (!response_ || encoding.empty()) {
return ARKWEB_INVALID_PARAM;
}
response_->encoding_ = encoding;
return ARKWEB_NET_OK;
}
std::string WebSchemeHandlerResponse::GetHeaderByName(const std::string name)
{
if (!response_ || name.empty()) {
return "";
}
auto it = response_->headers_.find(name);
if (it != response_->headers_.end()) {
return it->second;
}
return "";
}
int32_t WebSchemeHandlerResponse::SetHeaderByName(const std::string name, const std::string value, bool overwrite)
{
if (!response_ || name.empty() || value.empty()) {
return ARKWEB_INVALID_PARAM;
}
auto it = response_->headers_.find(name);
if (overwrite) {
response_->headers_[name] = value;
} else {
if (it == response_->headers_.end()) {
response_->headers_[name] = value;
}
}
return ARKWEB_NET_OK;
}
int32_t WebSchemeHandlerResponse::GetErrorCode() const
{
if (!response_) {
return ARKWEB_ERR_FAILED;
}
return response_->errorCode_;
}
int32_t WebSchemeHandlerResponse::SetErrorCode(int32_t code, const std::string errorDescription)
{
if (!response_) {
return ARKWEB_INVALID_PARAM;
}
response_->errorCode_ = code;
response_->errorDescription_ = errorDescription;
return ARKWEB_NET_OK;
}
WebSchemeHandler::WebSchemeHandler(napi_env env) : env_(env)
{
#ifdef IOS_PLATFORM
ArkWeb_SchemeHandler* handler = AceWebSchemeHandler::CreateArkHandler();
AceWebSchemeHandler::setOnRequestStart(handler, OnRequestStart);
AceWebSchemeHandler::setOnRequestStop(handler, OnRequestStop);
#endif
#ifdef ANDROID_PLATFORM
auto handler = WebSchemeHandlerJni::CreateArkSchemeHandler();
WebSchemeHandlerJni::SetOnRequestStart(handler, OnRequestStart);
WebSchemeHandlerJni::SetOnRequestStop(handler, OnRequestStop);
#endif
if (!handler) {
return;
}
std::lock_guard<std::mutex> auto_lock(g_mutexForHandlerMap);
g_web_scheme_handler_map.insert(std::make_pair(this, handler));
g_arkui_web_scheme_handler_map.insert(std::make_pair(handler, this));
}
WebSchemeHandler::~WebSchemeHandler()
{
ArkWeb_SchemeHandler* handler = const_cast<ArkWeb_SchemeHandler*>(GetArkWebSchemeHandler(this));
if (handler) {
std::lock_guard<std::mutex> auto_lock(g_mutexForHandlerMap);
g_web_scheme_handler_map.erase(this);
g_arkui_web_scheme_handler_map.erase(handler);
#ifdef IOS_PLATFORM
AceWebSchemeHandler::destroySchemeHandler(handler);
#endif
#ifdef ANDROID_PLATFORM
WebSchemeHandlerJni::DeleteArkSchemeHandler(handler);
#endif
}
}
ArkWeb_SchemeHandler* WebSchemeHandler::GetArkWebSchemeHandler(WebSchemeHandler* handler)
{
if (!handler) {
return nullptr;
}
std::lock_guard<std::mutex> lock(g_mutexForHandlerMap);
auto it = g_web_scheme_handler_map.find(handler);
if (it != g_web_scheme_handler_map.end()) {
return it->second;
}
return nullptr;
}
WebSchemeHandler* WebSchemeHandler::GetWebSchemeHandler(ArkWeb_SchemeHandler* arkHandler)
{
if (!arkHandler) {
return nullptr;
}
std::lock_guard<std::mutex> lock(g_mutexForHandlerMap);
auto it = g_arkui_web_scheme_handler_map.find(arkHandler);
if (it != g_arkui_web_scheme_handler_map.end()) {
return it->second;
}
return nullptr;
}
void WebSchemeHandler::ClearWebSchemeHandler()
{
std::lock_guard<std::mutex> lock(g_mutexForHandlerMap);
g_web_scheme_handler_map.clear();
g_arkui_web_scheme_handler_map.clear();
}
void WebSchemeHandler::PutRequestStart(napi_env, napi_value callback)
{
napi_status status = napi_create_reference(env_, callback, 1, &request_start_callback_);
if (status != napi_status::napi_ok) {
LOGE("PutRequestStart create reference failed.");
}
}
void WebSchemeHandler::PutRequestStop(napi_env, napi_value callback)
{
napi_status status = napi_create_reference(env_, callback, 1, &request_stop_callback_);
if (status != napi_status::napi_ok) {
LOGE("PutRequestStop create reference failed.");
}
}
void WebSchemeHandler::RequestStart(
ArkWeb_ResourceRequest* request, ArkWeb_ResourceHandler* arkWeb_ResourceHandler, bool* intercept)
{
napi_handle_scope scope;
napi_status status_scope = napi_open_handle_scope(env_, &scope);
if (status_scope != napi_ok) {
LOGE("scheme handler RequestStart scope is nullptr");
return;
}
size_t paramCount = 2;
napi_value callbackFunc = nullptr;
napi_status status;
if (!request_start_callback_) {
LOGE("scheme handler onRequestStart nil env");
return;
}
status = napi_get_reference_value(env_, request_start_callback_, &callbackFunc);
if (status != napi_ok || callbackFunc == nullptr) {
LOGE("scheme handler get onRequestStart func failed.");
return;
}
napi_value requestValue[2] = { 0 };
napi_create_object(env_, &requestValue[0]);
napi_create_object(env_, &requestValue[1]);
WebSchemeHandlerRequest* schemeHandlerRequest = new (std::nothrow) WebSchemeHandlerRequest(env_, request);
if (schemeHandlerRequest == nullptr) {
LOGE("RequestStart, new schemeHandlerRequest failed");
return;
}
std::shared_ptr<WebResourceHandler> resourceHandler =
std::make_shared<WebResourceHandler>(env_, arkWeb_ResourceHandler);
if (resourceHandler == nullptr) {
LOGE("RequestStart, new resourceHandler failed");
delete schemeHandlerRequest;
return;
}
napi_wrap(
env_, requestValue[0], schemeHandlerRequest,
[](napi_env , void* data, void* ) {
WebSchemeHandlerRequest* request = (WebSchemeHandlerRequest*)data;
delete request;
},
nullptr, nullptr);
NapiWebSchemeHandlerRequest::DefineProperties(env_, &requestValue[0]);
napi_wrap(
env_, requestValue[1], resourceHandler.get(),
[](napi_env , void* data, void* ) {}, nullptr, nullptr);
NapiWebResourceHandler::DefineProperties(env_, &requestValue[1]);
napi_value result = nullptr;
status = napi_call_function(env_, nullptr, callbackFunc, paramCount, requestValue, &result);
if (status != napi_status::napi_ok) {
LOGE("scheme handler call onRequestStart failed.");
}
if (!NapiParseUtils::ParseBoolean(env_, result, *intercept)) {
LOGE("scheme handler onRequestStart intercept parse failed");
*intercept = false;
}
if (!*intercept) {
resourceHandler->SetFinishFlag();
}
}
void WebSchemeHandler::RequestStop(ArkWeb_ResourceRequest* resourceRequest)
{
uv_loop_s* loop = nullptr;
uv_work_t* work = nullptr;
napi_get_uv_event_loop(env_, &loop);
if (loop == nullptr) {
return;
}
work = new (std::nothrow) uv_work_t;
if (work == nullptr) {
return;
}
RequestStopParam* param = new (std::nothrow) RequestStopParam();
if (param == nullptr) {
delete work;
return;
}
param->env_ = env_;
param->callbackRef_ = request_stop_callback_;
param->request_ = new (std::nothrow) WebSchemeHandlerRequest(param->env_, resourceRequest);
if (param->request_ == nullptr) {
delete work;
delete param;
return;
}
param->arkWebRequest_ = resourceRequest;
work->data = reinterpret_cast<void*>(param);
int ret = uv_queue_work_with_qos(loop, work, [](uv_work_t* work) {}, RequestStopAfterWorkCb, uv_qos_user_initiated);
if (ret != 0) {
if (param != nullptr) {
delete param;
param = nullptr;
}
if (work != nullptr) {
delete work;
work = nullptr;
}
}
}
static void RequestStopAfterWorkCb(uv_work_t* work, int status)
{
(void)status;
RequestStopParam* param = reinterpret_cast<RequestStopParam*>(work->data);
if (param == nullptr) {
delete work;
return;
}
napi_handle_scope scope;
napi_status status_scope = napi_open_handle_scope(param->env_, &scope);
if (status_scope != napi_ok) {
LOGE("scheme handler RequestStopAfterWorkCb scope is nullptr");
delete param->request_;
delete param;
delete work;
return;
}
if (param->callbackRef_) {
napi_value callbackFunc = nullptr;
napi_status napi_status = napi_get_reference_value(param->env_, param->callbackRef_, &callbackFunc);
if (napi_status == napi_ok && callbackFunc != nullptr) {
napi_value requestValue;
napi_create_object(param->env_, &requestValue);
napi_wrap(
param->env_, requestValue, param->request_,
[](napi_env , void* data, void* ) {
WebSchemeHandlerRequest* request = (WebSchemeHandlerRequest*)data;
delete request;
},
nullptr, nullptr);
NapiWebSchemeHandlerRequest::DefineProperties(param->env_, &requestValue);
napi_value result = nullptr;
napi_status = napi_call_function(param->env_, nullptr, callbackFunc, 1, &requestValue, &result);
if (napi_status != napi_status::napi_ok) {
LOGE("scheme handler call onRequestStop failed.");
}
}
} else {
delete param->request_;
}
napi_close_handle_scope(param->env_, scope);
delete param;
delete work;
}
}