• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2014 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 "base/files/file_path.h"
6 #include "base/macros.h"
7 #include "base/strings/stringprintf.h"
8 #include "chrome/browser/extensions/extension_browsertest.h"
9 #include "chrome/browser/extensions/test_extension_dir.h"
10 #include "chrome/browser/ui/browser.h"
11 #include "chrome/browser/ui/tabs/tab_strip_model.h"
12 #include "chrome/test/base/ui_test_utils.h"
13 #include "content/public/test/browser_test_utils.h"
14 #include "extensions/test/extension_test_message_listener.h"
15 #include "net/test/embedded_test_server/embedded_test_server.h"
16 #include "testing/gtest/include/gtest/gtest.h"
17 
18 namespace extensions {
19 
20 namespace {
21 
22 // Manifest permissions injected into |kManifest|:
23 const char* kPermissions[] = {
24   "*://*/*",              // ALL
25   "http://127.0.0.1/*",   // PARTICULAR
26   "http://nowhere.com/*"  // NOWHERE
27 };
28 
29 // Script matchers for injected into |kBackgroundScriptSource|:
30 const char* kScriptMatchers[] = {
31   "{ pageUrl: { hostContains: '' } }",          // ALL
32   "{ pageUrl: { hostEquals: '127.0.0.1' } }",   // PARTICULAR
33   "{ pageUrl: { hostEquals: 'nowhere.com' } }"  // NOWHERE
34 };
35 
36 enum PermissionOrMatcherType {
37   ALL = 0,
38   PARTICULAR,
39   NOWHERE
40 };
41 
42 // JSON/JS sources:
43 const char kManifest[] =
44     "{\n"
45     "  \"name\": \"Test DeclarativeContentScript\",\n"
46     "  \"manifest_version\": 2,\n"
47     "  \"version\": \"1.0\",\n"
48     "  \"description\": \"Test declarative content script interface\",\n"
49     "  \"permissions\": [\"declarativeContent\", \"%s\"],\n"
50     "  \"background\": {\n"
51     "    \"scripts\": [\"background.js\"]\n"
52     "  }\n"
53     "}\n";
54 const char kBackgroundScriptSource[] =
55     "var declarativeContent = chrome.declarativeContent;\n"
56     "var PageStateMatcher = declarativeContent.PageStateMatcher;\n"
57     "var RequestContentScript = declarativeContent.RequestContentScript;\n"
58     "var onPageChanged = declarativeContent.onPageChanged;\n"
59     "onPageChanged.removeRules(undefined, function() {\n"
60     "  onPageChanged.addRules(\n"
61     "      [{\n"
62     "        conditions: [new PageStateMatcher(%s)],\n"
63     "        actions: [new RequestContentScript({js: ['script.js']}\n"
64     "        )]\n"
65     "      }],\n"
66     "      function(details) {\n"
67     "        if (!chrome.runtime.lastError)\n"
68     "          chrome.test.sendMessage('injection setup');\n"
69     "      }\n"
70     "  );\n"
71     "});\n";
72 const char kContentScriptSource[] =
73     "chrome.test.sendMessage('injection succeeded');\n";
74 
75 // Messages from scripts:
76 const char kInjectionSetup[] = "injection setup";
77 const char kInjectionSucceeded[] = "injection succeeded";
78 
79 // Runs all pending tasks in the renderer associated with |web_contents|.
80 // Returns true on success.
RunAllPendingInRenderer(content::WebContents * web_contents)81 bool RunAllPendingInRenderer(content::WebContents* web_contents) {
82   // TODO(devlin): If too many tests start to need this, move it somewhere
83   // common.
84   // This is slight hack to achieve a RunPendingInRenderer() method. Since IPCs
85   // are sent synchronously, anything started prior to this method will finish
86   // before this method returns (as content::ExecuteScript() is synchronous).
87   return content::ExecuteScript(web_contents, "1 == 1;");
88 }
89 
90 }  // namespace
91 
92 class RequestContentScriptAPITest : public ExtensionBrowserTest {
93  public:
94   RequestContentScriptAPITest();
~RequestContentScriptAPITest()95   virtual ~RequestContentScriptAPITest() {}
96 
97   // Performs script injection test on a common local URL using the given
98   // |manifest_permission| and |script_matcher|. Does not return until
99   // the renderer should have completed its task and any browser-side reactions
100   // have been cleared from the task queue.
101   testing::AssertionResult RunTest(PermissionOrMatcherType manifest_permission,
102                                    PermissionOrMatcherType script_matcher,
103                                    bool should_inject);
104 
105  private:
106   testing::AssertionResult CreateAndLoadExtension(
107       PermissionOrMatcherType manifest_permission,
108       PermissionOrMatcherType script_matcher);
109 
110   scoped_ptr<TestExtensionDir> test_extension_dir_;
111   const Extension* extension_;
112 };
113 
RequestContentScriptAPITest()114 RequestContentScriptAPITest::RequestContentScriptAPITest()
115     : extension_(NULL) {}
116 
RunTest(PermissionOrMatcherType manifest_permission,PermissionOrMatcherType script_matcher,bool should_inject)117 testing::AssertionResult RequestContentScriptAPITest::RunTest(
118     PermissionOrMatcherType manifest_permission,
119     PermissionOrMatcherType script_matcher,
120     bool should_inject) {
121   if (extension_)
122     UnloadExtension(extension_->id());
123   testing::AssertionResult result = CreateAndLoadExtension(manifest_permission,
124                                                            script_matcher);
125   if (!result)
126     return result;
127 
128   // Setup listener for actual injection of script.
129   ExtensionTestMessageListener injection_succeeded_listener(
130       kInjectionSucceeded,
131       false /* won't reply */);
132   injection_succeeded_listener.set_extension_id(extension_->id());
133 
134   ui_test_utils::NavigateToURL(
135       browser(),
136       embedded_test_server()->GetURL("/extensions/test_file.html"));
137 
138   content::WebContents* web_contents =
139       browser() ? browser()->tab_strip_model()->GetActiveWebContents() : NULL;
140   if (!web_contents)
141     return testing::AssertionFailure() << "No web contents.";
142 
143   // Give the extension plenty of time to inject.
144   if (!RunAllPendingInRenderer(web_contents))
145     return testing::AssertionFailure() << "Could not run pending in renderer.";
146 
147   // Make sure all running tasks are complete.
148   content::RunAllPendingInMessageLoop();
149 
150   if (injection_succeeded_listener.was_satisfied() != should_inject) {
151     return testing::AssertionFailure()
152         << (should_inject ?
153             "Expected injection, but got none." :
154             "Expected no injection, but got one.");
155   }
156 
157   return testing::AssertionSuccess();
158 }
159 
CreateAndLoadExtension(PermissionOrMatcherType manifest_permission,PermissionOrMatcherType script_matcher)160 testing::AssertionResult RequestContentScriptAPITest::CreateAndLoadExtension(
161     PermissionOrMatcherType manifest_permission,
162     PermissionOrMatcherType script_matcher) {
163   // Setup a listener to note when injection rules have been setup.
164   ExtensionTestMessageListener injection_setup_listener(
165       kInjectionSetup,
166       false /* won't reply */);
167 
168   std::string manifest = base::StringPrintf(kManifest,
169                                             kPermissions[manifest_permission]);
170   std::string background_src = base::StringPrintf(
171       kBackgroundScriptSource,
172       kScriptMatchers[script_matcher]);
173 
174   scoped_ptr<TestExtensionDir> dir(new TestExtensionDir);
175   dir->WriteManifest(manifest);
176   dir->WriteFile(FILE_PATH_LITERAL("background.js"), background_src);
177   dir->WriteFile(FILE_PATH_LITERAL("script.js"),
178                  kContentScriptSource);
179 
180   const Extension* extension = LoadExtension(dir->unpacked_path());
181   if (!extension)
182     return testing::AssertionFailure() << "Failed to load extension.";
183 
184   test_extension_dir_.reset(dir.release());
185   extension_ = extension;
186 
187   // Wait for rules to be setup before navigating to trigger script injection.
188   injection_setup_listener.WaitUntilSatisfied();
189 
190   return testing::AssertionSuccess();
191 }
192 
193 
194 // Try different permutations of "match all", "match particular domain (that is
195 // visited by test)", and "match nonsense domain (not visited by test)" for
196 // both manifest permissions and injection matcher conditions.
IN_PROC_BROWSER_TEST_F(RequestContentScriptAPITest,PermissionMatcherAgreementInjection)197 IN_PROC_BROWSER_TEST_F(RequestContentScriptAPITest,
198                        PermissionMatcherAgreementInjection) {
199   ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
200 
201   // Positive tests: permissions and matcher contain conditions that match URL
202   // visited during test.
203   EXPECT_TRUE(RunTest(ALL, ALL, true));
204   EXPECT_TRUE(RunTest(ALL, PARTICULAR, true));
205   EXPECT_TRUE(RunTest(PARTICULAR, ALL, true));
206   EXPECT_TRUE(RunTest(PARTICULAR, PARTICULAR, true));
207 
208   // Negative tests: permissions or matcher (or both) contain conditions that
209   // do not match URL visited during test.
210   EXPECT_TRUE(RunTest(NOWHERE, ALL, false));
211   EXPECT_TRUE(RunTest(NOWHERE, PARTICULAR, false));
212   EXPECT_TRUE(RunTest(NOWHERE, NOWHERE, false));
213   EXPECT_TRUE(RunTest(ALL, NOWHERE, false));
214   EXPECT_TRUE(RunTest(PARTICULAR, NOWHERE, false));
215 
216   // TODO(markdittmer): Add more tests:
217   // - Inject script with multiple files
218   // - Inject multiple scripts
219   // - Match on CSS selector conditions
220   // - Match all frames in document containing frames
221 }
222 
223 }  // namespace extensions
224