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