Sources/Instrumentation/WKWebView/WKWebViewInstrumentor.swift (65 lines of code) (raw):
//
// Copyright 2023 aliyun-sls Authors
//
// 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 Foundation
import WebKit
public class WKWebViewInstrumentor {
public static func start(configuration: WKWebViewInstrumentationConfiguration) {
let selectors = [
#selector(WKWebView.load(_:))
]
let classes = InstrumentationUtils.objc_getClassList()
let selectorsCount = selectors.count
DispatchQueue.concurrentPerform(iterations: classes.count) { iteration in
let theClass: AnyClass = classes[iteration]
guard theClass != Self.self else { return }
var selectorFound = false
var methodCount: UInt32 = 0
guard let methodList = class_copyMethodList(theClass, &methodCount) else { return }
defer { free(methodList) }
for i in 0..<selectorsCount {
for j in 0..<Int(methodCount) {
if method_getName(methodList[j]) == selectors[i] {
selectorFound = true
WKWebViewInstrumentor.injectIntoWKWebView(cls: theClass)
break
}
}
if selectorFound {
break
}
}
}
}
static func injectIntoWKWebView(cls: AnyClass) {
injectIntoLoadRequest(cls: cls)
}
static func injectIntoLoadRequest(cls: AnyClass) {
let selector = #selector(WKWebView.load(_:))
guard let original = class_getInstanceMethod(cls, selector) else { return }
var originalIMP: IMP?
let block: @convention(block) (Any, URLRequest) -> WKNavigation? = { object, request in
if let webView = object as? WKWebView {
if !(webView.holderObject is WKWebViewInstrumentation) {
let instrumentation = WKWebViewInstrumentation(webView: webView, configuration: WKWebViewInstrumentationConfiguration())
instrumentation.start()
webView.holderObject = instrumentation
}
}
let castedIMP = unsafeBitCast(originalIMP, to: (@convention(c)(Any, Selector, URLRequest) -> WKNavigation?).self)
return castedIMP(object, selector, request)
}
let swizzledIMP = imp_implementationWithBlock(unsafeBitCast(block, to: AnyObject.self))
originalIMP = method_setImplementation(original, swizzledIMP)
}
}
fileprivate extension WKWebView {
private struct AssociateKeys {
static var holderKey = "wkwebview_instrumentation_holder_key"
}
var holderObject: AnyObject? {
get {
return objc_getAssociatedObject(self, &AssociateKeys.holderKey) as AnyObject
}
set {
objc_setAssociatedObject(self, &AssociateKeys.holderKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
}