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_apitest.h"
8 #include "chrome/browser/extensions/extension_tab_util.h"
9 #include "chrome/browser/extensions/extension_test_message_listener.h"
10 #include "chrome/browser/extensions/test_extension_dir.h"
11 #include "chrome/browser/ui/browser_window.h"
12 #include "chrome/browser/ui/location_bar/location_bar.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 "testing/gmock/include/gmock/gmock.h"
18 namespace extensions
{
21 const char kDeclarativeContentManifest
[] =
23 " \"name\": \"Declarative Content apitest\",\n"
24 " \"version\": \"0.1\",\n"
25 " \"manifest_version\": 2,\n"
26 " \"description\": \n"
27 " \"end-to-end browser test for the declarative Content API\",\n"
28 " \"background\": {\n"
29 " \"scripts\": [\"background.js\"]\n"
31 " \"page_action\": {},\n"
32 " \"permissions\": [\n"
33 " \"declarativeContent\"\n"
37 const char kBackgroundHelpers
[] =
38 "var PageStateMatcher = chrome.declarativeContent.PageStateMatcher;\n"
39 "var ShowPageAction = chrome.declarativeContent.ShowPageAction;\n"
40 "var onPageChanged = chrome.declarativeContent.onPageChanged;\n"
41 "var Reply = window.domAutomationController.send.bind(\n"
42 " window.domAutomationController);\n"
44 "function setRules(rules, responseString) {\n"
45 " onPageChanged.removeRules(undefined, function() {\n"
46 " onPageChanged.addRules(rules, function() {\n"
47 " if (chrome.runtime.lastError) {\n"
48 " Reply(chrome.runtime.lastError.message);\n"
51 " Reply(responseString);\n"
56 class DeclarativeContentApiTest
: public ExtensionApiTest
{
58 DeclarativeContentApiTest()
59 // Set the channel to "trunk" since declarativeContent is restricted
61 : current_channel_(chrome::VersionInfo::CHANNEL_UNKNOWN
) {
63 virtual ~DeclarativeContentApiTest() {}
65 extensions::ScopedCurrentChannel current_channel_
;
66 TestExtensionDir ext_dir_
;
69 IN_PROC_BROWSER_TEST_F(DeclarativeContentApiTest
, Overview
) {
70 ext_dir_
.WriteManifest(kDeclarativeContentManifest
);
72 FILE_PATH_LITERAL("background.js"),
73 "var declarative = chrome.declarative;\n"
75 "var PageStateMatcher = chrome.declarativeContent.PageStateMatcher;\n"
76 "var ShowPageAction = chrome.declarativeContent.ShowPageAction;\n"
79 " conditions: [new PageStateMatcher({\n"
80 " pageUrl: {hostPrefix: \"test1\"}}),\n"
81 " new PageStateMatcher({\n"
82 " css: [\"input[type='password']\"]})],\n"
83 " actions: [new ShowPageAction()]\n"
86 "var testEvent = chrome.declarativeContent.onPageChanged;\n"
88 "testEvent.removeRules(undefined, function() {\n"
89 " testEvent.addRules([rule0], function() {\n"
90 " chrome.test.sendMessage(\"ready\", function(reply) {\n"
94 ExtensionTestMessageListener
ready("ready", true);
95 const Extension
* extension
= LoadExtension(ext_dir_
.unpacked_path());
96 ASSERT_TRUE(extension
);
97 const ExtensionAction
* page_action
=
98 ExtensionActionManager::Get(browser()->profile())->
99 GetPageAction(*extension
);
100 ASSERT_TRUE(page_action
);
102 ASSERT_TRUE(ready
.WaitUntilSatisfied());
103 content::WebContents
* const tab
=
104 browser()->tab_strip_model()->GetWebContentsAt(0);
105 const int tab_id
= ExtensionTabUtil::GetTabId(tab
);
107 NavigateInRenderer(tab
, GURL("http://test1/"));
109 // The declarative API should show the page action instantly, rather
110 // than waiting for the extension to run.
111 EXPECT_TRUE(page_action
->GetIsVisible(tab_id
));
113 // Make sure leaving a matching page unshows the page action.
114 NavigateInRenderer(tab
, GURL("http://not_checked/"));
115 EXPECT_FALSE(page_action
->GetIsVisible(tab_id
));
117 // Insert a password field to make sure that's noticed.
118 // Notice that we touch offsetTop to force a synchronous layout.
119 ASSERT_TRUE(content::ExecuteScript(
120 tab
, "document.body.innerHTML = '<input type=\"password\">';"
121 "document.body.offsetTop;"));
123 // Give the style match a chance to run and send back the matching-selector
124 // update. This takes one time through the Blink message loop to apply the
125 // style to the new element, and a second to dedupe updates.
126 // FIXME: Remove this after https://codereview.chromium.org/145663012/
127 ASSERT_TRUE(content::ExecuteScript(tab
, std::string()));
128 ASSERT_TRUE(content::ExecuteScript(tab
, std::string()));
130 EXPECT_TRUE(page_action
->GetIsVisible(tab_id
))
131 << "Adding a matching element should show the page action.";
133 // Remove it again to make sure that reverts the action.
134 // Notice that we touch offsetTop to force a synchronous layout.
135 ASSERT_TRUE(content::ExecuteScript(
136 tab
, "document.body.innerHTML = 'Hello world';"
137 "document.body.offsetTop;"));
139 // Give the style match a chance to run and send back the matching-selector
140 // update. This takes one time through the Blink message loop to apply the
141 // style to the new element, and a second to dedupe updates.
142 // FIXME: Remove this after https://codereview.chromium.org/145663012/
143 ASSERT_TRUE(content::ExecuteScript(tab
, std::string()));
144 ASSERT_TRUE(content::ExecuteScript(tab
, std::string()));
146 EXPECT_FALSE(page_action
->GetIsVisible(tab_id
))
147 << "Removing the matching element should hide the page action again.";
150 // http://crbug.com/304373
151 IN_PROC_BROWSER_TEST_F(DeclarativeContentApiTest
,
152 UninstallWhileActivePageAction
) {
153 ext_dir_
.WriteManifest(kDeclarativeContentManifest
);
154 ext_dir_
.WriteFile(FILE_PATH_LITERAL("background.js"), kBackgroundHelpers
);
155 const Extension
* extension
= LoadExtension(ext_dir_
.unpacked_path());
156 ASSERT_TRUE(extension
);
157 const std::string extension_id
= extension
->id();
158 const ExtensionAction
* page_action
= ExtensionActionManager::Get(
159 browser()->profile())->GetPageAction(*extension
);
160 ASSERT_TRUE(page_action
);
162 const std::string kTestRule
=
164 " conditions: [new PageStateMatcher({\n"
165 " pageUrl: {hostPrefix: \"test\"}})],\n"
166 " actions: [new ShowPageAction()]\n"
167 "}], 'test_rule');\n";
168 EXPECT_EQ("test_rule",
169 ExecuteScriptInBackgroundPage(extension_id
, kTestRule
));
171 content::WebContents
* const tab
=
172 browser()->tab_strip_model()->GetWebContentsAt(0);
173 const int tab_id
= ExtensionTabUtil::GetTabId(tab
);
175 NavigateInRenderer(tab
, GURL("http://test/"));
177 EXPECT_TRUE(page_action
->GetIsVisible(tab_id
));
178 EXPECT_TRUE(WaitForPageActionVisibilityChangeTo(1));
179 LocationBarTesting
* location_bar
=
180 browser()->window()->GetLocationBar()->GetLocationBarForTesting();
181 EXPECT_EQ(1, location_bar
->PageActionCount());
182 EXPECT_EQ(1, location_bar
->PageActionVisibleCount());
184 ReloadExtension(extension_id
); // Invalidates page_action and extension.
185 EXPECT_EQ("test_rule",
186 ExecuteScriptInBackgroundPage(extension_id
, kTestRule
));
187 // TODO(jyasskin): Apply new rules to existing tabs, without waiting for a
189 NavigateInRenderer(tab
, GURL("http://test/"));
190 EXPECT_TRUE(WaitForPageActionVisibilityChangeTo(1));
191 EXPECT_EQ(1, location_bar
->PageActionCount());
192 EXPECT_EQ(1, location_bar
->PageActionVisibleCount());
194 UnloadExtension(extension_id
);
195 NavigateInRenderer(tab
, GURL("http://test/"));
196 EXPECT_TRUE(WaitForPageActionVisibilityChangeTo(0));
197 EXPECT_EQ(0, location_bar
->PageActionCount());
198 EXPECT_EQ(0, location_bar
->PageActionVisibleCount());
201 // This tests against a renderer crash that was present during development.
202 IN_PROC_BROWSER_TEST_F(DeclarativeContentApiTest
,
203 DISABLED_AddExtensionMatchingExistingTabWithDeadFrames
) {
204 ext_dir_
.WriteManifest(kDeclarativeContentManifest
);
205 ext_dir_
.WriteFile(FILE_PATH_LITERAL("background.js"), kBackgroundHelpers
);
206 content::WebContents
* const tab
=
207 browser()->tab_strip_model()->GetWebContentsAt(0);
208 const int tab_id
= ExtensionTabUtil::GetTabId(tab
);
210 ASSERT_TRUE(content::ExecuteScript(
211 tab
, "document.body.innerHTML = '<iframe src=\"http://test2\">';"));
212 // Replace the iframe to destroy its WebFrame.
213 ASSERT_TRUE(content::ExecuteScript(
214 tab
, "document.body.innerHTML = '<span class=\"foo\">';"));
216 const Extension
* extension
= LoadExtension(ext_dir_
.unpacked_path());
217 ASSERT_TRUE(extension
);
218 const ExtensionAction
* page_action
= ExtensionActionManager::Get(
219 browser()->profile())->GetPageAction(*extension
);
220 ASSERT_TRUE(page_action
);
221 EXPECT_FALSE(page_action
->GetIsVisible(tab_id
));
224 ExecuteScriptInBackgroundPage(
227 " conditions: [new PageStateMatcher({\n"
228 " css: [\"span[class=foo]\"]})],\n"
229 " actions: [new ShowPageAction()]\n"
231 // Give the renderer a chance to apply the rules change and notify the
232 // browser. This takes one time through the Blink message loop to receive
233 // the rule change and apply the new stylesheet, and a second to dedupe the
235 ASSERT_TRUE(content::ExecuteScript(tab
, std::string()));
236 ASSERT_TRUE(content::ExecuteScript(tab
, std::string()));
238 EXPECT_FALSE(tab
->IsCrashed());
239 EXPECT_TRUE(page_action
->GetIsVisible(tab_id
))
240 << "Loading an extension when an open page matches its rules "
241 << "should show the page action.";
244 ExecuteScriptInBackgroundPage(
246 "onPageChanged.removeRules(undefined, function() {\n"
247 " window.domAutomationController.send('removed');\n"
249 EXPECT_FALSE(page_action
->GetIsVisible(tab_id
));
252 IN_PROC_BROWSER_TEST_F(DeclarativeContentApiTest
,
253 ShowPageActionWithoutPageAction
) {
254 std::string manifest_without_page_action
= kDeclarativeContentManifest
;
255 ReplaceSubstringsAfterOffset(
256 &manifest_without_page_action
, 0, "\"page_action\": {},", "");
257 ext_dir_
.WriteManifest(manifest_without_page_action
);
258 ext_dir_
.WriteFile(FILE_PATH_LITERAL("background.js"), kBackgroundHelpers
);
259 const Extension
* extension
= LoadExtension(ext_dir_
.unpacked_path());
260 ASSERT_TRUE(extension
);
262 EXPECT_THAT(ExecuteScriptInBackgroundPage(
265 " conditions: [new PageStateMatcher({\n"
266 " pageUrl: {hostPrefix: \"test\"}})],\n"
267 " actions: [new ShowPageAction()]\n"
268 "}], 'test_rule');\n"),
269 testing::HasSubstr("without a page action"));
271 content::WebContents
* const tab
=
272 browser()->tab_strip_model()->GetWebContentsAt(0);
273 NavigateInRenderer(tab
, GURL("http://test/"));
276 ExtensionActionManager::Get(browser()->profile())->
277 GetPageAction(*extension
));
279 browser()->window()->GetLocationBar()->GetLocationBarForTesting()->
283 IN_PROC_BROWSER_TEST_F(DeclarativeContentApiTest
,
284 CanonicalizesPageStateMatcherCss
) {
285 ext_dir_
.WriteManifest(kDeclarativeContentManifest
);
287 FILE_PATH_LITERAL("background.js"),
288 "var PageStateMatcher = chrome.declarativeContent.PageStateMatcher;\n"
289 "function Return(obj) {\n"
290 " window.domAutomationController.send('' + obj);\n"
292 const Extension
* extension
= LoadExtension(ext_dir_
.unpacked_path());
293 ASSERT_TRUE(extension
);
295 EXPECT_EQ("input[type=\"password\"]",
296 ExecuteScriptInBackgroundPage(
298 "var psm = new PageStateMatcher(\n"
299 " {css: [\"input[type='password']\"]});\n"
300 "Return(psm.css);"));
302 EXPECT_THAT(ExecuteScriptInBackgroundPage(
305 " new PageStateMatcher({css: 'Not-an-array'});\n"
306 " Return('Failed to throw');\n"
308 " Return(e.message);\n"
310 testing::ContainsRegex("css.*Expected 'array'"));
311 EXPECT_THAT(ExecuteScriptInBackgroundPage(
314 " new PageStateMatcher({css: [null]});\n" // Not a string.
315 " Return('Failed to throw');\n"
317 " Return(e.message);\n"
319 testing::ContainsRegex("css\\.0.*Expected 'string'"));
320 EXPECT_THAT(ExecuteScriptInBackgroundPage(
324 " new PageStateMatcher({css: [\"input''\"]});\n"
325 " Return('Failed to throw');\n"
327 " Return(e.message);\n"
329 testing::ContainsRegex("valid.*: input''$"));
330 EXPECT_THAT(ExecuteScriptInBackgroundPage(
333 // "Complex" selector:
334 " new PageStateMatcher({css: ['div input']});\n"
335 " Return('Failed to throw');\n"
337 " Return(e.message);\n"
339 testing::ContainsRegex("compound selector.*: div input$"));
343 } // namespace extensions