0

MHTML: fix some issues with styles

This CL makes two changes:

1. For <style> nodes that have unmodified stylesheets, do not
   modify the <style> node. Because round-tripping from text
   to CSS rules back to text is lossy, this sometimes avoids
   problems. See crbug.com/40804066 for why it's lossy.
2. For <style> nodes that have been modified, serialize them
   inline as a <style> node rather than as a <link> node.
   Serializing styles into another resource changes the
   baseURL, which will break how relative URLs are
   interpreted.

Bug: 363289333
Change-Id: Id165d21e5ab61f41b60d27e3c030d24da7832615
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5883007
Reviewed-by: Min Qin <qinmin@chromium.org>
Reviewed-by: Daniel Cheng <dcheng@chromium.org>
Commit-Queue: Dan H <harringtond@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1373409}
This commit is contained in:
Dan Harrington
2024-10-24 17:31:57 +00:00
committed by Chromium LUCI CQ
parent 8ecbee8bec
commit 3436d753b0
12 changed files with 314 additions and 70 deletions

@ -78,7 +78,18 @@ const char kGetPageInfoScript[] = R"js(
const styleKeys = ['font-family', 'line-height', 'display'];
function elementStyles(el) {
const styles = window.getComputedStyle(el);
return Object.fromEntries(styleKeys.map(name => [name, styles[name]]));
const result = Object.fromEntries(styleKeys
.map(name => [name, styles[name]])
.filter(v=>v[1]));
// add background-image, but only the file name because the full path will
// change in the saved page.
let m = styles.backgroundImage.match(/url\((.*)\)/);
if (m) {
const url = m[1];
const parts = url.split('/');
result['backgroundImageFile'] = parts[parts.length-1];
}
return result;
}
function isVisible(el) {
const styles = window.getComputedStyle(el);
@ -157,8 +168,7 @@ struct CompareResult {
// A dummy WebContentsDelegate which tracks the results of a find operation.
class FindTrackingDelegate : public WebContentsDelegate {
public:
explicit FindTrackingDelegate(const std::string& search)
: search_(search), matches_(-1) {}
explicit FindTrackingDelegate(const std::string& search) : search_(search) {}
FindTrackingDelegate(const FindTrackingDelegate&) = delete;
FindTrackingDelegate& operator=(const FindTrackingDelegate&) = delete;
@ -197,7 +207,7 @@ class FindTrackingDelegate : public WebContentsDelegate {
private:
std::string search_;
int matches_;
int matches_ = -1;
base::RunLoop run_loop_;
};
@ -374,7 +384,6 @@ class RespondAndDisconnectMockWriter
~RespondAndDisconnectMockWriter() override = default;
};
class MHTMLGenerationTest : public ContentBrowserTest,
public testing::WithParamInterface<bool> {
public:
@ -986,7 +995,8 @@ IN_PROC_BROWSER_TEST_P(MHTMLGenerationImprovedTest, Styles) {
CompareOptions options;
options.expected_number_of_frames = 1;
options.expected_substrings = {"hidden1", "hidden4"};
options.expected_substrings = {"hidden1", "hidden4",
"This should show if inline CSS is escaped."};
options.forbidden_substrings = {"hidden2", "hidden3"};
CompareResult result = TestOriginalVsSavedPage(
embedded_test_server()->GetURL("/mhtml/styles.html"), params, options);

@ -7355,6 +7355,8 @@ data/media/webrtc_test_common.js
data/media/webrtc_test_utilities.js
data/mhtml/custom_element_defined.html
data/mhtml/custom_element_defined_in_frame.html
data/mhtml/imported_imported.css
data/mhtml/imported_inline.css
data/mhtml/style.css
data/mhtml/styles.html
data/midi/request_midi_access.html

@ -0,0 +1,14 @@
/* Copyright 2024 The Chromium Authors
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file. */
/*
This style won't serialize correctly because the spec does not allow it, see
crbug.com/40804066. Because this style element is not modified by JS, Chrome
should leave it alone rather than serialize a replacement.
*/
#font-c {
--font-sans: sans;
font: normal 600 60px var(--font-sans);
line-height: 30px;
}

@ -0,0 +1,15 @@
/* Copyright 2024 The Chromium Authors
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file. */
@import url("imported_imported.css");
/*
This style won't serialize correctly because the spec does not allow it, see
crbug.com/40804066. Because this style element is not modified by JS, Chrome
should leave it alone rather than serialize a replacement.
*/
#font-b {
--font-sans: sans;
font: normal 600 60px var(--font-sans);
line-height: 30px;
}

@ -1,15 +1,38 @@
<head>
<style id="inlinestyle1">
/* An inline style can't contain the end style tag. This escaped url string
evaluates to the style end tag, so it should remain escaped in the serialized
MHTML page. */
p#p-end-style-tag {
display: none;
background-image: url("\3C/style>");
}
p#p-end-style-tag {
display: revert;
}
</style>
<link rel="stylesheet" href="style.css">
</head>
<style>
@import url("imported_inline.css");
#hidden1 {
display: block;
}
#hidden2 {
display: none;
}
/*
This style won't serialize correctly because the spec does not allow it, see
crbug.com/40804066. Because this style element is not modified by JS, Chrome
should leave it alone rather than serialize a replacement.
*/
#font-a {
--font-sans: sans;
font: normal 600 60px var(--font-sans);
line-height: 30px;
}
</style>
@ -26,6 +49,28 @@
<!--Hidden by first inline style, Shown by <link>.-->
<div id="hidden4">hidden4</div>
<div id="font-a">font-a</div>
<!--Style affected by imported_inline.css.
TODO(crbug.com/363289333): This element's style won't be correct yet
in the serialized mhtml. We should write imported_inline.css without
any changes.
<div id="font-b">font-b</div>
-->
<!--Style affected by imported_imported.css.
TODO(crbug.com/363289333): This element's style won't be correct yet
in the serialized mhtml. We should write imported_inline.css without
any changes.
<div id="font-c">font-b</div>
-->
<div id="font-d">font-d</div>
<p id="p-end-style-tag">This should show if inline CSS is escaped.</p>
<script>
// Ensure we serialize the modified stylesheet.
const sheet1 = document.getElementById("inlinestyle1").sheet;
@ -35,6 +80,15 @@
sheet1.insertRule(`#hidden4 {
display: none;
}`);
// Ensure a style appended like this isn't re-serialized incorrectly.
const sheet2 = document.createElement("style");
sheet2.innerText = `
#hidden-by-appended-style {
--font-sans: sans;
font: normal 600 60px var(--font-sans);
line-height: 30px;
}`;
document.head.appendChild(sheet2);
</script>
</body>

