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