blob: e7c0572e004d2dde3f67bcd8bafd60865121f768 [file] [log] [blame]
/*
* Copyright (c) 2014, Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "core/dom/Document.h"
#include "core/dom/NodeWithIndex.h"
#include "core/dom/SynchronousMutationObserver.h"
#include "core/dom/Text.h"
#include "core/frame/FrameView.h"
#include "core/frame/Settings.h"
#include "core/html/HTMLHeadElement.h"
#include "core/html/HTMLInputElement.h"
#include "core/html/HTMLLinkElement.h"
#include "core/page/Page.h"
#include "core/page/ValidationMessageClient.h"
#include "core/testing/DummyPageHolder.h"
#include "platform/heap/Handle.h"
#include "platform/weborigin/ReferrerPolicy.h"
#include "platform/weborigin/SchemeRegistry.h"
#include "platform/weborigin/SecurityOrigin.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include <memory>
namespace blink {
class DocumentTest : public ::testing::Test {
protected:
void SetUp() override;
void TearDown() override { ThreadState::current()->collectAllGarbage(); }
Document& document() const { return m_dummyPageHolder->document(); }
Page& page() const { return m_dummyPageHolder->page(); }
void setHtmlInnerHTML(const char*);
private:
std::unique_ptr<DummyPageHolder> m_dummyPageHolder;
};
void DocumentTest::SetUp() {
m_dummyPageHolder = DummyPageHolder::create(IntSize(800, 600));
}
void DocumentTest::setHtmlInnerHTML(const char* htmlContent) {
document().documentElement()->setInnerHTML(String::fromUTF8(htmlContent));
document().view()->updateAllLifecyclePhases();
}
namespace {
class TestSynchronousMutationObserver
: public GarbageCollectedFinalized<TestSynchronousMutationObserver>,
public SynchronousMutationObserver {
USING_GARBAGE_COLLECTED_MIXIN(TestSynchronousMutationObserver);
public:
struct MergeTextNodesRecord : GarbageCollected<MergeTextNodesRecord> {
Member<const Text> m_node;
Member<Node> m_nodeToBeRemoved;
unsigned m_offset = 0;
MergeTextNodesRecord(const Text* node,
const NodeWithIndex& nodeWithIndex,
unsigned offset)
: m_node(node),
m_nodeToBeRemoved(nodeWithIndex.node()),
m_offset(offset) {}
DEFINE_INLINE_TRACE() {
visitor->trace(m_node);
visitor->trace(m_nodeToBeRemoved);
}
};
struct UpdateCharacterDataRecord
: GarbageCollected<UpdateCharacterDataRecord> {
Member<CharacterData> m_node;
unsigned m_offset = 0;
unsigned m_oldLength = 0;
unsigned m_newLength = 0;
UpdateCharacterDataRecord(CharacterData* node,
unsigned offset,
unsigned oldLength,
unsigned newLength)
: m_node(node),
m_offset(offset),
m_oldLength(oldLength),
m_newLength(newLength) {}
DEFINE_INLINE_TRACE() { visitor->trace(m_node); }
};
TestSynchronousMutationObserver(Document&);
virtual ~TestSynchronousMutationObserver() = default;
int countContextDestroyedCalled() const {
return m_contextDestroyedCalledCounter;
}
const HeapVector<Member<const ContainerNode>>& childrenChangedNodes() const {
return m_childrenChangedNodes;
}
const HeapVector<Member<MergeTextNodesRecord>>& mergeTextNodesRecords()
const {
return m_mergeTextNodesRecords;
}
const HeapVector<Member<const Node>>& moveTreeToNewDocumentNodes() const {
return m_moveTreeToNewDocumentNodes;
}
const HeapVector<Member<ContainerNode>>& removedChildrenNodes() const {
return m_removedChildrenNodes;
}
const HeapVector<Member<Node>>& removedNodes() const {
return m_removedNodes;
}
const HeapVector<Member<const Text>>& splitTextNodes() const {
return m_splitTextNodes;
}
const HeapVector<Member<UpdateCharacterDataRecord>>&
updatedCharacterDataRecords() const {
return m_updatedCharacterDataRecords;
}
DECLARE_TRACE();
private:
// Implement |SynchronousMutationObserver| member functions.
void contextDestroyed(Document*) final;
void didChangeChildren(const ContainerNode&) final;
void didMergeTextNodes(const Text&, const NodeWithIndex&, unsigned) final;
void didMoveTreeToNewDocument(const Node& root) final;
void didSplitTextNode(const Text&) final;
void didUpdateCharacterData(CharacterData*,
unsigned offset,
unsigned oldLength,
unsigned newLength) final;
void nodeChildrenWillBeRemoved(ContainerNode&) final;
void nodeWillBeRemoved(Node&) final;
int m_contextDestroyedCalledCounter = 0;
HeapVector<Member<const ContainerNode>> m_childrenChangedNodes;
HeapVector<Member<MergeTextNodesRecord>> m_mergeTextNodesRecords;
HeapVector<Member<const Node>> m_moveTreeToNewDocumentNodes;
HeapVector<Member<ContainerNode>> m_removedChildrenNodes;
HeapVector<Member<Node>> m_removedNodes;
HeapVector<Member<const Text>> m_splitTextNodes;
HeapVector<Member<UpdateCharacterDataRecord>> m_updatedCharacterDataRecords;
DISALLOW_COPY_AND_ASSIGN(TestSynchronousMutationObserver);
};
TestSynchronousMutationObserver::TestSynchronousMutationObserver(
Document& document) {
setContext(&document);
}
void TestSynchronousMutationObserver::contextDestroyed(Document*) {
++m_contextDestroyedCalledCounter;
}
void TestSynchronousMutationObserver::didChangeChildren(
const ContainerNode& container) {
m_childrenChangedNodes.push_back(&container);
}
void TestSynchronousMutationObserver::didMergeTextNodes(
const Text& node,
const NodeWithIndex& nodeWithIndex,
unsigned offset) {
m_mergeTextNodesRecords.push_back(
new MergeTextNodesRecord(&node, nodeWithIndex, offset));
}
void TestSynchronousMutationObserver::didMoveTreeToNewDocument(
const Node& root) {
m_moveTreeToNewDocumentNodes.push_back(&root);
}
void TestSynchronousMutationObserver::didSplitTextNode(const Text& node) {
m_splitTextNodes.push_back(&node);
}
void TestSynchronousMutationObserver::didUpdateCharacterData(
CharacterData* characterData,
unsigned offset,
unsigned oldLength,
unsigned newLength) {
m_updatedCharacterDataRecords.push_back(new UpdateCharacterDataRecord(
characterData, offset, oldLength, newLength));
}
void TestSynchronousMutationObserver::nodeChildrenWillBeRemoved(
ContainerNode& container) {
m_removedChildrenNodes.push_back(&container);
}
void TestSynchronousMutationObserver::nodeWillBeRemoved(Node& node) {
m_removedNodes.push_back(&node);
}
DEFINE_TRACE(TestSynchronousMutationObserver) {
visitor->trace(m_childrenChangedNodes);
visitor->trace(m_mergeTextNodesRecords);
visitor->trace(m_moveTreeToNewDocumentNodes);
visitor->trace(m_removedChildrenNodes);
visitor->trace(m_removedNodes);
visitor->trace(m_splitTextNodes);
visitor->trace(m_updatedCharacterDataRecords);
SynchronousMutationObserver::trace(visitor);
}
class MockValidationMessageClient
: public GarbageCollectedFinalized<MockValidationMessageClient>,
public ValidationMessageClient {
USING_GARBAGE_COLLECTED_MIXIN(MockValidationMessageClient);
public:
MockValidationMessageClient() { reset(); }
void reset() {
showValidationMessageWasCalled = false;
willUnloadDocumentWasCalled = false;
documentDetachedWasCalled = false;
}
bool showValidationMessageWasCalled;
bool willUnloadDocumentWasCalled;
bool documentDetachedWasCalled;
// ValidationMessageClient functions.
void showValidationMessage(const Element& anchor,
const String& mainMessage,
TextDirection,
const String& subMessage,
TextDirection) override {
showValidationMessageWasCalled = true;
}
void hideValidationMessage(const Element& anchor) override {}
bool isValidationMessageVisible(const Element& anchor) override {
return true;
}
void willUnloadDocument(const Document&) override {
willUnloadDocumentWasCalled = true;
}
void documentDetached(const Document&) override {
documentDetachedWasCalled = true;
}
void willBeDestroyed() override {}
// DEFINE_INLINE_VIRTUAL_TRACE() { ValidationMessageClient::trace(visitor); }
};
} // anonymous namespace
// This tests that we properly resize and re-layout pages for printing in the
// presence of media queries effecting elements in a subtree layout boundary
TEST_F(DocumentTest, PrintRelayout) {
setHtmlInnerHTML(
"<style>"
" div {"
" width: 100px;"
" height: 100px;"
" overflow: hidden;"
" }"
" span {"
" width: 50px;"
" height: 50px;"
" }"
" @media screen {"
" span {"
" width: 20px;"
" }"
" }"
"</style>"
"<p><div><span></span></div></p>");
FloatSize pageSize(400, 400);
float maximumShrinkRatio = 1.6;
document().frame()->setPrinting(true, pageSize, pageSize, maximumShrinkRatio);
EXPECT_EQ(document().documentElement()->offsetWidth(), 400);
document().frame()->setPrinting(false, FloatSize(), FloatSize(), 0);
EXPECT_EQ(document().documentElement()->offsetWidth(), 800);
}
// This test checks that Documunt::linkManifest() returns a value conform to the
// specification.
TEST_F(DocumentTest, LinkManifest) {
// Test the default result.
EXPECT_EQ(0, document().linkManifest());
// Check that we use the first manifest with <link rel=manifest>
HTMLLinkElement* link = HTMLLinkElement::create(document(), false);
link->setAttribute(blink::HTMLNames::relAttr, "manifest");
link->setAttribute(blink::HTMLNames::hrefAttr, "foo.json");
document().head()->appendChild(link);
EXPECT_EQ(link, document().linkManifest());
HTMLLinkElement* link2 = HTMLLinkElement::create(document(), false);
link2->setAttribute(blink::HTMLNames::relAttr, "manifest");
link2->setAttribute(blink::HTMLNames::hrefAttr, "bar.json");
document().head()->insertBefore(link2, link);
EXPECT_EQ(link2, document().linkManifest());
document().head()->appendChild(link2);
EXPECT_EQ(link, document().linkManifest());
// Check that crazy URLs are accepted.
link->setAttribute(blink::HTMLNames::hrefAttr, "http:foo.json");
EXPECT_EQ(link, document().linkManifest());
// Check that empty URLs are accepted.
link->setAttribute(blink::HTMLNames::hrefAttr, "");
EXPECT_EQ(link, document().linkManifest());
// Check that URLs from different origins are accepted.
link->setAttribute(blink::HTMLNames::hrefAttr,
"http://example.org/manifest.json");
EXPECT_EQ(link, document().linkManifest());
link->setAttribute(blink::HTMLNames::hrefAttr,
"http://foo.example.org/manifest.json");
EXPECT_EQ(link, document().linkManifest());
link->setAttribute(blink::HTMLNames::hrefAttr,
"http://foo.bar/manifest.json");
EXPECT_EQ(link, document().linkManifest());
// More than one token in @rel is accepted.
link->setAttribute(blink::HTMLNames::relAttr, "foo bar manifest");
EXPECT_EQ(link, document().linkManifest());
// Such as spaces around the token.
link->setAttribute(blink::HTMLNames::relAttr, " manifest ");
EXPECT_EQ(link, document().linkManifest());
// Check that rel=manifest actually matters.
link->setAttribute(blink::HTMLNames::relAttr, "");
EXPECT_EQ(link2, document().linkManifest());
link->setAttribute(blink::HTMLNames::relAttr, "manifest");
// Check that link outside of the <head> are ignored.
document().head()->removeChild(link);
document().head()->removeChild(link2);
EXPECT_EQ(0, document().linkManifest());
document().body()->appendChild(link);
EXPECT_EQ(0, document().linkManifest());
document().head()->appendChild(link);
document().head()->appendChild(link2);
// Check that some attribute values do not have an effect.
link->setAttribute(blink::HTMLNames::crossoriginAttr, "use-credentials");
EXPECT_EQ(link, document().linkManifest());
link->setAttribute(blink::HTMLNames::hreflangAttr, "klingon");
EXPECT_EQ(link, document().linkManifest());
link->setAttribute(blink::HTMLNames::typeAttr, "image/gif");
EXPECT_EQ(link, document().linkManifest());
link->setAttribute(blink::HTMLNames::sizesAttr, "16x16");
EXPECT_EQ(link, document().linkManifest());
link->setAttribute(blink::HTMLNames::mediaAttr, "print");
EXPECT_EQ(link, document().linkManifest());
}
TEST_F(DocumentTest, referrerPolicyParsing) {
EXPECT_EQ(ReferrerPolicyDefault, document().getReferrerPolicy());
struct TestCase {
const char* policy;
ReferrerPolicy expected;
bool isLegacy;
} tests[] = {
{"", ReferrerPolicyDefault, false},
// Test that invalid policy values are ignored.
{"not-a-real-policy", ReferrerPolicyDefault, false},
{"not-a-real-policy,also-not-a-real-policy", ReferrerPolicyDefault,
false},
{"not-a-real-policy,unsafe-url", ReferrerPolicyAlways, false},
{"unsafe-url,not-a-real-policy", ReferrerPolicyAlways, false},
// Test parsing each of the policy values.
{"always", ReferrerPolicyAlways, true},
{"default", ReferrerPolicyNoReferrerWhenDowngrade, true},
{"never", ReferrerPolicyNever, true},
{"no-referrer", ReferrerPolicyNever, false},
{"default", ReferrerPolicyNoReferrerWhenDowngrade, true},
{"no-referrer-when-downgrade", ReferrerPolicyNoReferrerWhenDowngrade,
false},
{"origin", ReferrerPolicyOrigin, false},
{"origin-when-crossorigin", ReferrerPolicyOriginWhenCrossOrigin, true},
{"origin-when-cross-origin", ReferrerPolicyOriginWhenCrossOrigin, false},
{"unsafe-url", ReferrerPolicyAlways},
};
for (auto test : tests) {
document().setReferrerPolicy(ReferrerPolicyDefault);
if (test.isLegacy) {
// Legacy keyword support must be explicitly enabled for the policy to
// parse successfully.
document().parseAndSetReferrerPolicy(test.policy);
EXPECT_EQ(ReferrerPolicyDefault, document().getReferrerPolicy());
document().parseAndSetReferrerPolicy(test.policy, true);
} else {
document().parseAndSetReferrerPolicy(test.policy);
}
EXPECT_EQ(test.expected, document().getReferrerPolicy()) << test.policy;
}
}
TEST_F(DocumentTest, OutgoingReferrer) {
document().setURL(KURL(KURL(), "https://www.example.com/hoge#fuga?piyo"));
document().setSecurityOrigin(
SecurityOrigin::create(KURL(KURL(), "https://www.example.com/")));
EXPECT_EQ("https://www.example.com/hoge", document().outgoingReferrer());
}
TEST_F(DocumentTest, OutgoingReferrerWithUniqueOrigin) {
document().setURL(KURL(KURL(), "https://www.example.com/hoge#fuga?piyo"));
document().setSecurityOrigin(SecurityOrigin::createUnique());
EXPECT_EQ(String(), document().outgoingReferrer());
}
TEST_F(DocumentTest, StyleVersion) {
setHtmlInnerHTML(
"<style>"
" .a * { color: green }"
" .b .c { color: green }"
"</style>"
"<div id='x'><span class='c'></span></div>");
Element* element = document().getElementById("x");
EXPECT_TRUE(element);
uint64_t previousStyleVersion = document().styleVersion();
element->setAttribute(blink::HTMLNames::classAttr, "notfound");
EXPECT_EQ(previousStyleVersion, document().styleVersion());
document().view()->updateAllLifecyclePhases();
previousStyleVersion = document().styleVersion();
element->setAttribute(blink::HTMLNames::classAttr, "a");
EXPECT_NE(previousStyleVersion, document().styleVersion());
document().view()->updateAllLifecyclePhases();
previousStyleVersion = document().styleVersion();
element->setAttribute(blink::HTMLNames::classAttr, "a b");
EXPECT_NE(previousStyleVersion, document().styleVersion());
}
TEST_F(DocumentTest, EnforceSandboxFlags) {
RefPtr<SecurityOrigin> origin =
SecurityOrigin::createFromString("http://example.test");
document().setSecurityOrigin(origin);
SandboxFlags mask = SandboxNavigation;
document().enforceSandboxFlags(mask);
EXPECT_EQ(origin, document().getSecurityOrigin());
EXPECT_FALSE(document().getSecurityOrigin()->isPotentiallyTrustworthy());
mask |= SandboxOrigin;
document().enforceSandboxFlags(mask);
EXPECT_TRUE(document().getSecurityOrigin()->isUnique());
EXPECT_FALSE(document().getSecurityOrigin()->isPotentiallyTrustworthy());
// A unique origin does not bypass secure context checks unless it
// is also potentially trustworthy.
SchemeRegistry::registerURLSchemeBypassingSecureContextCheck(
"very-special-scheme");
origin =
SecurityOrigin::createFromString("very-special-scheme://example.test");
document().setSecurityOrigin(origin);
document().enforceSandboxFlags(mask);
EXPECT_TRUE(document().getSecurityOrigin()->isUnique());
EXPECT_FALSE(document().getSecurityOrigin()->isPotentiallyTrustworthy());
SchemeRegistry::registerURLSchemeAsSecure("very-special-scheme");
document().setSecurityOrigin(origin);
document().enforceSandboxFlags(mask);
EXPECT_TRUE(document().getSecurityOrigin()->isUnique());
EXPECT_TRUE(document().getSecurityOrigin()->isPotentiallyTrustworthy());
origin = SecurityOrigin::createFromString("https://example.test");
document().setSecurityOrigin(origin);
document().enforceSandboxFlags(mask);
EXPECT_TRUE(document().getSecurityOrigin()->isUnique());
EXPECT_TRUE(document().getSecurityOrigin()->isPotentiallyTrustworthy());
}
TEST_F(DocumentTest, SynchronousMutationNotifier) {
auto& observer = *new TestSynchronousMutationObserver(document());
EXPECT_EQ(document(), observer.lifecycleContext());
EXPECT_EQ(0, observer.countContextDestroyedCalled());
Element* divNode = document().createElement("div");
document().body()->appendChild(divNode);
Element* boldNode = document().createElement("b");
divNode->appendChild(boldNode);
Element* italicNode = document().createElement("i");
divNode->appendChild(italicNode);
Node* textNode = document().createTextNode("0123456789");
boldNode->appendChild(textNode);
EXPECT_TRUE(observer.removedNodes().isEmpty());
textNode->remove();
ASSERT_EQ(1u, observer.removedNodes().size());
EXPECT_EQ(textNode, observer.removedNodes()[0]);
divNode->removeChildren();
EXPECT_EQ(1u, observer.removedNodes().size())
<< "ContainerNode::removeChildren() doesn't call nodeWillBeRemoved()";
ASSERT_EQ(1u, observer.removedChildrenNodes().size());
EXPECT_EQ(divNode, observer.removedChildrenNodes()[0]);
document().shutdown();
EXPECT_EQ(nullptr, observer.lifecycleContext());
EXPECT_EQ(1, observer.countContextDestroyedCalled());
}
TEST_F(DocumentTest, SynchronousMutationNotifieAppendChild) {
auto& observer = *new TestSynchronousMutationObserver(document());
document().body()->appendChild(document().createTextNode("a123456789"));
ASSERT_EQ(1u, observer.childrenChangedNodes().size());
EXPECT_EQ(document().body(), observer.childrenChangedNodes()[0]);
}
TEST_F(DocumentTest, SynchronousMutationNotifieInsertBefore) {
auto& observer = *new TestSynchronousMutationObserver(document());
document().documentElement()->insertBefore(
document().createTextNode("a123456789"), document().body());
ASSERT_EQ(1u, observer.childrenChangedNodes().size());
EXPECT_EQ(document().documentElement(), observer.childrenChangedNodes()[0]);
}
TEST_F(DocumentTest, SynchronousMutationNotifierMergeTextNodes) {
auto& observer = *new TestSynchronousMutationObserver(document());
Text* mergeSampleA = document().createTextNode("a123456789");
document().body()->appendChild(mergeSampleA);
Text* mergeSampleB = document().createTextNode("b123456789");
document().body()->appendChild(mergeSampleB);
EXPECT_EQ(0u, observer.mergeTextNodesRecords().size());
document().body()->normalize();
ASSERT_EQ(1u, observer.mergeTextNodesRecords().size());
EXPECT_EQ(mergeSampleA, observer.mergeTextNodesRecords()[0]->m_node);
EXPECT_EQ(mergeSampleB,
observer.mergeTextNodesRecords()[0]->m_nodeToBeRemoved);
EXPECT_EQ(10u, observer.mergeTextNodesRecords()[0]->m_offset);
}
TEST_F(DocumentTest, SynchronousMutationNotifierMoveTreeToNewDocument) {
auto& observer = *new TestSynchronousMutationObserver(document());
Node* moveSample = document().createElement("div");
moveSample->appendChild(document().createTextNode("a123"));
moveSample->appendChild(document().createTextNode("b456"));
document().body()->appendChild(moveSample);
Document& anotherDocument = *Document::create();
anotherDocument.appendChild(moveSample);
EXPECT_EQ(1u, observer.moveTreeToNewDocumentNodes().size());
EXPECT_EQ(moveSample, observer.moveTreeToNewDocumentNodes()[0]);
}
TEST_F(DocumentTest, SynchronousMutationNotifieRemoveChild) {
auto& observer = *new TestSynchronousMutationObserver(document());
document().documentElement()->removeChild(document().body());
ASSERT_EQ(1u, observer.childrenChangedNodes().size());
EXPECT_EQ(document().documentElement(), observer.childrenChangedNodes()[0]);
}
TEST_F(DocumentTest, SynchronousMutationNotifieReplaceChild) {
auto& observer = *new TestSynchronousMutationObserver(document());
Element* const replacedNode = document().body();
document().documentElement()->replaceChild(document().createElement("div"),
document().body());
ASSERT_EQ(2u, observer.childrenChangedNodes().size());
EXPECT_EQ(document().documentElement(), observer.childrenChangedNodes()[0]);
EXPECT_EQ(document().documentElement(), observer.childrenChangedNodes()[1]);
ASSERT_EQ(1u, observer.removedNodes().size());
EXPECT_EQ(replacedNode, observer.removedNodes()[0]);
}
TEST_F(DocumentTest, SynchronousMutationNotifierSplitTextNode) {
auto& observer = *new TestSynchronousMutationObserver(document());
Text* splitSample = document().createTextNode("0123456789");
document().body()->appendChild(splitSample);
splitSample->splitText(4, ASSERT_NO_EXCEPTION);
ASSERT_EQ(1u, observer.splitTextNodes().size());
EXPECT_EQ(splitSample, observer.splitTextNodes()[0]);
}
TEST_F(DocumentTest, SynchronousMutationNotifierUpdateCharacterData) {
auto& observer = *new TestSynchronousMutationObserver(document());
Text* appendSample = document().createTextNode("a123456789");
document().body()->appendChild(appendSample);
Text* deleteSample = document().createTextNode("b123456789");
document().body()->appendChild(deleteSample);
Text* insertSample = document().createTextNode("c123456789");
document().body()->appendChild(insertSample);
Text* replaceSample = document().createTextNode("c123456789");
document().body()->appendChild(replaceSample);
EXPECT_EQ(0u, observer.updatedCharacterDataRecords().size());
appendSample->appendData("abc");
ASSERT_EQ(1u, observer.updatedCharacterDataRecords().size());
EXPECT_EQ(appendSample, observer.updatedCharacterDataRecords()[0]->m_node);
EXPECT_EQ(10u, observer.updatedCharacterDataRecords()[0]->m_offset);
EXPECT_EQ(0u, observer.updatedCharacterDataRecords()[0]->m_oldLength);
EXPECT_EQ(3u, observer.updatedCharacterDataRecords()[0]->m_newLength);
deleteSample->deleteData(3, 4, ASSERT_NO_EXCEPTION);
ASSERT_EQ(2u, observer.updatedCharacterDataRecords().size());
EXPECT_EQ(deleteSample, observer.updatedCharacterDataRecords()[1]->m_node);
EXPECT_EQ(3u, observer.updatedCharacterDataRecords()[1]->m_offset);
EXPECT_EQ(4u, observer.updatedCharacterDataRecords()[1]->m_oldLength);
EXPECT_EQ(0u, observer.updatedCharacterDataRecords()[1]->m_newLength);
insertSample->insertData(3, "def", ASSERT_NO_EXCEPTION);
ASSERT_EQ(3u, observer.updatedCharacterDataRecords().size());
EXPECT_EQ(insertSample, observer.updatedCharacterDataRecords()[2]->m_node);
EXPECT_EQ(3u, observer.updatedCharacterDataRecords()[2]->m_offset);
EXPECT_EQ(0u, observer.updatedCharacterDataRecords()[2]->m_oldLength);
EXPECT_EQ(3u, observer.updatedCharacterDataRecords()[2]->m_newLength);
replaceSample->replaceData(6, 4, "ghi", ASSERT_NO_EXCEPTION);
ASSERT_EQ(4u, observer.updatedCharacterDataRecords().size());
EXPECT_EQ(replaceSample, observer.updatedCharacterDataRecords()[3]->m_node);
EXPECT_EQ(6u, observer.updatedCharacterDataRecords()[3]->m_offset);
EXPECT_EQ(4u, observer.updatedCharacterDataRecords()[3]->m_oldLength);
EXPECT_EQ(3u, observer.updatedCharacterDataRecords()[3]->m_newLength);
}
// This tests that meta-theme-color can be found correctly
TEST_F(DocumentTest, ThemeColor) {
{
setHtmlInnerHTML(
"<meta name=\"theme-color\" content=\"#00ff00\">"
"<body>");
EXPECT_EQ(Color(0, 255, 0), document().themeColor())
<< "Theme color should be bright green.";
}
{
setHtmlInnerHTML(
"<body>"
"<meta name=\"theme-color\" content=\"#00ff00\">");
EXPECT_EQ(Color(0, 255, 0), document().themeColor())
<< "Theme color should be bright green.";
}
}
TEST_F(DocumentTest, ValidationMessageCleanup) {
ValidationMessageClient* originalClient = &page().validationMessageClient();
MockValidationMessageClient* mockClient = new MockValidationMessageClient();
document().settings()->setScriptEnabled(true);
page().setValidationMessageClient(mockClient);
// implicitOpen()-implicitClose() makes Document.loadEventFinished()
// true. It's necessary to kick unload process.
document().implicitOpen(ForceSynchronousParsing);
document().implicitClose();
document().appendChild(document().createElement("html"));
setHtmlInnerHTML("<body><input required></body>");
Element* script = document().createElement("script");
script->setTextContent(
"window.onunload = function() {"
"document.querySelector('input').reportValidity(); };");
document().body()->appendChild(script);
HTMLInputElement* input = toHTMLInputElement(document().body()->firstChild());
DVLOG(0) << document().body()->outerHTML();
// Sanity check.
input->reportValidity();
EXPECT_TRUE(mockClient->showValidationMessageWasCalled);
mockClient->reset();
// prepareForCommit() unloads the document, and shutdown.
document().frame()->prepareForCommit();
EXPECT_TRUE(mockClient->willUnloadDocumentWasCalled);
EXPECT_TRUE(mockClient->documentDetachedWasCalled);
// Unload handler tried to show a validation message, but it should fail.
EXPECT_FALSE(mockClient->showValidationMessageWasCalled);
page().setValidationMessageClient(originalClient);
}
} // namespace blink