@ -183,15 +183,15 @@ void MarkupAccumulator::AppendStartMarkup(const Node& node) {
void MarkupAccumulator::AppendCustomAttributes(const Element&) {}
MarkupAccumulator::EmitChoice MarkupAccumulator::WillProcessAttribute(
MarkupAccumulator::EmitAttributeChoice MarkupAccumulator::WillProcessAttribute(
const Element& element,
const Attribute& attribute) const {
return EmitChoice::kEmit;
return EmitAttributeChoice::kEmit;
}
MarkupAccumulator::EmitChoice MarkupAccumulator::WillProcessElement(
MarkupAccumulator::EmitElementChoice MarkupAccumulator::WillProcessElement(
const Element& element) {
return EmitChoice::kEmit;
return EmitElementChoice::kEmit;
}
AtomicString MarkupAccumulator::AppendElement(const Element& element) {
@ -210,7 +210,8 @@ AtomicString MarkupAccumulator::AppendElement(const Element& element) {
AppendAttribute(element, Attribute(html_names::kIsAttr, is_value));
}
for (const auto& attribute : attributes) {
if (EmitChoice::kEmit == WillProcessAttribute(element, attribute)) {
if (EmitAttributeChoice::kEmit ==
WillProcessAttribute(element, attribute)) {
AppendAttribute(element, attribute);
}
}
@ -226,7 +227,8 @@ AtomicString MarkupAccumulator::AppendElement(const Element& element) {
if (!EqualIgnoringNullity(attribute.Value(), element.namespaceURI()))
continue;
}
if (EmitChoice::kEmit == WillProcessAttribute(element, attribute)) {
if (EmitAttributeChoice::kEmit ==
WillProcessAttribute(element, attribute)) {
AppendAttribute(element, attribute);
}
}
@ -631,7 +633,8 @@ void MarkupAccumulator::SerializeNodesWithNamespaces(
}
const auto& target_element = To<Element>(target_node);
if (WillProcessElement(target_element) == EmitChoice::kIgnore) {
EmitElementChoice emit_choice = WillProcessElement(target_element);
if (emit_choice == EmitElementChoice::kIgnore) {
return;
}
@ -644,33 +647,37 @@ void MarkupAccumulator::SerializeNodesWithNamespaces(
bool has_end_tag =
!(SerializeAsHTML() && ElementCannotHaveEndTag(target_element));
if (has_end_tag) {
const Node* parent = &target_element;
if (auto* template_element =
DynamicTo<HTMLTemplateElement>(target_element)) {
// Declarative shadow roots that are currently being parsed will have a
// null content() - don't serialize contents in this case.
parent = template_element->content();
}
// Traverses the shadow tree.
std::pair<ShadowRoot*, Element*> auxiliary_pair =
GetShadowTree(target_element);
if (ShadowRoot* auxiliary_tree = auxiliary_pair.first) {
Element* enclosing_element = auxiliary_pair.second;
AtomicString enclosing_element_prefix;
if (enclosing_element)
enclosing_element_prefix = AppendElement(*enclosing_element);
for (const Node& child : Strategy::ChildrenOf(*auxiliary_tree))
SerializeNodesWithNamespaces<Strategy>(child, kIncludeNode);
if (enclosing_element) {
WillCloseSyntheticTemplateElement(*auxiliary_tree);
AppendEndTag(*enclosing_element, enclosing_element_prefix);
if (emit_choice != EmitElementChoice::kEmitButIgnoreChildren) {
const Node* parent = &target_element;
if (auto* template_element =
DynamicTo<HTMLTemplateElement>(target_element)) {
// Declarative shadow roots that are currently being parsed will have a
// null content() - don't serialize contents in this case.
parent = template_element->content();
}
}
if (parent) {
for (const Node& child : Strategy::ChildrenOf(*parent)) {
SerializeNodesWithNamespaces<Strategy>(child, kIncludeNode);
// Traverses the shadow tree.
std::pair<ShadowRoot*, Element*> auxiliary_pair =
GetShadowTree(target_element);
if (ShadowRoot* auxiliary_tree = auxiliary_pair.first) {
Element* enclosing_element = auxiliary_pair.second;
AtomicString enclosing_element_prefix;
if (enclosing_element) {
enclosing_element_prefix = AppendElement(*enclosing_element);
}
for (const Node& child : Strategy::ChildrenOf(*auxiliary_tree)) {
SerializeNodesWithNamespaces<Strategy>(child, kIncludeNode);
}
if (enclosing_element) {
WillCloseSyntheticTemplateElement(*auxiliary_tree);
AppendEndTag(*enclosing_element, enclosing_element_prefix);
}
}
if (parent) {
for (const Node& child : Strategy::ChildrenOf(*parent)) {
SerializeNodesWithNamespaces<Strategy>(child, kIncludeNode);
}
}
}

@ -65,12 +65,22 @@ class CORE_EXPORT MarkupAccumulator {
CORE_EXPORT String SerializeNodes(const Node&, ChildrenOnly);
protected:
// Determines whether an element or attribute is emitted as markup.
enum class EmitChoice {
// Determines whether an attribute is emitted as markup.
enum class EmitAttributeChoice {
// Emit the attribute as markup.
kEmit,
// Do not emit the attribute.
kIgnore,
};
// Determines whether an element is emitted as markup.
enum class EmitElementChoice {
// Emit it as markup.
kEmit,
// Do not emit it or any children (for elements).
// Do not emit the element or any children.
kIgnore,
// Emit the element, but not its children.
kEmitButIgnoreChildren,
};
// Returns serialized prefix. It should be passed to AppendEndTag().
@ -81,7 +91,7 @@ class CORE_EXPORT MarkupAccumulator {
// This is called just before emitting markup for `element`. Derived classes
// may emit markup here, i.e., if they want to provide a substitute for this
// element.
virtual EmitChoice WillProcessElement(const Element& element);
virtual EmitElementChoice WillProcessElement(const Element& element);
// Called just before closing a <template> element used to serialize a
// shadow root. `auxiliary_tree` is the shadow root that has just been
// serialized into the <template> element.
@ -127,8 +137,8 @@ class CORE_EXPORT MarkupAccumulator {
AtomicString GeneratePrefix(const AtomicString& new_namespace);
virtual void AppendCustomAttributes(const Element&);
virtual EmitChoice WillProcessAttribute(const Element&,
const Attribute&) const;
virtual EmitAttributeChoice WillProcessAttribute(const Element&,
const Attribute&) const;
// Returns a shadow tree that needs also to be serialized. The ShadowRoot is
// returned as the 1st element in the pair, and can be null if no shadow tree

@ -125,6 +125,33 @@ void AppendLinkElement(StringBuilder& markup, const KURL& url) {
} // namespace
namespace internal {
// TODO(crbug.com/363289333): Try to add this functionality to wtf::String.
String ReplaceAllCaseInsensitive(
String source,
const String& from,
base::FunctionRef<String(const String&)> transform) {
size_t offset = 0;
size_t pos;
StringBuilder builder;
for (;;) {
pos = source.Find(from, offset,
TextCaseSensitivity::kTextCaseASCIIInsensitive);
if (pos == kNotFound) {
break;
}
builder.Append(source.Substring(offset, pos - offset));
builder.Append(transform(source.Substring(pos, from.length())));
offset = pos + from.length();
}
if (builder.empty()) {
return source;
}
builder.Append(source.Substring(offset));
return builder.ToString();
}
} // namespace internal
// Stores the list of serialized resources which constitute the frame. The
// first resource should be the frame's content (usually HTML).
class MultiResourcePacker {
@ -301,8 +328,9 @@ class SerializerMarkupAccumulator : public MarkupAccumulator {
return true;
}
EmitChoice WillProcessAttribute(const Element& element,
const Attribute& attribute) const override {
EmitAttributeChoice WillProcessAttribute(
const Element& element,
const Attribute& attribute) const override {
// TODO(fgorski): Presence of srcset attribute causes MHTML to not display
// images, as only the value of src is pulled into the archive. Discarding
// srcset prevents the problem. Long term we should make sure to MHTML plays
@ -310,7 +338,7 @@ class SerializerMarkupAccumulator : public MarkupAccumulator {
if (IsA<HTMLImageElement>(element) &&
(attribute.LocalName() == html_names::kSrcsetAttr ||
attribute.LocalName() == html_names::kSizesAttr)) {
return EmitChoice::kIgnore;
return EmitAttributeChoice::kIgnore;
}
// Do not save ping attribute since anyway the ping will be blocked from
@ -318,7 +346,7 @@ class SerializerMarkupAccumulator : public MarkupAccumulator {
// TODO(crbug.com/369219144): Should this be IsA<HTMLAnchorElementBase>?
if (IsA<HTMLAnchorElement>(element) &&
attribute.LocalName() == html_names::kPingAttr) {
return EmitChoice::kIgnore;
return EmitAttributeChoice::kIgnore;
}
// The special attribute in a template element to denote the shadow DOM
@ -328,7 +356,7 @@ class SerializerMarkupAccumulator : public MarkupAccumulator {
(attribute.LocalName() == kShadowModeAttributeName ||
attribute.LocalName() == kShadowDelegatesFocusAttributeName) &&
!shadow_template_elements_.Contains(&element)) {
return EmitChoice::kIgnore;
return EmitAttributeChoice::kIgnore;
}
// If srcdoc attribute for frame elements will be rewritten as src attribute
@ -339,22 +367,22 @@ class SerializerMarkupAccumulator : public MarkupAccumulator {
String new_link_for_the_element;
if (is_src_doc_attribute &&
RewriteLink(element, new_link_for_the_element)) {
return EmitChoice::kEmit;
return EmitAttributeChoice::kEmit;
}
// Drop integrity attribute for those links with subresource loaded.
auto* html_link_element = DynamicTo<HTMLLinkElement>(element);
if (attribute.LocalName() == html_names::kIntegrityAttr &&
html_link_element && html_link_element->sheet()) {
return EmitChoice::kIgnore;
return EmitAttributeChoice::kIgnore;
}
// Do not include attributes that contain javascript. This is because the
// script will not be executed when a MHTML page is being loaded.
if (element.IsScriptingAttribute(attribute)) {
return EmitChoice::kIgnore;
return EmitAttributeChoice::kIgnore;
}
return EmitChoice::kEmit;
return EmitAttributeChoice::kEmit;
}
bool RewriteLink(const Element& element, String& rewritten_link) const {
@ -457,16 +485,16 @@ class SerializerMarkupAccumulator : public MarkupAccumulator {
}
}
EmitChoice WillProcessElement(const Element& element) override {
EmitElementChoice WillProcessElement(const Element& element) override {
if (IsA<HTMLScriptElement>(element)) {
return EmitChoice::kIgnore;
return EmitElementChoice::kIgnore;
}
if (IsA<HTMLNoScriptElement>(element)) {
return EmitChoice::kIgnore;
return EmitElementChoice::kIgnore;
}
auto* meta = DynamicTo<HTMLMetaElement>(element);
if (meta && meta->ComputeEncoding().IsValid()) {
return EmitChoice::kIgnore;
return EmitElementChoice::kIgnore;
}
if (MHTMLImprovementsEnabled()) {
@ -476,33 +504,43 @@ class SerializerMarkupAccumulator : public MarkupAccumulator {
DynamicTo<HTMLStyleElement>(element)) {
CSSStyleSheet* sheet = style_element->sheet();
if (sheet) {
AppendStylesheet(*sheet);
return EmitChoice::kIgnore;
// JS may update styles programmatically for a <style> node. We detect
// whether this has happened, and serialize the stylesheet if it has.
// Otherwise, we leave the <style> node unmodified. Because CSS
// serialization isn't perfect, it's better to leave the original
// <style> element if possible.
SerializeCSSResources(*sheet);
if (!sheet->Contents()->IsMutable()) {
return EmitElementChoice::kEmit;
} else {
style_elements_to_replace_contents_.insert(style_element);
return EmitElementChoice::kEmitButIgnoreChildren;
}
}
}
} else {
// A <link> element is inserted in `AppendExtraForHeadElement()` as a
// substitute for this element.
if (IsA<HTMLStyleElement>(element)) {
return EmitChoice::kIgnore;
return EmitElementChoice::kIgnore;
}
}
if (ShouldIgnoreHiddenElement(element)) {
return EmitChoice::kIgnore;
return EmitElementChoice::kIgnore;
}
if (ShouldIgnoreMetaElement(element)) {
return EmitChoice::kIgnore;
return EmitElementChoice::kIgnore;
}
if (web_delegate_->RemovePopupOverlay() &&
ShouldIgnorePopupOverlayElement(element)) {
return EmitChoice::kIgnore;
return EmitElementChoice::kIgnore;
}
// Remove <link> for stylesheets that do not load.
auto* html_link_element = DynamicTo<HTMLLinkElement>(element);
if (html_link_element && html_link_element->RelAttribute().IsStyleSheet() &&
!html_link_element->sheet()) {
return EmitChoice::kIgnore;
return EmitElementChoice::kIgnore;
}
return MarkupAccumulator::WillProcessElement(element);
}
@ -535,6 +573,14 @@ class SerializerMarkupAccumulator : public MarkupAccumulator {
if (IsA<HTMLHtmlElement>(element)) {
AppendAdoptedStyleSheets(document_);
}
if (const HTMLStyleElement* style_element =
DynamicTo<HTMLStyleElement>(element)) {
if (style_elements_to_replace_contents_.Contains(style_element)) {
CSSStyleSheet* sheet = style_element->sheet();
markup_.Append(SerializeInlineCSSStyleSheet(*sheet));
}
}
}
MarkupAccumulator::AppendEndTag(element, prefix);
}
@ -736,6 +782,44 @@ class SerializerMarkupAccumulator : public MarkupAccumulator {
}
}
// Serializes `style_sheet` as text that can be added to an inline <style>
// tag. Ensures the style sheet does not include the </style> end tag.
String SerializeInlineCSSStyleSheet(CSSStyleSheet& style_sheet) {
StringBuilder css_text;
for (unsigned i = 0; i < style_sheet.length(); ++i) {
CSSRule* rule = style_sheet.ItemInternal(i);
String item_text = rule->cssText();
if (!item_text.empty()) {
css_text.Append(item_text);
if (i < style_sheet.length() - 1) {
css_text.Append("\n\n");
}
}
}
// `css_text` is the text that has already been parsed from the <style> tag,
// so it does not retain escape sequences. The only time we would need to
// emit an escape sequence is if the </style> tag appears within `css_text`.
// Parsing <style> contents is described in
// https://html.spec.whatwg.org/multipage/parsing.html#rawtext-state.
// Note that when replacing the "style" text. HTML tags are case
// insensitive, but this is escaped, so it's not not actually an HTML end
// tag.
return blink::internal::ReplaceAllCaseInsensitive(
css_text.ToString(), "</style", [](const String& text) {
StringBuilder builder;
builder.Append("\\3C/"); // \3C = '<'.
builder.Append(text.Substring(2));
return builder.ReleaseString();
});
}
// Attempts to serialize a stylesheet, if necessary. Does a couple things:
// 1. If `url` is valid and not a data URL, and we haven't already serialized
// this url, then serialize the stylesheet into a new resource. Note that this
// process is lossy, and may not perfectly reflect the intended style.
// 2. Even if `url` is invalid or a data URL, serialize the resources within
// `style_sheet`.
void SerializeCSSStyleSheet(CSSStyleSheet& style_sheet, const KURL& url) {
// If the URL is invalid or if it is a data URL this means that this CSS is
// defined inline, respectively in a <style> tag or in the data URL itself.
@ -794,12 +878,17 @@ class SerializerMarkupAccumulator : public MarkupAccumulator {
// Sub resources need to be serialized even if the CSS definition doesn't
// need to be.
SerializeCSSResources(style_sheet);
}
// Serializes resources referred to by `style_sheet`.
void SerializeCSSResources(CSSStyleSheet& style_sheet) {
for (unsigned i = 0; i < style_sheet.length(); ++i) {
SerializeCSSRule(style_sheet.ItemInternal(i));
SerializeCSSRuleResources(style_sheet.ItemInternal(i));
}
}
void SerializeCSSRule(CSSRule* rule) {
void SerializeCSSRuleResources(CSSRule* rule) {
DCHECK(rule->parentStyleSheet()->OwnerDocument());
Document& document = *rule->parentStyleSheet()->OwnerDocument();
@ -815,6 +904,9 @@ class SerializerMarkupAccumulator : public MarkupAccumulator {
DCHECK(sheet_base_url.IsValid());
KURL import_url = KURL(sheet_base_url, import_rule->href());
if (import_rule->styleSheet()) {
// TODO(crbug.com/363289333): When MHTMLImprovementsEnabled(), we
// should avoid serializing the imported stylesheet, and instead fetch
// the raw CSS resource.
SerializeCSSStyleSheet(*import_rule->styleSheet(), import_url);
}
break;
@ -830,7 +922,7 @@ class SerializerMarkupAccumulator : public MarkupAccumulator {
case CSSRule::kStartingStyleRule: {
CSSRuleList* rule_list = rule->cssRules();
for (unsigned i = 0; i < rule_list->length(); ++i) {
SerializeCSSRule(rule_list->item(i));
SerializeCSSRuleResources(rule_list->item(i));
}
break;
}
@ -925,6 +1017,8 @@ class SerializerMarkupAccumulator : public MarkupAccumulator {
// * Serialize styleSheets on shadow roots
// * Retain stylesheet order, previously order of stylesheets
// was sometimes wrong.
// * Serialize <style> nodes as <style> nodes instead of <link> nodes.
// * Leave <style> nodes alone if their stylesheet is unmodified.
bool MHTMLImprovementsEnabled() const {
return base::FeatureList::IsEnabled(blink::features::kMHTML_Improvements);
}
@ -941,6 +1035,11 @@ class SerializerMarkupAccumulator : public MarkupAccumulator {
// Adopted stylesheets can be reused. This stores the set of stylesheets
// already serialized as resources, along with their URL.
HeapHashMap<Member<blink::CSSStyleSheet>, KURL> stylesheet_pseudo_urls_;
// Style elements whose contents will be serialized just before inserting
// </style>.
HeapHashSet<Member<const HTMLStyleElement>>
style_elements_to_replace_contents_;
};
// TODO(tiger): Right now there is no support for rewriting URLs inside CSS

@ -32,6 +32,7 @@
#define THIRD_PARTY_BLINK_RENDERER_CORE_FRAME_FRAME_SERIALIZER_H_
#include "base/functional/callback.h"
#include "base/functional/function_ref.h"
#include "third_party/blink/public/web/web_frame_serializer.h"
#include "third_party/blink/renderer/core/core_export.h"
#include "third_party/blink/renderer/platform/weborigin/kurl.h"
@ -46,6 +47,16 @@ class Frame;
struct SerializedResource;
// Internal functionality exposed for unit testing.
namespace internal {
// Returns the result of replacing all case-insensitive occurrences of `from` in
// `source` with the result of `transform.Run(match)`.
CORE_EXPORT String
ReplaceAllCaseInsensitive(String source,
const String& from,
base::FunctionRef<String(const String&)> transform);
} // namespace internal
// This class is used to serialize frame's contents to MHTML. It serializes
// frame's document and resources such as images and CSS stylesheets.
class CORE_EXPORT FrameSerializer {

@ -548,4 +548,26 @@ TEST_F(FrameSerializerTest, markOfTheWebDeclaration) {
KURL("http://foo.com#bar--baz")));
}
TEST_F(FrameSerializerTest, ReplaceAllCaseInsensitive) {
auto transform = [](const String& from) { return String("</HI>"); };
EXPECT_EQ(
blink::internal::ReplaceAllCaseInsensitive("", "</style>", transform),
"");
EXPECT_EQ(
blink::internal::ReplaceAllCaseInsensitive("test", "</style>", transform),
"test");
EXPECT_EQ(blink::internal::ReplaceAllCaseInsensitive("</Style>", "</style>",
transform),
"</HI>");
EXPECT_EQ(blink::internal::ReplaceAllCaseInsensitive("x</Style>", "</style>",
transform),
"x</HI>");
EXPECT_EQ(blink::internal::ReplaceAllCaseInsensitive("</Style>x", "</style>",
transform),
"</HI>x");
EXPECT_EQ(blink::internal::ReplaceAllCaseInsensitive(
"test</Style>test</Style>testagain", "</style>", transform),
"test</HI>test</HI>testagain");
}
} // namespace blink

@ -31,10 +31,10 @@ String InnerHtmlBuilder::Build(HTMLElement& body) {
return SerializeNodes<EditingStrategy>(body, kIncludeNode);
}
MarkupAccumulator::EmitChoice InnerHtmlBuilder::WillProcessElement(
MarkupAccumulator::EmitElementChoice InnerHtmlBuilder::WillProcessElement(
const Element& e) {
if (e.IsScriptElement()) {
return EmitChoice::kIgnore;
return EmitElementChoice::kIgnore;
}
return MarkupAccumulator::WillProcessElement(e);
}

@ -35,7 +35,7 @@ class MODULES_EXPORT InnerHtmlBuilder final : public MarkupAccumulator {
String Build(HTMLElement& body);
// MarkupAccumulator:
EmitChoice WillProcessElement(const Element& e) override;
EmitElementChoice WillProcessElement(const Element& e) override;
};
} // namespace blink