/*
 * Copyright (c) 2025-2026 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.
 */

#import "AceWeb.h"
#import <Security/Security.h>
#import "AceWebPatternBridge.h"
#import "AceWebErrorReceiveInfoObject.h"
#import "AceWebObject.h"
#include "AceWebCallbackObjectWrapper.h"
#import "AceWebControllerBridge.h"
#import "WebMessageChannel.h"
#import <UIKit/UIKit.h>
#import <AVFoundation/AVFoundation.h>
#import <AVKit/AVKit.h>
#import "AceWebPatternOCBridge.h"
#import "adapter/ios/capability/vibrator/haptic_vibrator.h"
#include "core/components_ng/pattern/scrollable/scrollable_properties.h"
#include "scheme_handler/resource_request.h"
#include "ace_engine_types.h"
#import "AceWebInfoManager.h"

#define WEBVIEW_WIDTH  @"width"
#define WEBVIEW_HEIGHT  @"height"
#define WEBVIEW_POSITION_LEFT  @"left"
#define WEBVIEW_POSITION_TOP  @"top"
#define WEBVIEW_LOADDATA_DATA  @"load_data_data"
#define WEBVIEW_LOADDATA_MIMETYPE  @"load_data_mimetype"
#define WEBVIEW_LOADDATA_ENCODING  @"load_data_encoding"
#define SUCCESS         @"success"
#define FAIL            @"fail"
#define KEY_SOURCE      @"src"
#define KEY_VALUE       @"value"
#define DELAY_TIME      0.3
#define ZOOMIN_SCALE_VALUE   1.2
#define ZOOMOUT_SCALE_VALUE  0.8
#define POLLING_INTERVAL_MS 50
#define TIMEOUT_S 15
#define HTTP_STATUS_GATEWAY_TIMEOUT 504

#define WEB_FLAG        @"web@"
#define PARAM_AND       @"#HWJS-&-#"
#define PARAM_EQUALS    @"#HWJS-=-#"
#define PARAM_BEGIN     @"#HWJS-?-#"
#define METHOD          @"method"
#define EVENT           @"event"
#define WEBVIEW_SRC     @"event"
#define S_Scale         @"event"
#define CONSOLELOG      @"log"
#define CONSOLEERROR    @"error"
#define CONSOLEINFO     @"info"
#define CONSOLEDEBUG    @"debug"
#define CONSOLEWARN     @"warn"
#define ESTIMATEDPROGRESS  @"estimatedProgress"
#define TITLE           @"title"

#define NTC_ZOOM_ACCESS                   @"zoomAccess"
#define NTC_JAVASCRIPT_ACCESS             @"javascriptAccess"
#define NTC_MINFONTSIZE                   @"minFontSize"
#define NTC_HORIZONTALSCROLLBAR_ACCESS    @"horizontalScrollBarAccess"
#define NTC_VERTICALSCROLLBAR_ACCESS      @"verticalScrollBarAccess"
#define NTC_BACKGROUNDCOLOR               @"backgroundColor"
#define NTC_UPDATELAYOUT                  @"updateLayout"
#define NTC_ONLOADINTERCEPT               @"onLoadIntercept"
#define NTC_ONHTTPERRORRECEIVE            @"onHttpErrorReceive"
#define NTC_ONPROGRESSCHANGED             @"onProgressChanged"
#define NTC_ONRECEIVEDTITLE               @"onReceivedTitle"
#define NTC_ONWILL_SCROLLSTART            @"onWillScrollStart"
#define NTC_ONSCROLLSTART                 @"onScrollStart"
#define NTC_ONSCROLLEND                   @"onScrollEnd"
#define NTC_ONSCROLL                      @"onScroll"
#define NTC_ONSCALECHANGE                 @"onScaleChange"
#define NTC_ONCONSOLEMESSAGE              @"onConsoleMessage"
#define NTC_RICHTEXT_LOADDATA             @"loadData"
#define NTC_ONFULLSCREENENTER             @"onFullScreenEnter"
#define NTC_ONFULLSCREENEXIT              @"onFullScreenExit"
#define NTC_ONINTERCEPTREQUEST            @"onInterceptRequest"
#define NTC_REGISTEREDONINTERCEPTREQUEST  @"IsRegisteredOnInterceptRequest"
#define NTC_ONREFRESHACCESSED_HISTORYEVENT     @"onRefreshAccessedHistory"
#define CUSTOM_SCHEME                     @"arkuixcustomscheme"
#define CUSTOM_SCHEME_HANDLER             @"arkuixcustomschemehandler"
#define NTC_ONOVERRIDEURLLOADING          @"onOverrideUrlLoading"
#define NTC_ONSSLERROREVENTRECEIVE        @"onSslErrorEventReceive"
#define NTC_ONSSLERROREVENT               @"onSslErrorEvent"
#define NTC_ONCLIENTAUTHENTICATIONREQUEST @"onClientAuthenticationRequest"
#define WEBVIEW_PAGE_HALF                 2
#define NTC_TEXT_ZOOM_RATIO               @"textZoomRatio"
#define NTC_ENABLE_HAPTIC_FEEDBACK        @"enableHapticFeedback"
#define SELECTION_DRAG_THRESHOLD          2.0

typedef NS_ENUM(NSInteger, NestedScrollMode) {
    SELF_ONLY,
    SELF_FIRST,
    PARENT_FIRST, 
    PARALLEL
};
typedef NS_ENUM(NSUInteger, WebViewLoadType) {
    WebViewLoadTypeURL,
    WebViewLoadTypeData
};
@interface NestedScrollOptionsExt : NSObject
@property (nonatomic, assign) NestedScrollMode scrollUp;
@property (nonatomic, assign) NestedScrollMode scrollDown;
@property (nonatomic, assign) NestedScrollMode scrollLeft;
@property (nonatomic, assign) NestedScrollMode scrollRight;
@end
@implementation NestedScrollOptionsExt
@end

@interface DownloadTaskInfo : NSObject
@property (nonatomic, assign) bool isDownload;
@property (nonatomic, strong) NSString* filePath;
@property (nonatomic, strong) NSDate* lastUpdateTime;
@property (nonatomic, strong) NSURLSessionDownloadTask* downloadTask;
@end

@implementation DownloadTaskInfo
@end
typedef void (^PostMessageResultMethod)(NSString* ocResult);
typedef void (^PostMessageResultMethodExt)(id ocResult);
typedef void (^OnDownloadBeforeStart)(NSString* guid, NSString* method, NSString* mimeType, NSString* url);
typedef void (^onDownloadUpdated)(NSString* guid, NSString* state, int64_t totalBytes,
                                int64_t receivedBytes, NSString* suggestedFileName);
typedef void (^onDownloadFailed)(NSString* guid, NSString* state, int64_t code);
typedef void (^onDownloadFinish)(NSString* guid, NSString* path);
typedef id (^onJavaScriptFunction)(NSString* objName, NSString* methodName, NSArray* args);
@interface AceWeb()<WKScriptMessageHandler, WKUIDelegate, WKNavigationDelegate, 
                    UIScrollViewDelegate, NSURLSessionDownloadDelegate, WKHTTPCookieStoreObserver, WKURLSchemeHandler>
/**webView*/
@property (nonatomic, assign) WKWebView *webView;
@property (nonatomic, assign) int64_t incId;
@property (nonatomic, strong) WKPreferences *preferences;
@property (nonatomic, strong) WKWebpagePreferences *webpagePreferences;
@property (nonatomic, weak) UIViewController *target;
@property (nonatomic, strong) NSSet<UITouch *> *currentUiTouchs;
@property (nonatomic, strong) UIEvent  *currentEvent;
@property (nonatomic, assign) int8_t  currentType;
@property (nonatomic, assign) CGFloat screenScale;
@property (nonatomic, assign) CGFloat oldScale;
@property (nonatomic, assign) int httpErrorCode;
@property (nonatomic, assign) bool allowZoom;
@property (nonatomic, assign) bool isLoadRichText;
@property (nonatomic, assign) BOOL javascriptAccessSwitch;
@property (nonatomic, assign) BOOL enableHapticFeedbackSwitch;
@property (nonatomic, copy) IAceOnResourceEvent onEvent;
@property (nonatomic, strong) NSURLSession* session;
@property (nonatomic, strong) WebMessageChannel* webMessageChannel;
@property (nonatomic, strong) PostMessageResultMethod messageCallBack;
@property (nonatomic, strong) NSString *reloadUrl;
@property (nonatomic, strong) UIView *viewExitFullScreen;
@property (nonatomic, strong) UIViewController *viewControllerExitFull;
@property (nonatomic, copy) NSString *videoSrc;
@property (nonatomic, strong) NSMutableDictionary<NSString *, IAceOnCallSyncResourceMethod> *callSyncMethodMap;
@property (nonatomic, strong) PostMessageResultMethodExt messageCallBackExt;
@property (nonatomic, strong) OnDownloadBeforeStart onDownloadBeforeStartCallBack;
@property (nonatomic, strong) onDownloadUpdated onDownloadUpdatedCallBack;
@property (nonatomic, strong) onDownloadFailed onDownloadFailedCallBack;
@property (nonatomic, strong) onDownloadFinish onDownloadFinishCallBack;
@property (nonatomic, strong) onJavaScriptFunction onJavaScriptFunctionCallBack;
@property (nonatomic, strong) NSMutableDictionary<NSString*, DownloadTaskInfo*>* downloadTasksDic;
@property (nonatomic, strong) NSArray* syncMethodList;
@property (nonatomic, strong) NSArray* asyncMethodList;
@property (nonatomic, strong) NSString* objName;
@property (nonatomic, assign) bool allowIncognitoMode;
@property (nonatomic, assign) BOOL jsReady;
@property (nonatomic, strong) NSMutableDictionary<NSString*, NSValue*>* schemeHandlerMap;
@property (nonatomic, assign) ArkWeb_ResourceRequest* currentResourceRequest;
@property (nonatomic, assign) ArkWeb_ResourceHandler* currentResourceHandler;
@property (nonatomic, assign) NSInteger textZoomRatio;
@property (nonatomic, copy) NSString* referrer;
@property (nonatomic, assign) bool isMainFrame;
@property (nonatomic, copy) NSString* mainFrameUrl;
@property (nonatomic, assign) bool isLoadUrl;
@property (nonatomic, strong) NSMutableSet<NSString *> *handleSslErrorUrls;
@property (nonatomic, strong) NestedScrollOptionsExt *nestedOpt;
@property (nonatomic, assign) CGPoint dragStartPoint;
@property (nonatomic, assign) BOOL hasCalledOnScrollStart;
@property (nonatomic, strong) NSHashTable<UIGestureRecognizer*> *selectionGestureRecognizers;
@property (nonatomic, assign) BOOL isSelectionDragArmed;
@property (nonatomic, assign) BOOL hasTriggeredDragHaptic;
@property (nonatomic, assign) BOOL isSelectionLongPressActive;
@property (nonatomic, assign) CGPoint selectionDragStartPoint;
@property (nonatomic, assign) WebViewLoadType lastLoadType;
@property (nonatomic, copy) NSString *lastData;
@property (nonatomic, copy) NSString *lastMimeType;
@property (nonatomic, copy) NSString *lastEncoding;
@property (nonatomic, copy) NSString *lastBaseUrl;
@property (nonatomic, copy) NSString *lastHistoryUrl;
@end

static BOOL _webDebuggingAccessInit = NO;
static NSString *const kJavaScriptURLPrefix = @"javascript:";
using SslError = OHOS::Ace::NG::Converter::SslError;

@implementation AceWeb {
    dispatch_source_t _timer;
}

- (instancetype)init:(int64_t)incId
              target:(UIViewController*)target
             onEvent:(IAceOnResourceEvent)callback
   abilityInstanceId:(int32_t)abilityInstanceId;
{
    return [self init:incId incognitoMode:false target:target onEvent:callback
    abilityInstanceId:abilityInstanceId];
}

- (instancetype)init:(int64_t)incId
              incognitoMode:(bool)incognitoMode
              target:(UIViewController*)target
             onEvent:(IAceOnResourceEvent)callback
   abilityInstanceId:(int32_t)abilityInstanceId
{
    self.onEvent = callback;
    self.incId = incId;
    self.target = target;
    self.javascriptAccessSwitch = YES;
    self.enableHapticFeedbackSwitch = YES;
    self.allowIncognitoMode = incognitoMode;
    self.allowZoom = true;
    self.oldScale = 100.0f;
    self.httpErrorCode = 400;
    self.isLoadRichText = false;
    self.jsReady = false;
    self.currentResourceRequest = nullptr;
    self.currentResourceHandler = nullptr;
    self.textZoomRatio = 100;
    self.referrer = @"";
    self.isMainFrame = false;
    self.isLoadUrl = false;
    self.handleSslErrorUrls = [NSMutableSet set];
    [self initConfigure];
    [self initEventCallback];
    [self initWeb];
    return self;
}

-(void)initWeb
{
    WKWebViewConfiguration* config = [[WKWebViewConfiguration alloc] init];
    WKPreferences* preference = [[WKPreferences alloc] init];
    preference.minimumFontSize = 8;
    WKUserContentController* userContentController = [[WKUserContentController alloc] init];
    [self initConsole:CONSOLELOG controller:userContentController];
    [self initConsole:CONSOLEINFO controller:userContentController];
    [self initConsole:CONSOLEERROR controller:userContentController];
    [self initConsole:CONSOLEDEBUG controller:userContentController];
    [self initConsole:CONSOLEWARN controller:userContentController];
    [userContentController addScriptMessageHandler:self name:@"onWebMessagePortMessage"];
    [userContentController addScriptMessageHandler:self name:@"AceWebHandler"];
    [userContentController addScriptMessageHandler:self name:@"onTextSelectionChanged"];

    NSString *selectionJS =
        @"(function(){"
        "var h=window.webkit.messageHandlers.onTextSelectionChanged;"
        "function txt(){ try{ var s=window.getSelection?window.getSelection():null;return s?String(s):''; }"
        "catch(e){ return ''; } }"
        "document.addEventListener('selectionchange',function(){"
        "var t=txt();h.postMessage({ type:'selectionchange', text:t });"
        "},true);"
        "})();";
    WKUserScript *selectionScript = [[WKUserScript alloc] initWithSource:selectionJS
        injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:NO];
    [userContentController addUserScript:selectionScript];

    NSString *htmlFBody = @"var vSrc = e.target.currentSrc;window.webkit.messageHandlers.videoPlayed.postMessage(vSrc);";
    NSString *htmlFunc = [NSString stringWithFormat:@"function(e) {if (e.target.tagName === 'VIDEO') {%@}}", htmlFBody];
    NSString *htmlJS = [NSString stringWithFormat:@"document.addEventListener('play', %@, true);", htmlFunc];
    
    WKUserScript *script = [[WKUserScript alloc] initWithSource:htmlJS injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:YES];
    [userContentController addUserScript:script];
    [userContentController addScriptMessageHandler:self name:@"videoPlayed"];
    preference.javaScriptEnabled = self.javascriptAccessSwitch;

    config.preferences = preference;
    config.userContentController = userContentController;
    config.allowsInlineMediaPlayback = YES;
    config.mediaTypesRequiringUserActionForPlayback = WKAudiovisualMediaTypeNone;
    [config setURLSchemeHandler:self forURLScheme:CUSTOM_SCHEME];
    [config setURLSchemeHandler:self forURLScheme:CUSTOM_SCHEME_HANDLER];
    [self incognitoModeWithConfig:config];
    self.webView = [[WKWebView alloc] initWithFrame:CGRectZero configuration:config];
    self.webView.UIDelegate = self;
    self.webView.navigationDelegate = self;
    self.webView.scrollView.delegate = self;
    if (@available(iOS 11.0, *)) {
        self.webView.scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
    }
    [self.webView addObserver:self forKeyPath:ESTIMATEDPROGRESS options:NSKeyValueObservingOptionNew context:nil];
    [self.webView addObserver:self forKeyPath:TITLE options:NSKeyValueObservingOptionNew context:nil];
    self.hasCalledOnScrollStart = YES;
    self.selectionGestureRecognizers = [NSHashTable weakObjectsHashTable];
    self.isSelectionDragArmed = NO;
    self.hasTriggeredDragHaptic = NO;
    [self registerSelectionGestures];
}

