• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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_apitest.h"
6 
7 #include "base/strings/string_split.h"
8 #include "base/strings/string_util.h"
9 #include "base/strings/stringprintf.h"
10 #include "chrome/browser/chrome_notification_types.h"
11 #include "chrome/browser/extensions/extension_service.h"
12 #include "chrome/browser/extensions/unpacked_installer.h"
13 #include "chrome/browser/profiles/profile.h"
14 #include "chrome/browser/ui/browser.h"
15 #include "chrome/browser/ui/extensions/application_launch.h"
16 #include "chrome/test/base/ui_test_utils.h"
17 #include "content/public/browser/notification_registrar.h"
18 #include "content/public/browser/notification_service.h"
19 #include "extensions/browser/api/test/test_api.h"
20 #include "extensions/browser/extension_system.h"
21 #include "extensions/common/extension.h"
22 #include "extensions/common/extension_set.h"
23 #include "net/base/escape.h"
24 #include "net/base/filename_util.h"
25 #include "net/test/embedded_test_server/embedded_test_server.h"
26 #include "net/test/embedded_test_server/http_request.h"
27 #include "net/test/embedded_test_server/http_response.h"
28 #include "net/test/spawned_test_server/spawned_test_server.h"
29 
30 namespace {
31 
32 const char kTestCustomArg[] = "customArg";
33 const char kTestServerPort[] = "testServer.port";
34 const char kTestDataDirectory[] = "testDataDirectory";
35 const char kTestWebSocketPort[] = "testWebSocketPort";
36 const char kFtpServerPort[] = "ftpServer.port";
37 const char kSpawnedTestServerPort[] = "spawnedTestServer.port";
38 
HandleServerRedirectRequest(const net::test_server::HttpRequest & request)39 scoped_ptr<net::test_server::HttpResponse> HandleServerRedirectRequest(
40     const net::test_server::HttpRequest& request) {
41   if (!StartsWithASCII(request.relative_url, "/server-redirect?", true))
42     return scoped_ptr<net::test_server::HttpResponse>();
43 
44   size_t query_string_pos = request.relative_url.find('?');
45   std::string redirect_target =
46       request.relative_url.substr(query_string_pos + 1);
47 
48   scoped_ptr<net::test_server::BasicHttpResponse> http_response(
49       new net::test_server::BasicHttpResponse);
50   http_response->set_code(net::HTTP_MOVED_PERMANENTLY);
51   http_response->AddCustomHeader("Location", redirect_target);
52   return http_response.PassAs<net::test_server::HttpResponse>();
53 }
54 
HandleEchoHeaderRequest(const net::test_server::HttpRequest & request)55 scoped_ptr<net::test_server::HttpResponse> HandleEchoHeaderRequest(
56     const net::test_server::HttpRequest& request) {
57   if (!StartsWithASCII(request.relative_url, "/echoheader?", true))
58     return scoped_ptr<net::test_server::HttpResponse>();
59 
60   size_t query_string_pos = request.relative_url.find('?');
61   std::string header_name =
62       request.relative_url.substr(query_string_pos + 1);
63 
64   std::string header_value;
65   std::map<std::string, std::string>::const_iterator it = request.headers.find(
66       header_name);
67   if (it != request.headers.end())
68     header_value = it->second;
69 
70   scoped_ptr<net::test_server::BasicHttpResponse> http_response(
71       new net::test_server::BasicHttpResponse);
72   http_response->set_code(net::HTTP_OK);
73   http_response->set_content(header_value);
74   return http_response.PassAs<net::test_server::HttpResponse>();
75 }
76 
HandleSetCookieRequest(const net::test_server::HttpRequest & request)77 scoped_ptr<net::test_server::HttpResponse> HandleSetCookieRequest(
78     const net::test_server::HttpRequest& request) {
79   if (!StartsWithASCII(request.relative_url, "/set-cookie?", true))
80     return scoped_ptr<net::test_server::HttpResponse>();
81 
82   scoped_ptr<net::test_server::BasicHttpResponse> http_response(
83       new net::test_server::BasicHttpResponse);
84   http_response->set_code(net::HTTP_OK);
85 
86   size_t query_string_pos = request.relative_url.find('?');
87   std::string cookie_value =
88       request.relative_url.substr(query_string_pos + 1);
89 
90   std::vector<std::string> cookies;
91   base::SplitString(cookie_value, '&', &cookies);
92 
93   for (size_t i = 0; i < cookies.size(); i++)
94     http_response->AddCustomHeader("Set-Cookie", cookies[i]);
95 
96   return http_response.PassAs<net::test_server::HttpResponse>();
97 }
98 
HandleSetHeaderRequest(const net::test_server::HttpRequest & request)99 scoped_ptr<net::test_server::HttpResponse> HandleSetHeaderRequest(
100     const net::test_server::HttpRequest& request) {
101   if (!StartsWithASCII(request.relative_url, "/set-header?", true))
102     return scoped_ptr<net::test_server::HttpResponse>();
103 
104   size_t query_string_pos = request.relative_url.find('?');
105   std::string escaped_header =
106       request.relative_url.substr(query_string_pos + 1);
107 
108   std::string header =
109       net::UnescapeURLComponent(escaped_header,
110                                 net::UnescapeRule::NORMAL |
111                                 net::UnescapeRule::SPACES |
112                                 net::UnescapeRule::URL_SPECIAL_CHARS);
113 
114   size_t colon_pos = header.find(':');
115   if (colon_pos == std::string::npos)
116     return scoped_ptr<net::test_server::HttpResponse>();
117 
118   std::string header_name = header.substr(0, colon_pos);
119   // Skip space after colon.
120   std::string header_value = header.substr(colon_pos + 2);
121 
122   scoped_ptr<net::test_server::BasicHttpResponse> http_response(
123       new net::test_server::BasicHttpResponse);
124   http_response->set_code(net::HTTP_OK);
125   http_response->AddCustomHeader(header_name, header_value);
126   return http_response.PassAs<net::test_server::HttpResponse>();
127 }
128 
129 };  // namespace
130 
ExtensionApiTest()131 ExtensionApiTest::ExtensionApiTest() {
132   embedded_test_server()->RegisterRequestHandler(
133       base::Bind(&HandleServerRedirectRequest));
134   embedded_test_server()->RegisterRequestHandler(
135       base::Bind(&HandleEchoHeaderRequest));
136   embedded_test_server()->RegisterRequestHandler(
137       base::Bind(&HandleSetCookieRequest));
138   embedded_test_server()->RegisterRequestHandler(
139       base::Bind(&HandleSetHeaderRequest));
140 }
141 
~ExtensionApiTest()142 ExtensionApiTest::~ExtensionApiTest() {}
143 
ResultCatcher()144 ExtensionApiTest::ResultCatcher::ResultCatcher()
145     : profile_restriction_(NULL),
146       waiting_(false) {
147   registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_TEST_PASSED,
148                  content::NotificationService::AllSources());
149   registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_TEST_FAILED,
150                  content::NotificationService::AllSources());
151 }
152 
~ResultCatcher()153 ExtensionApiTest::ResultCatcher::~ResultCatcher() {
154 }
155 
GetNextResult()156 bool ExtensionApiTest::ResultCatcher::GetNextResult() {
157   // Depending on the tests, multiple results can come in from a single call
158   // to RunMessageLoop(), so we maintain a queue of results and just pull them
159   // off as the test calls this, going to the run loop only when the queue is
160   // empty.
161   if (results_.empty()) {
162     waiting_ = true;
163     content::RunMessageLoop();
164     waiting_ = false;
165   }
166 
167   if (!results_.empty()) {
168     bool ret = results_.front();
169     results_.pop_front();
170     message_ = messages_.front();
171     messages_.pop_front();
172     return ret;
173   }
174 
175   NOTREACHED();
176   return false;
177 }
178 
Observe(int type,const content::NotificationSource & source,const content::NotificationDetails & details)179 void ExtensionApiTest::ResultCatcher::Observe(
180     int type, const content::NotificationSource& source,
181     const content::NotificationDetails& details) {
182   if (profile_restriction_ &&
183       content::Source<Profile>(source).ptr() != profile_restriction_) {
184     return;
185   }
186 
187   switch (type) {
188     case chrome::NOTIFICATION_EXTENSION_TEST_PASSED:
189       VLOG(1) << "Got EXTENSION_TEST_PASSED notification.";
190       results_.push_back(true);
191       messages_.push_back(std::string());
192       if (waiting_)
193         base::MessageLoopForUI::current()->Quit();
194       break;
195 
196     case chrome::NOTIFICATION_EXTENSION_TEST_FAILED:
197       VLOG(1) << "Got EXTENSION_TEST_FAILED notification.";
198       results_.push_back(false);
199       messages_.push_back(*(content::Details<std::string>(details).ptr()));
200       if (waiting_)
201         base::MessageLoopForUI::current()->Quit();
202       break;
203 
204     default:
205       NOTREACHED();
206   }
207 }
208 
SetUpInProcessBrowserTestFixture()209 void ExtensionApiTest::SetUpInProcessBrowserTestFixture() {
210   DCHECK(!test_config_.get()) << "Previous test did not clear config state.";
211   test_config_.reset(new base::DictionaryValue());
212   test_config_->SetString(kTestDataDirectory,
213                           net::FilePathToFileURL(test_data_dir_).spec());
214   test_config_->SetInteger(kTestWebSocketPort, 0);
215   extensions::TestGetConfigFunction::set_test_config_state(
216       test_config_.get());
217 }
218 
TearDownInProcessBrowserTestFixture()219 void ExtensionApiTest::TearDownInProcessBrowserTestFixture() {
220   extensions::TestGetConfigFunction::set_test_config_state(NULL);
221   test_config_.reset(NULL);
222 }
223 
RunExtensionTest(const std::string & extension_name)224 bool ExtensionApiTest::RunExtensionTest(const std::string& extension_name) {
225   return RunExtensionTestImpl(
226       extension_name, std::string(), NULL, kFlagEnableFileAccess);
227 }
228 
RunExtensionTestIncognito(const std::string & extension_name)229 bool ExtensionApiTest::RunExtensionTestIncognito(
230     const std::string& extension_name) {
231   return RunExtensionTestImpl(extension_name,
232                               std::string(),
233                               NULL,
234                               kFlagEnableIncognito | kFlagEnableFileAccess);
235 }
236 
RunExtensionTestIgnoreManifestWarnings(const std::string & extension_name)237 bool ExtensionApiTest::RunExtensionTestIgnoreManifestWarnings(
238     const std::string& extension_name) {
239   return RunExtensionTestImpl(
240       extension_name, std::string(), NULL, kFlagIgnoreManifestWarnings);
241 }
242 
RunExtensionTestAllowOldManifestVersion(const std::string & extension_name)243 bool ExtensionApiTest::RunExtensionTestAllowOldManifestVersion(
244     const std::string& extension_name) {
245   return RunExtensionTestImpl(
246       extension_name,
247       std::string(),
248       NULL,
249       kFlagEnableFileAccess | kFlagAllowOldManifestVersions);
250 }
251 
RunComponentExtensionTest(const std::string & extension_name)252 bool ExtensionApiTest::RunComponentExtensionTest(
253     const std::string& extension_name) {
254   return RunExtensionTestImpl(extension_name,
255                               std::string(),
256                               NULL,
257                               kFlagEnableFileAccess | kFlagLoadAsComponent);
258 }
259 
RunExtensionTestNoFileAccess(const std::string & extension_name)260 bool ExtensionApiTest::RunExtensionTestNoFileAccess(
261     const std::string& extension_name) {
262   return RunExtensionTestImpl(extension_name, std::string(), NULL, kFlagNone);
263 }
264 
RunExtensionTestIncognitoNoFileAccess(const std::string & extension_name)265 bool ExtensionApiTest::RunExtensionTestIncognitoNoFileAccess(
266     const std::string& extension_name) {
267   return RunExtensionTestImpl(
268       extension_name, std::string(), NULL, kFlagEnableIncognito);
269 }
270 
RunExtensionSubtest(const std::string & extension_name,const std::string & page_url)271 bool ExtensionApiTest::RunExtensionSubtest(const std::string& extension_name,
272                                            const std::string& page_url) {
273   return RunExtensionSubtest(extension_name, page_url, kFlagEnableFileAccess);
274 }
275 
RunExtensionSubtest(const std::string & extension_name,const std::string & page_url,int flags)276 bool ExtensionApiTest::RunExtensionSubtest(const std::string& extension_name,
277                                            const std::string& page_url,
278                                            int flags) {
279   DCHECK(!page_url.empty()) << "Argument page_url is required.";
280   // See http://crbug.com/177163 for details.
281 #if defined(OS_WIN) && !defined(NDEBUG)
282   LOG(WARNING) << "Workaround for 177163, prematurely returning";
283   return true;
284 #else
285   return RunExtensionTestImpl(extension_name, page_url, NULL, flags);
286 #endif
287 }
288 
289 
RunPageTest(const std::string & page_url)290 bool ExtensionApiTest::RunPageTest(const std::string& page_url) {
291   return RunExtensionSubtest(std::string(), page_url);
292 }
293 
RunPageTest(const std::string & page_url,int flags)294 bool ExtensionApiTest::RunPageTest(const std::string& page_url,
295                                    int flags) {
296   return RunExtensionSubtest(std::string(), page_url, flags);
297 }
298 
RunPlatformAppTest(const std::string & extension_name)299 bool ExtensionApiTest::RunPlatformAppTest(const std::string& extension_name) {
300   return RunExtensionTestImpl(
301       extension_name, std::string(), NULL, kFlagLaunchPlatformApp);
302 }
303 
RunPlatformAppTestWithArg(const std::string & extension_name,const char * custom_arg)304 bool ExtensionApiTest::RunPlatformAppTestWithArg(
305     const std::string& extension_name, const char* custom_arg) {
306   return RunExtensionTestImpl(
307       extension_name, std::string(), custom_arg, kFlagLaunchPlatformApp);
308 }
309 
RunPlatformAppTestWithFlags(const std::string & extension_name,int flags)310 bool ExtensionApiTest::RunPlatformAppTestWithFlags(
311     const std::string& extension_name, int flags) {
312   return RunExtensionTestImpl(
313       extension_name, std::string(), NULL, flags | kFlagLaunchPlatformApp);
314 }
315 
316 // Load |extension_name| extension and/or |page_url| and wait for
317 // PASSED or FAILED notification.
RunExtensionTestImpl(const std::string & extension_name,const std::string & page_url,const char * custom_arg,int flags)318 bool ExtensionApiTest::RunExtensionTestImpl(const std::string& extension_name,
319                                             const std::string& page_url,
320                                             const char* custom_arg,
321                                             int flags) {
322   bool load_as_component = (flags & kFlagLoadAsComponent) != 0;
323   bool launch_platform_app = (flags & kFlagLaunchPlatformApp) != 0;
324   bool use_incognito = (flags & kFlagUseIncognito) != 0;
325 
326   if (custom_arg && custom_arg[0])
327     test_config_->SetString(kTestCustomArg, custom_arg);
328 
329   ResultCatcher catcher;
330   DCHECK(!extension_name.empty() || !page_url.empty()) <<
331       "extension_name and page_url cannot both be empty";
332 
333   const extensions::Extension* extension = NULL;
334   if (!extension_name.empty()) {
335     base::FilePath extension_path = test_data_dir_.AppendASCII(extension_name);
336     if (load_as_component) {
337       extension = LoadExtensionAsComponent(extension_path);
338     } else {
339       int browser_test_flags = ExtensionBrowserTest::kFlagNone;
340       if (flags & kFlagEnableIncognito)
341         browser_test_flags |= ExtensionBrowserTest::kFlagEnableIncognito;
342       if (flags & kFlagEnableFileAccess)
343         browser_test_flags |= ExtensionBrowserTest::kFlagEnableFileAccess;
344       if (flags & kFlagIgnoreManifestWarnings)
345         browser_test_flags |= ExtensionBrowserTest::kFlagIgnoreManifestWarnings;
346       if (flags & kFlagAllowOldManifestVersions) {
347         browser_test_flags |=
348             ExtensionBrowserTest::kFlagAllowOldManifestVersions;
349       }
350       extension = LoadExtensionWithFlags(extension_path, browser_test_flags);
351     }
352     if (!extension) {
353       message_ = "Failed to load extension.";
354       return false;
355     }
356   }
357 
358   // If there is a page_url to load, navigate it.
359   if (!page_url.empty()) {
360     GURL url = GURL(page_url);
361 
362     // Note: We use is_valid() here in the expectation that the provided url
363     // may lack a scheme & host and thus be a relative url within the loaded
364     // extension.
365     if (!url.is_valid()) {
366       DCHECK(!extension_name.empty()) <<
367           "Relative page_url given with no extension_name";
368 
369       url = extension->GetResourceURL(page_url);
370     }
371 
372     if (use_incognito)
373       ui_test_utils::OpenURLOffTheRecord(browser()->profile(), url);
374     else
375       ui_test_utils::NavigateToURL(browser(), url);
376   } else if (launch_platform_app) {
377     AppLaunchParams params(browser()->profile(),
378                            extension,
379                            extensions::LAUNCH_CONTAINER_NONE,
380                            NEW_WINDOW);
381     params.command_line = *CommandLine::ForCurrentProcess();
382     OpenApplication(params);
383   }
384 
385   if (!catcher.GetNextResult()) {
386     message_ = catcher.message();
387     return false;
388   }
389 
390   return true;
391 }
392 
393 // Test that exactly one extension is loaded, and return it.
GetSingleLoadedExtension()394 const extensions::Extension* ExtensionApiTest::GetSingleLoadedExtension() {
395   ExtensionService* service = extensions::ExtensionSystem::Get(
396       browser()->profile())->extension_service();
397 
398   const extensions::Extension* extension = NULL;
399   for (extensions::ExtensionSet::const_iterator it =
400            service->extensions()->begin();
401        it != service->extensions()->end(); ++it) {
402     // Ignore any component extensions. They are automatically loaded into all
403     // profiles and aren't the extension we're looking for here.
404     if ((*it)->location() == extensions::Manifest::COMPONENT)
405       continue;
406 
407     if (extension != NULL) {
408       // TODO(yoz): this is misleading; it counts component extensions.
409       message_ = base::StringPrintf(
410           "Expected only one extension to be present.  Found %u.",
411           static_cast<unsigned>(service->extensions()->size()));
412       return NULL;
413     }
414 
415     extension = it->get();
416   }
417 
418   if (!extension) {
419     message_ = "extension pointer is NULL.";
420     return NULL;
421   }
422   return extension;
423 }
424 
StartEmbeddedTestServer()425 bool ExtensionApiTest::StartEmbeddedTestServer() {
426   if (!embedded_test_server()->InitializeAndWaitUntilReady())
427     return false;
428 
429   // Build a dictionary of values that tests can use to build URLs that
430   // access the test server and local file system.  Tests can see these values
431   // using the extension API function chrome.test.getConfig().
432   test_config_->SetInteger(kTestServerPort,
433                            embedded_test_server()->port());
434 
435   return true;
436 }
437 
StartWebSocketServer(const base::FilePath & root_directory)438 bool ExtensionApiTest::StartWebSocketServer(
439     const base::FilePath& root_directory) {
440   websocket_server_.reset(new net::SpawnedTestServer(
441       net::SpawnedTestServer::TYPE_WS,
442       net::SpawnedTestServer::kLocalhost,
443       root_directory));
444 
445   if (!websocket_server_->Start())
446     return false;
447 
448   test_config_->SetInteger(kTestWebSocketPort,
449                            websocket_server_->host_port_pair().port());
450 
451   return true;
452 }
453 
StartFTPServer(const base::FilePath & root_directory)454 bool ExtensionApiTest::StartFTPServer(const base::FilePath& root_directory) {
455   ftp_server_.reset(new net::SpawnedTestServer(
456       net::SpawnedTestServer::TYPE_FTP,
457       net::SpawnedTestServer::kLocalhost,
458       root_directory));
459 
460   if (!ftp_server_->Start())
461     return false;
462 
463   test_config_->SetInteger(kFtpServerPort,
464                            ftp_server_->host_port_pair().port());
465 
466   return true;
467 }
468 
StartSpawnedTestServer()469 bool ExtensionApiTest::StartSpawnedTestServer() {
470   if (!test_server()->Start())
471     return false;
472 
473   // Build a dictionary of values that tests can use to build URLs that
474   // access the test server and local file system.  Tests can see these values
475   // using the extension API function chrome.test.getConfig().
476   test_config_->SetInteger(kSpawnedTestServerPort,
477                            test_server()->host_port_pair().port());
478 
479   return true;
480 }
481 
SetUpCommandLine(CommandLine * command_line)482 void ExtensionApiTest::SetUpCommandLine(CommandLine* command_line) {
483   ExtensionBrowserTest::SetUpCommandLine(command_line);
484   test_data_dir_ = test_data_dir_.AppendASCII("api_test");
485 }
486