Skip to content

Commit

Permalink
Serialize content of linked style sheets with URL replacement
Browse files Browse the repository at this point in the history
https://bugs.webkit.org/show_bug.cgi?id=264308
rdar://118029249

Reviewed by Ryosuke Niwa.

We need to replace URLs in external style sheets when saving them to disk.

API test: WebArchive.SaveResourcesLink

* Source/WebCore/css/CSSStyleSheet.cpp:
(WebCore::CSSStyleSheet::cssTextWithReplacementURLs):
* Source/WebCore/css/CSSStyleSheet.h:
* Source/WebCore/html/HTMLLinkElement.cpp:
(WebCore::HTMLLinkElement::styleSheetContentWithReplacementURLs const):
* Source/WebCore/html/HTMLLinkElement.h:
* Source/WebCore/html/HTMLStyleElement.cpp:
(WebCore::HTMLStyleElement::textContentWithReplacementURLs const):
* Source/WebCore/loader/archive/cf/LegacyWebArchive.cpp:
(WebCore::LegacyWebArchive::create):
* Tools/TestWebKitAPI/Tests/WebKitCocoa/CreateWebArchive.mm:

Canonical link: https://commits.webkit.org/270329@main
  • Loading branch information
szewai committed Nov 7, 2023
1 parent 3944b4d commit c72276a
Show file tree
Hide file tree
Showing 7 changed files with 152 additions and 17 deletions.
21 changes: 21 additions & 0 deletions Source/WebCore/css/CSSStyleSheet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,27 @@ String CSSStyleSheet::debugDescription() const
return makeString("CSSStyleSheet "_s, "0x"_s, hex(reinterpret_cast<uintptr_t>(this), Lowercase), ' ', href());
}

String CSSStyleSheet::cssTextWithReplacementURLs(const HashMap<String, String>& replacementURLStrings)
{
auto ruleList = cssRules();
if (!ruleList)
return { };

StringBuilder result;
for (unsigned index = 0; index < ruleList->length(); ++index) {
auto rule = ruleList->item(index);
if (!rule)
continue;

auto ruleText = rule->cssTextWithReplacementURLs(replacementURLStrings);
if (!result.isEmpty() && !ruleText.isEmpty())
result.append(" ");

result.append(ruleText);
}
return result.toString();
}

// https://w3c.github.io/csswg-drafts/cssom-1/#dom-cssstylesheet-replace
void CSSStyleSheet::replace(String&& text, Ref<DeferredPromise>&& promise)
{
Expand Down
1 change: 1 addition & 0 deletions Source/WebCore/css/CSSStyleSheet.h
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ class CSSStyleSheet final : public StyleSheet, public CanMakeCheckedPtr {
bool canAccessRules() const;

String debugDescription() const final;
String cssTextWithReplacementURLs(const HashMap<String, String>& replacementURLStrings);

private:
CSSStyleSheet(Ref<StyleSheetContents>&&, CSSImportRule* ownerRule);
Expand Down
8 changes: 8 additions & 0 deletions Source/WebCore/html/HTMLLinkElement.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -704,4 +704,12 @@ RequestPriority HTMLLinkElement::fetchPriorityHint() const
return RequestPriority::Auto;
}

String HTMLLinkElement::styleSheetContentWithReplacementURLs(const HashMap<String, String>& replacementURLStrings) const
{
if (!m_sheet)
return { };

return m_sheet->cssTextWithReplacementURLs(replacementURLStrings);
}

} // namespace WebCore
1 change: 1 addition & 0 deletions Source/WebCore/html/HTMLLinkElement.h
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ class HTMLLinkElement final : public HTMLElement, public CachedStyleSheetClient,
void setFetchPriorityForBindings(const AtomString&);
String fetchPriorityForBindings() const;
RequestPriority fetchPriorityHint() const;
String styleSheetContentWithReplacementURLs(const HashMap<String, String>&) const;