- (void)incognitoModeWithConfig:(WKWebViewConfiguration*) config
{
    if (self.allowIncognitoMode) {
        WKWebsiteDataStore *defaultDataStore = [WKWebsiteDataStore defaultDataStore];
        NSArray *dataTypes = @[WKWebsiteDataTypeCookies];
        [defaultDataStore removeDataOfTypes:[NSSet setWithArray:dataTypes] modifiedSince:[NSDate dateWithTimeIntervalSince1970:0] completionHandler:^{
        }];
        config.processPool = [[WKProcessPool alloc] init];
        WKWebsiteDataStore *nonPersistentDataStore = [WKWebsiteDataStore nonPersistentDataStore];
        [config setWebsiteDataStore:nonPersistentDataStore];
    } else {
        WKWebsiteDataStore *defaultDataStore = [WKWebsiteDataStore defaultDataStore];
        config.websiteDataStore = defaultDataStore;
    }
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000
    if (@available(iOS 11.0, *)) {
        [config.websiteDataStore.httpCookieStore addObserver:self];
    }
#endif
}

-(WKWebView*)getWeb {
    return self.webView;
}

- (void)initConsole:(NSString*)consoleLevel controller:(WKUserContentController*)controller
{
    NSString* jsConsole = @"";
    if ([consoleLevel isEqualToString:CONSOLELOG] ||
        [consoleLevel isEqualToString:CONSOLEINFO] ||
        [consoleLevel isEqualToString:CONSOLEDEBUG] ||
        [consoleLevel isEqualToString:CONSOLEWARN] ||
        [consoleLevel isEqualToString:CONSOLEERROR]) {
        jsConsole = [NSString stringWithFormat:
            @"console.%@ = (function(oriLogFunc) {"
             "  return function(...args) {"
             "    oriLogFunc.apply(console, args);"
             "    const msg = args.map(arg => typeof arg === 'object' && arg !== null ?"
             "      JSON.stringify(arg) : String(arg)).join(' ');"
             "    window.webkit.messageHandlers.%@.postMessage(msg);"
             "  }"
             "})(console.%@);",
            consoleLevel, consoleLevel, consoleLevel];
    }
    WKUserScript* script = [[WKUserScript alloc] initWithSource:jsConsole
                                                  injectionTime:WKUserScriptInjectionTimeAtDocumentEnd
                                               forMainFrameOnly:NO];
    [controller addUserScript:script];
    [controller addScriptMessageHandler:self name:consoleLevel];
}

-(void)loadUrl:(NSString*)url
{
    [self.webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:url]]];
    self.isLoadUrl = YES;
    self.lastLoadType = WebViewLoadTypeURL;
}

-(void)loadUrl:(NSString*)url header:(NSDictionary*) httpHeaders
{
    if(url == nil){
        LOGE("Error:AceWeb: url is nill");
        return;
    }
    if ([url hasSuffix:@".html"] && ![url hasPrefix:@"file://"] && ![url hasPrefix:@"http"]) {
        url = [NSString stringWithFormat:@"file://%@", url];
    }
    [self.handleSslErrorUrls removeAllObjects];
    if (httpHeaders == nil || httpHeaders.count == 0) {
        if ([self isJavascriptUrl:url]) {
            NSString *js = [url substringFromIndex:kJavaScriptURLPrefix.length];
            if (js.length > 0) {
                LOGI("AceWeb: load javascript url");
                [self evaluateJavaScript:js callback:nil];
            } else {
                LOGE("Error:AceWeb: javascript url is empty");
            }
        } else {
            [self.webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:url]]];
            self.isLoadUrl = YES;
            self.lastLoadType = WebViewLoadTypeURL;
        }
        return;
    }

    NSURLRequest *request = [NSURLRequest requestWithURL:[[NSURL alloc] initWithString:url]];
    NSDictionary *headerFields = httpHeaders;
    NSMutableURLRequest *mutableRequest = [request mutableCopy];
    [mutableRequest setAllHTTPHeaderFields:headerFields];
    [self.webView loadRequest:mutableRequest];
    self.isLoadUrl = YES;
    self.lastLoadType = WebViewLoadTypeURL;
}

- (BOOL)isJavascriptUrl:(NSString *)url
{
    if (url.length < kJavaScriptURLPrefix.length) {
        return NO;
    }
    return [url.lowercaseString hasPrefix:kJavaScriptURLPrefix];
}

- (void)loadData:(NSString*)data
        mimeType:(NSString*)mimeType
        encoding:(NSString*)encoding
         baseUrl:(NSString*)baseUrl
      historyUrl:(NSString*)historyUrl
{
    if (@available(iOS 9.0, *)) {
        [self.webView loadData:[data dataUsingEncoding:NSUTF8StringEncoding]
                         MIMEType:mimeType
            characterEncodingName:encoding
                          baseURL:[NSURL fileURLWithPath:baseUrl]];
    } else {
        [self.webView loadHTMLString:data baseURL:[NSURL URLWithString:baseUrl]];
    }

    self.lastLoadType = WebViewLoadTypeData;
    self.lastData = data;
    self.lastMimeType = mimeType;
    self.lastEncoding = encoding;
    self.lastBaseUrl = baseUrl;
    self.lastHistoryUrl = historyUrl;
}

- (void)evaluateJavaScript:(NSString*)script callback:(void (^)(id _Nullable obj, NSError* _Nullable error))callback
{
    LOGI("AceWeb: ExecuteJavaScript called");
    [self.webView evaluateJavaScript:script
                completionHandler:^(id _Nullable obj, NSError* _Nullable error) {
                    callback(obj, error);
                }];
}

- (NSString*)getUrl
{
    return self.webView.URL == nil ? @"" : [self.webView.URL absoluteString];
}

- (bool)accessBackward
{
    return self.webView.canGoBack;
}

- (bool)accessForward
{
    return self.webView.canGoForward;
}

- (void)backward
{
    [self.webView goBack];
}

- (void)forward
{
    [self.webView goForward];
}

- (void)refresh {
    switch (self.lastLoadType) {
        case WebViewLoadTypeURL:
            LOGI("AceWeb refresh WebViewLoadTypeURL");
            [self.webView reload];
            break;
            
        case WebViewLoadTypeData:
            LOGI("AceWeb refresh WebViewLoadTypeData");
            if (self.lastData) {
                LOGI("AceWeb refresh loadData");
                [self loadData:self.lastData
                      mimeType:self.lastMimeType
                      encoding:self.lastEncoding
                       baseUrl:self.lastBaseUrl
                    historyUrl:self.lastHistoryUrl];
            }
            break;
        default:
            LOGI("AceWeb refresh default");
            [self.webView reload];
            break;
    }
}

- (void)removeCache:(bool)value
{
    NSSet* websiteDataTypes = [[NSMutableSet alloc] init];
    if (value) {
        websiteDataTypes = [NSSet setWithArray:@[ WKWebsiteDataTypeMemoryCache, WKWebsiteDataTypeDiskCache ]];
    } else {
        websiteDataTypes = [NSSet setWithArray:@[ WKWebsiteDataTypeMemoryCache ]];
    }
    NSDate* dateFrom = [NSDate dateWithTimeIntervalSince1970:0];
    [[WKWebsiteDataStore defaultDataStore] removeDataOfTypes:websiteDataTypes
                                               modifiedSince:dateFrom
                                           completionHandler:^ {
                                           }];
}

- (void)backOrForward:(NSInteger)step
{
    if (step > 0) {
        if (self.webView.backForwardList.forwardList.count >= step) {
            WKBackForwardListItem* backForwardListItem = [self.webView.backForwardList itemAtIndex:step];
            [self.webView goToBackForwardListItem:backForwardListItem];
        }
    }

    if (step < 0) {
        if (self.webView.backForwardList.backList.count >= abs(step)) {
            WKBackForwardListItem* backForwardListItem = [self.webView.backForwardList itemAtIndex:step];
            [self.webView goToBackForwardListItem:backForwardListItem];
        }
    }
}

- (NSString*)getTitle
{
    return self.webView.title;
}

- (CGFloat)getPageHeight
{
    return self.webView.scrollView.contentSize.height;
}

- (void)createWebMessagePorts:(NSArray*)portsName
{
    self.webMessageChannel = [[WebMessageChannel alloc] init:portsName webView:self.webView];
    [self.webMessageChannel initJsPortInstance];
}

- (void)postWebMessage:(NSString*)message port:(NSString*)port targetUrl:(NSString*)targetUrl
{
    if (self.webMessageChannel == nil) {
        return;
    }
    [self.webMessageChannel postMessage:message portName:port uri:targetUrl];
}

- (void)postMessageEvent:(NSString*)message
{
    if (self.webMessageChannel == nil) {
        return;
    }
    [self.webMessageChannel postMessageEvent:message];
}

- (void)postMessageEventExt:(id)message {
    if (self.webMessageChannel) {
        [self.webMessageChannel postMessageEventExt:message];
    }
}

- (void)onMessageEvent:(void (^)(NSString* ocResult))callback
{
    self.messageCallBack = callback;
}

- (void)onMessageEventExt:(void (^)(id _Nullable ocResult))callback
{
    self.messageCallBackExt = callback;
}

- (void)closePort
{
    if (self.webMessageChannel == nil) {
        return;
    }
    [self.webMessageChannel closePort];
}

+ (bool)saveHttpAuthCredentials:(NSString*)host
                          realm:(NSString*)realm
                       username:(NSString*)username
                       password:(NSString*)password
{
    LOGI("AceWeb: saveHttpAuthCredentials called");
    if (host == nil || realm == nil || username == nil || password == nil) {
        return false;
    }

    NSURLCredential* credential =
        [NSURLCredential credentialWithUser:username password:password persistence:NSURLCredentialPersistencePermanent];
    NSURLProtectionSpace* protectionSpace =
        [[NSURLProtectionSpace alloc] initWithHost:host
                                              port:0
                                          protocol:NSURLProtectionSpaceHTTP
                                             realm:realm
                              authenticationMethod:NSURLAuthenticationMethodDefault];
    [[NSURLCredentialStorage sharedCredentialStorage] setDefaultCredential:credential
                                                        forProtectionSpace:protectionSpace];
    return true;
}

+ (NSURLCredential*)getHttpAuthCredentials:(NSString*)host realm:(NSString*)realm
{
    if (host == nil || realm == nil) {
        return nil;
    }
    NSURLProtectionSpace* protectionSpace =
        [[NSURLProtectionSpace alloc] initWithHost:host
                                              port:0
                                          protocol:NSURLProtectionSpaceHTTP
                                             realm:realm
                              authenticationMethod:NSURLAuthenticationMethodDefault];
    NSDictionary* userToCredentialMap =
        [[NSURLCredentialStorage sharedCredentialStorage] credentialsForProtectionSpace:protectionSpace];
    return userToCredentialMap.allValues.lastObject;
}

+ (bool)existHttpAuthCredentials
{
    LOGI("AceWeb: existHttpAuthCredentials called");
    NSURLCredentialStorage* store = [NSURLCredentialStorage sharedCredentialStorage];
    if ([[store allCredentials] count] > 0) {
        return true;
    }
    return false;
}

+ (bool)deleteHttpAuthCredentials
{
    LOGI("AceWeb: deleteHttpAuthCredentials called");
    NSURLCredentialStorage* store = [NSURLCredentialStorage sharedCredentialStorage];
    if (store == nil) {
        return false;
    }

    for (NSURLProtectionSpace* protectionSpace in [store allCredentials]) {
        NSDictionary* userToCredentialMap =
            [[NSURLCredentialStorage sharedCredentialStorage] credentialsForProtectionSpace:protectionSpace];
        for (NSString* user in userToCredentialMap) {
            NSURLCredential* credential = [userToCredentialMap objectForKey:user];
            [store removeCredential:credential forProtectionSpace:protectionSpace];
        }
    }
    return true;
}

- (bool)accessStep:(NSInteger)step
{
    if(step >= 0 && self.webView.backForwardList.forwardList.count >= step){
        return true;
    }

    if(step < 0 && self.webView.backForwardList.backList.count >= abs(step)){
        return true;
    }
    return false;
}

- (void)scrollTo:(CGFloat)x y:(CGFloat)y
{
    if ([[NSString stringWithFormat:@"%f", x] isEqualToString:@"nan"] || 
        [[NSString stringWithFormat:@"%f", y] isEqualToString:@"nan"]){
        x = 0.f;
        y = 0.f;
    }

    CGFloat offsetX = 0.f;
    CGFloat offsetY = 0.f;
    if (self.webView.scrollView.contentSize.width > self.webView.frame.size.width) {
        offsetX = x < 0 ? 0 : x;
    }
    if (self.webView.scrollView.contentSize.height > self.webView.frame.size.height) {
        offsetY = y < 0 ? 0 : y;
    }
    [self.webView.scrollView setContentOffset:CGPointMake(offsetX, offsetY) animated:YES];
}

- (void)scrollBy:(CGFloat)deltaX deltaY:(CGFloat)deltaY
{
    if ([[NSString stringWithFormat:@"%f", deltaX] isEqualToString:@"nan"] || 
        [[NSString stringWithFormat:@"%f", deltaY] isEqualToString:@"nan"]){
        deltaX = 0.f;
        deltaY = 0.f;
    }

    CGFloat offsetX = 0.f;
    CGFloat offsetY = 0.f;
    if (self.webView.scrollView.contentSize.width > self.webView.frame.size.width) {
        offsetX = self.webView.scrollView.contentOffset.x + deltaX;
    }
    if (self.webView.scrollView.contentSize.height > self.webView.frame.size.height) {
        offsetY = self.webView.scrollView.contentOffset.y + deltaY;
    }
    [self.webView.scrollView setContentOffset:CGPointMake(offsetX < 0 ? 0 : offsetX, offsetY < 0 ? 0 : offsetY)
                                     animated:YES];
}

