Diff
Modified: trunk/LayoutTests/ChangeLog (210832 => 210833)
--- trunk/LayoutTests/ChangeLog 2017-01-18 00:11:30 UTC (rev 210832)
+++ trunk/LayoutTests/ChangeLog 2017-01-18 00:14:37 UTC (rev 210833)
@@ -1,3 +1,22 @@
+2017-01-17 Chris Dumez <[email protected]>
+
+ Document title changed twice when setting document.title
+ https://bugs.webkit.org/show_bug.cgi?id=167065
+
+ Reviewed by Darin Adler.
+
+ * fast/dom/Node/textContent-mutationEvents-expected.txt: Added.
+ * fast/dom/Node/textContent-mutationEvents.html: Added.
+ Add layout test to make sure that the mutation events are properly
+ fired when setting Node.textContent.
+
+ * fast/dom/title-text-property-2-expected.txt:
+ * fast/dom/title-text-property-2.html:
+ * fast/dom/title-text-property-expected.txt:
+ * http/tests/globalhistory/history-delegate-basic-title-expected.txt:
+ Update / rebaseline existing tests now that we no longer temporarily
+ reset document.title to the empty string when overriding the title.
+
2017-01-17 Zalan Bujtas <[email protected]>
Editing nested RTL-LTR content makes the process unresponsive.
Added: trunk/LayoutTests/fast/dom/Node/textContent-mutationEvents-expected.txt (0 => 210833)
--- trunk/LayoutTests/fast/dom/Node/textContent-mutationEvents-expected.txt (rev 0)
+++ trunk/LayoutTests/fast/dom/Node/textContent-mutationEvents-expected.txt 2017-01-18 00:14:37 UTC (rev 210833)
@@ -0,0 +1,44 @@
+Tests that the DOM mutation events are fired correctly when Node.textContent is set.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+title.textContent = 'New title';
+
+* DOMNodeRemoved fired
+PASS firedDOMNodeRemoved is false
+PASS firedDOMNodeInserted is false
+PASS firedDOMSubtreeModified is false
+PASS document.title is "Original title"
+
+* DOMNodeInserted fired
+PASS firedDOMNodeRemoved is true
+PASS firedDOMNodeInserted is false
+PASS firedDOMSubtreeModified is false
+PASS document.title is "New title"
+
+* DOMSubtreeModified fired
+PASS firedDOMNodeRemoved is true
+PASS firedDOMNodeInserted is true
+PASS firedDOMSubtreeModified is false
+PASS document.title is "New title"
+
+PASS firedDOMNodeRemoved is true
+PASS firedDOMNodeInserted is true
+PASS firedDOMSubtreeModified is true
+PASS document.title is "New title"
+PASS firedMutationObserver is false
+
+* Mutation observer invoked
+PASS firedMutationObserver is false
+PASS testMutations.length is 1
+PASS mutation.type is "childList"
+PASS mutation.target is title
+PASS mutation.removedNodes.length is 1
+PASS mutation.removedNodes[0].data is "Original title"
+PASS mutation.addedNodes.length is 1
+PASS mutation.addedNodes[0].data is "New title"
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
Added: trunk/LayoutTests/fast/dom/Node/textContent-mutationEvents.html (0 => 210833)
--- trunk/LayoutTests/fast/dom/Node/textContent-mutationEvents.html (rev 0)
+++ trunk/LayoutTests/fast/dom/Node/textContent-mutationEvents.html 2017-01-18 00:14:37 UTC (rev 210833)
@@ -0,0 +1,79 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Original title</title>
+<script src=""
+</head>
+<body>
+<script>
+description("Tests that the DOM mutation events are fired correctly when Node.textContent is set.");
+jsTestIsAsync = true;
+
+var firedDOMNodeRemoved = false;
+var firedDOMNodeInserted = false;
+var firedDOMSubtreeModified = false;
+var firedMutationObserver = false;
+
+var title = document.getElementsByTagName("title")[0];
+var observer = new MutationObserver(function(mutations) {
+ debug("");
+ debug("* Mutation observer invoked");
+
+ shouldBeFalse("firedMutationObserver");
+ firedMutationObserver = true;
+
+ testMutations = mutations;
+ shouldBe("testMutations.length", "1");
+ mutation = mutations[0];
+ shouldBeEqualToString("mutation.type", "childList");
+ shouldBe("mutation.target", "title");
+ shouldBe("mutation.removedNodes.length", "1");
+ shouldBeEqualToString("mutation.removedNodes[0].data", "Original title");
+ shouldBe("mutation.addedNodes.length", "1");
+ shouldBeEqualToString("mutation.addedNodes[0].data", "New title");
+
+ finishJSTest();
+});
+observer.observe(title, { childList: true });
+
+title.addEventListener("DOMNodeRemoved", function() {
+ debug("");
+ debug("* DOMNodeRemoved fired");
+ shouldBeFalse("firedDOMNodeRemoved");
+ shouldBeFalse("firedDOMNodeInserted");
+ shouldBeFalse("firedDOMSubtreeModified");
+ firedDOMNodeRemoved = true;
+ shouldBeEqualToString("document.title", "Original title");
+});
+title.addEventListener("DOMNodeInserted", function() {
+ debug("");
+ debug("* DOMNodeInserted fired");
+ shouldBeTrue("firedDOMNodeRemoved");
+ shouldBeFalse("firedDOMNodeInserted");
+ shouldBeFalse("firedDOMSubtreeModified");
+ firedDOMNodeInserted = true;
+ shouldBeEqualToString("document.title", "New title");
+});
+title.addEventListener("DOMSubtreeModified", function() {
+ debug("");
+ debug("* DOMSubtreeModified fired");
+ shouldBeTrue("firedDOMNodeRemoved");
+ shouldBeTrue("firedDOMNodeInserted");
+ shouldBeFalse("firedDOMSubtreeModified");
+ firedDOMSubtreeModified = true;
+ shouldBeEqualToString("document.title", "New title");
+});
+
+evalAndLog("title.textContent = 'New title';");
+
+debug("");
+shouldBeTrue("firedDOMNodeRemoved");
+shouldBeTrue("firedDOMNodeInserted");
+shouldBeTrue("firedDOMSubtreeModified");
+shouldBeEqualToString("document.title", "New title");
+shouldBeFalse("firedMutationObserver");
+
+</script>
+<script src=""
+</body>
+</html>
Modified: trunk/LayoutTests/fast/dom/title-text-property-2-expected.txt (210832 => 210833)
--- trunk/LayoutTests/fast/dom/title-text-property-2-expected.txt 2017-01-18 00:11:30 UTC (rev 210832)
+++ trunk/LayoutTests/fast/dom/title-text-property-2-expected.txt 2017-01-18 00:14:37 UTC (rev 210833)
@@ -1,6 +1,8 @@
+CONSOLE MESSAGE: line 10: Setting document.title to TITLE1
+TITLE CHANGED: 'TITLE1'
+CONSOLE MESSAGE: line 15: Setting title element's text to TITLE2
+TITLE CHANGED: 'TITLE2'
+CONSOLE MESSAGE: line 19: Should not set title
+CONSOLE MESSAGE: line 25: Should reset title to the empty string
TITLE CHANGED: ''
-TITLE CHANGED: '1. setting document.title'
-TITLE CHANGED: ''
-TITLE CHANGED: '2. setting title.text'
-TITLE CHANGED: ''
Modified: trunk/LayoutTests/fast/dom/title-text-property-2.html (210832 => 210833)
--- trunk/LayoutTests/fast/dom/title-text-property-2.html 2017-01-18 00:11:30 UTC (rev 210832)
+++ trunk/LayoutTests/fast/dom/title-text-property-2.html 2017-01-18 00:14:37 UTC (rev 210833)
@@ -6,19 +6,23 @@
testRunner.dumpAsText();
testRunner.dumpTitleChanges();
}
-
- document.title = '1. setting document.title';
+ console.log("Setting document.title to TITLE1");
+ document.title = 'TITLE1';
+
title = document.getElementsByTagName('title').item(0);
- title.text = '2. setting title.text';
+ console.log("Setting title element's text to TITLE2");
+ title.text = 'TITLE2';
newTitle = document.createElement('title');
- newTitle.appendChild(document.createTextNode('3. appending a new title node (should not trigger a title change)'));
+ console.log("Should not set title");
+ newTitle.appendChild(document.createTextNode('FAIL'));
document.getElementsByTagName('head').item(0).appendChild(newTitle);
-
+
// Remove both title elements
+ console.log("Should reset title to the empty string");
titleElements = document.getElementsByTagName('title');
while (titleElements.length) {
titleElement = titleElements[titleElements.length - 1];
Modified: trunk/LayoutTests/fast/dom/title-text-property-expected.txt (210832 => 210833)
--- trunk/LayoutTests/fast/dom/title-text-property-expected.txt 2017-01-18 00:11:30 UTC (rev 210832)
+++ trunk/LayoutTests/fast/dom/title-text-property-expected.txt 2017-01-18 00:14:37 UTC (rev 210833)
@@ -1,4 +1,3 @@
-TITLE CHANGED: ''
TITLE CHANGED: 'This is the new title'
Original title is: 'Original Title'
Setting new title to: 'This is the new title'
Modified: trunk/LayoutTests/http/tests/globalhistory/history-delegate-basic-title-expected.txt (210832 => 210833)
--- trunk/LayoutTests/http/tests/globalhistory/history-delegate-basic-title-expected.txt 2017-01-18 00:11:30 UTC (rev 210832)
+++ trunk/LayoutTests/http/tests/globalhistory/history-delegate-basic-title-expected.txt 2017-01-18 00:14:37 UTC (rev 210833)
@@ -1,5 +1,4 @@
WebView navigated to url "http://127.0.0.1:8000/globalhistory/history-delegate-basic-title.html" with title "" with HTTP equivalent method "GET". The navigation was successful and was not a client redirect.
WebView updated the title for history URL "http://127.0.0.1:8000/globalhistory/history-delegate-basic-title.html" to "Test Title 1".
-WebView updated the title for history URL "http://127.0.0.1:8000/globalhistory/history-delegate-basic-title.html" to "".
WebView updated the title for history URL "http://127.0.0.1:8000/globalhistory/history-delegate-basic-title.html" to "Test Title 2".
This test sees if the history delegate is notified of title changes.
Modified: trunk/Source/WebCore/ChangeLog (210832 => 210833)
--- trunk/Source/WebCore/ChangeLog 2017-01-18 00:11:30 UTC (rev 210832)
+++ trunk/Source/WebCore/ChangeLog 2017-01-18 00:14:37 UTC (rev 210833)
@@ -1,3 +1,58 @@
+2017-01-17 Chris Dumez <[email protected]>
+
+ Document title changed twice when setting document.title
+ https://bugs.webkit.org/show_bug.cgi?id=167065
+
+ Reviewed by Darin Adler.
+
+ Setting document.title would call the document title to be set twice
+ first to the empty string and then to the new title. This is because
+ setting document.title is equivalent to setting title.textContent [1],
+ which first removes all children and then inserts the new one [2], and
+ we call updateTitle() for each step. This is because
+ HTMLTitleElement::childrenChanged() is called twice (once for the
+ removal of the existing children, and a second time when the new child
+ is inserted), and childrenChanged() calls document::titleElementTextChanged().
+
+ Since no JS event is fired between those 2 mutations, it is safe (i.e. non
+ observable from JS) to update the title only once after both mutations have
+ taken place. To achieve this, add a new replaceAllChildren() function
+ which implements [3]. This replaceAllChildren() has the benefit of
+ calling ContainerNode::childrenChanged() only once, after both mutations
+ have taken place, thus avoiding unnecessary work. This fixes the issue
+ when setting the title and should be performance-positive in general.
+
+ [1] https://html.spec.whatwg.org/#document.title
+ [2] https://dom.spec.whatwg.org/#dom-node-textcontent
+ [3] https://dom.spec.whatwg.org/#concept-node-replace-all
+
+ Test: fast/dom/Node/textContent-mutationEvents.html
+
+ * dom/ContainerNode.cpp:
+ (WebCore::ContainerNode::notifyChildInserted):
+ (WebCore::ContainerNode::removeChild):
+ (WebCore::ContainerNode::replaceAllChildren):
+ (WebCore::ContainerNode::rebuildSVGExtensionsElementsIfNecessary):
+ (WebCore::ContainerNode::removeChildren):
+ (WebCore::ContainerNode::updateTreeAfterInsertion):
+ * dom/ContainerNode.h:
+ Add new replaceAllChildrenWith() function which implements [3]
+ in a way that calls ContainerNode::childrenChanged() only once.
+
+ * dom/Element.cpp:
+ (WebCore::Element::childrenChanged):
+ Deal with new AllChildrenReplaced ChildChange type.
+
+ * dom/Node.cpp:
+ (WebCore::Node::setTextContent):
+ Call replaceAllChildrenWith() as per the specification:
+ - https://dom.spec.whatwg.org/#dom-node-textcontent
+
+ * dom/Range.cpp:
+ (WebCore::Range::surroundContents):
+ Call replaceAllChildrenWith(nullptr) as per the specification:
+ - https://dom.spec.whatwg.org/#dom-range-surroundcontents
+
2017-01-17 Joseph Pecoraro <[email protected]>
ENABLE(USER_TIMING) Not Defined for Apple Windows or OS X Ports
Modified: trunk/Source/WebCore/dom/ContainerNode.cpp (210832 => 210833)
--- trunk/Source/WebCore/dom/ContainerNode.cpp 2017-01-18 00:11:30 UTC (rev 210832)
+++ trunk/Source/WebCore/dom/ContainerNode.cpp 2017-01-18 00:14:37 UTC (rev 210833)
@@ -56,6 +56,7 @@
#include "SVGDocumentExtensions.h"
#include "SVGElement.h"
#include "SVGNames.h"
+#include "SVGUseElement.h"
#include "SelectorQuery.h"
#include "TemplateContentDocumentFragment.h"
#include <algorithm>
@@ -327,19 +328,26 @@
m_lastChild = &child;
}
-void ContainerNode::notifyChildInserted(Node& child, ChildChangeSource source)
+inline auto ContainerNode::changeForChildInsertion(Node& child, ChildChangeSource source, ReplacedAllChildren replacedAllChildren) -> ChildChange
{
+ if (replacedAllChildren == ReplacedAllChildren::Yes)
+ return { AllChildrenReplaced, nullptr, nullptr, source };
+
+ return {
+ child.isElementNode() ? ElementInserted : child.isTextNode() ? TextInserted : NonContentsChildChanged,
+ ElementTraversal::previousSibling(child),
+ ElementTraversal::nextSibling(child),
+ source
+ };
+}
+
+void ContainerNode::notifyChildInserted(Node& child, const ChildChange& change)
+{
ChildListMutationScope(*this).childAdded(child);
NodeVector postInsertionNotificationTargets;
notifyChildNodeInserted(*this, child, postInsertionNotificationTargets);
- ChildChange change;
- change.type = child.isElementNode() ? ElementInserted : child.isTextNode() ? TextInserted : NonContentsChildChanged;
- change.previousSiblingElement = ElementTraversal::previousSibling(child);
- change.nextSiblingElement = ElementTraversal::nextSibling(child);
- change.source = source;
-
childrenChanged(change);
for (auto& target : postInsertionNotificationTargets)
@@ -375,7 +383,7 @@
newChild.updateAncestorConnectedSubframeCountForInsertion();
- notifyChildInserted(newChild, ChildChangeSourceParser);
+ notifyChildInserted(newChild, changeForChildInsertion(newChild, ChildChangeSourceParser));
}
ExceptionOr<void> ContainerNode::replaceChild(Node& newChild, Node& oldChild)
@@ -534,13 +542,7 @@
notifyChildRemoved(child, prev, next, ChildChangeSourceAPI);
}
-
- if (document().svgExtensions()) {
- Element* shadowHost = this->shadowHost();
- if (!shadowHost || !shadowHost->hasTagName(SVGNames::useTag))
- document().accessSVGExtensions().rebuildElements();
- }
-
+ rebuildSVGExtensionsElementsIfNecessary();
dispatchSubtreeModifiedEvent();
return { };
@@ -598,6 +600,65 @@
notifyChildRemoved(oldChild, prev, next, ChildChangeSourceParser);
}
+// https://dom.spec.whatwg.org/#concept-node-replace-all
+void ContainerNode::replaceAllChildren(std::nullptr_t)
+{
+ ChildListMutationScope mutation(*this);
+ removeChildren();
+}
+
+// https://dom.spec.whatwg.org/#concept-node-replace-all
+void ContainerNode::replaceAllChildren(Ref<Node>&& node)
+{
+ // This function assumes the input node is not a DocumentFragment and is parentless to decrease complexity.
+ ASSERT(!is<DocumentFragment>(node));
+ ASSERT(!node->parentNode());
+
+ if (!hasChildNodes()) {
+ // appendChildWithoutPreInsertionValidityCheck() can only throw when node has a parent and we already asserted it doesn't.
+ auto result = appendChildWithoutPreInsertionValidityCheck(node);
+ ASSERT_UNUSED(result, !result.hasException());
+ return;
+ }
+
+ Ref<ContainerNode> protectedThis(*this);
+ ChildListMutationScope mutation(*this);
+
+ // If node is not null, adopt node into parent’s node document.
+ treeScope().adoptIfNeeded(node);
+
+ // Remove all parent's children, in tree order.
+ willRemoveChildren(*this);
+
+ {
+ WidgetHierarchyUpdatesSuspensionScope suspendWidgetHierarchyUpdates;
+ {
+ NoEventDispatchAssertion assertNoEventDispatch;
+ while (RefPtr<Node> child = m_firstChild) {
+ removeBetween(nullptr, child->nextSibling(), *child);
+ notifyChildNodeRemoved(*this, *child);
+ }
+
+ // If node is not null, insert node into parent before null.
+ ASSERT(!ensurePreInsertionValidity(node, nullptr).hasException());
+ InspectorInstrumentation::willInsertDOMNode(document(), *this);
+
+ appendChildCommon(node);
+ }
+
+ updateTreeAfterInsertion(node, ReplacedAllChildren::Yes);
+ }
+
+ rebuildSVGExtensionsElementsIfNecessary();
+ dispatchSubtreeModifiedEvent();
+}
+
+inline void ContainerNode::rebuildSVGExtensionsElementsIfNecessary()
+{
+ if (document().svgExtensions() && !is<SVGUseElement>(shadowHost()))
+ document().accessSVGExtensions().rebuildElements();
+}
+
// this differs from other remove functions because it forcibly removes all the children,
// regardless of read-only status or event exceptions, e.g.
void ContainerNode::removeChildren()
@@ -626,12 +687,7 @@
childrenChanged(change);
}
- if (document().svgExtensions()) {
- Element* shadowHost = this->shadowHost();
- if (!shadowHost || !shadowHost->hasTagName(SVGNames::useTag))
- document().accessSVGExtensions().rebuildElements();
- }
-
+ rebuildSVGExtensionsElementsIfNecessary();
dispatchSubtreeModifiedEvent();
}
@@ -709,7 +765,7 @@
newChild.updateAncestorConnectedSubframeCountForInsertion();
- notifyChildInserted(newChild, ChildChangeSourceParser);
+ notifyChildInserted(newChild, changeForChildInsertion(newChild, ChildChangeSourceParser));
}
void ContainerNode::childrenChanged(const ChildChange& change)
@@ -792,11 +848,11 @@
}
}
-void ContainerNode::updateTreeAfterInsertion(Node& child)
+void ContainerNode::updateTreeAfterInsertion(Node& child, ReplacedAllChildren replacedAllChildren)
{
ASSERT(child.refCount());
- notifyChildInserted(child, ChildChangeSourceAPI);
+ notifyChildInserted(child, changeForChildInsertion(child, ChildChangeSourceAPI, replacedAllChildren));
dispatchChildInsertionEvents(child);
}
Modified: trunk/Source/WebCore/dom/ContainerNode.h (210832 => 210833)
--- trunk/Source/WebCore/dom/ContainerNode.h 2017-01-18 00:11:30 UTC (rev 210832)
+++ trunk/Source/WebCore/dom/ContainerNode.h 2017-01-18 00:14:37 UTC (rev 210833)
@@ -53,6 +53,8 @@
ExceptionOr<void> replaceChild(Node& newChild, Node& oldChild);
WEBCORE_EXPORT ExceptionOr<void> removeChild(Node& child);
WEBCORE_EXPORT ExceptionOr<void> appendChild(Node& newChild);
+ void replaceAllChildren(Ref<Node>&&);
+ void replaceAllChildren(std::nullptr_t);
// These methods are only used during parsing.
// They don't send DOM mutation events or handle reparenting.
@@ -62,11 +64,12 @@
void parserInsertBefore(Node& newChild, Node& refChild);
void removeChildren();
+
void takeAllChildrenFrom(ContainerNode*);
void cloneChildNodes(ContainerNode& clone);
- enum ChildChangeType { ElementInserted, ElementRemoved, TextInserted, TextRemoved, TextChanged, AllChildrenRemoved, NonContentsChildChanged };
+ enum ChildChangeType { ElementInserted, ElementRemoved, TextInserted, TextRemoved, TextChanged, AllChildrenRemoved, NonContentsChildChanged, AllChildrenReplaced };
enum ChildChangeSource { ChildChangeSourceParser, ChildChangeSourceAPI };
struct ChildChange {
ChildChangeType type;
@@ -120,10 +123,13 @@
void insertBeforeCommon(Node& nextChild, Node& oldChild);
void appendChildCommon(Node&);
- void notifyChildInserted(Node& child, ChildChangeSource);
+ void notifyChildInserted(Node& child, const ChildChange&);
void notifyChildRemoved(Node& child, Node* previousSibling, Node* nextSibling, ChildChangeSource);
- void updateTreeAfterInsertion(Node& child);
+ enum class ReplacedAllChildren { No, Yes };
+ void updateTreeAfterInsertion(Node& child, ReplacedAllChildren = ReplacedAllChildren::No);
+ static ChildChange changeForChildInsertion(Node&, ChildChangeSource, ReplacedAllChildren = ReplacedAllChildren::No);
+ void rebuildSVGExtensionsElementsIfNecessary();
bool isContainerNode() const = delete;
Modified: trunk/Source/WebCore/dom/Element.cpp (210832 => 210833)
--- trunk/Source/WebCore/dom/Element.cpp 2017-01-18 00:11:30 UTC (rev 210832)
+++ trunk/Source/WebCore/dom/Element.cpp 2017-01-18 00:14:37 UTC (rev 210833)
@@ -2035,6 +2035,7 @@
// For elements, we notify shadowRoot in Element::insertedInto and Element::removedFrom.
break;
case AllChildrenRemoved:
+ case AllChildrenReplaced:
shadowRoot->didRemoveAllChildrenOfShadowHost();
break;
case TextInserted:
Modified: trunk/Source/WebCore/dom/Node.cpp (210832 => 210833)
--- trunk/Source/WebCore/dom/Node.cpp 2017-01-18 00:11:30 UTC (rev 210832)
+++ trunk/Source/WebCore/dom/Node.cpp 2017-01-18 00:14:37 UTC (rev 210833)
@@ -1507,12 +1507,12 @@
return setNodeValue(text);
case ELEMENT_NODE:
case DOCUMENT_FRAGMENT_NODE: {
- auto container = makeRef(downcast<ContainerNode>(*this));
- ChildListMutationScope mutation(container);
- container->removeChildren();
+ auto& container = downcast<ContainerNode>(*this);
if (text.isEmpty())
- return { };
- return container->appendChild(document().createTextNode(text));
+ container.replaceAllChildren(nullptr);
+ else
+ container.replaceAllChildren(document().createTextNode(text));
+ return { };
}
case DOCUMENT_NODE:
case DOCUMENT_TYPE_NODE:
Modified: trunk/Source/WebCore/dom/Range.cpp (210832 => 210833)
--- trunk/Source/WebCore/dom/Range.cpp 2017-01-18 00:11:30 UTC (rev 210832)
+++ trunk/Source/WebCore/dom/Range.cpp 2017-01-18 00:14:37 UTC (rev 210833)
@@ -1107,11 +1107,8 @@
return fragment.releaseException();
// Step 4: If newParent has children, replace all with null within newParent.
- while (auto* child = newParent.firstChild()) {
- auto result = downcast<ContainerNode>(newParent).removeChild(*child);
- if (result.hasException())
- return result.releaseException();
- }
+ if (newParent.hasChildNodes())
+ downcast<ContainerNode>(newParent).replaceAllChildren(nullptr);
// Step 5: Insert newParent into context object.
auto insertResult = insertNode(newParent);
Modified: trunk/Tools/ChangeLog (210832 => 210833)
--- trunk/Tools/ChangeLog 2017-01-18 00:11:30 UTC (rev 210832)
+++ trunk/Tools/ChangeLog 2017-01-18 00:14:37 UTC (rev 210833)
@@ -1,3 +1,16 @@
+2017-01-17 Chris Dumez <[email protected]>
+
+ Document title changed twice when setting document.title
+ https://bugs.webkit.org/show_bug.cgi?id=167065
+
+ Reviewed by Darin Adler.
+
+ Add WebKit2GTK API test that was written by Michael Catanzaro.
+
+ * TestWebKitAPI/Tests/WebKit2Gtk/TestWebKitWebView.cpp:
+ (testWebViewTitleChange):
+ (beforeAll):
+
2017-01-17 Joseph Pecoraro <[email protected]>
ENABLE(USER_TIMING) Not Defined for Apple Windows or OS X Ports
Modified: trunk/Tools/TestWebKitAPI/Tests/WebKit2Gtk/TestWebKitWebView.cpp (210832 => 210833)
--- trunk/Tools/TestWebKitAPI/Tests/WebKit2Gtk/TestWebKitWebView.cpp 2017-01-18 00:11:30 UTC (rev 210832)
+++ trunk/Tools/TestWebKitAPI/Tests/WebKit2Gtk/TestWebKitWebView.cpp 2017-01-18 00:14:37 UTC (rev 210833)
@@ -947,6 +947,52 @@
g_assert_cmpint(naturalSize.height, ==, 615);
}
+class WebViewTitleTest: public WebViewTest {
+public:
+ MAKE_GLIB_TEST_FIXTURE(WebViewTitleTest);
+
+ static void titleChangedCallback(WebKitWebView* view, GParamSpec*, WebViewTitleTest* test)
+ {
+ test->m_webViewTitles.append(webkit_web_view_get_title(view));
+ }
+
+ WebViewTitleTest()
+ {
+ g_signal_connect(m_webView, "notify::title", G_CALLBACK(titleChangedCallback), this);
+ }
+
+ Vector<CString> m_webViewTitles;
+};
+
+static void testWebViewTitleChange(WebViewTitleTest* test, gconstpointer)
+{
+ g_assert_cmpint(test->m_webViewTitles.size(), ==, 0);
+
+ test->loadHtml("<head><title>Page Title</title></head>", nullptr);
+ test->waitUntilLoadFinished();
+ g_assert_cmpint(test->m_webViewTitles.size(), ==, 1);
+ g_assert_cmpstr(test->m_webViewTitles[0].data(), ==, "Page Title");
+
+ test->loadHtml("<head><title>Another Page Title</title></head>", nullptr);
+ test->waitUntilLoadFinished();
+ g_assert_cmpint(test->m_webViewTitles.size(), ==, 3);
+ /* Page title should be immediately unset when loading a new page. */
+ g_assert_cmpstr(test->m_webViewTitles[1].data(), ==, "");
+ g_assert_cmpstr(test->m_webViewTitles[2].data(), ==, "Another Page Title");
+
+ test->loadHtml("<p>This page has no title!</p>", nullptr);
+ test->waitUntilLoadFinished();
+ g_assert_cmpint(test->m_webViewTitles.size(), ==, 4);
+ g_assert_cmpstr(test->m_webViewTitles[3].data(), ==, "");
+
+ test->loadHtml("<script>document.title = 'one'; document.title = 'two'; document.title = 'three';</script>", nullptr);
+ test->waitUntilLoadFinished();
+ g_assert_cmpint(test->m_webViewTitles.size(), ==, 7);
+ g_assert_cmpstr(test->m_webViewTitles[4].data(), ==, "one");
+ g_assert_cmpstr(test->m_webViewTitles[5].data(), ==, "two");
+ g_assert_cmpstr(test->m_webViewTitles[6].data(), ==, "three");
+}
+
static void serverCallback(SoupServer* server, SoupMessage* message, const char* path, GHashTable*, SoupClientContext*, gpointer)
{
if (message->method != SOUP_METHOD_GET) {
@@ -984,6 +1030,7 @@
IsPlayingAudioWebViewTest::add("WebKitWebView", "is-playing-audio", testWebViewIsPlayingAudio);
WebViewTest::add("WebKitWebView", "background-color", testWebViewBackgroundColor);
WebViewTest::add("WebKitWebView", "preferred-size", testWebViewPreferredSize);
+ WebViewTitleTest::add("WebKitWebView", "title-change", testWebViewTitleChange);
}
void afterAll()