private:
void attributeChanged(const QualifiedName&, const AtomString& oldValue, const AtomString& newValue, AttributeModificationReason) final;
Expand Down
19 changes: 2 additions & 17 deletions Source/WebCore/html/HTMLStyleElement.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -183,23 +183,8 @@ String HTMLStyleElement::textContentWithReplacementURLs(const HashMap<String, St
if (!styleSheet)
return TextNodeTraversal::contentsAsString(*this);

auto ruleList = styleSheet->cssRules();
if (!ruleList)
return TextNodeTraversal::contentsAsString(*this);

StringBuilder result;
for (unsigned index = 0; index < ruleList->length(); ++index) {
auto rule = ruleList->item(index);
if (!rule)
continue;

auto ruleText = rule->cssTextWithReplacementURLs(replacementURLStrings);
if (!result.isEmpty() && !ruleText.isEmpty())
result.append(" ");

result.append(ruleText);
}
return result.toString();
auto result = styleSheet->cssTextWithReplacementURLs(replacementURLStrings);
return result.isNull() ? TextNodeTraversal::contentsAsString(*this) : result;
}

}
29 changes: 29 additions & 0 deletions Source/WebCore/loader/archive/cf/LegacyWebArchive.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
#include "HTMLFrameElement.h"
#include "HTMLFrameOwnerElement.h"
#include "HTMLIFrameElement.h"
#include "HTMLLinkElement.h"
#include "HTMLNames.h"
#include "HTMLObjectElement.h"
#include "Image.h"
Expand Down Expand Up @@ -616,6 +617,34 @@ RefPtr<LegacyWebArchive> LegacyWebArchive::create(const String& markupString, Lo

subresources.append(resource.releaseNonNull());
}

if (!subresourcesDirectoryName.isNull() && is<HTMLLinkElement>(node)) {
auto& element = downcast<HTMLLinkElement>(node.get());
if (!element.sheet())
continue;
auto index = subresources.findIf([&](auto& resource) {
return resource->url() == element.href();
});
if (index == notFound)
continue;

HashMap<String, String> uniqueSubresourcesInElement;
for (auto [urlString, path] : uniqueSubresources) {
if (subresourceURLs.contains(URL { urlString })) {
// The linked file is placed in subresource directory as other subresource files.
uniqueSubresourcesInElement.add(urlString, FileSystem::lastComponentOfPathIgnoringTrailingSlash(path));
}
}

auto contentString = element.styleSheetContentWithReplacementURLs(uniqueSubresourcesInElement);
if (contentString.isEmpty())
continue;

if (auto newResource = ArchiveResource::create(utf8Buffer(contentString), subresources[index]->url(), subresources[index]->mimeType(), subresources[index]->textEncoding(), subresources[index]->frameName(), ResourceResponse(), subresources[index]->relativeFilePath())) {
subresources.remove(index);
subresources.append(newResource.releaseNonNull());
}
}
}
}

Expand Down
90 changes: 90 additions & 0 deletions Tools/TestWebKitAPI/Tests/WebKitCocoa/CreateWebArchive.mm
Original file line number Diff line number Diff line change
Expand Up @@ -1245,6 +1245,96 @@ function loaded() {
Util::run(&saved);
}

static const char* htmlDataBytesForLink = R"TESTRESOURCE(
<head>
<link href="style.css" rel="stylesheet">
</head>
<div id="div">Hello</div>
<script>
img = null;
function onImageLoad() {
img = null;
window.webkit.messageHandlers.testHandler.postMessage("done");
}
div = document.getElementById("div");
var img = document.createElement("img");
img.src = "files/image.png";
img.onload = onImageLoad;
</script>
)TESTRESOURCE";
static const char* cssDataBytesForLink = R"TESTRESOURCE(
div {
height: 50%;
width: 50%;
background-image: url("files/image.png");
}
)TESTRESOURCE";