- (void)zoom:(CGFloat)factor
{
    if(factor > 0 && factor <= 100) {
        [self.webView.scrollView setZoomScale:self.webView.scrollView.zoomScale * factor];
    }
}

- (void)zoomIn {
    UIScrollView *scrollView = self.webView.scrollView;
    CGFloat zoomInValue = scrollView.zoomScale * ZOOMIN_SCALE_VALUE;
    [scrollView setZoomScale:zoomInValue];
}

- (void)zoomOut {
    UIScrollView *scrollView = self.webView.scrollView;
    CGFloat zoomOutValue = scrollView.zoomScale * ZOOMOUT_SCALE_VALUE;
    [scrollView setZoomScale:zoomOutValue];
}

+ (BOOL)getWebDebuggingAccess {
    return _webDebuggingAccessInit;
}

+ (void)setWebDebuggingAccess:(bool)webDebuggingAccess
{
    _webDebuggingAccessInit = webDebuggingAccess == 1 ? YES : NO;
}

- (void)pageDown:(bool)value
{
    UIScrollView *scrollView = self.webView.scrollView;
    CGFloat screenHeight = scrollView.bounds.size.height;
    CGFloat contentHeight = scrollView.contentSize.height - screenHeight;
    CGPoint currentOffset = scrollView.contentOffset;
    CGPoint newOffset;
    if(value) {
        newOffset = CGPointMake(currentOffset.x, contentHeight);
    } else {
        CGFloat halfScreenHeight = screenHeight / WEBVIEW_PAGE_HALF;
        newOffset = CGPointMake(currentOffset.x, MIN(contentHeight, currentOffset.y + halfScreenHeight));
    }
    [scrollView setContentOffset:newOffset animated:YES];
}

- (void)postUrl:(NSString*)url postData:(NSData *)postData {
    if (!url) {
        LOGE("Error:AceWeb: url is NULL");
        return;
    }
    NSURL *nsUrl = [NSURL URLWithString:url];
    if (!nsUrl) {
        LOGE("Error:AceWeb: nsUrl is NULL");
        return;
    }
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:nsUrl];
    if (request) {
        [request setHTTPMethod:@"POST"];
        [request setHTTPBody:postData];
        [request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
        [self.webView loadRequest:request];
        self.lastLoadType = WebViewLoadTypeURL;
    }
}

- (void)stop
{
    [self.webView stopLoading];
}

- (bool)isZoomAccess
{
    return self.allowZoom;
}

- (NSString*)getOriginalUrl
{
    if(self.webView &&
        self.webView.backForwardList &&
        self.webView.backForwardList.currentItem &&
        self.webView.backForwardList.currentItem.initialURL)
    {
        return self.webView.backForwardList.currentItem.initialURL.absoluteString;
    }
    return @"";
}

- (void)pageUp:(bool)value
{
    UIScrollView* scrollView = self.webView.scrollView;
    CGFloat contentHeight = scrollView.contentOffset.y;
    CGFloat viewHeight = scrollView.bounds.size.height / WEBVIEW_PAGE_HALF;
    CGPoint offset;
    if (value) {
        offset = CGPointMake(0, 0);
    } else {
        offset = CGPointMake(0, MAX(0, contentHeight - viewHeight));
    }
    [scrollView setContentOffset:offset animated:YES];
}

- (void)setCustomUserAgent:(NSString*)userAgent
{
    [self.webView setCustomUserAgent:userAgent];
}

- (NSString*)getCustomUserAgent
{
    return [self.webView customUserAgent];
}

- (void)initConfigure
{
    self.callSyncMethodMap = [[NSMutableDictionary alloc] init];
    self.downloadTasksDic = [[NSMutableDictionary alloc] init];
    self.schemeHandlerMap = [[NSMutableDictionary alloc] init];
    self.screenScale = [UIScreen mainScreen].scale;
    InjectAceWebResourceObject();
}

- (void)fireCallback:(NSString *)method params:(NSString *)params
{
    NSString *method_hash = [NSString stringWithFormat:@"%@%lld%@%@%@%@", WEB_FLAG,
                             self.incId, EVENT, PARAM_EQUALS, method, PARAM_BEGIN];
    if (self.onEvent) {
        self.onEvent(method_hash, params);
    }
}

-(int64_t)getWebId
{
    return self.incId;
}

-(void)startDownload:(NSString*)url
{
    NSURLSessionConfiguration* config = [NSURLSessionConfiguration defaultSessionConfiguration];
    config.HTTPMaximumConnectionsPerHost = 1; 
    if (!self.session) {
        self.session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];
    }
    NSURLSessionDownloadTask* downloadTask = [self.session downloadTaskWithURL:[NSURL URLWithString:url]];
    NSString* guid = [NSString stringWithFormat:@"%lld_%lu", self.incId, downloadTask.taskIdentifier];
    NSString* method = downloadTask.originalRequest.HTTPMethod ? downloadTask.originalRequest.HTTPMethod : @"";
    NSString* mimeType = downloadTask.response.MIMEType ? downloadTask.response.MIMEType : @"";
    DownloadTaskInfo* downloadTaskInfo = [[DownloadTaskInfo alloc] init];
    downloadTaskInfo.isDownload = false;
    downloadTaskInfo.downloadTask = downloadTask;
    downloadTaskInfo.filePath = @"";
    downloadTaskInfo.lastUpdateTime = [NSDate date];
    [self.downloadTasksDic setObject:downloadTaskInfo forKey:guid];
    if (self.onDownloadBeforeStartCallBack) {
        self.onDownloadBeforeStartCallBack(guid, method, mimeType, url);
    }
}

-(void)onDownloadBeforeStart:(void (^)(NSString* guid, NSString *method,
                                    NSString *mimeType, NSString *url))callback
{
    self.onDownloadBeforeStartCallBack = callback;
}

- (void)onDownloadUpdated:(void (^)(NSString* guid, NSString* state, int64_t totalBytes,
                                int64_t receivedBytes, NSString *suggestedFileName))callback
{
    self.onDownloadUpdatedCallBack = callback;
}

- (void)onDownloadFailed:(void (^)(NSString* guid, NSString* state, int64_t code))callback
{
    self.onDownloadFailedCallBack = callback;
}

- (void)onDownloadFinish:(void (^)(NSString* guid, NSString* path))callback
{
    self.onDownloadFinishCallBack = callback;
}

-(bool)webDownloadItemStart:(NSString*)guid ocPath:(NSString*) ocPath
{
    DownloadTaskInfo* downloadTaskInfo = [self.downloadTasksDic objectForKey:guid];
    if (!downloadTaskInfo) {
        return false;
    }
    NSURLSessionDownloadTask* downloadTask = downloadTaskInfo.downloadTask;
    if (downloadTask) {
        downloadTaskInfo.isDownload = true;
        downloadTaskInfo.filePath = ocPath;
        [downloadTask resume];
        return true;
    }
    return false;
}

- (bool)webDownloadItemCancel:(NSString*)guid
{
    DownloadTaskInfo* downloadTaskInfo = [self.downloadTasksDic objectForKey:guid];
    if (!downloadTaskInfo) {
        return false;
    }
    NSURLSessionDownloadTask* downloadTask = downloadTaskInfo.downloadTask;
    if (downloadTask) {
        [self.downloadTasksDic removeObjectForKey:guid];
        [downloadTask cancel];
        self.onDownloadFailedCallBack(guid, @"CANCELED", 0);
        for (NSString* key in self.downloadTasksDic) {
            DownloadTaskInfo* info = [self.downloadTasksDic objectForKey:key];
            info.lastUpdateTime = [NSDate date];
        }
        return true;
    }
    return false;
}

- (bool)webDownloadItemPause:(NSString*)guid
{
    DownloadTaskInfo* downloadTaskInfo = [self.downloadTasksDic objectForKey:guid];
    if (!downloadTaskInfo) {
        return false;
    }
    NSURLSessionDownloadTask* downloadTask = downloadTaskInfo.downloadTask;
    if (downloadTask && downloadTaskInfo.isDownload) {
        downloadTaskInfo.isDownload = false;
        [downloadTask suspend];
        self.onDownloadUpdatedCallBack(guid, @"PAUSED", 0, 0, @"");
        for (NSString* key in self.downloadTasksDic) {
            DownloadTaskInfo* info = [self.downloadTasksDic objectForKey:key];
            info.lastUpdateTime = [NSDate date];
        }
        return true;
    }
    return false;
}

- (bool)webDownloadItemResume:(NSString*)guid
{
    DownloadTaskInfo* downloadTaskInfo = [self.downloadTasksDic objectForKey:guid];
    if (!downloadTaskInfo) {
        return false;
    }
    NSURLSessionDownloadTask* downloadTask = downloadTaskInfo.downloadTask;
    if (downloadTask && !downloadTaskInfo.isDownload) {
        downloadTaskInfo.isDownload = true;
        self.onDownloadUpdatedCallBack(guid, @"PENDING", 0, 0, @"");
        for (NSString* key in self.downloadTasksDic) {
            DownloadTaskInfo* info = [self.downloadTasksDic objectForKey:key];
            info.lastUpdateTime = [NSDate date];
        }
        [downloadTask resume];
        return true;
    }
    return false;
}

- (void)registerJavaScriptMethods:(NSArray*)syncMethodList
                  asyncMethodList:(NSArray*)asyncMethodList
                          objName:(NSString*)objName
{
    if ((syncMethodList == nil && asyncMethodList == nil) || objName == nil) {
        LOGE("Error: AceWeb: registerJavaScriptMethods parameters are nil");
        return;
    }
    for (NSString* method in syncMethodList) {
        NSString* js = [NSString stringWithFormat:
            @"window.%@ = window.%@ || {};"
            "window.%@.%@ = function(...args) {"
            "window.webkit.messageHandlers.AceWebHandler.postMessage({ class: '%@', method: '%@', params: args });"
            "   return new Promise((resolve, reject) => {"
            "       try {"
            "           window.%@.%@.resolve = resolve;"
            "       } catch (e) {"
            "           reject(e.toString());"
            "       }"
            "   });"
            "};", objName, objName, objName, method, objName, method, objName, method];
        [self.webView evaluateJavaScript:js completionHandler:^(id result, NSError* error) {}];
    }

    for (NSString* method in asyncMethodList) {
        NSString* js = [NSString stringWithFormat:
            @"window.%@ = window.%@ || {};"
            "window.%@.%@ = function(...args) {"
            "window.webkit.messageHandlers.AceWebHandler.postMessage({ class: '%@', method: '%@', params: args });"
            "};", objName, objName, objName, method, objName, method];
        [self.webView evaluateJavaScript:js completionHandler:^(id result, NSError* error) {}];
    }
}

- (void)registerJavaScriptProxy:(NSString*)objName
                 syncMethodList:(NSArray*)syncMethodList
                asyncMethodList:(NSArray*)asyncMethodList
                       callback:(id (^)(NSString* objName, NSString* methodName, NSArray* args))callback
{
    LOGI("registerJavaScriptProxy objName is : %{public}s", objName.UTF8String);
    if (!self.jsReady) {
        self.syncMethodList = syncMethodList;
        self.asyncMethodList = asyncMethodList;
        self.objName = objName;
    }
    [self registerJavaScriptMethods:syncMethodList asyncMethodList:asyncMethodList objName:objName];
    self.onJavaScriptFunctionCallBack = callback;
}

- (void)deleteJavaScriptRegister:(NSString*)objName
{
    LOGI("deleteJavaScriptRegister objName is : %{public}s", objName.UTF8String);
    NSString* js = [NSString stringWithFormat:@"delete window.%@;", objName];
    [self.webView evaluateJavaScript:js completionHandler:^(id result, NSError* error) {}];
    self.syncMethodList = nil;
    self.asyncMethodList = nil;
    self.objName = nil;
    self.onJavaScriptFunctionCallBack = nil;
}

- (BOOL)setWebSchemeHandler:(NSString*)scheme handler:(const ArkWeb_SchemeHandler*)handler
{
    if (scheme == nil || handler == nullptr) {
        return NO;
    }

    NSValue* existingHandlerValue = [self.schemeHandlerMap objectForKey:scheme];
    if (existingHandlerValue) {
        ArkWeb_SchemeHandler* existingHandler = (ArkWeb_SchemeHandler*)[existingHandlerValue pointerValue];
        if (existingHandler) {
            [self.schemeHandlerMap removeObjectForKey:scheme];
        }
    }
    NSValue* handlerValue = [NSValue valueWithPointer:handler];
    [self.schemeHandlerMap setObject:handlerValue forKey:scheme];
    return YES;
}

- (BOOL)clearWebSchemeHandler
{
    if (self.schemeHandlerMap == nil) {
        return NO;
    }
    [self.schemeHandlerMap removeAllObjects];
    return YES;
}

- (ArkWeb_SchemeHandler*)getSchemeHandler:(NSString*)scheme
{
    if (scheme == nil || self.schemeHandlerMap == nil) {
        return nullptr;
    }
    
    NSValue* handlerValue = [self.schemeHandlerMap objectForKey:scheme];
    if (handlerValue) {
        return (ArkWeb_SchemeHandler*)[handlerValue pointerValue];
    }
    
    return nullptr;
}

- (void)initEventCallback
{
    // zoomAccess callback
    [self setZoomAccessCallback];
    // javaScriptAccess callback
    [self setJavaScriptAccessCallback];
    // enableHapticFeedback callback
    [self setEnableHapticFeedbackCallback];
    // minFontSize callback
    [self setMinFontSizeCallback];
    // horizontalScrollBarAccess callback
    [self setHorizontalScrollBarAccessCallback];
    // verticalScrollBarAccesss callback
    [self setVerticalScrollBarAccessCallback];
    // backgroundColor callback
    [self setBackGroundColorCallback];
    // updateRichText callback
    [self setRichText];
    // updateLayout callback
    [self setUpdateLayout];
    // touchDown callback
    [self setTouchDownCallback];
    // touchMove callback
    [self setTouchMoveCallback];
    // touchUp callback
    [self setTouchUpCallback];
    // textZoomRatio callback
    [self updateTextZoomRatio];

    [self enterFullScreenOrExitFullScreenNotifi];
}

- (void)enterFullScreenOrExitFullScreenNotifi
{
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(beginFullScreen:) name:UIWindowDidBecomeVisibleNotification object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(endFullScreen:) name:UIWindowDidBecomeHiddenNotification object:nil];
}

- (void)beginFullScreen:(NSNotification *)notify
{
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, DELAY_TIME * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
        [self ergodicKeyWindow];
        [self setVideoSize];
    });
}

