Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / extensions / api / declarative_content / declarative_content_apitest.cc
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.
4
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"
18
19 namespace extensions {
20 namespace {
21
22 const char kDeclarativeContentManifest[] =
23     "{\n"
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"
31     "  },\n"
32     "  \"page_action\": {},\n"
33     "  \"permissions\": [\n"
34     "    \"declarativeContent\"\n"
35     "  ]\n"
36     "}\n";
37
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"
44     "\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"
50     "        return;\n"
51     "      }\n"
52     "      Reply(responseString);\n"
53     "    });\n"
54     "  });\n"
55     "};\n";
56
57 class DeclarativeContentApiTest : public ExtensionApiTest {
58  public:
59   DeclarativeContentApiTest()
60       // Set the channel to "trunk" since declarativeContent is restricted
61       // to trunk.
62       : current_channel_(chrome::VersionInfo::CHANNEL_UNKNOWN) {
63   }
64   virtual ~DeclarativeContentApiTest() {}
65
66   extensions::ScopedCurrentChannel current_channel_;
67   TestExtensionDir ext_dir_;
68 };
69
70 IN_PROC_BROWSER_TEST_F(DeclarativeContentApiTest, Overview) {
71   ext_dir_.WriteManifest(kDeclarativeContentManifest);
72   ext_dir_.WriteFile(
73       FILE_PATH_LITERAL("background.js"),
74       "var declarative = chrome.declarative;\n"
75       "\n"
76       "var PageStateMatcher = chrome.declarativeContent.PageStateMatcher;\n"
77       "var ShowPageAction = chrome.declarativeContent.ShowPageAction;\n"
78       "\n"
79       "var rule0 = {\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"
85       "}\n"
86       "\n"
87       "var testEvent = chrome.declarativeContent.onPageChanged;\n"
88       "\n"
89       "testEvent.removeRules(undefined, function() {\n"
90       "  testEvent.addRules([rule0], function() {\n"
91       "    chrome.test.sendMessage(\"ready\", function(reply) {\n"
92       "    })\n"
93       "  });\n"
94       "});\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);
102
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);
107
108   NavigateInRenderer(tab, GURL("http://test1/"));
109
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));
113
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));
117
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;"));
123
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()));
130
131   EXPECT_TRUE(page_action->GetIsVisible(tab_id))
132       << "Adding a matching element should show the page action.";
133
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;"));
139
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()));
146
147   EXPECT_FALSE(page_action->GetIsVisible(tab_id))
148       << "Removing the matching element should hide the page action again.";
149 }
150
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);
162
163   const std::string kTestRule =
164       "setRules([{\n"
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));
171
172   content::WebContents* const tab =
173       browser()->tab_strip_model()->GetWebContentsAt(0);
174   const int tab_id = ExtensionTabUtil::GetTabId(tab);
175
176   NavigateInRenderer(tab, GURL("http://test/"));
177
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));
182
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
187   // navigation.
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));
192
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));
198 }
199
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);
208
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\">';"));
214
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));
221
222   EXPECT_EQ("rule0",
223             ExecuteScriptInBackgroundPage(
224                 extension->id(),
225                 "setRules([{\n"
226                 "  conditions: [new PageStateMatcher({\n"
227                 "                   css: [\"span[class=foo]\"]})],\n"
228                 "  actions: [new ShowPageAction()]\n"
229                 "}], 'rule0');\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
233   // update.
234   ASSERT_TRUE(content::ExecuteScript(tab, std::string()));
235   ASSERT_TRUE(content::ExecuteScript(tab, std::string()));
236
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.";
241
242   EXPECT_EQ("removed",
243             ExecuteScriptInBackgroundPage(
244                 extension->id(),
245                 "onPageChanged.removeRules(undefined, function() {\n"
246                 "  window.domAutomationController.send('removed');\n"
247                 "});\n"));
248   EXPECT_FALSE(page_action->GetIsVisible(tab_id));
249 }
250
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);
260
261   EXPECT_THAT(ExecuteScriptInBackgroundPage(
262                   extension->id(),
263                   "setRules([{\n"
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"));
269
270   content::WebContents* const tab =
271       browser()->tab_strip_model()->GetWebContentsAt(0);
272   NavigateInRenderer(tab, GURL("http://test/"));
273
274   EXPECT_EQ(NULL,
275             ExtensionActionManager::Get(browser()->profile())->
276                 GetPageAction(*extension));
277   EXPECT_EQ(0u, extension_action_test_util::GetVisiblePageActionCount(tab));
278 }
279
280 IN_PROC_BROWSER_TEST_F(DeclarativeContentApiTest,
281                        CanonicalizesPageStateMatcherCss) {
282   ext_dir_.WriteManifest(kDeclarativeContentManifest);
283   ext_dir_.WriteFile(
284       FILE_PATH_LITERAL("background.js"),
285       "var PageStateMatcher = chrome.declarativeContent.PageStateMatcher;\n"
286       "function Return(obj) {\n"
287       "  window.domAutomationController.send('' + obj);\n"
288       "}\n");
289   const Extension* extension = LoadExtension(ext_dir_.unpacked_path());
290   ASSERT_TRUE(extension);
291
292   EXPECT_EQ("input[type=\"password\"]",
293             ExecuteScriptInBackgroundPage(
294                 extension->id(),
295                 "var psm = new PageStateMatcher(\n"
296                 "    {css: [\"input[type='password']\"]});\n"
297                 "Return(psm.css);"));
298
299   EXPECT_THAT(ExecuteScriptInBackgroundPage(
300                   extension->id(),
301                   "try {\n"
302                   "  new PageStateMatcher({css: 'Not-an-array'});\n"
303                   "  Return('Failed to throw');\n"
304                   "} catch (e) {\n"
305                   "  Return(e.message);\n"
306                   "}\n"),
307               testing::ContainsRegex("css.*Expected 'array'"));
308   EXPECT_THAT(ExecuteScriptInBackgroundPage(
309                   extension->id(),
310                   "try {\n"
311                   "  new PageStateMatcher({css: [null]});\n"  // Not a string.
312                   "  Return('Failed to throw');\n"
313                   "} catch (e) {\n"
314                   "  Return(e.message);\n"
315                   "}\n"),
316               testing::ContainsRegex("css\\.0.*Expected 'string'"));
317   EXPECT_THAT(ExecuteScriptInBackgroundPage(
318                   extension->id(),
319                   "try {\n"
320                   // Invalid CSS:
321                   "  new PageStateMatcher({css: [\"input''\"]});\n"
322                   "  Return('Failed to throw');\n"
323                   "} catch (e) {\n"
324                   "  Return(e.message);\n"
325                   "}\n"),
326               testing::ContainsRegex("valid.*: input''$"));
327   EXPECT_THAT(ExecuteScriptInBackgroundPage(
328                   extension->id(),
329                   "try {\n"
330                   // "Complex" selector:
331                   "  new PageStateMatcher({css: ['div input']});\n"
332                   "  Return('Failed to throw');\n"
333                   "} catch (e) {\n"
334                   "  Return(e.message);\n"
335                   "}\n"),
336               testing::ContainsRegex("compound selector.*: div input$"));
337 }
338
339 }  // namespace
340 }  // namespace extensions