TEST(WebArchive, SaveResourcesLink)
{
RetainPtr<NSURL> directoryURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:@"SaveResourcesTest"] isDirectory:YES];
NSFileManager *fileManager = [NSFileManager defaultManager];
[fileManager removeItemAtURL:directoryURL.get() error:nil];

auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
auto schemeHandler = adoptNS([[TestURLSchemeHandler alloc] init]);
[configuration setURLSchemeHandler:schemeHandler.get() forURLScheme:@"webarchivetest"];
NSData *htmlData = [NSData dataWithBytes:htmlDataBytesForLink length:strlen(htmlDataBytesForLink)];
NSData *cssData = [NSData dataWithBytes:cssDataBytesForLink length:strlen(cssDataBytesForLink)];
NSData *imageData = [NSData dataWithContentsOfURL:[[NSBundle mainBundle] URLForResource:@"400x400-green" withExtension:@"png" subdirectory:@"TestWebKitAPI.resources"]];
[schemeHandler setStartURLSchemeTaskHandler:^(WKWebView *, id<WKURLSchemeTask> task) {
NSData *data = nil;
NSString *mimeType = nil;
if ([task.request.URL.absoluteString isEqualToString:@"webarchivetest://host/main.html"]) {
mimeType = @"text/html";
data = htmlData;
} else if ([task.request.URL.absoluteString isEqualToString:@"webarchivetest://host/files/image.png"]) {
mimeType = @"image/png";
data = imageData;
} else if ([task.request.URL.absoluteString isEqualToString:@"webarchivetest://host/style.css"]) {
mimeType = @"text/css";
data = cssData;
}

auto response = adoptNS([[NSURLResponse alloc] initWithURL:task.request.URL MIMEType:mimeType expectedContentLength:data.length textEncodingName:nil]);
[task didReceiveResponse:response.get()];
[task didReceiveData:data];
[task didFinish];
}];

auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
static bool messageReceived = false;
[webView performAfterReceivingMessage:@"done" action:[&] {
messageReceived = true;
}];
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"webarchivetest://host/main.html"]]];
Util::run(&messageReceived);

static bool saved = false;
[webView _saveResources:directoryURL.get() suggestedFileName:@"host" completionHandler:^(NSError *error) {
EXPECT_NULL(error);
NSString *mainResourcePath = [directoryURL URLByAppendingPathComponent:@"host.html"].path;
EXPECT_TRUE([fileManager fileExistsAtPath:mainResourcePath]);
NSString *savedMainResource = [[NSString alloc] initWithData:[NSData dataWithContentsOfFile:mainResourcePath] encoding:NSUTF8StringEncoding];

NSString *resourceDirectoryName = @"host_files";
NSString *resourceDirectoryPath =[directoryURL URLByAppendingPathComponent:resourceDirectoryName].path;
NSArray *resourceFileNames = [fileManager contentsOfDirectoryAtPath:resourceDirectoryPath error:nil];
NSSet *savedFileNames = [NSSet setWithArray:resourceFileNames];
NSSet *expectedFileNames = [NSSet setWithArray:[NSArray arrayWithObjects:@"image.png", @"style.css", nil]];
EXPECT_TRUE([savedFileNames isEqualToSet:expectedFileNames]);

NSString *styleResourceFileName = [resourceDirectoryName stringByAppendingPathComponent:@"style.css"];
EXPECT_TRUE([savedMainResource containsString:styleResourceFileName]);
NSString *styleResourceFilePath = [resourceDirectoryPath stringByAppendingPathComponent:@"style.css"];
NSString *savedStyleResource = [[NSString alloc] initWithData:[NSData dataWithContentsOfFile:styleResourceFilePath] encoding:NSUTF8StringEncoding];
EXPECT_TRUE([savedStyleResource containsString:@"url(\"image.png\")"]);

saved = true;
}];
Util::run(&saved);
}

} // namespace TestWebKitAPI

#endif // PLATFORM(MAC) || PLATFORM(IOS_FAMILY)

0 comments on commit c72276a

Please sign in to comment.