- (void)ergodicKeyWindow
{
    NSArray *arr = [[[UIApplication sharedApplication] connectedScenes] allObjects];
    UIWindowScene *windowScene = (UIWindowScene *)arr[0];
    UIWindow *keyWindow = windowScene.keyWindow;
    for (UIView *transitionView in keyWindow.subviews) {
        if ([transitionView isKindOfClass:NSClassFromString(@"UITransitionView")]) {
            [self findFullScreen:transitionView];
        }
    }
}

- (void)setVideoSize
{
    CGFloat width = 0;
    CGFloat height = 0;
    NSURL *url = [NSURL URLWithString:self.videoSrc];
    AVURLAsset *asset = [AVURLAsset assetWithURL:url];
    NSArray *nSAarray = asset.tracks;
    CGSize videoSize = CGSizeZero;
    for (AVAssetTrack *track in nSAarray) {
        if ([track.mediaType isEqualToString:AVMediaTypeVideo]) {
            videoSize = track.naturalSize;
            width = videoSize.width;
            height = videoSize.height;
        }
    }
    
    AceWebFullScreenEnterObject* obj = new AceWebFullScreenEnterObject(width, height);
    FullEnterRequestExitMethod fullEnterRequestExit_callback = ^void() {
      @try {
          if ([self.viewExitFullScreen respondsToSelector:NSSelectorFromString(@"fullScreenButtonWasPressed")]) {
            [self.viewExitFullScreen performSelector:NSSelectorFromString(@"fullScreenButtonWasPressed")];
          } else if (self.viewControllerExitFull && [self.viewControllerExitFull respondsToSelector:NSSelectorFromString(@"doneButtonTapped:")]) {
            [self.viewControllerExitFull performSelector:NSSelectorFromString(@"doneButtonTapped:")];
          } else {
            LOGE("Error: exit button not found");
          }
      } @catch (NSException* exception) {
          LOGE("Error: ExitScreen call failed, reason: %{public}s", exception.reason.UTF8String);
      }
    };
    obj->SetFullEnterRequestExitCallback(fullEnterRequestExit_callback);
    AceWebObject([[self event_hashFormat:NTC_ONFULLSCREENENTER] UTF8String], [NTC_ONFULLSCREENENTER UTF8String], obj);
}

- (void)findFullScreen:(UIView *)view
{
    for (UIView *subView in view.subviews) {
        id nextResponder = [subView nextResponder];
        if ([nextResponder isKindOfClass:NSClassFromString(@"AVFullScreenViewController")]) {

            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, DELAY_TIME * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
                [self findCloseBtn:subView];
            });
            break;
        } else if ([subView isKindOfClass:NSClassFromString(@"UIDropShadowView")]) {
            [self findFullScreen:subView];
            break;
        } else if ([nextResponder isKindOfClass:NSClassFromString(@"AVPlayerViewController")]) {
            self.viewControllerExitFull = nextResponder;
            break;
        } else if ([nextResponder isKindOfClass:[UIViewController class]]) {
            [self findFullScreen:subView];
            break;
        } 
    }
}

- (void)findCloseBtn:(UIView *)view
{
    for (int i = 0; i < view.subviews.count; i++) {
        UIView *viewTemp = view.subviews[i];

        if ([viewTemp isKindOfClass:NSClassFromString(@"AVPlayerViewControllerContentView")]) {
            [self findCloseBtn:viewTemp];
            return;
        }

        if ([viewTemp isKindOfClass:NSClassFromString(@"AVMobileChromelessControlsView")]) {
            [self findCloseBtn:viewTemp];
            return;
        }

        if ([viewTemp isKindOfClass:NSClassFromString(@"AVMobileChromelessDisplayModeControlsView")]) {
            LOGI("find success");
            self.viewExitFullScreen = viewTemp;
            return;
        }
    }
}

- (void)endFullScreen:(NSNotification *)notify
{
    AceWebFullScreenExitObject* obj = new AceWebFullScreenExitObject();
    AceWebObject([[self event_hashFormat:NTC_ONFULLSCREENEXIT] UTF8String], [NTC_ONFULLSCREENEXIT UTF8String], obj);
}

- (void)setZoomAccessCallback
{
    // zoom callback
    __weak __typeof(self) weakSelf = self;
    NSString *zoom_method_hash = [self method_hashFormat:NTC_ZOOM_ACCESS];
    IAceOnCallSyncResourceMethod zoom_callback =
    ^NSString *(NSDictionary * param){
        bool isZoomEnable = [[param objectForKey:NTC_ZOOM_ACCESS] boolValue];
        __strong __typeof(weakSelf)strongSelf = weakSelf;
        if (strongSelf) {
            if(self.allowZoom == isZoomEnable) {
                LOGI("AceWeb: isZoomEnable same");
                return SUCCESS;
            }
            self.allowZoom = isZoomEnable;
            WKUserContentController *userController = [WKUserContentController new];
            NSString *injectionJSString;
            if(!self.allowZoom) {
                injectionJSString = @"var script = document.createElement('meta');"
                "script.name = 'viewport';"
                "script.content=\"width=device-width, initial-scale=1.0,maximum-scale-1.0,user-scalable=no\";"
                "document.getElementsByTagName('head')[0].appendChild(script);";
            } else {
                injectionJSString = @"var script = document.createElement('meta');"
                "script.name = 'viewport';"
                "script.content=\"width=device-width, initial-scale=1.0,user-scalable=yes\";"
                "document.getElementsByTagName('head')[0].appendChild(script);";
            }
            WKUserScript *script = [[WKUserScript alloc] initWithSource:injectionJSString injectionTime:
                                    WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:NO];
            [userController addUserScript:script];
            [self.webView.configuration.userContentController addUserScript:script];
            return SUCCESS;
        } else {
            return FAIL;
        }
    };
    [self.callSyncMethodMap setObject:[zoom_callback copy] forKey:zoom_method_hash];
}

- (void)setJavaScriptAccessCallback
{
    __weak __typeof(self) weakSelf = self;
     NSString *javascriptAccess_method_hash = [self method_hashFormat:NTC_JAVASCRIPT_ACCESS];
    IAceOnCallSyncResourceMethod javascriptAccess_callback =
    ^NSString *(NSDictionary * param){
        __strong __typeof(weakSelf)strongSelf = weakSelf;
        if (strongSelf) {
            bool isJavaScriptEnable = [[param objectForKey:NTC_JAVASCRIPT_ACCESS] boolValue];
            if(self.javascriptAccessSwitch == isJavaScriptEnable) {
                LOGI("AceWeb: javaScriptEnabled same");
                return SUCCESS;
            }
            BOOL jsWillOpen = isJavaScriptEnable ? YES : NO;
            if (@available(iOS 14.0, *)) {
                self.webView.configuration.defaultWebpagePreferences.allowsContentJavaScript = jsWillOpen;
                self.webView.configuration.preferences.javaScriptEnabled = jsWillOpen;
            } else {
                self.webView.configuration.preferences.javaScriptEnabled = jsWillOpen;
            }
            [self refresh];
            self.javascriptAccessSwitch = isJavaScriptEnable? YES : NO;

            return SUCCESS;
        } else {
            LOGE("AceWeb: javaScriptAccess fail");
            return FAIL;
        }
    };
    [self.callSyncMethodMap setObject:[javascriptAccess_callback copy] forKey:javascriptAccess_method_hash];
}

- (void)setEnableHapticFeedbackCallback
{
    __weak __typeof(self) weakSelf = self;
    NSString *enableHapticFeedback_method_hash = [self method_hashFormat:NTC_ENABLE_HAPTIC_FEEDBACK];
    IAceOnCallSyncResourceMethod enableHapticFeedback_callback =
    ^NSString *(NSDictionary * param){
        __strong __typeof(weakSelf)strongSelf = weakSelf;
        if (strongSelf) {
            bool isEnableHapticFeedback = [[param objectForKey:NTC_ENABLE_HAPTIC_FEEDBACK] boolValue];
            strongSelf.enableHapticFeedbackSwitch = isEnableHapticFeedback ? YES : NO;
            return SUCCESS;
        } else {
            LOGE("AceWeb: enableHapticFeedback fail");
            return FAIL;
        }
    };
    [self.callSyncMethodMap setObject:[enableHapticFeedback_callback copy] forKey:enableHapticFeedback_method_hash];
}

- (void)setRichText
{
    __weak __typeof(self) weakSelf = self;
    NSString* richText_method_hash = [self method_hashFormat:NTC_RICHTEXT_LOADDATA];
    IAceOnCallSyncResourceMethod richText_callback = ^NSString*(NSDictionary* param)
    {
        NSString* data = [param objectForKey:WEBVIEW_LOADDATA_DATA];
        NSString* type = [param objectForKey:WEBVIEW_LOADDATA_MIMETYPE];
        NSString* encodingName = [param objectForKey:WEBVIEW_LOADDATA_ENCODING];
        __strong __typeof(weakSelf) strongSelf = weakSelf;
        if (strongSelf) {
            strongSelf.isLoadRichText = true;
            [strongSelf loadData:data mimeType:type encoding:encodingName baseUrl:@"" historyUrl:@""];
            return SUCCESS;
        } else {
            LOGE("AceWeb: set richText fail");
            return FAIL;
        }
    };
    [self.callSyncMethodMap setObject:[richText_callback copy] forKey:richText_method_hash];
}

- (void)setUpdateLayout
{
    __weak __typeof(self) weakSelf = self;
    NSString *layout_method_hash = [self method_hashFormat:@"updateLayout"];
    IAceOnCallSyncResourceMethod layout_callback = ^NSString *(NSDictionary * param){
        LOGI("AceWeb: updateLayout called");
        __strong __typeof(weakSelf)strongSelf = weakSelf;
        if (strongSelf) {
            [strongSelf updateWebLayout:param];
            return SUCCESS;
        } else {
            LOGE("AceWeb: updateLayout fail");
            return FAIL;
        }
    };
    [self.callSyncMethodMap setObject:[layout_callback copy] forKey:layout_method_hash];
}

- (void)setMinFontSizeCallback
{
    __weak __typeof(self) weakSelf = self;
    NSString* minfontsize_method_hash = [self method_hashFormat:NTC_MINFONTSIZE];
    IAceOnCallSyncResourceMethod minFontSize_callback = ^NSString*(NSDictionary* param)
    {
        float minFontSize = [[param objectForKey:NTC_MINFONTSIZE] floatValue];
        __strong __typeof(weakSelf) strongSelf = weakSelf;
        if (strongSelf) {
            self.webView.configuration.preferences.minimumFontSize = (minFontSize / 96) * 72;
            return SUCCESS;
        } else {
            LOGE("AceWeb: minFontSize fail");
            return FAIL;
        }
    };
    [self.callSyncMethodMap setObject:[minFontSize_callback copy] forKey:minfontsize_method_hash];
}

- (void)setHorizontalScrollBarAccessCallback
{
    __weak __typeof(self) weakSelf = self;
    NSString* horizontalScrollBarAccess_method_hash = [self method_hashFormat:NTC_HORIZONTALSCROLLBAR_ACCESS];
    IAceOnCallSyncResourceMethod horizontalScrollBarAccess_callback = ^NSString*(NSDictionary* param)
    {
        bool isHorizontalScrollBarEnable = [[param objectForKey:NTC_HORIZONTALSCROLLBAR_ACCESS] boolValue];
        __strong __typeof(weakSelf) strongSelf = weakSelf;
        if (strongSelf) {
            if (!isHorizontalScrollBarEnable) {
                self.webView.scrollView.showsHorizontalScrollIndicator = NO;
            } else {
                self.webView.scrollView.showsHorizontalScrollIndicator = YES;
            }
            return SUCCESS;
        } else {
            LOGE("AceWeb: horizontalScrollBar fail");
            return FAIL;
        }
    };
    [self.callSyncMethodMap setObject:[horizontalScrollBarAccess_callback copy]
                               forKey:horizontalScrollBarAccess_method_hash];
}

- (void)setVerticalScrollBarAccessCallback
{
    __weak __typeof(self) weakSelf = self;
    NSString* verticalScrollBarAccess_method_hash = [self method_hashFormat:NTC_VERTICALSCROLLBAR_ACCESS];
    IAceOnCallSyncResourceMethod verticalScrollBarAccess_callback = ^NSString*(NSDictionary* param)
    {
        bool isVerticalScrollBarEnable = [[param objectForKey:NTC_VERTICALSCROLLBAR_ACCESS] boolValue];
        __strong __typeof(weakSelf) strongSelf = weakSelf;
        if (strongSelf) {
            if (!isVerticalScrollBarEnable) {
                self.webView.scrollView.showsVerticalScrollIndicator = NO;
            } else {
                self.webView.scrollView.showsVerticalScrollIndicator = YES;
            }
            return SUCCESS;
        } else {
            LOGE("AceWeb: horizontalScrollBar fail");
            return FAIL;
        }
    };
    [self.callSyncMethodMap setObject:[verticalScrollBarAccess_callback copy]
                               forKey:verticalScrollBarAccess_method_hash];
}

- (UIColor*)hexToColor:(int)hex
{
    return [UIColor colorWithRed:(hex >> 16 & 0xff) / 255.f
                           green:(hex >> 8 & 0xff) / 255.f
                            blue:(hex & 0xff) / 255.f
                           alpha:1];
}

- (void)setBackGroundColorCallback
{
    __weak __typeof(self) weakSelf = self;
    NSString* backGroundColor_method_hash = [self method_hashFormat:NTC_BACKGROUNDCOLOR];
    IAceOnCallSyncResourceMethod backGroundColor_callback = ^NSString*(NSDictionary* param)
    {
        int backgroundColor = [[param objectForKey:NTC_BACKGROUNDCOLOR] intValue];
        __strong __typeof(weakSelf) strongSelf = weakSelf;
        if (strongSelf) {
            if (@available(iOS 15.0, *)) {
                self.webView.underPageBackgroundColor = [self hexToColor:backgroundColor];
            } else {
                self.webView.backgroundColor = [self hexToColor:backgroundColor];
            }
            [self.webView setOpaque:NO];
            self.webView.scrollView.backgroundColor = [self hexToColor:backgroundColor];
            return SUCCESS;
        } else {
            LOGE("AceWeb: backgroundColor fail");
            return FAIL;
        }
    };
    [self.callSyncMethodMap setObject:[backGroundColor_callback copy] forKey:backGroundColor_method_hash];
}

- (void)setTouchDownCallback
{
    __weak __typeof(self) weakSelf = self;
    IAceOnCallSyncResourceMethod touchDown_callback = ^NSString *(NSDictionary * param){
        __strong __typeof(weakSelf)strongSelf = weakSelf;
        if (strongSelf) {
            // [strongSelf processCurrentTouchEvent];
            return SUCCESS;
        } else {
            LOGE("AceWeb: updateLayout fail");
            return FAIL;
        }
    };
    [self.callSyncMethodMap setObject:[touchDown_callback copy] forKey:[self method_hashFormat:@"touchDown"]];
}

