Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include <list> | 5 #include <list> |
| 6 #include <set> | 6 #include <set> |
| 7 #include <string> | 7 #include <string> |
| 8 | 8 |
| 9 #include "base/callback_helpers.h" | |
| 9 #include "base/macros.h" | 10 #include "base/macros.h" |
| 11 #include "base/memory/ptr_util.h" | |
| 12 #include "base/run_loop.h" | |
| 10 #include "base/scoped_observer.h" | 13 #include "base/scoped_observer.h" |
| 14 #include "base/strings/string_split.h" | |
| 15 #include "base/threading/thread_task_runner_handle.h" | |
| 11 #include "chrome/browser/extensions/extension_browsertest.h" | 16 #include "chrome/browser/extensions/extension_browsertest.h" |
| 17 #include "chrome/browser/extensions/extension_service.h" | |
| 12 #include "chrome/common/chrome_switches.h" | 18 #include "chrome/common/chrome_switches.h" |
| 13 #include "content/public/test/test_utils.h" | 19 #include "content/public/test/test_utils.h" |
| 14 #include "extensions/browser/content_verifier.h" | 20 #include "extensions/browser/content_verifier.h" |
| 15 #include "extensions/browser/content_verify_job.h" | 21 #include "extensions/browser/content_verify_job.h" |
| 22 #include "extensions/browser/crx_file_info.h" | |
| 16 #include "extensions/browser/extension_prefs.h" | 23 #include "extensions/browser/extension_prefs.h" |
| 17 #include "extensions/browser/extension_registry.h" | 24 #include "extensions/browser/extension_registry.h" |
| 18 #include "extensions/browser/extension_registry_observer.h" | 25 #include "extensions/browser/extension_registry_observer.h" |
| 26 #include "extensions/browser/external_install_info.h" | |
| 27 #include "extensions/browser/external_provider_interface.h" | |
| 28 #include "extensions/browser/management_policy.h" | |
| 29 #include "extensions/browser/updater/extension_downloader.h" | |
| 30 #include "extensions/browser/updater/extension_downloader_test_delegate.h" | |
| 31 #include "extensions/browser/updater/manifest_fetch_data.h" | |
| 32 #include "extensions/common/extension_urls.h" | |
| 19 | 33 |
| 20 namespace extensions { | 34 namespace extensions { |
| 21 | 35 |
| 22 namespace { | 36 namespace { |
| 23 | 37 |
| 24 // Helper for observing extension unloads. | 38 // Helper for observing extension registry events. |
| 25 class UnloadObserver : public ExtensionRegistryObserver { | 39 class RegistryObserver : public ExtensionRegistryObserver { |
| 26 public: | 40 public: |
| 27 explicit UnloadObserver(ExtensionRegistry* registry) : observer_(this) { | 41 explicit RegistryObserver(ExtensionRegistry* registry) : observer_(this) { |
| 28 observer_.Add(registry); | 42 observer_.Add(registry); |
| 29 } | 43 } |
| 30 ~UnloadObserver() override {} | 44 ~RegistryObserver() override {} |
| 31 | 45 |
| 32 void WaitForUnload(const ExtensionId& id) { | 46 // Waits until we've seen an unload for extension with |id|, returning true |
| 33 if (base::ContainsKey(observed_, id)) | 47 // if we saw one or false otherwise (typically because of test timeout). |
| 34 return; | 48 bool WaitForUnload(const ExtensionId& id) { |
| 49 if (base::ContainsKey(unloaded_, id)) | |
| 50 return true; | |
| 35 | 51 |
| 36 ASSERT_TRUE(loop_runner_.get() == NULL); | 52 base::RunLoop run_loop; |
| 37 awaited_id_ = id; | 53 awaited_unload_id_ = id; |
| 38 loop_runner_ = new content::MessageLoopRunner(); | 54 quit_closure_ = run_loop.QuitClosure(); |
| 39 loop_runner_->Run(); | 55 run_loop.Run(); |
| 56 return base::ContainsKey(unloaded_, id); | |
| 40 } | 57 } |
| 41 | 58 |
| 59 // Same as WaitForUnload, but for an install. | |
| 60 bool WaitForInstall(const ExtensionId& id) { | |
| 61 if (base::ContainsKey(installed_, id)) | |
| 62 return true; | |
| 63 | |
| 64 base::RunLoop run_loop; | |
| 65 awaited_install_id_ = id; | |
| 66 quit_closure_ = run_loop.QuitClosure(); | |
| 67 run_loop.Run(); | |
| 68 return base::ContainsKey(installed_, id); | |
| 69 } | |
| 70 | |
| 71 // ExtensionRegistryObserver | |
| 42 void OnExtensionUnloaded(content::BrowserContext* browser_context, | 72 void OnExtensionUnloaded(content::BrowserContext* browser_context, |
| 43 const Extension* extension, | 73 const Extension* extension, |
| 44 UnloadedExtensionInfo::Reason reason) override { | 74 UnloadedExtensionInfo::Reason reason) override { |
| 45 observed_.insert(extension->id()); | 75 unloaded_.insert(extension->id()); |
| 46 if (awaited_id_ == extension->id()) | 76 if (awaited_unload_id_ == extension->id()) { |
| 47 loop_runner_->Quit(); | 77 awaited_unload_id_.clear(); |
| 78 base::ResetAndReturn(&quit_closure_).Run(); | |
| 79 } | |
| 80 } | |
| 81 | |
| 82 void OnExtensionInstalled(content::BrowserContext* browser_context, | |
| 83 const Extension* extension, | |
| 84 bool is_update) override { | |
| 85 installed_.insert(extension->id()); | |
| 86 if (awaited_install_id_ == extension->id()) { | |
| 87 awaited_install_id_.clear(); | |
| 88 base::ResetAndReturn(&quit_closure_).Run(); | |
| 89 } | |
| 48 } | 90 } |
| 49 | 91 |
| 50 private: | 92 private: |
| 51 ExtensionId awaited_id_; | 93 // The id we're waiting for a load/install of respectively. |
| 52 std::set<ExtensionId> observed_; | 94 ExtensionId awaited_unload_id_; |
| 53 scoped_refptr<content::MessageLoopRunner> loop_runner_; | 95 ExtensionId awaited_install_id_; |
| 54 ScopedObserver<ExtensionRegistry, UnloadObserver> observer_; | 96 |
| 97 // The quit closure for stopping a running RunLoop, if we're waiting. | |
| 98 base::Closure quit_closure_; | |
| 99 | |
| 100 // The extension id's we've seen unloaded and installed, respectively. | |
| 101 std::set<ExtensionId> unloaded_; | |
| 102 std::set<ExtensionId> installed_; | |
| 103 | |
| 104 ScopedObserver<ExtensionRegistry, RegistryObserver> observer_; | |
| 105 | |
| 106 DISALLOW_COPY_AND_ASSIGN(RegistryObserver); | |
| 55 }; | 107 }; |
| 56 | 108 |
| 57 // Helper for forcing ContentVerifyJob's to return an error. | 109 // Helper for forcing ContentVerifyJob's to return an error. |
| 58 class JobDelegate : public ContentVerifyJob::TestDelegate { | 110 class JobDelegate : public ContentVerifyJob::TestDelegate { |
| 59 public: | 111 public: |
| 60 JobDelegate() | 112 JobDelegate() |
| 61 : fail_next_read_(false), | 113 : fail_next_read_(false), |
| 62 fail_next_done_(false), | 114 fail_next_done_(false), |
| 63 bytes_read_failed_(0), | 115 bytes_read_failed_(0), |
| 64 done_reading_failed_(0) {} | 116 done_reading_failed_(0) {} |
| (...skipping 178 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 243 loop_runner_ = nullptr; | 295 loop_runner_ = nullptr; |
| 244 } | 296 } |
| 245 | 297 |
| 246 void VerifierObserver::OnFetchComplete(const std::string& extension_id, | 298 void VerifierObserver::OnFetchComplete(const std::string& extension_id, |
| 247 bool success) { | 299 bool success) { |
| 248 completed_fetches_.insert(extension_id); | 300 completed_fetches_.insert(extension_id); |
| 249 if (extension_id == id_to_wait_for_) | 301 if (extension_id == id_to_wait_for_) |
| 250 loop_runner_->Quit(); | 302 loop_runner_->Quit(); |
| 251 } | 303 } |
| 252 | 304 |
| 305 // This lets us intercept requests for update checks of extensions, and | |
| 306 // substitute a local file as a simulated response. | |
| 307 class DownloaderTestDelegate : public ExtensionDownloaderTestDelegate { | |
| 308 public: | |
| 309 DownloaderTestDelegate() {} | |
| 310 | |
| 311 // This makes it so that update check requests for |extension_id| will return | |
| 312 // a downloaded file of |crx_path| that is claimed to have version | |
| 313 // |version_string|. | |
| 314 void AddResponse(const std::string& extension_id, | |
|
lazyboy
2016/09/02 19:21:53
ExtensionId instead of std::string for extension i
asargent_no_longer_on_chrome
2016/09/09 03:30:43
Done.
| |
| 315 const std::string& version_string, | |
| 316 const base::FilePath& crx_path) { | |
| 317 responses_[extension_id] = std::make_pair(version_string, crx_path); | |
| 318 } | |
| 319 | |
| 320 const std::vector<std::unique_ptr<ManifestFetchData>>& requests() { | |
| 321 return requests_; | |
| 322 } | |
| 323 | |
| 324 // ExtensionDownloaderTestDelegate: | |
| 325 void StartUpdateCheck( | |
| 326 ExtensionDownloader* downloader, | |
| 327 ExtensionDownloaderDelegate* delegate, | |
| 328 std::unique_ptr<ManifestFetchData> fetch_data) override { | |
| 329 requests_.push_back(std::move(fetch_data)); | |
| 330 const ManifestFetchData* data = requests_.back().get(); | |
| 331 | |
| 332 for (const auto& id : data->extension_ids()) { | |
| 333 if (ContainsKey(responses_, id)) { | |
| 334 // We use PostTask here instead of calling OnExtensionDownloadFinished | |
| 335 // immeditately, because the calling code isn't expecting a synchronous | |
| 336 // response (in non-test situations there are at least 2 network | |
| 337 // requests needed before a file could be returned). | |
| 338 base::ThreadTaskRunnerHandle::Get()->PostTask( | |
| 339 FROM_HERE, | |
| 340 base::Bind( | |
| 341 &ExtensionDownloaderDelegate::OnExtensionDownloadFinished, | |
| 342 base::Unretained(delegate), | |
| 343 CRXFileInfo(id, responses_[id].second), | |
| 344 false /* pass_file_ownership */, GURL(), responses_[id].first, | |
| 345 ExtensionDownloaderDelegate::PingResult(), data->request_ids(), | |
| 346 ExtensionDownloaderDelegate::InstallCallback())); | |
| 347 } | |
| 348 } | |
| 349 } | |
| 350 | |
| 351 private: | |
| 352 // The requests we've received. | |
| 353 std::vector<std::unique_ptr<ManifestFetchData>> requests_; | |
| 354 | |
| 355 // The prepared responses - this maps an extension id to a (version string, | |
| 356 // crx file path) pair. | |
| 357 std::map<std::string, std::pair<std::string, base::FilePath>> responses_; | |
| 358 | |
| 359 DISALLOW_COPY_AND_ASSIGN(DownloaderTestDelegate); | |
| 360 }; | |
| 361 | |
| 362 // This lets us simulate the behavior of an enterprise policy that wants | |
| 363 // a given extension to be installed via the webstore. | |
| 364 class TestExternalProvider : public ExternalProviderInterface { | |
| 365 public: | |
| 366 TestExternalProvider(VisitorInterface* visitor, | |
| 367 const std::string& extension_id) | |
| 368 : visitor_(visitor), extension_id_(extension_id) {} | |
| 369 | |
| 370 ~TestExternalProvider() override {} | |
| 371 | |
| 372 // ExternalProviderInterface: | |
| 373 void ServiceShutdown() override {} | |
| 374 | |
| 375 void VisitRegisteredExtension() override { | |
| 376 visitor_->OnExternalExtensionUpdateUrlFound( | |
| 377 ExternalInstallInfoUpdateUrl( | |
| 378 extension_id_, std::string() /* install_parameter */, | |
| 379 base::MakeUnique<GURL>(extension_urls::GetWebstoreUpdateUrl()), | |
| 380 Manifest::EXTERNAL_POLICY_DOWNLOAD, 0 /* creation_flags */, | |
| 381 true /* mark_acknowledged */), | |
| 382 true /* is_initial_load */); | |
| 383 visitor_->OnExternalProviderReady(this); | |
| 384 } | |
| 385 | |
| 386 bool HasExtension(const std::string& id) const override { | |
| 387 return id == std::string("npnbmohejbjohgpjnmjagbafnjhkmgko"); | |
| 388 } | |
| 389 | |
| 390 bool GetExtensionDetails( | |
| 391 const std::string& id, | |
| 392 Manifest::Location* location, | |
| 393 std::unique_ptr<base::Version>* version) const override { | |
| 394 ADD_FAILURE() << "Unexpected GetExtensionDetails call; id:" << id; | |
| 395 return false; | |
| 396 } | |
| 397 | |
| 398 bool IsReady() const override { return true; } | |
| 399 | |
| 400 private: | |
| 401 VisitorInterface* visitor_; | |
| 402 std::string extension_id_; | |
| 403 base::Closure quit_closure_; | |
| 404 | |
| 405 DISALLOW_COPY_AND_ASSIGN(TestExternalProvider); | |
| 406 }; | |
| 407 | |
| 408 // This lets us simulate a policy-installed extension being "force" installed; | |
| 409 // ie a user is not allowed to manually uninstall/disable it. | |
| 410 class ForceInstallProvider : public ManagementPolicy::Provider { | |
| 411 public: | |
| 412 explicit ForceInstallProvider(const std::string& id) : id_(id) {} | |
| 413 ~ForceInstallProvider() override {} | |
| 414 | |
| 415 std::string GetDebugPolicyProviderName() const override { | |
| 416 return "ForceInstallProvider"; | |
| 417 } | |
| 418 | |
| 419 // MananagementPolicy::Provider: | |
| 420 bool UserMayModifySettings(const Extension* extension, | |
| 421 base::string16* error) const override { | |
| 422 return extension->id() != id_; | |
| 423 } | |
| 424 | |
| 425 private: | |
| 426 // The extension id we want to disallow uninstall/disable for. | |
| 427 std::string id_; | |
| 428 | |
| 429 DISALLOW_COPY_AND_ASSIGN(ForceInstallProvider); | |
| 430 }; | |
| 431 | |
| 253 } // namespace | 432 } // namespace |
| 254 | 433 |
| 255 class ContentVerifierTest : public ExtensionBrowserTest { | 434 class ContentVerifierTest : public ExtensionBrowserTest { |
| 256 public: | 435 public: |
| 257 ContentVerifierTest() {} | 436 ContentVerifierTest() {} |
| 258 ~ContentVerifierTest() override {} | 437 ~ContentVerifierTest() override {} |
| 259 | 438 |
| 260 void SetUpCommandLine(base::CommandLine* command_line) override { | 439 void SetUpCommandLine(base::CommandLine* command_line) override { |
| 261 ExtensionBrowserTest::SetUpCommandLine(command_line); | 440 ExtensionBrowserTest::SetUpCommandLine(command_line); |
| 262 command_line->AppendSwitchASCII( | 441 command_line->AppendSwitchASCII( |
| (...skipping 10 matching lines...) Expand all Loading... | |
| 273 ContentVerifyJob::SetDelegateForTests(NULL); | 452 ContentVerifyJob::SetDelegateForTests(NULL); |
| 274 ContentVerifyJob::SetObserverForTests(NULL); | 453 ContentVerifyJob::SetObserverForTests(NULL); |
| 275 ExtensionBrowserTest::TearDownOnMainThread(); | 454 ExtensionBrowserTest::TearDownOnMainThread(); |
| 276 } | 455 } |
| 277 | 456 |
| 278 virtual void OpenPageAndWaitForUnload() { | 457 virtual void OpenPageAndWaitForUnload() { |
| 279 ContentVerifyJob::SetDelegateForTests(&delegate_); | 458 ContentVerifyJob::SetDelegateForTests(&delegate_); |
| 280 std::string id = "npnbmohejbjohgpjnmjagbafnjhkmgko"; | 459 std::string id = "npnbmohejbjohgpjnmjagbafnjhkmgko"; |
| 281 delegate_.set_id(id); | 460 delegate_.set_id(id); |
| 282 unload_observer_.reset( | 461 unload_observer_.reset( |
| 283 new UnloadObserver(ExtensionRegistry::Get(profile()))); | 462 new RegistryObserver(ExtensionRegistry::Get(profile()))); |
| 284 const Extension* extension = InstallExtensionFromWebstore( | 463 const Extension* extension = InstallExtensionFromWebstore( |
| 285 test_data_dir_.AppendASCII("content_verifier/v1.crx"), 1); | 464 test_data_dir_.AppendASCII("content_verifier/v1.crx"), 1); |
| 286 ASSERT_TRUE(extension); | 465 ASSERT_TRUE(extension); |
| 287 ASSERT_EQ(id, extension->id()); | 466 ASSERT_EQ(id, extension->id()); |
| 288 page_url_ = extension->GetResourceURL("page.html"); | 467 page_url_ = extension->GetResourceURL("page.html"); |
| 289 | 468 |
| 290 // This call passes false for |check_navigation_success|, because checking | 469 // This call passes false for |check_navigation_success|, because checking |
| 291 // for navigation success needs the WebContents to still exist after the | 470 // for navigation success needs the WebContents to still exist after the |
| 292 // navigation, whereas this navigation triggers an unload which destroys | 471 // navigation, whereas this navigation triggers an unload which destroys |
| 293 // the WebContents. | 472 // the WebContents. |
| 294 AddTabAtIndexToBrowser(browser(), 1, page_url_, ui::PAGE_TRANSITION_LINK, | 473 AddTabAtIndexToBrowser(browser(), 1, page_url_, ui::PAGE_TRANSITION_LINK, |
| 295 false); | 474 false); |
| 296 | 475 |
| 297 unload_observer_->WaitForUnload(id); | 476 EXPECT_TRUE(unload_observer_->WaitForUnload(id)); |
| 298 ExtensionPrefs* prefs = ExtensionPrefs::Get(profile()); | 477 ExtensionPrefs* prefs = ExtensionPrefs::Get(profile()); |
| 299 int reasons = prefs->GetDisableReasons(id); | 478 int reasons = prefs->GetDisableReasons(id); |
| 300 EXPECT_TRUE(reasons & Extension::DISABLE_CORRUPTED); | 479 EXPECT_TRUE(reasons & Extension::DISABLE_CORRUPTED); |
| 301 | 480 |
| 302 // This needs to happen before the ExtensionRegistry gets deleted, which | 481 // This needs to happen before the ExtensionRegistry gets deleted, which |
| 303 // happens before TearDownOnMainThread is called. | 482 // happens before TearDownOnMainThread is called. |
| 304 unload_observer_.reset(); | 483 unload_observer_.reset(); |
| 305 } | 484 } |
| 306 | 485 |
| 307 protected: | 486 protected: |
| 308 JobDelegate delegate_; | 487 JobDelegate delegate_; |
| 309 std::unique_ptr<UnloadObserver> unload_observer_; | 488 std::unique_ptr<RegistryObserver> unload_observer_; |
| 310 GURL page_url_; | 489 GURL page_url_; |
| 311 }; | 490 }; |
| 312 | 491 |
| 313 IN_PROC_BROWSER_TEST_F(ContentVerifierTest, FailOnRead) { | 492 IN_PROC_BROWSER_TEST_F(ContentVerifierTest, FailOnRead) { |
| 314 EXPECT_EQ(0, delegate_.bytes_read_failed()); | 493 EXPECT_EQ(0, delegate_.bytes_read_failed()); |
| 315 delegate_.fail_next_read(); | 494 delegate_.fail_next_read(); |
| 316 OpenPageAndWaitForUnload(); | 495 OpenPageAndWaitForUnload(); |
| 317 EXPECT_EQ(1, delegate_.bytes_read_failed()); | 496 EXPECT_EQ(1, delegate_.bytes_read_failed()); |
| 318 } | 497 } |
| 319 | 498 |
| (...skipping 96 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 416 DisableExtension(id); | 595 DisableExtension(id); |
| 417 job_observer.ExpectJobResult(id, | 596 job_observer.ExpectJobResult(id, |
| 418 base::FilePath(FILE_PATH_LITERAL("script.js")), | 597 base::FilePath(FILE_PATH_LITERAL("script.js")), |
| 419 JobObserver::Result::FAILURE); | 598 JobObserver::Result::FAILURE); |
| 420 EnableExtension(id); | 599 EnableExtension(id); |
| 421 EXPECT_TRUE(job_observer.WaitForExpectedJobs()); | 600 EXPECT_TRUE(job_observer.WaitForExpectedJobs()); |
| 422 | 601 |
| 423 ContentVerifyJob::SetObserverForTests(NULL); | 602 ContentVerifyJob::SetObserverForTests(NULL); |
| 424 } | 603 } |
| 425 | 604 |
| 605 // Test the case of a corrupt extension that is force-installed by policy and | |
|
lazyboy
2016/09/02 19:21:52
nit: Tests the ...
asargent_no_longer_on_chrome
2016/09/09 03:30:43
Done.
| |
| 606 // should not be allowed to be manually uninstalled/disabled by the user. | |
| 607 IN_PROC_BROWSER_TEST_F(ContentVerifierTest, PolicyCorrupted) { | |
|
lazyboy
2016/09/02 19:21:53
Nice and thorough test!
asargent_no_longer_on_chrome
2016/09/09 03:30:43
thanks!
| |
| 608 ExtensionSystem* system = ExtensionSystem::Get(profile()); | |
| 609 ExtensionService* service = system->extension_service(); | |
| 610 | |
| 611 // The id of our test extension. | |
| 612 std::string id("npnbmohejbjohgpjnmjagbafnjhkmgko"); | |
| 613 | |
| 614 // Setup fake policy and update check objects. | |
| 615 ForceInstallProvider policy(id); | |
| 616 DownloaderTestDelegate downloader; | |
| 617 system->management_policy()->RegisterProvider(&policy); | |
| 618 ExtensionDownloader::set_test_delegate(&downloader); | |
| 619 service->AddProviderForTesting( | |
| 620 base::MakeUnique<TestExternalProvider>(service, id)); | |
| 621 | |
| 622 base::FilePath crx_path = | |
| 623 test_data_dir_.AppendASCII("content_verifier/v1.crx"); | |
| 624 const Extension* extension = | |
| 625 InstallExtension(crx_path, 1, Manifest::EXTERNAL_POLICY_DOWNLOAD); | |
| 626 EXPECT_NE(extension, nullptr); | |
| 627 | |
| 628 downloader.AddResponse(id, extension->VersionString(), crx_path); | |
| 629 | |
| 630 RegistryObserver registry_observer(ExtensionRegistry::Get(profile())); | |
| 631 ContentVerifier* verifier = system->content_verifier(); | |
| 632 verifier->VerifyFailed(extension->id(), ContentVerifyJob::HASH_MISMATCH); | |
| 633 | |
| 634 // Make sure the extension first got disabled due to corruption. | |
| 635 EXPECT_TRUE(registry_observer.WaitForUnload(id)); | |
| 636 ExtensionPrefs* prefs = ExtensionPrefs::Get(profile()); | |
| 637 int reasons = prefs->GetDisableReasons(id); | |
| 638 EXPECT_TRUE(reasons & Extension::DISABLE_CORRUPTED); | |
| 639 | |
| 640 // Make sure the extension then got re-installed, and that after reinstall it | |
| 641 // is no longer disabled due to corruption. | |
| 642 EXPECT_TRUE(registry_observer.WaitForInstall(id)); | |
| 643 reasons = prefs->GetDisableReasons(id); | |
| 644 EXPECT_FALSE(reasons & Extension::DISABLE_CORRUPTED); | |
| 645 | |
| 646 // Make sure that the update check request properly included a parameter | |
| 647 // indicating that this was a corrupt policy reinstall. | |
| 648 bool found = false; | |
| 649 for (const auto& request : downloader.requests()) { | |
| 650 if (request->Includes(id)) { | |
| 651 std::string query = request->full_url().query(); | |
| 652 for (const auto& part : base::SplitString( | |
| 653 query, "&", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL)) { | |
| 654 if (part.find("x=") == 0 && | |
|
lazyboy
2016/09/02 19:21:53
base::StartWith(part, "x=")
asargent_no_longer_on_chrome
2016/09/09 03:30:43
Done.
| |
| 655 part.find(std::string("id%3D") + id) != std::string::npos) { | |
| 656 found = true; | |
| 657 EXPECT_NE(std::string::npos, part.find("%26cpr")); | |
| 658 } | |
| 659 } | |
| 660 } | |
| 661 } | |
| 662 EXPECT_TRUE(found); | |
| 663 } | |
| 664 | |
| 426 } // namespace extensions | 665 } // namespace extensions |
| OLD | NEW |