Index: Source/web/tests/WebFrameTest.cpp |
diff --git a/Source/web/tests/WebFrameTest.cpp b/Source/web/tests/WebFrameTest.cpp |
index 37d4f7efab0f4638a0e830bf2ac87dbef1ef4ed7..9cca26576890501468edcebed4f7bc618ab29039 100644 |
--- a/Source/web/tests/WebFrameTest.cpp |
+++ b/Source/web/tests/WebFrameTest.cpp |
@@ -32,6 +32,7 @@ |
#include "WebFrame.h" |
+#include <gmock/gmock.h> |
#include <gtest/gtest.h> |
#include "FrameTestHelpers.h" |
#include "SkBitmap.h" |
@@ -84,6 +85,7 @@ |
#include "public/platform/WebURLResponse.h" |
#include "wtf/dtoa/utils.h" |
#include "wtf/Forward.h" |
+#include <map> |
using namespace WebKit; |
using WebCore::Document; |
@@ -277,6 +279,266 @@ TEST_F(WebFrameTest, ChromePageNoJavascript) |
EXPECT_EQ(std::string::npos, content.find("Clobbered")); |
} |
+struct CSSCallbackWebFrameClient : public WebFrameClient { |
esprehn
2013/09/04 06:08:28
class
Jeffrey Yasskin
2013/09/12 22:09:59
Done.
|
+ CSSCallbackWebFrameClient() : m_updateCount(0) { } |
+ virtual void didMatchCSS(WebFrame* frame, const WebVector<WebString>& newlyMatchingSelectors, const WebVector<WebString>& stoppedMatchingSelectors) OVERRIDE |
esprehn
2013/09/04 06:08:28
I think we usually put long methods not in the cla
Jeffrey Yasskin
2013/09/12 22:09:59
Even in .cpp files? Ok.
|
+ { |
+ ++m_updateCount; |
+ std::set<std::string>& frameSelectors = m_matchedSelectors[frame]; |
+ for (size_t i = 0; i < newlyMatchingSelectors.size(); ++i) { |
+ std::string selector = newlyMatchingSelectors[i].utf8(); |
+ EXPECT_EQ(0U, frameSelectors.count(selector)) << selector; |
+ frameSelectors.insert(selector); |
+ } |
+ for (size_t i = 0; i < stoppedMatchingSelectors.size(); ++i) { |
+ std::string selector = stoppedMatchingSelectors[i].utf8(); |
+ EXPECT_EQ(1U, frameSelectors.count(selector)) << selector; |
+ frameSelectors.erase(selector); |
+ } |
+ } |
+ |
+ std::map<WebFrame*, std::set<std::string> > m_matchedSelectors; |
+ int m_updateCount; |
+}; |
+ |
+class WebFrameCSSCallbackTest : public testing::Test { |
+protected: |
+ WebFrameCSSCallbackTest() |
+ { |
+ m_webView = FrameTestHelpers::createWebViewAndLoad("about:blank", true, &m_client); |
+ m_frame = m_webView->mainFrame(); |
+ } |
+ |
+ ~WebFrameCSSCallbackTest() |
+ { |
+ EXPECT_EQ(1U, m_client.m_matchedSelectors.size()); |
+ m_webView->close(); |
+ } |
+ |
+ WebDocument doc() const |
+ { |
+ return m_frame->document(); |
+ } |
+ |
+ int updateCount() const |
+ { |
+ return m_client.m_updateCount; |
+ } |
+ |
+ const std::set<std::string>& matchedSelectors() |
+ { |
+ return m_client.m_matchedSelectors[m_frame]; |
+ } |
+ |
+ void loadHTML(const WebData& html) |
+ { |
+ m_frame->loadHTMLString(html, toKURL("about:blank")); |
+ runPendingTasks(); |
+ } |
+ |
+ void executeScript(const WebString& code) |
+ { |
+ m_frame->executeScript(WebScriptSource(code)); |
+ runPendingTasks(); |
+ } |
+ |
+ CSSCallbackWebFrameClient m_client; |
+ WebView* m_webView; |
+ WebFrame* m_frame; |
+}; |
+ |
+TEST_F(WebFrameCSSCallbackTest, AuthorStyleSheet) |
+{ |
+ loadHTML( |
+ "<style>" |
+ // This stylesheet checks that the internal property and value can't be |
+ // set by a stylesheet, only WebDocument::watchCSSSelectors(). |
+ "div.initial_on { -internal-callback: none; }" |
+ "div.initial_off { -internal-callback: -internal-presence; }" |
+ "</style>" |
+ "<div class=\"initial_on\"></div>" |
+ "<div class=\"initial_off\"></div>"); |
+ |
+ std::vector<WebString> selectors; |
+ selectors.push_back(WebString::fromUTF8("div.initial_on")); |
+ m_frame->document().watchCSSSelectors(WebVector<WebString>(selectors)); |
+ runPendingTasks(); |
+ EXPECT_EQ(1, updateCount()); |
+ EXPECT_THAT(matchedSelectors(), testing::ElementsAre("div.initial_on")); |
+ |
+ // Check that adding a watched selector calls back for already-present nodes. |
+ selectors.push_back(WebString::fromUTF8("div.initial_off")); |
+ doc().watchCSSSelectors(WebVector<WebString>(selectors)); |
+ runPendingTasks(); |
+ EXPECT_EQ(2, updateCount()); |
+ EXPECT_THAT(matchedSelectors(), testing::ElementsAre("div.initial_off", "div.initial_on")); |
+ |
+ // Check that we can turn off callbacks for certain selectors. |
+ doc().watchCSSSelectors(WebVector<WebString>()); |
+ runPendingTasks(); |
+ EXPECT_EQ(3, updateCount()); |
+ EXPECT_THAT(matchedSelectors(), testing::ElementsAre()); |
+} |
+ |
+TEST_F(WebFrameCSSCallbackTest, SharedRenderStyle) |
+{ |
+ // Check that adding an element calls back when it matches an existing rule. |
+ std::vector<WebString> selectors; |
+ selectors.push_back(WebString::fromUTF8("span")); |
+ doc().watchCSSSelectors(WebVector<WebString>(selectors)); |
+ |
+ executeScript( |
+ "i1 = document.createElement('span');" |
+ "i1.id = 'first_span';" |
+ "document.body.appendChild(i1)"); |
+ EXPECT_EQ(1, updateCount()); |
+ EXPECT_THAT(matchedSelectors(), testing::ElementsAre("span")); |
+ |
+ // Adding a second element that shares a RenderStyle shouldn't call back. |
+ // We use <span>s to avoid default style rules that can set |
+ // RenderStyle::unique(). |
+ executeScript( |
+ "i2 = document.createElement('span');" |
+ "i2.id = 'second_span';" |
+ "i1 = document.getElementById('first_span');" |
+ "i1.parentNode.insertBefore(i2, i1.nextSibling);"); |
+ EXPECT_EQ(1, updateCount()); |
+ EXPECT_THAT(matchedSelectors(), testing::ElementsAre("span")); |
+ |
+ // Removing the first element shouldn't call back. |
+ executeScript( |
+ "i1 = document.getElementById('first_span');" |
+ "i1.parentNode.removeChild(i1);"); |
+ EXPECT_EQ(1, updateCount()); |
+ EXPECT_THAT(matchedSelectors(), testing::ElementsAre("span")); |
+ |
+ // But removing the second element *should* call back. |
+ executeScript( |
+ "i2 = document.getElementById('second_span');" |
+ "i2.parentNode.removeChild(i2);"); |
+ EXPECT_EQ(2, updateCount()); |
+ EXPECT_THAT(matchedSelectors(), testing::ElementsAre()); |
+} |
+ |
+TEST_F(WebFrameCSSCallbackTest, CatchesAttributeChange) |
+{ |
+ loadHTML("<span></span>"); |
+ |
+ std::vector<WebString> selectors; |
+ selectors.push_back(WebString::fromUTF8("span[attr=\"value\"]")); |
+ doc().watchCSSSelectors(WebVector<WebString>(selectors)); |
+ runPendingTasks(); |
+ |
+ EXPECT_EQ(0, updateCount()); |
+ EXPECT_THAT(matchedSelectors(), testing::ElementsAre()); |
+ |
+ executeScript( |
+ "document.querySelector('span').setAttribute('attr', 'value');"); |
+ EXPECT_EQ(1, updateCount()); |
+ EXPECT_THAT(matchedSelectors(), testing::ElementsAre("span[attr=\"value\"]")); |
+} |
+ |
+TEST_F(WebFrameCSSCallbackTest, DisplayNone) |
+{ |
+ loadHTML("<div style='display:none'><span></span></div>"); |
+ |
+ std::vector<WebString> selectors; |
+ selectors.push_back(WebString::fromUTF8("span")); |
+ doc().watchCSSSelectors(WebVector<WebString>(selectors)); |
+ runPendingTasks(); |
+ |
+ EXPECT_EQ(0, updateCount()) << "Don't match elements in display:none trees."; |
+ |
+ executeScript( |
+ "d = document.querySelector('div');" |
+ "d.style.display = 'block';"); |
+ EXPECT_EQ(1, updateCount()) << "Match elements when they become displayed."; |
+ EXPECT_THAT(matchedSelectors(), testing::ElementsAre("span")); |
+ |
+ executeScript( |
+ "d = document.querySelector('div');" |
+ "d.style.display = 'none';"); |
+ EXPECT_EQ(2, updateCount()) << "Unmatch elements when they become undisplayed."; |
+ EXPECT_THAT(matchedSelectors(), testing::ElementsAre()); |
+ |
+ executeScript( |
+ "s = document.querySelector('span');" |
+ "s.style.display = 'none';"); |
+ EXPECT_EQ(2, updateCount()) << "No effect from no-display'ing a span that's already undisplayed."; |
+ |
+ executeScript( |
+ "d = document.querySelector('div');" |
+ "d.style.display = 'block';"); |
+ EXPECT_EQ(2, updateCount()) << "No effect from displaying a div whose span is display:none."; |
+ |
+ executeScript( |
+ "s = document.querySelector('span');" |
+ "s.style.display = 'inline';"); |
+ EXPECT_EQ(3, updateCount()) << "Now the span is visible and produces a callback."; |
+ EXPECT_THAT(matchedSelectors(), testing::ElementsAre("span")); |
+ |
+ executeScript( |
+ "s = document.querySelector('span');" |
+ "s.style.display = 'none';"); |
+ EXPECT_EQ(4, updateCount()) << "Undisplaying the span directly should produce another callback."; |
+ EXPECT_THAT(matchedSelectors(), testing::ElementsAre()); |
+} |
+ |
+TEST_F(WebFrameCSSCallbackTest, Reparenting) |
+{ |
+ loadHTML( |
+ "<div id='d1'><span></span></div>" |
+ "<div id='d2'></div>"); |
+ |
+ std::vector<WebString> selectors; |
+ selectors.push_back(WebString::fromUTF8("span")); |
+ doc().watchCSSSelectors(WebVector<WebString>(selectors)); |
+ runPendingTasks(); |
+ |
+ EXPECT_EQ(1, updateCount()); |
+ EXPECT_THAT(matchedSelectors(), testing::ElementsAre("span")); |
+ |
+ executeScript( |
+ "s = document.querySelector('span');" |
+ "d2 = document.getElementById('d2');" |
+ "d2.appendChild(s);"); |
+ EXPECT_EQ(1, updateCount()) << "Just moving an element that continues to match shouldn't send a spurious callback."; |
+ EXPECT_THAT(matchedSelectors(), testing::ElementsAre("span")); |
+} |
+ |
+TEST_F(WebFrameCSSCallbackTest, MultiSelector) |
+{ |
+ loadHTML("<span></span>"); |
+ |
+ // Check that selector lists match as the whole list, not as each element |
+ // independently. |
+ std::vector<WebString> selectors; |
+ selectors.push_back(WebString::fromUTF8("span")); |
+ selectors.push_back(WebString::fromUTF8("span,p")); |
+ doc().watchCSSSelectors(WebVector<WebString>(selectors)); |
+ |
+ runPendingTasks(); |
+ EXPECT_EQ(1, updateCount()); |
+ EXPECT_THAT(matchedSelectors(), testing::ElementsAre("span", "span, p")); |
+} |
+ |
+TEST_F(WebFrameCSSCallbackTest, InvalidSelector) |
+{ |
+ loadHTML("<p><span></span></p>"); |
+ |
+ // Build a list with one valid selector and one invalid. |
+ std::vector<WebString> selectors; |
+ selectors.push_back(WebString::fromUTF8("span")); |
+ selectors.push_back(WebString::fromUTF8("[")); // Invalid. |
+ selectors.push_back(WebString::fromUTF8("p span")); // Not compound. |
+ doc().watchCSSSelectors(WebVector<WebString>(selectors)); |
+ |
+ runPendingTasks(); |
+ EXPECT_EQ(1, updateCount()); |
+ EXPECT_THAT(matchedSelectors(), testing::ElementsAre("span")) |
+ << "An invalid selector shouldn't prevent other selectors from matching."; |
+} |
+ |
TEST_F(WebFrameTest, DispatchMessageEventWithOriginCheck) |
{ |
registerMockedHttpURLLoad("postmessage_test.html"); |