- (void)setTouchMoveCallback
{
    __weak __typeof(self) weakSelf = self;
    IAceOnCallSyncResourceMethod touchMove_callback = ^NSString *(NSDictionary * param){
        __strong __typeof(weakSelf)strongSelf = weakSelf;
        if (strongSelf) {
            // [strongSelf processCurrentTouchEvent];
            return SUCCESS;
        } else {
            LOGE("AceWeb: touchMove fail");
            return FAIL;
        }
    };
    [self.callSyncMethodMap setObject:[touchMove_callback copy] forKey:[self method_hashFormat:@"touchMove"]];
}

- (void)setTouchUpCallback
{
    __weak __typeof(self) weakSelf = self;
    IAceOnCallSyncResourceMethod touchUp_callback = ^NSString *(NSDictionary * param){
        __strong __typeof(weakSelf)strongSelf = weakSelf;
        if (strongSelf) {
            // [strongSelf processCurrentTouchEvent];
            return SUCCESS;
        } else {
            LOGE("AceWeb: touchUp fail");
            return FAIL;
        }
    };
    [self.callSyncMethodMap setObject:[touchUp_callback copy] forKey:[self method_hashFormat:@"touchUp"]];
    
}

- (void)updateTextZoomRatio
{
    __weak __typeof(self) weakSelf = self;
    NSString *text_zoom_ratio_hash = [self method_hashFormat:NTC_TEXT_ZOOM_RATIO];
    IAceOnCallSyncResourceMethod text_zoom_ratio_callback = ^NSString *(NSDictionary * param) {
        __strong __typeof(weakSelf) strongSelf = weakSelf;
        NSInteger textZoomRatio = [[param objectForKey:NTC_TEXT_ZOOM_RATIO] integerValue];
        if (strongSelf) {
            strongSelf.textZoomRatio = textZoomRatio;
            if (strongSelf.jsReady) {
                [strongSelf updateTextZoomRatio:textZoomRatio];
            }
            return SUCCESS;
        } else {
            LOGE("AceWeb: textZoomRatio fail");
            return FAIL;
        }
    };
    [self.callSyncMethodMap setObject:[text_zoom_ratio_callback copy] forKey:text_zoom_ratio_hash];
}

- (void)updateTextZoomRatio:(NSInteger)textZoomRatio {
    NSString* js = [NSString stringWithFormat:
        @"var style = document.createElement('style');"
        @"style.innerHTML = 'body { -webkit-text-size-adjust: %d%% !important; }';"
        @"document.head.appendChild(style);",
        textZoomRatio];
    [self.webView evaluateJavaScript:js completionHandler:^(id result, NSError* error) {
        if (error) {
            NSString* desc = error.localizedDescription ?: @"";
            LOGE("AceWeb: updateTextZoomRatio evaluateJavaScript error: %{public}s", desc.UTF8String);
        }
    }];
}

- (NSString *)method_hashFormat:(NSString *)method
{
    return [NSString stringWithFormat:@"%@%lld%@%@%@%@", WEB_FLAG, self.incId, METHOD, PARAM_EQUALS, method, PARAM_BEGIN];
}

- (NSString *)event_hashFormat:(NSString *)method
{
    return [NSString stringWithFormat:@"%@%lld%@%@%@%@", WEB_FLAG, self.incId, EVENT, PARAM_EQUALS, method, PARAM_BEGIN];
}

-(NSString*)updateWebLayout:(NSDictionary*)paramMap
{
    NSString*  left =   [paramMap objectForKey:WEBVIEW_POSITION_LEFT];
    NSString*  top =   [paramMap objectForKey:WEBVIEW_POSITION_TOP];
    NSString*  width =  [paramMap objectForKey:WEBVIEW_WIDTH];
    NSString*  height =   [paramMap objectForKey:WEBVIEW_HEIGHT];
    
     if(self.webView == nil){
        LOGE("Error:webView is NULL");
        return FAIL;
    }
    
    CGRect tempFrame = self.webView.frame;
    tempFrame.origin.x = [left floatValue]/self.screenScale;
    tempFrame.origin.y = [top floatValue]/self.screenScale;
    tempFrame.size.height = [height floatValue]/self.screenScale;
    tempFrame.size.width = [width floatValue]/self.screenScale;
    self.webView.frame = tempFrame;
    return SUCCESS;
}

- (void)releaseObject
{
    LOGI("AceWeb releaseObject");
    [self.webView removeObserver:self forKeyPath:ESTIMATEDPROGRESS];
    [self.webView removeObserver:self forKeyPath:TITLE];
    for (UIGestureRecognizer *gestureRecognizer in self.selectionGestureRecognizers) {
        [gestureRecognizer removeTarget:self action:@selector(onSelectionLongPress:)];
    }
    [self.selectionGestureRecognizers removeAllObjects];
    [self.webView.configuration.userContentController removeScriptMessageHandlerForName:CONSOLELOG];
    [self.webView.configuration.userContentController removeScriptMessageHandlerForName:CONSOLEINFO];
    [self.webView.configuration.userContentController removeScriptMessageHandlerForName:CONSOLEERROR];
    [self.webView.configuration.userContentController removeScriptMessageHandlerForName:CONSOLEDEBUG];
    [self.webView.configuration.userContentController removeScriptMessageHandlerForName:CONSOLEWARN];
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    if (self.callSyncMethodMap) {
        for (id key in self.callSyncMethodMap) {
            IAceOnCallSyncResourceMethod block = [self.callSyncMethodMap objectForKey:key];
            block = nil;
        }
        [self.callSyncMethodMap removeAllObjects];
        self.callSyncMethodMap = nil;
    }
    [self.webView removeFromSuperview];
    self.webView = nil;
    self.preferences = nil;
    self.webpagePreferences = nil;
    self.target = nil;
    [self stopGCDTimer];

    if (self.schemeHandlerMap) {
        for (NSString* scheme in self.schemeHandlerMap) {
            NSValue* handlerValue = [self.schemeHandlerMap objectForKey:scheme];
            if (handlerValue) {
                ArkWeb_SchemeHandler* handler = (ArkWeb_SchemeHandler*)[handlerValue pointerValue];
                if (handler) {
                    delete handler;
                }
            }
        }
        [self.schemeHandlerMap removeAllObjects];
        self.schemeHandlerMap = nil;
    }
}

- (NSDictionary<NSString *, IAceOnCallSyncResourceMethod> *)getSyncCallMethod
{
    return self.callSyncMethodMap;
}

- (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error
{
    LOGE("didFailNavigation === ");
}

- (void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation
{
    NSString *param = [NSString stringWithFormat:@"%@",webView.URL];
    if (self.isLoadRichText) {
        return;
    }
    [self fireCallback:@"onPageStarted" params:param];
    [self fireCallback:@"onPageVisible" params:param];
}

- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation
{
    if(!self.jsReady) {
        [self registerJavaScriptMethods:self.syncMethodList 
            asyncMethodList:self.asyncMethodList objName:self.objName];
    }
    self.jsReady = true;
    [self registerSelectionGestures];
    [self updateTextZoomRatio:self.textZoomRatio];
    NSString *param = [NSString stringWithFormat:@"%@",webView.URL];
    if (self.isLoadRichText) {
        self.isLoadRichText = false;
        return;
    }
    [self fireCallback:@"onPageFinished" params:param];
    bool isRefreshed = false;
    NSString *currrentUrl= webView.URL.absoluteString;
    if (self.reloadUrl && [currrentUrl isEqualToString:self.reloadUrl]) {
        isRefreshed = true;
        self.reloadUrl = @"";
    }
    NSString *url = self.webView.URL.absoluteString;
    AceWebRefreshAccessedHistoryObject *obj = new AceWebRefreshAccessedHistoryObject(std::string([url UTF8String]), isRefreshed);
    AceWebObject([[self event_hashFormat:NTC_ONREFRESHACCESSED_HISTORYEVENT] UTF8String], [NTC_ONREFRESHACCESSED_HISTORYEVENT UTF8String], obj);
}

- (void)releaseResourceRequest {
    if (self.currentResourceRequest) {
        delete self.currentResourceRequest;
        self.currentResourceRequest = nullptr;
    }
    if (self.currentResourceHandler) {
        delete self.currentResourceHandler;
        self.currentResourceHandler = nullptr;
    }
}

- (void)handleInterceptedRequest:(NSString*)requestURL 
                         handler:(ArkWeb_SchemeHandler*)handler
                         webView:(WKWebView*)webView {
    if (!self.currentResourceHandler || !self.currentResourceHandler->response_) {
        return;
    }

    if (self.currentResourceHandler->response_->errorCode_ != 0) {
            AceWebErrorReceiveInfoObject *obj = new AceWebErrorReceiveInfoObject(std::string([requestURL UTF8String]),
            self.currentResourceHandler->response_->errorDescription_,
            self.currentResourceHandler->response_->errorCode_);
        AceWebObject([[self event_hashFormat:@"onErrorReceive"] UTF8String], [@"onErrorReceive" UTF8String], obj);
    } else if (self.currentResourceHandler->isFailed_) {
        AceWebErrorReceiveInfoObject *obj = new AceWebErrorReceiveInfoObject(std::string([requestURL UTF8String]),
            self.currentResourceHandler->errorDescription_, self.currentResourceHandler->errorCode_);
        AceWebObject([[self event_hashFormat:@"onErrorReceive"] UTF8String], [@"onErrorReceive" UTF8String], obj);
    } 

    if (self.currentResourceHandler->response_->url_.empty()) {
        NSString* customURL = [NSString stringWithFormat:@"%@://%@", CUSTOM_SCHEME_HANDLER, requestURL];
        NSURLRequest* newRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:customURL]];
        [webView loadRequest:newRequest];
        self.lastLoadType = WebViewLoadTypeURL;
    } else if (self.currentResourceHandler->response_->errorCode_ == 0) {
        NSString* responseURL = [NSString stringWithUTF8String:self.currentResourceHandler->response_->url_.c_str()];
        NSURL* url = [NSURL URLWithString:responseURL];
        if (url == nil || url.scheme == nil) {
            NSString* combinedURL = [NSString stringWithFormat:@"%@%@", requestURL, responseURL];
            url = [NSURL URLWithString:combinedURL];
        }
        NSURLRequest* newRequest = [NSURLRequest requestWithURL:url];
        [webView loadRequest:newRequest];
        self.lastLoadType = WebViewLoadTypeURL;
    }
    
    if (handler->on_request_stop && (self.currentResourceHandler->isFinished_ ||
        self.currentResourceHandler->isFailed_ || self.currentResourceHandler->response_->errorCode_ != 0)) {
        handler->on_request_stop(handler, self.currentResourceRequest);
    }
    if (!self.currentResourceHandler->response_->url_.empty()) {
        [self releaseResourceRequest];
    }
}

- (void)handleCustomSchemeRequest:(NSURLRequest*)request 
                     responseData:(NSData**)responseData 
                         response:(NSHTTPURLResponse**)response {
    if (self.currentResourceHandler && self.currentResourceHandler->response_) {
        *responseData = [NSData dataWithBytes:self.currentResourceHandler->buffer_.c_str()
            length:self.currentResourceHandler->bufferLen_];
        if (self.currentResourceHandler->response_->mimeType_.empty() ||
            self.currentResourceHandler->response_->mimeType_.length() <= 0) {
            const_cast<ArkWeb_Response*>(self.currentResourceHandler->response_)->mimeType_ = "text/html";
        }
        NSString* contentType = [NSString stringWithFormat:@"%@;charset=%@;",
            [NSString stringWithUTF8String:self.currentResourceHandler->response_->mimeType_.c_str()],
            [NSString stringWithUTF8String:self.currentResourceHandler->response_->encoding_.c_str()]];
        NSMutableDictionary* headers = [NSMutableDictionary dictionaryWithDictionary:@{
            @"Content-Type": contentType,
            @"Content-Length": [@((*responseData).length) stringValue],
            @"Reason-Phrase": [NSString stringWithUTF8String:self.currentResourceHandler->response_->statusText_.c_str()]
        }];
        for (const auto& header : self.currentResourceHandler->response_->headers_) {
            NSString* key = [NSString stringWithUTF8String:header.first.c_str()];
            NSString* value = [NSString stringWithUTF8String:header.second.c_str()];
            [headers setObject:value forKey:key];
        }
        *response = [[NSHTTPURLResponse alloc] initWithURL:request.URL
            statusCode:self.currentResourceHandler->response_->status_ HTTPVersion:@"HTTP/1.1"
            headerFields:headers];
    }
    [self releaseResourceRequest];
}

- (void)handleAceWebResponse:(id<WKURLSchemeTask>)urlSchemeTask {
    const auto& AceWebResponse = AceWebObjectGetResponse();
    if (!AceWebResponse) {
        LOGE("AceWeb: onInterceptRequest fail");
        NSError *error = [NSError errorWithDomain:@"onInterceptRequest fail" code:HTTP_STATUS_GATEWAY_TIMEOUT userInfo:nil];
        [urlSchemeTask didFailWithError:error];
        return;
    }
    __weak __typeof(self) weakSelf = self;
    [self startGCDTimer:^(bool timeout) {
        __strong __typeof(weakSelf)strongSelf = weakSelf;
        if (!strongSelf) {
            LOGE("AceWeb: onInterceptRequest fail");
            NSError *error = [NSError errorWithDomain:@"onInterceptRequest fail" code:HTTP_STATUS_GATEWAY_TIMEOUT userInfo:nil];
            [urlSchemeTask didFailWithError:error];
            return;
        }
        NSURLRequest* request = urlSchemeTask.request;
        if (timeout) {
            dispatch_async(dispatch_get_main_queue(), ^{
                const std::string& rawData = "<html><body><h1>Response Timeout</h1><p>The request has timed out.</p></body></html>";
                NSData* responseData = [NSData dataWithBytes:rawData.c_str() length:rawData.size()];
                NSString* contentType = @"text/html;charset=tf-8;";
                NSMutableDictionary* headers = [NSMutableDictionary dictionaryWithDictionary:@{
                    @"Content-Type": contentType,
                    @"Content-Length": [@(responseData.length) stringValue],
                    @"Reason-Phrase": @"Response timed out"
                }];
                NSHTTPURLResponse* response = [[NSHTTPURLResponse alloc] initWithURL:request.URL
                    statusCode:HTTP_STATUS_GATEWAY_TIMEOUT HTTPVersion:@"HTTP/1.1"
                    headerFields:headers];
                [urlSchemeTask didReceiveResponse:response];
                [urlSchemeTask didReceiveData:responseData];
                [urlSchemeTask didFinish];
                [strongSelf stopGCDTimer];
            });
            return;
        }
        bool isReady = AceWebResponse->GetResponseStatus();
        if (isReady) {
            dispatch_async(dispatch_get_main_queue(), ^{
                const std::string& rawData = AceWebResponse->GetData();
                NSData* responseData = [NSData dataWithBytes:rawData.c_str() length:rawData.size()];
                NSString* contentType = [NSString stringWithFormat:@"%@;charset=%@;",
                    [NSString stringWithUTF8String:AceWebResponse->GetMimeType().c_str()],
                    [NSString stringWithUTF8String:AceWebResponse->GetEncoding().c_str()]];
                NSMutableDictionary* headers = [NSMutableDictionary dictionaryWithDictionary:@{
                    @"Content-Type": contentType,
                    @"Content-Length": [@(responseData.length) stringValue],
                    @"Reason-Phrase": [NSString stringWithUTF8String:AceWebResponse->GetReason().c_str()]
                }];
                const auto& cppHeaders = AceWebResponse->GetHeaders();
                for (const auto& header : cppHeaders) {
                    NSString* key = [NSString stringWithUTF8String:header.first.c_str()];
                    NSString* value = [NSString stringWithUTF8String:header.second.c_str()];
                    [headers setObject:value forKey:key];
                }
                NSHTTPURLResponse* response = [[NSHTTPURLResponse alloc] initWithURL:request.URL
                    statusCode:AceWebResponse->GetStatusCode() HTTPVersion:@"HTTP/1.1"
                    headerFields:headers];
                [urlSchemeTask didReceiveResponse:response];
                [urlSchemeTask didReceiveData:responseData];
                [urlSchemeTask didFinish];
                [strongSelf stopGCDTimer];
            });
        }
    }];
}

