Home | History | Annotate | Download | only in safe_browsing
      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/renderer/safe_browsing/phishing_classifier_delegate.h"
      6 
      7 #include "base/command_line.h"
      8 #include "base/memory/scoped_ptr.h"
      9 #include "base/run_loop.h"
     10 #include "base/strings/string_number_conversions.h"
     11 #include "base/strings/utf_string_conversions.h"
     12 #include "chrome/browser/ui/browser.h"
     13 #include "chrome/browser/ui/browser_commands.h"
     14 #include "chrome/browser/ui/tabs/tab_strip_model.h"
     15 #include "chrome/common/chrome_switches.h"
     16 #include "chrome/common/safe_browsing/csd.pb.h"
     17 #include "chrome/common/safe_browsing/safebrowsing_messages.h"
     18 #include "chrome/renderer/safe_browsing/features.h"
     19 #include "chrome/renderer/safe_browsing/phishing_classifier.h"
     20 #include "chrome/renderer/safe_browsing/scorer.h"
     21 #include "chrome/test/base/in_process_browser_test.h"
     22 #include "chrome/test/base/ui_test_utils.h"
     23 #include "content/public/browser/browser_message_filter.h"
     24 #include "content/public/browser/browser_thread.h"
     25 #include "content/public/browser/render_process_host.h"
     26 #include "content/public/browser/web_contents.h"
     27 #include "content/public/renderer/render_view.h"
     28 #include "content/public/test/browser_test_utils.h"
     29 #include "content/public/test/test_navigation_observer.h"
     30 #include "content/public/test/test_utils.h"
     31 #include "net/dns/mock_host_resolver.h"
     32 #include "net/test/embedded_test_server/embedded_test_server.h"
     33 #include "net/test/embedded_test_server/http_request.h"
     34 #include "net/test/embedded_test_server/http_response.h"
     35 #include "testing/gmock/include/gmock/gmock.h"
     36 #include "third_party/WebKit/public/platform/WebURL.h"
     37 #include "third_party/WebKit/public/platform/WebURLRequest.h"
     38 #include "third_party/WebKit/public/web/WebFrame.h"
     39 #include "third_party/WebKit/public/web/WebView.h"
     40 #include "url/gurl.h"
     41 
     42 using base::ASCIIToUTF16;
     43 using ::testing::_;
     44 using ::testing::InSequence;
     45 using ::testing::Mock;
     46 using ::testing::Pointee;
     47 using ::testing::StrictMock;
     48 
     49 namespace safe_browsing {
     50 
     51 namespace {
     52 
     53 // The RenderFrame is routing ID 1, and the RenderView is 2.
     54 const int kRenderViewRoutingId = 2;
     55 
     56 class MockPhishingClassifier : public PhishingClassifier {
     57  public:
     58   explicit MockPhishingClassifier(content::RenderView* render_view)
     59       : PhishingClassifier(render_view, NULL /* clock */) {}
     60 
     61   virtual ~MockPhishingClassifier() {}
     62 
     63   MOCK_METHOD2(BeginClassification,
     64                void(const base::string16*, const DoneCallback&));
     65   MOCK_METHOD0(CancelPendingClassification, void());
     66 
     67  private:
     68   DISALLOW_COPY_AND_ASSIGN(MockPhishingClassifier);
     69 };
     70 
     71 class MockScorer : public Scorer {
     72  public:
     73   MockScorer() : Scorer() {}
     74   virtual ~MockScorer() {}
     75 
     76   MOCK_CONST_METHOD1(ComputeScore, double(const FeatureMap&));
     77 
     78  private:
     79   DISALLOW_COPY_AND_ASSIGN(MockScorer);
     80 };
     81 
     82 class InterceptingMessageFilter : public content::BrowserMessageFilter {
     83  public:
     84   InterceptingMessageFilter()
     85       : BrowserMessageFilter(SafeBrowsingMsgStart),
     86         waiting_message_loop_(NULL) {
     87   }
     88 
     89   const ClientPhishingRequest* verdict() const { return verdict_.get(); }
     90   virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE {
     91     bool handled = true;
     92     IPC_BEGIN_MESSAGE_MAP(InterceptingMessageFilter, message)
     93         IPC_MESSAGE_HANDLER(SafeBrowsingHostMsg_PhishingDetectionDone,
     94                             OnPhishingDetectionDone)
     95         IPC_MESSAGE_UNHANDLED(handled = false);
     96     IPC_END_MESSAGE_MAP()
     97     return handled;
     98   }
     99 
    100   void Reset() {
    101     run_loop_.reset(new base::RunLoop());
    102     waiting_message_loop_ = base::MessageLoop::current();
    103     quit_closure_ = run_loop_->QuitClosure();
    104   }
    105 
    106   void RunUntilVerdictReceived() {
    107     content::RunThisRunLoop(run_loop_.get());
    108 
    109     // Clear out the synchronization state just in case.
    110     waiting_message_loop_ = NULL;
    111     quit_closure_.Reset();
    112     run_loop_.reset();
    113   }
    114 
    115   void OnPhishingDetectionDone(const std::string& verdict_str) {
    116     scoped_ptr<ClientPhishingRequest> verdict(new ClientPhishingRequest);
    117     if (verdict->ParseFromString(verdict_str) &&
    118         verdict->IsInitialized()) {
    119       verdict_.swap(verdict);
    120     }
    121     waiting_message_loop_->PostTask(FROM_HERE, quit_closure_);
    122   }
    123 
    124  private:
    125   virtual ~InterceptingMessageFilter() {}
    126 
    127   scoped_ptr<ClientPhishingRequest> verdict_;
    128   base::MessageLoop* waiting_message_loop_;
    129   base::Closure quit_closure_;
    130   scoped_ptr<base::RunLoop> run_loop_;
    131 };
    132 }  // namespace
    133 
    134 class PhishingClassifierDelegateTest : public InProcessBrowserTest {
    135  public:
    136   void CancelCalled() {
    137     if (runner_.get()) {
    138       content::BrowserThread::PostTask(
    139           content::BrowserThread::UI, FROM_HERE, runner_->QuitClosure());
    140     }
    141   }
    142 
    143  protected:
    144   virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
    145     command_line->AppendSwitch(switches::kSingleProcess);
    146 #if defined(OS_WIN)
    147     // Don't want to try to create a GPU process.
    148     command_line->AppendSwitch(switches::kDisableGpu);
    149 #endif
    150   }
    151 
    152   virtual void SetUpOnMainThread() OVERRIDE {
    153     intercepting_filter_ = new InterceptingMessageFilter();
    154     content::RenderView* render_view =
    155         content::RenderView::FromRoutingID(kRenderViewRoutingId);
    156 
    157     GetWebContents()->GetRenderProcessHost()->AddFilter(
    158         intercepting_filter_.get());
    159     classifier_ = new StrictMock<MockPhishingClassifier>(render_view);
    160     delegate_ = PhishingClassifierDelegate::Create(render_view, classifier_);
    161 
    162     ASSERT_TRUE(StartTestServer());
    163     host_resolver()->AddRule("*", "127.0.0.1");
    164   }
    165 
    166   // Runs the ClassificationDone callback, then waits for the
    167   // PhishingDetectionDone IPC to arrive.
    168   void RunClassificationDone(const ClientPhishingRequest& verdict) {
    169     // Clear out any previous state.
    170     intercepting_filter_->Reset();
    171     PostTaskToInProcessRendererAndWait(
    172         base::Bind(&PhishingClassifierDelegate::ClassificationDone,
    173         base::Unretained(delegate_),
    174         verdict));
    175     intercepting_filter_->RunUntilVerdictReceived();
    176   }
    177 
    178   void OnStartPhishingDetection(const GURL& url) {
    179     PostTaskToInProcessRendererAndWait(
    180         base::Bind(&PhishingClassifierDelegate::OnStartPhishingDetection,
    181                    base::Unretained(delegate_), url));
    182   }
    183 
    184   void PageCaptured(base::string16* page_text, bool preliminary_capture) {
    185     PostTaskToInProcessRendererAndWait(
    186         base::Bind(&PhishingClassifierDelegate::PageCaptured,
    187                    base::Unretained(delegate_), page_text,
    188                    preliminary_capture));
    189   }
    190 
    191   bool StartTestServer() {
    192     CHECK(!embedded_test_server_);
    193     embedded_test_server_.reset(new net::test_server::EmbeddedTestServer());
    194     embedded_test_server_->RegisterRequestHandler(
    195         base::Bind(&PhishingClassifierDelegateTest::HandleRequest,
    196                    base::Unretained(this)));
    197     return embedded_test_server_->InitializeAndWaitUntilReady();
    198   }
    199 
    200   scoped_ptr<net::test_server::HttpResponse> HandleRequest(
    201       const net::test_server::HttpRequest& request) {
    202     std::map<std::string, std::string>::const_iterator host_it =
    203         request.headers.find("Host");
    204     if (host_it == request.headers.end())
    205       return scoped_ptr<net::test_server::HttpResponse>();
    206 
    207     std::string url =
    208         std::string("http://") + host_it->second + request.relative_url;
    209     if (response_url_.spec() != url)
    210       return scoped_ptr<net::test_server::HttpResponse>();
    211 
    212     scoped_ptr<net::test_server::BasicHttpResponse> http_response(
    213         new net::test_server::BasicHttpResponse());
    214     http_response->set_code(net::HTTP_OK);
    215     http_response->set_content_type("text/html");
    216     http_response->set_content(response_content_);
    217     return http_response.PassAs<net::test_server::HttpResponse>();
    218   }
    219 
    220   content::WebContents* GetWebContents() {
    221     return browser()->tab_strip_model()->GetActiveWebContents();
    222   }
    223 
    224   // Returns the URL that was loaded.
    225   GURL LoadHtml(const std::string& host, const std::string& content) {
    226     GURL::Replacements replace_host;
    227     replace_host.SetHostStr(host);
    228     response_content_ = content;
    229     response_url_ =
    230         embedded_test_server_->base_url().ReplaceComponents(replace_host);
    231     ui_test_utils::NavigateToURL(browser(), response_url_);
    232     return response_url_;
    233   }
    234 
    235   void NavigateMainFrame(const GURL& url) {
    236     PostTaskToInProcessRendererAndWait(
    237         base::Bind(&PhishingClassifierDelegateTest::NavigateMainFrameInternal,
    238                    base::Unretained(this), url));
    239   }
    240 
    241   void NavigateMainFrameInternal(const GURL& url) {
    242     content::RenderView* render_view =
    243         content::RenderView::FromRoutingID(kRenderViewRoutingId);
    244     render_view->GetWebView()->mainFrame()->firstChild()->loadRequest(
    245         blink::WebURLRequest(url));
    246   }
    247 
    248   void GoBack() {
    249     GetWebContents()->GetController().GoBack();
    250     content::WaitForLoadStop(GetWebContents());
    251   }
    252 
    253   void GoForward() {
    254     GetWebContents()->GetController().GoForward();
    255     content::WaitForLoadStop(GetWebContents());
    256   }
    257 
    258   scoped_refptr<InterceptingMessageFilter> intercepting_filter_;
    259   GURL response_url_;
    260   std::string response_content_;
    261   scoped_ptr<net::test_server::EmbeddedTestServer> embedded_test_server_;
    262   scoped_ptr<ClientPhishingRequest> verdict_;
    263   StrictMock<MockPhishingClassifier>* classifier_;  // Owned by |delegate_|.
    264   PhishingClassifierDelegate* delegate_;  // Owned by the RenderView.
    265   scoped_refptr<content::MessageLoopRunner> runner_;
    266 };
    267 
    268 IN_PROC_BROWSER_TEST_F(PhishingClassifierDelegateTest, Navigation) {
    269   MockScorer scorer;
    270   delegate_->SetPhishingScorer(&scorer);
    271   ASSERT_TRUE(classifier_->is_ready());
    272 
    273   // Test an initial load.  We expect classification to happen normally.
    274   EXPECT_CALL(*classifier_, CancelPendingClassification()).Times(2);
    275   std::string port = base::IntToString(embedded_test_server_->port());
    276   std::string html = "<html><body><iframe src=\"http://sub1.com:";
    277   html += port;
    278   html += "/\"></iframe></body></html>";
    279   GURL url = LoadHtml("host.com", html);
    280   Mock::VerifyAndClearExpectations(classifier_);
    281   OnStartPhishingDetection(url);
    282   base::string16 page_text = ASCIIToUTF16("dummy");
    283   {
    284     InSequence s;
    285     EXPECT_CALL(*classifier_, CancelPendingClassification());
    286     EXPECT_CALL(*classifier_, BeginClassification(Pointee(page_text), _));
    287     PageCaptured(&page_text, false);
    288     Mock::VerifyAndClearExpectations(classifier_);
    289   }
    290 
    291   // Reloading the same page should not trigger a reclassification.
    292   // However, it will cancel any pending classification since the
    293   // content is being replaced.
    294   EXPECT_CALL(*classifier_, CancelPendingClassification()).Times(2);
    295 
    296   content::TestNavigationObserver observer(GetWebContents());
    297   chrome::Reload(browser(), CURRENT_TAB);
    298   observer.Wait();
    299 
    300   Mock::VerifyAndClearExpectations(classifier_);
    301   OnStartPhishingDetection(url);
    302   page_text = ASCIIToUTF16("dummy");
    303   EXPECT_CALL(*classifier_, CancelPendingClassification());
    304   PageCaptured(&page_text, false);
    305   Mock::VerifyAndClearExpectations(classifier_);
    306 
    307   // Navigating in a subframe will not change the toplevel URL.  However, this
    308   // should cancel pending classification since the page content is changing.
    309   // Currently, we do not start a new classification after subframe loads.
    310   EXPECT_CALL(*classifier_, CancelPendingClassification())
    311       .WillOnce(Invoke(this, &PhishingClassifierDelegateTest::CancelCalled));
    312 
    313   runner_ = new content::MessageLoopRunner;
    314   NavigateMainFrame(GURL(std::string("http://sub2.com:") + port + "/"));
    315 
    316   runner_->Run();
    317   runner_ = NULL;
    318 
    319   Mock::VerifyAndClearExpectations(classifier_);
    320 
    321   OnStartPhishingDetection(url);
    322   page_text = ASCIIToUTF16("dummy");
    323   EXPECT_CALL(*classifier_, CancelPendingClassification());
    324   PageCaptured(&page_text, false);
    325   Mock::VerifyAndClearExpectations(classifier_);
    326 
    327   // Scrolling to an anchor works similarly to a subframe navigation, but
    328   // see the TODO in PhishingClassifierDelegate::DidCommitProvisionalLoad.
    329   EXPECT_CALL(*classifier_, CancelPendingClassification());
    330   GURL foo_url = GURL(url.spec() + "#foo");
    331   ui_test_utils::NavigateToURL(browser(), foo_url);
    332   Mock::VerifyAndClearExpectations(classifier_);
    333   OnStartPhishingDetection(url);
    334   page_text = ASCIIToUTF16("dummy");
    335   EXPECT_CALL(*classifier_, CancelPendingClassification());
    336   PageCaptured(&page_text, false);
    337   Mock::VerifyAndClearExpectations(classifier_);
    338 
    339   // Now load a new toplevel page, which should trigger another classification.
    340   EXPECT_CALL(*classifier_, CancelPendingClassification())
    341       .WillOnce(Invoke(this, &PhishingClassifierDelegateTest::CancelCalled));
    342 
    343   runner_ = new content::MessageLoopRunner;
    344   url = LoadHtml("host2.com", "dummy2");
    345   runner_->Run();
    346   runner_ = NULL;
    347 
    348   Mock::VerifyAndClearExpectations(classifier_);
    349   page_text = ASCIIToUTF16("dummy2");
    350   OnStartPhishingDetection(url);
    351   {
    352     InSequence s;
    353     EXPECT_CALL(*classifier_, CancelPendingClassification());
    354     EXPECT_CALL(*classifier_, BeginClassification(Pointee(page_text), _));
    355     PageCaptured(&page_text, false);
    356     Mock::VerifyAndClearExpectations(classifier_);
    357   }
    358 
    359   // No classification should happen on back/forward navigation.
    360   // Note: in practice, the browser will not send a StartPhishingDetection IPC
    361   // in this case.  However, we want to make sure that the delegate behaves
    362   // correctly regardless.
    363   EXPECT_CALL(*classifier_, CancelPendingClassification()).Times(2);
    364   GoBack();
    365   Mock::VerifyAndClearExpectations(classifier_);
    366 
    367   page_text = ASCIIToUTF16("dummy");
    368   OnStartPhishingDetection(url);
    369   EXPECT_CALL(*classifier_, CancelPendingClassification());
    370   PageCaptured(&page_text, false);
    371   Mock::VerifyAndClearExpectations(classifier_);
    372 
    373   EXPECT_CALL(*classifier_, CancelPendingClassification());
    374   GoForward();
    375   Mock::VerifyAndClearExpectations(classifier_);
    376 
    377   page_text = ASCIIToUTF16("dummy2");
    378   OnStartPhishingDetection(url);
    379   EXPECT_CALL(*classifier_, CancelPendingClassification());
    380   PageCaptured(&page_text, false);
    381   Mock::VerifyAndClearExpectations(classifier_);
    382 
    383   // Now go back again and scroll to a different anchor.
    384   // No classification should happen.
    385   EXPECT_CALL(*classifier_, CancelPendingClassification()).Times(2);
    386   GoBack();
    387   Mock::VerifyAndClearExpectations(classifier_);
    388   page_text = ASCIIToUTF16("dummy");
    389 
    390   OnStartPhishingDetection(url);
    391   EXPECT_CALL(*classifier_, CancelPendingClassification());
    392   PageCaptured(&page_text, false);
    393   Mock::VerifyAndClearExpectations(classifier_);
    394 
    395   EXPECT_CALL(*classifier_, CancelPendingClassification());
    396   GURL foo2_url = GURL(foo_url.spec() + "2");
    397   ui_test_utils::NavigateToURL(browser(), foo2_url);
    398   Mock::VerifyAndClearExpectations(classifier_);
    399 
    400   OnStartPhishingDetection(url);
    401   page_text = ASCIIToUTF16("dummy");
    402   EXPECT_CALL(*classifier_, CancelPendingClassification());
    403   PageCaptured(&page_text, false);
    404   Mock::VerifyAndClearExpectations(classifier_);
    405 
    406   // The delegate will cancel pending classification on destruction.
    407   EXPECT_CALL(*classifier_, CancelPendingClassification());
    408 }
    409 
    410 IN_PROC_BROWSER_TEST_F(PhishingClassifierDelegateTest, NoScorer) {
    411   // For this test, we'll create the delegate with no scorer available yet.
    412   ASSERT_FALSE(classifier_->is_ready());
    413 
    414   // Queue up a pending classification, cancel it, then queue up another one.
    415   GURL url = LoadHtml("host.com", "dummy");
    416   base::string16 page_text = ASCIIToUTF16("dummy");
    417   OnStartPhishingDetection(url);
    418   PageCaptured(&page_text, false);
    419 
    420   url = LoadHtml("host2.com", "dummy2");
    421   page_text = ASCIIToUTF16("dummy2");
    422   OnStartPhishingDetection(url);
    423   PageCaptured(&page_text, false);
    424 
    425   // Now set a scorer, which should cause a classifier to be created and
    426   // the classification to proceed.
    427   page_text = ASCIIToUTF16("dummy2");
    428   EXPECT_CALL(*classifier_, BeginClassification(Pointee(page_text), _));
    429   MockScorer scorer;
    430   delegate_->SetPhishingScorer(&scorer);
    431   Mock::VerifyAndClearExpectations(classifier_);
    432 
    433   // If we set a new scorer while a classification is going on the
    434   // classification should be cancelled.
    435   EXPECT_CALL(*classifier_, CancelPendingClassification());
    436   delegate_->SetPhishingScorer(&scorer);
    437   Mock::VerifyAndClearExpectations(classifier_);
    438 
    439   // The delegate will cancel pending classification on destruction.
    440   EXPECT_CALL(*classifier_, CancelPendingClassification());
    441 }
    442 
    443 IN_PROC_BROWSER_TEST_F(PhishingClassifierDelegateTest, NoScorer_Ref) {
    444   // Similar to the last test, but navigates within the page before
    445   // setting the scorer.
    446   ASSERT_FALSE(classifier_->is_ready());
    447 
    448   // Queue up a pending classification, cancel it, then queue up another one.
    449   GURL url = LoadHtml("host.com", "dummy");
    450   base::string16 page_text = ASCIIToUTF16("dummy");
    451   OnStartPhishingDetection(url);
    452   PageCaptured(&page_text, false);
    453 
    454   OnStartPhishingDetection(url);
    455   page_text = ASCIIToUTF16("dummy");
    456   PageCaptured(&page_text, false);
    457 
    458   // Now set a scorer, which should cause a classifier to be created and
    459   // the classification to proceed.
    460   page_text = ASCIIToUTF16("dummy");
    461   EXPECT_CALL(*classifier_, BeginClassification(Pointee(page_text), _));
    462   MockScorer scorer;
    463   delegate_->SetPhishingScorer(&scorer);
    464   Mock::VerifyAndClearExpectations(classifier_);
    465 
    466   // The delegate will cancel pending classification on destruction.
    467   EXPECT_CALL(*classifier_, CancelPendingClassification());
    468 }
    469 
    470 IN_PROC_BROWSER_TEST_F(PhishingClassifierDelegateTest,
    471                        NoStartPhishingDetection) {
    472   // Tests the behavior when OnStartPhishingDetection has not yet been called
    473   // when the page load finishes.
    474   MockScorer scorer;
    475   delegate_->SetPhishingScorer(&scorer);
    476   ASSERT_TRUE(classifier_->is_ready());
    477 
    478   EXPECT_CALL(*classifier_, CancelPendingClassification());
    479   GURL url = LoadHtml("host.com", "<html><body>phish</body></html>");
    480   Mock::VerifyAndClearExpectations(classifier_);
    481   base::string16 page_text = ASCIIToUTF16("phish");
    482   EXPECT_CALL(*classifier_, CancelPendingClassification());
    483   PageCaptured(&page_text, false);
    484   Mock::VerifyAndClearExpectations(classifier_);
    485   // Now simulate the StartPhishingDetection IPC.  We expect classification
    486   // to begin.
    487   page_text = ASCIIToUTF16("phish");
    488   EXPECT_CALL(*classifier_, BeginClassification(Pointee(page_text), _));
    489   OnStartPhishingDetection(url);
    490   Mock::VerifyAndClearExpectations(classifier_);
    491 
    492   // Now try again, but this time we will navigate the page away before
    493   // the IPC is sent.
    494   EXPECT_CALL(*classifier_, CancelPendingClassification());
    495   LoadHtml("host2.com", "<html><body>phish</body></html>");
    496   Mock::VerifyAndClearExpectations(classifier_);
    497   page_text = ASCIIToUTF16("phish");
    498   EXPECT_CALL(*classifier_, CancelPendingClassification());
    499   PageCaptured(&page_text, false);
    500   Mock::VerifyAndClearExpectations(classifier_);
    501 
    502   EXPECT_CALL(*classifier_, CancelPendingClassification());
    503   LoadHtml("host3.com", "<html><body>phish</body></html>");
    504   Mock::VerifyAndClearExpectations(classifier_);
    505   OnStartPhishingDetection(url);
    506 
    507   // In this test, the original page is a redirect, which we do not get a
    508   // StartPhishingDetection IPC for.  We use location.replace() to load a
    509   // new page while reusing the original session history entry, and check that
    510   // classification begins correctly for the landing page.
    511   EXPECT_CALL(*classifier_, CancelPendingClassification());
    512   LoadHtml("host4.com", "<html><body>abc</body></html>");
    513   Mock::VerifyAndClearExpectations(classifier_);
    514   page_text = ASCIIToUTF16("abc");
    515   EXPECT_CALL(*classifier_, CancelPendingClassification());
    516   PageCaptured(&page_text, false);
    517   Mock::VerifyAndClearExpectations(classifier_);
    518   EXPECT_CALL(*classifier_, CancelPendingClassification());
    519 
    520   ui_test_utils::NavigateToURL(
    521       browser(), GURL("javascript:location.replace(\'redir\');"));
    522 
    523   Mock::VerifyAndClearExpectations(classifier_);
    524 
    525   std::string url_str = "http://host4.com:";
    526   url_str += base::IntToString(embedded_test_server_->port());
    527   url_str += "/redir";
    528   OnStartPhishingDetection(GURL(url_str));
    529   page_text = ASCIIToUTF16("123");
    530   {
    531     InSequence s;
    532     EXPECT_CALL(*classifier_, CancelPendingClassification());
    533     EXPECT_CALL(*classifier_, BeginClassification(Pointee(page_text), _));
    534     PageCaptured(&page_text, false);
    535     Mock::VerifyAndClearExpectations(classifier_);
    536   }
    537 
    538   // The delegate will cancel pending classification on destruction.
    539   EXPECT_CALL(*classifier_, CancelPendingClassification());
    540 }
    541 
    542 IN_PROC_BROWSER_TEST_F(PhishingClassifierDelegateTest,
    543                        IgnorePreliminaryCapture) {
    544   // Tests that preliminary PageCaptured notifications are ignored.
    545   MockScorer scorer;
    546   delegate_->SetPhishingScorer(&scorer);
    547   ASSERT_TRUE(classifier_->is_ready());
    548 
    549   EXPECT_CALL(*classifier_, CancelPendingClassification());
    550   GURL url = LoadHtml("host.com", "<html><body>phish</body></html>");
    551   Mock::VerifyAndClearExpectations(classifier_);
    552   OnStartPhishingDetection(url);
    553   base::string16 page_text = ASCIIToUTF16("phish");
    554   PageCaptured(&page_text, true);
    555 
    556   // Once the non-preliminary capture happens, classification should begin.
    557   page_text = ASCIIToUTF16("phish");
    558   {
    559     InSequence s;
    560     EXPECT_CALL(*classifier_, CancelPendingClassification());
    561     EXPECT_CALL(*classifier_, BeginClassification(Pointee(page_text), _));
    562     PageCaptured(&page_text, false);
    563     Mock::VerifyAndClearExpectations(classifier_);
    564   }
    565 
    566   // The delegate will cancel pending classification on destruction.
    567   EXPECT_CALL(*classifier_, CancelPendingClassification());
    568 }
    569 
    570 #if defined(ADDRESS_SANITIZER)
    571 #define Maybe_DuplicatePageCapture DISABLED_DuplicatePageCapture
    572 #else
    573 #define Maybe_DuplicatePageCapture DuplicatePageCapture
    574 #endif
    575 IN_PROC_BROWSER_TEST_F(PhishingClassifierDelegateTest,
    576                        Maybe_DuplicatePageCapture) {
    577   // Tests that a second PageCaptured notification causes classification to
    578   // be cancelled.
    579   MockScorer scorer;
    580   delegate_->SetPhishingScorer(&scorer);
    581   ASSERT_TRUE(classifier_->is_ready());
    582 
    583   EXPECT_CALL(*classifier_, CancelPendingClassification());
    584   GURL url = LoadHtml("host.com", "<html><body>phish</body></html>");
    585   Mock::VerifyAndClearExpectations(classifier_);
    586   OnStartPhishingDetection(url);
    587   base::string16 page_text = ASCIIToUTF16("phish");
    588   {
    589     InSequence s;
    590     EXPECT_CALL(*classifier_, CancelPendingClassification());
    591     EXPECT_CALL(*classifier_, BeginClassification(Pointee(page_text), _));
    592     PageCaptured(&page_text, false);
    593     Mock::VerifyAndClearExpectations(classifier_);
    594   }
    595 
    596   page_text = ASCIIToUTF16("phish");
    597   EXPECT_CALL(*classifier_, CancelPendingClassification());
    598   PageCaptured(&page_text, false);
    599   Mock::VerifyAndClearExpectations(classifier_);
    600 
    601   // The delegate will cancel pending classification on destruction.
    602   EXPECT_CALL(*classifier_, CancelPendingClassification());
    603 }
    604 
    605 IN_PROC_BROWSER_TEST_F(PhishingClassifierDelegateTest, PhishingDetectionDone) {
    606   // Tests that a PhishingDetectionDone IPC is sent to the browser
    607   // whenever we finish classification.
    608   MockScorer scorer;
    609   delegate_->SetPhishingScorer(&scorer);
    610   ASSERT_TRUE(classifier_->is_ready());
    611 
    612   // Start by loading a page to populate the delegate's state.
    613   EXPECT_CALL(*classifier_, CancelPendingClassification());
    614   GURL url = LoadHtml("host.com", "<html><body>phish</body></html>");
    615   Mock::VerifyAndClearExpectations(classifier_);
    616   base::string16 page_text = ASCIIToUTF16("phish");
    617   OnStartPhishingDetection(url);
    618   {
    619     InSequence s;
    620     EXPECT_CALL(*classifier_, CancelPendingClassification());
    621     EXPECT_CALL(*classifier_, BeginClassification(Pointee(page_text), _));
    622     PageCaptured(&page_text, false);
    623     Mock::VerifyAndClearExpectations(classifier_);
    624   }
    625 
    626   // Now run the callback to simulate the classifier finishing.
    627   ClientPhishingRequest verdict;
    628   verdict.set_url(url.spec());
    629   verdict.set_client_score(0.8f);
    630   verdict.set_is_phishing(false);  // Send IPC even if site is not phishing.
    631   RunClassificationDone(verdict);
    632   ASSERT_TRUE(intercepting_filter_->verdict());
    633   EXPECT_EQ(verdict.SerializeAsString(),
    634             intercepting_filter_->verdict()->SerializeAsString());
    635 
    636   // The delegate will cancel pending classification on destruction.
    637   EXPECT_CALL(*classifier_, CancelPendingClassification());
    638 }
    639 
    640 }  // namespace safe_browsing
    641