1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/extensions/extension_action.h"
6 #include "chrome/browser/extensions/extension_action_manager.h"
7 #include "chrome/browser/extensions/extension_action_test_util.h"
8 #include "chrome/browser/extensions/extension_apitest.h"
9 #include "chrome/browser/extensions/extension_tab_util.h"
10 #include "chrome/browser/extensions/test_extension_dir.h"
11 #include "chrome/browser/ui/browser.h"
12 #include "chrome/browser/ui/browser_window.h"
13 #include "chrome/browser/ui/tabs/tab_strip_model.h"
14 #include "chrome/common/extensions/features/feature_channel.h"
15 #include "content/public/test/browser_test_utils.h"
16 #include "extensions/test/extension_test_message_listener.h"
17 #include "testing/gmock/include/gmock/gmock.h"
19 namespace extensions
{
22 const char kDeclarativeContentManifest
[] =
24 " \"name\": \"Declarative Content apitest\",\n"
25 " \"version\": \"0.1\",\n"
26 " \"manifest_version\": 2,\n"
27 " \"description\": \n"
28 " \"end-to-end browser test for the declarative Content API\",\n"
29 " \"background\": {\n"
30 " \"scripts\": [\"background.js\"]\n"
32 " \"page_action\": {},\n"
33 " \"permissions\": [\n"
34 " \"declarativeContent\"\n"
38 const char kBackgroundHelpers
[] =
39 "var PageStateMatcher = chrome.declarativeContent.PageStateMatcher;\n"
40 "var ShowPageAction = chrome.declarativeContent.ShowPageAction;\n"
41 "var onPageChanged = chrome.declarativeContent.onPageChanged;\n"
42 "var Reply = window.domAutomationController.send.bind(\n"
43 " window.domAutomationController);\n"
45 "function setRules(rules, responseString) {\n"
46 " onPageChanged.removeRules(undefined, function() {\n"
47 " onPageChanged.addRules(rules, function() {\n"
48 " if (chrome.runtime.lastError) {\n"
49 " Reply(chrome.runtime.lastError.message);\n"
52 " Reply(responseString);\n"
57 class DeclarativeContentApiTest
: public ExtensionApiTest
{
59 DeclarativeContentApiTest()
60 // Set the channel to "trunk" since declarativeContent is restricted
62 : current_channel_(chrome::VersionInfo::CHANNEL_UNKNOWN
) {
64 ~DeclarativeContentApiTest() override
{}
66 extensions::ScopedCurrentChannel current_channel_
;
67 TestExtensionDir ext_dir_
;
70 IN_PROC_BROWSER_TEST_F(DeclarativeContentApiTest
, Overview
) {
71 ext_dir_
.WriteManifest(kDeclarativeContentManifest
);
73 FILE_PATH_LITERAL("background.js"),
74 "var declarative = chrome.declarative;\n"
76 "var PageStateMatcher = chrome.declarativeContent.PageStateMatcher;\n"
77 "var ShowPageAction = chrome.declarativeContent.ShowPageAction;\n"
80 " conditions: [new PageStateMatcher({\n"
81 " pageUrl: {hostPrefix: \"test1\"}}),\n"
82 " new PageStateMatcher({\n"
83 " css: [\"input[type='password']\"]})],\n"
84 " actions: [new ShowPageAction()]\n"
87 "var testEvent = chrome.declarativeContent.onPageChanged;\n"
89 "testEvent.removeRules(undefined, function() {\n"
90 " testEvent.addRules([rule0], function() {\n"
91 " chrome.test.sendMessage(\"ready\", function(reply) {\n"
95 ExtensionTestMessageListener
ready("ready", true);
96 const Extension
* extension
= LoadExtension(ext_dir_
.unpacked_path());
97 ASSERT_TRUE(extension
);
98 const ExtensionAction
* page_action
=
99 ExtensionActionManager::Get(browser()->profile())->
100 GetPageAction(*extension
);
101 ASSERT_TRUE(page_action
);
103 ASSERT_TRUE(ready
.WaitUntilSatisfied());
104 content::WebContents
* const tab
=
105 browser()->tab_strip_model()->GetWebContentsAt(0);
106 const int tab_id
= ExtensionTabUtil::GetTabId(tab
);
108 NavigateInRenderer(tab
, GURL("http://test1/"));
110 // The declarative API should show the page action instantly, rather
111 // than waiting for the extension to run.
112 EXPECT_TRUE(page_action
->GetIsVisible(tab_id
));
114 // Make sure leaving a matching page unshows the page action.
115 NavigateInRenderer(tab
, GURL("http://not_checked/"));
116 EXPECT_FALSE(page_action
->GetIsVisible(tab_id
));
118 // Insert a password field to make sure that's noticed.
119 // Notice that we touch offsetTop to force a synchronous layout.
120 ASSERT_TRUE(content::ExecuteScript(
121 tab
, "document.body.innerHTML = '<input type=\"password\">';"
122 "document.body.offsetTop;"));
124 // Give the style match a chance to run and send back the matching-selector
125 // update. This takes one time through the Blink message loop to apply the
126 // style to the new element, and a second to dedupe updates.
127 // FIXME: Remove this after https://codereview.chromium.org/145663012/
128 ASSERT_TRUE(content::ExecuteScript(tab
, std::string()));
129 ASSERT_TRUE(content::ExecuteScript(tab
, std::string()));
131 EXPECT_TRUE(page_action
->GetIsVisible(tab_id
))
132 << "Adding a matching element should show the page action.";
134 // Remove it again to make sure that reverts the action.
135 // Notice that we touch offsetTop to force a synchronous layout.
136 ASSERT_TRUE(content::ExecuteScript(
137 tab
, "document.body.innerHTML = 'Hello world';"
138 "document.body.offsetTop;"));
140 // Give the style match a chance to run and send back the matching-selector
141 // update. This takes one time through the Blink message loop to apply the
142 // style to the new element, and a second to dedupe updates.
143 // FIXME: Remove this after https://codereview.chromium.org/145663012/
144 ASSERT_TRUE(content::ExecuteScript(tab
, std::string()));
145 ASSERT_TRUE(content::ExecuteScript(tab
, std::string()));
147 EXPECT_FALSE(page_action
->GetIsVisible(tab_id
))
148 << "Removing the matching element should hide the page action again.";
151 // http://crbug.com/304373
152 IN_PROC_BROWSER_TEST_F(DeclarativeContentApiTest
,
153 UninstallWhileActivePageAction
) {
154 ext_dir_
.WriteManifest(kDeclarativeContentManifest
);
155 ext_dir_
.WriteFile(FILE_PATH_LITERAL("background.js"), kBackgroundHelpers
);
156 const Extension
* extension
= LoadExtension(ext_dir_
.unpacked_path());
157 ASSERT_TRUE(extension
);
158 const std::string extension_id
= extension
->id();
159 const ExtensionAction
* page_action
= ExtensionActionManager::Get(
160 browser()->profile())->GetPageAction(*extension
);
161 ASSERT_TRUE(page_action
);
163 const std::string kTestRule
=
165 " conditions: [new PageStateMatcher({\n"
166 " pageUrl: {hostPrefix: \"test\"}})],\n"
167 " actions: [new ShowPageAction()]\n"
168 "}], 'test_rule');\n";
169 EXPECT_EQ("test_rule",
170 ExecuteScriptInBackgroundPage(extension_id
, kTestRule
));
172 content::WebContents
* const tab
=
173 browser()->tab_strip_model()->GetWebContentsAt(0);
174 const int tab_id
= ExtensionTabUtil::GetTabId(tab
);
176 NavigateInRenderer(tab
, GURL("http://test/"));
178 EXPECT_TRUE(page_action
->GetIsVisible(tab_id
));
179 EXPECT_TRUE(WaitForPageActionVisibilityChangeTo(1));
180 EXPECT_EQ(1u, extension_action_test_util::GetVisiblePageActionCount(tab
));
181 EXPECT_EQ(1u, extension_action_test_util::GetTotalPageActionCount(tab
));
183 ReloadExtension(extension_id
); // Invalidates page_action and extension.
184 EXPECT_EQ("test_rule",
185 ExecuteScriptInBackgroundPage(extension_id
, kTestRule
));
186 // TODO(jyasskin): Apply new rules to existing tabs, without waiting for a
188 NavigateInRenderer(tab
, GURL("http://test/"));
189 EXPECT_TRUE(WaitForPageActionVisibilityChangeTo(1));
190 EXPECT_EQ(1u, extension_action_test_util::GetVisiblePageActionCount(tab
));
191 EXPECT_EQ(1u, extension_action_test_util::GetTotalPageActionCount(tab
));
193 UnloadExtension(extension_id
);
194 NavigateInRenderer(tab
, GURL("http://test/"));
195 EXPECT_TRUE(WaitForPageActionVisibilityChangeTo(0));
196 EXPECT_EQ(0u, extension_action_test_util::GetVisiblePageActionCount(tab
));
197 EXPECT_EQ(0u, extension_action_test_util::GetTotalPageActionCount(tab
));
200 // This tests against a renderer crash that was present during development.
201 IN_PROC_BROWSER_TEST_F(DeclarativeContentApiTest
,
202 DISABLED_AddExtensionMatchingExistingTabWithDeadFrames
) {
203 ext_dir_
.WriteManifest(kDeclarativeContentManifest
);
204 ext_dir_
.WriteFile(FILE_PATH_LITERAL("background.js"), kBackgroundHelpers
);
205 content::WebContents
* const tab
=
206 browser()->tab_strip_model()->GetWebContentsAt(0);
207 const int tab_id
= ExtensionTabUtil::GetTabId(tab
);
209 ASSERT_TRUE(content::ExecuteScript(
210 tab
, "document.body.innerHTML = '<iframe src=\"http://test2\">';"));
211 // Replace the iframe to destroy its WebFrame.
212 ASSERT_TRUE(content::ExecuteScript(
213 tab
, "document.body.innerHTML = '<span class=\"foo\">';"));
215 const Extension
* extension
= LoadExtension(ext_dir_
.unpacked_path());
216 ASSERT_TRUE(extension
);
217 const ExtensionAction
* page_action
= ExtensionActionManager::Get(
218 browser()->profile())->GetPageAction(*extension
);
219 ASSERT_TRUE(page_action
);
220 EXPECT_FALSE(page_action
->GetIsVisible(tab_id
));
223 ExecuteScriptInBackgroundPage(
226 " conditions: [new PageStateMatcher({\n"
227 " css: [\"span[class=foo]\"]})],\n"
228 " actions: [new ShowPageAction()]\n"
230 // Give the renderer a chance to apply the rules change and notify the
231 // browser. This takes one time through the Blink message loop to receive
232 // the rule change and apply the new stylesheet, and a second to dedupe the
234 ASSERT_TRUE(content::ExecuteScript(tab
, std::string()));
235 ASSERT_TRUE(content::ExecuteScript(tab
, std::string()));
237 EXPECT_FALSE(tab
->IsCrashed());
238 EXPECT_TRUE(page_action
->GetIsVisible(tab_id
))
239 << "Loading an extension when an open page matches its rules "
240 << "should show the page action.";
243 ExecuteScriptInBackgroundPage(
245 "onPageChanged.removeRules(undefined, function() {\n"
246 " window.domAutomationController.send('removed');\n"
248 EXPECT_FALSE(page_action
->GetIsVisible(tab_id
));
251 IN_PROC_BROWSER_TEST_F(DeclarativeContentApiTest
,
252 ShowPageActionWithoutPageAction
) {
253 std::string manifest_without_page_action
= kDeclarativeContentManifest
;
254 ReplaceSubstringsAfterOffset(
255 &manifest_without_page_action
, 0, "\"page_action\": {},", "");
256 ext_dir_
.WriteManifest(manifest_without_page_action
);
257 ext_dir_
.WriteFile(FILE_PATH_LITERAL("background.js"), kBackgroundHelpers
);
258 const Extension
* extension
= LoadExtension(ext_dir_
.unpacked_path());
259 ASSERT_TRUE(extension
);
261 EXPECT_THAT(ExecuteScriptInBackgroundPage(
264 " conditions: [new PageStateMatcher({\n"
265 " pageUrl: {hostPrefix: \"test\"}})],\n"
266 " actions: [new ShowPageAction()]\n"
267 "}], 'test_rule');\n"),
268 testing::HasSubstr("without a page action"));
270 content::WebContents
* const tab
=
271 browser()->tab_strip_model()->GetWebContentsAt(0);
272 NavigateInRenderer(tab
, GURL("http://test/"));
275 ExtensionActionManager::Get(browser()->profile())->
276 GetPageAction(*extension
));
277 EXPECT_EQ(0u, extension_action_test_util::GetVisiblePageActionCount(tab
));
280 IN_PROC_BROWSER_TEST_F(DeclarativeContentApiTest
,
281 CanonicalizesPageStateMatcherCss
) {
282 ext_dir_
.WriteManifest(kDeclarativeContentManifest
);
284 FILE_PATH_LITERAL("background.js"),
285 "var PageStateMatcher = chrome.declarativeContent.PageStateMatcher;\n"
286 "function Return(obj) {\n"
287 " window.domAutomationController.send('' + obj);\n"
289 const Extension
* extension
= LoadExtension(ext_dir_
.unpacked_path());
290 ASSERT_TRUE(extension
);
292 EXPECT_EQ("input[type=\"password\"]",
293 ExecuteScriptInBackgroundPage(
295 "var psm = new PageStateMatcher(\n"
296 " {css: [\"input[type='password']\"]});\n"
297 "Return(psm.css);"));
299 EXPECT_THAT(ExecuteScriptInBackgroundPage(
302 " new PageStateMatcher({css: 'Not-an-array'});\n"
303 " Return('Failed to throw');\n"
305 " Return(e.message);\n"
307 testing::ContainsRegex("css.*Expected 'array'"));
308 EXPECT_THAT(ExecuteScriptInBackgroundPage(
311 " new PageStateMatcher({css: [null]});\n" // Not a string.
312 " Return('Failed to throw');\n"
314 " Return(e.message);\n"
316 testing::ContainsRegex("css\\.0.*Expected 'string'"));
317 EXPECT_THAT(ExecuteScriptInBackgroundPage(
321 " new PageStateMatcher({css: [\"input''\"]});\n"
322 " Return('Failed to throw');\n"
324 " Return(e.message);\n"
326 testing::ContainsRegex("valid.*: input''$"));
327 EXPECT_THAT(ExecuteScriptInBackgroundPage(
330 // "Complex" selector:
331 " new PageStateMatcher({css: ['div input']});\n"
332 " Return('Failed to throw');\n"
334 " Return(e.message);\n"
336 testing::ContainsRegex("compound selector.*: div input$"));
340 } // namespace extensions