- (void)startGCDTimer:(nonnull void (^)(bool timeout))block {
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(TIMEOUT_S * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        if (_timer) {
            block(true);
        }
    });
    if (!_timer) {
        dispatch_queue_t queue = dispatch_queue_create("com.arkuix.timer.queue", DISPATCH_QUEUE_SERIAL);
        _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
        dispatch_source_set_timer(_timer, DISPATCH_TIME_NOW, POLLING_INTERVAL_MS * NSEC_PER_MSEC, 10 * NSEC_PER_MSEC);
        dispatch_source_set_event_handler(_timer, ^{
            block(false);
        });
        dispatch_resume(_timer);
    }
}

- (void)stopGCDTimer {
    if (_timer) {
        dispatch_source_cancel(_timer);
        _timer = nil;
    }
}

#pragma mark - WKURLSchemeHandler
- (void)webView:(WKWebView *)webView startURLSchemeTask:(id<WKURLSchemeTask>)urlSchemeTask {
    NSURLRequest* request = urlSchemeTask.request;
    if ([request.URL.scheme isEqualToString:CUSTOM_SCHEME_HANDLER]) {
        NSHTTPURLResponse* response = nil;
        NSData* responseData = nil;
        NSError* error = nil;
        [self handleCustomSchemeRequest:request responseData:&responseData response:&response];
        [urlSchemeTask didReceiveResponse:response];
        [urlSchemeTask didReceiveData:responseData];
        [urlSchemeTask didFinish];
    } else {
        [self handleAceWebResponse:urlSchemeTask];
    }
}

- (void)webView:(WKWebView *)webView stopURLSchemeTask:(id<WKURLSchemeTask>)urlSchemeTask {
    ArkWeb_SchemeHandler* handler = [self getSchemeHandler:urlSchemeTask.request.URL.scheme];
    if (handler && handler->on_request_stop) {
        handler->on_request_stop(handler, self.currentResourceRequest);
    }
    [self stopGCDTimer];
}

- (void)webView:(WKWebView *)webView
    decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction
                    decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
{
    NSString* requestURL =
            navigationAction.request.URL.absoluteString ? navigationAction.request.URL.absoluteString : @"";
    NSString *referer = navigationAction.request.allHTTPHeaderFields[@"Referer"];
    self.referrer = referer ? referer : @"";
    if (navigationAction.targetFrame.isMainFrame) {
        self.isMainFrame = YES;
        self.mainFrameUrl = requestURL;
    }
    if (navigationAction.navigationType == WKNavigationTypeReload ||
        (navigationAction.navigationType == WKNavigationTypeBackForward &&
        requestURL &&
        [requestURL isEqualToString:webView.backForwardList.currentItem.URL.absoluteString])) {
        self.reloadUrl = requestURL;
    }
    if ([self handleOverrideUrlLoading:navigationAction decisionHandler:decisionHandler]) {
        decisionHandler(WKNavigationActionPolicyCancel);
        return;
    }
    AceWebErrorReceiveInfoObject* obj = new AceWebErrorReceiveInfoObject(std::string([requestURL UTF8String]), "", 0);
    if (AceWebObjectWithBoolReturn(
            [[self event_hashFormat:NTC_ONLOADINTERCEPT] UTF8String], [NTC_ONLOADINTERCEPT UTF8String], obj)) {
        decisionHandler(WKNavigationActionPolicyCancel);
        return;
    }

    if (![requestURL hasPrefix:[NSString stringWithFormat:@"%@://", CUSTOM_SCHEME]] && AceWebObjectWithResponseReturn(
            [[self event_hashFormat:NTC_ONINTERCEPTREQUEST] UTF8String], [NTC_ONINTERCEPTREQUEST UTF8String], obj)) {
        NSString* customURL = [NSString stringWithFormat:@"%@://%@", CUSTOM_SCHEME, requestURL];
        NSURLRequest* newRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:customURL]];
        [webView loadRequest:newRequest];
        decisionHandler(WKNavigationActionPolicyCancel);
        return;
    }
    NSString* requestScheme = navigationAction.request.URL.scheme;
    ArkWeb_SchemeHandler* handler = [self getSchemeHandler:requestScheme];
    if (handler && handler->on_request_start) {
        bool intercept = false;
        self.currentResourceRequest = CreateResourceRequest(navigationAction);
        self.currentResourceHandler = new ArkWeb_ResourceHandler();
        handler->on_request_start(handler, self.currentResourceRequest,
            self.currentResourceHandler, &intercept);
        if (intercept) {
            [self handleInterceptedRequest:requestURL
                                   handler:handler
                                   webView:webView];
            decisionHandler(WKNavigationActionPolicyCancel);
            return;
        } else {
            [self releaseResourceRequest];
        }
    }
    
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 140500
    if (@available(iOS 14.5, *)) {
        if (navigationAction.shouldPerformDownload) {
            decisionHandler(WKNavigationActionPolicyDownload);
            return;
        }
    }
#endif
    decisionHandler(WKNavigationActionPolicyAllow);
    return;
}

- (BOOL)handleOverrideUrlLoading:(WKNavigationAction *)navigationAction decisionHandler:
(void (^)(WKNavigationActionPolicy))decisionHandler
{
    NSString* url =
            navigationAction.request.URL.absoluteString ? navigationAction.request.URL.absoluteString : @"";

    if (![self iSTraggerOverrideUrlLoading:navigationAction url:url]) {
        return NO;
    }

    AceWebErrorReceiveInfoObject* obj = new AceWebErrorReceiveInfoObject(std::string([url UTF8String]), "", 0);
    if (AceWebObjectWithBoolReturn(
            [[self event_hashFormat:NTC_ONOVERRIDEURLLOADING] UTF8String], 
            [NTC_ONOVERRIDEURLLOADING UTF8String], 
            obj)) {
        return YES;
    }
    return NO;
}

- (BOOL)iSTraggerOverrideUrlLoading:(WKNavigationAction *)navigationAction url:(NSString *)url
{
    if ([[navigationAction.request.HTTPMethod uppercaseString] isEqualToString:@"POST"]) {
        return NO;
    }

    if ([url hasPrefix:@"javascript:"]) {
        return NO;
    }

    if (self.isLoadUrl) {
        self.isLoadUrl = NO;
        return NO;
    }
    BOOL isIFrame = (navigationAction.targetFrame != nil) && 
                !navigationAction.targetFrame.isMainFrame;
    BOOL isWebURL = [url hasPrefix:@"http://"] || [url hasPrefix:@"https://"];
    if (isIFrame) {
        if (isWebURL || [url hasPrefix:@"about:blank"]) {
            return NO;
        }
    } else  {
        if (navigationAction.navigationType != WKNavigationTypeLinkActivated) {
            return NO;
        }
    }
    return YES;
}

- (void)webView:(WKWebView*)webView
    decidePolicyForNavigationResponse:(WKNavigationResponse*)navigationResponse
                      decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler
{
    NSHTTPURLResponse* response = (NSHTTPURLResponse*)navigationResponse.response;
    if (response && response.statusCode >= self.httpErrorCode) {
        NSString* requestURL = response.URL.absoluteString ? response.URL.absoluteString : @"";
        NSString* mineType = response.MIMEType ? response.MIMEType : @"";
        NSString* encodingName = response.textEncodingName ? response.textEncodingName : @"";
        AceWebHttpErrorReceiveObject* obj = new AceWebHttpErrorReceiveObject(std::string([requestURL UTF8String]),
            std::string([mineType UTF8String]), std::string([encodingName UTF8String]), response.statusCode);
        AceWebObject(
            [[self event_hashFormat:NTC_ONHTTPERRORRECEIVE] UTF8String], [NTC_ONHTTPERRORRECEIVE UTF8String], obj);
    }
    if (@available(iOS 14.5, *)) {
        if (!navigationResponse.canShowMIMEType) {
            decisionHandler(WKNavigationResponsePolicyDownload);
            return;
        }
    }
    decisionHandler(WKNavigationResponsePolicyAllow);
}

- (void)observeValueForKeyPath:(NSString*)keyPath
                      ofObject:(id)object
                        change:(NSDictionary<NSKeyValueChangeKey, id>*)change
                       context:(void*)context
{
    if ([keyPath isEqualToString:NSStringFromSelector(@selector(estimatedProgress))] && object == _webView) {
        NSString* param = [NSString stringWithFormat:@"%.f", _webView.estimatedProgress * 100];
        [self fireCallback:NTC_ONPROGRESSCHANGED params:param];
    } else if ([keyPath isEqualToString:TITLE] && object == _webView) {
        _webView.title == nil ? [self fireCallback:NTC_ONRECEIVEDTITLE params:@""]
                              : [self fireCallback:NTC_ONRECEIVEDTITLE params:_webView.title];
    }
}

#pragma mark - UIScrollViewDelegate
- (void)registerSelectionGestures
{
    if (!self.webView) {
        return;
    }
    [self addSelectionGesturesForView:self.webView];
}

- (void)addSelectionGesturesForView:(UIView *)view
{
    for (UIGestureRecognizer *gestureRecognizer in view.gestureRecognizers) {
        if ([self.selectionGestureRecognizers containsObject:gestureRecognizer]) {
            continue;
        }
        if ([gestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]]) {
            [gestureRecognizer addTarget:self action:@selector(onSelectionLongPress:)];
            [self.selectionGestureRecognizers addObject:gestureRecognizer];
        }
    }
    for (UIView *subview in view.subviews) {
        [self addSelectionGesturesForView:subview];
    }
}

- (void)resetSelectionDragState
{
    self.isSelectionDragArmed = NO;
    self.hasTriggeredDragHaptic = NO;
    self.selectionDragStartPoint = CGPointZero;
}

- (UIView *)findFirstResponderInView:(UIView *)view
{
    if (view.isFirstResponder) {
        return view;
    }
    for (UIView *subview in view.subviews) {
        UIView *firstResponder = [self findFirstResponderInView:subview];
        if (firstResponder) {
            return firstResponder;
        }
    }
    return nil;
}

- (BOOL)hasNativeTextSelection
{
    UIView *firstResponder = [self findFirstResponderInView:self.webView];
    if (!firstResponder) {
        return NO;
    }
    return [firstResponder canPerformAction:@selector(copy:) withSender:nil];
}

- (void)triggerDragHapticIfNeeded:(UIGestureRecognizer *)gestureRecognizer
{
    if (!self.enableHapticFeedbackSwitch || !self.isSelectionDragArmed ||
        self.hasTriggeredDragHaptic || ![self hasNativeTextSelection]) {
        return;
    }
    CGPoint currentPoint = [gestureRecognizer locationInView:gestureRecognizer.view];
    CGFloat deltaX = currentPoint.x - self.selectionDragStartPoint.x;
    CGFloat deltaY = currentPoint.y - self.selectionDragStartPoint.y;
    if (fabs(deltaX) < SELECTION_DRAG_THRESHOLD && fabs(deltaY) < SELECTION_DRAG_THRESHOLD) {
        return;
    }
    OHOS::Ace::Platform::HapticVibrator::StartVibraFeedback("haptic.slide");
    self.hasTriggeredDragHaptic = YES;
}

- (void)onSelectionLongPress:(UILongPressGestureRecognizer *)gestureRecognizer
{
    switch (gestureRecognizer.state) {
        case UIGestureRecognizerStateBegan:
            self.isSelectionLongPressActive = YES;
            if ([self hasNativeTextSelection]) {
                self.isSelectionDragArmed = YES;
                self.hasTriggeredDragHaptic = NO;
                self.selectionDragStartPoint = [gestureRecognizer locationInView:gestureRecognizer.view];
            } else {
                [self resetSelectionDragState];
            }
            break;
        case UIGestureRecognizerStateChanged:
            [self triggerDragHapticIfNeeded:gestureRecognizer];
            break;
        case UIGestureRecognizerStateEnded:
        case UIGestureRecognizerStateCancelled:
        case UIGestureRecognizerStateFailed:
            self.isSelectionLongPressActive = NO;
            [self resetSelectionDragState];
            break;
        default:
            break;
    }
}

- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
    AceWebOnScrollObject* obj = new AceWebOnScrollObject(scrollView.contentOffset.x, scrollView.contentOffset.y);
    AceWebObject([[self event_hashFormat:NTC_ONWILL_SCROLLSTART] UTF8String], [NTC_ONWILL_SCROLLSTART UTF8String], obj);
    self.dragStartPoint = scrollView.contentOffset;
    self.hasCalledOnScrollStart = NO;
}

- (void)scrollViewDidScroll:(UIScrollView*)scrollView
{
   float x = scrollView.contentOffset.x;
   float y = scrollView.contentOffset.y;
    if (!self.hasCalledOnScrollStart) {
        float velocityX = x - self.dragStartPoint.x;
        float velocityY = y - self.dragStartPoint.y;
        AceWebOnScrollObject* obj = new AceWebOnScrollObject(velocityX, velocityY);
        AceWebObject([[self event_hashFormat:NTC_ONSCROLLSTART] UTF8String], [NTC_ONSCROLLSTART UTF8String], obj);
        self.hasCalledOnScrollStart = YES;
        return;
    }
    float contentWidth = scrollView.contentSize.width;
    float contentHeight = scrollView.contentSize.height;
    float frameWidth = scrollView.bounds.size.width;
    float frameHeight = scrollView.bounds.size.height;
    AceWebOnScrollObject* obj = new AceWebOnScrollObject(x, y, contentWidth, contentHeight, frameWidth, frameHeight);
    AceWebObject([[self event_hashFormat:NTC_ONSCROLL] UTF8String], [NTC_ONSCROLL UTF8String], obj); 
}

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
    [self scrollViewDidEndScrolling:scrollView];
}

- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
    if (!decelerate) {
        [self scrollViewDidEndScrolling:scrollView];
    }
}

- (void)scrollViewDidEndScrolling:(UIScrollView *)scrollView {
    LOGI("AceWeb: scrollViewDidEndScrolling called");
    float x = scrollView.contentOffset.x;
    float y = scrollView.contentOffset.y;
    AceWebOnScrollObject* obj = new AceWebOnScrollObject(x, y);
    AceWebObject([[self event_hashFormat:NTC_ONSCROLLEND] UTF8String], [NTC_ONSCROLLEND UTF8String], obj);
}

- (void)scrollViewDidZoom:(UIScrollView*)scrollView
{
    float newScale = 0.f;
    if (scrollView.zoomScale) {
        newScale = scrollView.zoomScale * 100;
        AceWebOnScaleChangeObject* obj = new AceWebOnScaleChangeObject(newScale, self.oldScale);
        AceWebObject([[self event_hashFormat:NTC_ONSCALECHANGE] UTF8String], [NTC_ONSCALECHANGE UTF8String], obj);
        if (newScale != self.oldScale) {
            self.oldScale = newScale;
        }
    }
}

- (NSString*)generateJavaScriptForResult:(id)result className:(NSString*)className methodName:(NSString*)methodName
{
    if ([result isKindOfClass:[NSNumber class]]) {
        if (strcmp([result objCType], @encode(char)) == 0) {
            return [NSString stringWithFormat:@"window.%@.%@.resolve(%@);", className, methodName,
                [result boolValue] ? @"true" : @"false"];
        } else if (strcmp([result objCType], @encode(int)) == 0) {
            return [NSString stringWithFormat:@"window.%@.%@.resolve(%d);", className, methodName, [result intValue]];
        } else if (strcmp([result objCType], @encode(double)) == 0) {
            return [NSString stringWithFormat:@"window.%@.%@.resolve(%f);", className, methodName, [result doubleValue]];
        }
    } else if ([result isKindOfClass:[NSArray class]] || [result isKindOfClass:[NSDictionary class]]) {
        NSData* jsonData = [NSJSONSerialization dataWithJSONObject:result options:0 error:nil];
        NSString* jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
        return [NSString stringWithFormat:@"window.%@.%@.resolve(%@);", className, methodName, jsonString];
    } else {
        return [NSString stringWithFormat:@"window.%@.%@.resolve('%@');", className, methodName, result];
    }
}

- (void)userContentController:(WKUserContentController*)userContentController
      didReceiveScriptMessage:(WKScriptMessage*)message
{
    int messageLevel = 2;
    NSString* messageBody = message.body ? [NSString stringWithFormat:@"%@", message.body] : @"";
    if ([message.name hasPrefix:CONSOLEDEBUG]) {
        messageLevel = 1;
    } else if ([message.name hasPrefix:CONSOLEERROR]) {
        messageLevel = 4;
    } else if ([message.name hasPrefix:CONSOLEINFO] || [message.name hasPrefix:CONSOLELOG]) {
        messageLevel = 2;
    } else if ([message.name hasPrefix:CONSOLEWARN]) {
        messageLevel = 3;
    } else if ([message.name hasPrefix:@"onWebMessagePortMessage"]) {
        if (self.messageCallBack != nil) {
            self.messageCallBack(messageBody);
        }
        if (self.messageCallBackExt != nil) {
            self.messageCallBackExt(message.body);
        }
        return;
    } else if ([message.name isEqualToString:@"onTextSelectionChanged"]) {
        [self handleTextSelectionChangedMessage:message];
        return;
    } else if ([message.name isEqualToString:@"videoPlayed"]) {
        NSString *videoSrc = message.body;
        self.videoSrc = videoSrc;
        return;
    } else if ([message.name isEqualToString:@"AceWebHandler"] && self.onJavaScriptFunctionCallBack) {
        [self handleAceWebHandlerMessage:message];
        return;
    }
    AceWebOnConsoleObject* obj = new AceWebOnConsoleObject(std::string([messageBody UTF8String]), messageLevel);
    AceWebObjectWithBoolReturn(
        [[self event_hashFormat:NTC_ONCONSOLEMESSAGE] UTF8String], [NTC_ONCONSOLEMESSAGE UTF8String], obj);
}

- (void)handleTextSelectionChangedMessage:(WKScriptMessage*)message
{
    NSDictionary* selectionInfo = (NSDictionary *)message.body;
    NSString* selectedText = [selectionInfo[@"text"] isKindOfClass:[NSString class]] ? selectionInfo[@"text"] : @"";
    if (selectedText.length == 0) {
        return;
    }
    if (self.enableHapticFeedbackSwitch && !self.isSelectionLongPressActive) {
        OHOS::Ace::Platform::HapticVibrator::StartVibraFeedback("haptic.slide");
    }
}

- (void)handleAceWebHandlerMessage:(WKScriptMessage*)message
{
    NSDictionary* messageBody = (NSDictionary *)message.body;
    NSString* className = messageBody[@"class"];
    NSString* methodName = messageBody[@"method"];
    NSArray* params = [NSArray array];
    if ([messageBody[@"params"] isKindOfClass:NSArray.class]) {
        params = [NSArray arrayWithArray:messageBody[@"params"]];
    } else {
        NSString* jsonString = [NSString stringWithFormat:@"%@", messageBody[@"params"]];
        NSData* jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
        NSError* error;
        NSArray* array = [NSJSONSerialization JSONObjectWithData:jsonData
                            options:NSJSONReadingMutableContainers error:&error];
        if (!array) {
            LOGE("Failed to parse JSON errorCode: %{public}ld", (long)error.code);
        } else {
            params = array;
        }
    }
    id result = self.onJavaScriptFunctionCallBack(className, methodName, params);
    if (result != nil && ![result isEqual:@""]) {
        NSString* js = [self generateJavaScriptForResult:result className:className methodName:methodName];
        [self.webView evaluateJavaScript:js completionHandler:nil];
    }
}

- (void)webView:(WKWebView*)webView
    runJavaScriptAlertPanelWithMessage:(NSString*)message
                      initiatedByFrame:(WKFrameInfo*)frame
                     completionHandler:(void (^)(void))completionHandler
{
    NSString* url = [self getUrl];
    DialogResultMethod dialogResult_callback = ^void(int action, std::string promptResult) {
      @try {
          if (action == static_cast<int>(AceWebHandleResult::CONFIRM)) {
              completionHandler();
              return;
          } else if (action == static_cast<int>(AceWebHandleResult::CANCEL)) {
              completionHandler();
              return;
          }
          completionHandler();
      } @catch (NSException* exception) {
          LOGE("Error: alert dialog completionHandler call failed");
      }
    };
    AceWebDialogObject* obj =
        new AceWebDialogObject(std::string([url UTF8String]), std::string([message UTF8String]), "");
    obj->SetDialogResultCallback(dialogResult_callback);
    if (!AceWebObjectWithBoolReturn([[self event_hashFormat:@"onAlert"] UTF8String], [@"onAlert" UTF8String], obj)) {
        @try {
            completionHandler();
        } @catch (NSException* exception) {
            LOGE("Error: alert dialog completionHandler call failed");
        }
    }
}

- (void)webView:(WKWebView*)webView
    runJavaScriptConfirmPanelWithMessage:(NSString*)message
                        initiatedByFrame:(WKFrameInfo*)frame
                       completionHandler:(void (^)(BOOL result))completionHandler
{
    NSString* url = [self getUrl];
    DialogResultMethod dialogResult_callback = ^void(int action, std::string promptResult) {
      @try {
          if (action == static_cast<int>(AceWebHandleResult::CONFIRM)) {
              completionHandler(true);
              return;
          } else if (action == static_cast<int>(AceWebHandleResult::CANCEL)) {
              completionHandler(false);
              return;
          }
          completionHandler(false);
      } @catch (NSException* exception) {
          LOGE("Error: confirm dialog completionHandler call failed");
      }
    };
    AceWebDialogObject* obj =
        new AceWebDialogObject(std::string([url UTF8String]), std::string([message UTF8String]), "");
    obj->SetDialogResultCallback(dialogResult_callback);
    if (!AceWebObjectWithBoolReturn(
            [[self event_hashFormat:@"onConfirm"] UTF8String], [@"onConfirm" UTF8String], obj)) {
        @try {
            completionHandler(false);
        } @catch (NSException* exception) {
            LOGE("Error: confirm dialog completionHandler call failed");
        }
    }
}

- (void)webView:(WKWebView*)webView
    runJavaScriptTextInputPanelWithPrompt:(NSString*)prompt
                              defaultText:(NSString*)defaultText
                         initiatedByFrame:(WKFrameInfo*)frame
                        completionHandler:(void (^)(NSString* result))completionHandler
{
    NSString* url = [self getUrl];
    DialogResultMethod dialogResult_callback = ^void(int action, std::string promptResult) {
      @try {
          NSString* nsResult =
              [NSString stringWithCString:promptResult.c_str() encoding:[NSString defaultCStringEncoding]];
          if (action == static_cast<int>(AceWebHandleResult::PROMPTCONFIRM)) {
              completionHandler(nsResult);
              return;
          } else if (action == static_cast<int>(AceWebHandleResult::CANCEL)) {
              completionHandler(nil);
              return;
          }
          completionHandler(nil);
      } @catch (NSException* exception) {
          LOGE("Error: prompt dialog completionHandler call failed");
      }
    };
    AceWebDialogObject* obj = new AceWebDialogObject(
        std::string([url UTF8String]), std::string([prompt UTF8String]), std::string([defaultText UTF8String]));
    obj->SetDialogResultCallback(dialogResult_callback);
    if (!AceWebObjectWithBoolReturn([[self event_hashFormat:@"onPrompt"] UTF8String], [@"onPrompt" UTF8String], obj)) {
        @try {
            completionHandler(nil);
        } @catch (NSException* exception) {
            LOGE("Error: prompt dialog completionHandler call failed");
        }
    }
}

- (void)webView:(WKWebView*)webView
    requestMediaCapturePermissionForOrigin:(WKSecurityOrigin*)origin
                          initiatedByFrame:(WKFrameInfo*)frame
                                      type:(WKMediaCaptureType)type
                           decisionHandler:(void (^)(WKPermissionDecision decision))decisionHandler
    API_AVAILABLE(ios(15.0))
{
    NSString* host = origin.host ? origin.host : @"";
    int permissionType = 0;
    switch (type) {
        case WKMediaCaptureTypeCamera:
            permissionType = 1;
            break;
        case WKMediaCaptureTypeMicrophone:
            permissionType = 2;
            break;
        case WKMediaCaptureTypeCameraAndMicrophone:
            permissionType = 3;
            break;
        default:
            break;
    }
    PermissionRequestMethod permissionRequest_callback = ^void(int action, int ResourcesId) {
      @try {
          if (action == static_cast<int>(AceWebHandleResult::DENY)) {
              decisionHandler(WKPermissionDecisionDeny);
              return;
          } else if (ResourcesId > 0 && action == static_cast<int>(AceWebHandleResult::GRANT)) {
              decisionHandler(WKPermissionDecisionGrant);
              return;
          }
          decisionHandler(WKPermissionDecisionPrompt);
      } @catch (NSException* exception) {
          LOGE("Error: request permission completionHandler call failed");
      }
    };
    AceWebPermissionRequestObject* obj =
        new AceWebPermissionRequestObject(std::string([host UTF8String]), permissionType);
    obj->SetPermissionResultCallback(permissionRequest_callback);
    if (!AceWebObjectWithBoolReturn(
            [[self event_hashFormat:@"onPermissionRequest"] UTF8String], [@"onPermissionRequest" UTF8String], obj)) {
        @try {
            decisionHandler(WKPermissionDecisionPrompt);
        } @catch (NSException* exception) {
            LOGE("Error: request permission completionHandler call failed");
        }
    }
}

- (void)webView:(WKWebView*)webView
    didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge*)challenge
                    completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition,
                                          NSURLCredential* credential))completionHandler
{
    if (challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust) {
        [self handleAuthenticationMethodServerTrustWithUrl:
            [NSString stringWithFormat:@"%@://%@/", challenge.protectionSpace.protocol, challenge.protectionSpace.host]
            challenge:challenge completionHandler:completionHandler];
        return;
    }
    NSString* host = challenge.protectionSpace.host ? challenge.protectionSpace.host : @"";
    NSString* realm = challenge.protectionSpace.realm ? challenge.protectionSpace.realm : @"";

    HttpAuthRequestMethod authRequest_callback = ^bool(int action, std::string name, std::string pwd) {
      NSString* nsName = [NSString stringWithCString:name.c_str() encoding:[NSString defaultCStringEncoding]];
      NSString* nsPwd = [NSString stringWithCString:pwd.c_str() encoding:[NSString defaultCStringEncoding]];
      @try {
          if (action == static_cast<int>(AceWebHandleResult::CONFIRM)) {
              NSURLCredential* credential = [NSURLCredential credentialWithUser:nsName
                                                                       password:nsPwd
                                                                    persistence:NSURLCredentialPersistencePermanent];
              completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
              return true;
          } else if (action == static_cast<int>(AceWebHandleResult::CANCEL)) {
              completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
              return false;
          } else if (action == static_cast<int>(AceWebHandleResult::HTTPAUTHINFOSAVED)) {
              NSURLCredential* credential = [AceWeb getHttpAuthCredentials:host realm:realm];
              if (credential == nil) {
                  return false;
              }
              completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
              return true;
          }
          completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
          return false;
      } @catch (NSException* exception) {
          LOGE("Error: Http auth request completionHandler call failed");
          return false;
      }
    };
    AceWebOnHttpAuthRequestObject* obj =
        new AceWebOnHttpAuthRequestObject(std::string([host UTF8String]), std::string([realm UTF8String]));
    obj->SetAuthResultCallback(authRequest_callback);
    if (!AceWebObjectWithBoolReturn(
            [[self event_hashFormat:@"onHttpAuthRequest"] UTF8String], [@"onHttpAuthRequest" UTF8String], obj)) {
        @try {
            completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
        } @catch (NSException* exception) {
            LOGE("Error: Http auth request completionHandler call failed");
        }
    }
}

