У меня есть некоторый уровень уверенности, что не существует простого способа просто получить эту информацию с помощью API c publi. Таким образом, я собрал обходной путь javascript, который «достаточно хорошее» решение для моих целей. Он пытается извлечь ресурсы из проанализированного html, а затем сравнивает их с загруженными ресурсами, полученными из модуля window.performance. Главное предостережение заключается в том, что некоторые типы ресурсов вообще не обрабатываются, а другие, вероятно, упускаются.
Очевидно, его следует вызывать после того, как страница полностью загрузила то, что она собирается загрузить. Обычно это делается из метода делегата webViewDidFinishNavigation. Предоставленный аргумент завершения - это закрытие, которое вызывается с массивом заблокированных ресурсов в качестве единственного параметра.
Эта первая часть представляет собой функцию для создания javascript для извлечения ресурсов со страницы. Кажется, что с этим разделением Stackoverflow форматирует вещи лучше.
private static func buildResourceInfoJavascript() -> String {
let script = """
function extractUrls( fromCss ) {
let matches = fromCss.match(/url\\(.+?\\)/g);
if( !matches ) {
return [] ;
}
let urls = matches.map(url => url.replace(/url\\(['\\"]?(.+?)['\\"]?\\)/g, "$1"));
return urls;
}
function getPageResources() {
let pageResources = [...document.images].map(x => x.src);
pageResources = [...pageResources, ...[...document.scripts].map(x => x.src) ] ;
pageResources = [...pageResources, ...[...document.getElementsByTagName("link")].map(x => x.href) ];
[...document.styleSheets].forEach(sheet => {
if( !sheet.cssRules ) {
return ;
}
[...sheet.cssRules].forEach(rule => {
pageResources = [...pageResources, ...extractUrls( rule.cssText )];
} );
});
let inlineStyles = document.querySelectorAll( '*[style]') ;
[...inlineStyles].forEach(x => {
pageResources = [...pageResources, ...extractUrls( x.getAttributeNode("style").value )];
}) ;
let backgrounds = document.querySelectorAll( 'td[background], tr[background], table[background]') ;
[...backgrounds].forEach(x => {
pageResources.push( x.getAttributeNode("background").value );
}) ;
return pageResources.filter(x => (x != null && x != '') );
}
let pageResources = getPageResources() ;
let loadedResources = window.performance.getEntriesByType('resource').map(x => x.name );
let resourceInfo = {
'pageResources' : pageResources,
'loadedResources' : loadedResources.filter(x => (x != null && x != '') ),
};
JSON.stringify(resourceInfo);
"""
return script
}
Следующая часть - это функция, которая вызывается из делегата didFinishNavigation.
public static func getBlockedResourcesAsync( fromWebView:WKWebView, completion:@escaping (([String]) -> Void)) {
let script = buildResourceInfoJavascript()
fromWebView.evaluateJavaScript(script) { (results, error) in
guard let resultsData = (results as? String)?.data(using: .utf8) else {
NSLog("No results for getBlockedResources" )
completion( [] )
return
}
do {
let resourceInfo = try JSONSerialization.jsonObject(with: resultsData) as? [String:[String]] ?? [:]
let pageResources = Array(Set(resourceInfo["pageResources"] ?? []) )
let loadedResources = Array(Set( resourceInfo["loadedResources"] ?? []) )
let blockedResources = pageResources.filter { !loadedResources.contains($0) }
let unrecognizedResources = loadedResources.filter { !pageResources.contains($0) }
if unrecognizedResources.count > 0 {
NSLog("Didn't recognized resources \(unrecognizedResources)" )
}
completion( blockedResources )
}
catch let err {
NSLog("JSON decoding failed: \(err.localizedDescription)" )
completion([])
return
}
}
}