- (void)handleAuthenticationMethodServerTrustWithUrl:(NSString *)url challenge:(NSURLAuthenticationChallenge*)challenge
    completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition,
                          NSURLCredential* credential))completionHandler 
{
    NSString *challengeKey = [NSString stringWithFormat:@"%@:%@:%ld", 
        challenge.protectionSpace.protocol, challenge.protectionSpace.host, (long)challenge.protectionSpace.port];
    if ([self.handleSslErrorUrls containsObject:challengeKey] ||
        [[AceWebInfoManager sharedManager].authChallengeUseCredentials containsObject:challengeKey]) {
        completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
        return;
    }
    [self.handleSslErrorUrls addObject:challengeKey];
    SecTrustRef serverTrust = challenge.protectionSpace.serverTrust;
    SecTrustResultType result;
    OSStatus status = errSecSuccess;
    int errorCode = 0;
    if (@available(iOS 12.0, *)) {
        BOOL isTrusted = NO;
        CFErrorRef errorRef = NULL;
        isTrusted = SecTrustEvaluateWithError(serverTrust, &errorRef);
        result = isTrusted ? kSecTrustResultProceed : kSecTrustResultRecoverableTrustFailure;
        if (errorRef) {
            NSError *error = (__bridge NSError *)errorRef;
            errorCode = [self handleSslErrorEventErrorCode:(int)error.code];
            CFRelease(errorRef);
        }
    } else {
        status = SecTrustEvaluate(serverTrust, &result);
    }
    if (status == errSecSuccess && (result == kSecTrustResultProceed || result == kSecTrustResultUnspecified)) {
        NSURLCredential *credential = [NSURLCredential credentialForTrust:serverTrust];
        completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
    } else {
        __block BOOL completionHandlerCalled = NO;
        void (^safeCompletionHandler)(NSURLSessionAuthChallengeDisposition, NSURLCredential *) =
            ^(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential) {
                if (!completionHandlerCalled) {
                    completionHandlerCalled = YES;
                    completionHandler(disposition, credential);
                    if (disposition == NSURLSessionAuthChallengeUseCredential) {
                        [[AceWebInfoManager sharedManager].authChallengeUseCredentials addObject:challengeKey];
                    }
                }
            };
        NSString *host = challenge.protectionSpace.host;
        BOOL isMainFrame = [self.mainFrameUrl containsString:host];
        [self handleSslErrorEventWithUrl:url isMainFrame:isMainFrame
            serverTrust:serverTrust errorCode:errorCode completionHandler:safeCompletionHandler];
    }
}

- (int)handleSslErrorEventErrorCode:(NSInteger)errorCode {
    SslError error = SslError::INVALID;
    switch (errorCode) {
    case errSecHostNameMismatch:
        error = SslError::HOST_MISMATCH;
        break;
    case errSecCreateChainFailed:
    case errSecCertificateExpired:
    case errSecCertificateNotValidYet:
        error = SslError::DATE_INVALID;
        break;
    case errSecNotTrusted:
    case errSecVerifyActionFailed:
        error = SslError::UNTRUSTED;
        break;
    }
    return static_cast<int32_t>(error);
}

- (void)handleSslErrorEventWithUrl:(NSString *)url isMainFrame:(BOOL)isMainFrame 
    serverTrust:(SecTrustRef)serverTrust errorCode:(int)errorCode
    completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition,
                          NSURLCredential* credential))completionHandler
{
    SslEventMethod sslErrorEventConfirm_callback = ^void(int action) {
        @try {
            NSURLCredential *credential = [NSURLCredential credentialForTrust:serverTrust];
            completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
        } @catch (NSException* exception) {
            completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
            LOGE("AceWeb error: alert dialog confirm call failed");
        }
    };
    SslErrorEventCancelMethod sslErrorEventCancel_callBack = ^void(bool abortLoading) {
        @try {
            completionHandler(NSURLSessionAuthChallengeRejectProtectionSpace, nil);
        } @catch (NSException* exception) {
            completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
            LOGE("AceWeb error: alert dialog cancel call failed");
        }
    };
    NSArray *certificateChain = [self getCertificateChainFromTrust:serverTrust];
    std::vector<std::string> certChainData = convertCertificateChainToDer(certificateChain);
    AceWebSslErrorEventObject* obj = new AceWebSslErrorEventObject(errorCode, std::string([[self getUrl] UTF8String]),
        std::string([url UTF8String]), std::string([self.referrer UTF8String]), 
        false, isMainFrame, certChainData);
    obj->SetSslErrorEventConfirmCallBack(sslErrorEventConfirm_callback);
    obj->SetSslErrorEventCancelCallBack(sslErrorEventCancel_callBack);
    BOOL onSslErrorEventFail = NO;
    if (!AceWebObjectWithBoolReturn(
        [[self event_hashFormat:NTC_ONSSLERROREVENT] UTF8String], [NTC_ONSSLERROREVENT UTF8String], obj)) {
        onSslErrorEventFail = YES;
    }
    if (self.isMainFrame && isMainFrame) {
        [self handleSslErrorEventReceiveWithServerTrust:serverTrust errorCode:errorCode
            certChainData:certChainData onSslErrorEventFail:onSslErrorEventFail completionHandler:completionHandler];
    } else {
        if (onSslErrorEventFail) {
            @try {
                completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
            } @catch (NSException* exception) {
                LOGE("AceWeb error: ssl error event completionHandler call failed");
            }
        }
    }
}

- (void)handleSslErrorEventReceiveWithServerTrust:(SecTrustRef)serverTrust errorCode:(int)errorCode
    certChainData:(std::vector<std::string>&)certChainData onSslErrorEventFail:(BOOL)onSslErrorEventFail
    completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition,
                          NSURLCredential* credential))completionHandler {
    SslEventMethod sslErrorEventReceiveEventCancel_callback = ^void(int action) {
        @try {
            if (action == static_cast<int>(AceWebHandleResult::CONFIRM)) {
                NSURLCredential *credential = [NSURLCredential credentialForTrust:serverTrust];
                completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
            } else {
                completionHandler(NSURLSessionAuthChallengeRejectProtectionSpace, nil);
            }
        } @catch (NSException* exception) {
            completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
            LOGE("AceWeb error: alert dialog completionHandler call failed");
        }
    };
    AceWebOnSslErrorEventReceiveEventObject *obj = new AceWebOnSslErrorEventReceiveEventObject(errorCode, certChainData);
    obj->SetWebOnSslErrorEventReceiveCallBack(sslErrorEventReceiveEventCancel_callback);
    BOOL onSslErrorEventReceiveFail = NO;
    if (!AceWebObjectWithBoolReturn(
        [[self event_hashFormat:NTC_ONSSLERROREVENTRECEIVE] UTF8String], [NTC_ONSSLERROREVENTRECEIVE UTF8String], obj)) {
        onSslErrorEventReceiveFail = YES;
    }
    if (onSslErrorEventFail && onSslErrorEventReceiveFail) {
        @try {
            completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
        } @catch (NSException* exception) {
            LOGE("AceWeb error: ssl error event completionHandler call failed");
        }
    }
}

- (NSArray *)getCertificateChainFromTrust:(SecTrustRef)trust {
    CFIndex certificateCount = SecTrustGetCertificateCount(trust);
    NSMutableArray *certificateChain = [NSMutableArray arrayWithCapacity:certificateCount];
    for (CFIndex i = 0; i < certificateCount; i++) {
        SecCertificateRef certificate = SecTrustGetCertificateAtIndex(trust, i);
        [certificateChain addObject:(__bridge id)certificate];
    }
    return [certificateChain copy];
}

std::string certificateToDerString(SecCertificateRef cert) {
    CFDataRef derData = SecCertificateCopyData(cert);
    if (!derData) {
        LOGE("AceWeb error: SecCertificateCopyData failed");
        return "";
    }
    const UInt8 *bytes = CFDataGetBytePtr(derData);
    CFIndex length = CFDataGetLength(derData);
    std::string der(reinterpret_cast<const char*>(bytes), length);
    CFRelease(derData);
    return der;
}

std::vector<std::string> convertCertificateChainToDer(NSArray *certificateChain) {
    std::vector<std::string> result;
    for (id certObj in certificateChain) {
        SecCertificateRef cert = (__bridge SecCertificateRef)certObj;
        result.push_back(certificateToDerString(cert));
    }
    return result;
}

- (void)webView:(WKWebView*)webView
    navigationResponse:(WKNavigationResponse*)navigationResponse
     didBecomeDownload:(WKDownload*)download
    API_AVAILABLE(ios(14.5))
{
    NSHTTPURLResponse* response = (NSHTTPURLResponse*)navigationResponse.response;
    NSString* downloadURL = response.URL.absoluteString ? response.URL.absoluteString : @"";
    NSString* mimeType = response.MIMEType ? response.MIMEType : @"";
    long contentLength = response.expectedContentLength ? response.expectedContentLength : 0;
    [webView evaluateJavaScript:@"navigator.userAgent" completionHandler:^(id result, NSError *error) {
        NSString *userAgent = result ? result : @"";
        AceWebDownloadResponseObject* obj = new AceWebDownloadResponseObject(std::string([downloadURL UTF8String]), 
            std::string([mimeType UTF8String]), contentLength, std::string([userAgent UTF8String]));
        AceWebObject([[self event_hashFormat:@"onDownloadStart"] UTF8String], [@"onDownloadStart" UTF8String], obj);
    }];
}

- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:( WKNavigation *)navigation withError:(NSError *)error
{
    NSString *errorStr = @"";
    if(webView.URL.absoluteString) {
        errorStr = webView.URL.absoluteString;
    } else {
        if (error) {
            NSDictionary *userInfo = error.userInfo;
            errorStr = userInfo[@"NSErrorFailingURLStringKey"];
        }
    }
    
    if(errorStr && error.description) {
        AceWebErrorReceiveInfoObject *obj = new AceWebErrorReceiveInfoObject(std::string([errorStr UTF8String]),
                                                                             std::string([error.description UTF8String]), error.code);
        AceWebObject([[self event_hashFormat:@"onErrorReceive"] UTF8String], [@"onErrorReceive" UTF8String], obj);
    }
}

- (void)cookiesDidChangeInCookieStore:(WKHTTPCookieStore *)cookieStore
{
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000
    if (@available(iOS 11.0, *)) {
        [cookieStore getAllCookies:^(NSArray* cookies) {
            for (NSHTTPCookie *cookie in cookies) {
                [[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookie:cookie];
            }
        }];
    }
#endif
}

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask 
                                           didWriteData:(int64_t)bytesWritten 
                                      totalBytesWritten:(int64_t)totalBytesWritten 
                              totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {
    @synchronized (self) {
        NSString* guid = [NSString stringWithFormat:@"%lld_%lu", self.incId, downloadTask.taskIdentifier];
        DownloadTaskInfo* downloadTaskInfo = [self.downloadTasksDic objectForKey:guid];
        if (!downloadTaskInfo || !self.onDownloadUpdatedCallBack) {
            return;
        }
        static NSDate* lastUpdateTime;
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            lastUpdateTime = [NSDate dateWithTimeIntervalSince1970:0];
        });
        
        NSString* earliestGuid = guid;
        NSDate* earliestDate = [NSDate date];
        for (NSString* key in self.downloadTasksDic) {
            DownloadTaskInfo* info = [self.downloadTasksDic objectForKey:key];
            if ([info.lastUpdateTime compare:earliestDate] == NSOrderedAscending && info.isDownload) {
                earliestDate = info.lastUpdateTime;
                earliestGuid = key;
            }
        }
        NSDate* now = [NSDate date];
        if ([now timeIntervalSinceDate:lastUpdateTime] > 1 && [guid isEqualToString:earliestGuid]) {
            NSString* suggestedFilename = downloadTask.response.suggestedFilename;
            downloadTaskInfo.lastUpdateTime = now;
            lastUpdateTime = now;
            dispatch_async(dispatch_get_main_queue(), ^{
                self.onDownloadUpdatedCallBack(guid, @"IN_PROGRESS", totalBytesExpectedToWrite, totalBytesWritten, suggestedFilename);
            });
        }
    }
}

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask 
    didFinishDownloadingToURL:(NSURL *)location {
    NSString* guid = [NSString stringWithFormat:@"%lld_%lu", self.incId, downloadTask.taskIdentifier];
    NSString* suggestedFilename = downloadTask.response.suggestedFilename;
    NSString* documentsPath = [NSSearchPathForDirectoriesInDomains(
        NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
    NSString* path = documentsPath;
    DownloadTaskInfo* downloadTaskInfo = [self.downloadTasksDic objectForKey:guid];
    if (downloadTaskInfo) {
        path = [documentsPath stringByAppendingPathComponent:downloadTaskInfo.filePath];
    }
    NSFileManager* fileManager = [NSFileManager defaultManager];
    if (![fileManager fileExistsAtPath:path]) {
        [fileManager createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:nil];
    }
    NSString* destinationPath = [path stringByAppendingPathComponent:suggestedFilename];
    if ([fileManager fileExistsAtPath:destinationPath]) {
        [fileManager removeItemAtPath:destinationPath error:nil];
    }
    NSURL* destinationURL = [NSURL fileURLWithPath:destinationPath];
    NSError* fileError = nil;
    [[NSFileManager defaultManager] moveItemAtURL:location toURL:destinationURL error:&fileError];
    for (NSString* key in self.downloadTasksDic) {
        DownloadTaskInfo* info = [self.downloadTasksDic objectForKey:key];
        info.lastUpdateTime = [NSDate date];
    }
    dispatch_async(dispatch_get_main_queue(), ^{
        self.onDownloadFinishCallBack(guid, path);
    });
    [self.downloadTasksDic removeObjectForKey:guid];
}

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task 
    didCompleteWithError:(nullable NSError *)error {
    if (error) {
        for (NSString* key in self.downloadTasksDic) {
            DownloadTaskInfo* info = [self.downloadTasksDic objectForKey:key];
            info.lastUpdateTime = [NSDate date];
        }
        NSString* guid = [NSString stringWithFormat:@"%lld_%lu", self.incId, task.taskIdentifier];
        dispatch_async(dispatch_get_main_queue(), ^{
            self.onDownloadFailedCallBack(guid, @"INTERRUPTED", error.code);
        });
        [self.downloadTasksDic removeObjectForKey:guid];
    }
}

- (void)setNestedScrollOptionsExt:(void *)options {
    OHOS::Ace::NestedScrollOptionsExt* cppOptions = reinterpret_cast<OHOS::Ace::NestedScrollOptionsExt*>(options);
    self.nestedOpt = [[NestedScrollOptionsExt alloc] init];
    self.nestedOpt.scrollUp = static_cast<NestedScrollMode>(cppOptions->scrollUp);
    self.nestedOpt.scrollDown = static_cast<NestedScrollMode>(cppOptions->scrollDown);
    self.nestedOpt.scrollLeft = static_cast<NestedScrollMode>(cppOptions->scrollLeft);
    self.nestedOpt.scrollRight = static_cast<NestedScrollMode>(cppOptions->scrollRight);
    self.webView.scrollView.directionalLockEnabled = NO;
    self.webScrollEnabled = YES;
}

- (void)setWebScrollEnabled:(BOOL)webScrollEnabled {
    if (_webScrollEnabled != webScrollEnabled) {
        _webScrollEnabled = webScrollEnabled;
    }
    if (_webScrollEnabled != self.webView.scrollView.scrollEnabled) {
        self.webView.scrollView.scrollEnabled = _webScrollEnabled;
    }
}
@end