diff options
author | Andras Becsi <andras.becsi@digia.com> | 2013-12-11 21:33:03 +0100 |
---|---|---|
committer | Andras Becsi <andras.becsi@digia.com> | 2013-12-13 12:34:07 +0100 |
commit | f2a33ff9cbc6d19943f1c7fbddd1f23d23975577 (patch) | |
tree | 0586a32aa390ade8557dfd6b4897f43a07449578 /chromium/components | |
parent | 5362912cdb5eea702b68ebe23702468d17c3017a (diff) | |
download | qtwebengine-chromium-f2a33ff9cbc6d19943f1c7fbddd1f23d23975577.tar.gz |
Update Chromium to branch 1650 (31.0.1650.63)
Change-Id: I57d8c832eaec1eb2364e0a8e7352a6dd354db99f
Reviewed-by: Jocelyn Turcotte <jocelyn.turcotte@digia.com>
Diffstat (limited to 'chromium/components')
223 files changed, 10278 insertions, 6133 deletions
diff --git a/chromium/components/DEPS b/chromium/components/DEPS index d6fbc7c41ed..0ea897e6ff0 100644 --- a/chromium/components/DEPS +++ b/chromium/components/DEPS @@ -13,4 +13,7 @@ include_rules = [ # "+content/public/browser" rule. "-content", "+content/public/common", + + # Dependencies of variations component. + "+third_party/mt19937ar", ] diff --git a/chromium/components/OWNERS b/chromium/components/OWNERS index 7884fe2f806..4c134eb1420 100644 --- a/chromium/components/OWNERS +++ b/chromium/components/OWNERS @@ -8,8 +8,15 @@ per-file breakpad.gypi=jochen@chromium.org per-file breakpad.gypi=rsesek@chromium.org per-file breakpad.gypi=thestig@chromium.org -per-file tracing*=jbauman@chromium.org -per-file tracing*=nduca@chromium.org +per-file dom_distiller*=bengr@chromium.org +per-file dom_distiller*=cjhopman@chromium.org +per-file dom_distiller*=nyquist@chromium.org + +per-file json_schema.gypi=asargent@chromium.org +per-file json_schema.gypi=calamity@chromium.org +per-file json_schema.gypi=kalman@chromium.org +per-file json_schema.gypi=koz@chromium.org +per-file json_schema.gypi=mpcomplete@chromium.org per-file nacl*=bradchen@chromium.org per-file nacl*=bradnelson@chromium.org @@ -21,13 +28,29 @@ per-file nacl*=sehr@chromium.org per-file navigation_interception.gypi=joth@chromium.org per-file navigation_interception.gypi=mkosiba@chromium.org +per-file policy.gypi=mnissler@chromium.org +per-file policy.gypi=pastarmovj@chromium.org +per-file policy.gypi=joaodasilva@chromium.org +per-file policy.gypi=bartfab@chromium.org +per-file policy.gypi=atwilson@chromium.org +per-file policy.gypi=pneubeck@chromium.org + per-file sessions.gypi=marja@chromium.org per-file sessions.gypi=sky@chromium.org +per-file tracing*=jbauman@chromium.org +per-file tracing*=nduca@chromium.org + +per-file startup_metric_utils.gypi=jeremy@chromium.org + per-file user_prefs.gypi=battre@chromium.org per-file user_prefs.gypi=bauerb@chromium.org per-file user_prefs.gypi=mnissler@chromium.org per-file user_prefs.gypi=pam@chromium.org +per-file variations.gypi=asvitkine@chromium.org +per-file variations.gypi=jwd@chromium.org +per-file variations.gypi=stevet@chromium.org + per-file *.isolate=csharp@chromium.org per-file *.isolate=maruel@chromium.org diff --git a/chromium/components/autofill.gypi b/chromium/components/autofill.gypi index 92eccd3a1d6..92df65af23b 100644 --- a/chromium/components/autofill.gypi +++ b/chromium/components/autofill.gypi @@ -56,12 +56,12 @@ 'autofill/core/browser/android/component_jni_registrar.cc', 'autofill/core/browser/android/component_jni_registrar.h', 'autofill/core/browser/android/personal_data_manager_android.cc', - 'autofill/core/common/autocheckout_status.h', 'autofill/core/common/autofill_constants.cc', 'autofill/core/common/autofill_constants.h', 'autofill/core/common/autofill_messages.h', 'autofill/core/common/autofill_message_generator.cc', 'autofill/core/common/autofill_message_generator.h', + 'autofill/core/common/autofill_param_traits_macros.h', 'autofill/core/common/autofill_pref_names.cc', 'autofill/core/common/autofill_pref_names.h', 'autofill/core/common/autofill_switches.cc', @@ -74,6 +74,8 @@ 'autofill/core/common/form_field_data.h', 'autofill/core/common/form_field_data_predictions.cc', 'autofill/core/common/form_field_data_predictions.h', + 'autofill/core/common/password_form.cc', + 'autofill/core/common/password_form.h', 'autofill/core/common/password_form_fill_data.cc', 'autofill/core/common/password_form_fill_data.h', 'autofill/core/common/password_generation_util.cc', @@ -220,6 +222,7 @@ 'dependencies': [ 'autofill_core_common', 'autofill_core_browser', + '../skia/skia.gyp:skia', '../testing/gtest.gyp:gtest', ], 'sources': [ @@ -301,17 +304,6 @@ 'component_strings.gyp:component_strings', ], 'sources': [ - 'autofill/content/browser/autocheckout/whitelist_manager.cc', - 'autofill/content/browser/autocheckout/whitelist_manager.h', - 'autofill/content/browser/autocheckout_manager.cc', - 'autofill/content/browser/autocheckout_manager.h', - 'autofill/content/browser/autocheckout_page_meta_data.cc', - 'autofill/content/browser/autocheckout_page_meta_data.h', - 'autofill/content/browser/autocheckout_request_manager.cc', - 'autofill/content/browser/autocheckout_request_manager.h', - 'autofill/content/browser/autocheckout_statistic.cc', - 'autofill/content/browser/autocheckout_statistic.h', - 'autofill/content/browser/autocheckout_steps.h', 'autofill/content/browser/autofill_driver_impl.cc', 'autofill/content/browser/autofill_driver_impl.h', 'autofill/content/browser/risk/fingerprint.cc', @@ -369,6 +361,8 @@ 'autofill/content/renderer/page_click_tracker.h', 'autofill/content/renderer/password_autofill_agent.cc', 'autofill/content/renderer/password_autofill_agent.h', + 'autofill/content/renderer/password_form_conversion_utils.cc', + 'autofill/content/renderer/password_form_conversion_utils.h', 'autofill/content/renderer/password_generation_manager.cc', 'autofill/content/renderer/password_generation_manager.h', ], diff --git a/chromium/components/autofill/content/DEPS b/chromium/components/autofill/content/DEPS index bd7656f8b4e..4b94f27f569 100644 --- a/chromium/components/autofill/content/DEPS +++ b/chromium/components/autofill/content/DEPS @@ -4,3 +4,9 @@ include_rules = [ "+third_party/WebKit/public/platform", "+third_party/WebKit/public/web", ] + +specific_include_rules = { + '.*_[a-z]*test\.cc': [ + "+content/public/test", + ], +} diff --git a/chromium/components/autofill/content/browser/autocheckout/whitelist_manager.cc b/chromium/components/autofill/content/browser/autocheckout/whitelist_manager.cc deleted file mode 100644 index 7d5423adc8d..00000000000 --- a/chromium/components/autofill/content/browser/autocheckout/whitelist_manager.cc +++ /dev/null @@ -1,205 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "components/autofill/content/browser/autocheckout/whitelist_manager.h" - -#include "base/command_line.h" -#include "base/logging.h" -#include "base/memory/scoped_ptr.h" -#include "base/metrics/field_trial.h" -#include "base/strings/string_split.h" -#include "base/strings/string_util.h" -#include "components/autofill/core/common/autofill_switches.h" -#include "content/public/browser/browser_context.h" -#include "net/base/load_flags.h" -#include "net/http/http_status_code.h" -#include "net/url_request/url_fetcher.h" -#include "net/url_request/url_request_context_getter.h" -#include "url/gurl.h" - -namespace { - -// Back off in seconds after each whitelist download is attempted. -const int kDownloadIntervalSeconds = 86400; // 1 day - -// The delay in seconds after startup before download whitelist. This helps -// to reduce contention at startup time. -const int kInitialDownloadDelaySeconds = 3; - -const net::BackoffEntry::Policy kBackoffPolicy = { - // Number of initial errors to ignore before starting to back off. - 0, - - // Initial delay in ms: 3 seconds. - 3000, - - // Factor by which the waiting time is multiplied. - 6, - - // Fuzzing percentage: no fuzzing logic. - 0, - - // Maximum delay in ms: 1 hour. - 1000 * 60 * 60, - - // When to discard an entry: 3 hours. - 1000 * 60 * 60 * 3, - - // |always_use_initial_delay|; false means that the initial delay is - // applied after the first error, and starts backing off from there. - false, -}; - -const char kDefaultWhitelistUrl[] = - "https://www.gstatic.com/commerce/autocheckout/whitelist.csv"; - -const char kWhiteListKeyName[] = "autocheckout_whitelist_manager"; - -std::string GetWhitelistUrl() { - const CommandLine& command_line = *CommandLine::ForCurrentProcess(); - std::string whitelist_url = command_line.GetSwitchValueASCII( - autofill::switches::kAutocheckoutWhitelistUrl); - - return whitelist_url.empty() ? kDefaultWhitelistUrl : whitelist_url; -} - -} // namespace - - -namespace autofill { -namespace autocheckout { - -WhitelistManager::WhitelistManager() - : callback_is_pending_(false), - experimental_form_filling_enabled_( - CommandLine::ForCurrentProcess()->HasSwitch( - switches::kEnableExperimentalFormFilling) || - base::FieldTrialList::FindFullName("Autocheckout") == "Yes"), - bypass_autocheckout_whitelist_( - CommandLine::ForCurrentProcess()->HasSwitch( - switches::kBypassAutocheckoutWhitelist)), - retry_entry_(&kBackoffPolicy) { -} - -WhitelistManager::~WhitelistManager() {} - -void WhitelistManager::Init(net::URLRequestContextGetter* context_getter) { - DCHECK(context_getter); - context_getter_ = context_getter; - ScheduleDownload(base::TimeDelta::FromSeconds(kInitialDownloadDelaySeconds)); -} - -void WhitelistManager::ScheduleDownload(base::TimeDelta interval) { - if (!experimental_form_filling_enabled_) { - // The feature is not enabled: do not do the request. - return; - } - if (download_timer_.IsRunning() || callback_is_pending_) { - // A download activity is already scheduled or happening. - return; - } - StartDownloadTimer(interval); -} - -void WhitelistManager::StartDownloadTimer(base::TimeDelta interval) { - download_timer_.Start(FROM_HERE, - interval, - this, - &WhitelistManager::TriggerDownload); -} - -const AutofillMetrics& WhitelistManager::GetMetricLogger() const { - return metrics_logger_; -} - -void WhitelistManager::TriggerDownload() { - callback_is_pending_ = true; - - request_started_timestamp_ = base::Time::Now(); - - request_.reset(net::URLFetcher::Create( - 0, GURL(GetWhitelistUrl()), net::URLFetcher::GET, this)); - request_->SetRequestContext(context_getter_); - request_->SetAutomaticallyRetryOn5xx(false); - request_->SetLoadFlags(net::LOAD_DO_NOT_SAVE_COOKIES | - net::LOAD_DO_NOT_SEND_COOKIES); - request_->Start(); -} - -void WhitelistManager::StopDownloadTimer() { - download_timer_.Stop(); - callback_is_pending_ = false; -} - -void WhitelistManager::OnURLFetchComplete( - const net::URLFetcher* source) { - DCHECK(callback_is_pending_); - callback_is_pending_ = false; - scoped_ptr<net::URLFetcher> old_request = request_.Pass(); - DCHECK_EQ(source, old_request.get()); - - AutofillMetrics::AutocheckoutWhitelistDownloadStatus status; - base::TimeDelta duration = base::Time::Now() - request_started_timestamp_; - - // Refresh the whitelist after kDownloadIntervalSeconds (24 hours). - base::TimeDelta next_download_time = - base::TimeDelta::FromSeconds(kDownloadIntervalSeconds); - - if (source->GetResponseCode() == net::HTTP_OK) { - std::string data; - source->GetResponseAsString(&data); - BuildWhitelist(data); - status = AutofillMetrics::AUTOCHECKOUT_WHITELIST_DOWNLOAD_SUCCEEDED; - retry_entry_.Reset(); - } else { - status = AutofillMetrics::AUTOCHECKOUT_WHITELIST_DOWNLOAD_FAILED; - retry_entry_.InformOfRequest(false); - if (!retry_entry_.CanDiscard()) - next_download_time = retry_entry_.GetTimeUntilRelease(); - } - - GetMetricLogger().LogAutocheckoutWhitelistDownloadDuration(duration, status); - ScheduleDownload(next_download_time); -} - -std::string WhitelistManager::GetMatchedURLPrefix(const GURL& url) const { - if (!experimental_form_filling_enabled_ || url.is_empty()) - return std::string(); - - for (std::vector<std::string>::const_iterator it = url_prefixes_.begin(); - it != url_prefixes_.end(); ++it) { - // This is only for ~20 sites initially, liner search is sufficient. - // TODO(benquan): Look for optimization options when we support - // more sites. - if (StartsWithASCII(url.spec(), *it, true)) { - DVLOG(1) << "WhitelistManager matched URLPrefix: " << *it; - return *it; - } - } - return bypass_autocheckout_whitelist_ ? url.spec() : std::string(); -} - -void WhitelistManager::BuildWhitelist(const std::string& data) { - std::vector<std::string> new_url_prefixes; - - std::vector<std::string> lines; - base::SplitString(data, '\n', &lines); - - for (std::vector<std::string>::const_iterator line = lines.begin(); - line != lines.end(); ++line) { - if (!line->empty()) { - std::vector<std::string> fields; - base::SplitString(*line, ',', &fields); - // Currently we have only one column in the whitelist file, if we decide - // to add more metadata as additional columns, previous versions of - // Chrome can ignore them and continue to work. - if (!fields[0].empty()) - new_url_prefixes.push_back(fields[0]); - } - } - url_prefixes_ = new_url_prefixes; -} - -} // namespace autocheckout -} // namespace autofill diff --git a/chromium/components/autofill/content/browser/autocheckout/whitelist_manager.h b/chromium/components/autofill/content/browser/autocheckout/whitelist_manager.h deleted file mode 100644 index 40c16f5d697..00000000000 --- a/chromium/components/autofill/content/browser/autocheckout/whitelist_manager.h +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef COMPONENTS_AUTOFILL_CONTENT_BROWSER_AUTOCHECKOUT_WHITELIST_MANAGER_H_ -#define COMPONENTS_AUTOFILL_CONTENT_BROWSER_AUTOCHECKOUT_WHITELIST_MANAGER_H_ - -#include <string> -#include <vector> - -#include "base/timer/timer.h" -#include "components/autofill/core/browser/autofill_metrics.h" -#include "net/base/backoff_entry.h" -#include "net/url_request/url_fetcher_delegate.h" - -class GURL; - -namespace content { -class BrowserContext; -} - -namespace net { -class URLFetcher; -class URLRequestContextGetter; -} - -namespace autofill { -namespace autocheckout { - -// Downloads and caches the list of URL prefixes whitelisted for use with -// Autocheckout. -class WhitelistManager : public net::URLFetcherDelegate { - public: - WhitelistManager(); - virtual ~WhitelistManager(); - - // Schedule a fetch of the Autocheckout whitelist file if it's not already - // loaded. This helps ensure that the whitelist will be available by the time - // the user navigates to a form on which Autocheckout should be enabled. - void Init(net::URLRequestContextGetter* context_getter); - - // Matches the url with whitelist and return the matched url prefix. - // Returns empty string when it is not matched. - std::string GetMatchedURLPrefix(const GURL& url) const; - - protected: - // Schedules a future call to TriggerDownload if one isn't already pending. - virtual void ScheduleDownload(base::TimeDelta interval); - - // Start the download timer. It is called by ScheduleDownload(), and exposed - // as a separate method for mocking out in tests. - virtual void StartDownloadTimer(base::TimeDelta interval); - - // Returns the |AutofillMetrics| instance that should be used for logging - // Autocheckout whitelist file downloading. - virtual const AutofillMetrics& GetMetricLogger() const; - - // Timer callback indicating it's time to download whitelist from server. - void TriggerDownload(); - - // Used by tests only. - void StopDownloadTimer(); - - const std::vector<std::string>& url_prefixes() const { - return url_prefixes_; - } - - private: - // Implements net::URLFetcherDelegate. - virtual void OnURLFetchComplete(const net::URLFetcher* source) OVERRIDE; - - // Parse whitelist data and build whitelist. - void BuildWhitelist(const std::string& data); - - // A list of whitelisted url prefixes. - std::vector<std::string> url_prefixes_; - - base::OneShotTimer<WhitelistManager> download_timer_; - - // Indicates that the last triggered download hasn't resolved yet. - bool callback_is_pending_; - - // The context for the request. - net::URLRequestContextGetter* context_getter_; // WEAK - - // State of the kEnableExperimentalFormFilling flag. - const bool experimental_form_filling_enabled_; - - // State of the kBypassAutocheckoutWhitelist flag. - const bool bypass_autocheckout_whitelist_; - - // Exponential back-off delay to retry a failed download. - net::BackoffEntry retry_entry_; - - // Logger for UMA metrics. - AutofillMetrics metrics_logger_; - - // When the whitelist download started. Used to track download latency. - base::Time request_started_timestamp_; - - // The request object. - scoped_ptr<net::URLFetcher> request_; - - DISALLOW_COPY_AND_ASSIGN(WhitelistManager); -}; - -} // namespace autocheckout -} // namespace autofill - -#endif // COMPONENTS_AUTOFILL_CONTENT_BROWSER_AUTOCHECKOUT_WHITELIST_MANAGER_H_ - diff --git a/chromium/components/autofill/content/browser/autocheckout/whitelist_manager_unittest.cc b/chromium/components/autofill/content/browser/autocheckout/whitelist_manager_unittest.cc deleted file mode 100644 index 304d8bf02b8..00000000000 --- a/chromium/components/autofill/content/browser/autocheckout/whitelist_manager_unittest.cc +++ /dev/null @@ -1,309 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include <cmath> - -#include "base/command_line.h" -#include "base/format_macros.h" -#include "base/memory/scoped_ptr.h" -#include "base/message_loop/message_loop.h" -#include "base/strings/stringprintf.h" -#include "components/autofill/content/browser/autocheckout/whitelist_manager.h" -#include "components/autofill/core/browser/autofill_metrics.h" -#include "components/autofill/core/common/autofill_switches.h" -#include "content/public/test/test_browser_thread_bundle.h" -#include "net/base/net_errors.h" -#include "net/http/http_status_code.h" -#include "net/url_request/test_url_fetcher_factory.h" -#include "net/url_request/url_fetcher_delegate.h" -#include "net/url_request/url_request_status.h" -#include "testing/gmock/include/gmock/gmock.h" -#include "testing/gtest/include/gtest/gtest.h" -#include "url/gurl.h" - -namespace { - -const int64 kTestDownloadInterval = 3; // 3 seconds - -// First 4 retry delays are 3, 18, 108, 648 seconds, and following retries -// capped at one hour. -const int64 kBackoffDelaysInMs[] = { - 3000, 18000, 108000, 648000, 3600000, 3600000 }; - -const char kDownloadWhitelistResponse[] = - "https://www.merchant1.com/checkout/\n" - "https://cart.merchant2.com/"; - -} // namespace - -namespace autofill { -namespace autocheckout { - -class WhitelistManagerTest; - -class MockAutofillMetrics : public AutofillMetrics { - public: - MockAutofillMetrics() {} - MOCK_CONST_METHOD2(LogAutocheckoutWhitelistDownloadDuration, - void(const base::TimeDelta& duration, - AutofillMetrics::AutocheckoutWhitelistDownloadStatus)); - private: - DISALLOW_COPY_AND_ASSIGN(MockAutofillMetrics); -}; - -class TestWhitelistManager : public WhitelistManager { - public: - TestWhitelistManager() - : WhitelistManager(), - did_start_download_timer_(false) {} - - virtual void ScheduleDownload(base::TimeDelta interval) OVERRIDE { - did_start_download_timer_ = false; - download_interval_ = interval; - return WhitelistManager::ScheduleDownload(interval); - } - - virtual void StartDownloadTimer(base::TimeDelta interval) OVERRIDE { - WhitelistManager::StartDownloadTimer(interval); - did_start_download_timer_ = true; - } - - bool did_start_download_timer() const { - return did_start_download_timer_; - } - - void TriggerDownload() { - WhitelistManager::TriggerDownload(); - } - - void StopDownloadTimer() { - WhitelistManager::StopDownloadTimer(); - } - - const base::TimeDelta& download_interval() const { - return download_interval_; - } - - const std::vector<std::string>& url_prefixes() const { - return WhitelistManager::url_prefixes(); - } - - virtual const AutofillMetrics& GetMetricLogger() const OVERRIDE { - return mock_metrics_logger_; - } - - private: - bool did_start_download_timer_; - base::TimeDelta download_interval_; - - MockAutofillMetrics mock_metrics_logger_; - - DISALLOW_COPY_AND_ASSIGN(TestWhitelistManager); -}; - -class WhitelistManagerTest : public testing::Test { - public: - WhitelistManagerTest() - : thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP) {} - - protected: - void CreateWhitelistManager() { - if (!whitelist_manager_.get()) { - whitelist_manager_.reset(new TestWhitelistManager()); - } - } - - void DownloadWhitelist(int response_code, const std::string& response) { - // Create and register factory. - net::TestURLFetcherFactory factory; - - CreateWhitelistManager(); - - AutofillMetrics::AutocheckoutWhitelistDownloadStatus status; - if (response_code == net::HTTP_OK) - status = AutofillMetrics::AUTOCHECKOUT_WHITELIST_DOWNLOAD_SUCCEEDED; - else - status = AutofillMetrics::AUTOCHECKOUT_WHITELIST_DOWNLOAD_FAILED; - EXPECT_CALL( - static_cast<const MockAutofillMetrics&>( - whitelist_manager_->GetMetricLogger()), - LogAutocheckoutWhitelistDownloadDuration(testing::_, status)).Times(1); - - whitelist_manager_->TriggerDownload(); - net::TestURLFetcher* fetcher = factory.GetFetcherByID(0); - ASSERT_TRUE(fetcher); - fetcher->set_response_code(response_code); - fetcher->SetResponseString(response); - fetcher->delegate()->OnURLFetchComplete(fetcher); - } - - protected: - scoped_ptr<TestWhitelistManager> whitelist_manager_; - - private: - content::TestBrowserThreadBundle thread_bundle_; -}; - -TEST_F(WhitelistManagerTest, DownloadWhitelist) { - CommandLine::ForCurrentProcess()->AppendSwitch( - switches::kEnableExperimentalFormFilling); - DownloadWhitelist(net::HTTP_OK, kDownloadWhitelistResponse); - ASSERT_EQ(2U, whitelist_manager_->url_prefixes().size()); - EXPECT_EQ("https://www.merchant1.com/checkout/", - whitelist_manager_->url_prefixes()[0]); - EXPECT_EQ("https://cart.merchant2.com/", - whitelist_manager_->url_prefixes()[1]); -} - -TEST_F(WhitelistManagerTest, DoNotDownloadWhitelistWhenSwitchIsOff) { - CreateWhitelistManager(); - whitelist_manager_->ScheduleDownload( - base::TimeDelta::FromSeconds(kTestDownloadInterval)); - EXPECT_FALSE(whitelist_manager_->did_start_download_timer()); -} - -TEST_F(WhitelistManagerTest, DoNotDownloadWhitelistIfAlreadyScheduled) { - CommandLine::ForCurrentProcess()->AppendSwitch( - switches::kEnableExperimentalFormFilling); - CreateWhitelistManager(); - // First attempt should schedule a download. - whitelist_manager_->ScheduleDownload( - base::TimeDelta::FromSeconds(kTestDownloadInterval)); - EXPECT_TRUE(whitelist_manager_->did_start_download_timer()); - // Second attempt should NOT schedule a download while there is already one. - whitelist_manager_->ScheduleDownload( - base::TimeDelta::FromSeconds(kTestDownloadInterval)); - EXPECT_FALSE(whitelist_manager_->did_start_download_timer()); - // It should schedule a new download when not in backoff mode. - whitelist_manager_->StopDownloadTimer(); - whitelist_manager_->ScheduleDownload( - base::TimeDelta::FromSeconds(kTestDownloadInterval)); - EXPECT_TRUE(whitelist_manager_->did_start_download_timer()); -} - -TEST_F(WhitelistManagerTest, DownloadWhitelistFailed) { - CommandLine::ForCurrentProcess()->AppendSwitch( - switches::kEnableExperimentalFormFilling); - DownloadWhitelist(net::HTTP_INTERNAL_SERVER_ERROR, - kDownloadWhitelistResponse); - EXPECT_EQ(0U, whitelist_manager_->url_prefixes().size()); - - whitelist_manager_->StopDownloadTimer(); - DownloadWhitelist(net::HTTP_OK, kDownloadWhitelistResponse); - EXPECT_EQ(2U, whitelist_manager_->url_prefixes().size()); - - whitelist_manager_->StopDownloadTimer(); - DownloadWhitelist(net::HTTP_INTERNAL_SERVER_ERROR, - kDownloadWhitelistResponse); - EXPECT_EQ(2U, whitelist_manager_->url_prefixes().size()); -} - -TEST_F(WhitelistManagerTest, DownloadWhitelistRetry) { - CommandLine::ForCurrentProcess()->AppendSwitch( - switches::kEnableExperimentalFormFilling); - - for (size_t i = 0; i < arraysize(kBackoffDelaysInMs); ++i) { - DownloadWhitelist(net::HTTP_INTERNAL_SERVER_ERROR, - kDownloadWhitelistResponse); - SCOPED_TRACE(base::StringPrintf("Testing retry %" PRIuS - ", expecting delay: %" PRId64, - i, - kBackoffDelaysInMs[i])); - EXPECT_EQ( - kBackoffDelaysInMs[i], - whitelist_manager_->download_interval().InMillisecondsRoundedUp()); - } -} - -TEST_F(WhitelistManagerTest, GetMatchedURLPrefix) { - CommandLine::ForCurrentProcess()->AppendSwitch( - switches::kEnableExperimentalFormFilling); - DownloadWhitelist(net::HTTP_OK, kDownloadWhitelistResponse); - EXPECT_EQ(2U, whitelist_manager_->url_prefixes().size()); - - // Empty url. - EXPECT_EQ(std::string(), - whitelist_manager_->GetMatchedURLPrefix(GURL(std::string()))); - EXPECT_EQ(std::string(), - whitelist_manager_->GetMatchedURLPrefix(GURL())); - - // Positive tests. - EXPECT_EQ("https://www.merchant1.com/checkout/", - whitelist_manager_->GetMatchedURLPrefix( - GURL("https://www.merchant1.com/checkout/"))); - EXPECT_EQ("https://www.merchant1.com/checkout/", - whitelist_manager_->GetMatchedURLPrefix( - GURL("https://www.merchant1.com/checkout/Shipping"))); - EXPECT_EQ("https://www.merchant1.com/checkout/", - whitelist_manager_->GetMatchedURLPrefix( - GURL("https://www.merchant1.com/checkout/?a=b&c=d"))); - EXPECT_EQ("https://cart.merchant2.com/", - whitelist_manager_->GetMatchedURLPrefix( - GURL("https://cart.merchant2.com/"))); - EXPECT_EQ("https://cart.merchant2.com/", - whitelist_manager_->GetMatchedURLPrefix( - GURL("https://cart.merchant2.com/ShippingInfo"))); - EXPECT_EQ("https://cart.merchant2.com/", - whitelist_manager_->GetMatchedURLPrefix( - GURL("https://cart.merchant2.com/ShippingInfo?a=b&c=d"))); - - // Negative tests. - EXPECT_EQ(std::string(), - whitelist_manager_->GetMatchedURLPrefix( - GURL("https://www.merchant1.com/checkout"))); - EXPECT_EQ(std::string(), - whitelist_manager_->GetMatchedURLPrefix( - GURL("https://www.merchant1.com/"))); - EXPECT_EQ(std::string(), - whitelist_manager_->GetMatchedURLPrefix( - GURL("https://www.merchant1.com/Building"))); - EXPECT_EQ(std::string(), - whitelist_manager_->GetMatchedURLPrefix( - GURL("https://www.merchant2.com/cart"))); - EXPECT_EQ(std::string(), - whitelist_manager_->GetMatchedURLPrefix( - GURL("a random string"))); - - // Test different cases in schema, host and path. - EXPECT_EQ(std::string(), - whitelist_manager_->GetMatchedURLPrefix( - GURL("http://www.merchant1.com/checkout/"))); - EXPECT_EQ(std::string(), - whitelist_manager_->GetMatchedURLPrefix( - GURL("www.merchant1.com/checkout/"))); - EXPECT_EQ("https://www.merchant1.com/checkout/", - whitelist_manager_->GetMatchedURLPrefix( - GURL("https://www.Merchant1.com/checkout/"))); - EXPECT_EQ(std::string(), - whitelist_manager_->GetMatchedURLPrefix( - GURL("https://www.merchant1.com/CheckOut/"))); -} - -TEST_F(WhitelistManagerTest, BypassWhitelist) { - CommandLine::ForCurrentProcess()->AppendSwitch( - switches::kEnableExperimentalFormFilling); - CommandLine::ForCurrentProcess()->AppendSwitch( - switches::kBypassAutocheckoutWhitelist); - DownloadWhitelist(net::HTTP_OK, kDownloadWhitelistResponse); - EXPECT_EQ(2U, whitelist_manager_->url_prefixes().size()); - - // Empty url. - EXPECT_EQ(std::string(), - whitelist_manager_->GetMatchedURLPrefix(GURL(std::string()))); - // Positive tests. - EXPECT_EQ("https://www.merchant1.com/checkout/", - whitelist_manager_->GetMatchedURLPrefix( - GURL("https://www.merchant1.com/checkout/"))); - EXPECT_EQ("https://cart.merchant2.com/", - whitelist_manager_->GetMatchedURLPrefix( - GURL("https://cart.merchant2.com/ShippingInfo?a=b&c=d"))); - // Bypass other urls. - EXPECT_EQ(std::string("https://bypass.me/"), - whitelist_manager_->GetMatchedURLPrefix( - GURL("https://bypass.me/"))); -} - -} // namespace autocheckout -} // namespace autofill - diff --git a/chromium/components/autofill/content/browser/autocheckout_manager.cc b/chromium/components/autofill/content/browser/autocheckout_manager.cc deleted file mode 100644 index be8192362e8..00000000000 --- a/chromium/components/autofill/content/browser/autocheckout_manager.cc +++ /dev/null @@ -1,581 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "components/autofill/content/browser/autocheckout_manager.h" - -#include "base/basictypes.h" -#include "base/bind.h" -#include "base/strings/utf_string_conversions.h" -#include "components/autofill/content/browser/autocheckout_request_manager.h" -#include "components/autofill/content/browser/autocheckout_statistic.h" -#include "components/autofill/content/browser/autocheckout_steps.h" -#include "components/autofill/core/browser/autofill_country.h" -#include "components/autofill/core/browser/autofill_field.h" -#include "components/autofill/core/browser/autofill_manager.h" -#include "components/autofill/core/browser/autofill_metrics.h" -#include "components/autofill/core/browser/autofill_profile.h" -#include "components/autofill/core/browser/autofill_type.h" -#include "components/autofill/core/browser/credit_card.h" -#include "components/autofill/core/browser/form_structure.h" -#include "components/autofill/core/common/autofill_messages.h" -#include "components/autofill/core/common/form_data.h" -#include "components/autofill/core/common/form_field_data.h" -#include "components/autofill/core/common/web_element_descriptor.h" -#include "content/public/browser/browser_context.h" -#include "content/public/browser/browser_thread.h" -#include "content/public/browser/render_view_host.h" -#include "content/public/browser/web_contents.h" -#include "net/cookies/cookie_options.h" -#include "net/cookies/cookie_store.h" -#include "net/url_request/url_request_context.h" -#include "net/url_request/url_request_context_getter.h" -#include "ui/gfx/rect.h" -#include "url/gurl.h" - -using content::RenderViewHost; -using content::WebContents; - -namespace autofill { - -namespace { - -const char kGoogleAccountsUrl[] = "https://accounts.google.com/"; - -// Build FormFieldData based on the supplied |autocomplete_attribute|. Will -// fill rest of properties with default values. -FormFieldData BuildField(const std::string& autocomplete_attribute) { - FormFieldData field; - field.name = base::string16(); - field.value = base::string16(); - field.autocomplete_attribute = autocomplete_attribute; - field.form_control_type = "text"; - return field; -} - -// Build Autocheckout specific form data to be consumed by -// AutofillDialogController to show the Autocheckout specific UI. -FormData BuildAutocheckoutFormData() { - FormData formdata; - formdata.fields.push_back(BuildField("email")); - formdata.fields.push_back(BuildField("cc-name")); - formdata.fields.push_back(BuildField("cc-number")); - formdata.fields.push_back(BuildField("cc-exp-month")); - formdata.fields.push_back(BuildField("cc-exp-year")); - formdata.fields.push_back(BuildField("cc-csc")); - formdata.fields.push_back(BuildField("billing address-line1")); - formdata.fields.push_back(BuildField("billing address-line2")); - formdata.fields.push_back(BuildField("billing locality")); - formdata.fields.push_back(BuildField("billing region")); - formdata.fields.push_back(BuildField("billing country")); - formdata.fields.push_back(BuildField("billing postal-code")); - formdata.fields.push_back(BuildField("billing tel")); - formdata.fields.push_back(BuildField("shipping name")); - formdata.fields.push_back(BuildField("shipping address-line1")); - formdata.fields.push_back(BuildField("shipping address-line2")); - formdata.fields.push_back(BuildField("shipping locality")); - formdata.fields.push_back(BuildField("shipping region")); - formdata.fields.push_back(BuildField("shipping country")); - formdata.fields.push_back(BuildField("shipping postal-code")); - formdata.fields.push_back(BuildField("shipping tel")); - return formdata; -} - -AutofillMetrics::AutocheckoutBuyFlowMetric AutocheckoutStatusToUmaMetric( - AutocheckoutStatus status) { - switch (status) { - case SUCCESS: - return AutofillMetrics::AUTOCHECKOUT_BUY_FLOW_SUCCESS; - case MISSING_FIELDMAPPING: - return AutofillMetrics::AUTOCHECKOUT_BUY_FLOW_MISSING_FIELDMAPPING; - case MISSING_CLICK_ELEMENT_BEFORE_FORM_FILLING: - return AutofillMetrics:: - AUTOCHECKOUT_BUY_FLOW_MISSING_CLICK_ELEMENT_BEFORE_FORM_FILLING; - case MISSING_CLICK_ELEMENT_AFTER_FORM_FILLING: - return AutofillMetrics:: - AUTOCHECKOUT_BUY_FLOW_MISSING_CLICK_ELEMENT_AFTER_FORM_FILLING; - case MISSING_ADVANCE: - return AutofillMetrics::AUTOCHECKOUT_BUY_FLOW_MISSING_ADVANCE_ELEMENT; - case CANNOT_PROCEED: - return AutofillMetrics::AUTOCHECKOUT_BUY_FLOW_CANNOT_PROCEED; - case AUTOCHECKOUT_STATUS_NUM_STATUS: - NOTREACHED(); - } - - NOTREACHED(); - return AutofillMetrics::NUM_AUTOCHECKOUT_BUY_FLOW_METRICS; -} - -// Callback for retrieving Google Account cookies. |callback| is passed the -// retrieved cookies and posted back to the UI thread. |cookies| is any Google -// Account cookies. -void GetGoogleCookiesCallback( - const base::Callback<void(const std::string&)>& callback, - const std::string& cookies) { - content::BrowserThread::PostTask(content::BrowserThread::UI, - FROM_HERE, - base::Bind(callback, cookies)); -} - -// Gets Google Account cookies. Must be called on the IO thread. -// |request_context_getter| is a getter for the current request context. -// |callback| is called when retrieving cookies is completed. -void GetGoogleCookies( - scoped_refptr<net::URLRequestContextGetter> request_context_getter, - const base::Callback<void(const std::string&)>& callback) { - net::URLRequestContext* url_request_context = - request_context_getter->GetURLRequestContext(); - if (!url_request_context) - return; - - net::CookieStore* cookie_store = url_request_context->cookie_store(); - - base::Callback<void(const std::string&)> cookie_callback = base::Bind( - &GetGoogleCookiesCallback, - callback); - - net::CookieOptions cookie_options; - cookie_options.set_include_httponly(); - cookie_store->GetCookiesWithOptionsAsync(GURL(kGoogleAccountsUrl), - cookie_options, - cookie_callback); -} - -bool IsBillingGroup(FieldTypeGroup group) { - return group == ADDRESS_BILLING || - group == PHONE_BILLING || - group == NAME_BILLING; -} - -const char kTransactionIdNotSet[] = "transaction id not set"; - -} // namespace - -AutocheckoutManager::AutocheckoutManager(AutofillManager* autofill_manager) - : autofill_manager_(autofill_manager), - metric_logger_(new AutofillMetrics), - should_show_bubble_(true), - is_autocheckout_bubble_showing_(false), - in_autocheckout_flow_(false), - should_preserve_dialog_(false), - google_transaction_id_(kTransactionIdNotSet), - weak_ptr_factory_(this) {} - -AutocheckoutManager::~AutocheckoutManager() { -} - -void AutocheckoutManager::FillForms() { - // |page_meta_data_| should have been set by OnLoadedPageMetaData. - DCHECK(page_meta_data_); - - // Fill the forms on the page with data given by user. - std::vector<FormData> filled_forms; - const std::vector<FormStructure*>& form_structures = - autofill_manager_->GetFormStructures(); - for (std::vector<FormStructure*>::const_iterator iter = - form_structures.begin(); iter != form_structures.end(); ++iter) { - FormStructure* form_structure = *iter; - form_structure->set_filled_by_autocheckout(true); - FormData form = form_structure->ToFormData(); - DCHECK_EQ(form_structure->field_count(), form.fields.size()); - - for (size_t i = 0; i < form_structure->field_count(); ++i) { - const AutofillField* field = form_structure->field(i); - SetValue(*field, &form.fields[i]); - } - - filled_forms.push_back(form); - } - - // Send filled forms along with proceed descriptor to renderer. - RenderViewHost* host = - autofill_manager_->GetWebContents()->GetRenderViewHost(); - if (!host) - return; - - host->Send(new AutofillMsg_FillFormsAndClick( - host->GetRoutingID(), - filled_forms, - page_meta_data_->click_elements_before_form_fill, - page_meta_data_->click_elements_after_form_fill, - page_meta_data_->proceed_element_descriptor)); - // Record time taken for navigating current page. - RecordTimeTaken(page_meta_data_->current_page_number); -} - -void AutocheckoutManager::OnAutocheckoutPageCompleted( - AutocheckoutStatus status) { - if (!in_autocheckout_flow_) - return; - - DVLOG(2) << "OnAutocheckoutPageCompleted, page_no: " - << page_meta_data_->current_page_number - << " status: " - << status; - - DCHECK_NE(MISSING_FIELDMAPPING, status); - - SetStepProgressForPage( - page_meta_data_->current_page_number, - (status == SUCCESS) ? AUTOCHECKOUT_STEP_COMPLETED : - AUTOCHECKOUT_STEP_FAILED); - - if (page_meta_data_->IsEndOfAutofillableFlow() || status != SUCCESS) - EndAutocheckout(status); -} - -void AutocheckoutManager::OnLoadedPageMetaData( - scoped_ptr<AutocheckoutPageMetaData> page_meta_data) { - scoped_ptr<AutocheckoutPageMetaData> old_meta_data = page_meta_data_.Pass(); - page_meta_data_ = page_meta_data.Pass(); - - // If there is no click element in the last page, then it's the real last page - // of the flow, and the dialog will be closed when the page navigates. - // Otherwise, the dialog should be preserved for the page loaded by the click - // element on the last page of the flow. - // Note, |should_preserve_dialog_| has to be computed at this point because - // |in_autocheckout_flow_| may change after |OnLoadedPageMetaData| is called. - should_preserve_dialog_ = in_autocheckout_flow_ || - (old_meta_data.get() && - old_meta_data->IsEndOfAutofillableFlow() && - old_meta_data->proceed_element_descriptor.retrieval_method != - WebElementDescriptor::NONE); - - // Don't log that the bubble could be displayed if the user entered an - // Autocheckout flow and sees the first page of the flow again due to an - // error. - if (IsStartOfAutofillableFlow() && !in_autocheckout_flow_) { - metric_logger_->LogAutocheckoutBubbleMetric( - AutofillMetrics::BUBBLE_COULD_BE_DISPLAYED); - } - - // On the first page of an Autocheckout flow, when this function is called the - // user won't have opted into the flow yet. - if (!in_autocheckout_flow_) - return; - - AutocheckoutStatus status = SUCCESS; - - // Missing Autofill server results. - if (!page_meta_data_.get()) { - status = MISSING_FIELDMAPPING; - } else if (IsStartOfAutofillableFlow()) { - // Not possible unless Autocheckout failed to proceed. - status = CANNOT_PROCEED; - } else if (!page_meta_data_->IsInAutofillableFlow()) { - // Missing Autocheckout meta data in the Autofill server results. - status = MISSING_FIELDMAPPING; - } else if (page_meta_data_->current_page_number <= - old_meta_data->current_page_number) { - // Not possible unless Autocheckout failed to proceed. - status = CANNOT_PROCEED; - } - - // Encountered an error during the Autocheckout flow, probably to - // do with a problem on the previous page. - if (status != SUCCESS) { - SetStepProgressForPage(old_meta_data->current_page_number, - AUTOCHECKOUT_STEP_FAILED); - EndAutocheckout(status); - return; - } - - SetStepProgressForPage(page_meta_data_->current_page_number, - AUTOCHECKOUT_STEP_STARTED); - - FillForms(); -} - -void AutocheckoutManager::OnFormsSeen() { - should_show_bubble_ = true; -} - -bool AutocheckoutManager::ShouldIgnoreAjax() { - return in_autocheckout_flow_ && page_meta_data_->ignore_ajax; -} - -void AutocheckoutManager::MaybeShowAutocheckoutBubble( - const GURL& frame_url, - const gfx::RectF& bounding_box) { - if (!should_show_bubble_ || - is_autocheckout_bubble_showing_ || - !IsStartOfAutofillableFlow()) - return; - - base::Callback<void(const std::string&)> callback = base::Bind( - &AutocheckoutManager::ShowAutocheckoutBubble, - weak_ptr_factory_.GetWeakPtr(), - frame_url, - bounding_box); - - content::WebContents* web_contents = autofill_manager_->GetWebContents(); - if (!web_contents) - return; - - content::BrowserContext* browser_context = web_contents->GetBrowserContext(); - if(!browser_context) - return; - - scoped_refptr<net::URLRequestContextGetter> request_context = - scoped_refptr<net::URLRequestContextGetter>( - browser_context->GetRequestContext()); - - if (!request_context.get()) - return; - - base::Closure task = base::Bind(&GetGoogleCookies, request_context, callback); - - content::BrowserThread::PostTask(content::BrowserThread::IO, - FROM_HERE, - task); -} - -void AutocheckoutManager::ReturnAutocheckoutData( - const FormStructure* result, - const std::string& google_transaction_id) { - if (!result) { - // When user cancels the dialog, |result| is NULL. - // TODO(): add AutocheckoutStatus.USER_CANCELLED, and call - // EndAutocheckout(USER_CANCELLED) instead. - in_autocheckout_flow_ = false; - return; - } - - latency_statistics_.clear(); - last_step_completion_timestamp_ = base::TimeTicks().Now(); - google_transaction_id_ = google_transaction_id; - in_autocheckout_flow_ = true; - should_preserve_dialog_ = true; - metric_logger_->LogAutocheckoutBuyFlowMetric( - AutofillMetrics::AUTOCHECKOUT_BUY_FLOW_STARTED); - - profile_.reset(new AutofillProfile()); - credit_card_.reset(new CreditCard()); - billing_address_.reset(new AutofillProfile()); - - for (size_t i = 0; i < result->field_count(); ++i) { - const AutofillType& type = result->field(i)->Type(); - const base::string16& value = result->field(i)->value; - ServerFieldType server_type = type.GetStorableType(); - if (server_type == CREDIT_CARD_VERIFICATION_CODE) { - cvv_ = result->field(i)->value; - continue; - } - FieldTypeGroup group = type.group(); - if (group == CREDIT_CARD) { - credit_card_->SetRawInfo(server_type, value); - // TODO(dgwallinga): Find a way of cleanly deprecating CREDIT_CARD_NAME. - // code.google.com/p/chromium/issues/detail?id=263498 - if (server_type == CREDIT_CARD_NAME) - billing_address_->SetRawInfo(NAME_BILLING_FULL, value); - } else if (server_type == ADDRESS_HOME_COUNTRY) { - if (IsBillingGroup(group)) - billing_address_->SetInfo(type, value, autofill_manager_->app_locale()); - else - profile_->SetInfo(type, value, autofill_manager_->app_locale()); - } else if (IsBillingGroup(group)) { - billing_address_->SetRawInfo(server_type, value); - } else { - profile_->SetRawInfo(server_type, value); - } - } - - // Page types only available in first-page meta data, so save - // them for use later as we navigate. - page_types_ = page_meta_data_->page_types; - SetStepProgressForPage(page_meta_data_->current_page_number, - AUTOCHECKOUT_STEP_STARTED); - - FillForms(); -} - -void AutocheckoutManager::set_metric_logger( - scoped_ptr<AutofillMetrics> metric_logger) { - metric_logger_ = metric_logger.Pass(); -} - -void AutocheckoutManager::MaybeShowAutocheckoutDialog( - const GURL& frame_url, - AutocheckoutBubbleState state) { - is_autocheckout_bubble_showing_ = false; - - // User has taken action on the bubble, don't offer bubble again. - if (state != AUTOCHECKOUT_BUBBLE_IGNORED) - should_show_bubble_ = false; - - if (state != AUTOCHECKOUT_BUBBLE_ACCEPTED) - return; - - base::Callback<void(const FormStructure*, const std::string&)> callback = - base::Bind(&AutocheckoutManager::ReturnAutocheckoutData, - weak_ptr_factory_.GetWeakPtr()); - autofill_manager_->ShowRequestAutocompleteDialog(BuildAutocheckoutFormData(), - frame_url, - DIALOG_TYPE_AUTOCHECKOUT, - callback); - - for (std::map<int, std::vector<AutocheckoutStepType> >::const_iterator - it = page_meta_data_->page_types.begin(); - it != page_meta_data_->page_types.end(); ++it) { - for (size_t i = 0; i < it->second.size(); ++i) { - autofill_manager_->delegate()->AddAutocheckoutStep(it->second[i]); - } - } -} - -void AutocheckoutManager::ShowAutocheckoutBubble( - const GURL& frame_url, - const gfx::RectF& bounding_box, - const std::string& cookies) { - DCHECK(thread_checker_.CalledOnValidThread()); - - base::Callback<void(AutocheckoutBubbleState)> callback = base::Bind( - &AutocheckoutManager::MaybeShowAutocheckoutDialog, - weak_ptr_factory_.GetWeakPtr(), - frame_url); - is_autocheckout_bubble_showing_ = - autofill_manager_->delegate()->ShowAutocheckoutBubble( - bounding_box, - cookies.find("LSID") != std::string::npos, - callback); -} - -bool AutocheckoutManager::IsStartOfAutofillableFlow() const { - return page_meta_data_ && page_meta_data_->IsStartOfAutofillableFlow(); -} - -bool AutocheckoutManager::IsInAutofillableFlow() const { - return page_meta_data_ && page_meta_data_->IsInAutofillableFlow(); -} - -void AutocheckoutManager::SetValue(const AutofillField& field, - FormFieldData* field_to_fill) { - // No-op if Autofill server doesn't know about the field. - if (field.server_type() == NO_SERVER_DATA) - return; - - const AutofillType& type = field.Type(); - - ServerFieldType server_type = type.GetStorableType(); - if (server_type == FIELD_WITH_DEFAULT_VALUE) { - // For a form with radio buttons, like: - // <form> - // <input type="radio" name="sex" value="male">Male<br> - // <input type="radio" name="sex" value="female">Female - // </form> - // If the default value specified at the server is "female", then - // Autofill server responds back with following field mappings - // (fieldtype: FIELD_WITH_DEFAULT_VALUE, value: "female") - // (fieldtype: FIELD_WITH_DEFAULT_VALUE, value: "female") - // Note that, the field mapping is repeated twice to respond to both the - // input elements with the same name/signature in the form. - // - // FIELD_WITH_DEFAULT_VALUE can also be used for selects, the correspondent - // example of the radio buttons example above is: - // <SELECT name="sex"> - // <OPTION value="female">Female</OPTION> - // <OPTION value="male">Male</OPTION> - // </SELECT> - base::string16 default_value = UTF8ToUTF16(field.default_value()); - if (field.is_checkable) { - // Mark the field checked if server says the default value of the field - // to be this field's value. - field_to_fill->is_checked = (field.value == default_value); - } else if (field.form_control_type == "select-one") { - field_to_fill->value = default_value; - } else { - // FIELD_WITH_DEFAULT_VALUE should not be used for other type of fields. - NOTREACHED(); - } - return; - } - - // Handle verification code directly. - if (server_type == CREDIT_CARD_VERIFICATION_CODE) { - field_to_fill->value = cvv_; - return; - } - - if (type.group() == CREDIT_CARD) { - credit_card_->FillFormField( - field, 0, autofill_manager_->app_locale(), field_to_fill); - } else if (IsBillingGroup(type.group())) { - billing_address_->FillFormField( - field, 0, autofill_manager_->app_locale(), field_to_fill); - } else { - profile_->FillFormField( - field, 0, autofill_manager_->app_locale(), field_to_fill); - } -} - -void AutocheckoutManager::SendAutocheckoutStatus(AutocheckoutStatus status) { - // To ensure stale data isn't being sent. - DCHECK_NE(kTransactionIdNotSet, google_transaction_id_); - - AutocheckoutRequestManager::CreateForBrowserContext( - autofill_manager_->GetWebContents()->GetBrowserContext()); - AutocheckoutRequestManager* autocheckout_request_manager = - AutocheckoutRequestManager::FromBrowserContext( - autofill_manager_->GetWebContents()->GetBrowserContext()); - // It is assumed that the domain Autocheckout starts on does not change - // during the flow. If this proves to be incorrect, the |source_url| from - // AutofillDialogControllerImpl will need to be provided in its callback in - // addition to the Google transaction id. - autocheckout_request_manager->SendAutocheckoutStatus( - status, - autofill_manager_->GetWebContents()->GetURL(), - latency_statistics_, - google_transaction_id_); - - // Log the result of this Autocheckout flow to UMA. - metric_logger_->LogAutocheckoutBuyFlowMetric( - AutocheckoutStatusToUmaMetric(status)); - - google_transaction_id_ = kTransactionIdNotSet; -} - -void AutocheckoutManager::SetStepProgressForPage( - int page_number, - AutocheckoutStepStatus status) { - if (page_types_.count(page_number) == 1) { - for (size_t i = 0; i < page_types_[page_number].size(); ++i) { - autofill_manager_->delegate()->UpdateAutocheckoutStep( - page_types_[page_number][i], status); - } - } -} - -void AutocheckoutManager::RecordTimeTaken(int page_number) { - AutocheckoutStatistic statistic; - statistic.page_number = page_number; - if (page_types_.count(page_number) == 1) { - for (size_t i = 0; i < page_types_[page_number].size(); ++i) { - statistic.steps.push_back(page_types_[page_number][i]); - } - } - - statistic.time_taken = - base::TimeTicks().Now() - last_step_completion_timestamp_; - latency_statistics_.push_back(statistic); - - // Reset timestamp. - last_step_completion_timestamp_ = base::TimeTicks().Now(); -} - -void AutocheckoutManager::EndAutocheckout(AutocheckoutStatus status) { - DCHECK(in_autocheckout_flow_); - - DVLOG(2) << "EndAutocheckout at step: " - << page_meta_data_->current_page_number - << " with status: " - << status; - - SendAutocheckoutStatus(status); - if (status == SUCCESS) - autofill_manager_->delegate()->OnAutocheckoutSuccess(); - else - autofill_manager_->delegate()->OnAutocheckoutError(); - in_autocheckout_flow_ = false; -} - -} // namespace autofill diff --git a/chromium/components/autofill/content/browser/autocheckout_manager.h b/chromium/components/autofill/content/browser/autocheckout_manager.h deleted file mode 100644 index b265d8e2ec4..00000000000 --- a/chromium/components/autofill/content/browser/autocheckout_manager.h +++ /dev/null @@ -1,191 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef COMPONENTS_AUTOFILL_CONTENT_BROWSER_AUTOCHECKOUT_MANAGER_H_ -#define COMPONENTS_AUTOFILL_CONTENT_BROWSER_AUTOCHECKOUT_MANAGER_H_ - -#include <string> - -#include "base/callback_forward.h" -#include "base/memory/scoped_ptr.h" -#include "base/memory/weak_ptr.h" -#include "base/strings/string16.h" -#include "base/threading/thread_checker.h" -#include "base/time/time.h" -#include "components/autofill/content/browser/autocheckout_page_meta_data.h" -#include "components/autofill/content/browser/autocheckout_statistic.h" -#include "components/autofill/core/browser/autocheckout_bubble_state.h" -#include "components/autofill/core/common/autocheckout_status.h" - -class GURL; - -namespace gfx { -class RectF; -} - -namespace net { -class URLRequestContextGetter; -} - -namespace autofill { - -class AutofillField; -class AutofillManager; -class AutofillMetrics; -class AutofillProfile; -class CreditCard; -class FormStructure; - -struct FormData; -struct FormFieldData; - -class AutocheckoutManager { - public: - explicit AutocheckoutManager(AutofillManager* autofill_manager); - virtual ~AutocheckoutManager(); - - // Fill all the forms seen by the Autofill manager with the information - // gathered from the requestAutocomplete dialog. - void FillForms(); - - // Called to signal that the renderer has completed processing a page in the - // Autocheckout flow. |status| is the reason for the failure, or |SUCCESS| if - // there were no errors. - void OnAutocheckoutPageCompleted(AutocheckoutStatus status); - - // Sets |page_meta_data_| with the meta data for the current page. - void OnLoadedPageMetaData( - scoped_ptr<AutocheckoutPageMetaData> page_meta_data); - - // Called when a page containing forms is loaded. - void OnFormsSeen(); - - // Whether ajax on the current page should be ignored during - // an Autocheckout flow. - bool ShouldIgnoreAjax(); - - // Causes the Autocheckout bubble to be displayed if the user hasn't seen it - // yet for the current page. |frame_url| is the page where Autocheckout is - // being initiated. |bounding_box| is the bounding box of the input field in - // focus. - virtual void MaybeShowAutocheckoutBubble(const GURL& frame_url, - const gfx::RectF& bounding_box); - - // Determine whether we should keep the dialog visible. - bool should_preserve_dialog() const { return should_preserve_dialog_; } - - void set_should_show_bubble(bool should_show_bubble) { - should_show_bubble_ = should_show_bubble; - } - - bool is_autocheckout_bubble_showing() const { - return is_autocheckout_bubble_showing_; - } - - protected: - // Exposed for testing. - bool in_autocheckout_flow() const { return in_autocheckout_flow_; } - - // Exposed for testing. - bool should_show_bubble() const { return should_show_bubble_; } - - // Show the requestAutocomplete dialog if |state| is - // AUTOCHECKOUT_BUBBLE_ACCEPTED. Also, does bookkeeping for whether or not - // the bubble is showing. - virtual void MaybeShowAutocheckoutDialog(const GURL& frame_url, - AutocheckoutBubbleState state); - - // Callback called from AutofillDialogController on filling up the UI form. - void ReturnAutocheckoutData(const FormStructure* result, - const std::string& google_transaction_id); - - const AutofillMetrics& metric_logger() const { return *metric_logger_; } - void set_metric_logger(scoped_ptr<AutofillMetrics> metric_logger); - - private: - // Shows the Autocheckout bubble. Must be called on the UI thread. |frame_url| - // is the page where Autocheckout is being initiated. |bounding_box| is the - // bounding box of the input field in focus. |cookies| is any Google Account - // cookies. - void ShowAutocheckoutBubble(const GURL& frame_url, - const gfx::RectF& bounding_box, - const std::string& cookies); - - // Whether or not the current page is the start of a multipage Autofill flow. - bool IsStartOfAutofillableFlow() const; - - // Whether or not the current page is part of a multipage Autofill flow. - bool IsInAutofillableFlow() const; - - // Sends |status| to Online Wallet using AutocheckoutRequestManager. - void SendAutocheckoutStatus(AutocheckoutStatus status); - - // Sets value of form field data |field_to_fill| based on the Autofill - // field type specified by |field|. - void SetValue(const AutofillField& field, FormFieldData* field_to_fill); - - // Sets the progress of all steps for the given page to the provided value. - void SetStepProgressForPage(int page_number, AutocheckoutStepStatus status); - - // Account time spent between now and |last_step_completion_timestamp_| - // towards |page_number|. - void RecordTimeTaken(int page_number); - - // Terminate the Autocheckout flow and send Autocheckout status to Wallet - // server. - void EndAutocheckout(AutocheckoutStatus status); - - AutofillManager* autofill_manager_; // WEAK; owns us - - // Credit card verification code. - base::string16 cvv_; - - // Profile built using the data supplied by requestAutocomplete dialog. - scoped_ptr<AutofillProfile> profile_; - - // Credit card built using the data supplied by requestAutocomplete dialog. - scoped_ptr<CreditCard> credit_card_; - - // Billing address built using data supplied by requestAutocomplete dialog. - scoped_ptr<AutofillProfile> billing_address_; - - // Autocheckout specific page meta data of current page. - scoped_ptr<AutocheckoutPageMetaData> page_meta_data_; - - scoped_ptr<AutofillMetrics> metric_logger_; - - // Whether or not the Autocheckout bubble should be shown to user. - bool should_show_bubble_; - - // Whether or not the Autocheckout bubble is being displayed to the user. - bool is_autocheckout_bubble_showing_; - - // Whether or not the user is in an Autocheckout flow. - bool in_autocheckout_flow_; - - // Whether or not the currently visible dialog, if there is one, should be - // preserved. - bool should_preserve_dialog_; - - // AutocheckoutStepTypes for the various pages of the flow. - std::map<int, std::vector<AutocheckoutStepType> > page_types_; - - // Timestamp of last step's completion. - base::TimeTicks last_step_completion_timestamp_; - - // Per page latency statistics. - std::vector<AutocheckoutStatistic> latency_statistics_; - - std::string google_transaction_id_; - - base::WeakPtrFactory<AutocheckoutManager> weak_ptr_factory_; - - base::ThreadChecker thread_checker_; - - DISALLOW_COPY_AND_ASSIGN(AutocheckoutManager); -}; - -} // namespace autofill - -#endif // COMPONENTS_AUTOFILL_CONTENT_BROWSER_AUTOCHECKOUT_MANAGER_H_ diff --git a/chromium/components/autofill/content/browser/autocheckout_manager_unittest.cc b/chromium/components/autofill/content/browser/autocheckout_manager_unittest.cc deleted file mode 100644 index c8e425566ee..00000000000 --- a/chromium/components/autofill/content/browser/autocheckout_manager_unittest.cc +++ /dev/null @@ -1,949 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include <map> - -#include "base/strings/utf_string_conversions.h" -#include "base/tuple.h" -#include "chrome/test/base/chrome_render_view_host_test_harness.h" -#include "components/autofill/content/browser/autocheckout_manager.h" -#include "components/autofill/core/browser/autofill_common_test.h" -#include "components/autofill/core/browser/autofill_manager.h" -#include "components/autofill/core/browser/autofill_metrics.h" -#include "components/autofill/core/browser/form_structure.h" -#include "components/autofill/core/browser/test_autofill_driver.h" -#include "components/autofill/core/browser/test_autofill_manager_delegate.h" -#include "components/autofill/core/common/autofill_messages.h" -#include "components/autofill/core/common/form_data.h" -#include "content/public/browser/browser_thread.h" -#include "content/public/test/mock_render_process_host.h" -#include "content/public/test/test_browser_thread.h" -#include "content/public/test/test_utils.h" -#include "ipc/ipc_test_sink.h" -#include "testing/gmock/include/gmock/gmock.h" -#include "testing/gtest/include/gtest/gtest.h" - -using content::BrowserThread; - -namespace autofill { - -namespace { - -typedef Tuple4<std::vector<FormData>, - std::vector<WebElementDescriptor>, - std::vector<WebElementDescriptor>, - WebElementDescriptor> AutofillParam; - -enum ProceedElementPresence { - NO_PROCEED_ELEMENT, - HAS_PROCEED_ELEMENT, -}; - -FormFieldData BuildFieldWithValue( - const std::string& autocomplete_attribute, - const std::string& value) { - FormFieldData field; - field.name = ASCIIToUTF16(autocomplete_attribute); - field.value = ASCIIToUTF16(value); - field.autocomplete_attribute = autocomplete_attribute; - field.form_control_type = "text"; - return field; -} - -FormFieldData BuildField(const std::string& autocomplete_attribute) { - return BuildFieldWithValue(autocomplete_attribute, autocomplete_attribute); -} - -scoped_ptr<FormStructure> CreateTestFormStructure( - const std::vector<ServerFieldType>& autofill_types) { - FormData form; - form.name = ASCIIToUTF16("MyForm"); - form.method = ASCIIToUTF16("POST"); - form.origin = GURL("https://myform.com/form.html"); - form.action = GURL("https://myform.com/submit.html"); - form.user_submitted = true; - - // Add some fields, autocomplete_attribute is not important and we - // fake that server sends authoritative field mappings. - for (size_t i = 0; i < autofill_types.size(); ++i) - form.fields.push_back(BuildField("SomeField")); - - scoped_ptr<FormStructure> form_structure( - new FormStructure(form, std::string())); - - // Set mocked Autofill server field types. - for (size_t i = 0; i < autofill_types.size(); ++i) { - form_structure->field(i)->set_server_type(autofill_types[i]); - // Set heuristic type to make sure that server_types are used and not - // heuritic type. - form_structure->field(i)->set_heuristic_type(CREDIT_CARD_NUMBER); - } - - return form_structure.Pass(); -} - -scoped_ptr<FormStructure> CreateTestAddressFormStructure() { - std::vector<ServerFieldType> autofill_types; - autofill_types.push_back(NAME_FULL); - autofill_types.push_back(PHONE_HOME_WHOLE_NUMBER); - autofill_types.push_back(EMAIL_ADDRESS); - autofill_types.push_back(ADDRESS_HOME_LINE1); - autofill_types.push_back(ADDRESS_HOME_CITY); - autofill_types.push_back(ADDRESS_HOME_STATE); - autofill_types.push_back(ADDRESS_HOME_COUNTRY); - autofill_types.push_back(ADDRESS_HOME_ZIP); - autofill_types.push_back(NO_SERVER_DATA); - return CreateTestFormStructure(autofill_types); -} - -scoped_ptr<FormStructure> CreateTestCreditCardFormStructure() { - std::vector<ServerFieldType> autofill_types; - autofill_types.push_back(CREDIT_CARD_NAME); - autofill_types.push_back(CREDIT_CARD_NUMBER); - autofill_types.push_back(CREDIT_CARD_EXP_MONTH); - autofill_types.push_back(CREDIT_CARD_EXP_4_DIGIT_YEAR); - autofill_types.push_back(CREDIT_CARD_VERIFICATION_CODE); - autofill_types.push_back(ADDRESS_BILLING_LINE1); - autofill_types.push_back(ADDRESS_BILLING_CITY); - autofill_types.push_back(ADDRESS_BILLING_STATE); - autofill_types.push_back(ADDRESS_BILLING_COUNTRY); - autofill_types.push_back(ADDRESS_BILLING_ZIP); - return CreateTestFormStructure(autofill_types); -} - -scoped_ptr<FormStructure> CreateTestFormStructureWithDefaultValues() { - FormData form; - form.name = ASCIIToUTF16("MyForm"); - form.method = ASCIIToUTF16("POST"); - form.origin = GURL("https://myform.com/form.html"); - form.action = GURL("https://myform.com/submit.html"); - form.user_submitted = true; - - // Add two radio button fields. - FormFieldData male = BuildFieldWithValue("sex", "male"); - male.is_checkable = true; - form.fields.push_back(male); - FormFieldData female = BuildFieldWithValue("sex", "female"); - female.is_checkable = true; - form.fields.push_back(female); - FormFieldData select = BuildField("SelectField"); - select.form_control_type = "select-one"; - form.fields.push_back(select); - - scoped_ptr<FormStructure> form_structure( - new FormStructure(form, std::string())); - - // Fake server response. Set all fields as fields with default value. - form_structure->field(0)->set_server_type(FIELD_WITH_DEFAULT_VALUE); - form_structure->field(0)->set_default_value("female"); - form_structure->field(1)->set_server_type(FIELD_WITH_DEFAULT_VALUE); - form_structure->field(1)->set_default_value("female"); - form_structure->field(2)->set_server_type(FIELD_WITH_DEFAULT_VALUE); - form_structure->field(2)->set_default_value("Default Value"); - - return form_structure.Pass(); -} - -void PopulateClickElement(WebElementDescriptor* proceed_element, - const std::string& descriptor) { - proceed_element->descriptor = descriptor; - proceed_element->retrieval_method = WebElementDescriptor::ID; -} - -scoped_ptr<AutocheckoutPageMetaData> CreateStartOfFlowMetaData() { - scoped_ptr<AutocheckoutPageMetaData> start_of_flow( - new AutocheckoutPageMetaData()); - start_of_flow->current_page_number = 0; - start_of_flow->total_pages = 3; - start_of_flow->page_types[0].push_back(AUTOCHECKOUT_STEP_SHIPPING); - start_of_flow->page_types[1].push_back(AUTOCHECKOUT_STEP_DELIVERY); - start_of_flow->page_types[2].push_back(AUTOCHECKOUT_STEP_BILLING); - PopulateClickElement(&start_of_flow->proceed_element_descriptor, "#foo"); - return start_of_flow.Pass(); -} - -scoped_ptr<AutocheckoutPageMetaData> CreateInFlowMetaData() { - scoped_ptr<AutocheckoutPageMetaData> in_flow(new AutocheckoutPageMetaData()); - in_flow->current_page_number = 1; - in_flow->total_pages = 3; - PopulateClickElement(&in_flow->proceed_element_descriptor, "#foo"); - return in_flow.Pass(); -} - -scoped_ptr<AutocheckoutPageMetaData> CreateNotInFlowMetaData() { - scoped_ptr<AutocheckoutPageMetaData> not_in_flow( - new AutocheckoutPageMetaData()); - PopulateClickElement(¬_in_flow->proceed_element_descriptor, "#foo"); - return not_in_flow.Pass(); -} - -scoped_ptr<AutocheckoutPageMetaData> CreateEndOfFlowMetaData( - ProceedElementPresence proceed_element_presence) { - scoped_ptr<AutocheckoutPageMetaData> end_of_flow( - new AutocheckoutPageMetaData()); - end_of_flow->current_page_number = 2; - end_of_flow->total_pages = 3; - if (proceed_element_presence == HAS_PROCEED_ELEMENT) - PopulateClickElement(&end_of_flow->proceed_element_descriptor, "#foo"); - return end_of_flow.Pass(); -} - -scoped_ptr<AutocheckoutPageMetaData> CreateOnePageFlowMetaData( - ProceedElementPresence proceed_element_presence) { - scoped_ptr<AutocheckoutPageMetaData> one_page_flow( - new AutocheckoutPageMetaData()); - one_page_flow->current_page_number = 0; - one_page_flow->total_pages = 1; - one_page_flow->page_types[0].push_back(AUTOCHECKOUT_STEP_SHIPPING); - one_page_flow->page_types[0].push_back(AUTOCHECKOUT_STEP_DELIVERY); - one_page_flow->page_types[0].push_back(AUTOCHECKOUT_STEP_BILLING); - if (proceed_element_presence == HAS_PROCEED_ELEMENT) - PopulateClickElement(&one_page_flow->proceed_element_descriptor, "#foo"); - return one_page_flow.Pass(); -} - -scoped_ptr<AutocheckoutPageMetaData> CreateMissingProceedMetaData() { - scoped_ptr<AutocheckoutPageMetaData> missing_proceed( - new AutocheckoutPageMetaData()); - missing_proceed->current_page_number = 1; - missing_proceed->total_pages = 3; - return missing_proceed.Pass(); -} - -scoped_ptr<AutocheckoutPageMetaData> CreateMultiClickMetaData() { - scoped_ptr<AutocheckoutPageMetaData> metadata(new AutocheckoutPageMetaData()); - metadata->current_page_number = 1; - metadata->total_pages = 3; - PopulateClickElement(&metadata->proceed_element_descriptor, "#foo"); - WebElementDescriptor element; - PopulateClickElement(&element, "#before_form_fill_1"); - metadata->click_elements_before_form_fill.push_back(element); - PopulateClickElement(&element, "#before_form_fill_2"); - metadata->click_elements_before_form_fill.push_back(element); - PopulateClickElement(&element, "#after_form_fill"); - metadata->click_elements_after_form_fill.push_back(element); - return metadata.Pass(); -} - -struct TestField { - const char* const field_type; - const char* const field_value; - ServerFieldType autofill_type; -}; - -const TestField kTestFields[] = { - {"name", "Test User", NAME_FULL}, - {"tel", "650-123-9909", PHONE_HOME_WHOLE_NUMBER}, - {"email", "blah@blah.com", EMAIL_ADDRESS}, - {"cc-name", "Test User", CREDIT_CARD_NAME}, - {"cc-number", "4444444444444448", CREDIT_CARD_NUMBER}, - {"cc-exp-month", "10", CREDIT_CARD_EXP_MONTH}, - {"cc-exp-year", "2020", CREDIT_CARD_EXP_4_DIGIT_YEAR}, - {"cc-csc", "123", CREDIT_CARD_VERIFICATION_CODE}, - {"street-address", "Fake Street", ADDRESS_HOME_LINE1}, - {"locality", "Mocked City", ADDRESS_HOME_CITY}, - {"region", "California", ADDRESS_HOME_STATE}, - {"country", "USA", ADDRESS_HOME_COUNTRY}, - {"postal-code", "49012", ADDRESS_HOME_ZIP}, - {"billing-street-address", "Billing Street", ADDRESS_BILLING_LINE1}, - {"billing-locality", "Billing City", ADDRESS_BILLING_CITY}, - {"billing-region", "BillingState", ADDRESS_BILLING_STATE}, - {"billing-country", "Canada", ADDRESS_BILLING_COUNTRY}, - {"billing-postal-code", "11111", ADDRESS_BILLING_ZIP} -}; - -// Build Autocheckout specific form data to be consumed by -// AutofillDialogController to show the Autocheckout specific UI. -scoped_ptr<FormStructure> FakeUserSubmittedFormStructure() { - FormData formdata; - for (size_t i = 0; i < arraysize(kTestFields); i++) { - formdata.fields.push_back( - BuildFieldWithValue(kTestFields[i].field_type, - kTestFields[i].field_value)); - } - scoped_ptr<FormStructure> form_structure; - form_structure.reset(new FormStructure(formdata, std::string())); - for (size_t i = 0; i < arraysize(kTestFields); ++i) - form_structure->field(i)->set_server_type(kTestFields[i].autofill_type); - - return form_structure.Pass(); -} - -class MockAutofillManagerDelegate : public TestAutofillManagerDelegate { - public: - MockAutofillManagerDelegate() - : request_autocomplete_dialog_open_(false), - autocheckout_bubble_shown_(false), - should_autoclick_bubble_(true) {} - - virtual ~MockAutofillManagerDelegate() {} - - virtual void HideRequestAutocompleteDialog() OVERRIDE { - request_autocomplete_dialog_open_ = false; - } - - MOCK_METHOD0(OnAutocheckoutError, void()); - MOCK_METHOD0(OnAutocheckoutSuccess, void()); - - virtual bool ShowAutocheckoutBubble( - const gfx::RectF& bounds, - bool is_google_user, - const base::Callback<void(AutocheckoutBubbleState)>& callback) OVERRIDE { - autocheckout_bubble_shown_ = true; - if (should_autoclick_bubble_) - callback.Run(AUTOCHECKOUT_BUBBLE_ACCEPTED); - return true; - } - - virtual void ShowRequestAutocompleteDialog( - const FormData& form, - const GURL& source_url, - DialogType dialog_type, - const base::Callback<void(const FormStructure*, - const std::string&)>& callback) OVERRIDE { - request_autocomplete_dialog_open_ = true; - callback.Run(user_supplied_data_.get(), "google_transaction_id"); - } - - virtual void AddAutocheckoutStep(AutocheckoutStepType step_type) OVERRIDE { - if (autocheckout_steps_.count(step_type) == 0) - autocheckout_steps_[step_type] = AUTOCHECKOUT_STEP_UNSTARTED; - } - - virtual void UpdateAutocheckoutStep( - AutocheckoutStepType step_type, - AutocheckoutStepStatus step_status) OVERRIDE { - autocheckout_steps_[step_type] = step_status; - } - - void SetUserSuppliedData(scoped_ptr<FormStructure> user_supplied_data) { - user_supplied_data_.reset(user_supplied_data.release()); - } - - bool autocheckout_bubble_shown() const { - return autocheckout_bubble_shown_; - } - - void set_autocheckout_bubble_shown(bool autocheckout_bubble_shown) { - autocheckout_bubble_shown_ = autocheckout_bubble_shown; - } - - bool request_autocomplete_dialog_open() const { - return request_autocomplete_dialog_open_; - } - - void set_should_autoclick_bubble(bool should_autoclick_bubble) { - should_autoclick_bubble_ = should_autoclick_bubble; - } - - bool AutocheckoutStepExistsWithStatus( - AutocheckoutStepType step_type, - AutocheckoutStepStatus step_status) const { - std::map<AutocheckoutStepType, AutocheckoutStepStatus>::const_iterator it = - autocheckout_steps_.find(step_type); - return it != autocheckout_steps_.end() && it->second == step_status; - } - - private: - // Whether or not ShowRequestAutocompleteDialog method has been called. - bool request_autocomplete_dialog_open_; - - // Whether or not Autocheckout bubble is displayed to user. - bool autocheckout_bubble_shown_; - - // Whether or not to accept the Autocheckout bubble when offered. - bool should_autoclick_bubble_; - - // User specified data that would be returned to AutocheckoutManager when - // dialog is accepted. - scoped_ptr<FormStructure> user_supplied_data_; - - // Step status of various Autocheckout steps in this checkout flow. - std::map<AutocheckoutStepType, AutocheckoutStepStatus> autocheckout_steps_; -}; - -class TestAutofillManager : public AutofillManager { - public: - explicit TestAutofillManager(AutofillDriver* driver, - AutofillManagerDelegate* delegate) - : AutofillManager(driver, delegate, NULL) { - } - virtual ~TestAutofillManager() {} - - void SetFormStructure(scoped_ptr<FormStructure> form_structure) { - form_structures()->clear(); - form_structures()->push_back(form_structure.release()); - } -}; - -class MockAutofillMetrics : public AutofillMetrics { - public: - MockAutofillMetrics() {} - MOCK_CONST_METHOD1(LogAutocheckoutBubbleMetric, void(BubbleMetric)); - MOCK_CONST_METHOD1(LogAutocheckoutBuyFlowMetric, - void(AutocheckoutBuyFlowMetric)); - - private: - DISALLOW_COPY_AND_ASSIGN(MockAutofillMetrics); -}; - -class TestAutocheckoutManager: public AutocheckoutManager { - public: - explicit TestAutocheckoutManager(AutofillManager* autofill_manager) - : AutocheckoutManager(autofill_manager) { - set_metric_logger(scoped_ptr<AutofillMetrics>(new MockAutofillMetrics)); - } - - const MockAutofillMetrics& metric_logger() const { - return static_cast<const MockAutofillMetrics&>( - AutocheckoutManager::metric_logger()); - } - - virtual void MaybeShowAutocheckoutBubble( - const GURL& frame_url, - const gfx::RectF& bounding_box) OVERRIDE { - AutocheckoutManager::MaybeShowAutocheckoutBubble(frame_url, - bounding_box); - // Needed for AutocheckoutManager to post task on IO thread. - content::RunAllPendingInMessageLoop(BrowserThread::IO); - } - - using AutocheckoutManager::in_autocheckout_flow; - using AutocheckoutManager::should_show_bubble; - using AutocheckoutManager::MaybeShowAutocheckoutDialog; - using AutocheckoutManager::ReturnAutocheckoutData; -}; - -} // namespace - -class AutocheckoutManagerTest : public ChromeRenderViewHostTestHarness { - protected: - virtual void SetUp() OVERRIDE { - SetThreadBundleOptions(content::TestBrowserThreadBundle::REAL_IO_THREAD); - ChromeRenderViewHostTestHarness::SetUp(); - autofill_manager_delegate_.reset(new MockAutofillManagerDelegate()); - autofill_driver_.reset(new TestAutofillDriver(web_contents())); - autofill_manager_.reset(new TestAutofillManager( - autofill_driver_.get(), - autofill_manager_delegate_.get())); - autocheckout_manager_.reset( - new TestAutocheckoutManager(autofill_manager_.get())); - } - - virtual void TearDown() OVERRIDE { - autocheckout_manager_.reset(); - autofill_manager_delegate_.reset(); - autofill_manager_.reset(); - autofill_driver_.reset(); - ChromeRenderViewHostTestHarness::TearDown(); - } - - std::vector<FormData> ReadFilledForms() { - uint32 kMsgID = AutofillMsg_FillFormsAndClick::ID; - const IPC::Message* message = - process()->sink().GetFirstMessageMatching(kMsgID); - AutofillParam autofill_param; - AutofillMsg_FillFormsAndClick::Read(message, &autofill_param); - return autofill_param.a; - } - - void CheckFillFormsAndClickIpc( - ProceedElementPresence proceed_element_presence) { - EXPECT_EQ(1U, process()->sink().message_count()); - uint32 kMsgID = AutofillMsg_FillFormsAndClick::ID; - const IPC::Message* message = - process()->sink().GetFirstMessageMatching(kMsgID); - EXPECT_TRUE(message); - AutofillParam autofill_param; - AutofillMsg_FillFormsAndClick::Read(message, &autofill_param); - if (proceed_element_presence == HAS_PROCEED_ELEMENT) { - EXPECT_EQ(WebElementDescriptor::ID, autofill_param.d.retrieval_method); - EXPECT_EQ("#foo", autofill_param.d.descriptor); - } else { - EXPECT_EQ(WebElementDescriptor::NONE, autofill_param.d.retrieval_method); - } - ClearIpcSink(); - } - - void ClearIpcSink() { - process()->sink().ClearMessages(); - } - - void OpenRequestAutocompleteDialog() { - EXPECT_FALSE(autocheckout_manager_->in_autocheckout_flow()); - EXPECT_FALSE( - autofill_manager_delegate_->request_autocomplete_dialog_open()); - EXPECT_CALL(autocheckout_manager_->metric_logger(), - LogAutocheckoutBubbleMetric( - AutofillMetrics::BUBBLE_COULD_BE_DISPLAYED)).Times(1); - autocheckout_manager_->OnLoadedPageMetaData(CreateStartOfFlowMetaData()); - // Simulate the user submitting some data via the requestAutocomplete UI. - autofill_manager_delegate_->SetUserSuppliedData( - FakeUserSubmittedFormStructure()); - GURL frame_url; - EXPECT_CALL(autocheckout_manager_->metric_logger(), - LogAutocheckoutBuyFlowMetric( - AutofillMetrics::AUTOCHECKOUT_BUY_FLOW_STARTED)).Times(1); - autocheckout_manager_->MaybeShowAutocheckoutDialog( - frame_url, - AUTOCHECKOUT_BUBBLE_ACCEPTED); - CheckFillFormsAndClickIpc(HAS_PROCEED_ELEMENT); - EXPECT_TRUE(autocheckout_manager_->in_autocheckout_flow()); - EXPECT_TRUE(autocheckout_manager_->should_preserve_dialog()); - EXPECT_TRUE(autofill_manager_delegate_->request_autocomplete_dialog_open()); - EXPECT_TRUE(autofill_manager_delegate_ - ->AutocheckoutStepExistsWithStatus(AUTOCHECKOUT_STEP_SHIPPING, - AUTOCHECKOUT_STEP_STARTED)); - EXPECT_TRUE(autofill_manager_delegate_ - ->AutocheckoutStepExistsWithStatus(AUTOCHECKOUT_STEP_DELIVERY, - AUTOCHECKOUT_STEP_UNSTARTED)); - EXPECT_TRUE(autofill_manager_delegate_ - ->AutocheckoutStepExistsWithStatus(AUTOCHECKOUT_STEP_BILLING, - AUTOCHECKOUT_STEP_UNSTARTED)); - } - - void HideRequestAutocompleteDialog() { - EXPECT_TRUE( - autofill_manager_delegate_->request_autocomplete_dialog_open()); - autofill_manager_delegate_->HideRequestAutocompleteDialog(); - EXPECT_FALSE( - autofill_manager_delegate_->request_autocomplete_dialog_open()); - } - - // Test a multi-page Autocheckout flow end to end. - // |proceed_element_presence_on_last_page| indicates whether the last page - // of the flow should have a proceed element. - void TestFullAutocheckoutFlow( - ProceedElementPresence proceed_element_presence_on_last_page) { - // Test for progression through last page. - OpenRequestAutocompleteDialog(); - EXPECT_TRUE(autofill_manager_delegate_ - ->AutocheckoutStepExistsWithStatus(AUTOCHECKOUT_STEP_SHIPPING, - AUTOCHECKOUT_STEP_STARTED)); - // Complete the first page. - autocheckout_manager_->OnAutocheckoutPageCompleted(SUCCESS); - EXPECT_TRUE(autocheckout_manager_->should_preserve_dialog()); - EXPECT_TRUE(autofill_manager_delegate_ - ->AutocheckoutStepExistsWithStatus(AUTOCHECKOUT_STEP_SHIPPING, - AUTOCHECKOUT_STEP_COMPLETED)); - - // Go to the second page. - EXPECT_CALL(*autofill_manager_delegate_, OnAutocheckoutSuccess()).Times(1); - autocheckout_manager_->OnLoadedPageMetaData(CreateInFlowMetaData()); - EXPECT_TRUE(autocheckout_manager_->in_autocheckout_flow()); - EXPECT_TRUE(autocheckout_manager_->should_preserve_dialog()); - CheckFillFormsAndClickIpc(HAS_PROCEED_ELEMENT); - EXPECT_TRUE(autofill_manager_delegate_ - ->AutocheckoutStepExistsWithStatus(AUTOCHECKOUT_STEP_DELIVERY, - AUTOCHECKOUT_STEP_STARTED)); - autocheckout_manager_->OnAutocheckoutPageCompleted(SUCCESS); - EXPECT_TRUE(autofill_manager_delegate_ - ->AutocheckoutStepExistsWithStatus(AUTOCHECKOUT_STEP_DELIVERY, - AUTOCHECKOUT_STEP_COMPLETED)); - - // Go to the third page. - EXPECT_CALL(autocheckout_manager_->metric_logger(), - LogAutocheckoutBuyFlowMetric( - AutofillMetrics::AUTOCHECKOUT_BUY_FLOW_SUCCESS)).Times(1); - autocheckout_manager_->OnLoadedPageMetaData( - CreateEndOfFlowMetaData(proceed_element_presence_on_last_page)); - CheckFillFormsAndClickIpc(proceed_element_presence_on_last_page); - EXPECT_TRUE(autocheckout_manager_->in_autocheckout_flow()); - EXPECT_TRUE(autocheckout_manager_->should_preserve_dialog()); - EXPECT_TRUE(autofill_manager_delegate_ - ->AutocheckoutStepExistsWithStatus(AUTOCHECKOUT_STEP_BILLING, - AUTOCHECKOUT_STEP_STARTED)); - autocheckout_manager_->OnAutocheckoutPageCompleted(SUCCESS); - EXPECT_FALSE(autocheckout_manager_->in_autocheckout_flow()); - EXPECT_TRUE(autocheckout_manager_->should_preserve_dialog()); - EXPECT_TRUE(autofill_manager_delegate_ - ->AutocheckoutStepExistsWithStatus(AUTOCHECKOUT_STEP_BILLING, - AUTOCHECKOUT_STEP_COMPLETED)); - - EXPECT_TRUE(autofill_manager_delegate_->request_autocomplete_dialog_open()); - - // Go to the page after the flow. - autocheckout_manager_->OnLoadedPageMetaData( - scoped_ptr<AutocheckoutPageMetaData>()); - EXPECT_EQ(proceed_element_presence_on_last_page == HAS_PROCEED_ELEMENT, - autocheckout_manager_->should_preserve_dialog()); - // Go to another page and we should not preserve the dialog now. - autocheckout_manager_->OnLoadedPageMetaData( - scoped_ptr<AutocheckoutPageMetaData>()); - EXPECT_FALSE(autocheckout_manager_->should_preserve_dialog()); - } - - // Test a signle-page Autocheckout flow. |proceed_element_presence| indicates - // whether the page should have a proceed element. - void TestSinglePageFlow(ProceedElementPresence proceed_element_presence) { - // Test one page flow. - EXPECT_FALSE(autocheckout_manager_->in_autocheckout_flow()); - EXPECT_FALSE( - autofill_manager_delegate_->request_autocomplete_dialog_open()); - EXPECT_CALL(autocheckout_manager_->metric_logger(), - LogAutocheckoutBubbleMetric( - AutofillMetrics::BUBBLE_COULD_BE_DISPLAYED)).Times(1); - EXPECT_CALL(*autofill_manager_delegate_, OnAutocheckoutSuccess()).Times(1); - autocheckout_manager_->OnLoadedPageMetaData( - CreateOnePageFlowMetaData(proceed_element_presence)); - // Simulate the user submitting some data via the requestAutocomplete UI. - autofill_manager_delegate_->SetUserSuppliedData( - FakeUserSubmittedFormStructure()); - GURL frame_url; - EXPECT_CALL(autocheckout_manager_->metric_logger(), - LogAutocheckoutBuyFlowMetric( - AutofillMetrics::AUTOCHECKOUT_BUY_FLOW_STARTED)).Times(1); - EXPECT_CALL(autocheckout_manager_->metric_logger(), - LogAutocheckoutBuyFlowMetric( - AutofillMetrics::AUTOCHECKOUT_BUY_FLOW_SUCCESS)).Times(1); - autocheckout_manager_->MaybeShowAutocheckoutDialog( - frame_url, - AUTOCHECKOUT_BUBBLE_ACCEPTED); - autocheckout_manager_->OnAutocheckoutPageCompleted(SUCCESS); - CheckFillFormsAndClickIpc(proceed_element_presence); - EXPECT_FALSE(autocheckout_manager_->in_autocheckout_flow()); - EXPECT_TRUE(autocheckout_manager_->should_preserve_dialog()); - EXPECT_TRUE(autofill_manager_delegate_->request_autocomplete_dialog_open()); - EXPECT_TRUE(autofill_manager_delegate_ - ->AutocheckoutStepExistsWithStatus(AUTOCHECKOUT_STEP_SHIPPING, - AUTOCHECKOUT_STEP_COMPLETED)); - EXPECT_TRUE(autofill_manager_delegate_ - ->AutocheckoutStepExistsWithStatus(AUTOCHECKOUT_STEP_DELIVERY, - AUTOCHECKOUT_STEP_COMPLETED)); - EXPECT_TRUE(autofill_manager_delegate_ - ->AutocheckoutStepExistsWithStatus(AUTOCHECKOUT_STEP_BILLING, - AUTOCHECKOUT_STEP_COMPLETED)); - // Go to the page after the flow. - autocheckout_manager_->OnLoadedPageMetaData( - scoped_ptr<AutocheckoutPageMetaData>()); - EXPECT_EQ(proceed_element_presence == HAS_PROCEED_ELEMENT, - autocheckout_manager_->should_preserve_dialog()); - // Go to another page, and we should not preserve the dialog now. - autocheckout_manager_->OnLoadedPageMetaData( - scoped_ptr<AutocheckoutPageMetaData>()); - EXPECT_FALSE(autocheckout_manager_->should_preserve_dialog()); - } - - protected: - scoped_ptr<TestAutofillDriver> autofill_driver_; - scoped_ptr<TestAutofillManager> autofill_manager_; - scoped_ptr<TestAutocheckoutManager> autocheckout_manager_; - scoped_ptr<MockAutofillManagerDelegate> autofill_manager_delegate_; -}; - -TEST_F(AutocheckoutManagerTest, TestFillForms) { - OpenRequestAutocompleteDialog(); - - // Test if autocheckout manager can fill the first page. - autofill_manager_->SetFormStructure(CreateTestAddressFormStructure()); - - autocheckout_manager_->FillForms(); - - std::vector<FormData> filled_forms = ReadFilledForms(); - ASSERT_EQ(1U, filled_forms.size()); - ASSERT_EQ(9U, filled_forms[0].fields.size()); - EXPECT_EQ(ASCIIToUTF16("Test User"), filled_forms[0].fields[0].value); - EXPECT_EQ(ASCIIToUTF16("650-123-9909"), filled_forms[0].fields[1].value); - EXPECT_EQ(ASCIIToUTF16("blah@blah.com"), filled_forms[0].fields[2].value); - EXPECT_EQ(ASCIIToUTF16("Fake Street"), filled_forms[0].fields[3].value); - EXPECT_EQ(ASCIIToUTF16("Mocked City"), filled_forms[0].fields[4].value); - EXPECT_EQ(ASCIIToUTF16("California"), filled_forms[0].fields[5].value); - EXPECT_EQ(ASCIIToUTF16("United States"), filled_forms[0].fields[6].value); - EXPECT_EQ(ASCIIToUTF16("49012"), filled_forms[0].fields[7].value); - // Last field should not be filled, because there is no server mapping - // available for it. - EXPECT_EQ(ASCIIToUTF16("SomeField"), filled_forms[0].fields[8].value); - - filled_forms.clear(); - ClearIpcSink(); - - // Test if autocheckout manager can fill form on second page. - autofill_manager_->SetFormStructure(CreateTestCreditCardFormStructure()); - - autocheckout_manager_->FillForms(); - - filled_forms = ReadFilledForms(); - ASSERT_EQ(1U, filled_forms.size()); - ASSERT_EQ(10U, filled_forms[0].fields.size()); - EXPECT_EQ(ASCIIToUTF16("Test User"), filled_forms[0].fields[0].value); - EXPECT_EQ(ASCIIToUTF16("4444444444444448"), filled_forms[0].fields[1].value); - EXPECT_EQ(ASCIIToUTF16("10"), filled_forms[0].fields[2].value); - EXPECT_EQ(ASCIIToUTF16("2020"), filled_forms[0].fields[3].value); - EXPECT_EQ(ASCIIToUTF16("123"), filled_forms[0].fields[4].value); - EXPECT_EQ(ASCIIToUTF16("Billing Street"), filled_forms[0].fields[5].value); - EXPECT_EQ(ASCIIToUTF16("Billing City"), filled_forms[0].fields[6].value); - EXPECT_EQ(ASCIIToUTF16("BillingState"), filled_forms[0].fields[7].value); - EXPECT_EQ(ASCIIToUTF16("Canada"), filled_forms[0].fields[8].value); - EXPECT_EQ(ASCIIToUTF16("11111"), filled_forms[0].fields[9].value); - - filled_forms.clear(); - ClearIpcSink(); - - // Test form with default values. - autofill_manager_->SetFormStructure( - CreateTestFormStructureWithDefaultValues()); - - autocheckout_manager_->FillForms(); - - filled_forms = ReadFilledForms(); - ASSERT_EQ(1U, filled_forms.size()); - ASSERT_EQ(3U, filled_forms[0].fields.size()); - EXPECT_FALSE(filled_forms[0].fields[0].is_checked); - EXPECT_EQ(ASCIIToUTF16("male"), filled_forms[0].fields[0].value); - EXPECT_TRUE(filled_forms[0].fields[1].is_checked); - EXPECT_EQ(ASCIIToUTF16("female"), filled_forms[0].fields[1].value); - EXPECT_EQ(ASCIIToUTF16("Default Value"), filled_forms[0].fields[2].value); -} - -TEST_F(AutocheckoutManagerTest, OnFormsSeenTest) { - GURL frame_url; - gfx::RectF bounding_box; - EXPECT_CALL(autocheckout_manager_->metric_logger(), - LogAutocheckoutBubbleMetric( - AutofillMetrics::BUBBLE_COULD_BE_DISPLAYED)).Times(1); - autocheckout_manager_->OnLoadedPageMetaData(CreateStartOfFlowMetaData()); - autocheckout_manager_->MaybeShowAutocheckoutBubble(frame_url, bounding_box); - - EXPECT_FALSE(autocheckout_manager_->should_show_bubble()); - // OnFormsSeen resets whether or not the bubble was shown. - autocheckout_manager_->OnFormsSeen(); - EXPECT_TRUE(autocheckout_manager_->should_show_bubble()); -} - -TEST_F(AutocheckoutManagerTest, OnClickFailedTestMissingAdvance) { - OpenRequestAutocompleteDialog(); - - EXPECT_CALL(*autofill_manager_delegate_, OnAutocheckoutError()).Times(1); - EXPECT_CALL( - autocheckout_manager_->metric_logger(), - LogAutocheckoutBuyFlowMetric( - AutofillMetrics::AUTOCHECKOUT_BUY_FLOW_MISSING_ADVANCE_ELEMENT)) - .Times(1); - autocheckout_manager_->OnAutocheckoutPageCompleted(MISSING_ADVANCE); - EXPECT_FALSE(autocheckout_manager_->in_autocheckout_flow()); - EXPECT_TRUE( - autofill_manager_delegate_->request_autocomplete_dialog_open()); - EXPECT_TRUE(autofill_manager_delegate_ - ->AutocheckoutStepExistsWithStatus(AUTOCHECKOUT_STEP_SHIPPING, - AUTOCHECKOUT_STEP_FAILED)); -} - -TEST_F(AutocheckoutManagerTest, OnClickFailedTestMissingClickBeforeFilling) { - OpenRequestAutocompleteDialog(); - - EXPECT_CALL(*autofill_manager_delegate_, OnAutocheckoutError()).Times(1); - EXPECT_CALL( - autocheckout_manager_->metric_logger(), - LogAutocheckoutBuyFlowMetric(AutofillMetrics:: - AUTOCHECKOUT_BUY_FLOW_MISSING_CLICK_ELEMENT_BEFORE_FORM_FILLING)) - .Times(1); - autocheckout_manager_->OnAutocheckoutPageCompleted( - MISSING_CLICK_ELEMENT_BEFORE_FORM_FILLING); - EXPECT_FALSE(autocheckout_manager_->in_autocheckout_flow()); - EXPECT_TRUE( - autofill_manager_delegate_->request_autocomplete_dialog_open()); -} - -TEST_F(AutocheckoutManagerTest, OnClickFailedTestMissingClickAfterFilling) { - OpenRequestAutocompleteDialog(); - - EXPECT_CALL(*autofill_manager_delegate_, OnAutocheckoutError()).Times(1); - EXPECT_CALL( - autocheckout_manager_->metric_logger(), - LogAutocheckoutBuyFlowMetric(AutofillMetrics:: - AUTOCHECKOUT_BUY_FLOW_MISSING_CLICK_ELEMENT_AFTER_FORM_FILLING)) - .Times(1); - autocheckout_manager_->OnAutocheckoutPageCompleted( - MISSING_CLICK_ELEMENT_AFTER_FORM_FILLING); - EXPECT_FALSE(autocheckout_manager_->in_autocheckout_flow()); - EXPECT_TRUE( - autofill_manager_delegate_->request_autocomplete_dialog_open()); -} - -TEST_F(AutocheckoutManagerTest, MaybeShowAutocheckoutBubbleTest) { - GURL frame_url; - gfx::RectF bounding_box; - EXPECT_CALL(autocheckout_manager_->metric_logger(), - LogAutocheckoutBubbleMetric( - AutofillMetrics::BUBBLE_COULD_BE_DISPLAYED)).Times(1); - autocheckout_manager_->OnLoadedPageMetaData(CreateStartOfFlowMetaData()); - // MaybeShowAutocheckoutBubble shows bubble if it has not been shown. - autocheckout_manager_->MaybeShowAutocheckoutBubble(frame_url, bounding_box); - EXPECT_FALSE(autocheckout_manager_->should_show_bubble()); - EXPECT_TRUE(autofill_manager_delegate_->autocheckout_bubble_shown()); - - // Reset |autofill_manager_delegate_|. - HideRequestAutocompleteDialog(); - autofill_manager_delegate_->set_autocheckout_bubble_shown(false); - - // MaybeShowAutocheckoutBubble does nothing if the bubble was already shown - // for the current page. - autocheckout_manager_->MaybeShowAutocheckoutBubble(frame_url, bounding_box); - EXPECT_FALSE(autocheckout_manager_->should_show_bubble()); - EXPECT_FALSE(autofill_manager_delegate_->autocheckout_bubble_shown()); - EXPECT_FALSE(autofill_manager_delegate_->request_autocomplete_dialog_open()); -} - -TEST_F(AutocheckoutManagerTest, OnLoadedPageMetaDataMissingMetaData) { - // Gettting no meta data after any autocheckout page is an error. - OpenRequestAutocompleteDialog(); - EXPECT_CALL(*autofill_manager_delegate_, OnAutocheckoutError()).Times(1); - EXPECT_CALL( - autocheckout_manager_->metric_logger(), - LogAutocheckoutBuyFlowMetric( - AutofillMetrics::AUTOCHECKOUT_BUY_FLOW_MISSING_FIELDMAPPING)) - .Times(1); - autocheckout_manager_->OnLoadedPageMetaData( - scoped_ptr<AutocheckoutPageMetaData>()); - EXPECT_FALSE(autocheckout_manager_->in_autocheckout_flow()); - EXPECT_EQ(0U, process()->sink().message_count()); - EXPECT_TRUE( - autofill_manager_delegate_->request_autocomplete_dialog_open()); -} - -TEST_F(AutocheckoutManagerTest, OnLoadedPageMetaDataRepeatedStartPage) { - // Getting start page twice in a row is an error. - OpenRequestAutocompleteDialog(); - EXPECT_CALL(*autofill_manager_delegate_, OnAutocheckoutError()).Times(1); - EXPECT_CALL( - autocheckout_manager_->metric_logger(), - LogAutocheckoutBuyFlowMetric( - AutofillMetrics::AUTOCHECKOUT_BUY_FLOW_CANNOT_PROCEED)).Times(1); - autocheckout_manager_->OnLoadedPageMetaData(CreateStartOfFlowMetaData()); - EXPECT_FALSE(autocheckout_manager_->in_autocheckout_flow()); - EXPECT_EQ(0U, process()->sink().message_count()); - EXPECT_TRUE( - autofill_manager_delegate_->request_autocomplete_dialog_open()); - - EXPECT_TRUE(autofill_manager_delegate_ - ->AutocheckoutStepExistsWithStatus(AUTOCHECKOUT_STEP_SHIPPING, - AUTOCHECKOUT_STEP_FAILED)); -} - -TEST_F(AutocheckoutManagerTest, OnLoadedPageMetaDataRepeatedPage) { - // Repeating a page is an error. - OpenRequestAutocompleteDialog(); - // Go to second page. - autocheckout_manager_->OnLoadedPageMetaData(CreateInFlowMetaData()); - EXPECT_TRUE(autocheckout_manager_->in_autocheckout_flow()); - CheckFillFormsAndClickIpc(HAS_PROCEED_ELEMENT); - EXPECT_CALL(*autofill_manager_delegate_, OnAutocheckoutError()).Times(1); - EXPECT_CALL( - autocheckout_manager_->metric_logger(), - LogAutocheckoutBuyFlowMetric( - AutofillMetrics::AUTOCHECKOUT_BUY_FLOW_CANNOT_PROCEED)).Times(1); - autocheckout_manager_->OnLoadedPageMetaData(CreateInFlowMetaData()); - EXPECT_FALSE(autocheckout_manager_->in_autocheckout_flow()); - EXPECT_EQ(0U, process()->sink().message_count()); - EXPECT_TRUE( - autofill_manager_delegate_->request_autocomplete_dialog_open()); - EXPECT_TRUE(autofill_manager_delegate_ - ->AutocheckoutStepExistsWithStatus(AUTOCHECKOUT_STEP_DELIVERY, - AUTOCHECKOUT_STEP_FAILED)); -} - -TEST_F(AutocheckoutManagerTest, OnLoadedPageMetaDataNotInFlow) { - // Repeating a page is an error. - OpenRequestAutocompleteDialog(); - // Go to second page. - autocheckout_manager_->OnLoadedPageMetaData(CreateInFlowMetaData()); - EXPECT_TRUE(autocheckout_manager_->in_autocheckout_flow()); - CheckFillFormsAndClickIpc(HAS_PROCEED_ELEMENT); - EXPECT_CALL(*autofill_manager_delegate_, OnAutocheckoutError()).Times(1); - EXPECT_CALL( - autocheckout_manager_->metric_logger(), - LogAutocheckoutBuyFlowMetric( - AutofillMetrics::AUTOCHECKOUT_BUY_FLOW_MISSING_FIELDMAPPING)) - .Times(1); - autocheckout_manager_->OnLoadedPageMetaData(CreateNotInFlowMetaData()); - EXPECT_FALSE(autocheckout_manager_->in_autocheckout_flow()); - EXPECT_EQ(0U, process()->sink().message_count()); - EXPECT_TRUE( - autofill_manager_delegate_->request_autocomplete_dialog_open()); - EXPECT_TRUE(autofill_manager_delegate_ - ->AutocheckoutStepExistsWithStatus(AUTOCHECKOUT_STEP_DELIVERY, - AUTOCHECKOUT_STEP_FAILED)); -} - -TEST_F(AutocheckoutManagerTest, - OnLoadedPageMetaDataShouldNotFillFormsIfNotInFlow) { - // If not in flow, OnLoadedPageMetaData does not fill forms. - EXPECT_CALL(autocheckout_manager_->metric_logger(), - LogAutocheckoutBubbleMetric( - AutofillMetrics::BUBBLE_COULD_BE_DISPLAYED)).Times(1); - autocheckout_manager_->OnLoadedPageMetaData(CreateStartOfFlowMetaData()); - // Go to second page. - autocheckout_manager_->OnLoadedPageMetaData(CreateInFlowMetaData()); - EXPECT_EQ(0U, process()->sink().message_count()); -} - -TEST_F(AutocheckoutManagerTest, FullAutocheckoutFlow) { - TestFullAutocheckoutFlow(HAS_PROCEED_ELEMENT); -} - -TEST_F(AutocheckoutManagerTest, FullAutocheckoutFlowNoClickOnLastPage) { - TestFullAutocheckoutFlow(NO_PROCEED_ELEMENT); -} - -TEST_F(AutocheckoutManagerTest, CancelledAutocheckoutFlow) { - // Test for progression through last page. - OpenRequestAutocompleteDialog(); - // Go to second page. - autocheckout_manager_->OnLoadedPageMetaData(CreateInFlowMetaData()); - EXPECT_TRUE(autocheckout_manager_->in_autocheckout_flow()); - CheckFillFormsAndClickIpc(HAS_PROCEED_ELEMENT); - - // Cancel the flow. - autocheckout_manager_->ReturnAutocheckoutData(NULL, std::string()); - EXPECT_FALSE(autocheckout_manager_->in_autocheckout_flow()); - - // Go to third page. - EXPECT_CALL(autocheckout_manager_->metric_logger(), - LogAutocheckoutBuyFlowMetric(testing::_)).Times(0); - autocheckout_manager_->OnLoadedPageMetaData( - CreateEndOfFlowMetaData(HAS_PROCEED_ELEMENT)); - EXPECT_EQ(0U, process()->sink().message_count()); -} - -TEST_F(AutocheckoutManagerTest, SinglePageFlow) { - TestSinglePageFlow(HAS_PROCEED_ELEMENT); -} - -TEST_F(AutocheckoutManagerTest, SinglePageFlowNoClickElement) { - TestSinglePageFlow(NO_PROCEED_ELEMENT); -} - -TEST_F(AutocheckoutManagerTest, CancelAutocheckoutBubble) { - GURL frame_url; - gfx::RectF bounding_box; - autofill_manager_delegate_->set_should_autoclick_bubble(false); - EXPECT_FALSE(autocheckout_manager_->in_autocheckout_flow()); - EXPECT_FALSE(autofill_manager_delegate_->request_autocomplete_dialog_open()); - EXPECT_FALSE(autocheckout_manager_->is_autocheckout_bubble_showing()); - EXPECT_CALL(autocheckout_manager_->metric_logger(), - LogAutocheckoutBubbleMetric( - AutofillMetrics::BUBBLE_COULD_BE_DISPLAYED)).Times(1); - autocheckout_manager_->OnLoadedPageMetaData(CreateStartOfFlowMetaData()); - - autocheckout_manager_->MaybeShowAutocheckoutBubble(frame_url, bounding_box); - EXPECT_TRUE(autocheckout_manager_->is_autocheckout_bubble_showing()); - autocheckout_manager_->MaybeShowAutocheckoutDialog( - frame_url, - AUTOCHECKOUT_BUBBLE_IGNORED); - EXPECT_FALSE(autocheckout_manager_->is_autocheckout_bubble_showing()); - EXPECT_TRUE(autocheckout_manager_->should_show_bubble()); - - autocheckout_manager_->MaybeShowAutocheckoutBubble(frame_url, bounding_box); - EXPECT_TRUE(autocheckout_manager_->is_autocheckout_bubble_showing()); - autocheckout_manager_->MaybeShowAutocheckoutDialog( - frame_url, - AUTOCHECKOUT_BUBBLE_CANCELED); - EXPECT_FALSE(autocheckout_manager_->is_autocheckout_bubble_showing()); - EXPECT_FALSE(autocheckout_manager_->should_show_bubble()); - - autocheckout_manager_->MaybeShowAutocheckoutBubble(frame_url, bounding_box); - EXPECT_FALSE(autocheckout_manager_->is_autocheckout_bubble_showing()); -} - -} // namespace autofill diff --git a/chromium/components/autofill/content/browser/autocheckout_page_meta_data.cc b/chromium/components/autofill/content/browser/autocheckout_page_meta_data.cc deleted file mode 100644 index be03d70c592..00000000000 --- a/chromium/components/autofill/content/browser/autocheckout_page_meta_data.cc +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "components/autofill/content/browser/autocheckout_page_meta_data.h" - -namespace autofill { - -AutocheckoutPageMetaData::AutocheckoutPageMetaData() - : current_page_number(-1), - total_pages(-1), - ignore_ajax(true) {} - -AutocheckoutPageMetaData::~AutocheckoutPageMetaData() {} - -bool AutocheckoutPageMetaData::IsStartOfAutofillableFlow() const { - return current_page_number == 0 && total_pages > 0; -} - -bool AutocheckoutPageMetaData::IsInAutofillableFlow() const { - return current_page_number >= 0 && current_page_number < total_pages; -} - -bool AutocheckoutPageMetaData::IsEndOfAutofillableFlow() const { - return total_pages > 0 && current_page_number == total_pages - 1; -} - -} // namesapce autofill diff --git a/chromium/components/autofill/content/browser/autocheckout_page_meta_data.h b/chromium/components/autofill/content/browser/autocheckout_page_meta_data.h deleted file mode 100644 index 6839d1b3ec5..00000000000 --- a/chromium/components/autofill/content/browser/autocheckout_page_meta_data.h +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef COMPONENTS_AUTOFILL_CONTENT_BROWSER_AUTOCHECKOUT_PAGE_META_DATA_H_ -#define COMPONENTS_AUTOFILL_CONTENT_BROWSER_AUTOCHECKOUT_PAGE_META_DATA_H_ - -#include <map> -#include <vector> - -#include "base/basictypes.h" -#include "components/autofill/content/browser/autocheckout_steps.h" -#include "components/autofill/core/common/web_element_descriptor.h" - -namespace autofill { - -// Container for multipage Autocheckout data. -struct AutocheckoutPageMetaData { - AutocheckoutPageMetaData(); - ~AutocheckoutPageMetaData(); - - // Returns true if the Autofill server says that the current page is start of - // a multipage Autofill flow. - bool IsStartOfAutofillableFlow() const; - - // Returns true if the Autofill server says that the current page is in a - // multipage Autofill flow. - bool IsInAutofillableFlow() const; - - // Returns true if the Autofill server says that the current page is the end - // of a multipage Autofill flow. - bool IsEndOfAutofillableFlow() const; - - // Page number of the multipage Autofill flow this form belongs to - // (zero-indexed). If this form doesn't belong to any autofill flow, it is set - // to -1. - int current_page_number; - - // Total number of pages in the multipage Autofill flow. If this form doesn't - // belong to any autofill flow, it is set to -1. - int total_pages; - - // Whether ajaxy form changes that occur on this page during an Autocheckout - // flow should be ignored. - bool ignore_ajax; - - // A list of elements to click before filling form fields. Elements have to be - // clicked in order. - std::vector<WebElementDescriptor> click_elements_before_form_fill; - - // A list of elements to click after filling form fields, and before clicking - // page_advance_button. Elements have to be clicked in order. - std::vector<WebElementDescriptor> click_elements_after_form_fill; - - // The proceed element of the multipage Autofill flow. It can be empty - // if current page is the last page of a flow or isn't a member of a flow. - // - // We do expect page navigation when click on |proceed_element_descriptor|, - // and report an error if it doesn't. Oppositely, we do not expect page - // navigation when click elements in |click_elements_before_form_fill| and - // |click_elements_after_form_fill|. Because of this behavior difference and - // |proceed_element_descriptor| is optional, we separate it from - // |click_elements_after_form_fill|. - WebElementDescriptor proceed_element_descriptor; - - // Mapping of page numbers to the types of Autocheckout actions that will be - // performed on the given page of a multipage Autofill flow. - // If this form doesn't belong to such a flow, the map will be empty. - std::map<int, std::vector<AutocheckoutStepType> > page_types; - - private: - DISALLOW_COPY_AND_ASSIGN(AutocheckoutPageMetaData); -}; - -} // namespace autofill - -#endif // COMPONENTS_AUTOFILL_CONTENT_BROWSER_AUTOCHECKOUT_PAGE_META_DATA_H_ diff --git a/chromium/components/autofill/content/browser/autocheckout_page_meta_data_unittest.cc b/chromium/components/autofill/content/browser/autocheckout_page_meta_data_unittest.cc deleted file mode 100644 index 0e740608319..00000000000 --- a/chromium/components/autofill/content/browser/autocheckout_page_meta_data_unittest.cc +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "components/autofill/content/browser/autocheckout_page_meta_data.h" -#include "testing/gtest/include/gtest/gtest.h" - -namespace { - -void SetPageDetails(autofill::AutocheckoutPageMetaData* meta_data, - int current_page, - int total) { - meta_data->current_page_number = current_page; - meta_data->total_pages = total; -} - -} // namespace - -namespace autofill { - -TEST(AutocheckoutPageMetaDataTest, AutofillableFlow) { - - AutocheckoutPageMetaData page_meta_data; - EXPECT_FALSE(page_meta_data.IsStartOfAutofillableFlow()); - EXPECT_FALSE(page_meta_data.IsInAutofillableFlow()); - EXPECT_FALSE(page_meta_data.IsEndOfAutofillableFlow()); - - SetPageDetails(&page_meta_data, -1, 0); - EXPECT_FALSE(page_meta_data.IsStartOfAutofillableFlow()); - EXPECT_FALSE(page_meta_data.IsInAutofillableFlow()); - EXPECT_FALSE(page_meta_data.IsEndOfAutofillableFlow()); - - SetPageDetails(&page_meta_data, 0, 0); - EXPECT_FALSE(page_meta_data.IsStartOfAutofillableFlow()); - EXPECT_FALSE(page_meta_data.IsInAutofillableFlow()); - EXPECT_FALSE(page_meta_data.IsEndOfAutofillableFlow()); - - SetPageDetails(&page_meta_data, 0, 1); - EXPECT_TRUE(page_meta_data.IsStartOfAutofillableFlow()); - EXPECT_TRUE(page_meta_data.IsInAutofillableFlow()); - EXPECT_TRUE(page_meta_data.IsEndOfAutofillableFlow()); - - SetPageDetails(&page_meta_data, 1, 2); - EXPECT_FALSE(page_meta_data.IsStartOfAutofillableFlow()); - EXPECT_TRUE(page_meta_data.IsInAutofillableFlow()); - EXPECT_TRUE(page_meta_data.IsEndOfAutofillableFlow()); - - SetPageDetails(&page_meta_data, 2, 2); - EXPECT_FALSE(page_meta_data.IsStartOfAutofillableFlow()); - EXPECT_FALSE(page_meta_data.IsInAutofillableFlow()); - EXPECT_FALSE(page_meta_data.IsEndOfAutofillableFlow()); -} - -} // namespace autofill diff --git a/chromium/components/autofill/content/browser/autocheckout_request_manager.cc b/chromium/components/autofill/content/browser/autocheckout_request_manager.cc deleted file mode 100644 index e22e9c7b040..00000000000 --- a/chromium/components/autofill/content/browser/autocheckout_request_manager.cc +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "components/autofill/content/browser/autocheckout_request_manager.h" - -#include "components/autofill/core/browser/autofill_manager_delegate.h" -#include "content/public/browser/browser_context.h" - -namespace { - -const char kAutocheckoutRequestManagerKey[] = - "browser_context_autocheckout_request_manager"; - -} // namespace - -namespace autofill { - -AutocheckoutRequestManager::~AutocheckoutRequestManager() {} - -// static -void AutocheckoutRequestManager::CreateForBrowserContext( - content::BrowserContext* browser_context) { - if (FromBrowserContext(browser_context)) - return; - - browser_context->SetUserData( - kAutocheckoutRequestManagerKey, - new AutocheckoutRequestManager(browser_context->GetRequestContext())); -} - -// static -AutocheckoutRequestManager* AutocheckoutRequestManager::FromBrowserContext( - content::BrowserContext* browser_context) { - return static_cast<AutocheckoutRequestManager*>( - browser_context->GetUserData(kAutocheckoutRequestManagerKey)); -} - -void AutocheckoutRequestManager::SendAutocheckoutStatus( - AutocheckoutStatus status, - const GURL& source_url, - const std::vector<AutocheckoutStatistic>& latency_statistics, - const std::string& google_transaction_id) { - wallet_client_.SendAutocheckoutStatus(status, - source_url, - latency_statistics, - google_transaction_id); -} - - -const AutofillMetrics& AutocheckoutRequestManager::GetMetricLogger() const { - return metric_logger_; -} - -DialogType AutocheckoutRequestManager::GetDialogType() const { - return DIALOG_TYPE_AUTOCHECKOUT; -} - -std::string AutocheckoutRequestManager::GetRiskData() const { - NOTREACHED(); - return std::string(); -} - -std::string AutocheckoutRequestManager::GetWalletCookieValue() const { - return std::string(); -} - -bool AutocheckoutRequestManager::IsShippingAddressRequired() const { - NOTREACHED(); - return true; -} - -void AutocheckoutRequestManager::OnDidAcceptLegalDocuments() { - NOTREACHED(); -} - -void AutocheckoutRequestManager::OnDidAuthenticateInstrument(bool success) { - NOTREACHED(); -} - -void AutocheckoutRequestManager::OnDidGetFullWallet( - scoped_ptr<wallet::FullWallet> full_wallet) { - NOTREACHED(); -} - -void AutocheckoutRequestManager::OnDidGetWalletItems( - scoped_ptr<wallet::WalletItems> wallet_items) { - NOTREACHED(); -} - - -void AutocheckoutRequestManager::OnDidSaveToWallet( - const std::string& instrument_id, - const std::string& address_id, - const std::vector<wallet::RequiredAction>& required_actions, - const std::vector<wallet::FormFieldError>& form_field_errors) { - NOTREACHED(); -} - -void AutocheckoutRequestManager::OnWalletError( - wallet::WalletClient::ErrorType error_type) { - // Nothing to be done. |error_type| is logged by |metric_logger_|. -} - -AutocheckoutRequestManager::AutocheckoutRequestManager( - net::URLRequestContextGetter* request_context_getter) - : wallet_client_(request_context_getter, this) { -} - -} // namespace autofill diff --git a/chromium/components/autofill/content/browser/autocheckout_request_manager.h b/chromium/components/autofill/content/browser/autocheckout_request_manager.h deleted file mode 100644 index 4d20b5f727b..00000000000 --- a/chromium/components/autofill/content/browser/autocheckout_request_manager.h +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef COMPONENTS_AUTOFILL_CONTENT_BROWSER_AUTOCHECKOUT_REQUEST_MANAGER_H_ -#define COMPONENTS_AUTOFILL_CONTENT_BROWSER_AUTOCHECKOUT_REQUEST_MANAGER_H_ - -#include "base/supports_user_data.h" -#include "components/autofill/content/browser/autocheckout_statistic.h" -#include "components/autofill/content/browser/wallet/wallet_client.h" -#include "components/autofill/content/browser/wallet/wallet_client_delegate.h" -#include "components/autofill/core/browser/autofill_metrics.h" -#include "components/autofill/core/common/autocheckout_status.h" -#include "url/gurl.h" - -namespace content { -class BrowserContext; -} - -namespace net { -class URLRequestContextGetter; -} - -namespace autofill { - -// AutocheckoutRequestManager's only responsiblity is to make sure any -// SendAutocheckoutStatus calls succeed regardless of any actions the user may -// make in the browser i.e. closing a tab, the requestAutocomplete dialog, etc. -// To that end, it is a piece of user data tied to the BrowserContext. -class AutocheckoutRequestManager : public base::SupportsUserData::Data, - public wallet::WalletClientDelegate { - public: - virtual ~AutocheckoutRequestManager(); - - // Creates a new AutocheckoutRequestManager and stores it as user data in - // |browser_context| if one does not already exist. - static void CreateForBrowserContext( - content::BrowserContext* browser_context); - - // Retrieves the AutocheckoutRequestManager for |browser_context| if one - // exists. - static AutocheckoutRequestManager* FromBrowserContext( - content::BrowserContext* browser_context); - - // Sends the |status| of an Autocheckout flow to Online Wallet using - // |wallet_client_|. - void SendAutocheckoutStatus( - AutocheckoutStatus status, - const GURL& source_url, - const std::vector<AutocheckoutStatistic>& latency_statistics, - const std::string& google_transaction_id); - - // wallet::WalletClientDelegate: - virtual const AutofillMetrics& GetMetricLogger() const OVERRIDE; - virtual DialogType GetDialogType() const OVERRIDE; - virtual std::string GetRiskData() const OVERRIDE; - virtual std::string GetWalletCookieValue() const OVERRIDE; - virtual bool IsShippingAddressRequired() const OVERRIDE; - virtual void OnDidAcceptLegalDocuments() OVERRIDE; - virtual void OnDidAuthenticateInstrument(bool success) OVERRIDE; - virtual void OnDidGetFullWallet( - scoped_ptr<wallet::FullWallet> full_wallet) OVERRIDE; - virtual void OnDidGetWalletItems( - scoped_ptr<wallet::WalletItems> wallet_items) OVERRIDE; - virtual void OnDidSaveToWallet( - const std::string& instrument_id, - const std::string& address_id, - const std::vector<wallet::RequiredAction>& required_actions, - const std::vector<wallet::FormFieldError>& form_field_errors) OVERRIDE; - virtual void OnWalletError( - wallet::WalletClient::ErrorType error_type) OVERRIDE; - - private: - // |request_context_getter| is passed in to construct |wallet_client_|. - AutocheckoutRequestManager( - net::URLRequestContextGetter* request_context_getter); - - // Logs various UMA metrics. - AutofillMetrics metric_logger_; - - // Makes requests to Online Wallet. The only request this class is configured - // to make is SendAutocheckoutStatus. - wallet::WalletClient wallet_client_; - - DISALLOW_COPY_AND_ASSIGN(AutocheckoutRequestManager); -}; - -} // namespace autofill - -#endif // COMPONENTS_AUTOFILL_CONTENT_BROWSER_AUTOCHECKOUT_REQUEST_MANAGER_H_ diff --git a/chromium/components/autofill/content/browser/autocheckout_statistic.cc b/chromium/components/autofill/content/browser/autocheckout_statistic.cc deleted file mode 100644 index 9df4f4e4a61..00000000000 --- a/chromium/components/autofill/content/browser/autocheckout_statistic.cc +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "components/autofill/content/browser/autocheckout_statistic.h" - -#include "base/logging.h" -#include "base/strings/string_number_conversions.h" -#include "base/values.h" - -namespace autofill { - -namespace { - -std::string AutocheckoutStepToString(AutocheckoutStepType step) { - switch(step) { - case AUTOCHECKOUT_STEP_SHIPPING: - return "AUTOCHECKOUT_STEP_SHIPPING"; - case AUTOCHECKOUT_STEP_DELIVERY: - return "AUTOCHECKOUT_STEP_DELIVERY"; - case AUTOCHECKOUT_STEP_BILLING: - return "AUTOCHECKOUT_STEP_BILLING"; - case AUTOCHECKOUT_STEP_PROXY_CARD: - return "AUTOCHECKOUT_STEP_PROXY_CARD"; - } - NOTREACHED(); - return "NOT_POSSIBLE"; -} - -} // namespace - -AutocheckoutStatistic::AutocheckoutStatistic() : page_number(-1) { -} - -AutocheckoutStatistic::~AutocheckoutStatistic() { -} - -scoped_ptr<base::DictionaryValue> AutocheckoutStatistic::ToDictionary() const { - scoped_ptr<base::DictionaryValue> dict(new base::DictionaryValue()); - std::string description = base::IntToString(page_number); - for (size_t i = 0; i < steps.size(); ++i) { - description = description + "_" + AutocheckoutStepToString(steps[i]); - } - dict->SetString("step_description", description); - dict->SetInteger("time_taken", time_taken.InMilliseconds()); - DVLOG(1) << "Step : " << description - << ", time_taken: " << time_taken.InMilliseconds(); - return dict.Pass(); -} - -} // namespace autofill - diff --git a/chromium/components/autofill/content/browser/autocheckout_statistic.h b/chromium/components/autofill/content/browser/autocheckout_statistic.h deleted file mode 100644 index 74f7df7cd9b..00000000000 --- a/chromium/components/autofill/content/browser/autocheckout_statistic.h +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef COMPONENTS_AUTOFILL_CONTENT_BROWSER_AUTOCHECKOUT_STATISTIC_H__ -#define COMPONENTS_AUTOFILL_CONTENT_BROWSER_AUTOCHECKOUT_STATISTIC_H__ - -#include <vector> - -#include "base/memory/scoped_ptr.h" -#include "base/time/time.h" -#include "components/autofill/content/browser/autocheckout_steps.h" - -namespace base { -class DictionaryValue; -} - -namespace autofill { - -// Per page statistics gathered in Autocheckout flow. -struct AutocheckoutStatistic { - AutocheckoutStatistic(); - ~AutocheckoutStatistic(); - - // Page number for which this statistic is being recorded. - int page_number; - - // Autocheckout steps that are part of |page_number|. - std::vector<AutocheckoutStepType> steps; - - // Time taken for performing |steps|, used for measuring latency. - base::TimeDelta time_taken; - - // Helper method to convert AutocheckoutStatistic to json representation. - scoped_ptr<base::DictionaryValue> ToDictionary() const; -}; - -} // namespace autofill - -#endif // COMPONENTS_AUTOFILL_CONTENT_BROWSER_AUTOCHECKOUT_STATISTIC_H__ diff --git a/chromium/components/autofill/content/browser/autocheckout_steps.h b/chromium/components/autofill/content/browser/autocheckout_steps.h deleted file mode 100644 index d46abccc40f..00000000000 --- a/chromium/components/autofill/content/browser/autocheckout_steps.h +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef COMPONENTS_AUTOFILL_BROWSER_AUTOCHECKOUT_STEPS_H_ -#define COMPONENTS_AUTOFILL_BROWSER_AUTOCHECKOUT_STEPS_H_ - -namespace autofill { - -// Stages of a buy flow that may be encountered on an Autocheckout-supported -// site, used primarily for display purposes. -// Indexed for easy conversion from int values returned by Autofill server. -enum AutocheckoutStepType { - AUTOCHECKOUT_STEP_MIN_VALUE = 1, - AUTOCHECKOUT_STEP_SHIPPING = AUTOCHECKOUT_STEP_MIN_VALUE, - AUTOCHECKOUT_STEP_DELIVERY = 2, - AUTOCHECKOUT_STEP_BILLING = 3, - AUTOCHECKOUT_STEP_PROXY_CARD = 4, - AUTOCHECKOUT_STEP_MAX_VALUE = AUTOCHECKOUT_STEP_PROXY_CARD, -}; - -// Possible statuses for the above step types, again used primarily for display. -enum AutocheckoutStepStatus { - AUTOCHECKOUT_STEP_UNSTARTED, - AUTOCHECKOUT_STEP_STARTED, - AUTOCHECKOUT_STEP_COMPLETED, - AUTOCHECKOUT_STEP_FAILED, -}; - -} // namespace autofill - -#endif // COMPONENTS_AUTOFILL_BROWSER_AUTOCHECKOUT_STEPS_H_ diff --git a/chromium/components/autofill/content/browser/autofill_driver_impl.cc b/chromium/components/autofill/content/browser/autofill_driver_impl.cc index d0cb1f62861..c63a072524f 100644 --- a/chromium/components/autofill/content/browser/autofill_driver_impl.cc +++ b/chromium/components/autofill/content/browser/autofill_driver_impl.cc @@ -44,11 +44,6 @@ void AutofillDriverImpl::CreateForWebContentsAndDelegate( delegate, app_locale, enable_download_manager)); - // Trigger the lazy creation of AutocheckoutWhitelistManagerService, and - // schedule a fetch of the Autocheckout whitelist file if it's not already - // loaded. This helps ensure that the whitelist will be available by the time - // the user navigates to a form on which Autocheckout should be enabled. - delegate->GetAutocheckoutWhitelistManager(); } // static @@ -188,12 +183,6 @@ bool AutofillDriverImpl::OnMessageReceived(const IPC::Message& message) { IPC_MESSAGE_FORWARD(AutofillHostMsg_RequestAutocomplete, autofill_manager_.get(), AutofillManager::OnRequestAutocomplete) - IPC_MESSAGE_FORWARD(AutofillHostMsg_AutocheckoutPageCompleted, - autofill_manager_.get(), - AutofillManager::OnAutocheckoutPageCompleted) - IPC_MESSAGE_FORWARD(AutofillHostMsg_MaybeShowAutocheckoutBubble, - autofill_manager_.get(), - AutofillManager::OnMaybeShowAutocheckoutBubble) IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP() return handled; diff --git a/chromium/components/autofill/content/browser/autofill_driver_impl_unittest.cc b/chromium/components/autofill/content/browser/autofill_driver_impl_unittest.cc index 875b783a565..6986f0779b5 100644 --- a/chromium/components/autofill/content/browser/autofill_driver_impl_unittest.cc +++ b/chromium/components/autofill/content/browser/autofill_driver_impl_unittest.cc @@ -176,7 +176,7 @@ TEST_F(AutofillDriverImplTest, FormDataSentToRenderer) { TEST_F(AutofillDriverImplTest, TypePredictionsNotSentToRendererWhenDisabled) { FormData form; test::CreateTestAddressFormData(&form); - FormStructure form_structure(form, std::string()); + FormStructure form_structure(form); std::vector<FormStructure*> forms(1, &form_structure); driver_->SendAutofillTypePredictionsToRenderer(forms); EXPECT_FALSE(GetFieldTypePredictionsAvailable(NULL)); @@ -188,7 +188,7 @@ TEST_F(AutofillDriverImplTest, TypePredictionsSentToRendererWhenEnabled) { FormData form; test::CreateTestAddressFormData(&form); - FormStructure form_structure(form, std::string()); + FormStructure form_structure(form); std::vector<FormStructure*> forms(1, &form_structure); std::vector<FormDataPredictions> expected_type_predictions; FormStructure::GetFieldTypePredictions(forms, &expected_type_predictions); diff --git a/chromium/components/autofill/content/browser/risk/fingerprint.cc b/chromium/components/autofill/content/browser/risk/fingerprint.cc index 635224d1395..1823d8ec29f 100644 --- a/chromium/components/autofill/content/browser/risk/fingerprint.cc +++ b/chromium/components/autofill/content/browser/risk/fingerprint.cc @@ -16,11 +16,13 @@ #include "base/callback.h" #include "base/cpu.h" #include "base/logging.h" +#include "base/memory/weak_ptr.h" #include "base/scoped_observer.h" #include "base/strings/string_split.h" #include "base/strings/utf_string_conversions.h" #include "base/sys_info.h" #include "base/time/time.h" +#include "base/timer/timer.h" #include "base/values.h" #include "components/autofill/content/browser/risk/proto/fingerprint.pb.h" #include "content/public/browser/browser_thread.h" @@ -51,6 +53,10 @@ namespace { const int32 kFingerprinterVersion = 1; +// Maximum amount of time, in seconds, to wait for loading asynchronous +// fingerprint data. +const int kTimeoutSeconds = 4; + // Returns the delta between the local timezone and UTC. base::TimeDelta GetTimezoneOffset() { const base::Time utc = base::Time::Now(); @@ -68,20 +74,6 @@ std::string GetOperatingSystemVersion() { base::SysInfo::OperatingSystemVersion(); } -Fingerprint::MachineCharacteristics::BrowserFeature - DialogTypeToBrowserFeature(DialogType dialog_type) { - switch (dialog_type) { - case DIALOG_TYPE_AUTOCHECKOUT: - return Fingerprint::MachineCharacteristics::FEATURE_AUTOCHECKOUT; - - case DIALOG_TYPE_REQUEST_AUTOCOMPLETE: - return Fingerprint::MachineCharacteristics::FEATURE_REQUEST_AUTOCOMPLETE; - } - - NOTREACHED(); - return Fingerprint::MachineCharacteristics::FEATURE_UNKNOWN; -} - // Adds the list of |fonts| to the |machine|. void AddFontsToFingerprint(const base::ListValue& fonts, Fingerprint::MachineCharacteristics* machine) { @@ -172,7 +164,8 @@ void AddCpuInfoToFingerprint(Fingerprint::MachineCharacteristics* machine) { void AddGpuInfoToFingerprint(Fingerprint::MachineCharacteristics* machine) { const gpu::GPUInfo& gpu_info = content::GpuDataManager::GetInstance()->GetGPUInfo(); - DCHECK(gpu_info.finalized); + if (!gpu_info.finalized) + return; Fingerprint::MachineCharacteristics::Graphics* graphics = machine->mutable_graphics_card(); @@ -188,6 +181,74 @@ void AddGpuInfoToFingerprint(Fingerprint::MachineCharacteristics* machine) { gpu_performance->set_overall_score(gpu_info.performance_stats.overall); } +// Waits for geoposition data to be loaded. Lives on the IO thread. +class GeopositionLoader { + public: + // |callback_| will be called on the UI thread with the loaded geoposition, + // once it is available. + GeopositionLoader( + const base::TimeDelta& timeout, + const base::Callback<void(const content::Geoposition&)>& callback); + ~GeopositionLoader() {} + + private: + // Methods to communicate with the GeolocationProvider. + void OnGotGeoposition(const content::Geoposition& geoposition); + + // The callback that will be called once the geoposition is available. + // Will be called on the UI thread. + const base::Callback<void(const content::Geoposition&)> callback_; + + // The callback used as an "observer" of the GeolocationProvider. + content::GeolocationProvider::LocationUpdateCallback geolocation_callback_; + + // Timer to enforce a maximum timeout before the |callback_| is called, even + // if the geoposition has not been loaded. + base::OneShotTimer<GeopositionLoader> timeout_timer_; +}; + +GeopositionLoader::GeopositionLoader( + const base::TimeDelta& timeout, + const base::Callback<void(const content::Geoposition&)>& callback) + : callback_(callback) { + timeout_timer_.Start(FROM_HERE, timeout, + base::Bind(&GeopositionLoader::OnGotGeoposition, + base::Unretained(this), + content::Geoposition())); + + geolocation_callback_ = + base::Bind(&GeopositionLoader::OnGotGeoposition, base::Unretained(this)); + content::GeolocationProvider::GetInstance()->AddLocationUpdateCallback( + geolocation_callback_, false); +} + +void GeopositionLoader::OnGotGeoposition( + const content::Geoposition& geoposition) { + content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE, + base::Bind(callback_, geoposition)); + + // Unregister as an observer, since this class instance might be destroyed + // after this callback. Note: It's important to unregister *after* posting + // the task above. Unregistering as an observer can have the side-effect of + // modifying the value of |geoposition|. + bool removed = + content::GeolocationProvider::GetInstance()->RemoveLocationUpdateCallback( + geolocation_callback_); + DCHECK(removed); + + delete this; +} + +// Asynchronously loads the user's current geoposition and calls |callback_| on +// the UI thread with the loaded geoposition, once it is available. Expected to +// be called on the IO thread. +void LoadGeoposition( + const base::TimeDelta& timeout, + const base::Callback<void(const content::Geoposition&)>& callback) { + // The loader is responsible for freeing its own memory. + new GeopositionLoader(timeout, callback); +} + // Waits for all asynchronous data required for the fingerprint to be loaded, // then fills out the fingerprint. class FingerprintDataLoader : public content::GpuDataManagerObserver { @@ -201,8 +262,8 @@ class FingerprintDataLoader : public content::GpuDataManagerObserver { const std::string& charset, const std::string& accept_languages, const base::Time& install_time, - DialogType dialog_type, const std::string& app_locale, + const base::TimeDelta& timeout, const base::Callback<void(scoped_ptr<Fingerprint>)>& callback); private: @@ -216,11 +277,6 @@ class FingerprintDataLoader : public content::GpuDataManagerObserver { void OnGotPlugins(const std::vector<content::WebPluginInfo>& plugins); void OnGotGeoposition(const content::Geoposition& geoposition); - // Methods that run on the IO thread to communicate with the - // GeolocationProvider. - void LoadGeoposition(); - void OnGotGeopositionOnIOThread(const content::Geoposition& geoposition); - // If all of the asynchronous data has been loaded, calls |callback_| with // the fingerprint data. void MaybeFillFingerprint(); @@ -232,14 +288,10 @@ class FingerprintDataLoader : public content::GpuDataManagerObserver { // Weak reference because the GpuDataManager class is a singleton. content::GpuDataManager* const gpu_data_manager_; - // Ensures that any observer registratiosn for the GPU data are cleaned up by + // Ensures that any observer registrations for the GPU data are cleaned up by // the time this object is destroyed. ScopedObserver<content::GpuDataManager, FingerprintDataLoader> gpu_observer_; - // The callback used as an "observer" of the GeolocationProvider. Accessed - // only on the IO thread. - content::GeolocationProvider::LocationUpdateCallback geolocation_callback_; - // Data that will be passed on to the next loading phase. See the comment for // GetFingerprint() for a description of these variables. const uint64 obfuscated_gaia_id_; @@ -250,7 +302,6 @@ class FingerprintDataLoader : public content::GpuDataManagerObserver { const std::string charset_; const std::string accept_languages_; const base::Time install_time_; - DialogType dialog_type_; // Data that will be loaded asynchronously. scoped_ptr<base::ListValue> fonts_; @@ -258,6 +309,14 @@ class FingerprintDataLoader : public content::GpuDataManagerObserver { bool waiting_on_plugins_; content::Geoposition geoposition_; + // Timer to enforce a maximum timeout before the |callback_| is called, even + // if not all asynchronous data has been loaded. + base::OneShotTimer<FingerprintDataLoader> timeout_timer_; + + // For invalidating asynchronous callbacks that might arrive after |this| + // instance is destroyed. + base::WeakPtrFactory<FingerprintDataLoader> weak_ptr_factory_; + // The current application locale. std::string app_locale_; @@ -276,8 +335,8 @@ FingerprintDataLoader::FingerprintDataLoader( const std::string& charset, const std::string& accept_languages, const base::Time& install_time, - DialogType dialog_type, const std::string& app_locale, + const base::TimeDelta& timeout, const base::Callback<void(scoped_ptr<Fingerprint>)>& callback) : gpu_data_manager_(content::GpuDataManager::GetInstance()), gpu_observer_(this), @@ -289,11 +348,15 @@ FingerprintDataLoader::FingerprintDataLoader( charset_(charset), accept_languages_(accept_languages), install_time_(install_time), - dialog_type_(dialog_type), waiting_on_plugins_(true), + weak_ptr_factory_(this), callback_(callback) { DCHECK(!install_time_.is_null()); + timeout_timer_.Start(FROM_HERE, timeout, + base::Bind(&FingerprintDataLoader::MaybeFillFingerprint, + weak_ptr_factory_.GetWeakPtr())); + // Load GPU data if needed. if (!gpu_data_manager_->IsCompleteGpuInfoAvailable()) { gpu_observer_.Add(gpu_data_manager_); @@ -303,20 +366,24 @@ FingerprintDataLoader::FingerprintDataLoader( #if defined(ENABLE_PLUGINS) // Load plugin data. content::PluginService::GetInstance()->GetPlugins( - base::Bind(&FingerprintDataLoader::OnGotPlugins, base::Unretained(this))); + base::Bind(&FingerprintDataLoader::OnGotPlugins, + weak_ptr_factory_.GetWeakPtr())); #else waiting_on_plugins_ = false; #endif // Load font data. content::GetFontListAsync( - base::Bind(&FingerprintDataLoader::OnGotFonts, base::Unretained(this))); + base::Bind(&FingerprintDataLoader::OnGotFonts, + weak_ptr_factory_.GetWeakPtr())); // Load geolocation data. content::BrowserThread::PostTask( content::BrowserThread::IO, FROM_HERE, - base::Bind(&FingerprintDataLoader::LoadGeoposition, - base::Unretained(this))); + base::Bind(&LoadGeoposition, + timeout, + base::Bind(&FingerprintDataLoader::OnGotGeoposition, + weak_ptr_factory_.GetWeakPtr()))); } void FingerprintDataLoader::OnGpuInfoUpdate() { @@ -352,38 +419,15 @@ void FingerprintDataLoader::OnGotGeoposition( MaybeFillFingerprint(); } -void FingerprintDataLoader::LoadGeoposition() { - geolocation_callback_ = - base::Bind(&FingerprintDataLoader::OnGotGeopositionOnIOThread, - base::Unretained(this)); - content::GeolocationProvider::GetInstance()->AddLocationUpdateCallback( - geolocation_callback_, false); -} - -void FingerprintDataLoader::OnGotGeopositionOnIOThread( - const content::Geoposition& geoposition) { - content::BrowserThread::PostTask( - content::BrowserThread::UI, FROM_HERE, - base::Bind(&FingerprintDataLoader::OnGotGeoposition, - base::Unretained(this), geoposition)); - - // Unregister as an observer, since this class instance might be destroyed - // after this callback. Note: It's important to unregister *after* posting - // the task above. Unregistering as an observer can have the side-effect of - // modifying the value of |geoposition|. - bool removed = - content::GeolocationProvider::GetInstance()->RemoveLocationUpdateCallback( - geolocation_callback_); - DCHECK(removed); -} - void FingerprintDataLoader::MaybeFillFingerprint() { - // If all of the data has been loaded, fill the fingerprint and clean up. - if (gpu_data_manager_->IsCompleteGpuInfoAvailable() && - fonts_ && - !waiting_on_plugins_ && - (geoposition_.Validate() || - geoposition_.error_code != content::Geoposition::ERROR_CODE_NONE)) { + // If all of the data has been loaded, or if the |timeout_timer_| has expired, + // fill the fingerprint and clean up. + if (!timeout_timer_.IsRunning() || + (gpu_data_manager_->IsCompleteGpuInfoAvailable() && + fonts_ && + !waiting_on_plugins_ && + (geoposition_.Validate() || + geoposition_.error_code != content::Geoposition::ERROR_CODE_NONE))) { FillFingerprint(); delete this; } @@ -404,8 +448,10 @@ void FingerprintDataLoader::FillFingerprint() { machine->set_user_agent(content::GetUserAgent(GURL())); machine->set_ram(base::SysInfo::AmountOfPhysicalMemory()); machine->set_browser_build(version_); - machine->set_browser_feature(DialogTypeToBrowserFeature(dialog_type_)); - AddFontsToFingerprint(*fonts_, machine); + machine->set_browser_feature( + Fingerprint::MachineCharacteristics::FEATURE_REQUEST_AUTOCOMPLETE); + if (fonts_) + AddFontsToFingerprint(*fonts_, machine); AddPluginsToFingerprint(plugins_, machine); AddAcceptLanguagesToFingerprint(accept_languages_, machine); AddScreenInfoToFingerprint(screen_info_, machine); @@ -430,7 +476,8 @@ void FingerprintDataLoader::FillFingerprint() { // available to JS. // TODO(isherman): Record more user behavior data. - if (geoposition_.error_code == content::Geoposition::ERROR_CODE_NONE) { + if (geoposition_.Validate() && + geoposition_.error_code == content::Geoposition::ERROR_CODE_NONE) { Fingerprint::UserCharacteristics::Location* location = fingerprint->mutable_user_characteristics()->mutable_location(); location->set_altitude(geoposition_.altitude); @@ -463,14 +510,14 @@ void GetFingerprintInternal( const std::string& charset, const std::string& accept_languages, const base::Time& install_time, - DialogType dialog_type, const std::string& app_locale, + const base::TimeDelta& timeout, const base::Callback<void(scoped_ptr<Fingerprint>)>& callback) { // Begin loading all of the data that we need to load asynchronously. // This class is responsible for freeing its own memory. new FingerprintDataLoader(obfuscated_gaia_id, window_bounds, content_bounds, screen_info, version, charset, accept_languages, - install_time, dialog_type, app_locale, callback); + install_time, app_locale, timeout, callback); } } // namespace internal @@ -483,7 +530,6 @@ void GetFingerprint( const std::string& charset, const std::string& accept_languages, const base::Time& install_time, - DialogType dialog_type, const std::string& app_locale, const base::Callback<void(scoped_ptr<Fingerprint>)>& callback) { gfx::Rect content_bounds; @@ -497,8 +543,8 @@ void GetFingerprint( internal::GetFingerprintInternal( obfuscated_gaia_id, window_bounds, content_bounds, screen_info, version, - charset, accept_languages, install_time, dialog_type, app_locale, - callback); + charset, accept_languages, install_time, app_locale, + base::TimeDelta::FromSeconds(kTimeoutSeconds), callback); } } // namespace risk diff --git a/chromium/components/autofill/content/browser/risk/fingerprint.h b/chromium/components/autofill/content/browser/risk/fingerprint.h index d66bde43b86..283e60777ff 100644 --- a/chromium/components/autofill/content/browser/risk/fingerprint.h +++ b/chromium/components/autofill/content/browser/risk/fingerprint.h @@ -58,7 +58,6 @@ void GetFingerprint( const std::string& charset, const std::string& accept_languages, const base::Time& install_time, - DialogType dialog_type, const std::string& app_locale, const base::Callback<void(scoped_ptr<Fingerprint>)>& callback); diff --git a/chromium/components/autofill/content/browser/wallet/mock_wallet_client.h b/chromium/components/autofill/content/browser/wallet/mock_wallet_client.h index c8d03b33a46..a2f1fea7f5d 100644 --- a/chromium/components/autofill/content/browser/wallet/mock_wallet_client.h +++ b/chromium/components/autofill/content/browser/wallet/mock_wallet_client.h @@ -51,12 +51,6 @@ class MockWalletClient : public WalletClient { Address* address, const GURL& source_url)); - MOCK_METHOD4(SendAutocheckoutStatus, - void(autofill::AutocheckoutStatus status, - const GURL& source_url, - const std::vector<AutocheckoutStatistic>& latency_statistics, - const std::string& google_transaction_id)); - private: DISALLOW_COPY_AND_ASSIGN(MockWalletClient); }; diff --git a/chromium/components/autofill/content/browser/wallet/wallet_client.cc b/chromium/components/autofill/content/browser/wallet/wallet_client.cc index bb5c525273d..04addee47dc 100644 --- a/chromium/components/autofill/content/browser/wallet/wallet_client.cc +++ b/chromium/components/autofill/content/browser/wallet/wallet_client.cc @@ -52,40 +52,6 @@ const size_t kMaxBits = 56; // accept. const size_t kMinBits = 40; -std::string AutocheckoutStatusToString(AutocheckoutStatus status) { - switch (status) { - case MISSING_FIELDMAPPING: - return "MISSING_FIELDMAPPING"; - case MISSING_ADVANCE: - return "MISSING_ADVANCE"; - case MISSING_CLICK_ELEMENT_BEFORE_FORM_FILLING: - return "MISSING_CLICK_ELEMENT_BEFORE_FORM_FILLING"; - case MISSING_CLICK_ELEMENT_AFTER_FORM_FILLING: - return "MISSING_CLICK_ELEMENT_AFTER_FORM_FILLING"; - case CANNOT_PROCEED: - return "CANNOT_PROCEED"; - case SUCCESS: - // SUCCESS cannot be sent to the server as it will result in a failure. - NOTREACHED(); - return "ERROR"; - case AUTOCHECKOUT_STATUS_NUM_STATUS: - NOTREACHED(); - } - NOTREACHED(); - return "NOT_POSSIBLE"; -} - -std::string DialogTypeToFeatureString(autofill::DialogType dialog_type) { - switch (dialog_type) { - case DIALOG_TYPE_REQUEST_AUTOCOMPLETE: - return "REQUEST_AUTOCOMPLETE"; - case DIALOG_TYPE_AUTOCHECKOUT: - return "AUTOCHECKOUT"; - } - NOTREACHED(); - return "NOT_POSSIBLE"; -} - std::string RiskCapabilityToString( WalletClient::RiskCapability risk_capability) { switch (risk_capability) { @@ -122,9 +88,9 @@ WalletClient::ErrorType StringToErrorType(const std::string& error_type) { // Get the more specific WalletClient::ErrorType when the error is // |BUYER_ACCOUNT_ERROR|. WalletClient::ErrorType BuyerErrorStringToErrorType( - const std::string& buyer_error_type) { + const std::string& message_type_for_buyer) { std::string trimmed; - TrimWhitespaceASCII(buyer_error_type, + TrimWhitespaceASCII(message_type_for_buyer, TRIM_ALL, &trimmed); if (LowerCaseEqualsASCII(trimmed, "bla_country_not_supported")) @@ -245,31 +211,26 @@ AutofillMetrics::WalletRequiredActionMetric RequiredActionToUmaMetric( const char kAcceptedLegalDocumentKey[] = "accepted_legal_document"; const char kApiKeyKey[] = "api_key"; const char kAuthResultKey[] = "auth_result"; -const char kBuyerErrorTypeKey[] = "wallet_error.buyer_error_type"; -const char kEncryptedOtpKey[] = "encrypted_otp"; const char kErrorTypeKey[] = "wallet_error.error_type"; const char kFeatureKey[] = "feature"; const char kGoogleTransactionIdKey[] = "google_transaction_id"; const char kInstrumentIdKey[] = "instrument_id"; const char kInstrumentKey[] = "instrument"; -const char kInstrumentEscrowHandleKey[] = "instrument_escrow_handle"; const char kInstrumentExpMonthKey[] = "instrument.credit_card.exp_month"; const char kInstrumentExpYearKey[] = "instrument.credit_card.exp_year"; const char kInstrumentType[] = "instrument.type"; const char kInstrumentPhoneNumberKey[] = "instrument_phone_number"; const char kMerchantDomainKey[] = "merchant_domain"; +const char kMessageTypeForBuyerKey[] = "wallet_error.message_type_for_buyer"; +const char kNewWalletUser[] = "new_wallet_user"; const char kPhoneNumberRequired[] = "phone_number_required"; -const char kReasonKey[] = "reason"; const char kRiskCapabilitiesKey[] = "supported_risk_challenge"; const char kRiskParamsKey[] = "risk_params"; const char kSelectedAddressIdKey[] = "selected_address_id"; const char kSelectedInstrumentIdKey[] = "selected_instrument_id"; -const char kSessionMaterialKey[] = "session_material"; const char kShippingAddressIdKey[] = "shipping_address_id"; const char kShippingAddressKey[] = "shipping_address"; const char kShippingAddressRequired[] = "shipping_address_required"; -const char kAutocheckoutStepsKey[] = "steps"; -const char kSuccessKey[] = "success"; const char kUpgradedBillingAddressKey[] = "upgraded_billing_address"; const char kUpgradedInstrumentIdKey[] = "upgraded_instrument_id"; const char kUseMinimalAddresses[] = "use_minimal_addresses"; @@ -281,12 +242,14 @@ WalletClient::FullWalletRequest::FullWalletRequest( const std::string& address_id, const GURL& source_url, const std::string& google_transaction_id, - const std::vector<RiskCapability> risk_capabilities) + const std::vector<RiskCapability> risk_capabilities, + bool new_wallet_user) : instrument_id(instrument_id), address_id(address_id), source_url(source_url), google_transaction_id(google_transaction_id), - risk_capabilities(risk_capabilities) {} + risk_capabilities(risk_capabilities), + new_wallet_user(new_wallet_user) {} WalletClient::FullWalletRequest::~FullWalletRequest() {} @@ -368,6 +331,7 @@ void WalletClient::GetFullWallet(const FullWalletRequest& full_wallet_request) { request_dict.SetString(kRiskParamsKey, delegate_->GetRiskData()); request_dict.SetBoolean(kUseMinimalAddresses, false); request_dict.SetBoolean(kPhoneNumberRequired, true); + request_dict.SetBoolean(kNewWalletUser, full_wallet_request.new_wallet_user); request_dict.SetString(kSelectedInstrumentIdKey, full_wallet_request.instrument_id); @@ -377,8 +341,7 @@ void WalletClient::GetFullWallet(const FullWalletRequest& full_wallet_request) { full_wallet_request.source_url.GetWithEmptyPath().spec()); request_dict.SetString(kGoogleTransactionIdKey, full_wallet_request.google_transaction_id); - request_dict.SetString(kFeatureKey, - DialogTypeToFeatureString(delegate_->GetDialogType())); + request_dict.SetString(kFeatureKey, "REQUEST_AUTOCOMPLETE"); scoped_ptr<base::ListValue> risk_capabilities_list(new base::ListValue()); for (std::vector<RiskCapability>::const_iterator it = @@ -528,52 +491,6 @@ void WalletClient::GetWalletItems(const GURL& source_url) { MakeWalletRequest(GetGetWalletItemsUrl(), post_body, kJsonMimeType); } -void WalletClient::SendAutocheckoutStatus( - AutocheckoutStatus status, - const GURL& source_url, - const std::vector<AutocheckoutStatistic>& latency_statistics, - const std::string& google_transaction_id) { - DVLOG(1) << "Sending Autocheckout Status: " << status - << " for: " << source_url; - if (HasRequestInProgress()) { - pending_requests_.push(base::Bind(&WalletClient::SendAutocheckoutStatus, - base::Unretained(this), - status, - source_url, - latency_statistics, - google_transaction_id)); - return; - } - - DCHECK_EQ(NO_PENDING_REQUEST, request_type_); - request_type_ = SEND_STATUS; - - base::DictionaryValue request_dict; - request_dict.SetString(kApiKeyKey, google_apis::GetAPIKey()); - bool success = status == SUCCESS; - request_dict.SetBoolean(kSuccessKey, success); - request_dict.SetString(kMerchantDomainKey, - source_url.GetWithEmptyPath().spec()); - if (!success) - request_dict.SetString(kReasonKey, AutocheckoutStatusToString(status)); - if (!latency_statistics.empty()) { - scoped_ptr<base::ListValue> latency_statistics_json( - new base::ListValue()); - for (size_t i = 0; i < latency_statistics.size(); ++i) { - latency_statistics_json->Append( - latency_statistics[i].ToDictionary().release()); - } - request_dict.Set(kAutocheckoutStepsKey, - latency_statistics_json.release()); - } - request_dict.SetString(kGoogleTransactionIdKey, google_transaction_id); - - std::string post_body; - base::JSONWriter::Write(&request_dict, &post_body); - - MakeWalletRequest(GetSendStatusUrl(), post_body, kJsonMimeType); -} - bool WalletClient::HasRequestInProgress() const { return request_; } @@ -639,10 +556,8 @@ void WalletClient::MakeWalletRequest(const GURL& url, request_->Start(); delegate_->GetMetricLogger().LogWalletErrorMetric( - delegate_->GetDialogType(), AutofillMetrics::WALLET_ERROR_BASELINE_ISSUED_REQUEST); delegate_->GetMetricLogger().LogWalletRequiredActionMetric( - delegate_->GetDialogType(), AutofillMetrics::WALLET_REQUIRED_ACTION_BASELINE_ISSUED_REQUEST); } @@ -675,6 +590,8 @@ void WalletClient::OnURLFetchComplete( scoped_ptr<base::DictionaryValue> response_dict; int response_code = source->GetResponseCode(); + delegate_->GetMetricLogger().LogWalletResponseCode(response_code); + switch (response_code) { // HTTP_BAD_REQUEST means the arguments are invalid. No point retrying. case net::HTTP_BAD_REQUEST: { @@ -703,12 +620,14 @@ void WalletClient::OnURLFetchComplete( WalletClient::ErrorType error_type = StringToErrorType(error_type_string); if (error_type == BUYER_ACCOUNT_ERROR) { - // If the error_type is |BUYER_ACCOUNT_ERROR|, then buyer_error_type - // field contains more specific information about the error. - std::string buyer_error_type_string; - if (response_dict->GetString(kBuyerErrorTypeKey, - &buyer_error_type_string)) { - error_type = BuyerErrorStringToErrorType(buyer_error_type_string); + // If the error_type is |BUYER_ACCOUNT_ERROR|, then + // message_type_for_buyer field contains more specific information + // about the error. + std::string message_type_for_buyer_string; + if (response_dict->GetString(kMessageTypeForBuyerKey, + &message_type_for_buyer_string)) { + error_type = BuyerErrorStringToErrorType( + message_type_for_buyer_string); } } @@ -728,9 +647,8 @@ void WalletClient::OnURLFetchComplete( RequestType type = request_type_; request_type_ = NO_PENDING_REQUEST; - if (!(type == ACCEPT_LEGAL_DOCUMENTS || type == SEND_STATUS) && - !response_dict) { - HandleMalformedResponse(scoped_request.get()); + if (type != ACCEPT_LEGAL_DOCUMENTS && !response_dict) { + HandleMalformedResponse(type, scoped_request.get()); return; } @@ -749,14 +667,11 @@ void WalletClient::OnURLFetchComplete( delegate_->OnDidAuthenticateInstrument( LowerCaseEqualsASCII(trimmed, "success")); } else { - HandleMalformedResponse(scoped_request.get()); + HandleMalformedResponse(type, scoped_request.get()); } break; } - case SEND_STATUS: - break; - case GET_FULL_WALLET: { scoped_ptr<FullWallet> full_wallet( FullWallet::CreateFullWallet(*response_dict)); @@ -765,7 +680,7 @@ void WalletClient::OnURLFetchComplete( LogRequiredActions(full_wallet->required_actions()); delegate_->OnDidGetFullWallet(full_wallet.Pass()); } else { - HandleMalformedResponse(scoped_request.get()); + HandleMalformedResponse(type, scoped_request.get()); } break; } @@ -777,7 +692,7 @@ void WalletClient::OnURLFetchComplete( LogRequiredActions(wallet_items->required_actions()); delegate_->OnDidGetWalletItems(wallet_items.Pass()); } else { - HandleMalformedResponse(scoped_request.get()); + HandleMalformedResponse(type, scoped_request.get()); } break; } @@ -794,7 +709,7 @@ void WalletClient::OnURLFetchComplete( GetFormFieldErrors(*response_dict, &form_errors); if (instrument_id.empty() && shipping_address_id.empty() && required_actions.empty()) { - HandleMalformedResponse(scoped_request.get()); + HandleMalformedResponse(type, scoped_request.get()); } else { LogRequiredActions(required_actions); delegate_->OnDidSaveToWallet(instrument_id, @@ -819,9 +734,13 @@ void WalletClient::StartNextPendingRequest() { next_request.Run(); } -void WalletClient::HandleMalformedResponse(net::URLFetcher* request) { +void WalletClient::HandleMalformedResponse(RequestType request_type, + net::URLFetcher* request) { // Called to inform exponential backoff logic of the error. request->ReceivedContentWasMalformed(); + // Record failed API call in metrics. + delegate_->GetMetricLogger().LogWalletMalformedResponseMetric( + RequestTypeToUmaMetric(request_type)); HandleWalletError(MALFORMED_RESPONSE); } @@ -870,7 +789,7 @@ void WalletClient::HandleWalletError(WalletClient::ErrorType error_type) { delegate_->OnWalletError(error_type); delegate_->GetMetricLogger().LogWalletErrorMetric( - delegate_->GetDialogType(), ErrorTypeToUmaMetric(error_type)); + ErrorTypeToUmaMetric(error_type)); } // Logs an UMA metric for each of the |required_actions|. @@ -878,7 +797,6 @@ void WalletClient::LogRequiredActions( const std::vector<RequiredAction>& required_actions) const { for (size_t i = 0; i < required_actions.size(); ++i) { delegate_->GetMetricLogger().LogWalletRequiredActionMetric( - delegate_->GetDialogType(), RequiredActionToUmaMetric(required_actions[i])); } } @@ -896,8 +814,6 @@ AutofillMetrics::WalletApiCallMetric WalletClient::RequestTypeToUmaMetric( return AutofillMetrics::GET_WALLET_ITEMS; case SAVE_TO_WALLET: return AutofillMetrics::SAVE_TO_WALLET; - case SEND_STATUS: - return AutofillMetrics::SEND_STATUS; case NO_PENDING_REQUEST: NOTREACHED(); return AutofillMetrics::UNKNOWN_API_CALL; diff --git a/chromium/components/autofill/content/browser/wallet/wallet_client.h b/chromium/components/autofill/content/browser/wallet/wallet_client.h index d4f96073127..89827a8611d 100644 --- a/chromium/components/autofill/content/browser/wallet/wallet_client.h +++ b/chromium/components/autofill/content/browser/wallet/wallet_client.h @@ -14,12 +14,10 @@ #include "base/memory/weak_ptr.h" #include "base/time/time.h" #include "base/values.h" -#include "components/autofill/content/browser/autocheckout_statistic.h" #include "components/autofill/content/browser/wallet/full_wallet.h" #include "components/autofill/content/browser/wallet/wallet_items.h" #include "components/autofill/core/browser/autofill_manager_delegate.h" #include "components/autofill/core/browser/autofill_metrics.h" -#include "components/autofill/core/common/autocheckout_status.h" #include "net/url_request/url_fetcher_delegate.h" #include "testing/gtest/include/gtest/gtest_prod.h" #include "url/gurl.h" @@ -56,8 +54,6 @@ class WalletClientDelegate; // a) GetFullWallet may return a Risk challenge for the user. In that case, // the user will need to verify who they are by authenticating their // chosen backing instrument through AuthenticateInstrument -// 4) If the user initiated Autocheckout, SendAutocheckoutStatus to notify -// Online Wallet of the status flow to record various metrics. // // WalletClient is designed so only one request to Online Wallet can be outgoing // at any one time. If |HasRequestInProgress()| is true while calling e.g. @@ -110,7 +106,8 @@ class WalletClient : public net::URLFetcherDelegate { const std::string& address_id, const GURL& source_url, const std::string& google_transaction_id, - const std::vector<RiskCapability> risk_capabilities); + const std::vector<RiskCapability> risk_capabilities, + bool new_wallet_user); ~FullWalletRequest(); // The ID of the backing instrument. Should have been selected by the user @@ -130,6 +127,9 @@ class WalletClient : public net::URLFetcherDelegate { // The Risk challenges supported by the user of WalletClient std::vector<RiskCapability> risk_capabilities; + // True if the user does not have Wallet profile. + bool new_wallet_user; + private: DISALLOW_ASSIGN(FullWalletRequest); }; @@ -174,17 +174,6 @@ class WalletClient : public net::URLFetcherDelegate { scoped_ptr<Address> address, const GURL& source_url); - // SendAutocheckoutStatus is used for tracking the success of Autocheckout - // flows. |status| is the result of the flow, |source_url| is the domain - // where the purchase occured, and |google_transaction_id| is the same as the - // one provided by GetWalletItems. |latency_statistics| contain statistics - // required to measure Autocheckout process. - virtual void SendAutocheckoutStatus( - autofill::AutocheckoutStatus status, - const GURL& source_url, - const std::vector<AutocheckoutStatistic>& latency_statistics, - const std::string& google_transaction_id); - bool HasRequestInProgress() const; // Cancels and clears the current |request_| and |pending_requests_| (if any). @@ -201,7 +190,6 @@ class WalletClient : public net::URLFetcherDelegate { GET_FULL_WALLET, GET_WALLET_ITEMS, SAVE_TO_WALLET, - SEND_STATUS, }; // Like AcceptLegalDocuments, but takes a vector of document ids. @@ -217,7 +205,8 @@ class WalletClient : public net::URLFetcherDelegate { const std::string& mime_type); // Performs bookkeeping tasks for any invalid requests. - void HandleMalformedResponse(net::URLFetcher* request); + void HandleMalformedResponse(RequestType request_type, + net::URLFetcher* request); void HandleNetworkError(int response_code); void HandleWalletError(ErrorType error_type); diff --git a/chromium/components/autofill/content/browser/wallet/wallet_client_delegate.h b/chromium/components/autofill/content/browser/wallet/wallet_client_delegate.h index 8de95046e5f..8380fbf27b8 100644 --- a/chromium/components/autofill/content/browser/wallet/wallet_client_delegate.h +++ b/chromium/components/autofill/content/browser/wallet/wallet_client_delegate.h @@ -33,9 +33,6 @@ class WalletClientDelegate { // Wallet metrics. virtual const AutofillMetrics& GetMetricLogger() const = 0; - // Returns the dialog type that the delegate corresponds to. - virtual DialogType GetDialogType() const = 0; - // Returns the serialized fingerprint data to be sent to the Risk server. virtual std::string GetRiskData() const = 0; diff --git a/chromium/components/autofill/content/browser/wallet/wallet_client_unittest.cc b/chromium/components/autofill/content/browser/wallet/wallet_client_unittest.cc index 031e5cd958b..32d598ea6d9 100644 --- a/chromium/components/autofill/content/browser/wallet/wallet_client_unittest.cc +++ b/chromium/components/autofill/content/browser/wallet/wallet_client_unittest.cc @@ -13,7 +13,6 @@ #include "base/strings/stringprintf.h" #include "base/values.h" #include "chrome/test/base/testing_profile.h" -#include "components/autofill/content/browser/autocheckout_steps.h" #include "components/autofill/content/browser/wallet/full_wallet.h" #include "components/autofill/content/browser/wallet/instrument.h" #include "components/autofill/content/browser/wallet/wallet_client.h" @@ -21,7 +20,6 @@ #include "components/autofill/content/browser/wallet/wallet_items.h" #include "components/autofill/content/browser/wallet/wallet_test_util.h" #include "components/autofill/core/browser/autofill_metrics.h" -#include "components/autofill/core/common/autocheckout_status.h" #include "content/public/test/test_browser_thread_bundle.h" #include "net/base/escape.h" #include "net/base/net_errors.h" @@ -319,6 +317,23 @@ const char kGetFullWalletValidRequest[] = "\"feature\":\"REQUEST_AUTOCOMPLETE\"," "\"google_transaction_id\":\"google_transaction_id\"," "\"merchant_domain\":\"https://example.com/\"," + "\"new_wallet_user\":false," + "\"phone_number_required\":true," + "\"risk_params\":\"risky business\"," + "\"selected_address_id\":\"shipping_address_id\"," + "\"selected_instrument_id\":\"instrument_id\"," + "\"supported_risk_challenge\":" + "[" + "]," + "\"use_minimal_addresses\":false" + "}"; + +const char kGetFullWalletValidRequestNewUser[] = + "{" + "\"feature\":\"REQUEST_AUTOCOMPLETE\"," + "\"google_transaction_id\":\"google_transaction_id\"," + "\"merchant_domain\":\"https://example.com/\"," + "\"new_wallet_user\":true," "\"phone_number_required\":true," "\"risk_params\":\"risky business\"," "\"selected_address_id\":\"shipping_address_id\"," @@ -334,6 +349,7 @@ const char kGetFullWalletWithRiskCapabilitesValidRequest[] = "\"feature\":\"REQUEST_AUTOCOMPLETE\"," "\"google_transaction_id\":\"google_transaction_id\"," "\"merchant_domain\":\"https://example.com/\"," + "\"new_wallet_user\":false," "\"phone_number_required\":true," "\"risk_params\":\"risky business\"," "\"selected_address_id\":\"shipping_address_id\"," @@ -469,30 +485,6 @@ const char kSaveInstrumentAndAddressValidRequest[] = "\"use_minimal_addresses\":false" "}"; -const char kSendAutocheckoutStatusOfSuccessValidRequest[] = - "{" - "\"google_transaction_id\":\"google_transaction_id\"," - "\"merchant_domain\":\"https://example.com/\"," - "\"success\":true" - "}"; - -const char kSendAutocheckoutStatusWithStatisticsValidRequest[] = - "{" - "\"google_transaction_id\":\"google_transaction_id\"," - "\"merchant_domain\":\"https://example.com/\"," - "\"steps\":[{\"step_description\":\"1_AUTOCHECKOUT_STEP_SHIPPING\"" - ",\"time_taken\":100}]," - "\"success\":true" - "}"; - -const char kSendAutocheckoutStatusOfFailureValidRequest[] = - "{" - "\"google_transaction_id\":\"google_transaction_id\"," - "\"merchant_domain\":\"https://example.com/\"," - "\"reason\":\"CANNOT_PROCEED\"," - "\"success\":false" - "}"; - const char kUpdateAddressValidRequest[] = "{" "\"merchant_domain\":\"https://example.com/\"," @@ -589,11 +581,11 @@ class MockAutofillMetrics : public AutofillMetrics { MOCK_CONST_METHOD2(LogWalletApiCallDuration, void(WalletApiCallMetric metric, const base::TimeDelta& duration)); - MOCK_CONST_METHOD2(LogWalletErrorMetric, - void(DialogType dialog_type, WalletErrorMetric metric)); - MOCK_CONST_METHOD2(LogWalletRequiredActionMetric, - void(DialogType dialog_type, - WalletRequiredActionMetric action)); + MOCK_CONST_METHOD1(LogWalletErrorMetric, void(WalletErrorMetric metric)); + MOCK_CONST_METHOD1(LogWalletRequiredActionMetric, + void(WalletRequiredActionMetric action)); + MOCK_CONST_METHOD1(LogWalletMalformedResponseMetric, + void(WalletApiCallMetric metric)); private: DISALLOW_COPY_AND_ASSIGN(MockAutofillMetrics); }; @@ -610,10 +602,6 @@ class MockWalletClientDelegate : public WalletClientDelegate { return metric_logger_; } - virtual DialogType GetDialogType() const OVERRIDE { - return DIALOG_TYPE_REQUEST_AUTOCOMPLETE; - } - virtual std::string GetRiskData() const OVERRIDE { return "risky business"; } @@ -637,26 +625,26 @@ class MockWalletClientDelegate : public WalletClientDelegate { LogWalletApiCallDuration(metric, testing::_)).Times(times); } + void ExpectLogWalletMalformedResponse( + AutofillMetrics::WalletApiCallMetric metric) { + EXPECT_CALL(metric_logger_, + LogWalletMalformedResponseMetric(metric)).Times(1); + } + void ExpectWalletErrorMetric(AutofillMetrics::WalletErrorMetric metric) { - EXPECT_CALL( - metric_logger_, - LogWalletErrorMetric( - DIALOG_TYPE_REQUEST_AUTOCOMPLETE, metric)).Times(1); + EXPECT_CALL(metric_logger_, LogWalletErrorMetric(metric)).Times(1); } void ExpectWalletRequiredActionMetric( AutofillMetrics::WalletRequiredActionMetric metric) { - EXPECT_CALL( - metric_logger_, - LogWalletRequiredActionMetric( - DIALOG_TYPE_REQUEST_AUTOCOMPLETE, metric)).Times(1); + EXPECT_CALL(metric_logger_, + LogWalletRequiredActionMetric(metric)).Times(1); } void ExpectBaselineMetrics() { EXPECT_CALL( metric_logger_, LogWalletErrorMetric( - DIALOG_TYPE_REQUEST_AUTOCOMPLETE, AutofillMetrics::WALLET_ERROR_BASELINE_ISSUED_REQUEST)) .Times(1); ExpectWalletRequiredActionMetric( @@ -797,7 +785,7 @@ class WalletClientTest : public testing::Test { void TestWalletErrorCode( const std::string& error_type_string, - const std::string& buyer_error_type_string, + const std::string& message_type_for_buyer_string, WalletClient::ErrorType expected_error_type, AutofillMetrics::WalletErrorMetric expected_autofill_metric) { static const char kResponseTemplate[] = @@ -825,25 +813,22 @@ class WalletClientTest : public testing::Test { " }" "}"; EXPECT_CALL(delegate_, OnWalletError(expected_error_type)).Times(1); - delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::SEND_STATUS, 1); + delegate_.ExpectLogWalletApiCallDuration( + AutofillMetrics::GET_WALLET_ITEMS, 1); delegate_.ExpectBaselineMetrics(); delegate_.ExpectWalletErrorMetric(expected_autofill_metric); - std::vector<AutocheckoutStatistic> statistics; - wallet_client_->SendAutocheckoutStatus(autofill::SUCCESS, - GURL(kMerchantUrl), - statistics, - "google_transaction_id"); + wallet_client_->GetWalletItems(GURL(kMerchantUrl)); std::string buyer_error; - if (!buyer_error_type_string.empty()) { - buyer_error = base::StringPrintf("\"buyer_error_type\":\"%s\",", - buyer_error_type_string.c_str()); + if (!message_type_for_buyer_string.empty()) { + buyer_error = base::StringPrintf("\"message_type_for_buyer\":\"%s\",", + message_type_for_buyer_string.c_str()); } std::string response = base::StringPrintf(kResponseTemplate, error_type_string.c_str(), buyer_error.c_str()); VerifyAndFinishRequest(net::HTTP_INTERNAL_SERVER_ERROR, - kSendAutocheckoutStatusOfSuccessValidRequest, + kGetWalletItemsValidRequest, response); } @@ -875,11 +860,11 @@ class WalletClientTest : public testing::Test { TEST_F(WalletClientTest, WalletErrorCodes) { struct { std::string error_type_string; - std::string buyer_error_type_string; + std::string message_type_for_buyer_string; WalletClient::ErrorType expected_error_type; AutofillMetrics::WalletErrorMetric expected_autofill_metric; } test_cases[] = { - // General |BUYER_ACCOUNT_ERROR| with no |buyer_error_type_string|. + // General |BUYER_ACCOUNT_ERROR| with no |message_type_for_buyer_string|. { "buyer_account_error", "", @@ -887,21 +872,22 @@ TEST_F(WalletClientTest, WalletErrorCodes) { AutofillMetrics::WALLET_BUYER_ACCOUNT_ERROR }, // |BUYER_ACCOUNT_ERROR| with "buyer_legal_address_not_supported" in - // buyer_error_type field. + // message_type_for_buyer field. { "buyer_account_error", "bla_country_not_supported", WalletClient::BUYER_LEGAL_ADDRESS_NOT_SUPPORTED, AutofillMetrics::WALLET_BUYER_LEGAL_ADDRESS_NOT_SUPPORTED }, - // |BUYER_ACCOUNT_ERROR| with KYC error code in buyer_error_type field. + // |BUYER_ACCOUNT_ERROR| with KYC error code in message_type_for_buyer + // field. { "buyer_account_error", "buyer_kyc_error", WalletClient::UNVERIFIED_KNOW_YOUR_CUSTOMER_STATUS, AutofillMetrics::WALLET_UNVERIFIED_KNOW_YOUR_CUSTOMER_STATUS }, - // |BUYER_ACCOUNT_ERROR| with un-recognizable |buyer_error_type|. + // |BUYER_ACCOUNT_ERROR| with un-recognizable |message_type_for_buyer|. { "buyer_account_error", "random_string", @@ -956,11 +942,12 @@ TEST_F(WalletClientTest, WalletErrorCodes) { for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) { SCOPED_TRACE( - base::StringPrintf("%s - %s", - test_cases[i].error_type_string.c_str(), - test_cases[i].buyer_error_type_string.c_str())); + base::StringPrintf( + "%s - %s", + test_cases[i].error_type_string.c_str(), + test_cases[i].message_type_for_buyer_string.c_str())); TestWalletErrorCode(test_cases[i].error_type_string, - test_cases[i].buyer_error_type_string, + test_cases[i].message_type_for_buyer_string, test_cases[i].expected_error_type, test_cases[i].expected_autofill_metric); } @@ -969,36 +956,17 @@ TEST_F(WalletClientTest, WalletErrorCodes) { TEST_F(WalletClientTest, WalletErrorResponseMissing) { EXPECT_CALL(delegate_, OnWalletError( WalletClient::UNKNOWN_ERROR)).Times(1); - delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::SEND_STATUS, 1); + delegate_.ExpectLogWalletApiCallDuration( + AutofillMetrics::GET_WALLET_ITEMS, 1); delegate_.ExpectBaselineMetrics(); delegate_.ExpectWalletErrorMetric(AutofillMetrics::WALLET_UNKNOWN_ERROR); - std::vector<AutocheckoutStatistic> statistics; - wallet_client_->SendAutocheckoutStatus(autofill::SUCCESS, - GURL(kMerchantUrl), - statistics, - "google_transaction_id"); + wallet_client_->GetWalletItems(GURL(kMerchantUrl)); VerifyAndFinishRequest(net::HTTP_INTERNAL_SERVER_ERROR, - kSendAutocheckoutStatusOfSuccessValidRequest, + kGetWalletItemsValidRequest, kErrorTypeMissingInResponse); } -TEST_F(WalletClientTest, NetworkFailureOnExpectedVoidResponse) { - EXPECT_CALL(delegate_, OnWalletError(WalletClient::NETWORK_ERROR)).Times(1); - delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::SEND_STATUS, 1); - delegate_.ExpectBaselineMetrics(); - delegate_.ExpectWalletErrorMetric(AutofillMetrics::WALLET_NETWORK_ERROR); - - std::vector<AutocheckoutStatistic> statistics; - wallet_client_->SendAutocheckoutStatus(autofill::SUCCESS, - GURL(kMerchantUrl), - statistics, - "google_transaction_id"); - VerifyAndFinishRequest(net::HTTP_UNAUTHORIZED, - kSendAutocheckoutStatusOfSuccessValidRequest, - std::string()); -} - TEST_F(WalletClientTest, NetworkFailureOnExpectedResponse) { EXPECT_CALL(delegate_, OnWalletError(WalletClient::NETWORK_ERROR)).Times(1); delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::GET_WALLET_ITEMS, @@ -1014,17 +982,14 @@ TEST_F(WalletClientTest, NetworkFailureOnExpectedResponse) { TEST_F(WalletClientTest, RequestError) { EXPECT_CALL(delegate_, OnWalletError(WalletClient::BAD_REQUEST)).Times(1); - delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::SEND_STATUS, 1); + delegate_.ExpectLogWalletApiCallDuration( + AutofillMetrics::GET_WALLET_ITEMS, 1); delegate_.ExpectBaselineMetrics(); delegate_.ExpectWalletErrorMetric(AutofillMetrics::WALLET_BAD_REQUEST); - std::vector<AutocheckoutStatistic> statistics; - wallet_client_->SendAutocheckoutStatus(autofill::SUCCESS, - GURL(kMerchantUrl), - statistics, - "google_transaction_id"); + wallet_client_->GetWalletItems(GURL(kMerchantUrl)); VerifyAndFinishRequest(net::HTTP_BAD_REQUEST, - kSendAutocheckoutStatusOfSuccessValidRequest, + kGetWalletItemsValidRequest, std::string()); } @@ -1037,7 +1002,8 @@ TEST_F(WalletClientTest, GetFullWalletSuccess) { "shipping_address_id", GURL(kMerchantUrl), "google_transaction_id", - std::vector<WalletClient::RiskCapability>()); + std::vector<WalletClient::RiskCapability>(), + false); wallet_client_->GetFullWallet(full_wallet_request); VerifyAndFinishFormEncodedRequest(net::HTTP_OK, @@ -1047,6 +1013,26 @@ TEST_F(WalletClientTest, GetFullWalletSuccess) { EXPECT_EQ(1U, delegate_.full_wallets_received()); } +TEST_F(WalletClientTest, GetFullWalletSuccessNewuser) { + delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::GET_FULL_WALLET, 1); + delegate_.ExpectBaselineMetrics(); + + WalletClient::FullWalletRequest full_wallet_request( + "instrument_id", + "shipping_address_id", + GURL(kMerchantUrl), + "google_transaction_id", + std::vector<WalletClient::RiskCapability>(), + true); + wallet_client_->GetFullWallet(full_wallet_request); + + VerifyAndFinishFormEncodedRequest(net::HTTP_OK, + kGetFullWalletValidRequestNewUser, + kGetFullWalletValidResponse, + 3U); + EXPECT_EQ(1U, delegate_.full_wallets_received()); +} + TEST_F(WalletClientTest, GetFullWalletWithRiskCapabilitesSuccess) { delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::GET_FULL_WALLET, 1); delegate_.ExpectBaselineMetrics(); @@ -1058,7 +1044,8 @@ TEST_F(WalletClientTest, GetFullWalletWithRiskCapabilitesSuccess) { "shipping_address_id", GURL(kMerchantUrl), "google_transaction_id", - risk_capabilities); + risk_capabilities, + false); wallet_client_->GetFullWallet(full_wallet_request); VerifyAndFinishFormEncodedRequest( @@ -1076,13 +1063,15 @@ TEST_F(WalletClientTest, GetFullWalletMalformedResponse) { delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::GET_FULL_WALLET, 1); delegate_.ExpectBaselineMetrics(); delegate_.ExpectWalletErrorMetric(AutofillMetrics::WALLET_MALFORMED_RESPONSE); + delegate_.ExpectLogWalletMalformedResponse(AutofillMetrics::GET_FULL_WALLET); WalletClient::FullWalletRequest full_wallet_request( "instrument_id", "shipping_address_id", GURL(kMerchantUrl), "google_transaction_id", - std::vector<WalletClient::RiskCapability>()); + std::vector<WalletClient::RiskCapability>(), + false); wallet_client_->GetFullWallet(full_wallet_request); VerifyAndFinishFormEncodedRequest(net::HTTP_OK, @@ -1157,6 +1146,8 @@ TEST_F(WalletClientTest, AuthenticateInstrumentFailedMalformedResponse) { 1); delegate_.ExpectBaselineMetrics(); delegate_.ExpectWalletErrorMetric(AutofillMetrics::WALLET_MALFORMED_RESPONSE); + delegate_.ExpectLogWalletMalformedResponse( + AutofillMetrics::AUTHENTICATE_INSTRUMENT); wallet_client_->AuthenticateInstrument("instrument_id", "123"); @@ -1250,6 +1241,7 @@ TEST_F(WalletClientTest, SaveAddressFailedInvalidRequiredAction) { delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::SAVE_TO_WALLET, 1); delegate_.ExpectBaselineMetrics(); delegate_.ExpectWalletErrorMetric(AutofillMetrics::WALLET_MALFORMED_RESPONSE); + delegate_.ExpectLogWalletMalformedResponse(AutofillMetrics::SAVE_TO_WALLET); scoped_ptr<Address> address = GetTestSaveableAddress(); wallet_client_->SaveToWallet(scoped_ptr<Instrument>(), @@ -1266,6 +1258,7 @@ TEST_F(WalletClientTest, SaveAddressFailedMalformedResponse) { delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::SAVE_TO_WALLET, 1); delegate_.ExpectBaselineMetrics(); delegate_.ExpectWalletErrorMetric(AutofillMetrics::WALLET_MALFORMED_RESPONSE); + delegate_.ExpectLogWalletMalformedResponse(AutofillMetrics::SAVE_TO_WALLET); scoped_ptr<Address> address = GetTestSaveableAddress(); wallet_client_->SaveToWallet(scoped_ptr<Instrument>(), @@ -1334,6 +1327,7 @@ TEST_F(WalletClientTest, SaveInstrumentFailedInvalidRequiredActions) { delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::SAVE_TO_WALLET, 1); delegate_.ExpectBaselineMetrics(); delegate_.ExpectWalletErrorMetric(AutofillMetrics::WALLET_MALFORMED_RESPONSE); + delegate_.ExpectLogWalletMalformedResponse(AutofillMetrics::SAVE_TO_WALLET); EXPECT_CALL(delegate_, OnWalletError(WalletClient::MALFORMED_RESPONSE)); @@ -1355,6 +1349,7 @@ TEST_F(WalletClientTest, SaveInstrumentFailedMalformedResponse) { delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::SAVE_TO_WALLET, 1); delegate_.ExpectBaselineMetrics(); delegate_.ExpectWalletErrorMetric(AutofillMetrics::WALLET_MALFORMED_RESPONSE); + delegate_.ExpectLogWalletMalformedResponse(AutofillMetrics::SAVE_TO_WALLET); scoped_ptr<Instrument> instrument = GetTestInstrument(); wallet_client_->SaveToWallet(instrument.Pass(), @@ -1435,6 +1430,7 @@ TEST_F(WalletClientTest, SaveInstrumentAndAddressFailedInvalidRequiredAction) { 1); delegate_.ExpectBaselineMetrics(); delegate_.ExpectWalletErrorMetric(AutofillMetrics::WALLET_MALFORMED_RESPONSE); + delegate_.ExpectLogWalletMalformedResponse(AutofillMetrics::SAVE_TO_WALLET); scoped_ptr<Instrument> instrument = GetTestInstrument(); scoped_ptr<Address> address = GetTestSaveableAddress(); @@ -1506,6 +1502,7 @@ TEST_F(WalletClientTest, UpdateAddressFailedInvalidRequiredAction) { delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::SAVE_TO_WALLET, 1); delegate_.ExpectBaselineMetrics(); delegate_.ExpectWalletErrorMetric(AutofillMetrics::WALLET_MALFORMED_RESPONSE); + delegate_.ExpectLogWalletMalformedResponse(AutofillMetrics::SAVE_TO_WALLET); scoped_ptr<Address> address = GetTestShippingAddress(); address->set_object_id("shipping_address_id"); @@ -1524,6 +1521,7 @@ TEST_F(WalletClientTest, UpdateAddressMalformedResponse) { delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::SAVE_TO_WALLET, 1); delegate_.ExpectBaselineMetrics(); delegate_.ExpectWalletErrorMetric(AutofillMetrics::WALLET_MALFORMED_RESPONSE); + delegate_.ExpectLogWalletMalformedResponse(AutofillMetrics::SAVE_TO_WALLET); scoped_ptr<Address> address = GetTestShippingAddress(); address->set_object_id("shipping_address_id"); @@ -1635,6 +1633,7 @@ TEST_F(WalletClientTest, UpdateInstrumentFailedInvalidRequiredAction) { 1); delegate_.ExpectBaselineMetrics(); delegate_.ExpectWalletErrorMetric(AutofillMetrics::WALLET_MALFORMED_RESPONSE); + delegate_.ExpectLogWalletMalformedResponse(AutofillMetrics::SAVE_TO_WALLET); wallet_client_->SaveToWallet(GetTestAddressUpgradeInstrument(), scoped_ptr<Address>(), @@ -1652,6 +1651,7 @@ TEST_F(WalletClientTest, UpdateInstrumentMalformedResponse) { 1); delegate_.ExpectBaselineMetrics(); delegate_.ExpectWalletErrorMetric(AutofillMetrics::WALLET_MALFORMED_RESPONSE); + delegate_.ExpectLogWalletMalformedResponse(AutofillMetrics::SAVE_TO_WALLET); wallet_client_->SaveToWallet(GetTestAddressUpgradeInstrument(), scoped_ptr<Address>(), @@ -1662,39 +1662,6 @@ TEST_F(WalletClientTest, UpdateInstrumentMalformedResponse) { kUpdateMalformedResponse); } -TEST_F(WalletClientTest, SendAutocheckoutOfStatusSuccess) { - delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::SEND_STATUS, 1); - delegate_.ExpectBaselineMetrics(); - - AutocheckoutStatistic statistic; - statistic.page_number = 1; - statistic.steps.push_back(AUTOCHECKOUT_STEP_SHIPPING); - statistic.time_taken = base::TimeDelta::FromMilliseconds(100); - std::vector<AutocheckoutStatistic> statistics; - statistics.push_back(statistic); - wallet_client_->SendAutocheckoutStatus(autofill::SUCCESS, - GURL(kMerchantUrl), - statistics, - "google_transaction_id"); - VerifyAndFinishRequest(net::HTTP_OK, - kSendAutocheckoutStatusWithStatisticsValidRequest, - ")]}"); // Invalid JSON. Should be ignored. -} - -TEST_F(WalletClientTest, SendAutocheckoutStatusOfFailure) { - delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::SEND_STATUS, 1); - delegate_.ExpectBaselineMetrics(); - - std::vector<AutocheckoutStatistic> statistics; - wallet_client_->SendAutocheckoutStatus(autofill::CANNOT_PROCEED, - GURL(kMerchantUrl), - statistics, - "google_transaction_id"); - VerifyAndFinishRequest(net::HTTP_OK, - kSendAutocheckoutStatusOfFailureValidRequest, - ")]}"); // Invalid JSON. Should be ignored. -} - TEST_F(WalletClientTest, HasRequestInProgress) { EXPECT_FALSE(wallet_client_->HasRequestInProgress()); delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::GET_WALLET_ITEMS, diff --git a/chromium/components/autofill/content/browser/wallet/wallet_items.cc b/chromium/components/autofill/content/browser/wallet/wallet_items.cc index 03cf3f442c2..0ef44d2f0b8 100644 --- a/chromium/components/autofill/content/browser/wallet/wallet_items.cc +++ b/chromium/components/autofill/content/browser/wallet/wallet_items.cc @@ -80,6 +80,21 @@ WalletItems::MaskedInstrument::Status return WalletItems::MaskedInstrument::INAPPLICABLE; } +base::string16 DisplayStringFromType(WalletItems::MaskedInstrument::Type type) { + switch (type) { + case WalletItems::MaskedInstrument::AMEX: + return CreditCard::TypeForDisplay(kAmericanExpressCard); + case WalletItems::MaskedInstrument::DISCOVER: + return CreditCard::TypeForDisplay(kDiscoverCard); + case WalletItems::MaskedInstrument::MASTER_CARD: + return CreditCard::TypeForDisplay(kMasterCard); + case WalletItems::MaskedInstrument::VISA: + return CreditCard::TypeForDisplay(kVisaCard); + default: + return CreditCard::TypeForDisplay(kGenericCard); + } +} + } // anonymous namespace WalletItems::MaskedInstrument::MaskedInstrument( @@ -261,21 +276,9 @@ base::string16 WalletItems::MaskedInstrument::DisplayNameDetail() const { } base::string16 WalletItems::MaskedInstrument::TypeAndLastFourDigits() const { - base::string16 display_type; - - if (type_ == AMEX) - display_type = CreditCard::TypeForDisplay(kAmericanExpressCard); - else if (type_ == DISCOVER) - display_type = CreditCard::TypeForDisplay(kDiscoverCard); - else if (type_ == MASTER_CARD) - display_type = CreditCard::TypeForDisplay(kMasterCard); - else if (type_ == VISA) - display_type = CreditCard::TypeForDisplay(kVisaCard); - else - display_type = CreditCard::TypeForDisplay(kGenericCard); - // TODO(dbeam): i18n. - return display_type + ASCIIToUTF16(" - ") + last_four_digits(); + return DisplayStringFromType(type_) + ASCIIToUTF16(" - ") + + last_four_digits(); } const gfx::Image& WalletItems::MaskedInstrument::CardIcon() const { @@ -327,6 +330,9 @@ base::string16 WalletItems::MaskedInstrument::GetInfo( case CREDIT_CARD_VERIFICATION_CODE: break; + case CREDIT_CARD_TYPE: + return DisplayStringFromType(type_); + default: NOTREACHED(); } @@ -386,12 +392,14 @@ WalletItems::WalletItems(const std::vector<RequiredAction>& required_actions, const std::string& google_transaction_id, const std::string& default_instrument_id, const std::string& default_address_id, - const std::string& obfuscated_gaia_id) + const std::string& obfuscated_gaia_id, + AmexPermission amex_permission) : required_actions_(required_actions), google_transaction_id_(google_transaction_id), default_instrument_id_(default_instrument_id), default_address_id_(default_address_id), - obfuscated_gaia_id_(obfuscated_gaia_id) {} + obfuscated_gaia_id_(obfuscated_gaia_id), + amex_permission_(amex_permission) {} WalletItems::~WalletItems() {} @@ -435,11 +443,18 @@ scoped_ptr<WalletItems> if (!dictionary.GetString("obfuscated_gaia_id", &obfuscated_gaia_id)) DVLOG(1) << "Response from Google wallet missing obfuscated gaia id"; + bool amex_disallowed = true; + if (!dictionary.GetBoolean("amex_disallowed", &amex_disallowed)) + DVLOG(1) << "Response from Google wallet missing the amex_disallowed field"; + AmexPermission amex_permission = + amex_disallowed ? AMEX_DISALLOWED : AMEX_ALLOWED; + scoped_ptr<WalletItems> wallet_items(new WalletItems(required_action, google_transaction_id, default_instrument_id, default_address_id, - obfuscated_gaia_id)); + obfuscated_gaia_id, + amex_permission)); const ListValue* legal_docs; if (dictionary.GetList("required_legal_document", &legal_docs)) { diff --git a/chromium/components/autofill/content/browser/wallet/wallet_items.h b/chromium/components/autofill/content/browser/wallet/wallet_items.h index 53a49a17d05..9838ec83dfd 100644 --- a/chromium/components/autofill/content/browser/wallet/wallet_items.h +++ b/chromium/components/autofill/content/browser/wallet/wallet_items.h @@ -38,6 +38,11 @@ namespace wallet { class WalletItemsTest; +enum AmexPermission { + AMEX_ALLOWED, + AMEX_DISALLOWED, +}; + // WalletItems is a collection of cards and addresses that a user picks from to // construct a full wallet. However, it also provides a transaction ID which // must be used throughout all API calls being made using this data. @@ -252,10 +257,12 @@ class WalletItems { const std::vector<LegalDocument*>& legal_documents() const { return legal_documents_.get(); } + bool is_amex_allowed() const { return amex_permission_ == AMEX_ALLOWED; } private: friend class WalletItemsTest; - friend scoped_ptr<WalletItems> GetTestWalletItems(); + friend scoped_ptr<WalletItems> GetTestWalletItemsWithDefaultIds( + const std::string&, const std::string&, AmexPermission); FRIEND_TEST_ALL_PREFIXES(WalletItemsTest, CreateWalletItems); FRIEND_TEST_ALL_PREFIXES(WalletItemsTest, CreateWalletItemsWithRequiredActions); @@ -264,7 +271,8 @@ class WalletItems { const std::string& google_transaction_id, const std::string& default_instrument_id, const std::string& default_address_id, - const std::string& obfuscated_gaia_id); + const std::string& obfuscated_gaia_id, + AmexPermission amex_permission); // Actions that must be completed by the user before a FullWallet can be // issued to them by the Online Wallet service. @@ -291,6 +299,9 @@ class WalletItems { // Legal documents the user must accept before using Online Wallet. ScopedVector<LegalDocument> legal_documents_; + // Whether Google Wallet allows American Express card for this merchant. + AmexPermission amex_permission_; + DISALLOW_COPY_AND_ASSIGN(WalletItems); }; diff --git a/chromium/components/autofill/content/browser/wallet/wallet_items_unittest.cc b/chromium/components/autofill/content/browser/wallet/wallet_items_unittest.cc index 83f8b1939fc..75da0fdc3e2 100644 --- a/chromium/components/autofill/content/browser/wallet/wallet_items_unittest.cc +++ b/chromium/components/autofill/content/browser/wallet/wallet_items_unittest.cc @@ -63,7 +63,8 @@ const char kMaskedInstrumentMissingStatus[] = " \"phone_number\":\"phone_number\"," " \"country_code\":\"country_code\"" " }," - " \"object_id\":\"object_id\"" + " \"object_id\":\"object_id\"," + " \"amex_disallowed\":true" "}"; const char kMaskedInstrumentMissingType[] = @@ -278,6 +279,7 @@ const char kWalletItemsMissingGoogleTransactionId[] = " ]," " \"default_address_id\":\"default_address_id\"," " \"obfuscated_gaia_id\":\"obfuscated_gaia_id\"," + " \"amex_disallowed\":true," " \"required_legal_document\":" " [" " {" @@ -343,7 +345,8 @@ const char kWalletItems[] = " }" " ]," " \"default_address_id\":\"default_address_id\"," - " \"obfuscated_gaia_id\":\"obfuscated_gaia_id\""; + " \"obfuscated_gaia_id\":\"obfuscated_gaia_id\"," + " \"amex_disallowed\":true"; const char kRequiredLegalDocument[] = " ," @@ -484,7 +487,8 @@ TEST_F(WalletItemsTest, CreateWalletItemsWithRequiredActions) { std::string(), std::string(), std::string(), - std::string()); + std::string(), + AMEX_DISALLOWED); EXPECT_EQ(expected, *WalletItems::CreateWalletItems(*dict)); ASSERT_FALSE(required_actions.empty()); @@ -493,7 +497,8 @@ TEST_F(WalletItemsTest, CreateWalletItemsWithRequiredActions) { std::string(), std::string(), std::string(), - std::string()); + std::string(), + AMEX_DISALLOWED); EXPECT_NE(expected, different_required_actions); } @@ -507,6 +512,12 @@ TEST_F(WalletItemsTest, CreateWalletItemsMissingGoogleTransactionId) { EXPECT_EQ(NULL, WalletItems::CreateWalletItems(*dict).get()); } +TEST_F(WalletItemsTest, CreateWalletItemsMissingAmexDisallowed) { + SetUpDictionary(std::string(kWalletItems) + std::string(kCloseJson)); + EXPECT_TRUE(dict->Remove("amex_disallowed", NULL)); + EXPECT_FALSE(WalletItems::CreateWalletItems(*dict)->is_amex_allowed()); +} + TEST_F(WalletItemsTest, CreateWalletItems) { SetUpDictionary(std::string(kWalletItems) + std::string(kCloseJson)); std::vector<RequiredAction> required_actions; @@ -514,7 +525,8 @@ TEST_F(WalletItemsTest, CreateWalletItems) { "google_transaction_id", "default_instrument_id", "default_address_id", - "obfuscated_gaia_id"); + "obfuscated_gaia_id", + AMEX_DISALLOWED); scoped_ptr<Address> billing_address(new Address("country_code", ASCIIToUTF16("name"), diff --git a/chromium/components/autofill/content/browser/wallet/wallet_service_url.cc b/chromium/components/autofill/content/browser/wallet/wallet_service_url.cc index e95a201a446..c56d5864fa8 100644 --- a/chromium/components/autofill/content/browser/wallet/wallet_service_url.cc +++ b/chromium/components/autofill/content/browser/wallet/wallet_service_url.cc @@ -27,10 +27,16 @@ const char kSandboxWalletSecureServiceUrl[] = "https://wallet-web.sandbox.google.com/"; bool IsWalletProductionEnabled() { - const CommandLine& command_line = *CommandLine::ForCurrentProcess(); - return command_line.HasSwitch(switches::kWalletServiceUseProd) || - base::FieldTrialList::FindFullName("WalletProductionService") == "Yes" || - base::FieldTrialList::FindFullName("Autocheckout") == "Yes"; + const CommandLine* command_line = CommandLine::ForCurrentProcess(); + std::string sandbox_enabled( + command_line->GetSwitchValueASCII(switches::kWalletServiceUseSandbox)); + if (!sandbox_enabled.empty()) + return sandbox_enabled != "1"; +#if defined(OS_MACOSX) + return false; +#else + return true; +#endif } GURL GetWalletHostUrl() { @@ -122,6 +128,8 @@ GURL GetSignInUrl() { GURL url(GaiaUrls::GetInstance()->service_login_url()); url = net::AppendQueryParameter(url, "service", "toolbar"); url = net::AppendQueryParameter(url, "nui", "1"); + // Prevents promos from showing (see http://crbug.com/235227). + url = net::AppendQueryParameter(url, "sarp", "1"); url = net::AppendQueryParameter(url, "continue", GetSignInContinueUrl().spec()); diff --git a/chromium/components/autofill/content/browser/wallet/wallet_service_url_unittest.cc b/chromium/components/autofill/content/browser/wallet/wallet_service_url_unittest.cc index 3ed3600fc0a..b028a15ce9e 100644 --- a/chromium/components/autofill/content/browser/wallet/wallet_service_url_unittest.cc +++ b/chromium/components/autofill/content/browser/wallet/wallet_service_url_unittest.cc @@ -12,9 +12,11 @@ namespace autofill { namespace wallet { TEST(WalletServiceUrl, CheckDefaultUrls) { +#if defined(OS_MACOSX) + // Default on Mac (for now) is to use sandbox servers. EXPECT_EQ("https://payments-form-dogfood.sandbox.google.com/online/v2/wallet/" - "autocheckout/v1/getWalletItemsJwtless", - GetGetWalletItemsUrl().spec()); + "autocheckout/v1/getWalletItemsJwtless", + GetGetWalletItemsUrl().spec()); EXPECT_EQ("https://wallet-web.sandbox.google.com/online-secure/v2/" "autocheckout/v1/getFullWalletJwtless?s7e=otp", GetGetFullWalletUrl().spec()); @@ -40,21 +42,57 @@ TEST(WalletServiceUrl, CheckDefaultUrls) { EXPECT_EQ("https://payments-form-dogfood.sandbox.google.com/online/v2/" "passiveauth?isChromePayments=true", GetPassiveAuthUrl().spec()); +#else + EXPECT_EQ("https://wallet.google.com/online/v2/wallet/" + "autocheckout/v1/getWalletItemsJwtless", + GetGetWalletItemsUrl().spec()); + EXPECT_EQ("https://wallet.google.com/online-secure/v2/" + "autocheckout/v1/getFullWalletJwtless?s7e=otp", + GetGetFullWalletUrl().spec()); + EXPECT_EQ("https://wallet.google.com/manage/paymentMethods", + GetManageInstrumentsUrl().spec()); + EXPECT_EQ("https://wallet.google.com/manage/settings/addresses", + GetManageAddressesUrl().spec()); + EXPECT_EQ("https://wallet.google.com/online/v2/wallet/" + "autocheckout/v1/acceptLegalDocument", + GetAcceptLegalDocumentsUrl().spec()); + EXPECT_EQ("https://wallet.google.com/online-secure/v2/" + "autocheckout/v1/authenticateInstrument?s7e=cvn", + GetAuthenticateInstrumentUrl().spec()); + EXPECT_EQ("https://wallet.google.com/online/v2/wallet/" + "autocheckout/v1/reportStatus", + GetSendStatusUrl().spec()); + EXPECT_EQ("https://wallet.google.com/online/v2/wallet/" + "autocheckout/v1/saveToWallet", + GetSaveToWalletNoEscrowUrl().spec()); + EXPECT_EQ("https://wallet.google.com/online-secure/v2/" + "autocheckout/v1/saveToWallet?s7e=card_number%3Bcvn", + GetSaveToWalletUrl().spec()); + EXPECT_EQ("https://wallet.google.com/online/v2/" + "passiveauth?isChromePayments=true", + GetPassiveAuthUrl().spec()); +#endif } TEST(WalletServiceUrl, IsUsingProd) { - // The sandbox servers are the default (for now). Update if this changes. +#if defined(OS_MACOSX) EXPECT_FALSE(IsUsingProd()); - - CommandLine* command_line = CommandLine::ForCurrentProcess(); - command_line->AppendSwitch(switches::kWalletServiceUseProd); +#else EXPECT_TRUE(IsUsingProd()); +#endif - const GURL prod_get_items_url = GetGetWalletItemsUrl(); - command_line->AppendSwitchASCII(switches::kWalletServiceUrl, "http://goo.gl"); + CommandLine* command_line = CommandLine::ForCurrentProcess(); + command_line->AppendSwitchASCII(switches::kWalletServiceUseSandbox, "1"); EXPECT_FALSE(IsUsingProd()); - ASSERT_NE(prod_get_items_url, GetGetWalletItemsUrl()); + const GURL sandbox_get_items_url = GetGetWalletItemsUrl(); + const GURL fake_service_url = GURL("http://goo.gl"); + command_line->AppendSwitchASCII(switches::kWalletServiceUrl, + fake_service_url.spec()); + + const GURL flag_get_items_url = GetGetWalletItemsUrl(); + ASSERT_NE(sandbox_get_items_url, flag_get_items_url); + EXPECT_EQ(fake_service_url.GetOrigin(), flag_get_items_url.GetOrigin()); } } // namespace wallet diff --git a/chromium/components/autofill/content/browser/wallet/wallet_test_util.cc b/chromium/components/autofill/content/browser/wallet/wallet_test_util.cc index 15f916afdc0..0a15c3e338e 100644 --- a/chromium/components/autofill/content/browser/wallet/wallet_test_util.cc +++ b/chromium/components/autofill/content/browser/wallet/wallet_test_util.cc @@ -179,13 +179,15 @@ scoped_ptr<WalletItems::MaskedInstrument> GetTestMaskedInstrumentInvalid() { WalletItems::MaskedInstrument::DECLINED); } -scoped_ptr<WalletItems::MaskedInstrument> GetTestMaskedInstrumentAmex() { +scoped_ptr<WalletItems::MaskedInstrument> GetTestMaskedInstrumentAmex( + AmexPermission amex_permission) { return GetTestMaskedInstrumentWithDetails( "default_instrument_id", GetTestAddress(), WalletItems::MaskedInstrument::AMEX, - // Amex cards are marked with status AMEX_NOT_SUPPORTED by the server. - WalletItems::MaskedInstrument::AMEX_NOT_SUPPORTED); + (amex_permission == AMEX_ALLOWED) + ? WalletItems::MaskedInstrument::VALID + : WalletItems::MaskedInstrument::AMEX_NOT_SUPPORTED); } scoped_ptr<WalletItems::MaskedInstrument> GetTestNonDefaultMaskedInstrument() { @@ -224,13 +226,23 @@ scoped_ptr<Address> GetTestNonDefaultShippingAddress() { return address.Pass(); } -scoped_ptr<WalletItems> GetTestWalletItems() { +scoped_ptr<WalletItems> GetTestWalletItems(AmexPermission amex_permission) { + return GetTestWalletItemsWithDefaultIds("default_instrument_id", + "default_address_id", + amex_permission); +} + +scoped_ptr<WalletItems> GetTestWalletItemsWithDefaultIds( + const std::string& default_instrument_id, + const std::string& default_address_id, + AmexPermission amex_permission) { return scoped_ptr<WalletItems>( new wallet::WalletItems(std::vector<RequiredAction>(), "google_transaction_id", - "default_instrument_id", - "default_address_id", - "obfuscated_gaia_id")); + default_instrument_id, + default_address_id, + "obfuscated_gaia_id", + amex_permission)); } } // namespace wallet diff --git a/chromium/components/autofill/content/browser/wallet/wallet_test_util.h b/chromium/components/autofill/content/browser/wallet/wallet_test_util.h index fd78b05b865..b9bfbdd4a37 100644 --- a/chromium/components/autofill/content/browser/wallet/wallet_test_util.h +++ b/chromium/components/autofill/content/browser/wallet/wallet_test_util.h @@ -27,15 +27,27 @@ scoped_ptr<WalletItems::LegalDocument> GetTestLegalDocument(); scoped_ptr<WalletItems::MaskedInstrument> GetTestMaskedInstrument(); scoped_ptr<WalletItems::MaskedInstrument> GetTestMaskedInstrumentExpired(); scoped_ptr<WalletItems::MaskedInstrument> GetTestMaskedInstrumentInvalid(); -scoped_ptr<WalletItems::MaskedInstrument> GetTestMaskedInstrumentAmex(); +scoped_ptr<WalletItems::MaskedInstrument> GetTestMaskedInstrumentAmex( + AmexPermission amex_permission); scoped_ptr<WalletItems::MaskedInstrument> GetTestNonDefaultMaskedInstrument(); +scoped_ptr<WalletItems::MaskedInstrument> GetTestMaskedInstrumentWithId( + const std::string& id); scoped_ptr<WalletItems::MaskedInstrument> GetTestMaskedInstrumentWithIdAndAddress( const std::string& id, scoped_ptr<Address> address); +scoped_ptr<WalletItems::MaskedInstrument> GetTestMaskedInstrumentWithDetails( + const std::string& id, + scoped_ptr<Address> address, + WalletItems::MaskedInstrument::Type type, + WalletItems::MaskedInstrument::Status status); scoped_ptr<Address> GetTestSaveableAddress(); scoped_ptr<Address> GetTestShippingAddress(); scoped_ptr<Address> GetTestNonDefaultShippingAddress(); -scoped_ptr<WalletItems> GetTestWalletItems(); +scoped_ptr<WalletItems> GetTestWalletItems(AmexPermission amex_permission); +scoped_ptr<WalletItems> GetTestWalletItemsWithDefaultIds( + const std::string& default_instrument_id, + const std::string& default_address_id, + AmexPermission amex_permission); } // namespace wallet } // namespace autofill diff --git a/chromium/components/autofill/content/renderer/autofill_agent.cc b/chromium/components/autofill/content/renderer/autofill_agent.cc index e8509c74750..ca57847f7d4 100644 --- a/chromium/components/autofill/content/renderer/autofill_agent.cc +++ b/chromium/components/autofill/content/renderer/autofill_agent.cc @@ -20,8 +20,8 @@ #include "components/autofill/core/common/form_data.h" #include "components/autofill/core/common/form_data_predictions.h" #include "components/autofill/core/common/form_field_data.h" +#include "components/autofill/core/common/password_form.h" #include "components/autofill/core/common/web_element_descriptor.h" -#include "content/public/common/password_form.h" #include "content/public/common/ssl_status.h" #include "content/public/common/url_constants.h" #include "content/public/renderer/render_view.h" @@ -39,8 +39,8 @@ #include "third_party/WebKit/public/web/WebNodeCollection.h" #include "third_party/WebKit/public/web/WebOptionElement.h" #include "third_party/WebKit/public/web/WebView.h" -#include "ui/base/keycodes/keyboard_codes.h" #include "ui/base/l10n/l10n_util.h" +#include "ui/events/keycodes/keyboard_codes.h" using WebKit::WebAutofillClient; using WebKit::WebFormControlElement; @@ -63,8 +63,6 @@ const size_t kMaximumTextSizeForAutofill = 1000; // via IPC (to prevent long IPC messages). const size_t kMaximumDataListSizeForAutofill = 30; -const int kAutocheckoutClickTimeout = 3; - // Gets all the data list values (with corresponding label) for the given // element. @@ -130,14 +128,11 @@ AutofillAgent::AutofillAgent(content::RenderView* render_view, password_autofill_agent_(password_autofill_agent), autofill_query_id_(0), autofill_action_(AUTOFILL_NONE), - topmost_frame_(NULL), web_view_(render_view->GetWebView()), display_warning_if_disabled_(false), was_query_node_autofilled_(false), has_shown_autofill_popup_for_current_edit_(false), did_set_node_text_(false), - autocheckout_click_in_progress_(false), - is_autocheckout_supported_(false), has_new_forms_for_browser_(false), ignore_text_changes_(false), weak_ptr_factory_(this) { @@ -153,7 +148,6 @@ AutofillAgent::~AutofillAgent() {} bool AutofillAgent::OnMessageReceived(const IPC::Message& message) { bool handled = true; IPC_BEGIN_MESSAGE_MAP(AutofillAgent, message) - IPC_MESSAGE_HANDLER(AutofillMsg_GetAllForms, OnGetAllForms) IPC_MESSAGE_HANDLER(AutofillMsg_FormDataFilled, OnFormDataFilled) IPC_MESSAGE_HANDLER(AutofillMsg_FieldTypePredictionsAvailable, OnFieldTypePredictionsAvailable) @@ -173,10 +167,6 @@ bool AutofillAgent::OnMessageReceived(const IPC::Message& message) { OnAcceptPasswordAutofillSuggestion) IPC_MESSAGE_HANDLER(AutofillMsg_RequestAutocompleteResult, OnRequestAutocompleteResult) - IPC_MESSAGE_HANDLER(AutofillMsg_FillFormsAndClick, - OnFillFormsAndClick) - IPC_MESSAGE_HANDLER(AutofillMsg_AutocheckoutSupported, - OnAutocheckoutSupported) IPC_MESSAGE_HANDLER(AutofillMsg_PageShown, OnPageShown) IPC_MESSAGE_UNHANDLED(handled = false) @@ -194,7 +184,6 @@ void AutofillAgent::DidFinishDocumentLoad(WebFrame* frame) { std::vector<FormData> forms; bool has_more_forms = false; if (!frame->parent()) { - topmost_frame_ = frame; form_elements_.clear(); has_more_forms = form_cache_.ExtractFormsAndFormElements( *frame, kRequiredAutofillFields, &forms, &form_elements_); @@ -213,41 +202,13 @@ void AutofillAgent::DidFinishDocumentLoad(WebFrame* frame) { } } -void AutofillAgent::DidStartProvisionalLoad(WebFrame* frame) { - if (!frame->parent()) { - is_autocheckout_supported_ = false; - topmost_frame_ = NULL; - if (click_timer_.IsRunning()) { - click_timer_.Stop(); - autocheckout_click_in_progress_ = true; - } - } -} - -void AutofillAgent::DidFailProvisionalLoad(WebFrame* frame, - const WebKit::WebURLError& error) { - if (!frame->parent() && autocheckout_click_in_progress_) { - autocheckout_click_in_progress_ = false; - ClickFailed(); - } -} - void AutofillAgent::DidCommitProvisionalLoad(WebFrame* frame, bool is_new_navigation) { in_flight_request_form_.reset(); - if (!frame->parent() && autocheckout_click_in_progress_) { - autocheckout_click_in_progress_ = false; - CompleteAutocheckoutPage(SUCCESS); - } } void AutofillAgent::FrameDetached(WebFrame* frame) { form_cache_.ResetFrame(*frame); - if (!frame->parent()) { - // |frame| is about to be destroyed so we need to clear |top_most_frame_|. - topmost_frame_ = NULL; - click_timer_.Stop(); - } } void AutofillAgent::WillSubmitForm(WebFrame* frame, @@ -288,24 +249,10 @@ void AutofillAgent::FocusedNodeChanged(const WebKit::WebNode& node) { return; element_ = *element; - - MaybeShowAutocheckoutBubble(); } -void AutofillAgent::MaybeShowAutocheckoutBubble() { - if (element_.isNull() || !element_.focused()) - return; - - FormData form; - FormFieldData field; - // This must be called to short circuit this method if it fails. - if (!FindFormAndFieldForInputElement(element_, &form, &field, REQUIRE_NONE)) - return; - - Send(new AutofillHostMsg_MaybeShowAutocheckoutBubble( - routing_id(), - form, - GetScaledBoundingBox(web_view_->pageScaleFactor(), &element_))); +void AutofillAgent::OrientationChangeEvent(int orientation) { + HideAutofillUI(); } void AutofillAgent::DidChangeScrollOffset(WebKit::WebFrame*) { @@ -318,7 +265,7 @@ void AutofillAgent::didRequestAutocomplete(WebKit::WebFrame* frame, content::SSLStatus ssl_status = render_view()->GetSSLStatusOfFrame(frame); FormData form_data; if (!in_flight_request_form_.isNull() || - (url.SchemeIs(chrome::kHttpsScheme) && + (url.SchemeIs(content::kHttpsScheme) && (net::IsCertStatusError(ssl_status.cert_status) || net::IsCertStatusMinorError(ssl_status.cert_status))) || !WebFormElementToFormData(form, @@ -524,24 +471,6 @@ void AutofillAgent::OnAcceptPasswordAutofillSuggestion( DCHECK(handled); } -void AutofillAgent::OnGetAllForms() { - form_elements_.clear(); - - // Force fetch all non empty forms. - std::vector<FormData> forms; - form_cache_.ExtractFormsAndFormElements( - *topmost_frame_, 0, &forms, &form_elements_); - - // OnGetAllForms should only be called if AutofillAgent reported to - // AutofillManager that there are more forms - DCHECK(!forms.empty()); - - // Report to AutofillManager that all forms are being sent. - Send(new AutofillHostMsg_FormsSeen(routing_id(), forms, - forms_seen_timestamp_, - NO_SPECIAL_FORMS_SEEN)); -} - void AutofillAgent::OnRequestAutocompleteResult( WebFormElement::AutocompleteResult result, const FormData& form_data) { if (in_flight_request_form_.isNull()) @@ -557,77 +486,7 @@ void AutofillAgent::OnRequestAutocompleteResult( in_flight_request_form_.reset(); } -void AutofillAgent::OnFillFormsAndClick( - const std::vector<FormData>& forms, - const std::vector<WebElementDescriptor>& click_elements_before_form_fill, - const std::vector<WebElementDescriptor>& click_elements_after_form_fill, - const WebElementDescriptor& click_element_descriptor) { - DCHECK_EQ(forms.size(), form_elements_.size()); - - // Click elements in click_elements_before_form_fill. - for (size_t i = 0; i < click_elements_before_form_fill.size(); ++i) { - if (!ClickElement(topmost_frame_->document(), - click_elements_before_form_fill[i])) { - CompleteAutocheckoutPage(MISSING_CLICK_ELEMENT_BEFORE_FORM_FILLING); - return; - } - } - - // Fill the form. - for (size_t i = 0; i < forms.size(); ++i) - FillFormForAllElements(forms[i], form_elements_[i]); - - // Click elements in click_elements_after_form_fill. - for (size_t i = 0; i < click_elements_after_form_fill.size(); ++i) { - if (!ClickElement(topmost_frame_->document(), - click_elements_after_form_fill[i])) { - CompleteAutocheckoutPage(MISSING_CLICK_ELEMENT_AFTER_FORM_FILLING); - return; - } - } - - // Exit early if there is nothing to click. - if (click_element_descriptor.retrieval_method == WebElementDescriptor::NONE) { - CompleteAutocheckoutPage(SUCCESS); - return; - } - - // It's possible that clicking the element to proceed in an Autocheckout - // flow will not actually proceed to the next step in the flow, e.g. there - // is a new required field that Autocheckout does not know how to fill. In - // order to capture this case and present the user with an error a timer is - // set that informs the browser of the error. |click_timer_| has to be started - // before clicking so it can start before DidStartProvisionalLoad started. - click_timer_.Start(FROM_HERE, - base::TimeDelta::FromSeconds(kAutocheckoutClickTimeout), - this, - &AutofillAgent::ClickFailed); - if (!ClickElement(topmost_frame_->document(), - click_element_descriptor)) { - CompleteAutocheckoutPage(MISSING_ADVANCE); - } -} - -void AutofillAgent::OnAutocheckoutSupported() { - is_autocheckout_supported_ = true; - if (has_new_forms_for_browser_) - MaybeSendDynamicFormsSeen(); - MaybeShowAutocheckoutBubble(); -} - void AutofillAgent::OnPageShown() { - if (is_autocheckout_supported_) - MaybeShowAutocheckoutBubble(); -} - -void AutofillAgent::CompleteAutocheckoutPage( - autofill::AutocheckoutStatus status) { - click_timer_.Stop(); - Send(new AutofillHostMsg_AutocheckoutPageCompleted(routing_id(), status)); -} - -void AutofillAgent::ClickFailed() { - CompleteAutocheckoutPage(CANNOT_PROCEED); } void AutofillAgent::ShowSuggestions(const WebInputElement& element, @@ -748,38 +607,18 @@ void AutofillAgent::HideAutofillUI() { Send(new AutofillHostMsg_HideAutofillUI(routing_id())); } +// TODO(isherman): Decide if we want to support non-password autofill with AJAX. void AutofillAgent::didAssociateFormControls( const WebKit::WebVector<WebKit::WebNode>& nodes) { for (size_t i = 0; i < nodes.size(); ++i) { - WebKit::WebNode node = nodes[i]; - if (node.document().frame() == topmost_frame_) { - forms_seen_timestamp_ = base::TimeTicks::Now(); - has_new_forms_for_browser_ = true; - break; + WebKit::WebFrame* frame = nodes[i].document().frame(); + // Only monitors dynamic forms created in the top frame. Dynamic forms + // inserted in iframes are not captured yet. + if (!frame->parent()) { + password_autofill_agent_->OnDynamicFormsSeen(frame); + return; } } - - if (has_new_forms_for_browser_ && is_autocheckout_supported_) - MaybeSendDynamicFormsSeen(); -} - -void AutofillAgent::MaybeSendDynamicFormsSeen() { - has_new_forms_for_browser_ = false; - form_elements_.clear(); - std::vector<FormData> forms; - // This will only be called for Autocheckout flows, so send all forms to - // save an IPC. - form_cache_.ExtractFormsAndFormElements( - *topmost_frame_, 0, &forms, &form_elements_); - autofill::FormsSeenState state = autofill::DYNAMIC_FORMS_SEEN; - - if (!forms.empty()) { - if (click_timer_.IsRunning()) - click_timer_.Stop(); - Send(new AutofillHostMsg_FormsSeen(routing_id(), forms, - forms_seen_timestamp_, - state)); - } } } // namespace autofill diff --git a/chromium/components/autofill/content/renderer/autofill_agent.h b/chromium/components/autofill/content/renderer/autofill_agent.h index 9236246c18d..3f979bf95b3 100644 --- a/chromium/components/autofill/content/renderer/autofill_agent.h +++ b/chromium/components/autofill/content/renderer/autofill_agent.h @@ -15,7 +15,6 @@ #include "base/timer/timer.h" #include "components/autofill/content/renderer/form_cache.h" #include "components/autofill/content/renderer/page_click_listener.h" -#include "components/autofill/core/common/autocheckout_status.h" #include "components/autofill/core/common/forms_seen_state.h" #include "content/public/renderer/render_view_observer.h" #include "third_party/WebKit/public/web/WebAutofillClient.h" @@ -61,10 +60,6 @@ class AutofillAgent : public content::RenderViewObserver, // RenderView::Observer: virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE; virtual void DidFinishDocumentLoad(WebKit::WebFrame* frame) OVERRIDE; - virtual void DidStartProvisionalLoad(WebKit::WebFrame* frame) OVERRIDE; - virtual void DidFailProvisionalLoad( - WebKit::WebFrame* frame, - const WebKit::WebURLError& error) OVERRIDE; virtual void DidCommitProvisionalLoad(WebKit::WebFrame* frame, bool is_new_navigation) OVERRIDE; virtual void FrameDetached(WebKit::WebFrame* frame) OVERRIDE; @@ -73,6 +68,7 @@ class AutofillAgent : public content::RenderViewObserver, virtual void ZoomLevelChanged() OVERRIDE; virtual void DidChangeScrollOffset(WebKit::WebFrame* frame) OVERRIDE; virtual void FocusedNodeChanged(const WebKit::WebNode& node) OVERRIDE; + virtual void OrientationChangeEvent(int orientation) OVERRIDE; // PageClickListener: virtual void InputElementClicked(const WebKit::WebInputElement& element, @@ -108,7 +104,6 @@ class AutofillAgent : public content::RenderViewObserver, void OnSetNodeText(const base::string16& value); void OnAcceptDataListSuggestion(const base::string16& value); void OnAcceptPasswordAutofillSuggestion(const base::string16& value); - void OnGetAllForms(); // Called when interactive autocomplete finishes. void OnRequestAutocompleteResult( @@ -119,29 +114,10 @@ class AutofillAgent : public content::RenderViewObserver, void FinishAutocompleteRequest( WebKit::WebFormElement::AutocompleteResult result); - // Called when the Autofill server hints that this page should be filled using - // Autocheckout. All the relevant form fields in |form_data| will be filled - // and then element specified by |element_descriptor| will be clicked to - // proceed to the next step of the form. - void OnFillFormsAndClick( - const std::vector<FormData>& form_data, - const std::vector<WebElementDescriptor>& click_elements_before_form_fill, - const std::vector<WebElementDescriptor>& click_elements_after_form_fill, - const WebElementDescriptor& element_descriptor); - - // Called when |topmost_frame_| is supported for Autocheckout. - void OnAutocheckoutSupported(); - // Called when the page is actually shown in the browser, as opposed to simply // being preloaded. void OnPageShown(); - // Called when an Autocheckout page is completed by the renderer. - void CompleteAutocheckoutPage(autofill::AutocheckoutStatus status); - - // Called when clicking an Autocheckout proceed element fails to do anything. - void ClickFailed(); - // Called in a posted task by textFieldDidChange() to work-around a WebKit bug // http://bugs.webkit.org/show_bug.cgi?id=16976 void TextFieldDidChangeImpl(const WebKit::WebInputElement& element); @@ -191,11 +167,6 @@ class AutofillAgent : public content::RenderViewObserver, // Hides any currently showing Autofill UI. void HideAutofillUI(); - void MaybeSendDynamicFormsSeen(); - - // Send |AutofillHostMsg_MaybeShowAutocheckoutBubble| to browser if needed. - void MaybeShowAutocheckoutBubble(); - FormCache form_cache_; PasswordAutofillAgent* password_autofill_agent_; // WEAK reference. @@ -216,10 +187,6 @@ class AutofillAgent : public content::RenderViewObserver, // The action to take when receiving Autofill data from the AutofillManager. AutofillAction autofill_action_; - // Pointer to the current topmost frame. Used in autocheckout flows so - // elements can be clicked. - WebKit::WebFrame* topmost_frame_; - // Pointer to the WebView. Used to access page scale factor. WebKit::WebView* web_view_; @@ -236,16 +203,6 @@ class AutofillAgent : public content::RenderViewObserver, // If true we just set the node text so we shouldn't show the popup. bool did_set_node_text_; - // Watchdog timer for clicking in Autocheckout flows. - base::OneShotTimer<AutofillAgent> click_timer_; - - // Used to signal that we need to watch for loading failures in an - // Autocheckout flow. - bool autocheckout_click_in_progress_; - - // Whether or not |topmost_frame_| is whitelisted for Autocheckout. - bool is_autocheckout_supported_; - // Whether or not new forms/fields have been dynamically added // since the last loaded forms were sent to the browser process. bool has_new_forms_for_browser_; diff --git a/chromium/components/autofill/content/renderer/form_autofill_util.cc b/chromium/components/autofill/content/renderer/form_autofill_util.cc index 55f3a03e7c9..b9eca17434f 100644 --- a/chromium/components/autofill/content/renderer/form_autofill_util.cc +++ b/chromium/components/autofill/content/renderer/form_autofill_util.cc @@ -79,7 +79,7 @@ bool IsNoScriptElement(const WebElement& element) { } bool HasTagName(const WebNode& node, const WebKit::WebString& tag) { - return node.isElementNode() && node.toConst<WebElement>().hasTagName(tag); + return node.isElementNode() && node.toConst<WebElement>().hasHTMLTagName(tag); } bool IsAutofillableElement(const WebFormControlElement& element) { @@ -87,18 +87,9 @@ bool IsAutofillableElement(const WebFormControlElement& element) { return IsAutofillableInputElement(input_element) || IsSelectElement(element); } -bool IsAutocheckoutEnabled() { - return base::FieldTrialList::FindFullName("Autocheckout") == "Yes" || - CommandLine::ForCurrentProcess()->HasSwitch( - switches::kEnableExperimentalFormFilling); -} - // Check whether the given field satisfies the REQUIRE_AUTOCOMPLETE requirement. -// When Autocheckout is enabled, this requirement is enforced in the browser -// process rather than in the renderer process, and hence all fields are -// considered to satisfy this requirement. bool SatisfiesRequireAutocomplete(const WebInputElement& input_element) { - return input_element.autoComplete() || IsAutocheckoutEnabled(); + return input_element.autoComplete(); } // Appends |suffix| to |prefix| so that any intermediary whitespace is collapsed @@ -599,9 +590,30 @@ std::string RetrievalMethodToString( return "UNKNOWN"; } +// Recursively checks whether |node| or any of its children have a non-empty +// bounding box. The recursion depth is bounded by |depth|. +bool IsWebNodeVisibleImpl(const WebKit::WebNode& node, const int depth) { + if (depth < 0) + return false; + if (node.hasNonEmptyBoundingBox()) + return true; + + // The childNodes method is not a const method. Therefore it cannot be called + // on a const reference. Therefore we need a const cast. + const WebKit::WebNodeList& children = + const_cast<WebKit::WebNode&>(node).childNodes(); + size_t length = children.length(); + for (size_t i = 0; i < length; ++i) { + const WebKit::WebNode& item = children.item(i); + if (IsWebNodeVisibleImpl(item, depth - 1)) + return true; + } + return false; +} + } // namespace -const size_t kMaxParseableFields = 100; +const size_t kMaxParseableFields = 200; // All text fields, including password fields, should be extracted. bool IsTextInput(const WebInputElement* element) { @@ -634,6 +646,14 @@ const base::string16 GetFormIdentifier(const WebFormElement& form) { return identifier; } +bool IsWebNodeVisible(const WebKit::WebNode& node) { + // In the bug http://crbug.com/237216 the form's bounding box is empty + // however the form has non empty children. Thus we need to look at the + // form's children. + int kNodeSearchDepth = 2; + return IsWebNodeVisibleImpl(node, kNodeSearchDepth); +} + bool ClickElement(const WebDocument& document, const WebElementDescriptor& element_descriptor) { WebString web_descriptor = WebString::fromUTF8(element_descriptor.descriptor); @@ -1056,4 +1076,54 @@ bool FormWithElementIsAutofilled(const WebInputElement& element) { return false; } +bool IsWebpageEmpty(const WebKit::WebFrame* frame) { + WebKit::WebDocument document = frame->document(); + + return IsWebElementEmpty(document.head()) && + IsWebElementEmpty(document.body()); +} + +bool IsWebElementEmpty(const WebKit::WebElement& element) { + // This array contains all tags which can be present in an empty page. + const char* const kAllowedValue[] = { + "script", + "meta", + "title", + }; + const size_t kAllowedValueLength = arraysize(kAllowedValue); + + if (element.isNull()) + return true; + // The childNodes method is not a const method. Therefore it cannot be called + // on a const reference. Therefore we need a const cast. + const WebKit::WebNodeList& children = + const_cast<WebKit::WebElement&>(element).childNodes(); + for (size_t i = 0; i < children.length(); ++i) { + const WebKit::WebNode& item = children.item(i); + + if (item.isTextNode() && + !ContainsOnlyWhitespaceASCII(item.nodeValue().utf8())) + return false; + + // We ignore all other items with names which begin with + // the character # because they are not html tags. + if (item.nodeName().utf8()[0] == '#') + continue; + + bool tag_is_allowed = false; + // Test if the item name is in the kAllowedValue array + for (size_t allowed_value_index = 0; + allowed_value_index < kAllowedValueLength; ++allowed_value_index) { + if (HasTagName(item, + WebString::fromUTF8(kAllowedValue[allowed_value_index]))) { + tag_is_allowed = true; + break; + } + } + if (!tag_is_allowed) + return false; + } + return true; +} + } // namespace autofill diff --git a/chromium/components/autofill/content/renderer/form_autofill_util.h b/chromium/components/autofill/content/renderer/form_autofill_util.h index 5a1241897f8..8c109c467e5 100644 --- a/chromium/components/autofill/content/renderer/form_autofill_util.h +++ b/chromium/components/autofill/content/renderer/form_autofill_util.h @@ -11,9 +11,12 @@ namespace WebKit { class WebDocument; +class WebElement; class WebFormElement; class WebFormControlElement; +class WebFrame; class WebInputElement; +class WebNode; } namespace autofill { @@ -61,6 +64,10 @@ bool IsCheckableElement(const WebKit::WebInputElement* element); // autofilled. {Text, Radiobutton, Checkbox}. bool IsAutofillableInputElement(const WebKit::WebInputElement* element); +// Recursively checks whether |node| or any of its children have a non-empty +// bounding box. +bool IsWebNodeVisible(const WebKit::WebNode& node); + // Returns the form's |name| attribute if non-empty; otherwise the form's |id| // attribute. const base::string16 GetFormIdentifier(const WebKit::WebFormElement& form); @@ -139,6 +146,21 @@ bool ClearPreviewedFormWithElement(const WebKit::WebInputElement& element, // Returns true if |form| has any auto-filled fields. bool FormWithElementIsAutofilled(const WebKit::WebInputElement& element); +// Checks if the webpage is empty. +// This kind of webpage is considered as empty: +// <html> +// <head> +// <head/> +// <body> +// <body/> +// <html/> +// Meta, script and title tags don't influence the emptiness of a webpage. +bool IsWebpageEmpty(const WebKit::WebFrame* frame); + +// This function checks whether the children of |element| +// are of the type <script>, <meta>, or <title>. +bool IsWebElementEmpty(const WebKit::WebElement& element); + } // namespace autofill #endif // COMPONENTS_AUTOFILL_CONTENT_RENDERER_FORM_AUTOFILL_UTIL_H_ diff --git a/chromium/components/autofill/content/renderer/password_autofill_agent.cc b/chromium/components/autofill/content/renderer/password_autofill_agent.cc index b6294abf59e..ce2f1031844 100644 --- a/chromium/components/autofill/content/renderer/password_autofill_agent.cc +++ b/chromium/components/autofill/content/renderer/password_autofill_agent.cc @@ -10,11 +10,11 @@ #include "base/metrics/histogram.h" #include "base/strings/utf_string_conversions.h" #include "components/autofill/content/renderer/form_autofill_util.h" +#include "components/autofill/content/renderer/password_form_conversion_utils.h" #include "components/autofill/core/common/autofill_messages.h" #include "components/autofill/core/common/form_field_data.h" +#include "components/autofill/core/common/password_form.h" #include "components/autofill/core/common/password_form_fill_data.h" -#include "content/public/common/password_form.h" -#include "content/public/renderer/password_form_conversion_utils.h" #include "content/public/renderer/render_view.h" #include "third_party/WebKit/public/platform/WebVector.h" #include "third_party/WebKit/public/web/WebAutofillClient.h" @@ -23,9 +23,12 @@ #include "third_party/WebKit/public/web/WebFormElement.h" #include "third_party/WebKit/public/web/WebFrame.h" #include "third_party/WebKit/public/web/WebInputEvent.h" +#include "third_party/WebKit/public/web/WebNode.h" +#include "third_party/WebKit/public/web/WebNodeList.h" #include "third_party/WebKit/public/web/WebSecurityOrigin.h" +#include "third_party/WebKit/public/web/WebUserGestureIndicator.h" #include "third_party/WebKit/public/web/WebView.h" -#include "ui/base/keycodes/keyboard_codes.h" +#include "ui/events/keycodes/keyboard_codes.h" namespace autofill { namespace { @@ -348,27 +351,39 @@ bool PasswordAutofillAgent::ShowSuggestions( return ShowSuggestionPopup(iter->second.fill_data, element); } +bool PasswordAutofillAgent::OriginCanAccessPasswordManager( + const WebKit::WebSecurityOrigin& origin) { + return origin.canAccessPasswordManager(); +} + +void PasswordAutofillAgent::OnDynamicFormsSeen(WebKit::WebFrame* frame) { + SendPasswordForms(frame, false /* only_visible */); +} + void PasswordAutofillAgent::SendPasswordForms(WebKit::WebFrame* frame, - bool only_visible) { + bool only_visible) { // Make sure that this security origin is allowed to use password manager. WebKit::WebSecurityOrigin origin = frame->document().securityOrigin(); - if (!origin.canAccessPasswordManager()) + if (!OriginCanAccessPasswordManager(origin)) + return; + + // Checks whether the webpage is a redirect page or an empty page. + if (IsWebpageEmpty(frame)) return; WebKit::WebVector<WebKit::WebFormElement> forms; frame->document().forms(forms); - std::vector<content::PasswordForm> password_forms; + std::vector<PasswordForm> password_forms; for (size_t i = 0; i < forms.size(); ++i) { const WebKit::WebFormElement& form = forms[i]; // If requested, ignore non-rendered forms, e.g. those styled with // display:none. - if (only_visible && !form.hasNonEmptyBoundingBox()) + if (only_visible && !IsWebNodeVisible(form)) continue; - scoped_ptr<content::PasswordForm> password_form( - content::CreatePasswordForm(form)); + scoped_ptr<PasswordForm> password_form(CreatePasswordForm(form)); if (password_form.get()) password_forms.push_back(*password_form); } @@ -381,8 +396,8 @@ void PasswordAutofillAgent::SendPasswordForms(WebKit::WebFrame* frame, } if (only_visible) { - Send(new AutofillHostMsg_PasswordFormsRendered( - routing_id(), password_forms)); + Send(new AutofillHostMsg_PasswordFormsRendered(routing_id(), + password_forms)); } else { Send(new AutofillHostMsg_PasswordFormsParsed(routing_id(), password_forms)); } @@ -428,6 +443,63 @@ void PasswordAutofillAgent::FrameWillClose(WebKit::WebFrame* frame) { FrameClosing(frame); } +void PasswordAutofillAgent::WillSendSubmitEvent( + WebKit::WebFrame* frame, + const WebKit::WebFormElement& form) { + // Some login forms have onSubmit handlers that put a hash of the password + // into a hidden field and then clear the password (http://crbug.com/28910). + // This method gets called before any of those handlers run, so save away + // a copy of the password in case it gets lost. + provisionally_saved_forms_[frame].reset(CreatePasswordForm(form).release()); +} + +void PasswordAutofillAgent::WillSubmitForm(WebKit::WebFrame* frame, + const WebKit::WebFormElement& form) { + scoped_ptr<PasswordForm> submitted_form = CreatePasswordForm(form); + + // If there is a provisionally saved password, copy over the previous + // password value so we get the user's typed password, not the value that + // may have been transformed for submit. + // TODO(gcasto): Do we need to have this action equality check? Is it trying + // to prevent accidentally copying over passwords from a different form? + if (submitted_form) { + if (provisionally_saved_forms_[frame].get() && + submitted_form->action == provisionally_saved_forms_[frame]->action) { + submitted_form->password_value = + provisionally_saved_forms_[frame]->password_value; + } + + // Some observers depend on sending this information now instead of when + // the frame starts loading. If there are redirects that cause a new + // RenderView to be instantiated (such as redirects to the WebStore) + // we will never get to finish the load. + Send(new AutofillHostMsg_PasswordFormSubmitted(routing_id(), + *submitted_form)); + // Remove reference since we have already submitted this form. + provisionally_saved_forms_.erase(frame); + } +} + +void PasswordAutofillAgent::DidStartProvisionalLoad(WebKit::WebFrame* frame) { + if (!frame->parent()) { + // If the navigation is not triggered by a user gesture, e.g. by some ajax + // callback, then inherit the submitted password form from the previous + // state. This fixes the no password save issue for ajax login, tracked in + // [http://crbug/43219]. Note that there are still some sites that this + // fails for because they use some element other than a submit button to + // trigger submission (which means WillSendSubmitEvent will not be called). + if (!WebKit::WebUserGestureIndicator::isProcessingUserGesture() && + provisionally_saved_forms_[frame].get()) { + Send(new AutofillHostMsg_PasswordFormSubmitted( + routing_id(), + *provisionally_saved_forms_[frame])); + provisionally_saved_forms_.erase(frame); + } + // Clear the whole map during main frame navigation. + provisionally_saved_forms_.clear(); + } +} + void PasswordAutofillAgent::OnFillPasswordForm( const PasswordFormFillData& form_data) { if (usernames_usage_ == NOTHING_TO_AUTOFILL) { diff --git a/chromium/components/autofill/content/renderer/password_autofill_agent.h b/chromium/components/autofill/content/renderer/password_autofill_agent.h index 30fef628c15..a389ed2fae6 100644 --- a/chromium/components/autofill/content/renderer/password_autofill_agent.h +++ b/chromium/components/autofill/content/renderer/password_autofill_agent.h @@ -8,6 +8,7 @@ #include <map> #include <vector> +#include "base/memory/linked_ptr.h" #include "base/memory/weak_ptr.h" #include "components/autofill/core/common/password_form_fill_data.h" #include "content/public/renderer/render_view_observer.h" @@ -16,6 +17,7 @@ namespace WebKit { class WebInputElement; class WebKeyboardEvent; +class WebSecurityOrigin; class WebView; } @@ -48,6 +50,13 @@ class PasswordAutofillAgent : public content::RenderViewObserver { // Returns true if any suggestions were shown, false otherwise. bool ShowSuggestions(const WebKit::WebInputElement& element); + // Called when new form controls are inserted. + void OnDynamicFormsSeen(WebKit::WebFrame* frame); + + protected: + virtual bool OriginCanAccessPasswordManager( + const WebKit::WebSecurityOrigin& origin); + private: friend class PasswordAutofillAgentTest; @@ -67,14 +76,21 @@ class PasswordAutofillAgent : public content::RenderViewObserver { PasswordInfo() : backspace_pressed_last(false) {} }; typedef std::map<WebKit::WebElement, PasswordInfo> LoginToPasswordInfoMap; + typedef std::map<WebKit::WebFrame*, + linked_ptr<PasswordForm> > FrameToPasswordFormMap; // RenderViewObserver: virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE; + virtual void DidStartProvisionalLoad(WebKit::WebFrame* frame) OVERRIDE; virtual void DidStartLoading() OVERRIDE; virtual void DidFinishDocumentLoad(WebKit::WebFrame* frame) OVERRIDE; virtual void DidFinishLoad(WebKit::WebFrame* frame) OVERRIDE; virtual void FrameDetached(WebKit::WebFrame* frame) OVERRIDE; virtual void FrameWillClose(WebKit::WebFrame* frame) OVERRIDE; + virtual void WillSendSubmitEvent(WebKit::WebFrame* frame, + const WebKit::WebFormElement& form) OVERRIDE; + virtual void WillSubmitForm(WebKit::WebFrame* frame, + const WebKit::WebFormElement& form) OVERRIDE; // RenderView IPC handlers: void OnFillPasswordForm(const PasswordFormFillData& form_data); @@ -123,6 +139,10 @@ class PasswordAutofillAgent : public content::RenderViewObserver { // Pointer to the WebView. Used to access page scale factor. WebKit::WebView* web_view_; + // Set if the user might be submitting a password form on the current page, + // but the submit may still fail (i.e. doesn't pass JavaScript validation). + FrameToPasswordFormMap provisionally_saved_forms_; + base::WeakPtrFactory<PasswordAutofillAgent> weak_ptr_factory_; DISALLOW_COPY_AND_ASSIGN(PasswordAutofillAgent); diff --git a/chromium/components/autofill/content/renderer/password_form_conversion_utils.cc b/chromium/components/autofill/content/renderer/password_form_conversion_utils.cc new file mode 100644 index 00000000000..7dfd45dcaa6 --- /dev/null +++ b/chromium/components/autofill/content/renderer/password_form_conversion_utils.cc @@ -0,0 +1,62 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/autofill/content/renderer/password_form_conversion_utils.h" + +#include "components/autofill/content/renderer/form_autofill_util.h" +#include "components/autofill/core/common/password_form.h" +#include "third_party/WebKit/public/web/WebFormControlElement.h" +#include "third_party/WebKit/public/web/WebPasswordFormData.h" + +using WebKit::WebFormElement; +using WebKit::WebPasswordFormData; + +namespace autofill { +namespace { + +scoped_ptr<PasswordForm> InitPasswordFormFromWebPasswordForm( + const WebFormElement& web_form, + const WebKit::WebPasswordFormData& web_password_form) { + PasswordForm* password_form = new PasswordForm(); + password_form->signon_realm = web_password_form.signonRealm.utf8(); + password_form->origin = web_password_form.origin; + password_form->action = web_password_form.action; + password_form->submit_element = web_password_form.submitElement; + password_form->username_element = web_password_form.userNameElement; + password_form->username_value = web_password_form.userNameValue; + password_form->other_possible_usernames.insert( + password_form->other_possible_usernames.begin(), + web_password_form.possibleUserNames.data(), + web_password_form.possibleUserNames.data() + + web_password_form.possibleUserNames.size()); + password_form->password_element = web_password_form.passwordElement; + password_form->password_value = web_password_form.passwordValue; + password_form->password_autocomplete_set = + web_password_form.passwordShouldAutocomplete; + password_form->old_password_element = web_password_form.oldPasswordElement; + password_form->old_password_value = web_password_form.oldPasswordValue; + password_form->scheme = PasswordForm::SCHEME_HTML; + password_form->ssl_valid = false; + password_form->preferred = false; + password_form->blacklisted_by_user = false; + password_form->type = PasswordForm::TYPE_MANUAL; + WebFormElementToFormData(web_form, + WebKit::WebFormControlElement(), + REQUIRE_NONE, + EXTRACT_NONE, + &password_form->form_data, + NULL /* FormFieldData */); + return scoped_ptr<PasswordForm>(password_form); +} + +} // namespace + +scoped_ptr<PasswordForm> CreatePasswordForm(const WebFormElement& webform) { + WebPasswordFormData web_password_form(webform); + if (web_password_form.isValid()) + return InitPasswordFormFromWebPasswordForm(webform, web_password_form); + return scoped_ptr<PasswordForm>(); +} + +} // namespace autofill diff --git a/chromium/components/autofill/content/renderer/password_form_conversion_utils.h b/chromium/components/autofill/content/renderer/password_form_conversion_utils.h new file mode 100644 index 00000000000..59786ede987 --- /dev/null +++ b/chromium/components/autofill/content/renderer/password_form_conversion_utils.h @@ -0,0 +1,27 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_AUTOFILL_CONTENT_RENDERER_PASSWORD_FORM_CONVERSION_UTILS_H_ +#define COMPONENTS_AUTOFILL_CONTENT_RENDERER_PASSWORD_FORM_CONVERSION_UTILS_H_ + +#include "base/memory/scoped_ptr.h" + +namespace WebKit { +class WebFormElement; +} + +namespace autofill { + +struct PasswordForm; + +// Create a PasswordForm from DOM form. Webkit doesn't allow storing +// custom metadata to DOM nodes, so we have to do this every time an event +// happens with a given form and compare against previously Create'd forms +// to identify..which sucks. +scoped_ptr<PasswordForm> CreatePasswordForm( + const WebKit::WebFormElement& form); + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CONTENT_RENDERER_PASSWORD_FORM_CONVERSION_UTILS_H__ diff --git a/chromium/components/autofill/content/renderer/password_form_conversion_utils_browsertest.cc b/chromium/components/autofill/content/renderer/password_form_conversion_utils_browsertest.cc new file mode 100644 index 00000000000..01b5f55e905 --- /dev/null +++ b/chromium/components/autofill/content/renderer/password_form_conversion_utils_browsertest.cc @@ -0,0 +1,91 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/strings/string16.h" +#include "base/strings/utf_string_conversions.h" +#include "components/autofill/content/renderer/password_form_conversion_utils.h" +#include "components/autofill/core/common/password_form.h" +#include "content/public/test/render_view_test.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/WebKit/public/platform/WebVector.h" +#include "third_party/WebKit/public/web/WebDocument.h" +#include "third_party/WebKit/public/web/WebFormElement.h" +#include "third_party/WebKit/public/web/WebPasswordFormData.h" + +using WebKit::WebFormElement; +using WebKit::WebFrame; +using WebKit::WebPasswordFormData; +using WebKit::WebVector; + +namespace autofill { +namespace { + +class PasswordFormConversionUtilsTest : public content::RenderViewTest { + public: + PasswordFormConversionUtilsTest() : content::RenderViewTest() {} + virtual ~PasswordFormConversionUtilsTest() {} + + private: + DISALLOW_COPY_AND_ASSIGN(PasswordFormConversionUtilsTest); +}; + +} // namespace + +TEST_F(PasswordFormConversionUtilsTest, ValidWebFormElementToPasswordForm) { + LoadHTML("<FORM name=\"TestForm\" action=\"http://cnn.com\" method=\"post\">" + " <INPUT type=\"text\" name=\"username\" " + " id=\"username\" value=\"johnsmith\"/>" + " <INPUT type=\"submit\" name=\"submit\" value=\"Submit\"/>" + " <INPUT type=\"password\" name=\"password\" id=\"password\" " + " value=\"secret\"/>" + "</FORM>"); + + WebFrame* frame = GetMainFrame(); + ASSERT_NE(static_cast<WebFrame*>(NULL), frame); + + WebVector<WebFormElement> forms; + frame->document().forms(forms); + ASSERT_EQ(1U, forms.size()); + WebPasswordFormData web_password_form(forms[0]); + ASSERT_TRUE(web_password_form.isValid()); + + scoped_ptr<PasswordForm> password_form = CreatePasswordForm(forms[0]); + ASSERT_NE(static_cast<PasswordForm*>(NULL), password_form.get()); + + EXPECT_EQ("data:", password_form->signon_realm); + EXPECT_EQ(GURL("http://cnn.com"), password_form->action); + EXPECT_EQ(UTF8ToUTF16("username"), password_form->username_element); + EXPECT_EQ(UTF8ToUTF16("johnsmith"), password_form->username_value); + EXPECT_EQ(UTF8ToUTF16("password"), password_form->password_element); + EXPECT_EQ(UTF8ToUTF16("secret"), password_form->password_value); + EXPECT_EQ(PasswordForm::SCHEME_HTML, password_form->scheme); + EXPECT_FALSE(password_form->ssl_valid); + EXPECT_FALSE(password_form->preferred); + EXPECT_FALSE(password_form->blacklisted_by_user); + EXPECT_EQ(PasswordForm::TYPE_MANUAL, password_form->type); +} + +TEST_F(PasswordFormConversionUtilsTest, InvalidWebFormElementToPasswordForm) { + LoadHTML("<FORM name=\"TestForm\" action=\"invalid\" method=\"post\">" + " <INPUT type=\"text\" name=\"username\" " + " id=\"username\" value=\"johnsmith\"/>" + " <INPUT type=\"submit\" name=\"submit\" value=\"Submit\"/>" + " <INPUT type=\"password\" name=\"password\" id=\"password\" " + " value=\"secret\"/>" + "</FORM>"); + + WebFrame* frame = GetMainFrame(); + ASSERT_NE(static_cast<WebFrame*>(NULL), frame); + + WebVector<WebFormElement> forms; + frame->document().forms(forms); + ASSERT_EQ(1U, forms.size()); + WebPasswordFormData web_password_form(forms[0]); + ASSERT_FALSE(web_password_form.isValid()); + + scoped_ptr<PasswordForm> password_form = CreatePasswordForm(forms[0]); + EXPECT_EQ(static_cast<PasswordForm*>(NULL), password_form.get()); +} + +} // namespace autofill diff --git a/chromium/components/autofill/content/renderer/password_generation_manager.cc b/chromium/components/autofill/content/renderer/password_generation_manager.cc index aa50cdc0b4d..8c035c7c0c4 100644 --- a/chromium/components/autofill/content/renderer/password_generation_manager.cc +++ b/chromium/components/autofill/content/renderer/password_generation_manager.cc @@ -5,9 +5,12 @@ #include "components/autofill/content/renderer/password_generation_manager.h" #include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "components/autofill/content/renderer/password_form_conversion_utils.h" #include "components/autofill/core/common/autofill_messages.h" +#include "components/autofill/core/common/form_data.h" +#include "components/autofill/core/common/password_form.h" #include "components/autofill/core/common/password_generation_util.h" -#include "content/public/renderer/password_form_conversion_utils.h" #include "content/public/renderer/render_view.h" #include "google_apis/gaia/gaia_urls.h" #include "third_party/WebKit/public/platform/WebCString.h" @@ -64,6 +67,27 @@ bool GetAccountCreationPasswordFields( return false; } +bool ContainsURL(const std::vector<GURL>& urls, const GURL& url) { + return std::find(urls.begin(), urls.end(), url) != urls.end(); +} + +// Returns true if the |form1| is essentially equal to |form2|. +bool FormEquals(const autofill::FormData& form1, + const PasswordForm& form2) { + // TODO(zysxqn): use more signals than just origin to compare. + return form1.origin == form2.origin; +} + +bool ContainsForm(const std::vector<autofill::FormData>& forms, + const PasswordForm& form) { + for (std::vector<autofill::FormData>::const_iterator it = + forms.begin(); it != forms.end(); ++it) { + if (FormEquals(*it, form)) + return true; + } + return false; +} + } // namespace PasswordGenerationManager::PasswordGenerationManager( @@ -83,10 +107,13 @@ void PasswordGenerationManager::DidFinishDocumentLoad(WebKit::WebFrame* frame) { // as we don't want subframe loads to clear state that we have recieved from // the main frame. Note that we assume there is only one account creation // form, but there could be multiple password forms in each frame. + // + // TODO(zysxqn): Add stat when local heuristic fires but we don't show the + // password generation icon. if (!frame->parent()) { not_blacklisted_password_form_origins_.clear(); - // Initialize to an empty and invalid GURL. - account_creation_form_origin_ = GURL(); + account_creation_forms_.clear(); + possible_account_creation_form_.reset(new PasswordForm()); passwords_.clear(); } } @@ -108,8 +135,8 @@ void PasswordGenerationManager::DidFinishLoad(WebKit::WebFrame* frame) { // If we can't get a valid PasswordForm, we skip this form because the // the password won't get saved even if we generate it. - scoped_ptr<content::PasswordForm> password_form( - content::CreatePasswordForm(forms[i])); + scoped_ptr<PasswordForm> password_form( + CreatePasswordForm(forms[i])); if (!password_form.get()) { DVLOG(2) << "Skipping form as it would not be saved"; continue; @@ -118,7 +145,7 @@ void PasswordGenerationManager::DidFinishLoad(WebKit::WebFrame* frame) { // Do not generate password for GAIA since it is used to retrieve the // generated paswords. GURL realm(password_form->signon_realm); - if (realm == GURL(GaiaUrls::GetInstance()->gaia_login_form_realm())) + if (realm == GaiaUrls::GetInstance()->gaia_login_form_realm()) continue; std::vector<WebKit::WebInputElement> passwords; @@ -127,7 +154,7 @@ void PasswordGenerationManager::DidFinishLoad(WebKit::WebFrame* frame) { password_generation::LogPasswordGenerationEvent( password_generation::SIGN_UP_DETECTED); passwords_ = passwords; - account_creation_form_origin_ = password_form->origin; + possible_account_creation_form_.swap(password_form); MaybeShowIcon(); // We assume that there is only one account creation field per URL. return; @@ -154,8 +181,8 @@ void PasswordGenerationManager::openPasswordGenerator( WebKit::WebInputElement& element) { WebKit::WebElement button(element.passwordGeneratorButtonElement()); gfx::Rect rect(button.boundsInViewportSpace()); - scoped_ptr<content::PasswordForm> password_form( - content::CreatePasswordForm(element.form())); + scoped_ptr<PasswordForm> password_form( + CreatePasswordForm(element.form())); // We should not have shown the icon we can't create a valid PasswordForm. DCHECK(password_form.get()); @@ -176,13 +203,14 @@ bool PasswordGenerationManager::OnMessageReceived(const IPC::Message& message) { OnPasswordAccepted) IPC_MESSAGE_HANDLER(AutofillMsg_PasswordGenerationEnabled, OnPasswordGenerationEnabled) + IPC_MESSAGE_HANDLER(AutofillMsg_AccountCreationFormsDetected, + OnAccountCreationFormsDetected) IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP() return handled; } -void PasswordGenerationManager::OnFormNotBlacklisted( - const content::PasswordForm& form) { +void PasswordGenerationManager::OnFormNotBlacklisted(const PasswordForm& form) { not_blacklisted_password_form_origins_.push_back(form.origin); MaybeShowIcon(); } @@ -203,27 +231,39 @@ void PasswordGenerationManager::OnPasswordGenerationEnabled(bool enabled) { enabled_ = enabled; } +void PasswordGenerationManager::OnAccountCreationFormsDetected( + const std::vector<autofill::FormData>& forms) { + account_creation_forms_.insert( + account_creation_forms_.end(), forms.begin(), forms.end()); + MaybeShowIcon(); +} + void PasswordGenerationManager::MaybeShowIcon() { // We should show the password generation icon only when we have detected - // account creation form and we have confirmed from browser that this form - // is not blacklisted by the users. - if (!account_creation_form_origin_.is_valid() || + // account creation form, we have confirmed from browser that this form + // is not blacklisted by the users, and the Autofill server has marked one + // of its field as ACCOUNT_CREATION_PASSWORD. + if (!possible_account_creation_form_.get() || passwords_.empty() || - not_blacklisted_password_form_origins_.empty()) { + not_blacklisted_password_form_origins_.empty() || + account_creation_forms_.empty()) { return; } - for (std::vector<GURL>::iterator it = - not_blacklisted_password_form_origins_.begin(); - it != not_blacklisted_password_form_origins_.end(); ++it) { - if (*it == account_creation_form_origin_) { - passwords_[0].passwordGeneratorButtonElement().setAttribute("style", - "display:block"); - password_generation::LogPasswordGenerationEvent( - password_generation::ICON_SHOWN); - return; - } + if (!ContainsURL(not_blacklisted_password_form_origins_, + possible_account_creation_form_->origin)) { + return; } + + if (!ContainsForm(account_creation_forms_, + *possible_account_creation_form_)) { + return; + } + + passwords_[0].passwordGeneratorButtonElement().setAttribute("style", + "display:block"); + password_generation::LogPasswordGenerationEvent( + password_generation::ICON_SHOWN); } } // namespace autofill diff --git a/chromium/components/autofill/content/renderer/password_generation_manager.h b/chromium/components/autofill/content/renderer/password_generation_manager.h index a6154286167..82ce48932d8 100644 --- a/chromium/components/autofill/content/renderer/password_generation_manager.h +++ b/chromium/components/autofill/content/renderer/password_generation_manager.h @@ -9,6 +9,7 @@ #include <utility> #include <vector> +#include "base/memory/scoped_ptr.h" #include "content/public/renderer/render_view_observer.h" #include "third_party/WebKit/public/web/WebInputElement.h" #include "third_party/WebKit/public/web/WebPasswordGeneratorClient.h" @@ -19,12 +20,11 @@ class WebCString; class WebDocument; } -namespace content { -struct PasswordForm; -} - namespace autofill { +struct FormData; +struct PasswordForm; + // This class is responsible for controlling communication for password // generation between the browser (which shows the popup and generates // passwords) and WebKit (shows the generation icon in the password field). @@ -51,9 +51,11 @@ class PasswordGenerationManager : public content::RenderViewObserver, virtual void openPasswordGenerator(WebKit::WebInputElement& element) OVERRIDE; // Message handlers. - void OnFormNotBlacklisted(const content::PasswordForm& form); + void OnFormNotBlacklisted(const PasswordForm& form); void OnPasswordAccepted(const base::string16& password); void OnPasswordGenerationEnabled(bool enabled); + void OnAccountCreationFormsDetected( + const std::vector<autofill::FormData>& forms); // Helper function to decide whether we should show password generation icon. void MaybeShowIcon(); @@ -65,13 +67,17 @@ class PasswordGenerationManager : public content::RenderViewObserver, bool enabled_; // Stores the origin of the account creation form we detected. - GURL account_creation_form_origin_; + scoped_ptr<PasswordForm> possible_account_creation_form_; // Stores the origins of the password forms confirmed not to be blacklisted // by the browser. A form can be blacklisted if a user chooses "never save // passwords for this site". std::vector<GURL> not_blacklisted_password_form_origins_; + // Stores each password form for which the Autofill server classifies one of + // the form's fields as an ACCOUNT_CREATION_PASSWORD. + std::vector<autofill::FormData> account_creation_forms_; + std::vector<WebKit::WebInputElement> passwords_; DISALLOW_COPY_AND_ASSIGN(PasswordGenerationManager); diff --git a/chromium/components/autofill/content/renderer/test_password_autofill_agent.cc b/chromium/components/autofill/content/renderer/test_password_autofill_agent.cc new file mode 100644 index 00000000000..10252d4f1f5 --- /dev/null +++ b/chromium/components/autofill/content/renderer/test_password_autofill_agent.cc @@ -0,0 +1,20 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/autofill/content/renderer/test_password_autofill_agent.h" + +namespace autofill { + +TestPasswordAutofillAgent::TestPasswordAutofillAgent( + content::RenderView* render_view) + : PasswordAutofillAgent(render_view) {} + +TestPasswordAutofillAgent::~TestPasswordAutofillAgent() {} + +bool TestPasswordAutofillAgent::OriginCanAccessPasswordManager( + const WebKit::WebSecurityOrigin& origin) { + return true; +} + +} // namespace autofill diff --git a/chromium/components/autofill/content/renderer/test_password_autofill_agent.h b/chromium/components/autofill/content/renderer/test_password_autofill_agent.h new file mode 100644 index 00000000000..1045ae01018 --- /dev/null +++ b/chromium/components/autofill/content/renderer/test_password_autofill_agent.h @@ -0,0 +1,27 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_AUTOFILL_CONTENT_RENDERER_TEST_PASSWORD_AUTOFILL_AGENT_H_ +#define COMPONENTS_AUTOFILL_CONTENT_RENDERER_TEST_PASSWORD_AUTOFILL_AGENT_H_ + +#include "components/autofill/content/renderer/password_autofill_agent.h" + +namespace autofill { + +class TestPasswordAutofillAgent : public PasswordAutofillAgent { + public: + explicit TestPasswordAutofillAgent(content::RenderView* render_view); + virtual ~TestPasswordAutofillAgent(); + + private: + // Always returns true. This allows browser tests with "data: " URL scheme to + // work with the password manager. + // PasswordAutofillAgent: + virtual bool OriginCanAccessPasswordManager( + const WebKit::WebSecurityOrigin& origin) OVERRIDE; +}; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CONTENT_RENDERER_TEST_PASSWORD_AUTOFILL_AGENT_H_ diff --git a/chromium/components/autofill/core/DEPS b/chromium/components/autofill/core/DEPS index caaebde2f13..d4d63b4a380 100644 --- a/chromium/components/autofill/core/DEPS +++ b/chromium/components/autofill/core/DEPS @@ -5,6 +5,5 @@ include_rules = [ # and please do not introduce more #includes of these files. "!content/public/common/common_param_traits.h", "!content/public/common/common_param_traits_macros.h", - "!content/public/common/password_form.h", "!third_party/WebKit/public/web/WebFormElement.h", ] diff --git a/chromium/components/autofill/core/browser/DEPS b/chromium/components/autofill/core/browser/DEPS index c59b4e28000..77ddfea7270 100644 --- a/chromium/components/autofill/core/browser/DEPS +++ b/chromium/components/autofill/core/browser/DEPS @@ -12,10 +12,6 @@ include_rules = [ # # Do not add to the list of temporarily-allowed dependencies below, # and please do not introduce more #includes of these files. - "!components/autofill/content/browser/autocheckout/whitelist_manager.h", - "!components/autofill/content/browser/autocheckout_manager.h", - "!components/autofill/content/browser/autocheckout_page_meta_data.h", - "!components/autofill/content/browser/autocheckout_steps.h", "!content/public/browser/android/content_view_core.h", "!content/public/browser/browser_context.h", "!content/public/browser/browser_thread.h", diff --git a/chromium/components/autofill/core/browser/address.cc b/chromium/components/autofill/core/browser/address.cc index 6f7f2a93ac7..e96183c15b0 100644 --- a/chromium/components/autofill/core/browser/address.cc +++ b/chromium/components/autofill/core/browser/address.cc @@ -44,8 +44,8 @@ Address& Address::operator=(const Address& address) { } base::string16 Address::GetRawInfo(ServerFieldType type) const { - // TODO(isherman): Is GetStorableType even necessary? - switch (AutofillType(type).GetStorableType()) { + DCHECK_EQ(ADDRESS_HOME, AutofillType(type).group()); + switch (type) { case ADDRESS_HOME_LINE1: return line1_; @@ -70,8 +70,8 @@ base::string16 Address::GetRawInfo(ServerFieldType type) const { } void Address::SetRawInfo(ServerFieldType type, const base::string16& value) { - // TODO(isherman): Is GetStorableType even necessary? - switch (AutofillType(type).GetStorableType()) { + DCHECK_EQ(ADDRESS_HOME, AutofillType(type).group()); + switch (type) { case ADDRESS_HOME_LINE1: line1_ = value; break; diff --git a/chromium/components/autofill/core/browser/android/component_jni_registrar.cc b/chromium/components/autofill/core/browser/android/component_jni_registrar.cc deleted file mode 100644 index f3aac1f02ad..00000000000 --- a/chromium/components/autofill/core/browser/android/component_jni_registrar.cc +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "components/autofill/core/browser/android/component_jni_registrar.h" - -#include "base/android/jni_android.h" -#include "base/android/jni_registrar.h" -#include "components/autofill/core/browser/android/auxiliary_profile_loader_android.h" - -namespace autofill { - -static base::android::RegistrationMethod kComponentRegisteredMethods[] = { - { "RegisterAuxiliaryProfileLoader", - autofill::RegisterAuxiliaryProfileLoader }, -}; - -bool RegisterAutofillAndroidJni(JNIEnv* env) { - return RegisterNativeMethods(env, - kComponentRegisteredMethods, arraysize(kComponentRegisteredMethods)); -} - -} // namespace autofill diff --git a/chromium/components/autofill/core/browser/android/component_jni_registrar.h b/chromium/components/autofill/core/browser/android/component_jni_registrar.h deleted file mode 100644 index 6cc0900cfdd..00000000000 --- a/chromium/components/autofill/core/browser/android/component_jni_registrar.h +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef COMPONENTS_AUTOFILL_CORE_BROWSER_ANDROID_COMPONENT_JNI_REGISTRAR_H_ -#define COMPONENTS_AUTOFILL_CORE_BROWSER_ANDROID_COMPONENT_JNI_REGISTRAR_H_ - -#include <jni.h> - -namespace autofill { - -// Register all JNI bindings necessary for the autofill -// component. -bool RegisterAutofillAndroidJni(JNIEnv* env); - -} // namespace autofill - -#endif // COMPONENTS_AUTOFILL_CORE_BROWSER_ANDROID_COMPONENT_JNI_REGISTRAR_H_ diff --git a/chromium/components/autofill/core/browser/android/java/src/org/chromium/components/browser/autofill/PersonalAutofillPopulator.java b/chromium/components/autofill/core/browser/android/java/src/org/chromium/components/browser/autofill/PersonalAutofillPopulator.java deleted file mode 100644 index b00ed61df29..00000000000 --- a/chromium/components/autofill/core/browser/android/java/src/org/chromium/components/browser/autofill/PersonalAutofillPopulator.java +++ /dev/null @@ -1,319 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -// Populates data fields from Android contacts profile API (i.e. "me" contact). - -package org.chromium.components.browser.autofill; - -import android.app.Activity; -import android.content.ContentProviderOperation; -import android.content.ContentResolver; -import android.content.Context; -import android.content.OperationApplicationException; -import android.content.pm.PackageManager; -import android.database.Cursor; -import android.database.DatabaseUtils; -import android.net.Uri; -import android.os.Bundle; -import android.os.RemoteException; -import android.provider.ContactsContract.CommonDataKinds.Phone; -import android.provider.ContactsContract.Profile; -import android.provider.ContactsContract; -import android.util.Log; -import android.view.View.OnClickListener; -import android.view.View; -import android.widget.Button; -import android.widget.Toast; - -import org.chromium.base.CalledByNative; -import org.chromium.base.JNINamespace; - -import java.util.ArrayList; - -/** - * Loads user profile information stored under the "Me" contact. - * Requires permissions: READ_CONTACTS and READ_PROFILE. - */ -@JNINamespace("autofill") -public class PersonalAutofillPopulator { - /** - * SQL query definitions for obtaining specific profile information. - */ - private abstract static class ProfileQuery { - Uri profileDataUri = Uri.withAppendedPath( - ContactsContract.Profile.CONTENT_URI, - ContactsContract.Contacts.Data.CONTENT_DIRECTORY - ); - public abstract String[] projection(); - public abstract String mimeType(); - } - - private static class EmailProfileQuery extends ProfileQuery { - private static final int EMAIL_ADDRESS = 0; - - @Override - public String[] projection() { - return new String[] { - ContactsContract.CommonDataKinds.Email.ADDRESS, - }; - } - - @Override - public String mimeType() { - return ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE; - } - } - - private static class PhoneProfileQuery extends ProfileQuery { - private static final int NUMBER = 0; - - @Override - public String[] projection() { - return new String[] { - ContactsContract.CommonDataKinds.Phone.NUMBER, - }; - } - - @Override - public String mimeType() { - return ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE; - } - } - - private static class AddressProfileQuery extends ProfileQuery { - private static final int STREET = 0; - private static final int POBOX = 1; - private static final int NEIGHBORHOOD = 2; - private static final int CITY = 3; - private static final int REGION = 4; - private static final int POSTALCODE = 5; - private static final int COUNTRY = 6; - - @Override - public String[] projection() { - return new String[] { - ContactsContract.CommonDataKinds.StructuredPostal.STREET, - ContactsContract.CommonDataKinds.StructuredPostal.POBOX, - ContactsContract.CommonDataKinds.StructuredPostal.NEIGHBORHOOD, - ContactsContract.CommonDataKinds.StructuredPostal.CITY, - ContactsContract.CommonDataKinds.StructuredPostal.REGION, - ContactsContract.CommonDataKinds.StructuredPostal.POSTCODE, - ContactsContract.CommonDataKinds.StructuredPostal.COUNTRY, - }; - } - - @Override - public String mimeType() { - return ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE; - } - } - - private static class NameProfileQuery extends ProfileQuery { - private static final int GIVEN_NAME = 0; - private static final int MIDDLE_NAME = 1; - private static final int FAMILY_NAME = 2; - private static final int SUFFIX = 3; - - @Override - public String[] projection() { - return new String[] { - ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME, - ContactsContract.CommonDataKinds.StructuredName.MIDDLE_NAME, - ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME, - ContactsContract.CommonDataKinds.StructuredName.SUFFIX - }; - } - - @Override - public String mimeType() { - return ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE; - } - } - - /** - * Takes a query object, transforms into actual query and returns cursor. - * Primary contact values will be first. - */ - private Cursor cursorFromProfileQuery(ProfileQuery query, ContentResolver contentResolver) { - String sortDescriptor = ContactsContract.Contacts.Data.IS_PRIMARY + " DESC"; - return contentResolver.query( - query.profileDataUri, - query.projection(), - ContactsContract.Contacts.Data.MIMETYPE + " = ?", - new String[]{query.mimeType()}, - sortDescriptor - ); - } - // Extracted data variables. - private String[] mEmailAddresses; - private String mGivenName; - private String mMiddleName; - private String mFamilyName; - private String mSuffix; - private String mPobox; - private String mStreet; - private String mNeighborhood; - private String mCity; - private String mRegion; - private String mCountry; - private String mPostalCode; - private String[] mPhoneNumbers; - private boolean mHasPermissions; - - /** - * Constructor - * @param context a valid android context reference - */ - PersonalAutofillPopulator(Context context) { - mHasPermissions = hasPermissions(context); - if (mHasPermissions) { - ContentResolver contentResolver = context.getContentResolver(); - populateName(contentResolver); - populateEmail(contentResolver); - populateAddress(contentResolver); - populatePhone(contentResolver); - } - } - - // Check if the user has granted permissions. - private boolean hasPermissions(Context context) { - String [] permissions = { - "android.permission.READ_CONTACTS", - "android.permission.READ_PROFILE" - }; - for (String permission : permissions) { - int res = context.checkCallingOrSelfPermission(permission); - if (res != PackageManager.PERMISSION_GRANTED) return false; - } - return true; - } - - // Populating data fields. - private void populateName(ContentResolver contentResolver) { - NameProfileQuery nameProfileQuery = new NameProfileQuery(); - Cursor nameCursor = cursorFromProfileQuery(nameProfileQuery, contentResolver); - if (nameCursor.moveToNext()) { - mGivenName = nameCursor.getString(nameProfileQuery.GIVEN_NAME); - mMiddleName = nameCursor.getString(nameProfileQuery.MIDDLE_NAME); - mFamilyName = nameCursor.getString(nameProfileQuery.FAMILY_NAME); - mSuffix = nameCursor.getString(nameProfileQuery.SUFFIX); - } - nameCursor.close(); - } - - private void populateEmail(ContentResolver contentResolver) { - EmailProfileQuery emailProfileQuery = new EmailProfileQuery(); - Cursor emailCursor = cursorFromProfileQuery(emailProfileQuery, contentResolver); - mEmailAddresses = new String[emailCursor.getCount()]; - for (int i = 0; emailCursor.moveToNext(); i++) { - mEmailAddresses[i] = emailCursor.getString(emailProfileQuery.EMAIL_ADDRESS); - } - emailCursor.close(); - } - - private void populateAddress(ContentResolver contentResolver) { - AddressProfileQuery addressProfileQuery = new AddressProfileQuery(); - Cursor addressCursor = cursorFromProfileQuery(addressProfileQuery, contentResolver); - if(addressCursor.moveToNext()) { - mPobox = addressCursor.getString(addressProfileQuery.POBOX); - mStreet = addressCursor.getString(addressProfileQuery.STREET); - mNeighborhood = addressCursor.getString(addressProfileQuery.NEIGHBORHOOD); - mCity = addressCursor.getString(addressProfileQuery.CITY); - mRegion = addressCursor.getString(addressProfileQuery.REGION); - mPostalCode = addressCursor.getString(addressProfileQuery.POSTALCODE); - mCountry = addressCursor.getString(addressProfileQuery.COUNTRY); - } - addressCursor.close(); - } - - private void populatePhone(ContentResolver contentResolver) { - PhoneProfileQuery phoneProfileQuery = new PhoneProfileQuery(); - Cursor phoneCursor = cursorFromProfileQuery(phoneProfileQuery, contentResolver); - mPhoneNumbers = new String[phoneCursor.getCount()]; - for (int i = 0; phoneCursor.moveToNext(); i++) { - mPhoneNumbers[i] = phoneCursor.getString(phoneProfileQuery.NUMBER); - } - phoneCursor.close(); - } - - /** - * Static factory method for instance creation. - * @param context valid Android context. - * @return PersonalAutofillPopulator new instance of PersonalAutofillPopulator. - */ - @CalledByNative - static PersonalAutofillPopulator create(Context context) { - return new PersonalAutofillPopulator(context); - } - - @CalledByNative - private String getFirstName() { - return mGivenName; - } - - @CalledByNative - private String getLastName() { - return mFamilyName; - } - - @CalledByNative - private String getMiddleName() { - return mMiddleName; - } - - @CalledByNative - private String getSuffix() { - return mSuffix; - } - - @CalledByNative - private String[] getEmailAddresses() { - return mEmailAddresses; - } - - @CalledByNative - private String getStreet() { - return mStreet; - } - - @CalledByNative - private String getPobox() { - return mPobox; - } - - @CalledByNative - private String getNeighborhood() { - return mNeighborhood; - } - - @CalledByNative - private String getCity() { - return mCity; - } - - @CalledByNative - private String getRegion() { - return mRegion; - } - - @CalledByNative - private String getPostalCode() { - return mPostalCode; - } - - @CalledByNative - private String getCountry() { - return mCountry; - } - - @CalledByNative - private String[] getPhoneNumbers() { - return mPhoneNumbers; - } - - @CalledByNative - private boolean getHasPermissions() { - return mHasPermissions; - } -} diff --git a/chromium/components/autofill/core/browser/autocheckout_bubble_state.h b/chromium/components/autofill/core/browser/autocheckout_bubble_state.h deleted file mode 100644 index b015cb01d30..00000000000 --- a/chromium/components/autofill/core/browser/autocheckout_bubble_state.h +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef COMPONENTS_AUTOFILL_CORE_BROWSER_AUTOCHECKOUT_BUBBLE_STATE_H_ -#define COMPONENTS_AUTOFILL_CORE_BROWSER_AUTOCHECKOUT_BUBBLE_STATE_H_ - -namespace autofill { - -// Specifies the state of Autocheckout bubble. -enum AutocheckoutBubbleState { - // User clicked on continue. - AUTOCHECKOUT_BUBBLE_ACCEPTED = 0, - // User clicked on "No Thanks". - AUTOCHECKOUT_BUBBLE_CANCELED = 1, - // Bubble is ignored. - AUTOCHECKOUT_BUBBLE_IGNORED = 2, -}; - -} // namespace autofill - -#endif // COMPONENTS_AUTOFILL_CORE_BROWSER_AUTOCHECKOUT_BUBBLE_STATE_H_ diff --git a/chromium/components/autofill/core/browser/autofill_common_test.cc b/chromium/components/autofill/core/browser/autofill_common_test.cc index f477b25c2e6..2187d8b7bea 100644 --- a/chromium/components/autofill/core/browser/autofill_common_test.cc +++ b/chromium/components/autofill/core/browser/autofill_common_test.cc @@ -128,12 +128,25 @@ CreditCard GetCreditCard() { return credit_card; } +CreditCard GetCreditCard2() { + CreditCard credit_card(base::GenerateGUID(), "https://www.example.com"); + SetCreditCardInfo( + &credit_card, "Someone Else", "378282246310005" /* AmEx */, "07", "2019"); + return credit_card; +} + CreditCard GetVerifiedCreditCard() { CreditCard credit_card(GetCreditCard()); credit_card.set_origin(kSettingsOrigin); return credit_card; } +CreditCard GetVerifiedCreditCard2() { + CreditCard credit_card(GetCreditCard2()); + credit_card.set_origin(kSettingsOrigin); + return credit_card; +} + void SetProfileInfo(AutofillProfile* profile, const char* first_name, const char* middle_name, const char* last_name, const char* email, const char* company, diff --git a/chromium/components/autofill/core/browser/autofill_common_test.h b/chromium/components/autofill/core/browser/autofill_common_test.h index b0818f70df7..8d6a56ff0fa 100644 --- a/chromium/components/autofill/core/browser/autofill_common_test.h +++ b/chromium/components/autofill/core/browser/autofill_common_test.h @@ -46,9 +46,15 @@ AutofillProfile GetVerifiedProfile2(); // Returns a credit card full of dummy info. CreditCard GetCreditCard(); +// Returns a credit card full of dummy info, different to the above. +CreditCard GetCreditCard2(); + // Returns a verified credit card full of dummy info. CreditCard GetVerifiedCreditCard(); +// Returns a verified credit card full of dummy info, different to the above. +CreditCard GetVerifiedCreditCard2(); + // A unit testing utility that is common to a number of the Autofill unit // tests. |SetProfileInfo| provides a quick way to populate a profile with // c-strings. diff --git a/chromium/components/autofill/core/browser/autofill_download_unittest.cc b/chromium/components/autofill/core/browser/autofill_download_unittest.cc index 9035c9cdafa..834772f9d04 100644 --- a/chromium/components/autofill/core/browser/autofill_download_unittest.cc +++ b/chromium/components/autofill/core/browser/autofill_download_unittest.cc @@ -164,7 +164,7 @@ TEST_F(AutofillDownloadTest, QueryAndUploadTest) { field.form_control_type = "submit"; form.fields.push_back(field); - FormStructure *form_structure = new FormStructure(form, std::string()); + FormStructure *form_structure = new FormStructure(form); ScopedVector<FormStructure> form_structures; form_structures.push_back(form_structure); @@ -190,7 +190,7 @@ TEST_F(AutofillDownloadTest, QueryAndUploadTest) { field.form_control_type = "submit"; form.fields.push_back(field); - form_structure = new FormStructure(form, std::string()); + form_structure = new FormStructure(form); form_structures.push_back(form_structure); // Request with id 0. @@ -283,7 +283,7 @@ TEST_F(AutofillDownloadTest, QueryAndUploadTest) { field.name = ASCIIToUTF16("address2"); field.form_control_type = "text"; form.fields.push_back(field); - form_structure = new FormStructure(form, std::string()); + form_structure = new FormStructure(form); form_structures.push_back(form_structure); // Request with id 3. @@ -354,7 +354,7 @@ TEST_F(AutofillDownloadTest, CacheQueryTest) { field.name = ASCIIToUTF16("lastname"); form.fields.push_back(field); - FormStructure *form_structure = new FormStructure(form, std::string()); + FormStructure *form_structure = new FormStructure(form); ScopedVector<FormStructure> form_structures0; form_structures0.push_back(form_structure); @@ -362,7 +362,7 @@ TEST_F(AutofillDownloadTest, CacheQueryTest) { field.label = ASCIIToUTF16("email"); field.name = ASCIIToUTF16("email"); form.fields.push_back(field); - form_structure = new FormStructure(form, std::string()); + form_structure = new FormStructure(form); ScopedVector<FormStructure> form_structures1; form_structures1.push_back(form_structure); @@ -371,7 +371,7 @@ TEST_F(AutofillDownloadTest, CacheQueryTest) { field.label = ASCIIToUTF16("email2"); field.name = ASCIIToUTF16("email2"); form.fields.push_back(field); - form_structure = new FormStructure(form, std::string()); + form_structure = new FormStructure(form); ScopedVector<FormStructure> form_structures2; form_structures2.push_back(form_structure); diff --git a/chromium/components/autofill/core/browser/autofill_external_delegate.cc b/chromium/components/autofill/core/browser/autofill_external_delegate.cc index 95119bfad99..ca6696e32a2 100644 --- a/chromium/components/autofill/core/browser/autofill_external_delegate.cc +++ b/chromium/components/autofill/core/browser/autofill_external_delegate.cc @@ -36,7 +36,7 @@ AutofillExternalDelegate::AutofillExternalDelegate( display_warning_if_disabled_(false), has_autofill_suggestion_(false), has_shown_autofill_popup_for_current_edit_(false), - registered_keyboard_listener_with_(NULL), + registered_key_press_event_callback_with_(NULL), weak_ptr_factory_(this) { DCHECK(autofill_manager); } @@ -162,10 +162,12 @@ void AutofillExternalDelegate::SetCurrentDataListValues( } void AutofillExternalDelegate::OnPopupShown( - content::KeyboardListener* listener) { - if (!registered_keyboard_listener_with_) { - registered_keyboard_listener_with_ = web_contents_->GetRenderViewHost(); - registered_keyboard_listener_with_->AddKeyboardListener(listener); + content::RenderWidgetHost::KeyPressEventCallback* callback) { + if (callback && !registered_key_press_event_callback_with_) { + registered_key_press_event_callback_with_ = + web_contents_->GetRenderViewHost(); + registered_key_press_event_callback_with_->AddKeyPressEventCallback( + *callback); } autofill_manager_->OnDidShowAutofillSuggestions( @@ -174,14 +176,19 @@ void AutofillExternalDelegate::OnPopupShown( } void AutofillExternalDelegate::OnPopupHidden( - content::KeyboardListener* listener) { - if ((!web_contents_->IsBeingDestroyed()) && - (registered_keyboard_listener_with_ == + content::RenderWidgetHost::KeyPressEventCallback* callback) { + if (callback && (!web_contents_->IsBeingDestroyed()) && + (registered_key_press_event_callback_with_ == web_contents_->GetRenderViewHost())) { - web_contents_->GetRenderViewHost()->RemoveKeyboardListener(listener); + web_contents_->GetRenderViewHost()->RemoveKeyPressEventCallback(*callback); } - registered_keyboard_listener_with_ = NULL; + registered_key_press_event_callback_with_ = NULL; +} + +bool AutofillExternalDelegate::ShouldRepostEvent(const ui::MouseEvent& event) { + NOTREACHED(); + return true; } void AutofillExternalDelegate::DidSelectSuggestion(int identifier) { diff --git a/chromium/components/autofill/core/browser/autofill_external_delegate.h b/chromium/components/autofill/core/browser/autofill_external_delegate.h index 3362ff8e19b..0a0093e9ed1 100644 --- a/chromium/components/autofill/core/browser/autofill_external_delegate.h +++ b/chromium/components/autofill/core/browser/autofill_external_delegate.h @@ -47,8 +47,11 @@ class AutofillExternalDelegate virtual ~AutofillExternalDelegate(); // AutofillPopupDelegate implementation. - virtual void OnPopupShown(content::KeyboardListener* listener) OVERRIDE; - virtual void OnPopupHidden(content::KeyboardListener* listener) OVERRIDE; + virtual void OnPopupShown( + content::RenderWidgetHost::KeyPressEventCallback* callback) OVERRIDE; + virtual void OnPopupHidden( + content::RenderWidgetHost::KeyPressEventCallback* callback) OVERRIDE; + virtual bool ShouldRepostEvent(const ui::MouseEvent& event) OVERRIDE; virtual void DidSelectSuggestion(int identifier) OVERRIDE; virtual void DidAcceptSuggestion(const base::string16& value, int identifier) OVERRIDE; @@ -167,9 +170,9 @@ class AutofillExternalDelegate // currently editing? Used to keep track of state for metrics logging. bool has_shown_autofill_popup_for_current_edit_; - // The RenderViewHost that this object has been registered with as a - // keyboard listener. - content::RenderViewHost* registered_keyboard_listener_with_; + // The RenderViewHost that this object has been registered with as a key press + // event callback. + content::RenderViewHost* registered_key_press_event_callback_with_; // The current data list values. std::vector<base::string16> data_list_values_; diff --git a/chromium/components/autofill/core/browser/autofill_manager.cc b/chromium/components/autofill/core/browser/autofill_manager.cc index 4c66699b819..6f99772f1ec 100644 --- a/chromium/components/autofill/core/browser/autofill_manager.cc +++ b/chromium/components/autofill/core/browser/autofill_manager.cc @@ -20,8 +20,6 @@ #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" #include "base/threading/sequenced_worker_pool.h" -#include "components/autofill/content/browser/autocheckout/whitelist_manager.h" -#include "components/autofill/content/browser/autocheckout_manager.h" #include "components/autofill/core/browser/autocomplete_history_manager.h" #include "components/autofill/core/browser/autofill_data_model.h" #include "components/autofill/core/browser/autofill_driver.h" @@ -129,7 +127,7 @@ bool SectionIsAutofilled(const FormStructure& form_structure, } bool FormIsHTTPS(const FormStructure& form) { - return form.source_url().SchemeIs(chrome::kHttpsScheme); + return form.source_url().SchemeIs(content::kHttpsScheme); } // Uses the existing personal data in |profiles| and |credit_cards| to determine @@ -148,16 +146,21 @@ void DeterminePossibleFieldTypesForUpload( // profile or credit card, identify any stored types that match the value. for (size_t i = 0; i < submitted_form->field_count(); ++i) { AutofillField* field = submitted_form->field(i); - base::string16 value = CollapseWhitespace(field->value, false); - ServerFieldTypeSet matching_types; - for (std::vector<AutofillProfile>::const_iterator it = profiles.begin(); - it != profiles.end(); ++it) { - it->GetMatchingTypes(value, app_locale, &matching_types); - } - for (std::vector<CreditCard>::const_iterator it = credit_cards.begin(); - it != credit_cards.end(); ++it) { - it->GetMatchingTypes(value, app_locale, &matching_types); + + // If it's a password field, set the type directly. + if (field->form_control_type == "password") { + matching_types.insert(autofill::PASSWORD); + } else { + base::string16 value = CollapseWhitespace(field->value, false); + for (std::vector<AutofillProfile>::const_iterator it = profiles.begin(); + it != profiles.end(); ++it) { + it->GetMatchingTypes(value, app_locale, &matching_types); + } + for (std::vector<CreditCard>::const_iterator it = credit_cards.begin(); + it != credit_cards.end(); ++it) { + it->GetMatchingTypes(value, app_locale, &matching_types); + } } if (matching_types.empty()) @@ -190,7 +193,6 @@ AutofillManager::AutofillManager( personal_data_(delegate->GetPersonalDataManager()), autocomplete_history_manager_( new AutocompleteHistoryManager(driver, delegate)), - autocheckout_manager_(this), metric_logger_(new AutofillMetrics), has_logged_autofill_enabled_(false), has_logged_address_suggestions_count_(false), @@ -251,24 +253,16 @@ bool AutofillManager::OnFormSubmitted(const FormData& form, // Let Autocomplete know as well. autocomplete_history_manager_->OnFormSubmitted(form); - if (!IsAutofillEnabled()) - return false; + // Grab a copy of the form data. + scoped_ptr<FormStructure> submitted_form(new FormStructure(form)); - if (driver_->GetWebContents()->GetBrowserContext()->IsOffTheRecord()) + if (!ShouldUploadForm(*submitted_form)) return false; // Don't save data that was submitted through JavaScript. if (!form.user_submitted) return false; - // Grab a copy of the form data. - scoped_ptr<FormStructure> submitted_form( - new FormStructure(form, GetAutocheckoutURLPrefix())); - - // Disregard forms that we wouldn't ever autofill in the first place. - if (!submitted_form->ShouldBeParsed(true)) - return false; - // Ignore forms not present in our cache. These are typically forms with // wonky JavaScript that also makes them not auto-fillable. FormStructure* cached_submitted_form; @@ -276,9 +270,7 @@ bool AutofillManager::OnFormSubmitted(const FormData& form, return false; submitted_form->UpdateFromCache(*cached_submitted_form); - // Don't prompt the user to save data entered by Autocheckout. - if (submitted_form->IsAutofillable(true) && - !submitted_form->filled_by_autocheckout()) + if (submitted_form->IsAutofillable(true)) ImportFormData(*submitted_form); // Only upload server statistics and UMA metrics if at least some local data @@ -328,34 +320,14 @@ void AutofillManager::OnFormsSeen(const std::vector<FormData>& forms, const TimeTicks& timestamp, autofill::FormsSeenState state) { bool is_post_document_load = state == autofill::DYNAMIC_FORMS_SEEN; - bool has_more_forms = state == autofill::PARTIAL_FORMS_SEEN; - // If new forms were added dynamically, and the autocheckout manager - // doesn't tell us to ignore ajax on this page, treat as a new page. - if (is_post_document_load) { - if (autocheckout_manager_.ShouldIgnoreAjax()) - return; - + // If new forms were added dynamically, treat as a new page. + if (is_post_document_load) Reset(); - } RenderViewHost* host = driver_->GetWebContents()->GetRenderViewHost(); if (!host) return; - if (!GetAutocheckoutURLPrefix().empty()) { - // If whitelisted URL, fetch all the forms. - if (has_more_forms) - host->Send(new AutofillMsg_GetAllForms(host->GetRoutingID())); - if (!is_post_document_load) { - host->Send( - new AutofillMsg_AutocheckoutSupported(host->GetRoutingID())); - } - // Now return early, as OnFormsSeen will get called again with all forms. - if (has_more_forms) - return; - } - - autocheckout_manager_.OnFormsSeen(); bool enabled = IsAutofillEnabled(); if (!has_logged_autofill_enabled_) { metric_logger_->LogIsAutofillEnabledAtPageLoad(enabled); @@ -378,7 +350,6 @@ void AutofillManager::OnTextFieldDidChange(const FormData& form, return; if (!user_did_type_) { - autocheckout_manager_.set_should_show_bubble(false); user_did_type_ = true; metric_logger_->LogUserHappinessMetric(AutofillMetrics::USER_DID_TYPE); } @@ -403,9 +374,6 @@ void AutofillManager::OnQueryFormFieldAutofill(int query_id, const FormFieldData& field, const gfx::RectF& bounding_box, bool display_warning) { - if (autocheckout_manager_.is_autocheckout_bubble_showing()) - return; - std::vector<base::string16> values; std::vector<base::string16> labels; std::vector<base::string16> icons; @@ -616,7 +584,6 @@ void AutofillManager::OnHideAutofillUI() { return; manager_delegate_->HideAutofillPopup(); - manager_delegate_->HideAutocheckoutBubble(); } void AutofillManager::RemoveAutofillProfileOrCreditCard(int unique_id) { @@ -649,16 +616,6 @@ const std::vector<FormStructure*>& AutofillManager::GetFormStructures() { return form_structures_.get(); } -void AutofillManager::ShowRequestAutocompleteDialog( - const FormData& form, - const GURL& source_url, - autofill::DialogType dialog_type, - const base::Callback<void(const FormStructure*, - const std::string&)>& callback) { - manager_delegate_->ShowRequestAutocompleteDialog( - form, source_url, dialog_type, callback); -} - void AutofillManager::SetTestDelegate( autofill::AutofillManagerTestDelegate* delegate) { test_delegate_ = delegate; @@ -698,11 +655,10 @@ void AutofillManager::OnRequestAutocomplete( return; } - base::Callback<void(const FormStructure*, const std::string&)> callback = + base::Callback<void(const FormStructure*)> callback = base::Bind(&AutofillManager::ReturnAutocompleteData, weak_ptr_factory_.GetWeakPtr()); - ShowRequestAutocompleteDialog( - form, frame_url, autofill::DIALOG_TYPE_REQUEST_AUTOCOMPLETE, callback); + ShowRequestAutocompleteDialog(form, frame_url, callback); } void AutofillManager::ReturnAutocompleteResult( @@ -721,9 +677,7 @@ void AutofillManager::ReturnAutocompleteResult( form_data)); } -void AutofillManager::ReturnAutocompleteData( - const FormStructure* result, - const std::string& unused_transaction_id) { +void AutofillManager::ReturnAutocompleteData(const FormStructure* result) { if (!result) { ReturnAutocompleteResult(WebFormElement::AutocompleteResultErrorCancel, FormData()); @@ -735,27 +689,14 @@ void AutofillManager::ReturnAutocompleteData( void AutofillManager::OnLoadedServerPredictions( const std::string& response_xml) { - scoped_ptr<autofill::AutocheckoutPageMetaData> page_meta_data( - new autofill::AutocheckoutPageMetaData()); - // Parse and store the server predictions. FormStructure::ParseQueryResponse(response_xml, form_structures_.get(), - page_meta_data.get(), *metric_logger_); - if (page_meta_data->IsInAutofillableFlow()) { - RenderViewHost* host = driver_->GetWebContents()->GetRenderViewHost(); - if (host) - host->Send(new AutofillMsg_AutocheckoutSupported(host->GetRoutingID())); - } - - // TODO(ahutter): Remove this once Autocheckout is implemented on other - // platforms. See http://crbug.com/173416. -#if defined(TOOLKIT_VIEWS) - if (!GetAutocheckoutURLPrefix().empty()) - autocheckout_manager_.OnLoadedPageMetaData(page_meta_data.Pass()); -#endif // #if defined(TOOLKIT_VIEWS) + // Forward form structures to the password generation manager to detect + // account creation forms. + manager_delegate_->DetectAccountCreationForms(form_structures_.get()); // If the corresponding flag is set, annotate forms with the predicted types. driver_->SendAutofillTypePredictionsToRenderer(form_structures_.get()); @@ -765,22 +706,6 @@ void AutofillManager::OnDidEndTextFieldEditing() { external_delegate_->DidEndTextFieldEditing(); } -void AutofillManager::OnAutocheckoutPageCompleted( - autofill::AutocheckoutStatus status) { - autocheckout_manager_.OnAutocheckoutPageCompleted(status); -} - -std::string AutofillManager::GetAutocheckoutURLPrefix() const { - if (!driver_->GetWebContents()) - return std::string(); - - autofill::autocheckout::WhitelistManager* whitelist_manager = - manager_delegate_->GetAutocheckoutWhitelistManager(); - - return whitelist_manager ? whitelist_manager->GetMatchedURLPrefix( - driver_->GetWebContents()->GetURL()) : std::string(); -} - bool AutofillManager::IsAutofillEnabled() const { return manager_delegate_->GetPrefs()->GetBoolean(prefs::kAutofillEnabled); } @@ -796,8 +721,9 @@ void AutofillManager::ImportFormData(const FormStructure& submitted_form) { manager_delegate_->ConfirmSaveCreditCard( *metric_logger_, *imported_credit_card, - base::Bind(&PersonalDataManager::SaveImportedCreditCard, - base::Unretained(personal_data_), *imported_credit_card)); + base::Bind( + base::IgnoreResult(&PersonalDataManager::SaveImportedCreditCard), + base::Unretained(personal_data_), *imported_credit_card)); } } @@ -819,26 +745,6 @@ void AutofillManager::UploadFormDataAsyncCallback( UploadFormData(*submitted_form); } -void AutofillManager::OnMaybeShowAutocheckoutBubble( - const FormData& form, - const gfx::RectF& bounding_box) { - if (!IsAutofillEnabled()) - return; - - // Don't show bubble if corresponding FormStructure doesn't have anything to - // autofill. - FormStructure* cached_form; - if (!FindCachedForm(form, &cached_form)) - return; - - // Don't offer Autocheckout bubble if Autofill server is not aware of this - // form in the context of Autocheckout experiment. - if (!HasServerSpecifiedFieldTypes(*cached_form)) - return; - - autocheckout_manager_.MaybeShowAutocheckoutBubble(form.origin, bounding_box); -} - void AutofillManager::UploadFormData(const FormStructure& submitted_form) { if (!download_manager_) return; @@ -856,9 +762,59 @@ void AutofillManager::UploadFormData(const FormStructure& submitted_form) { ServerFieldTypeSet non_empty_types; personal_data_->GetNonEmptyTypes(&non_empty_types); + // Always add PASSWORD to |non_empty_types| so that if |submitted_form| + // contains a password field it will be uploaded to the server. If + // |submitted_form| doesn't contain a password field, there is no side + // effect from adding PASSWORD to |non_empty_types|. + non_empty_types.insert(autofill::PASSWORD); download_manager_->StartUploadRequest(submitted_form, was_autofilled, - non_empty_types); + non_empty_types); +} + +bool AutofillManager::UploadPasswordGenerationForm(const FormData& form) { + FormStructure form_structure(form); + + if (!ShouldUploadForm(form_structure)) + return false; + + if (!form_structure.ShouldBeCrowdsourced()) + return false; + + // TODO(gcasto): Check that PasswordGeneration is enabled? + + // Find the first password field to label. We don't try to label anything + // else. + bool found_password_field = false; + for (size_t i = 0; i < form_structure.field_count(); ++i) { + AutofillField* field = form_structure.field(i); + + ServerFieldTypeSet types; + if (!found_password_field && field->form_control_type == "password") { + types.insert(ACCOUNT_CREATION_PASSWORD); + found_password_field = true; + } else { + types.insert(UNKNOWN_TYPE); + } + field->set_possible_types(types); + } + DCHECK(found_password_field); + + // Only one field type should be present. + ServerFieldTypeSet available_field_types; + available_field_types.insert(ACCOUNT_CREATION_PASSWORD); + + // Force uploading as these events are relatively rare and we want to make + // sure to receive them. It also makes testing easier if these requests + // always pass. + form_structure.set_upload_required(UPLOAD_REQUIRED); + + if (!download_manager_) + return false; + + return download_manager_->StartUploadRequest(form_structure, + false /* was_autofilled */, + available_field_types); } void AutofillManager::Reset() { @@ -883,7 +839,6 @@ AutofillManager::AutofillManager(AutofillDriver* driver, personal_data_(personal_data), autocomplete_history_manager_( new AutocompleteHistoryManager(driver, delegate)), - autocheckout_manager_(this), metric_logger_(new AutofillMetrics), has_logged_autofill_enabled_(false), has_logged_address_suggestions_count_(false), @@ -983,7 +938,7 @@ bool AutofillManager::GetCachedFormAndField(const FormData& form, // If we do not have this form in our cache but it is parseable, we'll add it // in the call to |UpdateCachedForm()|. if (!FindCachedForm(form, form_structure) && - !FormStructure(form, GetAutocheckoutURLPrefix()).ShouldBeParsed(false)) { + !FormStructure(form).ShouldBeParsed(false)) { return false; } @@ -1030,8 +985,7 @@ bool AutofillManager::UpdateCachedForm(const FormData& live_form, return false; // Add the new or updated form to our cache. - form_structures_.push_back( - new FormStructure(live_form, GetAutocheckoutURLPrefix())); + form_structures_.push_back(new FormStructure(live_form)); *updated_form = *form_structures_.rbegin(); (*updated_form)->DetermineHeuristicTypes(*metric_logger_); @@ -1107,11 +1061,9 @@ void AutofillManager::GetCreditCardSuggestions( void AutofillManager::ParseForms(const std::vector<FormData>& forms) { std::vector<FormStructure*> non_queryable_forms; - std::string autocheckout_url_prefix = GetAutocheckoutURLPrefix(); for (std::vector<FormData>::const_iterator iter = forms.begin(); iter != forms.end(); ++iter) { - scoped_ptr<FormStructure> form_structure( - new FormStructure(*iter, autocheckout_url_prefix)); + scoped_ptr<FormStructure> form_structure(new FormStructure(*iter)); if (!form_structure->ShouldBeParsed(false)) continue; @@ -1125,13 +1077,7 @@ void AutofillManager::ParseForms(const std::vector<FormData>& forms) { non_queryable_forms.push_back(form_structure.release()); } - if (form_structures_.empty()) { - // Call OnLoadedPageMetaData with no page metadata immediately if there is - // no form in the page. This give |autocheckout_manager| a chance to - // terminate Autocheckout and send Autocheckout status. - autocheckout_manager_.OnLoadedPageMetaData( - scoped_ptr<autofill::AutocheckoutPageMetaData>()); - } else if (download_manager_) { + if (!form_structures_.empty() && download_manager_) { // Query the server if we have at least one of the forms were parsed. download_manager_->StartQueryRequest(form_structures_.get(), *metric_logger_); @@ -1208,6 +1154,14 @@ void AutofillManager::UnpackGUIDs(int id, *profile_guid = IDToGUID(profile_id); } +void AutofillManager::ShowRequestAutocompleteDialog( + const FormData& form, + const GURL& source_url, + const base::Callback<void(const FormStructure*)>& callback) { + manager_delegate_->ShowRequestAutocompleteDialog( + form, source_url, callback); +} + void AutofillManager::UpdateInitialInteractionTimestamp( const TimeTicks& interaction_timestamp) { if (initial_interaction_timestamp_.is_null() || @@ -1216,4 +1170,18 @@ void AutofillManager::UpdateInitialInteractionTimestamp( } } +bool AutofillManager::ShouldUploadForm(const FormStructure& form) { + if (!IsAutofillEnabled()) + return false; + + if (driver_->GetWebContents()->GetBrowserContext()->IsOffTheRecord()) + return false; + + // Disregard forms that we wouldn't ever autofill in the first place. + if (!form.ShouldBeParsed(true)) + return false; + + return true; +} + } // namespace autofill diff --git a/chromium/components/autofill/core/browser/autofill_manager.h b/chromium/components/autofill/core/browser/autofill_manager.h index b9e4d311522..a149e2856b9 100644 --- a/chromium/components/autofill/core/browser/autofill_manager.h +++ b/chromium/components/autofill/core/browser/autofill_manager.h @@ -19,13 +19,11 @@ #include "base/memory/weak_ptr.h" #include "base/strings/string16.h" #include "base/time/time.h" -#include "components/autofill/content/browser/autocheckout_manager.h" #include "components/autofill/core/browser/autocomplete_history_manager.h" #include "components/autofill/core/browser/autofill_download.h" #include "components/autofill/core/browser/autofill_manager_delegate.h" #include "components/autofill/core/browser/form_structure.h" #include "components/autofill/core/browser/personal_data_manager.h" -#include "components/autofill/core/common/autocheckout_status.h" #include "components/autofill/core/common/form_data.h" #include "components/autofill/core/common/forms_seen_state.h" #include "third_party/WebKit/public/web/WebFormElement.h" @@ -110,14 +108,6 @@ class AutofillManager : public AutofillDownloadManager::Observer { // Returns the present form structures seen by Autofill manager. const std::vector<FormStructure*>& GetFormStructures(); - // Causes the dialog for request autocomplete feature to be shown. - virtual void ShowRequestAutocompleteDialog( - const FormData& form, - const GURL& source_url, - autofill::DialogType dialog_type, - const base::Callback<void(const FormStructure*, - const std::string&)>& callback); - // Happens when the autocomplete dialog runs its callback when being closed. void RequestAutocompleteDialogClosed(); @@ -167,23 +157,19 @@ class AutofillManager : public AutofillDownloadManager::Observer { void OnRequestAutocomplete(const FormData& form, const GURL& frame_url); - // Called to signal a page is completed in renderer in the Autocheckout flow. - void OnAutocheckoutPageCompleted(autofill::AutocheckoutStatus status); - - // Shows the Autocheckout bubble if conditions are right. See comments for - // AutocheckoutManager::MaybeShowAutocheckoutBubble. Input element requesting - // bubble belongs to |form|. |bounding_box| is the bounding box of the input - // field in focus. - virtual void OnMaybeShowAutocheckoutBubble(const FormData& form, - const gfx::RectF& bounding_box); + // Try and upload |form|. This differs from OnFormSubmitted() in a few ways. + // - This function will only label the first <input type="password"> field + // as ACCOUNT_CREATION_PASSWORD. Other fields will stay unlabeled, as they + // should have been labeled during the upload for OnFormSubmitted(). + // - This function does not assume that |form| is being uploaded during + // the same browsing session as it was originally submitted (as we may + // not have the necessary information to classify the form at that time) + // so it bypasses the cache and doesn't log the same quality UMA metrics. + bool UploadPasswordGenerationForm(const FormData& form); // Resets cache. virtual void Reset(); - autofill::AutocheckoutManager* autocheckout_manager() { - return &autocheckout_manager_; - } - protected: // Test code should prefer to use this constructor. AutofillManager(AutofillDriver* driver, @@ -227,23 +213,24 @@ class AutofillManager : public AutofillDownloadManager::Observer { return external_delegate_; } + // Causes the dialog for request autocomplete feature to be shown. + virtual void ShowRequestAutocompleteDialog( + const FormData& form, + const GURL& source_url, + const base::Callback<void(const FormStructure*)>& callback); + // Tell the renderer the current interactive autocomplete finished. virtual void ReturnAutocompleteResult( WebKit::WebFormElement::AutocompleteResult result, const FormData& form_data); private: - // AutofillDownloadManager::Observer: virtual void OnLoadedServerPredictions( const std::string& response_xml) OVERRIDE; // Passes return data for an OnRequestAutocomplete call back to the page. - void ReturnAutocompleteData(const FormStructure* result, - const std::string& unused_transaction_id); - - // Returns the matched whitelist URL prefix for the current tab's url. - virtual std::string GetAutocheckoutURLPrefix() const; + void ReturnAutocompleteData(const FormStructure* result); // Fills |host| with the RenderViewHost for this tab. // Returns false if Autofill is disabled or if the host is unavailable. @@ -310,6 +297,9 @@ class AutofillManager : public AutofillDownloadManager::Observer { void UpdateInitialInteractionTimestamp( const base::TimeTicks& interaction_timestamp); + // Shared code to determine if |form| should be uploaded. + bool ShouldUploadForm(const FormStructure& form); + // Provides driver-level context to the shared code of the component. Must // outlive this object. AutofillDriver* driver_; @@ -333,9 +323,6 @@ class AutofillManager : public AutofillDownloadManager::Observer { // Handles single-field autocomplete form data. scoped_ptr<AutocompleteHistoryManager> autocomplete_history_manager_; - // Handles autocheckout flows. - autofill::AutocheckoutManager autocheckout_manager_; - // For logging UMA metrics. Overridden by metrics tests. scoped_ptr<const AutofillMetrics> metric_logger_; // Have we logged whether Autofill is enabled for this page load? diff --git a/chromium/components/autofill/core/browser/autofill_manager_delegate.h b/chromium/components/autofill/core/browser/autofill_manager_delegate.h index 374c105fd5d..cadd1f7a283 100644 --- a/chromium/components/autofill/core/browser/autofill_manager_delegate.h +++ b/chromium/components/autofill/core/browser/autofill_manager_delegate.h @@ -11,12 +11,6 @@ #include "base/i18n/rtl.h" #include "base/memory/weak_ptr.h" #include "base/strings/string16.h" -#include "components/autofill/content/browser/autocheckout_steps.h" -#include "components/autofill/core/browser/autocheckout_bubble_state.h" - -namespace content { -struct PasswordForm; -} namespace gfx { class Rect; @@ -36,17 +30,7 @@ class FormStructure; class PasswordGenerator; class PersonalDataManager; struct FormData; - -namespace autocheckout { -class WhitelistManager; -} - -enum DialogType { - // Autofill dialog for the Autocheckout feature. - DIALOG_TYPE_AUTOCHECKOUT, - // Autofill dialog for the requestAutocomplete feature. - DIALOG_TYPE_REQUEST_AUTOCOMPLETE, -}; +struct PasswordForm; // A delegate interface that needs to be supplied to AutofillManager // by the embedder. @@ -66,23 +50,9 @@ class AutofillManagerDelegate { // Gets the preferences associated with the delegate. virtual PrefService* GetPrefs() = 0; - // Gets the autocheckout::WhitelistManager instance associated with the - // delegate. - virtual autocheckout::WhitelistManager* - GetAutocheckoutWhitelistManager() const = 0; - // Hides the associated request autocomplete dialog (if it exists). virtual void HideRequestAutocompleteDialog() = 0; - // Causes an error explaining that Autocheckout has failed to be displayed to - // the user. - virtual void OnAutocheckoutError() = 0; - - // Called when an Autocheckout flow has succeeded. Causes a notification - // explaining that they must confirm their purchase to be displayed to the - // user. - virtual void OnAutocheckoutSuccess() = 0; - // Causes the Autofill settings UI to be shown. virtual void ShowAutofillSettings() = 0; @@ -93,26 +63,11 @@ class AutofillManagerDelegate { const CreditCard& credit_card, const base::Closure& save_card_callback) = 0; - // Causes the Autocheckout bubble UI to be displayed. |bounding_box| is the - // anchor for the bubble. |is_google_user| is whether or not the user is - // logged into or has been logged into accounts.google.com. |callback| is run - // if the bubble is accepted. The returned boolean informs the caller whether - // or not the bubble is successfully shown. - virtual bool ShowAutocheckoutBubble( - const gfx::RectF& bounding_box, - bool is_google_user, - const base::Callback<void(AutocheckoutBubbleState)>& callback) = 0; - // Causes the dialog for request autocomplete feature to be shown. virtual void ShowRequestAutocompleteDialog( const FormData& form, const GURL& source_url, - DialogType dialog_type, - const base::Callback<void(const FormStructure*, - const std::string&)>& callback) = 0; - - // Hide the Autocheckout bubble if one is currently showing. - virtual void HideAutocheckoutBubble() = 0; + const base::Callback<void(const FormStructure*)>& callback) = 0; // Shows an Autofill popup with the given |values|, |labels|, |icons|, and // |identifiers| for the element at |element_bounds|. |delegate| will be @@ -137,11 +92,10 @@ class AutofillManagerDelegate { // Whether the Autocomplete feature of Autofill should be enabled. virtual bool IsAutocompleteEnabled() = 0; - // Update progress of the Autocheckout flow as displayed to the user. - virtual void AddAutocheckoutStep(AutocheckoutStepType step_type) = 0; - virtual void UpdateAutocheckoutStep( - AutocheckoutStepType step_type, - AutocheckoutStepStatus step_status) = 0; + // Pass the form structures to the password generation manager to detect + // account creation forms. + virtual void DetectAccountCreationForms( + const std::vector<autofill::FormStructure*>& forms) = 0; }; } // namespace autofill diff --git a/chromium/components/autofill/core/browser/autofill_manager_unittest.cc b/chromium/components/autofill/core/browser/autofill_manager_unittest.cc index 7aeb81ec6d5..a56e86bfafc 100644 --- a/chromium/components/autofill/core/browser/autofill_manager_unittest.cc +++ b/chromium/components/autofill/core/browser/autofill_manager_unittest.cc @@ -84,7 +84,7 @@ class TestPersonalDataManager : public PersonalDataManager { return NULL; } - MOCK_METHOD1(SaveImportedProfile, void(const AutofillProfile&)); + MOCK_METHOD1(SaveImportedProfile, std::string(const AutofillProfile&)); AutofillProfile* GetProfileWithGUID(const char* guid) { for (std::vector<AutofillProfile *>::iterator it = web_profiles_.begin(); @@ -479,14 +479,6 @@ class TestAutofillManager : public AutofillManager { autofill_enabled_ = autofill_enabled; } - void set_autocheckout_url_prefix(const std::string& autocheckout_url_prefix) { - autocheckout_url_prefix_ = autocheckout_url_prefix; - } - - virtual std::string GetAutocheckoutURLPrefix() const OVERRIDE { - return autocheckout_url_prefix_; - } - const std::vector<std::pair<WebFormElement::AutocompleteResult, FormData> >& request_autocomplete_results() const { return request_autocomplete_results_; @@ -533,14 +525,6 @@ class TestAutofillManager : public AutofillManager { submission_time); } - virtual void OnMaybeShowAutocheckoutBubble( - const FormData& form, - const gfx::RectF& bounding_box) OVERRIDE { - AutofillManager::OnMaybeShowAutocheckoutBubble(form, bounding_box); - // Needed for AutocheckoutManager to post task on IO thread. - content::RunAllPendingInMessageLoop(content::BrowserThread::IO); - } - // Resets the MessageLoopRunner so that it can wait for an asynchronous form // submission to complete. void ResetMessageLoopRunner() { @@ -597,35 +581,6 @@ class TestAutofillManager : public AutofillManager { request_autocomplete_results_.push_back(std::make_pair(result, form_data)); } - // Set autocheckout manager's page meta data to first page on Autocheckout - // flow. - void MarkAsFirstPageInAutocheckoutFlow() { - scoped_ptr<AutocheckoutPageMetaData> start_of_flow( - new AutocheckoutPageMetaData()); - start_of_flow->current_page_number = 0; - start_of_flow->total_pages = 3; - WebElementDescriptor* proceed_element = - &start_of_flow->proceed_element_descriptor; - proceed_element->descriptor = "#foo"; - proceed_element->retrieval_method = WebElementDescriptor::ID; - autocheckout_manager()->OnLoadedPageMetaData(start_of_flow.Pass()); - } - - // Set autocheckout manager's page meta data to first page on Autocheckout - // flow. - void MarkAsFirstPageInAutocheckoutFlowIgnoringAjax() { - scoped_ptr<AutocheckoutPageMetaData> start_of_flow( - new AutocheckoutPageMetaData()); - start_of_flow->current_page_number = 0; - start_of_flow->total_pages = 3; - start_of_flow->ignore_ajax = true; - WebElementDescriptor* proceed_element = - &start_of_flow->proceed_element_descriptor; - proceed_element->descriptor = "#foo"; - proceed_element->retrieval_method = WebElementDescriptor::ID; - autocheckout_manager()->OnLoadedPageMetaData(start_of_flow.Pass()); - } - private: // Weak reference. TestPersonalDataManager* personal_data_; @@ -636,7 +591,6 @@ class TestAutofillManager : public AutofillManager { scoped_refptr<content::MessageLoopRunner> message_loop_runner_; - std::string autocheckout_url_prefix_; std::string submitted_form_signature_; std::vector<ServerFieldTypeSet> expected_submitted_field_types_; @@ -839,14 +793,6 @@ class AutofillManagerTest : public ChromeRenderViewHostTestHarness { return autofill_manager_->PackGUIDs(cc_guid, profile_guid); } - - bool HasSeenAutofillGetAllFormsMessage() { - const uint32 kMsgID = AutofillMsg_GetAllForms::ID; - const IPC::Message* message = - process()->sink().GetFirstMessageMatching(kMsgID); - return message != NULL; - } - protected: scoped_ptr<MockAutofillDriver> autofill_driver_; scoped_ptr<TestAutofillManager> autofill_manager_; @@ -861,7 +807,7 @@ class AutofillManagerTest : public ChromeRenderViewHostTestHarness { class TestFormStructure : public FormStructure { public: explicit TestFormStructure(const FormData& form) - : FormStructure(form, std::string()) {} + : FormStructure(form) {} virtual ~TestFormStructure() {} void SetFieldTypes(const std::vector<ServerFieldType>& heuristic_types, @@ -883,19 +829,6 @@ class TestFormStructure : public FormStructure { DISALLOW_COPY_AND_ASSIGN(TestFormStructure); }; -// Test that browser asks for all forms when Autocheckout is enabled. -TEST_F(AutofillManagerTest, GetAllForms) { - FormData form; - test::CreateTestAddressFormData(&form); - std::vector<FormData> forms(1, form); - // Enable autocheckout. - autofill_manager_->set_autocheckout_url_prefix("test-prefix"); - - PartialFormsSeen(forms); - - ASSERT_TRUE(HasSeenAutofillGetAllFormsMessage()); -} - // Test that we return all address profile suggestions when all form fields are // empty. TEST_F(AutofillManagerTest, GetProfileSuggestionsEmptyValue) { @@ -930,79 +863,6 @@ TEST_F(AutofillManagerTest, GetProfileSuggestionsEmptyValue) { expected_labels, expected_icons, expected_unique_ids); } -// Test that in the case of Autocheckout, forms seen are in order supplied. -TEST_F(AutofillManagerTest, AutocheckoutFormsSeen) { - FormData shipping_options; - CreateTestShippingOptionsFormData(&shipping_options); - FormData user_supplied; - CreateTestFormWithAutocompleteAttribute(&user_supplied); - FormData address; - test::CreateTestAddressFormData(&address); - - // Push user_supplied before address and observe order changing when - // Autocheckout is not enabled.. - std::vector<FormData> forms; - forms.push_back(shipping_options); - forms.push_back(user_supplied); - forms.push_back(address); - - // Test without enabling Autocheckout. FormStructure should only contain - // form1. Shipping Options form will not qualify as parsable form. - FormsSeen(forms); - std::vector<FormStructure*> form_structures; - form_structures = autofill_manager_->GetFormStructures(); - ASSERT_EQ(2U, form_structures.size()); - EXPECT_EQ("/form.html", form_structures[0]->source_url().path()); - EXPECT_EQ("/userspecified.html", form_structures[1]->source_url().path()); - autofill_manager_->ClearFormStructures(); - - // Test after enabling Autocheckout. Order should be shipping_options, - // userspecified and then address form. - autofill_manager_->set_autocheckout_url_prefix("yes-autocheckout"); - FormsSeen(forms); - form_structures = autofill_manager_->GetFormStructures(); - ASSERT_EQ(3U, form_structures.size()); - EXPECT_EQ("/shipping.html", form_structures[0]->source_url().path()); - EXPECT_EQ("/userspecified.html", form_structures[1]->source_url().path()); - EXPECT_EQ("/form.html", form_structures[2]->source_url().path()); -} - -// Test that in the case of Autocheckout, forms seen are in order supplied. -TEST_F(AutofillManagerTest, DynamicFormsSeen) { - FormData shipping_options; - CreateTestShippingOptionsFormData(&shipping_options); - FormData user_supplied; - CreateTestFormWithAutocompleteAttribute(&user_supplied); - FormData address; - test::CreateTestAddressFormData(&address); - - autofill_manager_->set_autocheckout_url_prefix("test-prefix"); - // Push user_supplied only - std::vector<FormData> forms; - forms.push_back(user_supplied); - - // Make sure normal form is handled correctly. - FormsSeen(forms); - std::vector<FormStructure*> form_structures; - form_structures = autofill_manager_->GetFormStructures(); - ASSERT_EQ(1U, form_structures.size()); - EXPECT_EQ("/userspecified.html", form_structures[0]->source_url().path()); - - // Push other forms - forms.push_back(shipping_options); - forms.push_back(address); - - // FormStructure should contain three and only three forms. Otherwise, it - // would indicate that the manager didn't reset upon being notified of - // the new forms; - DynamicFormsSeen(forms); - form_structures = autofill_manager_->GetFormStructures(); - ASSERT_EQ(3U, form_structures.size()); - EXPECT_EQ("/userspecified.html", form_structures[0]->source_url().path()); - EXPECT_EQ("/shipping.html", form_structures[1]->source_url().path()); - EXPECT_EQ("/form.html", form_structures[2]->source_url().path()); -} - // Test that we return only matching address profile suggestions when the // selected form field has been partially filled out. TEST_F(AutofillManagerTest, GetProfileSuggestionsMatchCharacter) { @@ -2683,7 +2543,7 @@ TEST_F(AutofillManagerTest, FormSubmittedWithDifferentFields) { FormsSeen(forms); // Cache the expected form signature. - std::string signature = FormStructure(form, std::string()).FormSignature(); + std::string signature = FormStructure(form).FormSignature(); // Change the structure of the form prior to submission. // Websites would typically invoke JavaScript either on page load or on form @@ -3037,6 +2897,12 @@ TEST_F(AutofillManagerTest, DeterminePossibleFieldTypesForUpload) { form.fields.push_back(field); expected_types.push_back(types); + test::CreateTestFormField("", "40", "mypassword", "password", &field); + types.clear(); + types.insert(PASSWORD); + form.fields.push_back(field); + expected_types.push_back(types); + autofill_manager_->set_expected_submitted_field_types(expected_types); FormSubmitted(form); } @@ -3108,39 +2974,22 @@ namespace { class MockAutofillManagerDelegate : public TestAutofillManagerDelegate { public: - MockAutofillManagerDelegate() - : autocheckout_bubble_shown_(false) {} + MockAutofillManagerDelegate() {} virtual ~MockAutofillManagerDelegate() {} - virtual bool ShowAutocheckoutBubble( - const gfx::RectF& bounds, - bool is_google_user, - const base::Callback<void(AutocheckoutBubbleState)>& callback) OVERRIDE { - autocheckout_bubble_shown_ = true; - callback.Run(AUTOCHECKOUT_BUBBLE_ACCEPTED); - return true; - } - virtual void ShowRequestAutocompleteDialog( const FormData& form, const GURL& source_url, - DialogType dialog_type, - const base::Callback<void(const FormStructure*, - const std::string&)>& callback) OVERRIDE { - callback.Run(user_supplied_data_.get(), "google_transaction_id"); + const base::Callback<void(const FormStructure*)>& callback) OVERRIDE { + callback.Run(user_supplied_data_.get()); } void SetUserSuppliedData(scoped_ptr<FormStructure> user_supplied_data) { user_supplied_data_.reset(user_supplied_data.release()); } - bool autocheckout_bubble_shown() const { - return autocheckout_bubble_shown_; - } - private: - bool autocheckout_bubble_shown_; scoped_ptr<FormStructure> user_supplied_data_; DISALLOW_COPY_AND_ASSIGN(MockAutofillManagerDelegate); @@ -3148,65 +2997,6 @@ class MockAutofillManagerDelegate : public TestAutofillManagerDelegate { } // namespace -// Test that Autocheckout bubble is offered when server specifies field types. -TEST_F(AutofillManagerTest, TestBubbleShown) { - MockAutofillManagerDelegate delegate; - autofill_manager_.reset(new TestAutofillManager( - autofill_driver_.get(), &delegate, &personal_data_)); - autofill_manager_->set_autofill_enabled(true); - autofill_manager_->MarkAsFirstPageInAutocheckoutFlow(); - - FormData form; - test::CreateTestAddressFormData(&form); - - TestFormStructure* form_structure = new TestFormStructure(form); - AutofillMetrics metrics_logger; // ignored - form_structure->DetermineHeuristicTypes(metrics_logger); - - // Build and add form structure with server data. - std::vector<ServerFieldType> heuristic_types, server_types; - for (size_t i = 0; i < form.fields.size(); ++i) { - heuristic_types.push_back(UNKNOWN_TYPE); - server_types.push_back(form_structure->field(i)->heuristic_type()); - } - form_structure->SetFieldTypes(heuristic_types, server_types); - autofill_manager_->AddSeenForm(form_structure); - - autofill_manager_->OnMaybeShowAutocheckoutBubble(form, gfx::RectF()); - - EXPECT_TRUE(delegate.autocheckout_bubble_shown()); -} - -// Test that Autocheckout bubble is not offered when server doesn't have data -// for the form. -TEST_F(AutofillManagerTest, TestAutocheckoutBubbleNotShown) { - MockAutofillManagerDelegate delegate; - autofill_manager_.reset(new TestAutofillManager( - autofill_driver_.get(), &delegate, &personal_data_)); - autofill_manager_->set_autofill_enabled(true); - autofill_manager_->MarkAsFirstPageInAutocheckoutFlow(); - - FormData form; - test::CreateTestAddressFormData(&form); - - TestFormStructure* form_structure = new TestFormStructure(form); - AutofillMetrics metrics_logger; // ignored - form_structure->DetermineHeuristicTypes(metrics_logger); - - // Build form structure without server data. - std::vector<ServerFieldType> heuristic_types, server_types; - for (size_t i = 0; i < form.fields.size(); ++i) { - heuristic_types.push_back(form_structure->field(i)->heuristic_type()); - server_types.push_back(NO_SERVER_DATA); - } - form_structure->SetFieldTypes(heuristic_types, server_types); - autofill_manager_->AddSeenForm(form_structure); - - autofill_manager_->OnMaybeShowAutocheckoutBubble(form, gfx::RectF()); - - EXPECT_FALSE(delegate.autocheckout_bubble_shown()); -} - // Test our external delegate is called at the right time. TEST_F(AutofillManagerTest, TestExternalDelegate) { FormData form; @@ -3219,53 +3009,4 @@ TEST_F(AutofillManagerTest, TestExternalDelegate) { EXPECT_TRUE(external_delegate_->on_query_seen()); } -// Test that in the case of Autocheckout, forms seen are in order supplied. -TEST_F(AutofillManagerTest, DynamicFormsSeenAndIgnored) { - MockAutofillManagerDelegate delegate; - autofill_manager_.reset(new TestAutofillManager( - autofill_driver_.get(), &delegate, &personal_data_)); - FormData shipping_options; - CreateTestShippingOptionsFormData(&shipping_options); - FormData user_supplied; - CreateTestFormWithAutocompleteAttribute(&user_supplied); - FormData address; - test::CreateTestAddressFormData(&address); - - autofill_manager_->set_autocheckout_url_prefix("test-prefix"); - // Push address only - std::vector<FormData> forms; - forms.push_back(address); - - // Build and add form structure with server data. - scoped_ptr<TestFormStructure> form_structure(new TestFormStructure(address)); - std::vector<ServerFieldType> heuristic_types, server_types; - for (size_t i = 0; i < address.fields.size(); ++i) { - heuristic_types.push_back(UNKNOWN_TYPE); - server_types.push_back(form_structure->field(i)->heuristic_type()); - } - form_structure->SetFieldTypes(heuristic_types, server_types); - autofill_manager_->AddSeenForm(form_structure.release()); - - // Make sure normal form is handled correctly. - autofill_manager_->MarkAsFirstPageInAutocheckoutFlowIgnoringAjax(); - std::vector<FormStructure*> form_structures; - form_structures = autofill_manager_->GetFormStructures(); - ASSERT_EQ(1U, form_structures.size()); - EXPECT_EQ("/form.html", form_structures[0]->source_url().path()); - - scoped_ptr<FormStructure> filled_form(new TestFormStructure(address)); - delegate.SetUserSuppliedData(filled_form.Pass()); - autofill_manager_->OnMaybeShowAutocheckoutBubble(address, gfx::RectF()); - - // Push other forms - forms.push_back(shipping_options); - forms.push_back(user_supplied); - - // FormStructure should contain the same forms as before. - DynamicFormsSeen(forms); - form_structures = autofill_manager_->GetFormStructures(); - ASSERT_EQ(1U, form_structures.size()); - EXPECT_EQ("/form.html", form_structures[0]->source_url().path()); -} - } // namespace autofill diff --git a/chromium/components/autofill/core/browser/autofill_merge_unittest.cc b/chromium/components/autofill/core/browser/autofill_merge_unittest.cc index e007a59d905..ef60bf7b728 100644 --- a/chromium/components/autofill/core/browser/autofill_merge_unittest.cc +++ b/chromium/components/autofill/core/browser/autofill_merge_unittest.cc @@ -88,7 +88,8 @@ class PersonalDataManagerMock : public PersonalDataManager { void Reset(); // PersonalDataManager: - virtual void SaveImportedProfile(const AutofillProfile& profile) OVERRIDE; + virtual std::string SaveImportedProfile( + const AutofillProfile& profile) OVERRIDE; virtual const std::vector<AutofillProfile*>& web_profiles() const OVERRIDE; private: @@ -108,11 +109,14 @@ void PersonalDataManagerMock::Reset() { profiles_.clear(); } -void PersonalDataManagerMock::SaveImportedProfile( +std::string PersonalDataManagerMock::SaveImportedProfile( const AutofillProfile& profile) { std::vector<AutofillProfile> profiles; - if (!MergeProfile(profile, profiles_.get(), "en-US", &profiles)) + std::string merged_guid = + MergeProfile(profile, profiles_.get(), "en-US", &profiles); + if (merged_guid == profile.guid()) profiles_.push_back(new AutofillProfile(profile)); + return merged_guid; } const std::vector<AutofillProfile*>& PersonalDataManagerMock::web_profiles() @@ -213,7 +217,7 @@ void AutofillMergeTest::MergeProfiles(const std::string& profiles, // followed by an explicit separator. if ((i > 0 && line == kProfileSeparator) || i == lines.size() - 1) { // Reached the end of a profile. Try to import it. - FormStructure form_structure(form, std::string()); + FormStructure form_structure(form); for (size_t i = 0; i < form_structure.field_count(); ++i) { // Set the heuristic type for each field, which is currently serialized // into the field's name. diff --git a/chromium/components/autofill/core/browser/autofill_metrics.cc b/chromium/components/autofill/core/browser/autofill_metrics.cc index 57743c3b7fd..930783d93b5 100644 --- a/chromium/components/autofill/core/browser/autofill_metrics.cc +++ b/chromium/components/autofill/core/browser/autofill_metrics.cc @@ -6,6 +6,7 @@ #include "base/logging.h" #include "base/metrics/histogram.h" +#include "base/metrics/sparse_histogram.h" #include "base/time/time.h" #include "components/autofill/core/browser/autofill_type.h" #include "components/autofill/core/browser/form_structure.h" @@ -55,6 +56,7 @@ enum FieldTypeGroupForMetrics { CREDIT_CARD_NUMBER, CREDIT_CARD_DATE, CREDIT_CARD_TYPE, + PASSWORD, NUM_FIELD_TYPE_GROUPS_FOR_METRICS }; @@ -84,13 +86,14 @@ int GetFieldTypeGroupMetric(const ServerFieldType field_type, const int num_possible_metrics) { DCHECK_LT(metric, num_possible_metrics); - FieldTypeGroupForMetrics group; + FieldTypeGroupForMetrics group = AMBIGUOUS; switch (AutofillType(field_type).group()) { case ::autofill::NO_GROUP: group = AMBIGUOUS; break; case ::autofill::NAME: + case ::autofill::NAME_BILLING: group = NAME; break; @@ -99,7 +102,8 @@ int GetFieldTypeGroupMetric(const ServerFieldType field_type, break; case ::autofill::ADDRESS_HOME: - switch (field_type) { + case ::autofill::ADDRESS_BILLING: + switch (AutofillType(field_type).GetStorableType()) { case ADDRESS_HOME_LINE1: group = ADDRESS_LINE_1; break; @@ -121,6 +125,7 @@ int GetFieldTypeGroupMetric(const ServerFieldType field_type, default: NOTREACHED(); group = AMBIGUOUS; + break; } break; @@ -129,6 +134,7 @@ int GetFieldTypeGroupMetric(const ServerFieldType field_type, break; case ::autofill::PHONE_HOME: + case ::autofill::PHONE_BILLING: group = PHONE; break; @@ -142,14 +148,24 @@ int GetFieldTypeGroupMetric(const ServerFieldType field_type, break; case ::autofill::CREDIT_CARD_TYPE: group = CREDIT_CARD_TYPE; - default: + break; + case ::autofill::CREDIT_CARD_EXP_MONTH: + case ::autofill::CREDIT_CARD_EXP_2_DIGIT_YEAR: + case ::autofill::CREDIT_CARD_EXP_4_DIGIT_YEAR: + case ::autofill::CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR: + case ::autofill::CREDIT_CARD_EXP_DATE_4_DIGIT_YEAR: group = CREDIT_CARD_DATE; + break; + default: + NOTREACHED(); + group = AMBIGUOUS; + break; } break; - default: - NOTREACHED(); - group = AMBIGUOUS; + case ::autofill::PASSWORD_FIELD: + group = PASSWORD; + break; } // Interpolate the |metric| with the |group|, so that all metrics for a given @@ -157,20 +173,6 @@ int GetFieldTypeGroupMetric(const ServerFieldType field_type, return (group * num_possible_metrics) + metric; } -// Returns the histogram prefix to use for reporting metrics for |dialog_type|. -std::string GetPrefixForDialogType(autofill::DialogType dialog_type) { - switch (dialog_type) { - case autofill::DIALOG_TYPE_AUTOCHECKOUT: - return "Autocheckout"; - - case autofill::DIALOG_TYPE_REQUEST_AUTOCOMPLETE: - return "RequestAutocomplete"; - } - - NOTREACHED(); - return "UnknownDialogType"; -} - std::string WalletApiMetricToString( AutofillMetrics::WalletApiCallMetric metric) { switch (metric) { @@ -184,9 +186,8 @@ std::string WalletApiMetricToString( return "GetWalletItems"; case AutofillMetrics::SAVE_TO_WALLET: return "SaveToWallet"; - case AutofillMetrics::SEND_STATUS: - return "SendStatus"; case AutofillMetrics::UNKNOWN_API_CALL: + case AutofillMetrics::NUM_WALLET_API_CALLS: NOTREACHED(); return "UnknownApiCall"; } @@ -276,7 +277,7 @@ void LogServerExperimentId(const std::string& histogram_name, ServerExperiment metric = UNKNOWN_EXPERIMENT; const std::string default_experiment_name = - FormStructure(FormData(), std::string()).server_experiment_id(); + FormStructure(FormData()).server_experiment_id(); if (experiment_id.empty()) metric = NO_EXPERIMENT; else if (experiment_id == "ar06") @@ -324,20 +325,6 @@ AutofillMetrics::AutofillMetrics() { AutofillMetrics::~AutofillMetrics() { } -void AutofillMetrics::LogAutocheckoutBubbleMetric(BubbleMetric metric) const { - DCHECK_LT(metric, NUM_BUBBLE_METRICS); - - UMA_HISTOGRAM_ENUMERATION("Autocheckout.Bubble", metric, NUM_BUBBLE_METRICS); -} - -void AutofillMetrics::LogAutocheckoutBuyFlowMetric( - AutocheckoutBuyFlowMetric metric) const { - DCHECK_LT(metric, NUM_AUTOCHECKOUT_BUY_FLOW_METRICS); - - UMA_HISTOGRAM_ENUMERATION("Autocheckout.BuyFlow", metric, - NUM_AUTOCHECKOUT_BUY_FLOW_METRICS); -} - void AutofillMetrics::LogCreditCardInfoBarMetric(InfoBarMetric metric) const { DCHECK_LT(metric, NUM_INFO_BAR_METRICS); @@ -346,47 +333,36 @@ void AutofillMetrics::LogCreditCardInfoBarMetric(InfoBarMetric metric) const { } void AutofillMetrics::LogDialogDismissalState( - autofill::DialogType dialog_type, DialogDismissalState state) const { - std::string name = GetPrefixForDialogType(dialog_type) + ".DismissalState"; - LogUMAHistogramEnumeration(name, state, NUM_DIALOG_DISMISSAL_STATES); + UMA_HISTOGRAM_ENUMERATION("RequestAutocomplete.DismissalState", + state, NUM_DIALOG_DISMISSAL_STATES); } void AutofillMetrics::LogDialogInitialUserState( - autofill::DialogType dialog_type, DialogInitialUserStateMetric user_type) const { - std::string name = GetPrefixForDialogType(dialog_type) + ".InitialUserState"; - LogUMAHistogramEnumeration( - name, user_type, NUM_DIALOG_INITIAL_USER_STATE_METRICS); + UMA_HISTOGRAM_ENUMERATION("RequestAutocomplete.InitialUserState", + user_type, NUM_DIALOG_INITIAL_USER_STATE_METRICS); } void AutofillMetrics::LogDialogLatencyToShow( - autofill::DialogType dialog_type, const base::TimeDelta& duration) const { - std::string name = - GetPrefixForDialogType(dialog_type) + ".UiLatencyToShow"; - LogUMAHistogramTimes(name, duration); + LogUMAHistogramTimes("RequestAutocomplete.UiLatencyToShow", duration); } -void AutofillMetrics::LogDialogPopupEvent(autofill::DialogType dialog_type, - DialogPopupEvent event) const { - std::string name = GetPrefixForDialogType(dialog_type) + ".PopupInDialog"; - LogUMAHistogramEnumeration(name, event, NUM_DIALOG_POPUP_EVENTS); +void AutofillMetrics::LogDialogPopupEvent(DialogPopupEvent event) const { + UMA_HISTOGRAM_ENUMERATION("RequestAutocomplete.PopupInDialog", + event, NUM_DIALOG_POPUP_EVENTS); } void AutofillMetrics::LogDialogSecurityMetric( - autofill::DialogType dialog_type, DialogSecurityMetric metric) const { - std::string name = GetPrefixForDialogType(dialog_type) + ".Security"; - LogUMAHistogramEnumeration(name, metric, NUM_DIALOG_SECURITY_METRICS); + UMA_HISTOGRAM_ENUMERATION("RequestAutocomplete.Security", + metric, NUM_DIALOG_SECURITY_METRICS); } void AutofillMetrics::LogDialogUiDuration( const base::TimeDelta& duration, - autofill::DialogType dialog_type, DialogDismissalAction dismissal_action) const { - std::string prefix = GetPrefixForDialogType(dialog_type); - std::string suffix; switch (dismissal_action) { case DIALOG_ACCEPTED: @@ -398,20 +374,19 @@ void AutofillMetrics::LogDialogUiDuration( break; } - LogUMAHistogramLongTimes(prefix + ".UiDuration", duration); - LogUMAHistogramLongTimes(prefix + ".UiDuration." + suffix, duration); + LogUMAHistogramLongTimes("RequestAutocomplete.UiDuration", duration); + LogUMAHistogramLongTimes("RequestAutocomplete.UiDuration." + suffix, + duration); } -void AutofillMetrics::LogDialogUiEvent(autofill::DialogType dialog_type, - DialogUiEvent event) const { - std::string name = GetPrefixForDialogType(dialog_type) + ".UiEvents"; - LogUMAHistogramEnumeration(name, event, NUM_DIALOG_UI_EVENTS); +void AutofillMetrics::LogDialogUiEvent(DialogUiEvent event) const { + UMA_HISTOGRAM_ENUMERATION("RequestAutocomplete.UiEvents", event, + NUM_DIALOG_UI_EVENTS); } -void AutofillMetrics::LogWalletErrorMetric(autofill::DialogType dialog_type, - WalletErrorMetric metric) const { - std::string name = GetPrefixForDialogType(dialog_type) + ".WalletErrors"; - LogUMAHistogramEnumeration(name, metric, NUM_WALLET_ERROR_METRICS); +void AutofillMetrics::LogWalletErrorMetric(WalletErrorMetric metric) const { + UMA_HISTOGRAM_ENUMERATION("RequestAutocomplete.WalletErrors", metric, + NUM_WALLET_ERROR_METRICS); } void AutofillMetrics::LogWalletApiCallDuration( @@ -421,54 +396,20 @@ void AutofillMetrics::LogWalletApiCallDuration( WalletApiMetricToString(metric), duration); } -void AutofillMetrics::LogWalletRequiredActionMetric( - autofill::DialogType dialog_type, - WalletRequiredActionMetric required_action) const { - std::string name = - GetPrefixForDialogType(dialog_type) + ".WalletRequiredActions"; - LogUMAHistogramEnumeration( - name, required_action, NUM_WALLET_REQUIRED_ACTIONS); +void AutofillMetrics::LogWalletMalformedResponseMetric( + WalletApiCallMetric metric) const { + UMA_HISTOGRAM_ENUMERATION("Wallet.MalformedResponse", metric, + NUM_WALLET_API_CALLS); } -void AutofillMetrics::LogAutocheckoutDuration( - const base::TimeDelta& duration, - AutocheckoutCompletionStatus status) const { - std::string suffix; - switch (status) { - case AUTOCHECKOUT_CANCELLED: - suffix = "Cancelled"; - break; - - case AUTOCHECKOUT_FAILED: - suffix = "Failed"; - break; - - case AUTOCHECKOUT_SUCCEEDED: - suffix = "Succeeded"; - break; - } - - LogUMAHistogramLongTimes("Autocheckout.FlowDuration", duration); - LogUMAHistogramLongTimes("Autocheckout.FlowDuration." + suffix, duration); +void AutofillMetrics::LogWalletRequiredActionMetric( + WalletRequiredActionMetric required_action) const { + UMA_HISTOGRAM_ENUMERATION("RequestAutocomplete.WalletRequiredActions", + required_action, NUM_WALLET_REQUIRED_ACTIONS); } -void AutofillMetrics::LogAutocheckoutWhitelistDownloadDuration( - const base::TimeDelta& duration, - AutocheckoutWhitelistDownloadStatus status) const { - std::string suffix; - switch (status) { - case AUTOCHECKOUT_WHITELIST_DOWNLOAD_FAILED: - suffix = "Failed"; - break; - - case AUTOCHECKOUT_WHITELIST_DOWNLOAD_SUCCEEDED: - suffix = "Succeeded"; - break; - } - - LogUMAHistogramTimes("Autocheckout.WhitelistDownloadDuration", duration); - LogUMAHistogramTimes( - "Autocheckout.WhitelistDownloadDuration." + suffix, duration); +void AutofillMetrics::LogWalletResponseCode(int response_code) const { + UMA_HISTOGRAM_SPARSE_SLOWLY("Wallet.ResponseCode", response_code); } void AutofillMetrics::LogDeveloperEngagementMetric( diff --git a/chromium/components/autofill/core/browser/autofill_metrics.h b/chromium/components/autofill/core/browser/autofill_metrics.h index d6ae790b392..c9f56a9cc75 100644 --- a/chromium/components/autofill/core/browser/autofill_metrics.h +++ b/chromium/components/autofill/core/browser/autofill_metrics.h @@ -20,47 +20,6 @@ namespace autofill { class AutofillMetrics { public: - // The possible results of an Autocheckout flow. - enum AutocheckoutBuyFlowMetric { - // The user has initated Autocheckout. The baseline metric. - AUTOCHECKOUT_BUY_FLOW_STARTED, - // Autocheckout completed successfully. - AUTOCHECKOUT_BUY_FLOW_SUCCESS, - // Autocheckout failed due to missing server side data. - AUTOCHECKOUT_BUY_FLOW_MISSING_FIELDMAPPING, - // Autocheckout failed due to a missing proceed element. - AUTOCHECKOUT_BUY_FLOW_MISSING_ADVANCE_ELEMENT, - // Autocheckout failed for any number of other reasons, e.g, the proceed - // element click failed, the page numbers were not increasing, etc. - AUTOCHECKOUT_BUY_FLOW_CANNOT_PROCEED, - // Autocheckout failed due to a missing click element before form filling. - AUTOCHECKOUT_BUY_FLOW_MISSING_CLICK_ELEMENT_BEFORE_FORM_FILLING, - // Autocheckout failed due to a missing click element after form filling. - AUTOCHECKOUT_BUY_FLOW_MISSING_CLICK_ELEMENT_AFTER_FORM_FILLING, - NUM_AUTOCHECKOUT_BUY_FLOW_METRICS - }; - - // The success or failure of Autocheckout. - enum AutocheckoutCompletionStatus { - AUTOCHECKOUT_CANCELLED, // The user canceled Autocheckout while it was in - // progress. - AUTOCHECKOUT_FAILED, // The user canceled out of the dialog after - // an Autocheckout failure. - AUTOCHECKOUT_SUCCEEDED, // The dialog was closed after Autocheckout - // succeeded. - }; - - // The action a user took to dismiss a bubble. - enum BubbleMetric { - BUBBLE_CREATED = 0, // The bubble was created. - BUBBLE_ACCEPTED, // The user accepted, i.e. confirmed, the - // bubble. - BUBBLE_DISMISSED, // The user dismissed the bubble. - BUBBLE_IGNORED, // The user did not interact with the bubble. - BUBBLE_COULD_BE_DISPLAYED, // The bubble could be displayed. - NUM_BUBBLE_METRICS, - }; - enum DeveloperEngagementMetric { // Parsed a form that is potentially autofillable. FILLABLE_FORM_PARSED = 0, @@ -119,8 +78,8 @@ class AutofillMetrics { NUM_DIALOG_INITIAL_USER_STATE_METRICS }; - // Events related to the Autofill popup shown in a requestAutocomplete or - // Autocheckout dialog. + // Events related to the Autofill popup shown in a requestAutocomplete + // dialog. enum DialogPopupEvent { // An Autofill popup was shown. DIALOG_POPUP_SHOWN = 0, @@ -163,21 +122,21 @@ class AutofillMetrics { DIALOG_UI_SIGNIN_SHOWN, // Selecting a different item from a suggestion menu dropdown: - DIALOG_UI_EMAIL_SELECTED_SUGGESTION_CHANGED, + DEPRECATED_DIALOG_UI_EMAIL_SELECTED_SUGGESTION_CHANGED, DIALOG_UI_BILLING_SELECTED_SUGGESTION_CHANGED, DIALOG_UI_CC_BILLING_SELECTED_SUGGESTION_CHANGED, DIALOG_UI_SHIPPING_SELECTED_SUGGESTION_CHANGED, DIALOG_UI_CC_SELECTED_SUGGESTION_CHANGED, // Showing the editing UI for a section of the dialog: - DIALOG_UI_EMAIL_EDIT_UI_SHOWN, - DIALOG_UI_BILLING_EDIT_UI_SHOWN, - DIALOG_UI_CC_BILLING_EDIT_UI_SHOWN, - DIALOG_UI_SHIPPING_EDIT_UI_SHOWN, - DIALOG_UI_CC_EDIT_UI_SHOWN, + DEPRECATED_DIALOG_UI_EMAIL_EDIT_UI_SHOWN, + DEPRECATED_DIALOG_UI_BILLING_EDIT_UI_SHOWN, + DEPRECATED_DIALOG_UI_CC_BILLING_EDIT_UI_SHOWN, + DEPRECATED_DIALOG_UI_SHIPPING_EDIT_UI_SHOWN, + DEPRECATED_DIALOG_UI_CC_EDIT_UI_SHOWN, // Adding a new item in a section of the dialog: - DIALOG_UI_EMAIL_ITEM_ADDED, + DEPRECATED_DIALOG_UI_EMAIL_ITEM_ADDED, DIALOG_UI_BILLING_ITEM_ADDED, DIALOG_UI_CC_BILLING_ITEM_ADDED, DIALOG_UI_SHIPPING_ITEM_ADDED, @@ -296,8 +255,8 @@ class AutofillMetrics { AUTHENTICATE_INSTRUMENT, GET_FULL_WALLET, GET_WALLET_ITEMS, - SEND_STATUS, SAVE_TO_WALLET, + NUM_WALLET_API_CALLS }; // For measuring the frequency of errors while communicating with the Wallet @@ -362,22 +321,9 @@ class AutofillMetrics { NUM_WALLET_REQUIRED_ACTIONS }; - // The success or failure of downloading Autocheckout whitelist file. - enum AutocheckoutWhitelistDownloadStatus { - AUTOCHECKOUT_WHITELIST_DOWNLOAD_FAILED, - AUTOCHECKOUT_WHITELIST_DOWNLOAD_SUCCEEDED, - }; - AutofillMetrics(); virtual ~AutofillMetrics(); - // Logs how the user interacted with the Autocheckout bubble. - virtual void LogAutocheckoutBubbleMetric(BubbleMetric metric) const; - - // Logs the result of an Autocheckout buy flow. - virtual void LogAutocheckoutBuyFlowMetric( - AutocheckoutBuyFlowMetric metric) const; - virtual void LogCreditCardInfoBarMetric(InfoBarMetric metric) const; virtual void LogDeveloperEngagementMetric( @@ -402,66 +348,54 @@ class AutofillMetrics { virtual void LogUserHappinessMetric(UserHappinessMetric metric) const; - // Logs |state| to the dismissal states histogram for |dialog_type|. - virtual void LogDialogDismissalState(autofill::DialogType dialog_type, - DialogDismissalState state) const; + // Logs |state| to the dismissal states histogram. + virtual void LogDialogDismissalState(DialogDismissalState state) const; // This should be called as soon as the user's signed-in status and Wallet // item count is known. Records that a user starting out in |user_state| is - // interacting with a dialog of |dialog_type|. + // interacting with a dialog. virtual void LogDialogInitialUserState( - autofill::DialogType dialog_type, DialogInitialUserStateMetric user_type) const; - // Logs the time elapsed between the dialog being shown for |dialog_type| and - // when it is ready for user interaction. - virtual void LogDialogLatencyToShow(autofill::DialogType dialog_type, - const base::TimeDelta& duration) const; + // Logs the time elapsed between the dialog being shown and when it is ready + // for user interaction. + virtual void LogDialogLatencyToShow(const base::TimeDelta& duration) const; - // Logs |event| to the popup events histogram for |dialog_type|. - virtual void LogDialogPopupEvent(autofill::DialogType dialog_type, - DialogPopupEvent event) const; + // Logs |event| to the popup events histogram. + virtual void LogDialogPopupEvent(DialogPopupEvent event) const; - // Logs |metric| to the security metrics histogram for |dialog_type|. - virtual void LogDialogSecurityMetric(autofill::DialogType dialog_type, - DialogSecurityMetric metric) const; + // Logs |metric| to the security metrics histogram. + virtual void LogDialogSecurityMetric(DialogSecurityMetric metric) const; - // This should be called when the Autofill dialog, invoked by a dialog of type - // |dialog_type|, is closed. |duration| should be the time elapsed between - // the dialog being shown and it being closed. |dismissal_action| should - // indicate whether the user dismissed the dialog by submitting the form data - // or by canceling. + // This should be called when the Autofill dialog is closed. |duration| + // should be the time elapsed between the dialog being shown and it being + // closed. |dismissal_action| should indicate whether the user dismissed + // the dialog by submitting the form data or by canceling. virtual void LogDialogUiDuration( const base::TimeDelta& duration, - autofill::DialogType dialog_type, DialogDismissalAction dismissal_action) const; - // Logs |event| to the UI events histogram for |dialog_type|. - virtual void LogDialogUiEvent(autofill::DialogType dialog_type, - DialogUiEvent event) const; + // Logs |event| to the UI events histogram. + virtual void LogDialogUiEvent(DialogUiEvent event) const; - // Logs |metric| to the Wallet errors histogram for |dialog_type|. - virtual void LogWalletErrorMetric(autofill::DialogType dialog_type, - WalletErrorMetric metric) const; + // Logs |metric| to the Wallet errors histogram. + virtual void LogWalletErrorMetric(WalletErrorMetric metric) const; // Logs the network request time of Wallet API calls. virtual void LogWalletApiCallDuration( WalletApiCallMetric metric, const base::TimeDelta& duration) const; - // Logs |required_action| to the required actions histogram for |dialog_type|. + // Logs that the Wallet API call corresponding to |metric| was malformed. + virtual void LogWalletMalformedResponseMetric( + WalletApiCallMetric metric) const; + + // Logs |required_action| to the required actions histogram. virtual void LogWalletRequiredActionMetric( - autofill::DialogType dialog_type, WalletRequiredActionMetric required_action) const; - virtual void LogAutocheckoutDuration( - const base::TimeDelta& duration, - AutocheckoutCompletionStatus status) const; - - // Logs the time taken to download Autocheckout whitelist file. - virtual void LogAutocheckoutWhitelistDownloadDuration( - const base::TimeDelta& duration, - AutocheckoutWhitelistDownloadStatus status) const; + // Logs HTTP response codes recieved by wallet client. + virtual void LogWalletResponseCode(int response_code) const; // This should be called when a form that has been Autofilled is submitted. // |duration| should be the time elapsed between form load and submission. diff --git a/chromium/components/autofill/core/browser/autofill_metrics_unittest.cc b/chromium/components/autofill/core/browser/autofill_metrics_unittest.cc index ece18d8dfd3..c12b2ff5f54 100644 --- a/chromium/components/autofill/core/browser/autofill_metrics_unittest.cc +++ b/chromium/components/autofill/core/browser/autofill_metrics_unittest.cc @@ -16,7 +16,6 @@ #include "chrome/browser/ui/autofill/tab_autofill_manager_delegate.h" #include "chrome/test/base/chrome_render_view_host_test_harness.h" #include "chrome/test/base/testing_profile.h" -#include "components/autofill/content/browser/autocheckout_page_meta_data.h" #include "components/autofill/core/browser/autofill_common_test.h" #include "components/autofill/core/browser/autofill_external_delegate.h" #include "components/autofill/core/browser/autofill_manager.h" @@ -126,7 +125,7 @@ class TestPersonalDataManager : public PersonalDataManager { } MOCK_METHOD1(SaveImportedCreditCard, - void(const CreditCard& imported_credit_card)); + std::string(const CreditCard& imported_credit_card)); private: void CreateTestAutofillProfiles(ScopedVector<AutofillProfile>* profiles) { @@ -154,8 +153,7 @@ class TestPersonalDataManager : public PersonalDataManager { class TestFormStructure : public FormStructure { public: - explicit TestFormStructure(const FormData& form) - : FormStructure(form, std::string()) {} + explicit TestFormStructure(const FormData& form) : FormStructure(form) {} virtual ~TestFormStructure() {} void SetFieldTypes(const std::vector<ServerFieldType>& heuristic_types, @@ -196,10 +194,6 @@ class TestAutofillManager : public AutofillManager { } virtual ~TestAutofillManager() {} - virtual std::string GetAutocheckoutURLPrefix() const OVERRIDE { - return std::string(); - } - virtual bool IsAutofillEnabled() const OVERRIDE { return autofill_enabled_; } void set_autofill_enabled(bool autofill_enabled) { @@ -325,8 +319,9 @@ scoped_ptr<ConfirmInfoBarDelegate> AutofillMetricsTest::CreateDelegate( CreditCard credit_card; return AutofillCCInfoBarDelegate::Create( metric_logger, - base::Bind(&TestPersonalDataManager::SaveImportedCreditCard, - base::Unretained(personal_data_.get()), credit_card)); + base::Bind( + base::IgnoreResult(&TestPersonalDataManager::SaveImportedCreditCard), + base::Unretained(personal_data_.get()), credit_card)); } // Test that we log quality metrics appropriately. @@ -1176,11 +1171,9 @@ TEST_F(AutofillMetricsTest, ServerQueryExperimentIdForQuery) { EXPECT_CALL(metric_logger, LogServerQueryMetric( AutofillMetrics::QUERY_RESPONSE_MATCHED_LOCAL_HEURISTICS)); - AutocheckoutPageMetaData page_meta_data; FormStructure::ParseQueryResponse( "<autofillqueryresponse></autofillqueryresponse>", std::vector<FormStructure*>(), - &page_meta_data, metric_logger); // Experiment "ar1" specified. @@ -1196,7 +1189,6 @@ TEST_F(AutofillMetricsTest, ServerQueryExperimentIdForQuery) { FormStructure::ParseQueryResponse( "<autofillqueryresponse experimentid=\"ar1\"></autofillqueryresponse>", std::vector<FormStructure*>(), - &page_meta_data, metric_logger); } diff --git a/chromium/components/autofill/core/browser/autofill_popup_delegate.h b/chromium/components/autofill/core/browser/autofill_popup_delegate.h index ec9a2207bc6..86b5eff4266 100644 --- a/chromium/components/autofill/core/browser/autofill_popup_delegate.h +++ b/chromium/components/autofill/core/browser/autofill_popup_delegate.h @@ -6,9 +6,10 @@ #define COMPONENTS_AUTOFILL_CORE_BROWSER_AUTOFILL_POPUP_DELEGATE_H_ #include "base/strings/string16.h" +#include "content/public/browser/render_view_host.h" -namespace content { -class KeyboardListener; +namespace ui { +class MouseEvent; } namespace autofill { @@ -17,13 +18,19 @@ namespace autofill { // of events by the controller. class AutofillPopupDelegate { public: - // Called when the Autofill popup is shown. |listener| may be used to pass + // Called when the Autofill popup is shown. |callback| may be used to pass // keyboard events to the popup. - virtual void OnPopupShown(content::KeyboardListener* listener) = 0; + virtual void OnPopupShown( + content::RenderWidgetHost::KeyPressEventCallback* callback) = 0; - // Called when the Autofill popup is hidden. |listener| must be unregistered + // Called when the Autofill popup is hidden. |callback| must be unregistered // if it was registered in OnPopupShown. - virtual void OnPopupHidden(content::KeyboardListener* listener) = 0; + virtual void OnPopupHidden( + content::RenderWidgetHost::KeyPressEventCallback* callback) = 0; + + // Called when the Autofill popup recieves a click outside of the popup view + // to determine if the event should be reposted to the native window manager. + virtual bool ShouldRepostEvent(const ui::MouseEvent& event) = 0; // Called when the autofill suggestion indicated by |identifier| has been // temporarily selected (e.g., hovered). diff --git a/chromium/components/autofill/core/browser/autofill_profile.cc b/chromium/components/autofill/core/browser/autofill_profile.cc index 6e18011e5c4..735549f7768 100644 --- a/chromium/components/autofill/core/browser/autofill_profile.cc +++ b/chromium/components/autofill/core/browser/autofill_profile.cc @@ -417,29 +417,26 @@ bool AutofillProfile::IsEmpty(const std::string& app_locale) const { bool AutofillProfile::IsPresentButInvalid(ServerFieldType type) const { std::string country = UTF16ToUTF8(GetRawInfo(ADDRESS_HOME_COUNTRY)); base::string16 data = GetRawInfo(type); + if (data.empty()) + return false; + switch (type) { case ADDRESS_HOME_STATE: - if (!data.empty() && country == "US" && !autofill::IsValidState(data)) - return true; - break; + return country == "US" && !autofill::IsValidState(data); case ADDRESS_HOME_ZIP: - if (!data.empty() && country == "US" && !autofill::IsValidZip(data)) - return true; - break; + return country == "US" && !autofill::IsValidZip(data); - case PHONE_HOME_WHOLE_NUMBER: { - if (!data.empty() && !i18n::PhoneObject(data, country).IsValidNumber()) - return true; - break; - } + case PHONE_HOME_WHOLE_NUMBER: + return !i18n::PhoneObject(data, country).IsValidNumber(); + + case EMAIL_ADDRESS: + return !autofill::IsValidEmailAddress(data); default: NOTREACHED(); - break; + return false; } - - return false; } @@ -862,6 +859,7 @@ FormGroup* AutofillProfile::MutableFormGroupForType(const AutofillType& type) { case NO_GROUP: case CREDIT_CARD: + case PASSWORD_FIELD: return NULL; } diff --git a/chromium/components/autofill/core/browser/autofill_type.cc b/chromium/components/autofill/core/browser/autofill_type.cc index f1e280ba9d8..97168acffdf 100644 --- a/chromium/components/autofill/core/browser/autofill_type.cc +++ b/chromium/components/autofill/core/browser/autofill_type.cc @@ -108,6 +108,10 @@ FieldTypeGroup AutofillType::group() const { case COMPANY_NAME: return COMPANY; + case PASSWORD: + case ACCOUNT_CREATION_PASSWORD: + return PASSWORD_FIELD; + case NO_SERVER_DATA: case EMPTY_TYPE: case PHONE_FAX_NUMBER: @@ -116,6 +120,8 @@ FieldTypeGroup AutofillType::group() const { case PHONE_FAX_CITY_AND_NUMBER: case PHONE_FAX_WHOLE_NUMBER: case FIELD_WITH_DEFAULT_VALUE: + case MERCHANT_EMAIL_SIGNUP: + case MERCHANT_PROMO_CODE: return NO_GROUP; case MAX_VALID_FIELD_TYPE: @@ -529,6 +535,14 @@ std::string AutofillType::ToString() const { return "PHONE_BILLING_CITY_AND_NUMBER"; case PHONE_BILLING_WHOLE_NUMBER: return "PHONE_BILLING_WHOLE_NUMBER"; + case MERCHANT_EMAIL_SIGNUP: + return "MERCHANT_EMAIL_SIGNUP"; + case MERCHANT_PROMO_CODE: + return "MERCHANT_PROMO_CODE"; + case PASSWORD: + return "PASSWORD"; + case ACCOUNT_CREATION_PASSWORD: + return "ACCOUNT_CREATION_PASSWORD"; case MAX_VALID_FIELD_TYPE: return std::string(); } diff --git a/chromium/components/autofill/core/browser/autofill_xml_parser.cc b/chromium/components/autofill/core/browser/autofill_xml_parser.cc index c1acbe90f7f..caa101b227f 100644 --- a/chromium/components/autofill/core/browser/autofill_xml_parser.cc +++ b/chromium/components/autofill/core/browser/autofill_xml_parser.cc @@ -8,8 +8,6 @@ #include <string.h> #include "base/logging.h" -#include "base/strings/string_number_conversions.h" -#include "components/autofill/content/browser/autocheckout_page_meta_data.h" #include "components/autofill/core/browser/autofill_server_field_info.h" #include "third_party/libjingle/source/talk/xmllite/qname.h" @@ -37,18 +35,12 @@ void AutofillXmlParser::Error(buzz::XmlParseContext* context, AutofillQueryXmlParser::AutofillQueryXmlParser( std::vector<AutofillServerFieldInfo>* field_infos, UploadRequired* upload_required, - std::string* experiment_id, - AutocheckoutPageMetaData* page_meta_data) + std::string* experiment_id) : field_infos_(field_infos), upload_required_(upload_required), - experiment_id_(experiment_id), - page_meta_data_(page_meta_data), - current_click_element_(NULL), - current_page_number_for_page_types_(0), - is_in_type_section_(false) { + experiment_id_(experiment_id) { DCHECK(upload_required_); DCHECK(experiment_id_); - DCHECK(page_meta_data_); } AutofillQueryXmlParser::~AutofillQueryXmlParser() {} @@ -112,48 +104,6 @@ void AutofillQueryXmlParser::StartElement(buzz::XmlParseContext* context, // Record this field type, default value pair. field_infos_->push_back(field_info); - } else if (element.compare("autofill_flow") == 0) { - // |attrs| is a NULL-terminated list of (attribute, value) pairs. - while (*attrs) { - buzz::QName attribute_qname = context->ResolveQName(*attrs, true); - ++attrs; - const std::string& attribute_name = attribute_qname.LocalPart(); - if (attribute_name.compare("page_no") == 0) - page_meta_data_->current_page_number = GetIntValue(context, *attrs); - else if (attribute_name.compare("total_pages") == 0) - page_meta_data_->total_pages = GetIntValue(context, *attrs); - else if (attribute_name.compare("ignore_ajax") == 0) - page_meta_data_->ignore_ajax = strcmp(*attrs, "false") != 0; - ++attrs; - } - } else if (element.compare("page_advance_button") == 0) { - page_meta_data_->proceed_element_descriptor = WebElementDescriptor(); - ParseElementDescriptor(context, - attrs, - &page_meta_data_->proceed_element_descriptor); - } else if (element.compare("click_elements_before_formfill") == 0) { - page_meta_data_->click_elements_before_form_fill.push_back( - WebElementDescriptor()); - current_click_element_ = &page_meta_data_->click_elements_before_form_fill. - back(); - } else if (element.compare("click_elements_after_formfill") == 0) { - page_meta_data_->click_elements_after_form_fill.push_back( - WebElementDescriptor()); - current_click_element_ = &page_meta_data_->click_elements_after_form_fill. - back(); - } else if (element.compare("web_element") == 0) { - ParseElementDescriptor(context, attrs, current_click_element_); - } else if (element.compare("flow_page") == 0) { - while (*attrs) { - buzz::QName attribute_qname = context->ResolveQName(*attrs, true); - ++attrs; - const std::string& attribute_name = attribute_qname.LocalPart(); - if (attribute_name.compare("page_no") == 0) - current_page_number_for_page_types_ = GetIntValue(context, *attrs); - ++attrs; - } - } else if (element.compare("type") == 0) { - is_in_type_section_ = true; } } @@ -185,27 +135,6 @@ void AutofillQueryXmlParser::ParseElementDescriptor( } } -void AutofillQueryXmlParser::EndElement(buzz::XmlParseContext* context, - const char* name) { - is_in_type_section_ = false; -} - -void AutofillQueryXmlParser::CharacterData( - buzz::XmlParseContext* context, const char* text, int len) { - if (!is_in_type_section_) - return; - - int type = -1; - base::StringToInt(std::string(text, len), &type); - if (type >= AUTOCHECKOUT_STEP_MIN_VALUE && - type <= AUTOCHECKOUT_STEP_MAX_VALUE) { - AutocheckoutStepType step_type = - static_cast<AutocheckoutStepType>(type); - page_meta_data_->page_types[current_page_number_for_page_types_] - .push_back(step_type); - } -} - int AutofillQueryXmlParser::GetIntValue(buzz::XmlParseContext* context, const char* attribute) { char* attr_end = NULL; diff --git a/chromium/components/autofill/core/browser/autofill_xml_parser.h b/chromium/components/autofill/core/browser/autofill_xml_parser.h index c01f13759c9..46836f67e19 100644 --- a/chromium/components/autofill/core/browser/autofill_xml_parser.h +++ b/chromium/components/autofill/core/browser/autofill_xml_parser.h @@ -18,8 +18,6 @@ namespace autofill { -struct AutocheckoutPageMetaData; - // The base class that contains common functionality between // AutofillQueryXmlParser and AutofillUploadXmlParser. class AutofillXmlParser : public buzz::XmlParseHandler { @@ -76,8 +74,7 @@ class AutofillQueryXmlParser : public AutofillXmlParser { public: AutofillQueryXmlParser(std::vector<AutofillServerFieldInfo>* field_infos, UploadRequired* upload_required, - std::string* experiment_id, - AutocheckoutPageMetaData* page_meta_data); + std::string* experiment_id); virtual ~AutofillQueryXmlParser(); private: @@ -97,21 +94,6 @@ class AutofillQueryXmlParser : public AutofillXmlParser { const char* const* attrs, WebElementDescriptor* element_descriptor); - // A callback for the end of an </element>, called by Expat. - // |context| is a parsing context used to resolve element/attribute names. - // |name| is the name of the element. - virtual void EndElement(buzz::XmlParseContext* context, - const char* name) OVERRIDE; - - // The callback for character data between tags (<element>text...</element>). - // |context| is a parsing context used to resolve element/attribute names. - // |text| is a pointer to the beginning of character data (not null - // terminated). - // |len| is the length of the string pointed to by text. - virtual void CharacterData(buzz::XmlParseContext* context, - const char* text, - int len) OVERRIDE; - // A helper function to retrieve integer values from strings. Raises an // XML parse error if it fails. // |context| is the current parsing context. @@ -129,18 +111,6 @@ class AutofillQueryXmlParser : public AutofillXmlParser { // For the default server implementation, this is empty. std::string* experiment_id_; - // Page metadata for multipage autofill flow. - AutocheckoutPageMetaData* page_meta_data_; - - // The click element the parser is currently processing. - WebElementDescriptor* current_click_element_; - - // Number of page whose type is currently being parsed. - int current_page_number_for_page_types_; - - // Whether the instance is currently parsing inside 'type' tags. - bool is_in_type_section_; - DISALLOW_COPY_AND_ASSIGN(AutofillQueryXmlParser); }; diff --git a/chromium/components/autofill/core/browser/autofill_xml_parser_unittest.cc b/chromium/components/autofill/core/browser/autofill_xml_parser_unittest.cc index 0c6b0f2154b..a622bef6d0f 100644 --- a/chromium/components/autofill/core/browser/autofill_xml_parser_unittest.cc +++ b/chromium/components/autofill/core/browser/autofill_xml_parser_unittest.cc @@ -7,7 +7,6 @@ #include "base/memory/scoped_ptr.h" #include "base/strings/string_number_conversions.h" -#include "components/autofill/content/browser/autocheckout_page_meta_data.h" #include "components/autofill/core/browser/autofill_xml_parser.h" #include "components/autofill/core/browser/field_types.h" #include "testing/gtest/include/gtest/gtest.h" @@ -26,8 +25,7 @@ class AutofillQueryXmlParserTest : public testing::Test { // Create a parser. AutofillQueryXmlParser parse_handler(&field_infos_, &upload_required_, - &experiment_id_, - &page_meta_data_); + &experiment_id_); buzz::XmlParser parser(&parse_handler); parser.Parse(xml.c_str(), xml.length(), true); EXPECT_EQ(should_succeed, parse_handler.succeeded()); @@ -36,7 +34,6 @@ class AutofillQueryXmlParserTest : public testing::Test { std::vector<AutofillServerFieldInfo> field_infos_; UploadRequired upload_required_; std::string experiment_id_; - autofill::AutocheckoutPageMetaData page_meta_data_; }; class AutofillUploadXmlParserTest : public testing::Test { @@ -165,187 +162,6 @@ TEST_F(AutofillQueryXmlParserTest, ParseExperimentId) { EXPECT_EQ("ServerSmartyPants", experiment_id_); } -// Fails on ASAN bot. http://crbug.com/253797 -#if defined(ADDRESS_SANITIZER) -#define MAYBE_ParseAutofillFlow DISABLED_ParseAutofillFlow -#else -#define MAYBE_ParseAutofillFlow ParseAutofillFlow -#endif - -// Test XML response with autofill_flow information. -TEST_F(AutofillQueryXmlParserTest, MAYBE_ParseAutofillFlow) { - std::string xml = "<autofillqueryresponse>" - "<field autofilltype=\"55\"/>" - "<autofill_flow page_no=\"1\" total_pages=\"10\">" - "<page_advance_button id=\"foo\"/>" - "<flow_page page_no=\"0\">" - "<type>1</type>" - "<type>2</type>" - "</flow_page>" - "<flow_page page_no=\"1\">" - "<type>3</type>" - "</flow_page>" - "</autofill_flow>" - "</autofillqueryresponse>"; - - ParseQueryXML(xml, true); - - EXPECT_EQ(1U, field_infos_.size()); - EXPECT_EQ(1, page_meta_data_.current_page_number); - EXPECT_EQ(10, page_meta_data_.total_pages); - EXPECT_TRUE(page_meta_data_.ignore_ajax); - EXPECT_EQ("foo", page_meta_data_.proceed_element_descriptor.descriptor); - EXPECT_EQ(autofill::WebElementDescriptor::ID, - page_meta_data_.proceed_element_descriptor.retrieval_method); - EXPECT_EQ(2U, page_meta_data_.page_types.size()); - EXPECT_EQ(2U, page_meta_data_.page_types[0].size()); - EXPECT_EQ(1U, page_meta_data_.page_types[1].size()); - EXPECT_EQ(AUTOCHECKOUT_STEP_SHIPPING, page_meta_data_.page_types[0][0]); - EXPECT_EQ(AUTOCHECKOUT_STEP_DELIVERY, page_meta_data_.page_types[0][1]); - EXPECT_EQ(AUTOCHECKOUT_STEP_BILLING, page_meta_data_.page_types[1][0]); - - // Clear |field_infos_| for the next test; - field_infos_.clear(); - - // Test css_selector as page_advance_button. - xml = "<autofillqueryresponse>" - "<field autofilltype=\"55\"/>" - "<autofill_flow page_no=\"1\" total_pages=\"10\">" - "<page_advance_button css_selector=\"[name="foo"]\"/>" - "</autofill_flow>" - "</autofillqueryresponse>"; - - ParseQueryXML(xml, true); - - EXPECT_EQ(1U, field_infos_.size()); - EXPECT_EQ(1, page_meta_data_.current_page_number); - EXPECT_EQ(10, page_meta_data_.total_pages); - EXPECT_EQ("[name=\"foo\"]", - page_meta_data_.proceed_element_descriptor.descriptor); - EXPECT_EQ(autofill::WebElementDescriptor::CSS_SELECTOR, - page_meta_data_.proceed_element_descriptor.retrieval_method); - - // Clear |field_infos_| for the next test; - field_infos_.clear(); - - // Test first attribute is always the one set. - xml = "<autofillqueryresponse>" - "<field autofilltype=\"55\"/>" - "<autofill_flow page_no=\"1\" total_pages=\"10\">" - "<page_advance_button css_selector=\"[name="foo"]\"" - " id=\"foo\"/>" - "</autofill_flow>" - "</autofillqueryresponse>"; - - ParseQueryXML(xml, true); - - EXPECT_EQ(1U, field_infos_.size()); - EXPECT_EQ(1, page_meta_data_.current_page_number); - EXPECT_EQ(10, page_meta_data_.total_pages); - EXPECT_EQ("[name=\"foo\"]", - page_meta_data_.proceed_element_descriptor.descriptor); - EXPECT_EQ(autofill::WebElementDescriptor::CSS_SELECTOR, - page_meta_data_.proceed_element_descriptor.retrieval_method); - - // Clear |field_infos_| for the next test; - field_infos_.clear(); - - // Test parsing click_elements_before_formfill correctly. - xml = "<autofillqueryresponse>" - "<field autofilltype=\"55\"/>" - "<autofill_flow page_no=\"1\" total_pages=\"10\">" - "<click_elements_before_formfill>" - "<web_element id=\"btn1\" /></click_elements_before_formfill>" - "<click_elements_before_formfill>" - "<web_element css_selector=\"[name="btn2"]\"/>" - "</click_elements_before_formfill>" - "</autofill_flow>" - "</autofillqueryresponse>"; - - ParseQueryXML(xml, true); - - EXPECT_EQ(1U, field_infos_.size()); - EXPECT_EQ(1, page_meta_data_.current_page_number); - EXPECT_EQ(10, page_meta_data_.total_pages); - ASSERT_EQ(2U, page_meta_data_.click_elements_before_form_fill.size()); - autofill::WebElementDescriptor& click_elment = - page_meta_data_.click_elements_before_form_fill[0]; - EXPECT_EQ("btn1", click_elment.descriptor); - EXPECT_EQ(autofill::WebElementDescriptor::ID, click_elment.retrieval_method); - click_elment = page_meta_data_.click_elements_before_form_fill[1]; - EXPECT_EQ("[name=\"btn2\"]", click_elment.descriptor); - EXPECT_EQ(autofill::WebElementDescriptor::CSS_SELECTOR, - click_elment.retrieval_method); - - // Clear |field_infos_| for the next test; - field_infos_.clear(); - - // Test parsing click_elements_after_formfill correctly. - xml = "<autofillqueryresponse>" - "<field autofilltype=\"55\"/>" - "<autofill_flow page_no=\"1\" total_pages=\"10\">" - "<click_elements_after_formfill>" - "<web_element id=\"btn1\" /></click_elements_after_formfill>" - "</autofill_flow>" - "</autofillqueryresponse>"; - - ParseQueryXML(xml, true); - - EXPECT_EQ(1U, field_infos_.size()); - EXPECT_EQ(1, page_meta_data_.current_page_number); - EXPECT_EQ(10, page_meta_data_.total_pages); - ASSERT_EQ(1U, page_meta_data_.click_elements_after_form_fill.size()); - click_elment = page_meta_data_.click_elements_after_form_fill[0]; - EXPECT_EQ("btn1", click_elment.descriptor); - EXPECT_EQ(autofill::WebElementDescriptor::ID, click_elment.retrieval_method); - - // Clear |field_infos_| for the next test. - field_infos_.clear(); - - // Test setting of ignore_ajax attribute. - xml = "<autofillqueryresponse>" - "<field autofilltype=\"55\"/>" - "<autofill_flow page_no=\"1\" total_pages=\"10\" ignore_ajax=\"true\">" - "<page_advance_button css_selector=\"[name="foo"]\"" - " id=\"foo\"/>" - "</autofill_flow>" - "</autofillqueryresponse>"; - - ParseQueryXML(xml, true); - - EXPECT_EQ(1U, field_infos_.size()); - EXPECT_EQ(1, page_meta_data_.current_page_number); - EXPECT_EQ(10, page_meta_data_.total_pages); - EXPECT_TRUE(page_meta_data_.ignore_ajax); - EXPECT_EQ("[name=\"foo\"]", - page_meta_data_.proceed_element_descriptor.descriptor); - EXPECT_EQ(autofill::WebElementDescriptor::CSS_SELECTOR, - page_meta_data_.proceed_element_descriptor.retrieval_method); - - // Clear |field_infos_| for the next test. - field_infos_.clear(); - - // Test redundant setting to false of ignore_ajax attribute. - xml = "<autofillqueryresponse>" - "<field autofilltype=\"55\"/>" - "<autofill_flow page_no=\"1\" total_pages=\"10\" ignore_ajax=\"false\">" - "<page_advance_button css_selector=\"[name="foo"]\"" - " id=\"foo\"/>" - "</autofill_flow>" - "</autofillqueryresponse>"; - - ParseQueryXML(xml, true); - - EXPECT_EQ(1U, field_infos_.size()); - EXPECT_EQ(1, page_meta_data_.current_page_number); - EXPECT_EQ(10, page_meta_data_.total_pages); - EXPECT_FALSE(page_meta_data_.ignore_ajax); - EXPECT_EQ("[name=\"foo\"]", - page_meta_data_.proceed_element_descriptor.descriptor); - EXPECT_EQ(autofill::WebElementDescriptor::CSS_SELECTOR, - page_meta_data_.proceed_element_descriptor.retrieval_method); -} - // Test badly formed XML queries. TEST_F(AutofillQueryXmlParserTest, ParseErrors) { // Test no Autofill type. diff --git a/chromium/components/autofill/core/browser/contact_info.cc b/chromium/components/autofill/core/browser/contact_info.cc index 603c4fa3b54..b3ef9857474 100644 --- a/chromium/components/autofill/core/browser/contact_info.cc +++ b/chromium/components/autofill/core/browser/contact_info.cc @@ -52,8 +52,8 @@ void NameInfo::GetSupportedTypes(ServerFieldTypeSet* supported_types) const { } base::string16 NameInfo::GetRawInfo(ServerFieldType type) const { - // TODO(isherman): Is GetStorableType even necessary? - switch (AutofillType(type).GetStorableType()) { + DCHECK_EQ(NAME, AutofillType(type).group()); + switch (type) { case NAME_FIRST: return first(); @@ -75,10 +75,8 @@ base::string16 NameInfo::GetRawInfo(ServerFieldType type) const { } void NameInfo::SetRawInfo(ServerFieldType type, const base::string16& value) { - // TODO(isherman): Is GetStorableType even necessary? - ServerFieldType storable_type = AutofillType(type).GetStorableType(); - DCHECK_EQ(NAME, AutofillType(storable_type).group()); - switch (storable_type) { + DCHECK_EQ(NAME, AutofillType(type).group()); + switch (type) { case NAME_FIRST: first_ = value; break; diff --git a/chromium/components/autofill/core/browser/credit_card.cc b/chromium/components/autofill/core/browser/credit_card.cc index f3ac6b112ab..a3e3044f18b 100644 --- a/chromium/components/autofill/core/browser/credit_card.cc +++ b/chromium/components/autofill/core/browser/credit_card.cc @@ -6,6 +6,7 @@ #include <stddef.h> +#include <algorithm> #include <ostream> #include <string> @@ -276,6 +277,7 @@ std::string CreditCard::GetCreditCardType(const base::string16& number) { } base::string16 CreditCard::GetRawInfo(ServerFieldType type) const { + DCHECK_EQ(CREDIT_CARD, AutofillType(type).group()); switch (type) { case CREDIT_CARD_NAME: return name_on_card_; @@ -323,6 +325,7 @@ base::string16 CreditCard::GetRawInfo(ServerFieldType type) const { void CreditCard::SetRawInfo(ServerFieldType type, const base::string16& value) { + DCHECK_EQ(CREDIT_CARD, AutofillType(type).group()); switch (type) { case CREDIT_CARD_NAME: name_on_card_ = value; diff --git a/chromium/components/autofill/core/browser/data_driven_test.cc b/chromium/components/autofill/core/browser/data_driven_test.cc index 18f461ece0e..3218b17ec00 100644 --- a/chromium/components/autofill/core/browser/data_driven_test.cc +++ b/chromium/components/autofill/core/browser/data_driven_test.cc @@ -15,7 +15,7 @@ namespace { // Reads |file| into |content|, and converts Windows line-endings to Unix ones. // Returns true on success. bool ReadFile(const base::FilePath& file, std::string* content) { - if (!file_util::ReadFileToString(file, content)) + if (!base::ReadFileToString(file, content)) return false; ReplaceSubstringsAfterOffset(content, 0, "\r\n", "\n"); diff --git a/chromium/components/autofill/core/browser/field_types.h b/chromium/components/autofill/core/browser/field_types.h index 081a7a75c42..0eda2e278c8 100644 --- a/chromium/components/autofill/core/browser/field_types.h +++ b/chromium/components/autofill/core/browser/field_types.h @@ -99,10 +99,22 @@ enum ServerFieldType { NAME_BILLING_FULL = 71, NAME_BILLING_SUFFIX = 72, + // Field types for options generally found in merchant buyflows. Given that + // these are likely to be filled out differently on a case by case basis, + // they are here primarly for use by Autocheckout. + MERCHANT_EMAIL_SIGNUP = 73, + MERCHANT_PROMO_CODE = 74, + + // Field types for the password fields. PASSWORD is the default type for all + // password fields. ACCOUNT_CREATION_PASSWORD is the first password field in + // an account creation form and will trigger password generation. + PASSWORD = 75, + ACCOUNT_CREATION_PASSWORD = 76, + // No new types can be added without a corresponding change to the Autofill // server. - MAX_VALID_FIELD_TYPE = 73, + MAX_VALID_FIELD_TYPE = 77, }; // The list of all HTML autocomplete field type hints supported by Chrome. @@ -181,6 +193,7 @@ enum FieldTypeGroup { PHONE_HOME, PHONE_BILLING, CREDIT_CARD, + PASSWORD_FIELD, }; typedef std::set<ServerFieldType> ServerFieldTypeSet; diff --git a/chromium/components/autofill/core/browser/form_structure.cc b/chromium/components/autofill/core/browser/form_structure.cc index 0a534f8d847..ae4de043a11 100644 --- a/chromium/components/autofill/core/browser/form_structure.cc +++ b/chromium/components/autofill/core/browser/form_structure.cc @@ -16,7 +16,6 @@ #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" #include "base/time/time.h" -#include "components/autofill/content/browser/autocheckout_page_meta_data.h" #include "components/autofill/core/browser/autofill_metrics.h" #include "components/autofill/core/browser/autofill_type.h" #include "components/autofill/core/browser/autofill_xml_parser.h" @@ -48,7 +47,6 @@ const char kAttributeName[] = "name"; const char kAttributeSignature[] = "signature"; const char kAttributeUrlprefixSignature[] = "urlprefixsignature"; const char kAcceptedFeaturesExperiment[] = "e"; // e=experiments -const char kAcceptedFeaturesAutocheckoutExperiment[] = "a,e"; // a=autocheckout const char kClientVersion[] = "6.1.1715.1442/en (GGLL)"; const char kXMLDeclaration[] = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"; const char kXMLElementAutofillQuery[] = "autofillquery"; @@ -324,8 +322,7 @@ std::string StripDigitsIfRequired(const base::string16& input) { } // namespace -FormStructure::FormStructure(const FormData& form, - const std::string& autocheckout_url_prefix) +FormStructure::FormStructure(const FormData& form) : form_name_(form.name), source_url_(form.origin), target_url_(form.action), @@ -333,9 +330,7 @@ FormStructure::FormStructure(const FormData& form, active_field_count_(0), upload_required_(USE_UPLOAD_RATES), server_experiment_id_("no server response"), - has_author_specified_types_(false), - autocheckout_url_prefix_(autocheckout_url_prefix), - filled_by_autocheckout_(false) { + has_author_specified_types_(false) { // Copy the form fields. std::map<base::string16, size_t> unique_names; for (std::vector<FormFieldData>::const_iterator field = @@ -495,16 +490,6 @@ bool FormStructure::EncodeQueryRequest( autofill_request_xml.SetAttr(buzz::QName(kAttributeClientVersion), kClientVersion); - // autocheckout_url_prefix tells the Autofill server where the forms in the - // request came from, and the the Autofill server checks internal status and - // decide to enable Autocheckout or not and may return Autocheckout related - // data in the response accordingly. - // There is no page/frame level object associated with FormStructure that - // we could extract URL prefix from. But, all the forms should come from the - // same frame, so they should have the same Autocheckout URL prefix. Thus we - // use URL prefix from the first form with Autocheckout enabled. - std::string autocheckout_url_prefix; - // Some badly formatted web sites repeat forms - detect that and encode only // one form as returned data would be the same for all the repeated forms. std::set<std::string> processed_forms; @@ -524,15 +509,6 @@ bool FormStructure::EncodeQueryRequest( encompassing_xml_element.get())) continue; // Malformed form, skip it. - if ((*it)->IsAutocheckoutEnabled()) { - if (autocheckout_url_prefix.empty()) { - autocheckout_url_prefix = (*it)->autocheckout_url_prefix_; - } else { - // Making sure all the forms in the request has the same url_prefix. - DCHECK_EQ(autocheckout_url_prefix, (*it)->autocheckout_url_prefix_); - } - } - autofill_request_xml.AddElement(encompassing_xml_element.release()); encoded_signatures->push_back(signature); } @@ -540,15 +516,8 @@ bool FormStructure::EncodeQueryRequest( if (!encoded_signatures->size()) return false; - if (autocheckout_url_prefix.empty()) { - autofill_request_xml.SetAttr(buzz::QName(kAttributeAcceptedFeatures), - kAcceptedFeaturesExperiment); - } else { - autofill_request_xml.SetAttr(buzz::QName(kAttributeAcceptedFeatures), - kAcceptedFeaturesAutocheckoutExperiment); - autofill_request_xml.SetAttr(buzz::QName(kAttributeUrlprefixSignature), - Hash64Bit(autocheckout_url_prefix)); - } + autofill_request_xml.SetAttr(buzz::QName(kAttributeAcceptedFeatures), + kAcceptedFeaturesExperiment); // Obtain the XML structure as a string. *encoded_xml = kXMLDeclaration; @@ -561,7 +530,6 @@ bool FormStructure::EncodeQueryRequest( void FormStructure::ParseQueryResponse( const std::string& response_xml, const std::vector<FormStructure*>& forms, - autofill::AutocheckoutPageMetaData* page_meta_data, const AutofillMetrics& metric_logger) { metric_logger.LogServerQueryMetric(AutofillMetrics::QUERY_RESPONSE_RECEIVED); @@ -571,8 +539,7 @@ void FormStructure::ParseQueryResponse( std::string experiment_id; AutofillQueryXmlParser parse_handler(&field_infos, &upload_required, - &experiment_id, - page_meta_data); + &experiment_id); buzz::XmlParser parser(&parse_handler); parser.Parse(response_xml.c_str(), response_xml.length(), true); if (!parse_handler.succeeded()) @@ -692,21 +659,12 @@ std::string FormStructure::FormSignature() const { return Hash64Bit(form_string); } -bool FormStructure::IsAutocheckoutEnabled() const { - return !autocheckout_url_prefix_.empty(); -} - bool FormStructure::ShouldSkipField(const FormFieldData& field) const { - return (field.is_checkable || field.form_control_type == "password") && - !IsAutocheckoutEnabled(); -} - -size_t FormStructure::RequiredFillableFields() const { - return IsAutocheckoutEnabled() ? 0 : kRequiredAutofillFields; + return field.is_checkable; } bool FormStructure::IsAutofillable(bool require_method_post) const { - if (autofill_count() < RequiredFillableFields()) + if (autofill_count() < kRequiredAutofillFields) return false; return ShouldBeParsed(require_method_post); @@ -723,7 +681,7 @@ void FormStructure::UpdateAutofillCount() { } bool FormStructure::ShouldBeParsed(bool require_method_post) const { - if (active_field_count() < RequiredFillableFields()) + if (active_field_count() < kRequiredAutofillFields) return false; // Rule out http(s)://*/search?... @@ -732,25 +690,19 @@ bool FormStructure::ShouldBeParsed(bool require_method_post) const { if (target_url_.path() == "/search") return false; - if (!IsAutocheckoutEnabled()) { - // Make sure there is at least one text field when Autocheckout is - // not enabled. - bool has_text_field = false; - for (std::vector<AutofillField*>::const_iterator it = begin(); - it != end() && !has_text_field; ++it) { - has_text_field |= (*it)->form_control_type != "select-one"; - } - if (!has_text_field) - return false; + bool has_text_field = false; + for (std::vector<AutofillField*>::const_iterator it = begin(); + it != end() && !has_text_field; ++it) { + has_text_field |= (*it)->form_control_type != "select-one"; } + if (!has_text_field) + return false; return !require_method_post || (method_ == POST); } bool FormStructure::ShouldBeCrowdsourced() const { - // Allow all forms in Autocheckout flow to be crowdsourced. - return (!has_author_specified_types_ && ShouldBeParsed(true)) || - IsAutocheckoutEnabled(); + return !has_author_specified_types_ && ShouldBeParsed(true); } void FormStructure::UpdateFromCache(const FormStructure& cached_form) { @@ -782,7 +734,6 @@ void FormStructure::UpdateFromCache(const FormStructure& cached_form) { UpdateAutofillCount(); - filled_by_autocheckout_ = cached_form.filled_by_autocheckout(); server_experiment_id_ = cached_form.server_experiment_id(); // The form signature should match between query and upload requests to the @@ -929,7 +880,7 @@ void FormStructure::LogQualityMetrics( } } - if (num_detected_field_types < RequiredFillableFields()) { + if (num_detected_field_types < kRequiredAutofillFields) { metric_logger.LogUserHappinessMetric( AutofillMetrics::SUBMITTED_NON_FILLABLE_FORM); } else { diff --git a/chromium/components/autofill/core/browser/form_structure.h b/chromium/components/autofill/core/browser/form_structure.h index 1af765057c2..6382c20a715 100644 --- a/chromium/components/autofill/core/browser/form_structure.h +++ b/chromium/components/autofill/core/browser/form_structure.h @@ -40,7 +40,6 @@ namespace autofill { class AutofillMetrics; -struct AutocheckoutPageMetaData; struct FormData; struct FormDataPredictions; @@ -48,8 +47,7 @@ struct FormDataPredictions; // in the fields along with additional information needed by Autofill. class FormStructure { public: - FormStructure(const FormData& form, - const std::string& autocheckout_url_prefix); + FormStructure(const FormData& form); virtual ~FormStructure(); // Runs several heuristics against the form fields to determine their possible @@ -82,7 +80,6 @@ class FormStructure { static void ParseQueryResponse( const std::string& response_xml, const std::vector<FormStructure*>& forms, - autofill::AutocheckoutPageMetaData* page_meta_data, const AutofillMetrics& metric_logger); // Fills |forms| with the details from the given |form_structures| and their @@ -156,6 +153,9 @@ class FormStructure { const GURL& source_url() const { return source_url_; } + void set_upload_required(UploadRequired required) { + upload_required_ = required; + } UploadRequired upload_required() const { return upload_required_; } virtual std::string server_experiment_id() const; @@ -164,11 +164,6 @@ class FormStructure { // |user_submitted| is currently always false. FormData ToFormData() const; - bool filled_by_autocheckout() const { return filled_by_autocheckout_; } - void set_filled_by_autocheckout(bool filled_by_autocheckout) { - filled_by_autocheckout_ = filled_by_autocheckout; - } - bool operator==(const FormData& form) const; bool operator!=(const FormData& form) const; @@ -199,13 +194,9 @@ class FormStructure { // distinguishing credit card sections from non-credit card ones -- is made. void IdentifySections(bool has_author_specified_sections); - bool IsAutocheckoutEnabled() const; - // Returns true if field should be skipped when talking to Autofill server. bool ShouldSkipField(const FormFieldData& field) const; - // Returns the minimal number of fillable fields required to start autofill. - size_t RequiredFillableFields() const; size_t active_field_count() const; // The name of the form. @@ -247,13 +238,6 @@ class FormStructure { // author, via the |autocompletetype| attribute. bool has_author_specified_types_; - // The URL prefix matched in autocheckout whitelist. An empty string implies - // autocheckout is not enabled for this form. - std::string autocheckout_url_prefix_; - - // Whether or not this form was filled by Autocheckout. - bool filled_by_autocheckout_; - DISALLOW_COPY_AND_ASSIGN(FormStructure); }; diff --git a/chromium/components/autofill/core/browser/form_structure_unittest.cc b/chromium/components/autofill/core/browser/form_structure_unittest.cc index 7d38165b4d1..a6d08a7cb3f 100644 --- a/chromium/components/autofill/core/browser/form_structure_unittest.cc +++ b/chromium/components/autofill/core/browser/form_structure_unittest.cc @@ -7,7 +7,6 @@ #include "base/memory/scoped_ptr.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" -#include "components/autofill/content/browser/autocheckout_page_meta_data.h" #include "components/autofill/core/browser/autofill_metrics.h" #include "components/autofill/core/common/form_data.h" #include "components/autofill/core/common/form_field_data.h" @@ -92,11 +91,7 @@ TEST(FormStructureTest, FieldCount) { // The render process sends all fields to browser including fields with // autocomplete=off - form_structure.reset(new FormStructure(form, std::string())); - EXPECT_EQ(4U, form_structure->field_count()); - - // We expect the same count when autocheckout is enabled. - form_structure.reset(new FormStructure(form, "http://fake_url")); + form_structure.reset(new FormStructure(form)); EXPECT_EQ(4U, form_structure->field_count()); } @@ -127,7 +122,7 @@ TEST(FormStructureTest, AutofillCount) { form.fields.push_back(field); // Only text and select fields that are heuristically matched are counted. - form_structure.reset(new FormStructure(form, std::string())); + form_structure.reset(new FormStructure(form)); form_structure->DetermineHeuristicTypes(TestAutofillMetrics()); EXPECT_EQ(1U, form_structure->autofill_count()); @@ -138,25 +133,20 @@ TEST(FormStructureTest, AutofillCount) { field.should_autocomplete = false; form.fields.push_back(field); - form_structure.reset(new FormStructure(form, std::string())); + form_structure.reset(new FormStructure(form)); form_structure->DetermineHeuristicTypes(TestAutofillMetrics()); // DetermineHeuristicTypes also assign field type for fields with // autocomplete=off thus autofill_count includes them. This is a bug, // and they should not be counted. See http://crbug.com/176432 for details. // TODO(benquan): change it to EXPECT_EQ(1U, ... when the bug is fixed. EXPECT_EQ(2U, form_structure->autofill_count()); - - // All fields should be counted when Autocheckout is enabled. - form_structure.reset(new FormStructure(form, "http://fake_url")); - form_structure->DetermineHeuristicTypes(TestAutofillMetrics()); - EXPECT_EQ(2U, form_structure->autofill_count()); } TEST(FormStructureTest, SourceURL) { FormData form; form.origin = GURL("http://www.foo.com/"); form.method = ASCIIToUTF16("post"); - FormStructure form_structure(form, std::string()); + FormStructure form_structure(form); EXPECT_EQ(form.origin, form_structure.source_url()); } @@ -169,11 +159,6 @@ TEST(FormStructureTest, IsAutofillable) { form.method = ASCIIToUTF16("post"); FormFieldData field; - // When autocheckout is enabled, we enable autofill even the form has - // no fields - form_structure.reset(new FormStructure(form, "http://fake_url")); - form_structure->DetermineHeuristicTypes(TestAutofillMetrics()); - EXPECT_TRUE(form_structure->IsAutofillable(true)); field.label = ASCIIToUTF16("username"); field.name = ASCIIToUTF16("username"); @@ -190,15 +175,10 @@ TEST(FormStructureTest, IsAutofillable) { field.form_control_type = "submit"; form.fields.push_back(field); - form_structure.reset(new FormStructure(form, std::string())); + form_structure.reset(new FormStructure(form)); form_structure->DetermineHeuristicTypes(TestAutofillMetrics()); EXPECT_FALSE(form_structure->IsAutofillable(true)); - // We do not limit to three text fields when autocheckout is enabled. - form_structure.reset(new FormStructure(form, "http://fake_url")); - form_structure->DetermineHeuristicTypes(TestAutofillMetrics()); - EXPECT_TRUE(form_structure->IsAutofillable(true)); - // We now have three text fields, but only two auto-fillable fields. field.label = ASCIIToUTF16("First Name"); field.name = ASCIIToUTF16("firstname"); @@ -210,7 +190,7 @@ TEST(FormStructureTest, IsAutofillable) { field.form_control_type = "text"; form.fields.push_back(field); - form_structure.reset(new FormStructure(form, std::string())); + form_structure.reset(new FormStructure(form)); form_structure->DetermineHeuristicTypes(TestAutofillMetrics()); EXPECT_FALSE(form_structure->IsAutofillable(true)); @@ -220,14 +200,14 @@ TEST(FormStructureTest, IsAutofillable) { field.form_control_type = "email"; form.fields.push_back(field); - form_structure.reset(new FormStructure(form, std::string())); + form_structure.reset(new FormStructure(form)); form_structure->DetermineHeuristicTypes(TestAutofillMetrics()); EXPECT_TRUE(form_structure->IsAutofillable(true)); // The method must be 'post', though we can intentionally ignore this // criterion for the sake of providing a helpful warning message to the user. form.method = ASCIIToUTF16("get"); - form_structure.reset(new FormStructure(form, std::string())); + form_structure.reset(new FormStructure(form)); form_structure->DetermineHeuristicTypes(TestAutofillMetrics()); EXPECT_FALSE(form_structure->IsAutofillable(true)); EXPECT_TRUE(form_structure->IsAutofillable(false)); @@ -235,13 +215,13 @@ TEST(FormStructureTest, IsAutofillable) { // The target cannot include http(s)://*/search... form.method = ASCIIToUTF16("post"); form.action = GURL("http://google.com/search?q=hello"); - form_structure.reset(new FormStructure(form, std::string())); + form_structure.reset(new FormStructure(form)); form_structure->DetermineHeuristicTypes(TestAutofillMetrics()); EXPECT_FALSE(form_structure->IsAutofillable(true)); // But search can be in the URL. form.action = GURL("http://search.com/?q=hello"); - form_structure.reset(new FormStructure(form, std::string())); + form_structure.reset(new FormStructure(form)); form_structure->DetermineHeuristicTypes(TestAutofillMetrics()); EXPECT_TRUE(form_structure->IsAutofillable(true)); } @@ -270,14 +250,9 @@ TEST(FormStructureTest, ShouldBeParsed) { form.fields.push_back(checkable_field); // We have only one text field, should not be parsed. - form_structure.reset(new FormStructure(form, std::string())); + form_structure.reset(new FormStructure(form)); EXPECT_FALSE(form_structure->ShouldBeParsed(true)); - // The form should be parsed for autocheckout even it has less than three - // text fields. - form_structure.reset(new FormStructure(form, "http://fake_url")); - EXPECT_TRUE(form_structure->ShouldBeParsed(true)); - // We now have three text fields, though only two are auto-fillable. field.label = ASCIIToUTF16("First Name"); field.name = ASCIIToUTF16("firstname"); @@ -289,25 +264,25 @@ TEST(FormStructureTest, ShouldBeParsed) { field.form_control_type = "text"; form.fields.push_back(field); - form_structure.reset(new FormStructure(form, std::string())); + form_structure.reset(new FormStructure(form)); EXPECT_TRUE(form_structure->ShouldBeParsed(true)); // The method must be 'post', though we can intentionally ignore this // criterion for the sake of providing a helpful warning message to the user. form.method = ASCIIToUTF16("get"); - form_structure.reset(new FormStructure(form, std::string())); + form_structure.reset(new FormStructure(form)); EXPECT_FALSE(form_structure->IsAutofillable(true)); EXPECT_TRUE(form_structure->ShouldBeParsed(false)); // The target cannot include http(s)://*/search... form.method = ASCIIToUTF16("post"); form.action = GURL("http://google.com/search?q=hello"); - form_structure.reset(new FormStructure(form, std::string())); + form_structure.reset(new FormStructure(form)); EXPECT_FALSE(form_structure->ShouldBeParsed(true)); // But search can be in the URL. form.action = GURL("http://search.com/?q=hello"); - form_structure.reset(new FormStructure(form, std::string())); + form_structure.reset(new FormStructure(form)); EXPECT_TRUE(form_structure->ShouldBeParsed(true)); // The form need only have three fields, but at least one must be a text @@ -329,17 +304,13 @@ TEST(FormStructureTest, ShouldBeParsed) { field.form_control_type = "select-one"; form.fields.push_back(field); - form_structure.reset(new FormStructure(form, std::string())); + form_structure.reset(new FormStructure(form)); EXPECT_TRUE(form_structure->ShouldBeParsed(true)); form.fields[0].form_control_type = "select-one"; // Now, no text fields. - form_structure.reset(new FormStructure(form, std::string())); + form_structure.reset(new FormStructure(form)); EXPECT_FALSE(form_structure->ShouldBeParsed(true)); - - // It should be parsed when autocheckout is enabled. - form_structure.reset(new FormStructure(form, "http://fake_url")); - EXPECT_TRUE(form_structure->ShouldBeParsed(true)); } TEST(FormStructureTest, HeuristicsContactInfo) { @@ -383,7 +354,7 @@ TEST(FormStructureTest, HeuristicsContactInfo) { field.form_control_type = "submit"; form.fields.push_back(field); - form_structure.reset(new FormStructure(form, std::string())); + form_structure.reset(new FormStructure(form)); form_structure->DetermineHeuristicTypes(TestAutofillMetrics()); EXPECT_TRUE(form_structure->IsAutofillable(true)); @@ -434,7 +405,7 @@ TEST(FormStructureTest, HeuristicsAutocompleteAttribute) { field.autocomplete_attribute = "email"; form.fields.push_back(field); - form_structure.reset(new FormStructure(form, std::string())); + form_structure.reset(new FormStructure(form)); form_structure->DetermineHeuristicTypes(TestAutofillMetrics()); EXPECT_TRUE(form_structure->IsAutofillable(true)); @@ -475,7 +446,7 @@ TEST(FormStructureTest, HeuristicsAutocompleteAttributePhoneTypes) { field.autocomplete_attribute = "tel-local-suffix"; form.fields.push_back(field); - form_structure.reset(new FormStructure(form, std::string())); + form_structure.reset(new FormStructure(form)); form_structure->DetermineHeuristicTypes(TestAutofillMetrics()); EXPECT_TRUE(form_structure->IsAutofillable(true)); @@ -516,7 +487,7 @@ TEST(FormStructureTest, AutocompleteAttributeOverridesOtherHeuristics) { field.name = ASCIIToUTF16("email"); form.fields.push_back(field); - form_structure.reset(new FormStructure(form, std::string())); + form_structure.reset(new FormStructure(form)); form_structure->DetermineHeuristicTypes(TestAutofillMetrics()); EXPECT_TRUE(form_structure->IsAutofillable(true)); EXPECT_TRUE(form_structure->ShouldBeCrowdsourced()); @@ -530,7 +501,7 @@ TEST(FormStructureTest, AutocompleteAttributeOverridesOtherHeuristics) { // Now update the first form field to include an 'autocomplete' attribute. form.fields.front().autocomplete_attribute = "x-other"; - form_structure.reset(new FormStructure(form, std::string())); + form_structure.reset(new FormStructure(form)); form_structure->DetermineHeuristicTypes(TestAutofillMetrics()); EXPECT_FALSE(form_structure->IsAutofillable(true)); EXPECT_FALSE(form_structure->ShouldBeCrowdsourced()); @@ -541,20 +512,6 @@ TEST(FormStructureTest, AutocompleteAttributeOverridesOtherHeuristics) { EXPECT_EQ(UNKNOWN_TYPE, form_structure->field(0)->heuristic_type()); EXPECT_EQ(UNKNOWN_TYPE, form_structure->field(1)->heuristic_type()); EXPECT_EQ(UNKNOWN_TYPE, form_structure->field(2)->heuristic_type()); - - // When Autocheckout is enabled, we should ignore 'autocomplete' attribute - // when deciding to crowdsource. - form_structure.reset(new FormStructure(form, "http://fake.url")); - form_structure->DetermineHeuristicTypes(TestAutofillMetrics()); - EXPECT_TRUE(form_structure->IsAutofillable(true)); - EXPECT_TRUE(form_structure->ShouldBeCrowdsourced()); - - ASSERT_EQ(3U, form_structure->field_count()); - ASSERT_EQ(0U, form_structure->autofill_count()); - - EXPECT_EQ(UNKNOWN_TYPE, form_structure->field(0)->heuristic_type()); - EXPECT_EQ(UNKNOWN_TYPE, form_structure->field(1)->heuristic_type()); - EXPECT_EQ(UNKNOWN_TYPE, form_structure->field(2)->heuristic_type()); } // Verify that we can correctly process sections listed in the |autocomplete| @@ -604,7 +561,7 @@ TEST(FormStructureTest, HeuristicsAutocompleteAttributeWithSections) { field.autocomplete_attribute = "section-foo cc-number"; form.fields.push_back(field); - FormStructure form_structure(form, std::string()); + FormStructure form_structure(form); form_structure.DetermineHeuristicTypes(TestAutofillMetrics()); EXPECT_TRUE(form_structure.IsAutofillable(true)); @@ -649,7 +606,7 @@ TEST(FormStructureTest, HeuristicsAutocompleteAttributeWithSectionsDegenerate) { field.autocomplete_attribute = "garbage billing email"; form.fields.push_back(field); - FormStructure form_structure(form, std::string()); + FormStructure form_structure(form); form_structure.DetermineHeuristicTypes(TestAutofillMetrics()); // Expect the correct number of fields. @@ -679,7 +636,7 @@ TEST(FormStructureTest, HeuristicsAutocompleteAttributeWithSectionsRepeated) { field.autocomplete_attribute = "section-foo address-line1"; form.fields.push_back(field); - FormStructure form_structure(form, std::string()); + FormStructure form_structure(form); form_structure.DetermineHeuristicTypes(TestAutofillMetrics()); // Expect the correct number of fields. @@ -717,7 +674,7 @@ TEST(FormStructureTest, HeuristicsDontOverrideAutocompleteAttributeSections) { field.autocomplete_attribute = "address-line1"; form.fields.push_back(field); - FormStructure form_structure(form, std::string()); + FormStructure form_structure(form); form_structure.DetermineHeuristicTypes(TestAutofillMetrics()); // Expect the correct number of fields. @@ -782,7 +739,7 @@ TEST(FormStructureTest, HeuristicsSample8) { field.form_control_type = "submit"; form.fields.push_back(field); - form_structure.reset(new FormStructure(form, std::string())); + form_structure.reset(new FormStructure(form)); form_structure->DetermineHeuristicTypes(TestAutofillMetrics()); EXPECT_TRUE(form_structure->IsAutofillable(true)); ASSERT_EQ(10U, form_structure->field_count()); @@ -850,7 +807,7 @@ TEST(FormStructureTest, HeuristicsSample6) { field.form_control_type = "submit"; form.fields.push_back(field); - form_structure.reset(new FormStructure(form, std::string())); + form_structure.reset(new FormStructure(form)); form_structure->DetermineHeuristicTypes(TestAutofillMetrics()); EXPECT_TRUE(form_structure->IsAutofillable(true)); ASSERT_EQ(7U, form_structure->field_count()); @@ -916,7 +873,7 @@ TEST(FormStructureTest, HeuristicsLabelsOnly) { field.form_control_type = "submit"; form.fields.push_back(field); - form_structure.reset(new FormStructure(form, std::string())); + form_structure.reset(new FormStructure(form)); form_structure->DetermineHeuristicTypes(TestAutofillMetrics()); EXPECT_TRUE(form_structure->IsAutofillable(true)); ASSERT_EQ(8U, form_structure->field_count()); @@ -974,7 +931,7 @@ TEST(FormStructureTest, HeuristicsCreditCardInfo) { field.form_control_type = "submit"; form.fields.push_back(field); - form_structure.reset(new FormStructure(form, std::string())); + form_structure.reset(new FormStructure(form)); form_structure->DetermineHeuristicTypes(TestAutofillMetrics()); EXPECT_TRUE(form_structure->IsAutofillable(true)); ASSERT_EQ(6U, form_structure->field_count()); @@ -1035,7 +992,7 @@ TEST(FormStructureTest, HeuristicsCreditCardInfoWithUnknownCardField) { field.form_control_type = "submit"; form.fields.push_back(field); - form_structure.reset(new FormStructure(form, std::string())); + form_structure.reset(new FormStructure(form)); form_structure->DetermineHeuristicTypes(TestAutofillMetrics()); EXPECT_TRUE(form_structure->IsAutofillable(true)); ASSERT_EQ(7U, form_structure->field_count()); @@ -1083,7 +1040,7 @@ TEST(FormStructureTest, ThreeAddressLines) { field.name = ASCIIToUTF16("city"); form.fields.push_back(field); - form_structure.reset(new FormStructure(form, std::string())); + form_structure.reset(new FormStructure(form)); form_structure->DetermineHeuristicTypes(TestAutofillMetrics()); EXPECT_TRUE(form_structure->IsAutofillable(true)); ASSERT_EQ(4U, form_structure->field_count()); @@ -1125,7 +1082,7 @@ TEST(FormStructureTest, BillingAndShippingAddresses) { field.name = ASCIIToUTF16("billing.address.addressLine2"); form.fields.push_back(field); - form_structure.reset(new FormStructure(form, std::string())); + form_structure.reset(new FormStructure(form)); form_structure->DetermineHeuristicTypes(TestAutofillMetrics()); EXPECT_TRUE(form_structure->IsAutofillable(true)); ASSERT_EQ(4U, form_structure->field_count()); @@ -1166,7 +1123,7 @@ TEST(FormStructureTest, SurplusAddressLinesIgnored) { field.name = ASCIIToUTF16("billing.address.addressLine4"); form.fields.push_back(field); - form_structure.reset(new FormStructure(form, std::string())); + form_structure.reset(new FormStructure(form)); form_structure->DetermineHeuristicTypes(TestAutofillMetrics()); ASSERT_EQ(4U, form_structure->field_count()); ASSERT_EQ(2U, form_structure->autofill_count()); @@ -1210,7 +1167,7 @@ TEST(FormStructureTest, ThreeAddressLinesExpedia) { field.name = ASCIIToUTF16("FOPIH_RgWebCC_0_IHAddress_adct"); form.fields.push_back(field); - form_structure.reset(new FormStructure(form, std::string())); + form_structure.reset(new FormStructure(form)); form_structure->DetermineHeuristicTypes(TestAutofillMetrics()); EXPECT_TRUE(form_structure->IsAutofillable(true)); ASSERT_EQ(4U, form_structure->field_count()); @@ -1249,7 +1206,7 @@ TEST(FormStructureTest, TwoAddressLinesEbay) { field.name = ASCIIToUTF16("city"); form.fields.push_back(field); - form_structure.reset(new FormStructure(form, std::string())); + form_structure.reset(new FormStructure(form)); form_structure->DetermineHeuristicTypes(TestAutofillMetrics()); EXPECT_TRUE(form_structure->IsAutofillable(true)); ASSERT_EQ(3U, form_structure->field_count()); @@ -1283,7 +1240,7 @@ TEST(FormStructureTest, HeuristicsStateWithProvince) { field.name = ASCIIToUTF16("State"); form.fields.push_back(field); - form_structure.reset(new FormStructure(form, std::string())); + form_structure.reset(new FormStructure(form)); form_structure->DetermineHeuristicTypes(TestAutofillMetrics()); EXPECT_TRUE(form_structure->IsAutofillable(true)); ASSERT_EQ(3U, form_structure->field_count()); @@ -1350,7 +1307,7 @@ TEST(FormStructureTest, HeuristicsWithBilling) { field.name = ASCIIToUTF16("email$emailBox"); form.fields.push_back(field); - form_structure.reset(new FormStructure(form, std::string())); + form_structure.reset(new FormStructure(form)); form_structure->DetermineHeuristicTypes(TestAutofillMetrics()); EXPECT_TRUE(form_structure->IsAutofillable(true)); ASSERT_EQ(11U, form_structure->field_count()); @@ -1401,7 +1358,7 @@ TEST(FormStructureTest, ThreePartPhoneNumber) { field.max_length = 0; form.fields.push_back(field); - form_structure.reset(new FormStructure(form, std::string())); + form_structure.reset(new FormStructure(form)); form_structure->DetermineHeuristicTypes(TestAutofillMetrics()); EXPECT_TRUE(form_structure->IsAutofillable(true)); ASSERT_EQ(4U, form_structure->field_count()); @@ -1447,7 +1404,7 @@ TEST(FormStructureTest, HeuristicsInfernoCC) { field.name = ASCIIToUTF16("expiration_year"); form.fields.push_back(field); - form_structure.reset(new FormStructure(form, std::string())); + form_structure.reset(new FormStructure(form)); form_structure->DetermineHeuristicTypes(TestAutofillMetrics()); EXPECT_TRUE(form_structure->IsAutofillable(true)); @@ -1500,7 +1457,7 @@ TEST(FormStructureTest, CVCCodeClash) { field.name = ASCIIToUTF16("csc"); form.fields.push_back(field); - form_structure.reset(new FormStructure(form, std::string())); + form_structure.reset(new FormStructure(form)); form_structure->DetermineHeuristicTypes(TestAutofillMetrics()); EXPECT_TRUE(form_structure->IsAutofillable(true)); @@ -1559,7 +1516,7 @@ TEST(FormStructureTest, EncodeQueryRequest) { form.fields.push_back(checkable_field); ScopedVector<FormStructure> forms; - forms.push_back(new FormStructure(form, std::string())); + forms.push_back(new FormStructure(form)); std::vector<std::string> encoded_signatures; std::string encoded_xml; const char * const kSignature1 = "11337937696949187602"; @@ -1579,7 +1536,7 @@ TEST(FormStructureTest, EncodeQueryRequest) { // Add the same form, only one will be encoded, so EncodeQueryRequest() should // return the same data. - forms.push_back(new FormStructure(form, std::string())); + forms.push_back(new FormStructure(form)); ASSERT_TRUE(FormStructure::EncodeQueryRequest(forms.get(), &encoded_signatures, &encoded_xml)); @@ -1593,7 +1550,7 @@ TEST(FormStructureTest, EncodeQueryRequest) { form.fields.push_back(field); } - forms.push_back(new FormStructure(form, std::string())); + forms.push_back(new FormStructure(form)); ASSERT_TRUE(FormStructure::EncodeQueryRequest(forms.get(), &encoded_signatures, &encoded_xml)); @@ -1624,7 +1581,7 @@ TEST(FormStructureTest, EncodeQueryRequest) { malformed_form.fields.push_back(field); } - forms.push_back(new FormStructure(malformed_form, std::string())); + forms.push_back(new FormStructure(malformed_form)); ASSERT_TRUE(FormStructure::EncodeQueryRequest(forms.get(), &encoded_signatures, &encoded_xml)); @@ -1635,37 +1592,12 @@ TEST(FormStructureTest, EncodeQueryRequest) { // Check that we fail if there are only bad form(s). ScopedVector<FormStructure> bad_forms; - bad_forms.push_back(new FormStructure(malformed_form, std::string())); + bad_forms.push_back(new FormStructure(malformed_form)); EXPECT_FALSE(FormStructure::EncodeQueryRequest(bad_forms.get(), &encoded_signatures, &encoded_xml)); EXPECT_EQ(0U, encoded_signatures.size()); EXPECT_EQ("", encoded_xml); - - // Check the behaviour with autocheckout enabled. - ScopedVector<FormStructure> checkable_forms; - checkable_forms.push_back( - new FormStructure(form, "https://www.sample1.com/query/path")); - - ASSERT_TRUE(FormStructure::EncodeQueryRequest(checkable_forms.get(), - &encoded_signatures, - &encoded_xml)); - const char * const kSignature3 = "7747357776717901584"; - const char * const kResponse3 = - "<?xml version=\"1.0\" encoding=\"UTF-8\"?><autofillquery " - "clientversion=\"6.1.1715.1442/en (GGLL)\" accepts=\"a,e\" " - "urlprefixsignature=\"7648393911063090788\">" - "<form signature=\"7747357776717901584\">" - "<field signature=\"412125936\"/>" - "<field signature=\"1917667676\"/><field signature=\"2226358947\"/><field" - " signature=\"747221617\"/><field signature=\"4108155786\"/><field " - "signature=\"3410250678\"/><field signature=\"509334676\"/><field " - "signature=\"509334676\"/><field signature=\"509334676\"/><field " - "signature=\"509334676\"/><field signature=\"509334676\"/></form>" - "</autofillquery>"; - ASSERT_EQ(1U, encoded_signatures.size()); - EXPECT_EQ(kSignature3, encoded_signatures[0]); - EXPECT_EQ(kResponse3, encoded_xml); } TEST(FormStructureTest, EncodeUploadRequest) { @@ -1673,7 +1605,7 @@ TEST(FormStructureTest, EncodeUploadRequest) { std::vector<ServerFieldTypeSet> possible_field_types; FormData form; form.method = ASCIIToUTF16("post"); - form_structure.reset(new FormStructure(form, std::string())); + form_structure.reset(new FormStructure(form)); form_structure->DetermineHeuristicTypes(TestAutofillMetrics()); FormFieldData field; @@ -1721,7 +1653,7 @@ TEST(FormStructureTest, EncodeUploadRequest) { possible_field_types.push_back(ServerFieldTypeSet()); possible_field_types.back().insert(ADDRESS_HOME_COUNTRY); - form_structure.reset(new FormStructure(form, std::string())); + form_structure.reset(new FormStructure(form)); ASSERT_EQ(form_structure->field_count(), possible_field_types.size()); for (size_t i = 0; i < form_structure->field_count(); ++i) @@ -1779,7 +1711,7 @@ TEST(FormStructureTest, EncodeUploadRequest) { possible_field_types.back().insert(ADDRESS_BILLING_LINE2); } - form_structure.reset(new FormStructure(form, std::string())); + form_structure.reset(new FormStructure(form)); ASSERT_EQ(form_structure->field_count(), possible_field_types.size()); for (size_t i = 0; i < form_structure->field_count(); ++i) form_structure->field(i)->set_possible_types(possible_field_types[i]); @@ -1818,7 +1750,7 @@ TEST(FormStructureTest, EncodeUploadRequest) { possible_field_types.back().insert(ADDRESS_BILLING_LINE1); possible_field_types.back().insert(ADDRESS_BILLING_LINE2); } - form_structure.reset(new FormStructure(form, std::string())); + form_structure.reset(new FormStructure(form)); ASSERT_EQ(form_structure->field_count(), possible_field_types.size()); for (size_t i = 0; i < form_structure->field_count(); ++i) form_structure->field(i)->set_possible_types(possible_field_types[i]); @@ -1831,7 +1763,7 @@ TEST(FormStructureTest, EncodeFieldAssignments) { std::vector<ServerFieldTypeSet> possible_field_types; FormData form; form.method = ASCIIToUTF16("post"); - form_structure.reset(new FormStructure(form, std::string())); + form_structure.reset(new FormStructure(form)); form_structure->DetermineHeuristicTypes(TestAutofillMetrics()); FormFieldData field; @@ -1879,7 +1811,7 @@ TEST(FormStructureTest, EncodeFieldAssignments) { possible_field_types.push_back(ServerFieldTypeSet()); possible_field_types.back().insert(ADDRESS_HOME_COUNTRY); - form_structure.reset(new FormStructure(form, std::string())); + form_structure.reset(new FormStructure(form)); ASSERT_EQ(form_structure->field_count(), possible_field_types.size()); for (size_t i = 0; i < form_structure->field_count(); ++i) @@ -1924,7 +1856,7 @@ TEST(FormStructureTest, EncodeFieldAssignments) { possible_field_types.back().insert(ADDRESS_BILLING_LINE2); } - form_structure.reset(new FormStructure(form, std::string())); + form_structure.reset(new FormStructure(form)); ASSERT_EQ(form_structure->field_count(), possible_field_types.size()); for (size_t i = 0; i < form_structure->field_count(); ++i) form_structure->field(i)->set_possible_types(possible_field_types[i]); @@ -1973,7 +1905,7 @@ TEST(FormStructureTest, CheckDataPresence) { field.name = ASCIIToUTF16("email"); form.fields.push_back(field); - FormStructure form_structure(form, std::string()); + FormStructure form_structure(form); ServerFieldTypeSet unknown_type; unknown_type.insert(UNKNOWN_TYPE); @@ -2239,7 +2171,7 @@ TEST(FormStructureTest, CheckMultipleTypes) { possible_field_types.push_back(ServerFieldTypeSet()); possible_field_types.back().insert(ADDRESS_HOME_LINE1); - form_structure.reset(new FormStructure(form, std::string())); + form_structure.reset(new FormStructure(form)); for (size_t i = 0; i < form_structure->field_count(); ++i) form_structure->field(i)->set_possible_types(possible_field_types[i]); @@ -2329,36 +2261,38 @@ TEST(FormStructureTest, CheckFormSignature) { field.name = ASCIIToUTF16("first"); form.fields.push_back(field); - // Password fields shouldn't affect the signature. - field.label = ASCIIToUTF16("Password"); - field.name = ASCIIToUTF16("password"); - field.form_control_type = "password"; + // Checkable fields shouldn't affect the signature. + field.label = ASCIIToUTF16("Select"); + field.name = ASCIIToUTF16("Select"); + field.form_control_type = "checkbox"; + field.is_checkable = true; form.fields.push_back(field); - form_structure.reset(new FormStructure(form, std::string())); + form_structure.reset(new FormStructure(form)); EXPECT_EQ(FormStructureTest::Hash64Bit( std::string("://&&email&first")), form_structure->FormSignature()); form.origin = GURL(std::string("http://www.facebook.com")); - form_structure.reset(new FormStructure(form, std::string())); + form_structure.reset(new FormStructure(form)); EXPECT_EQ(FormStructureTest::Hash64Bit( std::string("http://www.facebook.com&&email&first")), form_structure->FormSignature()); form.action = GURL(std::string("https://login.facebook.com/path")); - form_structure.reset(new FormStructure(form, std::string())); + form_structure.reset(new FormStructure(form)); EXPECT_EQ(FormStructureTest::Hash64Bit( std::string("https://login.facebook.com&&email&first")), form_structure->FormSignature()); form.name = ASCIIToUTF16("login_form"); - form_structure.reset(new FormStructure(form, std::string())); + form_structure.reset(new FormStructure(form)); EXPECT_EQ(FormStructureTest::Hash64Bit( std::string("https://login.facebook.com&login_form&email&first")), form_structure->FormSignature()); + field.is_checkable = false; field.label = ASCIIToUTF16("Random Field label"); field.name = ASCIIToUTF16("random1234"); field.form_control_type = "text"; @@ -2372,7 +2306,7 @@ TEST(FormStructureTest, CheckFormSignature) { field.label = ASCIIToUTF16("Random Field label3"); field.name = ASCIIToUTF16("12345random"); form.fields.push_back(field); - form_structure.reset(new FormStructure(form, std::string())); + form_structure.reset(new FormStructure(form)); EXPECT_EQ(FormStructureTest::Hash64Bit( std::string("https://login.facebook.com&login_form&email&first&" "random1234&random&1random&random")), @@ -2403,12 +2337,12 @@ TEST(FormStructureTest, ToFormData) { field.form_control_type = "submit"; form.fields.push_back(field); - EXPECT_EQ(form, FormStructure(form, std::string()).ToFormData()); + EXPECT_EQ(form, FormStructure(form).ToFormData()); // Currently |FormStructure(form_data)ToFormData().user_submitted| is always // false. This forces a future author that changes this to update this test. form.user_submitted = true; - EXPECT_NE(form, FormStructure(form, std::string()).ToFormData()); + EXPECT_NE(form, FormStructure(form).ToFormData()); } TEST(FormStructureTest, SkipFieldTest) { @@ -2424,18 +2358,20 @@ TEST(FormStructureTest, SkipFieldTest) { field.form_control_type = "text"; form.fields.push_back(field); - field.label = ASCIIToUTF16("password"); - field.name = ASCIIToUTF16("password"); - field.form_control_type = "password"; + field.label = ASCIIToUTF16("select"); + field.name = ASCIIToUTF16("select"); + field.form_control_type = "checkbox"; + field.is_checkable = true; form.fields.push_back(field); field.label = base::string16(); field.name = ASCIIToUTF16("email"); field.form_control_type = "text"; + field.is_checkable = false; form.fields.push_back(field); ScopedVector<FormStructure> forms; - forms.push_back(new FormStructure(form, std::string())); + forms.push_back(new FormStructure(form)); std::vector<std::string> encoded_signatures; std::string encoded_xml; @@ -2451,16 +2387,6 @@ TEST(FormStructureTest, SkipFieldTest) { ASSERT_EQ(1U, encoded_signatures.size()); EXPECT_EQ(kSignature, encoded_signatures[0]); EXPECT_EQ(kResponse, encoded_xml); - - AutocheckoutPageMetaData page_meta_data; - const char * const kServerResponse = - "<autofillqueryresponse><field autofilltype=\"3\" />" - "<field autofilltype=\"9\" /></autofillqueryresponse>"; - FormStructure::ParseQueryResponse(kServerResponse, forms.get(), - &page_meta_data, TestAutofillMetrics()); - ASSERT_EQ(NAME_FIRST, forms[0]->field(0)->server_type()); - ASSERT_EQ(NO_SERVER_DATA, forms[0]->field(1)->server_type()); - ASSERT_EQ(EMAIL_ADDRESS, forms[0]->field(2)->server_type()); } } // namespace autofill diff --git a/chromium/components/autofill/core/browser/password_autofill_manager.cc b/chromium/components/autofill/core/browser/password_autofill_manager.cc index b568f8d36ed..def93f407f2 100644 --- a/chromium/components/autofill/core/browser/password_autofill_manager.cc +++ b/chromium/components/autofill/core/browser/password_autofill_manager.cc @@ -6,7 +6,7 @@ #include "components/autofill/core/common/autofill_messages.h" #include "content/public/browser/render_view_host.h" #include "content/public/browser/web_contents.h" -#include "ui/base/keycodes/keyboard_codes.h" +#include "ui/events/keycodes/keyboard_codes.h" namespace autofill { diff --git a/chromium/components/autofill/core/browser/personal_data_manager.cc b/chromium/components/autofill/core/browser/personal_data_manager.cc index 9ddff255d5d..c3bd991fc10 100644 --- a/chromium/components/autofill/core/browser/personal_data_manager.cc +++ b/chromium/components/autofill/core/browser/personal_data_manager.cc @@ -726,7 +726,7 @@ bool PersonalDataManager::IsValidLearnableProfile( } // static -bool PersonalDataManager::MergeProfile( +std::string PersonalDataManager::MergeProfile( const AutofillProfile& new_profile, const std::vector<AutofillProfile*>& existing_profiles, const std::string& app_locale, @@ -735,6 +735,7 @@ bool PersonalDataManager::MergeProfile( // Set to true if |existing_profiles| already contains an equivalent profile. bool matching_profile_found = false; + std::string guid = new_profile.guid(); // If we have already saved this address, merge in any missing values. // Only merge with the first match. @@ -751,6 +752,7 @@ bool PersonalDataManager::MergeProfile( // data. If an automatically aggregated profile would overwrite a // verified profile, just drop it. matching_profile_found = true; + guid = existing_profile->guid(); if (!existing_profile->IsVerified() || new_profile.IsVerified()) existing_profile->OverwriteWithOrAddTo(new_profile, app_locale); } @@ -761,7 +763,7 @@ bool PersonalDataManager::MergeProfile( if (!matching_profile_found) merged_profiles->push_back(new_profile); - return matching_profile_found; + return guid; } void PersonalDataManager::SetProfiles(std::vector<AutofillProfile>* profiles) { @@ -962,10 +964,10 @@ void PersonalDataManager::CancelPendingQuery( *handle = 0; } -void PersonalDataManager::SaveImportedProfile( +std::string PersonalDataManager::SaveImportedProfile( const AutofillProfile& imported_profile) { if (browser_context_->IsOffTheRecord()) - return; + return std::string(); // Don't save a web profile if the data in the profile is a subset of an // auxiliary profile. @@ -973,32 +975,39 @@ void PersonalDataManager::SaveImportedProfile( auxiliary_profiles_.begin(); iter != auxiliary_profiles_.end(); ++iter) { if (imported_profile.IsSubsetOf(**iter, app_locale_)) - return; + return (*iter)->guid(); } std::vector<AutofillProfile> profiles; - MergeProfile(imported_profile, web_profiles_.get(), app_locale_, &profiles); + std::string guid = + MergeProfile(imported_profile, web_profiles_.get(), app_locale_, + &profiles); SetProfiles(&profiles); + return guid; } -void PersonalDataManager::SaveImportedCreditCard( +std::string PersonalDataManager::SaveImportedCreditCard( const CreditCard& imported_card) { DCHECK(!imported_card.number().empty()); if (browser_context_->IsOffTheRecord()) - return; + return std::string(); // Set to true if |imported_card| is merged into the credit card list. bool merged = false; + std::string guid = imported_card.guid(); std::vector<CreditCard> credit_cards; for (std::vector<CreditCard*>::const_iterator card = credit_cards_.begin(); card != credit_cards_.end(); ++card) { // If |imported_card| has not yet been merged, check whether it should be // with the current |card|. - if (!merged && (*card)->UpdateFromImportedCard(imported_card, app_locale_)) + if (!merged && + (*card)->UpdateFromImportedCard(imported_card, app_locale_)) { + guid = (*card)->guid(); merged = true; + } credit_cards.push_back(**card); } @@ -1007,6 +1016,7 @@ void PersonalDataManager::SaveImportedCreditCard( credit_cards.push_back(imported_card); SetCreditCards(&credit_cards); + return guid; } void PersonalDataManager::LogProfileCount() const { diff --git a/chromium/components/autofill/core/browser/personal_data_manager.h b/chromium/components/autofill/core/browser/personal_data_manager.h index 9b71aa55c58..d5dc85ea81b 100644 --- a/chromium/components/autofill/core/browser/personal_data_manager.h +++ b/chromium/components/autofill/core/browser/personal_data_manager.h @@ -79,11 +79,15 @@ class PersonalDataManager : public WebDataServiceConsumer, bool ImportFormData(const FormStructure& form, const CreditCard** credit_card); - // Saves |imported_profile| to the WebDB if it exists. - virtual void SaveImportedProfile(const AutofillProfile& imported_profile); + // Saves |imported_profile| to the WebDB if it exists. Returns the guid of + // the new or updated profile, or the empty string if no profile was saved. + virtual std::string SaveImportedProfile( + const AutofillProfile& imported_profile); - // Saves a credit card value detected in |ImportedFormData|. - virtual void SaveImportedCreditCard(const CreditCard& imported_credit_card); + // Saves a credit card value detected in |ImportedFormData|. Returns the guid + // of the new or updated card, or the empty string if no card was saved. + virtual std::string SaveImportedCreditCard( + const CreditCard& imported_credit_card); // Adds |profile| to the web database. void AddProfile(const AutofillProfile& profile); @@ -169,8 +173,9 @@ class PersonalDataManager : public WebDataServiceConsumer, // Merges |new_profile| into one of the |existing_profiles| if possible; // otherwise appends |new_profile| to the end of that list. Fills - // |merged_profiles| with the result. - static bool MergeProfile( + // |merged_profiles| with the result. Returns the |guid| of the new or updated + // profile. + static std::string MergeProfile( const AutofillProfile& new_profile, const std::vector<AutofillProfile*>& existing_profiles, const std::string& app_locale, diff --git a/chromium/components/autofill/core/browser/personal_data_manager_unittest.cc b/chromium/components/autofill/core/browser/personal_data_manager_unittest.cc index 7c08e5f8983..cc378e56f7f 100644 --- a/chromium/components/autofill/core/browser/personal_data_manager_unittest.cc +++ b/chromium/components/autofill/core/browser/personal_data_manager_unittest.cc @@ -99,7 +99,9 @@ class PersonalDataManagerTest : public testing::Test { } void MakeProfileIncognito() { - profile_->set_incognito(true); + // Switch to an incognito profile. + profile_->ForceIncognito(true); + DCHECK(profile_->IsOffTheRecord()); } base::MessageLoopForUI message_loop_; @@ -594,7 +596,7 @@ TEST_F(PersonalDataManagerTest, ImportFormData) { form.fields.push_back(field); test::CreateTestFormField("Zip:", "zip", "94102", "text", &field); form.fields.push_back(field); - FormStructure form_structure(form, std::string()); + FormStructure form_structure(form); form_structure.DetermineHeuristicTypes(TestAutofillMetrics()); const CreditCard* imported_credit_card; EXPECT_TRUE(personal_data_->ImportFormData(form_structure, @@ -635,7 +637,7 @@ TEST_F(PersonalDataManagerTest, ImportFormDataBadEmail) { form.fields.push_back(field); test::CreateTestFormField("Zip:", "zip", "94102", "text", &field); form.fields.push_back(field); - FormStructure form_structure(form, std::string()); + FormStructure form_structure(form); form_structure.DetermineHeuristicTypes(TestAutofillMetrics()); const CreditCard* imported_credit_card; EXPECT_FALSE(personal_data_->ImportFormData(form_structure, @@ -668,7 +670,7 @@ TEST_F(PersonalDataManagerTest, ImportFormDataTwoEmails) { test::CreateTestFormField( "Confirm email:", "confirm_email", "example@example.com", "text", &field); form.fields.push_back(field); - FormStructure form_structure(form, std::string()); + FormStructure form_structure(form); form_structure.DetermineHeuristicTypes(TestAutofillMetrics()); const CreditCard* imported_credit_card; EXPECT_TRUE(personal_data_->ImportFormData(form_structure, @@ -699,7 +701,7 @@ TEST_F(PersonalDataManagerTest, ImportFormDataTwoDifferentEmails) { test::CreateTestFormField( "Email:", "email2", "example2@example.com", "text", &field); form.fields.push_back(field); - FormStructure form_structure(form, std::string()); + FormStructure form_structure(form); form_structure.DetermineHeuristicTypes(TestAutofillMetrics()); const CreditCard* imported_credit_card; EXPECT_FALSE(personal_data_->ImportFormData(form_structure, @@ -720,7 +722,7 @@ TEST_F(PersonalDataManagerTest, ImportFormDataNotEnoughFilledFields) { test::CreateTestFormField( "Card number:", "card_number", "4111 1111 1111 1111", "text", &field); form.fields.push_back(field); - FormStructure form_structure(form, std::string()); + FormStructure form_structure(form); form_structure.DetermineHeuristicTypes(TestAutofillMetrics()); const CreditCard* imported_credit_card; EXPECT_FALSE(personal_data_->ImportFormData(form_structure, @@ -751,7 +753,7 @@ TEST_F(PersonalDataManagerTest, ImportFormMinimumAddressUSA) { form.fields.push_back(field); test::CreateTestFormField("Country:", "country", "USA", "text", &field); form.fields.push_back(field); - FormStructure form_structure(form, std::string()); + FormStructure form_structure(form); form_structure.DetermineHeuristicTypes(TestAutofillMetrics()); const CreditCard* imported_credit_card; EXPECT_TRUE(personal_data_->ImportFormData(form_structure, @@ -778,7 +780,7 @@ TEST_F(PersonalDataManagerTest, ImportFormMinimumAddressGB) { test::CreateTestFormField( "Country:", "country", "United Kingdom", "text", &field); form.fields.push_back(field); - FormStructure form_structure(form, std::string()); + FormStructure form_structure(form); form_structure.DetermineHeuristicTypes(TestAutofillMetrics()); const CreditCard* imported_credit_card; EXPECT_TRUE(personal_data_->ImportFormData(form_structure, @@ -800,7 +802,7 @@ TEST_F(PersonalDataManagerTest, ImportFormMinimumAddressGI) { form.fields.push_back(field); test::CreateTestFormField("Country:", "country", "Gibraltar", "text", &field); form.fields.push_back(field); - FormStructure form_structure(form, std::string()); + FormStructure form_structure(form); form_structure.DetermineHeuristicTypes(TestAutofillMetrics()); const CreditCard* imported_credit_card; EXPECT_TRUE(personal_data_->ImportFormData(form_structure, @@ -839,7 +841,7 @@ TEST_F(PersonalDataManagerTest, ImportPhoneNumberSplitAcrossMultipleFields) { form.fields.push_back(field); test::CreateTestFormField("Zip:", "zip", "94102", "text", &field); form.fields.push_back(field); - FormStructure form_structure(form, std::string()); + FormStructure form_structure(form); form_structure.DetermineHeuristicTypes(TestAutofillMetrics()); const CreditCard* imported_credit_card; EXPECT_TRUE(personal_data_->ImportFormData(form_structure, @@ -919,7 +921,7 @@ TEST_F(PersonalDataManagerTest, AggregateTwoDifferentProfiles) { test::CreateTestFormField("Zip:", "zip", "94102", "text", &field); form1.fields.push_back(field); - FormStructure form_structure1(form1, std::string()); + FormStructure form_structure1(form1); form_structure1.DetermineHeuristicTypes(TestAutofillMetrics()); const CreditCard* imported_credit_card; EXPECT_TRUE(personal_data_->ImportFormData(form_structure1, @@ -960,7 +962,7 @@ TEST_F(PersonalDataManagerTest, AggregateTwoDifferentProfiles) { test::CreateTestFormField("Zip:", "zip", "94102", "text", &field); form2.fields.push_back(field); - FormStructure form_structure2(form2, std::string()); + FormStructure form_structure2(form2); form_structure2.DetermineHeuristicTypes(TestAutofillMetrics()); EXPECT_TRUE(personal_data_->ImportFormData(form_structure2, &imported_credit_card)); @@ -1004,7 +1006,7 @@ TEST_F(PersonalDataManagerTest, AggregateTwoProfilesWithMultiValue) { test::CreateTestFormField("Zip:", "zip", "94102", "text", &field); form1.fields.push_back(field); - FormStructure form_structure1(form1, std::string()); + FormStructure form_structure1(form1); form_structure1.DetermineHeuristicTypes(TestAutofillMetrics()); const CreditCard* imported_credit_card; EXPECT_TRUE(personal_data_->ImportFormData(form_structure1, @@ -1044,7 +1046,7 @@ TEST_F(PersonalDataManagerTest, AggregateTwoProfilesWithMultiValue) { test::CreateTestFormField("Zip:", "zip", "94102", "text", &field); form2.fields.push_back(field); - FormStructure form_structure2(form2, std::string()); + FormStructure form_structure2(form2); form_structure2.DetermineHeuristicTypes(TestAutofillMetrics()); EXPECT_TRUE(personal_data_->ImportFormData(form_structure2, &imported_credit_card)); @@ -1097,7 +1099,7 @@ TEST_F(PersonalDataManagerTest, AggregateSameProfileWithConflict) { test::CreateTestFormField("Phone:", "phone", "6505556666", "text", &field); form1.fields.push_back(field); - FormStructure form_structure1(form1, std::string()); + FormStructure form_structure1(form1); form_structure1.DetermineHeuristicTypes(TestAutofillMetrics()); const CreditCard* imported_credit_card; EXPECT_TRUE(personal_data_->ImportFormData(form_structure1, @@ -1148,7 +1150,7 @@ TEST_F(PersonalDataManagerTest, AggregateSameProfileWithConflict) { test::CreateTestFormField("Phone:", "phone", "6502231234", "text", &field); form2.fields.push_back(field); - FormStructure form_structure2(form2, std::string()); + FormStructure form_structure2(form2); form_structure2.DetermineHeuristicTypes(TestAutofillMetrics()); EXPECT_TRUE(personal_data_->ImportFormData(form_structure2, &imported_credit_card)); @@ -1190,7 +1192,7 @@ TEST_F(PersonalDataManagerTest, AggregateProfileWithMissingInfoInOld) { test::CreateTestFormField("Zip:", "zipcode", "19106", "text", &field); form1.fields.push_back(field); - FormStructure form_structure1(form1, std::string()); + FormStructure form_structure1(form1); form_structure1.DetermineHeuristicTypes(TestAutofillMetrics()); const CreditCard* imported_credit_card; EXPECT_TRUE(personal_data_->ImportFormData(form_structure1, @@ -1231,7 +1233,7 @@ TEST_F(PersonalDataManagerTest, AggregateProfileWithMissingInfoInOld) { test::CreateTestFormField("Zip:", "zipcode", "19106", "text", &field); form2.fields.push_back(field); - FormStructure form_structure2(form2, std::string()); + FormStructure form_structure2(form2); form_structure2.DetermineHeuristicTypes(TestAutofillMetrics()); EXPECT_TRUE(personal_data_->ImportFormData(form_structure2, &imported_credit_card)); @@ -1277,7 +1279,7 @@ TEST_F(PersonalDataManagerTest, AggregateProfileWithMissingInfoInNew) { test::CreateTestFormField("Zip:", "zipcode", "19106", "text", &field); form1.fields.push_back(field); - FormStructure form_structure1(form1, std::string()); + FormStructure form_structure1(form1); form_structure1.DetermineHeuristicTypes(TestAutofillMetrics()); const CreditCard* imported_credit_card; EXPECT_TRUE(personal_data_->ImportFormData(form_structure1, @@ -1319,7 +1321,7 @@ TEST_F(PersonalDataManagerTest, AggregateProfileWithMissingInfoInNew) { test::CreateTestFormField("Zip:", "zipcode", "19106", "text", &field); form2.fields.push_back(field); - FormStructure form_structure2(form2, std::string()); + FormStructure form_structure2(form2); form_structure2.DetermineHeuristicTypes(TestAutofillMetrics()); EXPECT_TRUE(personal_data_->ImportFormData(form_structure2, &imported_credit_card)); @@ -1358,7 +1360,7 @@ TEST_F(PersonalDataManagerTest, AggregateProfileWithInsufficientAddress) { test::CreateTestFormField("City:", "city", "Philadelphia", "text", &field); form1.fields.push_back(field); - FormStructure form_structure1(form1, std::string()); + FormStructure form_structure1(form1); form_structure1.DetermineHeuristicTypes(TestAutofillMetrics()); const CreditCard* imported_credit_card; EXPECT_FALSE(personal_data_->ImportFormData(form_structure1, @@ -1412,7 +1414,7 @@ TEST_F(PersonalDataManagerTest, AggregateExistingAuxiliaryProfile) { test::CreateTestFormField("Phone:", "phone", "4158889999", "text", &field); form.fields.push_back(field); - FormStructure form_structure(form, std::string()); + FormStructure form_structure(form); form_structure.DetermineHeuristicTypes(TestAutofillMetrics()); const CreditCard* imported_credit_card; EXPECT_TRUE(personal_data_->ImportFormData(form_structure, @@ -1445,7 +1447,7 @@ TEST_F(PersonalDataManagerTest, AggregateTwoDifferentCreditCards) { test::CreateTestFormField("Exp Year:", "exp_year", "2011", "text", &field); form1.fields.push_back(field); - FormStructure form_structure1(form1, std::string()); + FormStructure form_structure1(form1); form_structure1.DetermineHeuristicTypes(TestAutofillMetrics()); const CreditCard* imported_credit_card; EXPECT_TRUE(personal_data_->ImportFormData(form_structure1, @@ -1479,7 +1481,7 @@ TEST_F(PersonalDataManagerTest, AggregateTwoDifferentCreditCards) { test::CreateTestFormField("Exp Year:", "exp_year", "2012", "text", &field); form2.fields.push_back(field); - FormStructure form_structure2(form2, std::string()); + FormStructure form_structure2(form2); form_structure2.DetermineHeuristicTypes(TestAutofillMetrics()); EXPECT_TRUE(personal_data_->ImportFormData(form_structure2, &imported_credit_card)); @@ -1516,7 +1518,7 @@ TEST_F(PersonalDataManagerTest, AggregateInvalidCreditCard) { test::CreateTestFormField("Exp Year:", "exp_year", "2011", "text", &field); form1.fields.push_back(field); - FormStructure form_structure1(form1, std::string()); + FormStructure form_structure1(form1); form_structure1.DetermineHeuristicTypes(TestAutofillMetrics()); const CreditCard* imported_credit_card; EXPECT_TRUE(personal_data_->ImportFormData(form_structure1, @@ -1550,7 +1552,7 @@ TEST_F(PersonalDataManagerTest, AggregateInvalidCreditCard) { test::CreateTestFormField("Exp Year:", "exp_year", "2012", "text", &field); form2.fields.push_back(field); - FormStructure form_structure2(form2, std::string()); + FormStructure form_structure2(form2); form_structure2.DetermineHeuristicTypes(TestAutofillMetrics()); EXPECT_FALSE(personal_data_->ImportFormData(form_structure2, &imported_credit_card)); @@ -1581,7 +1583,7 @@ TEST_F(PersonalDataManagerTest, AggregateSameCreditCardWithConflict) { test::CreateTestFormField("Exp Year:", "exp_year", "2011", "text", &field); form1.fields.push_back(field); - FormStructure form_structure1(form1, std::string()); + FormStructure form_structure1(form1); form_structure1.DetermineHeuristicTypes(TestAutofillMetrics()); const CreditCard* imported_credit_card; EXPECT_TRUE(personal_data_->ImportFormData(form_structure1, @@ -1616,7 +1618,7 @@ TEST_F(PersonalDataManagerTest, AggregateSameCreditCardWithConflict) { test::CreateTestFormField("Exp Year:", "exp_year", "2012", "text", &field); form2.fields.push_back(field); - FormStructure form_structure2(form2, std::string()); + FormStructure form_structure2(form2); form_structure2.DetermineHeuristicTypes(TestAutofillMetrics()); EXPECT_TRUE(personal_data_->ImportFormData(form_structure2, &imported_credit_card)); @@ -1653,7 +1655,7 @@ TEST_F(PersonalDataManagerTest, AggregateEmptyCreditCardWithConflict) { test::CreateTestFormField("Exp Year:", "exp_year", "2011", "text", &field); form1.fields.push_back(field); - FormStructure form_structure1(form1, std::string()); + FormStructure form_structure1(form1); form_structure1.DetermineHeuristicTypes(TestAutofillMetrics()); const CreditCard* imported_credit_card; EXPECT_TRUE(personal_data_->ImportFormData(form_structure1, @@ -1684,7 +1686,7 @@ TEST_F(PersonalDataManagerTest, AggregateEmptyCreditCardWithConflict) { test::CreateTestFormField("Exp Year:", "exp_year", "2012", "text", &field); form2.fields.push_back(field); - FormStructure form_structure2(form2, std::string()); + FormStructure form_structure2(form2); form_structure2.DetermineHeuristicTypes(TestAutofillMetrics()); EXPECT_FALSE(personal_data_->ImportFormData(form_structure2, &imported_credit_card)); @@ -1719,7 +1721,7 @@ TEST_F(PersonalDataManagerTest, AggregateCreditCardWithMissingInfoInNew) { test::CreateTestFormField("Exp Year:", "exp_year", "2011", "text", &field); form1.fields.push_back(field); - FormStructure form_structure1(form1, std::string()); + FormStructure form_structure1(form1); form_structure1.DetermineHeuristicTypes(TestAutofillMetrics()); const CreditCard* imported_credit_card; EXPECT_TRUE(personal_data_->ImportFormData(form_structure1, @@ -1752,7 +1754,7 @@ TEST_F(PersonalDataManagerTest, AggregateCreditCardWithMissingInfoInNew) { test::CreateTestFormField("Exp Year:", "exp_year", "2011", "text", &field); form2.fields.push_back(field); - FormStructure form_structure2(form2, std::string()); + FormStructure form_structure2(form2); form_structure2.DetermineHeuristicTypes(TestAutofillMetrics()); EXPECT_TRUE(personal_data_->ImportFormData(form_structure2, &imported_credit_card)); @@ -1780,7 +1782,7 @@ TEST_F(PersonalDataManagerTest, AggregateCreditCardWithMissingInfoInNew) { form3.fields.push_back(field); // Note missing expiration month and year.. - FormStructure form_structure3(form3, std::string()); + FormStructure form_structure3(form3); form_structure3.DetermineHeuristicTypes(TestAutofillMetrics()); EXPECT_FALSE(personal_data_->ImportFormData(form_structure3, &imported_credit_card)); @@ -1832,7 +1834,7 @@ TEST_F(PersonalDataManagerTest, AggregateCreditCardWithMissingInfoInOld) { test::CreateTestFormField("Exp Year:", "exp_year", "2012", "text", &field); form.fields.push_back(field); - FormStructure form_structure(form, std::string()); + FormStructure form_structure(form); form_structure.DetermineHeuristicTypes(TestAutofillMetrics()); const CreditCard* imported_credit_card; EXPECT_TRUE(personal_data_->ImportFormData(form_structure, @@ -1887,7 +1889,7 @@ TEST_F(PersonalDataManagerTest, AggregateSameCreditCardWithSeparators) { test::CreateTestFormField("Exp Year:", "exp_year", "2011", "text", &field); form.fields.push_back(field); - FormStructure form_structure(form, std::string()); + FormStructure form_structure(form); form_structure.DetermineHeuristicTypes(TestAutofillMetrics()); const CreditCard* imported_credit_card; EXPECT_TRUE(personal_data_->ImportFormData(form_structure, @@ -1945,7 +1947,7 @@ TEST_F(PersonalDataManagerTest, AggregateExistingVerifiedProfileWithConflict) { test::CreateTestFormField("Zip:", "zip", "91601", "text", &field); form.fields.push_back(field); - FormStructure form_structure(form, std::string()); + FormStructure form_structure(form); form_structure.DetermineHeuristicTypes(TestAutofillMetrics()); const CreditCard* imported_credit_card; EXPECT_TRUE(personal_data_->ImportFormData(form_structure, @@ -1995,7 +1997,7 @@ TEST_F(PersonalDataManagerTest, test::CreateTestFormField("Exp Year:", "exp_year", "2012", "text", &field); form.fields.push_back(field); - FormStructure form_structure(form, std::string()); + FormStructure form_structure(form); form_structure.DetermineHeuristicTypes(TestAutofillMetrics()); const CreditCard* imported_credit_card; EXPECT_TRUE(personal_data_->ImportFormData(form_structure, @@ -2278,7 +2280,7 @@ TEST_F(PersonalDataManagerTest, CaseInsensitiveMultiValueAggregation) { "Phone number:", "phone_number", "817-555-6789", "text", &field); form1.fields.push_back(field); - FormStructure form_structure1(form1, std::string()); + FormStructure form_structure1(form1); form_structure1.DetermineHeuristicTypes(TestAutofillMetrics()); const CreditCard* imported_credit_card; EXPECT_TRUE(personal_data_->ImportFormData(form_structure1, @@ -2322,7 +2324,7 @@ TEST_F(PersonalDataManagerTest, CaseInsensitiveMultiValueAggregation) { "Phone number:", "phone_number", "214-555-1234", "text", &field); form2.fields.push_back(field); - FormStructure form_structure2(form2, std::string()); + FormStructure form_structure2(form2); form_structure2.DetermineHeuristicTypes(TestAutofillMetrics()); EXPECT_TRUE(personal_data_->ImportFormData(form_structure2, &imported_credit_card)); diff --git a/chromium/components/autofill/core/browser/phone_number.cc b/chromium/components/autofill/core/browser/phone_number.cc index 2ee67f98b72..b068b038f2f 100644 --- a/chromium/components/autofill/core/browser/phone_number.cc +++ b/chromium/components/autofill/core/browser/phone_number.cc @@ -74,8 +74,8 @@ void PhoneNumber::GetSupportedTypes(ServerFieldTypeSet* supported_types) const { } base::string16 PhoneNumber::GetRawInfo(ServerFieldType type) const { - // TODO(isherman): Is GetStorableType even necessary? - if (AutofillType(type).GetStorableType() == PHONE_HOME_WHOLE_NUMBER) + DCHECK_EQ(PHONE_HOME, AutofillType(type).group()); + if (type == PHONE_HOME_WHOLE_NUMBER) return number_; // Only the whole number is available as raw data. All of the other types are @@ -86,8 +86,7 @@ base::string16 PhoneNumber::GetRawInfo(ServerFieldType type) const { void PhoneNumber::SetRawInfo(ServerFieldType type, const base::string16& value) { - // TODO(isherman): Is GetStorableType even necessary? - type = AutofillType(type).GetStorableType(); + DCHECK_EQ(PHONE_HOME, AutofillType(type).group()); if (type != PHONE_HOME_CITY_AND_NUMBER && type != PHONE_HOME_WHOLE_NUMBER) { // Only full phone numbers should be set directly. The remaining field // field types are read-only. diff --git a/chromium/components/autofill/core/browser/test_autofill_manager_delegate.cc b/chromium/components/autofill/core/browser/test_autofill_manager_delegate.cc index dc9781d11e0..b9f2c86f5c4 100644 --- a/chromium/components/autofill/core/browser/test_autofill_manager_delegate.cc +++ b/chromium/components/autofill/core/browser/test_autofill_manager_delegate.cc @@ -13,21 +13,12 @@ PersonalDataManager* TestAutofillManagerDelegate::GetPersonalDataManager() { return NULL; } -autocheckout::WhitelistManager* -TestAutofillManagerDelegate::GetAutocheckoutWhitelistManager() const { - return NULL; -} - PrefService* TestAutofillManagerDelegate::GetPrefs() { return NULL; } void TestAutofillManagerDelegate::HideRequestAutocompleteDialog() {} -void TestAutofillManagerDelegate::OnAutocheckoutError() {} - -void TestAutofillManagerDelegate::OnAutocheckoutSuccess() {} - void TestAutofillManagerDelegate::ShowAutofillSettings() {} void TestAutofillManagerDelegate::ConfirmSaveCreditCard( @@ -35,21 +26,10 @@ void TestAutofillManagerDelegate::ConfirmSaveCreditCard( const CreditCard& credit_card, const base::Closure& save_card_callback) {} -bool TestAutofillManagerDelegate::ShowAutocheckoutBubble( - const gfx::RectF& bounding_box, - bool is_google_user, - const base::Callback<void(AutocheckoutBubbleState)>& callback) { - return true; -} - -void TestAutofillManagerDelegate::HideAutocheckoutBubble() {} - void TestAutofillManagerDelegate::ShowRequestAutocompleteDialog( const FormData& form, const GURL& source_url, - DialogType dialog_type, - const base::Callback<void(const FormStructure*, - const std::string&)>& callback) {} + const base::Callback<void(const FormStructure*)>& callback) {} void TestAutofillManagerDelegate::ShowAutofillPopup( const gfx::RectF& element_bounds, @@ -66,15 +46,11 @@ void TestAutofillManagerDelegate::UpdateAutofillPopupDataListValues( void TestAutofillManagerDelegate::HideAutofillPopup() {} -void TestAutofillManagerDelegate::AddAutocheckoutStep( - AutocheckoutStepType step_type) {} - -void TestAutofillManagerDelegate::UpdateAutocheckoutStep( - AutocheckoutStepType step_type, - AutocheckoutStepStatus step_status) {} - bool TestAutofillManagerDelegate::IsAutocompleteEnabled() { return true; } +void TestAutofillManagerDelegate::DetectAccountCreationForms( + const std::vector<autofill::FormStructure*>& forms) {} + } // namespace autofill diff --git a/chromium/components/autofill/core/browser/test_autofill_manager_delegate.h b/chromium/components/autofill/core/browser/test_autofill_manager_delegate.h index 1fee0ba24a9..6c4681cad93 100644 --- a/chromium/components/autofill/core/browser/test_autofill_manager_delegate.h +++ b/chromium/components/autofill/core/browser/test_autofill_manager_delegate.h @@ -7,7 +7,6 @@ #include "base/compiler_specific.h" #include "base/i18n/rtl.h" -#include "components/autofill/core/browser/autocheckout_bubble_state.h" #include "components/autofill/core/browser/autofill_manager_delegate.h" namespace autofill { @@ -22,27 +21,16 @@ class TestAutofillManagerDelegate : public AutofillManagerDelegate { // AutofillManagerDelegate implementation. virtual PersonalDataManager* GetPersonalDataManager() OVERRIDE; virtual PrefService* GetPrefs() OVERRIDE; - virtual autocheckout::WhitelistManager* - GetAutocheckoutWhitelistManager() const OVERRIDE; virtual void HideRequestAutocompleteDialog() OVERRIDE; - virtual void OnAutocheckoutError() OVERRIDE; - virtual void OnAutocheckoutSuccess() OVERRIDE; virtual void ShowAutofillSettings() OVERRIDE; virtual void ConfirmSaveCreditCard( const AutofillMetrics& metric_logger, const CreditCard& credit_card, const base::Closure& save_card_callback) OVERRIDE; - virtual bool ShowAutocheckoutBubble( - const gfx::RectF& bounding_box, - bool is_google_user, - const base::Callback<void(AutocheckoutBubbleState)>& callback) OVERRIDE; - virtual void HideAutocheckoutBubble() OVERRIDE; virtual void ShowRequestAutocompleteDialog( const FormData& form, const GURL& source_url, - DialogType dialog_type, - const base::Callback<void(const FormStructure*, - const std::string&)>& callback) OVERRIDE; + const base::Callback<void(const FormStructure*)>& callback) OVERRIDE; virtual void ShowAutofillPopup( const gfx::RectF& element_bounds, base::i18n::TextDirection text_direction, @@ -57,10 +45,8 @@ class TestAutofillManagerDelegate : public AutofillManagerDelegate { virtual void HideAutofillPopup() OVERRIDE; virtual bool IsAutocompleteEnabled() OVERRIDE; - virtual void AddAutocheckoutStep(AutocheckoutStepType step_type) OVERRIDE; - virtual void UpdateAutocheckoutStep( - AutocheckoutStepType step_type, - AutocheckoutStepStatus step_status) OVERRIDE; + virtual void DetectAccountCreationForms( + const std::vector<autofill::FormStructure*>& forms) OVERRIDE; private: DISALLOW_COPY_AND_ASSIGN(TestAutofillManagerDelegate); diff --git a/chromium/components/autofill/core/browser/test_personal_data_manager.cc b/chromium/components/autofill/core/browser/test_personal_data_manager.cc index 0a517d7655b..7ce487bd24d 100644 --- a/chromium/components/autofill/core/browser/test_personal_data_manager.cc +++ b/chromium/components/autofill/core/browser/test_personal_data_manager.cc @@ -34,9 +34,16 @@ const std::vector<CreditCard*>& TestPersonalDataManager:: return credit_cards_; } -void TestPersonalDataManager::SaveImportedProfile( +std::string TestPersonalDataManager::SaveImportedProfile( const AutofillProfile& imported_profile) { imported_profile_ = imported_profile; + return imported_profile.guid(); +} + +std::string TestPersonalDataManager::SaveImportedCreditCard( + const CreditCard& imported_credit_card) { + imported_credit_card_ = imported_credit_card; + return imported_credit_card.guid(); } } // namespace autofill diff --git a/chromium/components/autofill/core/browser/test_personal_data_manager.h b/chromium/components/autofill/core/browser/test_personal_data_manager.h index c9f9a43d4a4..9d6767a6082 100644 --- a/chromium/components/autofill/core/browser/test_personal_data_manager.h +++ b/chromium/components/autofill/core/browser/test_personal_data_manager.h @@ -27,16 +27,21 @@ class TestPersonalDataManager : public PersonalDataManager { void AddTestingCreditCard(CreditCard* credit_card); virtual const std::vector<AutofillProfile*>& GetProfiles() OVERRIDE; - virtual void SaveImportedProfile(const AutofillProfile& imported_profile) - OVERRIDE; + virtual const std::vector<CreditCard*>& GetCreditCards() const OVERRIDE; + + virtual std::string SaveImportedProfile( + const AutofillProfile& imported_profile) OVERRIDE; + virtual std::string SaveImportedCreditCard( + const CreditCard& imported_credit_card) OVERRIDE; const AutofillProfile& imported_profile() { return imported_profile_; } - virtual const std::vector<CreditCard*>& GetCreditCards() const OVERRIDE; + const CreditCard& imported_credit_card() { return imported_credit_card_; } private: std::vector<AutofillProfile*> profiles_; std::vector<CreditCard*> credit_cards_; AutofillProfile imported_profile_; + CreditCard imported_credit_card_; }; } // namespace autofill diff --git a/chromium/components/autofill/core/browser/webdata/autofill_table.cc b/chromium/components/autofill/core/browser/webdata/autofill_table.cc index 9066d9c0df0..1f61bb3967e 100644 --- a/chromium/components/autofill/core/browser/webdata/autofill_table.cc +++ b/chromium/components/autofill/core/browser/webdata/autofill_table.cc @@ -2095,7 +2095,7 @@ bool AutofillTable::MigrateToVersion37MergeAndCullOlderProfiles() { if (PersonalDataManager::IsValidLearnableProfile(*profile, app_locale_)) { std::vector<AutofillProfile> merged_profiles; - bool merged = PersonalDataManager::MergeProfile( + std::string merged_guid = PersonalDataManager::MergeProfile( *profile, accumulated_profiles_p, app_locale_, &merged_profiles); std::swap(accumulated_profiles, merged_profiles); @@ -2108,9 +2108,8 @@ bool AutofillTable::MigrateToVersion37MergeAndCullOlderProfiles() { address_of<AutofillProfile>); // If the profile got merged trash the original. - if (merged) + if (merged_guid != profile->guid()) AddAutofillGUIDToTrash(profile->guid()); - } else { // An invalid profile, so trash it. AddAutofillGUIDToTrash(profile->guid()); diff --git a/chromium/components/autofill/core/common/autocheckout_status.h b/chromium/components/autofill/core/common/autocheckout_status.h deleted file mode 100644 index 81bc7005e80..00000000000 --- a/chromium/components/autofill/core/common/autocheckout_status.h +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef COMPONENTS_AUTOFILL_CORE_COMMON_AUTOCHECKOUT_STATUS_H_ -#define COMPONENTS_AUTOFILL_CORE_COMMON_AUTOCHECKOUT_STATUS_H_ - -namespace autofill { - -enum AutocheckoutStatus { - SUCCESS, - MISSING_FIELDMAPPING, - MISSING_ADVANCE, - MISSING_CLICK_ELEMENT_BEFORE_FORM_FILLING, - MISSING_CLICK_ELEMENT_AFTER_FORM_FILLING, - CANNOT_PROCEED, - AUTOCHECKOUT_STATUS_NUM_STATUS, -}; - -} // namespace autofill - -#endif // COMPONENTS_AUTOFILL_CORE_COMMON_AUTOCHECKOUT_STATUS_H_ diff --git a/chromium/components/autofill/core/common/autofill_message_generator.cc b/chromium/components/autofill/core/common/autofill_message_generator.cc index 48988146ab4..d55373b60e3 100644 --- a/chromium/components/autofill/core/common/autofill_message_generator.cc +++ b/chromium/components/autofill/core/common/autofill_message_generator.cc @@ -17,17 +17,23 @@ // Generate param traits write methods. #include "ipc/param_traits_write_macros.h" namespace IPC { +#undef COMPONENTS_AUTOFILL_CORE_COMMON_AUTOFILL_PARAM_TRAITS_MACROS_H_ #include "components/autofill/core/common/autofill_message_generator.h" +#include "components/autofill/core/common/autofill_param_traits_macros.h" } // namespace IPC // Generate param traits read methods. #include "ipc/param_traits_read_macros.h" namespace IPC { +#undef COMPONENTS_AUTOFILL_CORE_COMMON_AUTOFILL_PARAM_TRAITS_MACROS_H_ #include "components/autofill/core/common/autofill_message_generator.h" +#include "components/autofill/core/common/autofill_param_traits_macros.h" } // namespace IPC // Generate param traits log methods. #include "ipc/param_traits_log_macros.h" namespace IPC { +#undef COMPONENTS_AUTOFILL_CORE_COMMON_AUTOFILL_PARAM_TRAITS_MACROS_H_ #include "components/autofill/core/common/autofill_message_generator.h" +#include "components/autofill/core/common/autofill_param_traits_macros.h" } // namespace IPC diff --git a/chromium/components/autofill/core/common/autofill_messages.h b/chromium/components/autofill/core/common/autofill_messages.h index a432887af6f..006744fbc32 100644 --- a/chromium/components/autofill/core/common/autofill_messages.h +++ b/chromium/components/autofill/core/common/autofill_messages.h @@ -7,27 +7,24 @@ #include <string> #include "base/time/time.h" -#include "components/autofill/core/common/autocheckout_status.h" +#include "components/autofill/core/common/autofill_param_traits_macros.h" #include "components/autofill/core/common/form_data.h" #include "components/autofill/core/common/form_data_predictions.h" #include "components/autofill/core/common/form_field_data.h" #include "components/autofill/core/common/form_field_data_predictions.h" #include "components/autofill/core/common/forms_seen_state.h" +#include "components/autofill/core/common/password_form.h" #include "components/autofill/core/common/password_form_fill_data.h" #include "components/autofill/core/common/web_element_descriptor.h" #include "content/public/common/common_param_traits.h" #include "content/public/common/common_param_traits_macros.h" -#include "content/public/common/password_form.h" #include "ipc/ipc_message_macros.h" #include "ipc/ipc_message_utils.h" #include "third_party/WebKit/public/web/WebFormElement.h" #include "ui/gfx/rect.h" -#include "url/gurl.h" #define IPC_MESSAGE_START AutofillMsgStart -IPC_ENUM_TRAITS_MAX_VALUE(autofill::AutocheckoutStatus, - autofill::AUTOCHECKOUT_STATUS_NUM_STATUS - 1) IPC_ENUM_TRAITS_MAX_VALUE(autofill::FormsSeenState, autofill::FORMS_SEEN_STATE_NUM_STATES - 1) IPC_ENUM_TRAITS_MAX_VALUE(base::i18n::TextDirection, @@ -38,7 +35,8 @@ IPC_STRUCT_TRAITS_BEGIN(autofill::WebElementDescriptor) IPC_STRUCT_TRAITS_MEMBER(retrieval_method) IPC_STRUCT_TRAITS_END() -IPC_ENUM_TRAITS(autofill::WebElementDescriptor::RetrievalMethod) +IPC_ENUM_TRAITS_MAX_VALUE(autofill::WebElementDescriptor::RetrievalMethod, + autofill::WebElementDescriptor::NONE) IPC_STRUCT_TRAITS_BEGIN(autofill::FormFieldData) IPC_STRUCT_TRAITS_MEMBER(label) @@ -65,15 +63,6 @@ IPC_STRUCT_TRAITS_BEGIN(autofill::FormFieldDataPredictions) IPC_STRUCT_TRAITS_MEMBER(overall_type) IPC_STRUCT_TRAITS_END() -IPC_STRUCT_TRAITS_BEGIN(autofill::FormData) - IPC_STRUCT_TRAITS_MEMBER(name) - IPC_STRUCT_TRAITS_MEMBER(method) - IPC_STRUCT_TRAITS_MEMBER(origin) - IPC_STRUCT_TRAITS_MEMBER(action) - IPC_STRUCT_TRAITS_MEMBER(user_submitted) - IPC_STRUCT_TRAITS_MEMBER(fields) -IPC_STRUCT_TRAITS_END() - IPC_STRUCT_TRAITS_BEGIN(autofill::FormDataPredictions) IPC_STRUCT_TRAITS_MEMBER(data) IPC_STRUCT_TRAITS_MEMBER(signature) @@ -99,13 +88,12 @@ IPC_STRUCT_TRAITS_BEGIN(autofill::PasswordAndRealm) IPC_STRUCT_TRAITS_MEMBER(realm) IPC_STRUCT_TRAITS_END() -IPC_ENUM_TRAITS(WebKit::WebFormElement::AutocompleteResult) +IPC_ENUM_TRAITS_MAX_VALUE( + WebKit::WebFormElement::AutocompleteResult, + WebKit::WebFormElement::AutocompleteResultErrorInvalid) // Autofill messages sent from the browser to the renderer. -// Request to parse all the forms without field count limit. -IPC_MESSAGE_ROUTED0(AutofillMsg_GetAllForms) - // Reply to the AutofillHostMsg_FillAutofillFormData message with the // Autofill form data. IPC_MESSAGE_ROUTED2(AutofillMsg_FormDataFilled, @@ -159,7 +147,7 @@ IPC_MESSAGE_ROUTED1(AutofillMsg_AcceptPasswordAutofillSuggestion, // Tells the renderer that this password form is not blacklisted. A form can // be blacklisted if a user chooses "never save passwords for this site". IPC_MESSAGE_ROUTED1(AutofillMsg_FormNotBlacklisted, - content::PasswordForm /* form checked */) + autofill::PasswordForm /* form checked */) // Sent when requestAutocomplete() finishes (either succesfully or with an // error). If it was a success, the renderer fills the form that requested @@ -168,25 +156,15 @@ IPC_MESSAGE_ROUTED2(AutofillMsg_RequestAutocompleteResult, WebKit::WebFormElement::AutocompleteResult /* result */, autofill::FormData /* form_data */) -// Sent when a page should be filled using Autocheckout. This happens when the -// Autofill server hints that a page is Autocheckout enabled. -IPC_MESSAGE_ROUTED4(AutofillMsg_FillFormsAndClick, - std::vector<autofill::FormData> /* form_data */, - std::vector<autofill::WebElementDescriptor> /* - click_elements_before_form_fill */, - std::vector<autofill::WebElementDescriptor> /* - click_elements_after_form_fill */, - autofill::WebElementDescriptor /* element_descriptor */) - -// Sent when Autocheckout is supported for the current page. The page has to -// be whitelisted and the Autofill server must have returned Autocheckout page -// metadata. -IPC_MESSAGE_ROUTED0(AutofillMsg_AutocheckoutSupported) - // Sent when the current page is actually displayed in the browser, possibly // after being preloaded. IPC_MESSAGE_ROUTED0(AutofillMsg_PageShown) +// Sent when Autofill manager gets the query response from the Autofill server +// and there are fields classified as ACCOUNT_CREATION_PASSWORD in the response. +IPC_MESSAGE_ROUTED1(AutofillMsg_AccountCreationFormsDetected, + std::vector<autofill::FormData> /* forms */) + // Autofill messages sent from the renderer to the browser. // TODO(creis): check in the browser that the renderer actually has permission @@ -202,12 +180,16 @@ IPC_MESSAGE_ROUTED3(AutofillHostMsg_FormsSeen, // Notification that password forms have been seen that are candidates for // filling/submitting by the password manager. IPC_MESSAGE_ROUTED1(AutofillHostMsg_PasswordFormsParsed, - std::vector<content::PasswordForm> /* forms */) + std::vector<autofill::PasswordForm> /* forms */) // Notification that initial layout has occurred and the following password // forms are visible on the page (e.g. not set to display:none.) IPC_MESSAGE_ROUTED1(AutofillHostMsg_PasswordFormsRendered, - std::vector<content::PasswordForm> /* forms */) + std::vector<autofill::PasswordForm> /* forms */) + +// Notification that this password form was submitted by the user. +IPC_MESSAGE_ROUTED1(AutofillHostMsg_PasswordFormSubmitted, + autofill::PasswordForm /* form */) // Notification that a form has been submitted. The user hit the button. IPC_MESSAGE_ROUTED2(AutofillHostMsg_FormSubmitted, @@ -220,11 +202,6 @@ IPC_MESSAGE_ROUTED3(AutofillHostMsg_TextFieldDidChange, autofill::FormFieldData /* the form field */, base::TimeTicks /* timestamp */) -// Shows the Autocheckout bubble if the conditions are right. -IPC_MESSAGE_ROUTED2(AutofillHostMsg_MaybeShowAutocheckoutBubble, - autofill::FormData /* form */, - gfx::RectF /* bounding_box */) - // Queries the browser for Autofill suggestions for a form input field. IPC_MESSAGE_ROUTED5(AutofillHostMsg_QueryFormFieldAutofill, int /* id of this message */, @@ -262,18 +239,13 @@ IPC_MESSAGE_ROUTED0(AutofillHostMsg_DidEndTextFieldEditing) // Instructs the browser to hide the Autofill UI. IPC_MESSAGE_ROUTED0(AutofillHostMsg_HideAutofillUI) -// Sent when the renderer filled an Autocheckout page and clicked the proceed -// button or if there was an error. -IPC_MESSAGE_ROUTED1(AutofillHostMsg_AutocheckoutPageCompleted, - autofill::AutocheckoutStatus /* status */) - // Instructs the browser to show the password generation bubble at the // specified location. This location should be specified in the renderers // coordinate system. Form is the form associated with the password field. IPC_MESSAGE_ROUTED3(AutofillHostMsg_ShowPasswordGenerationPopup, gfx::Rect /* source location */, int /* max length of the password */, - content::PasswordForm) + autofill::PasswordForm) // Instruct the browser that a password mapping has been found for a field. IPC_MESSAGE_ROUTED2(AutofillHostMsg_AddPasswordFormMapping, diff --git a/chromium/components/autofill/core/common/autofill_param_traits_macros.h b/chromium/components/autofill/core/common/autofill_param_traits_macros.h new file mode 100644 index 00000000000..91d357a1e1f --- /dev/null +++ b/chromium/components/autofill/core/common/autofill_param_traits_macros.h @@ -0,0 +1,46 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Singly or multiply-included shared traits file depending on circumstances. +// This allows the use of Autofill IPC serialization macros in more than one IPC +// message file. +#ifndef COMPONENTS_AUTOFILL_CORE_COMMON_AUTOFILL_PARAM_TRAITS_MACROS_H_ +#define COMPONENTS_AUTOFILL_CORE_COMMON_AUTOFILL_PARAM_TRAITS_MACROS_H_ + +#include "components/autofill/core/common/password_form.h" +#include "ipc/ipc_message_macros.h" + +IPC_ENUM_TRAITS(autofill::PasswordForm::Type) + +IPC_STRUCT_TRAITS_BEGIN(autofill::FormData) + IPC_STRUCT_TRAITS_MEMBER(name) + IPC_STRUCT_TRAITS_MEMBER(method) + IPC_STRUCT_TRAITS_MEMBER(origin) + IPC_STRUCT_TRAITS_MEMBER(action) + IPC_STRUCT_TRAITS_MEMBER(user_submitted) + IPC_STRUCT_TRAITS_MEMBER(fields) +IPC_STRUCT_TRAITS_END() + +IPC_STRUCT_TRAITS_BEGIN(autofill::PasswordForm) + IPC_STRUCT_TRAITS_MEMBER(signon_realm) + IPC_STRUCT_TRAITS_MEMBER(origin) + IPC_STRUCT_TRAITS_MEMBER(action) + IPC_STRUCT_TRAITS_MEMBER(submit_element) + IPC_STRUCT_TRAITS_MEMBER(username_element) + IPC_STRUCT_TRAITS_MEMBER(username_value) + IPC_STRUCT_TRAITS_MEMBER(other_possible_usernames) + IPC_STRUCT_TRAITS_MEMBER(password_element) + IPC_STRUCT_TRAITS_MEMBER(password_value) + IPC_STRUCT_TRAITS_MEMBER(password_autocomplete_set) + IPC_STRUCT_TRAITS_MEMBER(old_password_element) + IPC_STRUCT_TRAITS_MEMBER(old_password_value) + IPC_STRUCT_TRAITS_MEMBER(ssl_valid) + IPC_STRUCT_TRAITS_MEMBER(preferred) + IPC_STRUCT_TRAITS_MEMBER(blacklisted_by_user) + IPC_STRUCT_TRAITS_MEMBER(type) + IPC_STRUCT_TRAITS_MEMBER(times_used) + IPC_STRUCT_TRAITS_MEMBER(form_data) +IPC_STRUCT_TRAITS_END() + +#endif // COMPONENTS_AUTOFILL_CORE_COMMON_AUTOFILL_PARAM_TRAITS_MACROS_H_ diff --git a/chromium/components/autofill/core/common/autofill_switches.cc b/chromium/components/autofill/core/common/autofill_switches.cc index a41a9c7b96a..c752cc7d468 100644 --- a/chromium/components/autofill/core/common/autofill_switches.cc +++ b/chromium/components/autofill/core/common/autofill_switches.cc @@ -7,15 +7,9 @@ namespace autofill { namespace switches { -// Flag used to tell Chrome the Autochecout whitelist url. -const char kAutocheckoutWhitelistUrl[] = "autocheckout-whitelist-url"; - // Flag used to tell Chrome the base url of the Autofill service. const char kAutofillServiceUrl[] = "autofill-service-url"; -// Bypass autocheckout whitelist check, so all sites are enabled. -const char kBypassAutocheckoutWhitelist[] = "bypass-autocheckout-whitelist"; - // Disables an interactive autocomplete UI. See kEnableInteractiveAutocomplete // for a description. const char kDisableInteractiveAutocomplete[] = @@ -41,9 +35,8 @@ const char kWalletSecureServiceUrl[] = "wallet-secure-service-url"; // API calls. const char kWalletServiceUrl[] = "wallet-service-url"; -// Enable production Online Wallet service. If this flag is not set, the sandbox -// service will be used. -const char kWalletServiceUseProd[] = "wallet-service-use-prod"; +// Use the sandbox Online Wallet service URL (for developer testing). +const char kWalletServiceUseSandbox[] = "wallet-service-use-sandbox"; } // namespace switches } // namespace autofill diff --git a/chromium/components/autofill/core/common/autofill_switches.h b/chromium/components/autofill/core/common/autofill_switches.h index 84d76d59c59..7061bb61172 100644 --- a/chromium/components/autofill/core/common/autofill_switches.h +++ b/chromium/components/autofill/core/common/autofill_switches.h @@ -10,16 +10,14 @@ namespace switches { // All switches in alphabetical order. The switches should be documented // alongside the definition of their values in the .cc file. -extern const char kAutocheckoutWhitelistUrl[]; extern const char kAutofillServiceUrl[]; -extern const char kBypassAutocheckoutWhitelist[]; extern const char kDisableInteractiveAutocomplete[]; extern const char kEnableExperimentalFormFilling[]; extern const char kEnableInteractiveAutocomplete[]; extern const char kShowAutofillTypePredictions[]; extern const char kWalletSecureServiceUrl[]; extern const char kWalletServiceUrl[]; -extern const char kWalletServiceUseProd[]; +extern const char kWalletServiceUseSandbox[]; } // namespace switches } // namespace autofill diff --git a/chromium/components/autofill/core/common/form_data.cc b/chromium/components/autofill/core/common/form_data.cc index e0c3c1c4636..050e2e57582 100644 --- a/chromium/components/autofill/core/common/form_data.cc +++ b/chromium/components/autofill/core/common/form_data.cc @@ -4,10 +4,52 @@ #include "components/autofill/core/common/form_data.h" +#include "base/pickle.h" #include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "components/autofill/core/common/form_field_data.h" namespace autofill { +namespace { + +const int kPickleVersion = 1; + +bool ReadGURL(PickleIterator* iter, GURL* url) { + std::string spec; + if (!iter->ReadString(&spec)) + return false; + + *url = GURL(spec); + return true; +} + +void SerializeFormFieldDataVector(const std::vector<FormFieldData> fields, + Pickle* pickle) { + pickle->WriteInt(static_cast<int>(fields.size())); + for (size_t i = 0; i < fields.size(); ++i) { + SerializeFormFieldData(fields[i], pickle); + } +} + +bool DeserializeFormFieldDataVector(PickleIterator* iter, + std::vector<FormFieldData>* fields) { + int size; + if (!iter->ReadInt(&size)) + return false; + + FormFieldData temp; + for (int i = 0; i < size; ++i) { + if (!DeserializeFormFieldData(iter, &temp)) + return false; + + fields->push_back(temp); + } + return true; +} + +} // namespace + FormData::FormData() : user_submitted(false) { } @@ -37,4 +79,55 @@ bool FormData::operator!=(const FormData& form) const { return !operator==(form); } +std::ostream& operator<<(std::ostream& os, const FormData& form) { + os << UTF16ToUTF8(form.name) << " " + << UTF16ToUTF8(form.method) << " " + << form.origin << " " + << form.action << " " + << form.user_submitted << " " + << "Fields:"; + for (size_t i = 0; i < form.fields.size(); ++i) { + os << form.fields[i] << ","; + } + return os; +} + +void SerializeFormData(const FormData& form_data, Pickle* pickle) { + pickle->WriteInt(kPickleVersion); + pickle->WriteString16(form_data.name); + pickle->WriteString16(form_data.method); + pickle->WriteString(form_data.origin.spec()); + pickle->WriteString(form_data.action.spec()); + pickle->WriteBool(form_data.user_submitted); + SerializeFormFieldDataVector(form_data.fields, pickle); +} + +bool DeserializeFormData(PickleIterator* iter, FormData* form_data) { + int version; + if (!iter->ReadInt(&version)) { + LOG(ERROR) << "Bad pickle of FormData, no version present"; + return false; + } + + switch (version) { + case 1: { + if (!iter->ReadString16(&form_data->name) || + !iter->ReadString16(&form_data->method) || + !ReadGURL(iter, &form_data->origin) || + !ReadGURL(iter, &form_data->action) || + !iter->ReadBool(&form_data->user_submitted) || + !DeserializeFormFieldDataVector(iter, &form_data->fields)) { + LOG(ERROR) << "Could not deserialize FormData from pickle"; + return false; + } + break; + } + default: { + LOG(ERROR) << "Unknown FormData pickle version " << version; + return false; + } + } + return true; +} + } // namespace autofill diff --git a/chromium/components/autofill/core/common/form_data.h b/chromium/components/autofill/core/common/form_data.h index 202fb8d3f95..b41619c5390 100644 --- a/chromium/components/autofill/core/common/form_data.h +++ b/chromium/components/autofill/core/common/form_data.h @@ -19,7 +19,7 @@ struct FormData { FormData(const FormData& data); ~FormData(); - // Used by FormStructureTest. + // Used in testing. bool operator==(const FormData& form) const; bool operator!=(const FormData& form) const; @@ -37,6 +37,16 @@ struct FormData { std::vector<FormFieldData> fields; }; +// For testing. +std::ostream& operator<<(std::ostream& os, const FormData& form); + +// Serialize FormData. Used by the PasswordManager to persist FormData +// pertaining to password forms. Serialized data is appended to |pickle| +void SerializeFormData(const FormData& form_data, Pickle* pickle); +// Deserialize FormData. This assumes that |iter| is currently pointing to +// the part of a pickle created by SerializeFormData. Returns true on success. +bool DeserializeFormData(PickleIterator* iter, FormData* form_data); + } // namespace autofill #endif // COMPONENTS_AUTOFILL_CORE_COMMON_FORM_DATA_H__ diff --git a/chromium/components/autofill/core/common/form_data_unittest.cc b/chromium/components/autofill/core/common/form_data_unittest.cc new file mode 100644 index 00000000000..2d4b7aacb24 --- /dev/null +++ b/chromium/components/autofill/core/common/form_data_unittest.cc @@ -0,0 +1,57 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/autofill/core/common/form_data.h" + +#include "base/pickle.h" +#include "base/strings/utf_string_conversions.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace autofill { + +TEST(FormDataTest, SerializeAndDeserialize) { + FormData data; + data.name = ASCIIToUTF16("name"); + data.method = ASCIIToUTF16("POST"); + data.origin = GURL("origin"); + data.action = GURL("action"); + data.user_submitted = true; + + FormFieldData field_data; + field_data.label = ASCIIToUTF16("label"); + field_data.name = ASCIIToUTF16("name"); + field_data.value = ASCIIToUTF16("value"); + field_data.form_control_type = "password"; + field_data.autocomplete_attribute = "off"; + field_data.max_length = 200; + field_data.is_autofilled = true; + field_data.is_checked = true; + field_data.is_checkable = true; + field_data.is_focusable = true; + field_data.should_autocomplete = false; + field_data.text_direction = base::i18n::RIGHT_TO_LEFT; + field_data.option_values.push_back(ASCIIToUTF16("First")); + field_data.option_values.push_back(ASCIIToUTF16("Second")); + field_data.option_contents.push_back(ASCIIToUTF16("First")); + field_data.option_contents.push_back(ASCIIToUTF16("Second")); + + data.fields.push_back(field_data); + + // Change a few fields. + field_data.max_length = 150; + field_data.option_values.push_back(ASCIIToUTF16("Third")); + field_data.option_contents.push_back(ASCIIToUTF16("Third")); + data.fields.push_back(field_data); + + Pickle pickle; + SerializeFormData(data, &pickle); + + PickleIterator iter(pickle); + FormData actual; + EXPECT_TRUE(DeserializeFormData(&iter, &actual)); + + EXPECT_EQ(actual, data); +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/common/form_field_data.cc b/chromium/components/autofill/core/common/form_field_data.cc index 1de786dcf3c..afa9f53e0bc 100644 --- a/chromium/components/autofill/core/common/form_field_data.cc +++ b/chromium/components/autofill/core/common/form_field_data.cc @@ -4,9 +4,59 @@ #include "components/autofill/core/common/form_field_data.h" +#include "base/pickle.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" +namespace { + +const int kPickleVersion = 1; + +void AddVectorToPickle(std::vector<base::string16> strings, + Pickle* pickle) { + pickle->WriteInt(static_cast<int>(strings.size())); + for (size_t i = 0; i < strings.size(); ++i) { + pickle->WriteString16(strings[i]); + } +} + +bool ReadStringVector(PickleIterator* iter, + std::vector<base::string16>* strings) { + int size; + if (!iter->ReadInt(&size)) + return false; + + string16 pickle_data; + for (int i = 0; i < size; i++) { + if (!iter->ReadString16(&pickle_data)) + return false; + + strings->push_back(pickle_data); + } + return true; +} + +bool ReadTextDirection(PickleIterator* iter, + base::i18n::TextDirection* direction) { + int pickle_data; + if (!iter->ReadInt(&pickle_data)) + return false; + + *direction = static_cast<base::i18n::TextDirection>(pickle_data); + return true; +} + +bool ReadSize(PickleIterator* iter, size_t* size) { + uint64 pickle_data; + if (!iter->ReadUInt64(&pickle_data)) + return false; + + *size = static_cast<size_t>(pickle_data); + return true; +} + +} // namespace + namespace autofill { FormFieldData::FormFieldData() @@ -43,6 +93,62 @@ bool FormFieldData::operator<(const FormFieldData& field) const { return label < field.label; } +void SerializeFormFieldData(const FormFieldData& field_data, + Pickle* pickle) { + pickle->WriteInt(kPickleVersion); + pickle->WriteString16(field_data.label); + pickle->WriteString16(field_data.name); + pickle->WriteString16(field_data.value); + pickle->WriteString(field_data.form_control_type); + pickle->WriteString(field_data.autocomplete_attribute); + pickle->WriteUInt64(static_cast<uint64>(field_data.max_length)); + pickle->WriteBool(field_data.is_autofilled); + pickle->WriteBool(field_data.is_checked); + pickle->WriteBool(field_data.is_checkable); + pickle->WriteBool(field_data.is_focusable); + pickle->WriteBool(field_data.should_autocomplete); + pickle->WriteInt(field_data.text_direction); + AddVectorToPickle(field_data.option_values, pickle); + AddVectorToPickle(field_data.option_contents, pickle); +} + +bool DeserializeFormFieldData(PickleIterator* iter, + FormFieldData* field_data) { + int version; + if (!iter->ReadInt(&version)) { + LOG(ERROR) << "Bad pickle of FormFieldData, no version present"; + return false; + } + + switch (version) { + case 1: { + if (!iter->ReadString16(&field_data->label) || + !iter->ReadString16(&field_data->name) || + !iter->ReadString16(&field_data->value) || + !iter->ReadString(&field_data->form_control_type) || + !iter->ReadString(&field_data->autocomplete_attribute) || + !ReadSize(iter, &field_data->max_length) || + !iter->ReadBool(&field_data->is_autofilled) || + !iter->ReadBool(&field_data->is_checked) || + !iter->ReadBool(&field_data->is_checkable) || + !iter->ReadBool(&field_data->is_focusable) || + !iter->ReadBool(&field_data->should_autocomplete) || + !ReadTextDirection(iter, &field_data->text_direction) || + !ReadStringVector(iter, &field_data->option_values) || + !ReadStringVector(iter, &field_data->option_contents)) { + LOG(ERROR) << "Could not deserialize FormFieldData from pickle"; + return false; + } + break; + } + default: { + LOG(ERROR) << "Unknown FormFieldData pickle version " << version; + return false; + } + } + return true; +} + std::ostream& operator<<(std::ostream& os, const FormFieldData& field) { return os << UTF16ToUTF8(field.label) diff --git a/chromium/components/autofill/core/common/form_field_data.h b/chromium/components/autofill/core/common/form_field_data.h index f0e7579cd0a..1bf163e95f2 100644 --- a/chromium/components/autofill/core/common/form_field_data.h +++ b/chromium/components/autofill/core/common/form_field_data.h @@ -10,6 +10,9 @@ #include "base/i18n/rtl.h" #include "base/strings/string16.h" +class Pickle; +class PickleIterator; + namespace autofill { // Stores information about a field in a form. @@ -45,6 +48,13 @@ struct FormFieldData { std::vector<base::string16> option_contents; }; +// Serialize and deserialize FormFieldData. These are used when FormData objects +// are serialized and deserialized. +void SerializeFormFieldData(const FormFieldData& form_field_data, + Pickle* serialized); +bool DeserializeFormFieldData(PickleIterator* pickle_iterator, + FormFieldData* form_field_data); + // So we can compare FormFieldDatas with EXPECT_EQ(). std::ostream& operator<<(std::ostream& os, const FormFieldData& field); @@ -66,4 +76,3 @@ std::ostream& operator<<(std::ostream& os, const FormFieldData& field); } // namespace autofill #endif // COMPONENTS_AUTOFILL_CORE_COMMON_FORM_FIELD_DATA_H_ - diff --git a/chromium/components/autofill/core/common/form_field_data_unittest.cc b/chromium/components/autofill/core/common/form_field_data_unittest.cc new file mode 100644 index 00000000000..7f4ef09f2d2 --- /dev/null +++ b/chromium/components/autofill/core/common/form_field_data_unittest.cc @@ -0,0 +1,43 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/autofill/core/common/form_field_data.h" + +#include "base/i18n/rtl.h" +#include "base/pickle.h" +#include "base/strings/utf_string_conversions.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace autofill { + +TEST(FormFieldDataTest, SerializeAndDeserialize) { + FormFieldData data; + data.label = ASCIIToUTF16("label"); + data.name = ASCIIToUTF16("name"); + data.value = ASCIIToUTF16("value"); + data.form_control_type = "password"; + data.autocomplete_attribute = "off"; + data.max_length = 200; + data.is_autofilled = true; + data.is_checked = true; + data.is_checkable = true; + data.is_focusable = true; + data.should_autocomplete = false; + data.text_direction = base::i18n::RIGHT_TO_LEFT; + data.option_values.push_back(ASCIIToUTF16("First")); + data.option_values.push_back(ASCIIToUTF16("Second")); + data.option_contents.push_back(ASCIIToUTF16("First")); + data.option_contents.push_back(ASCIIToUTF16("Second")); + + Pickle pickle; + SerializeFormFieldData(data, &pickle); + + PickleIterator iter(pickle); + FormFieldData actual; + EXPECT_TRUE(DeserializeFormFieldData(&iter, &actual)); + + EXPECT_EQ(actual, data); +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/common/password_form.cc b/chromium/components/autofill/core/common/password_form.cc new file mode 100644 index 00000000000..dcbc874ac42 --- /dev/null +++ b/chromium/components/autofill/core/common/password_form.cc @@ -0,0 +1,79 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <ostream> + +#include "base/strings/string16.h" +#include "base/strings/utf_string_conversions.h" +#include "components/autofill/core/common/password_form.h" + +namespace autofill { + +PasswordForm::PasswordForm() + : scheme(SCHEME_HTML), + password_autocomplete_set(true), + ssl_valid(false), + preferred(false), + blacklisted_by_user(false), + type(TYPE_MANUAL), + times_used(0) { +} + +PasswordForm::~PasswordForm() { +} + +bool PasswordForm::IsPublicSuffixMatch() const { + return !original_signon_realm.empty(); +} + +bool PasswordForm::operator==(const PasswordForm& form) const { + return signon_realm == form.signon_realm && + origin == form.origin && + action == form.action && + submit_element == form.submit_element && + username_element == form.username_element && + username_value == form.username_value && + other_possible_usernames == form.other_possible_usernames && + password_element == form.password_element && + password_value == form.password_value && + password_autocomplete_set == form.password_autocomplete_set && + old_password_element == form.old_password_element && + old_password_value == form.old_password_value && + ssl_valid == form.ssl_valid && + preferred == form.preferred && + date_created == form.date_created && + blacklisted_by_user == form.blacklisted_by_user && + type == form.type && + times_used == form.times_used && + form_data == form.form_data; +} + +bool PasswordForm::operator!=(const PasswordForm& form) const { + return !operator==(form); +} + +std::ostream& operator<<(std::ostream& os, const PasswordForm& form) { + return os << "scheme: " << form.scheme + << " signon_realm: " << form.signon_realm + << " origin: " << form.origin + << " action: " << form.action + << " submit_element: " << UTF16ToUTF8(form.submit_element) + << " username_elem: " << UTF16ToUTF8(form.username_element) + << " username_value: " << UTF16ToUTF8(form.username_value) + << " password_elem: " << UTF16ToUTF8(form.password_element) + << " password_value: " << UTF16ToUTF8(form.password_value) + << " old_password_element: " + << UTF16ToUTF8(form.old_password_element) + << " old_password_value: " << UTF16ToUTF8(form.old_password_value) + << " autocomplete_set:" << form.password_autocomplete_set + << " blacklisted: " << form.blacklisted_by_user + << " preferred: " << form.preferred + << " ssl_valid: " << form.ssl_valid + << " date_created: " << form.date_created.ToDoubleT() + << " type: " << form.type + << " times_used: " << form.times_used + << " form_data: " << form.form_data; +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/common/password_form.h b/chromium/components/autofill/core/common/password_form.h new file mode 100644 index 00000000000..8485225a35f --- /dev/null +++ b/chromium/components/autofill/core/common/password_form.h @@ -0,0 +1,211 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_AUTOFILL_CORE_COMMON_PASSWORD_FORM_H__ +#define COMPONENTS_AUTOFILL_CORE_COMMON_PASSWORD_FORM_H__ + +#include <map> +#include <string> +#include <vector> + +#include "base/time/time.h" +#include "components/autofill/core/common/form_data.h" +#include "url/gurl.h" + +namespace autofill { + +// The PasswordForm struct encapsulates information about a login form, +// which can be an HTML form or a dialog with username/password text fields. +// +// The Web Data database stores saved username/passwords and associated form +// metdata using a PasswordForm struct, typically one that was created from +// a parsed HTMLFormElement or LoginDialog, but the saved entries could have +// also been created by imported data from another browser. +// +// The PasswordManager implements a fuzzy-matching algorithm to compare saved +// PasswordForm entries against PasswordForms that were created from a parsed +// HTML or dialog form. As one might expect, the more data contained in one +// of the saved PasswordForms, the better the job the PasswordManager can do +// in matching it against the actual form it was saved on, and autofill +// accurately. But it is not always possible, especially when importing from +// other browsers with different data models, to copy over all the information +// about a particular "saved password entry" to our PasswordForm +// representation. +// +// The field descriptions in the struct specification below are intended to +// describe which fields are not strictly required when adding a saved password +// entry to the database and how they can affect the matching process. + +struct PasswordForm { + // Enum to differentiate between HTML form based authentication, and dialogs + // using basic or digest schemes. Default is SCHEME_HTML. Only PasswordForms + // of the same Scheme will be matched/autofilled against each other. + enum Scheme { + SCHEME_HTML, + SCHEME_BASIC, + SCHEME_DIGEST, + SCHEME_OTHER + } scheme; + + // The "Realm" for the sign-on (scheme, host, port for SCHEME_HTML, and + // contains the HTTP realm for dialog-based forms). + // The signon_realm is effectively the primary key used for retrieving + // data from the database, so it must not be empty. + std::string signon_realm; + + // The original "Realm" for the sign-on (scheme, host, port for SCHEME_HTML, + // and contains the HTTP realm for dialog-based forms). This realm is only set + // when two PasswordForms are matched when trying to find a login/pass pair + // for a site. It is only set to a non-empty value during a match of the + // original stored login/pass and the current observed form if all these + // statements are true: + // 1) The full signon_realm is not the same. + // 2) The registry controlled domain is the same. For example; example.com, + // m.example.com, foo.login.example.com and www.example.com would all resolve + // to example.com since .com is the public suffix. + // 3) The scheme is the same. + // 4) The port is the same. + // For example, if there exists a stored password for http://www.example.com + // (where .com is the public suffix) and the observed form is + // http://m.example.com, |original_signon_realm| must be set to + // http://www.example.com. + std::string original_signon_realm; + + // The URL (minus query parameters) containing the form. This is the primary + // data used by the PasswordManager to decide (in longest matching prefix + // fashion) whether or not a given PasswordForm result from the database is a + // good fit for a particular form on a page, so it must not be empty. + GURL origin; + + // The action target of the form. This is the primary data used by the + // PasswordManager for form autofill; that is, the action of the saved + // credentials must match the action of the form on the page to be autofilled. + // If this is empty / not available, it will result in a "restricted" + // IE-like autofill policy, where we wait for the user to type in his + // username before autofilling the password. In these cases, after successful + // login the action URL will automatically be assigned by the + // PasswordManager. + // + // When parsing an HTML form, this must always be set. + GURL action; + + // The name of the submit button used. Optional; only used in scoring + // of PasswordForm results from the database to make matches as tight as + // possible. + // + // When parsing an HTML form, this must always be set. + string16 submit_element; + + // The name of the username input element. Optional (improves scoring). + // + // When parsing an HTML form, this must always be set. + string16 username_element; + + // The username. Optional. + // + // When parsing an HTML form, this is typically empty unless the site + // has implemented some form of autofill. + string16 username_value; + + // This member is populated in cases where we there are multiple input + // elements that could possibly be the username. Used when our heuristics for + // determining the username are incorrect. Optional. + // + // When parsing an HTML form, this is typically empty. + std::vector<string16> other_possible_usernames; + + // The name of the password input element, Optional (improves scoring). + // + // When parsing an HTML form, this must always be set. + string16 password_element; + + // The password. Required. + // + // When parsing an HTML form, this is typically empty. + string16 password_value; + + // False if autocomplete is set to "off" for the password input element; + // True otherwise. + bool password_autocomplete_set; + + // If the form was a change password form, the name of the + // 'old password' input element. Optional. + string16 old_password_element; + + // The old password. Optional. + string16 old_password_value; + + // Whether or not this login was saved under an HTTPS session with a valid + // SSL cert. We will never match or autofill a PasswordForm where + // ssl_valid == true with a PasswordForm where ssl_valid == false. This means + // passwords saved under HTTPS will never get autofilled onto an HTTP page. + // When importing, this should be set to true if the page URL is HTTPS, thus + // giving it "the benefit of the doubt" that the SSL cert was valid when it + // was saved. Default to false. + bool ssl_valid; + + // True if this PasswordForm represents the last username/password login the + // user selected to log in to the site. If there is only one saved entry for + // the site, this will always be true, but when there are multiple entries + // the PasswordManager ensures that only one of them has a preferred bit set + // to true. Default to false. + // + // When parsing an HTML form, this is not used. + bool preferred; + + // When the login was saved (by chrome). + // + // When parsing an HTML form, this is not used. + base::Time date_created; + + // Tracks if the user opted to never remember passwords for this form. Default + // to false. + // + // When parsing an HTML form, this is not used. + bool blacklisted_by_user; + + // Enum to differentiate between manually filled forms and forms with auto + // generated passwords. + enum Type { + TYPE_MANUAL, + TYPE_GENERATED, + }; + + // The form type. Not used yet. Please see http://crbug.com/152422 + Type type; + + // The number of times that this username/password has been used to + // authenticate the user. + // + // When parsing an HTML form, this is not used. + int times_used; + + // Autofill representation of this form. Used to communicate with the + // Autofill servers if necessary. Currently this is only used to help + // determine forms where we can trigger password generation. + // + // When parsing an HTML form, this is normally set. + FormData form_data; + + // Returns true if this match was found using public suffix matching. + bool IsPublicSuffixMatch() const; + + // Equality operators for testing. + bool operator==(const PasswordForm& form) const; + bool operator!=(const PasswordForm& form) const; + + PasswordForm(); + ~PasswordForm(); +}; + +// Map username to PasswordForm* for convenience. See password_form_manager.h. +typedef std::map<string16, PasswordForm*> PasswordFormMap; + +// For testing. +std::ostream& operator<<(std::ostream& os, + const autofill::PasswordForm& form); + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CORE_COMMON_PASSWORD_FORM_H__ diff --git a/chromium/components/autofill/core/common/password_form_fill_data.cc b/chromium/components/autofill/core/common/password_form_fill_data.cc index 80b26989cce..7dbc97d0ab6 100644 --- a/chromium/components/autofill/core/common/password_form_fill_data.cc +++ b/chromium/components/autofill/core/common/password_form_fill_data.cc @@ -30,9 +30,9 @@ PasswordFormFillData::~PasswordFormFillData() { } void InitPasswordFormFillData( - const content::PasswordForm& form_on_page, - const content::PasswordFormMap& matches, - const content::PasswordForm* const preferred_match, + const PasswordForm& form_on_page, + const PasswordFormMap& matches, + const PasswordForm* const preferred_match, bool wait_for_username_before_autofill, bool enable_other_possible_usernames, PasswordFormFillData* result) { @@ -57,7 +57,7 @@ void InitPasswordFormFillData( result->preferred_realm = preferred_match->original_signon_realm; // Copy additional username/value pairs. - content::PasswordFormMap::const_iterator iter; + PasswordFormMap::const_iterator iter; for (iter = matches.begin(); iter != matches.end(); iter++) { if (iter->second != preferred_match) { PasswordAndRealm value; diff --git a/chromium/components/autofill/core/common/password_form_fill_data.h b/chromium/components/autofill/core/common/password_form_fill_data.h index 9bcd5388d7a..14c4759865b 100644 --- a/chromium/components/autofill/core/common/password_form_fill_data.h +++ b/chromium/components/autofill/core/common/password_form_fill_data.h @@ -9,7 +9,7 @@ #include "base/memory/scoped_ptr.h" #include "components/autofill/core/common/form_data.h" -#include "content/public/common/password_form.h" +#include "components/autofill/core/common/password_form.h" namespace autofill { @@ -75,9 +75,9 @@ struct PasswordFormFillData { // If |enable_possible_usernames| is true, we will populate possible_usernames // in |result|. void InitPasswordFormFillData( - const content::PasswordForm& form_on_page, - const content::PasswordFormMap& matches, - const content::PasswordForm* const preferred_match, + const PasswordForm& form_on_page, + const PasswordFormMap& matches, + const PasswordForm* const preferred_match, bool wait_for_username_before_autofill, bool enable_other_possible_usernames, PasswordFormFillData* result); diff --git a/chromium/components/autofill/core/common/password_form_fill_data_unittest.cc b/chromium/components/autofill/core/common/password_form_fill_data_unittest.cc index 1242774b12a..7c30f81f3e1 100644 --- a/chromium/components/autofill/core/common/password_form_fill_data_unittest.cc +++ b/chromium/components/autofill/core/common/password_form_fill_data_unittest.cc @@ -5,13 +5,10 @@ #include "components/autofill/core/common/password_form_fill_data.h" #include "base/strings/utf_string_conversions.h" -#include "content/public/common/password_form.h" +#include "components/autofill/core/common/password_form.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" -using content::PasswordForm; -using content::PasswordFormMap; - namespace autofill { // Tests that the when there is a single preferred match, and no extra diff --git a/chromium/components/browser_context_keyed_service/browser_context_dependency_manager.cc b/chromium/components/browser_context_keyed_service/browser_context_dependency_manager.cc index 1031f4e8310..66134233c76 100644 --- a/chromium/components/browser_context_keyed_service/browser_context_dependency_manager.cc +++ b/chromium/components/browser_context_keyed_service/browser_context_dependency_manager.cc @@ -40,9 +40,22 @@ void BrowserContextDependencyManager::AddEdge( } void BrowserContextDependencyManager::CreateBrowserContextServices( - content::BrowserContext* context, bool is_testing_context) { + content::BrowserContext* context) { + DoCreateBrowserContextServices(context, false, false); +} + +void BrowserContextDependencyManager::CreateBrowserContextServicesForTest( + content::BrowserContext* context, + bool force_register_prefs) { + DoCreateBrowserContextServices(context, true, force_register_prefs); +} + +void BrowserContextDependencyManager::DoCreateBrowserContextServices( + content::BrowserContext* context, + bool is_testing_context, + bool force_register_prefs) { TRACE_EVENT0("browser", - "BrowserContextDependencyManager::CreateBrowserContextServices") + "BrowserContextDependencyManager::DoCreateBrowserContextServices") #ifndef NDEBUG // Unmark |context| as dead. This exists because of unit tests, which will // often have similar stack structures. 0xWhatever might be created, go out @@ -64,9 +77,12 @@ void BrowserContextDependencyManager::CreateBrowserContextServices( BrowserContextKeyedBaseFactory* factory = static_cast<BrowserContextKeyedBaseFactory*>(construction_order[i]); - if (!context->IsOffTheRecord()) { + if (!context->IsOffTheRecord() || force_register_prefs) { // We only register preferences on normal contexts because the incognito - // context shares the pref service with the normal one. + // context shares the pref service with the normal one. Always register + // for standalone testing contexts (testing contexts that don't have an + // "original" profile set) as otherwise the preferences won't be + // registered. factory->RegisterUserPrefsOnBrowserContext(context); } diff --git a/chromium/components/browser_context_keyed_service/browser_context_dependency_manager.h b/chromium/components/browser_context_keyed_service/browser_context_dependency_manager.h index 3e96eff98b5..5482aaa9283 100644 --- a/chromium/components/browser_context_keyed_service/browser_context_dependency_manager.h +++ b/chromium/components/browser_context_keyed_service/browser_context_dependency_manager.h @@ -34,12 +34,22 @@ class BROWSER_CONTEXT_KEYED_SERVICE_EXPORT BrowserContextDependencyManager { BrowserContextKeyedBaseFactory* dependee); // Called by each BrowserContext to alert us of its creation. Several services - // want to be started when a context is created. Testing configuration is also - // done at this time. (If you want your BrowserContextKeyedService to be - // started with the BrowserContext, override BrowserContextKeyedBaseFactory:: - // ServiceIsCreatedWithBrowserContext() to return true.) - void CreateBrowserContextServices(content::BrowserContext* context, - bool is_testing_context); + // want to be started when a context is created. If you want your + // BrowserContextKeyedService to be started with the BrowserContext, override + // BrowserContextKeyedBaseFactory::ServiceIsCreatedWithBrowserContext() to + // return true. This method also registers any service-related preferences + // for non-incognito profiles. + void CreateBrowserContextServices(content::BrowserContext* context); + + // Similar to CreateBrowserContextServices(), except this is used for creating + // test BrowserContexts - these contexts will not create services for any + // BrowserContextKeyedBaseFactories that return true from + // ServiceIsNULLWhileTesting(). Callers can pass |force_register_prefs| as + // true to have preferences registered even for incognito profiles - this + // allows tests to specify a standalone incognito profile without an + // associated normal profile. + void CreateBrowserContextServicesForTest(content::BrowserContext* context, + bool force_register_prefs); // Called by each BrowserContext to alert us that we should destroy services // associated with it. @@ -58,6 +68,11 @@ class BROWSER_CONTEXT_KEYED_SERVICE_EXPORT BrowserContextDependencyManager { friend class BrowserContextDependencyManagerUnittests; friend struct DefaultSingletonTraits<BrowserContextDependencyManager>; + // Helper function used by CreateBrowserContextServices[ForTest]. + void DoCreateBrowserContextServices(content::BrowserContext* context, + bool is_testing_context, + bool force_register_prefs); + BrowserContextDependencyManager(); virtual ~BrowserContextDependencyManager(); diff --git a/chromium/components/browser_context_keyed_service/browser_context_keyed_base_factory.cc b/chromium/components/browser_context_keyed_service/browser_context_keyed_base_factory.cc index e6caa69d63c..cc3f487d188 100644 --- a/chromium/components/browser_context_keyed_service/browser_context_keyed_base_factory.cc +++ b/chromium/components/browser_context_keyed_service/browser_context_keyed_base_factory.cc @@ -68,7 +68,6 @@ void BrowserContextKeyedBaseFactory::RegisterUserPrefsOnBrowserContext( // to enforce a uniquenes check here because some tests create one context and // multiple services of the same type attached to that context (serially, not // parallel) and we don't want to register multiple times on the same context. - DCHECK(!context->IsOffTheRecord()); std::set<content::BrowserContext*>::iterator it = registered_preferences_.find(context); diff --git a/chromium/components/component_strings.grd b/chromium/components/component_strings.grd index ba3840e2dbb..28e2a464953 100644 --- a/chromium/components/component_strings.grd +++ b/chromium/components/component_strings.grd @@ -33,7 +33,13 @@ <output filename="component_strings_eo.pak" type="data_package" lang="eo" /> </if> <output filename="component_strings_es.pak" type="data_package" lang="es" /> - <output filename="component_strings_es-419.pak" type="data_package" lang="es-419" /> + <if expr="is_ios"> + <!-- iOS uses es-MX for es-419 --> + <output filename="component_strings_es-MX.pak" type="data_package" lang="es-419" /> + </if> + <if expr="not is_ios"> + <output filename="component_strings_es-419.pak" type="data_package" lang="es-419" /> + </if> <output filename="component_strings_et.pak" type="data_package" lang="et" /> <if expr="pp_ifdef('use_third_party_translations')"> <output filename="component_strings_eu.pak" type="data_package" lang="eu" /> @@ -77,11 +83,11 @@ be 'nb'. --> <output filename="component_strings_nb.pak" type="data_package" lang="no" /> <output filename="component_strings_pl.pak" type="data_package" lang="pl" /> - <if expr="pp_ifdef('ios')"> + <if expr="is_ios"> <!-- iOS uses pt for pt-BR --> <output filename="component_strings_pt.pak" type="data_package" lang="pt-BR" /> </if> - <if expr="not pp_ifdef('ios')"> + <if expr="not is_ios"> <output filename="component_strings_pt-BR.pak" type="data_package" lang="pt-BR" /> </if> <output filename="component_strings_pt-PT.pak" type="data_package" lang="pt-PT" /> @@ -162,6 +168,7 @@ <release seq="1" allow_pseudo="false"> <messages fallback_to_english="true"> <part file="autofill_strings.grdp" /> + <part file="dom_distiller_strings.grdp" /> </messages> </release> </grit> diff --git a/chromium/components/components.gyp b/chromium/components/components.gyp index 61c496c19d6..0bfe536e2f9 100644 --- a/chromium/components/components.gyp +++ b/chromium/components/components.gyp @@ -15,9 +15,14 @@ 'breakpad.gypi', 'browser_context_keyed_service.gypi', 'components_tests.gypi', + 'dom_distiller.gypi', + 'json_schema.gypi', 'navigation_interception.gypi', + 'policy.gypi', 'sessions.gypi', + 'startup_metric_utils.gypi', 'user_prefs.gypi', + 'variations.gypi', 'visitedlink.gypi', 'webdata.gypi', 'web_contents_delegate_android.gypi', diff --git a/chromium/components/components_tests.gypi b/chromium/components/components_tests.gypi index b8e560008b9..491e21445d6 100644 --- a/chromium/components/components_tests.gypi +++ b/chromium/components/components_tests.gypi @@ -10,12 +10,23 @@ 'target_name': 'components_unittests', 'type': '<(gtest_target_type)', 'sources': [ + 'autofill/core/common/form_data_unittest.cc', + 'autofill/core/common/form_field_data_unittest.cc', 'auto_login_parser/auto_login_parser_unittest.cc', 'browser_context_keyed_service/browser_context_dependency_manager_unittest.cc', 'browser_context_keyed_service/dependency_graph_unittest.cc', + 'dom_distiller/core/dom_distiller_database_unittest.cc', + 'json_schema/json_schema_validator_unittest.cc', + 'json_schema/json_schema_validator_unittest_base.cc', + 'json_schema/json_schema_validator_unittest_base.h', 'navigation_interception/intercept_navigation_resource_throttle_unittest.cc', 'sessions/serialized_navigation_entry_unittest.cc', 'test/run_all_unittests.cc', + # TODO(asvitkine): These should be tested on iOS too. + 'variations/entropy_provider_unittest.cc', + 'variations/metrics_util_unittest.cc', + 'variations/variations_associated_data_unittest.cc', + 'variations/variations_seed_processor_unittest.cc', 'visitedlink/test/visitedlink_unittest.cc', 'webdata/encryptor/encryptor_password_mac_unittest.cc', 'webdata/encryptor/encryptor_unittest.cc', @@ -29,25 +40,41 @@ '../testing/gmock.gyp:gmock', '../testing/gtest.gyp:gtest', + # Dependencies of autofill + 'autofill_core_common', + # Dependencies of auto_login_parser 'auto_login_parser', # Dependencies of browser_context_keyed_service 'browser_context_keyed_service', + # Dependencies of dom_distiller + 'dom_distiller_core', + 'dom_distiller_core_proto', + # Dependencies of encryptor 'encryptor', + # Dependencies of json_schema + 'json_schema', + # Dependencies of intercept_navigation_resource_throttle_unittest.cc '../content/content.gyp:test_support_content', '../skia/skia.gyp:skia', 'navigation_interception', + # Dependencies of policy + 'policy_component', + # Dependencies of sessions '../third_party/protobuf/protobuf.gyp:protobuf_lite', 'sessions', 'sessions_test_support', + # Dependencies of variations + 'variations', + # Dependencies of visitedlink 'visitedlink_browser', 'visitedlink_renderer', @@ -87,6 +114,12 @@ 'ldflags': ['-rdynamic'], }, }], + ['configuration_policy==1', { + 'sources': [ + 'policy/core/common/policy_schema_unittest.cc', + 'policy/core/common/schema_unittest.cc', + ], + }], ], # TODO(jschuh): crbug.com/167187 fix size_t to int truncations. 'msvs_disabled_warnings': [4267, ], diff --git a/chromium/components/components_unittests.isolate b/chromium/components/components_unittests.isolate index 0c18d8fe838..f60a380f60d 100644 --- a/chromium/components/components_unittests.isolate +++ b/chromium/components/components_unittests.isolate @@ -8,6 +8,9 @@ 'isolate_dependency_tracked': [ '<(PRODUCT_DIR)/content_resources.pak', ], + 'isolate_dependency_untracked': [ + 'test/data/', + ], }, }], ], diff --git a/chromium/components/dom_distiller.gypi b/chromium/components/dom_distiller.gypi new file mode 100644 index 00000000000..d4f9f44d893 --- /dev/null +++ b/chromium/components/dom_distiller.gypi @@ -0,0 +1,77 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +{ + 'targets': [ + { + 'target_name': 'dom_distiller_webui', + 'type': 'static_library', + 'dependencies': [ + 'component_strings.gyp:component_strings', + 'dom_distiller_core', + 'dom_distiller_resources', + '../base/base.gyp:base', + '../content/content.gyp:content_browser', + '../skia/skia.gyp:skia', + ], + 'include_dirs': [ + '..', + ], + 'sources': [ + 'dom_distiller/webui/dom_distiller_ui.cc', + 'dom_distiller/webui/dom_distiller_ui.h', + 'dom_distiller/webui/dom_distiller_handler.cc', + 'dom_distiller/webui/dom_distiller_handler.h', + ], + }, + { + 'target_name': 'dom_distiller_resources', + 'type': 'none', + 'variables': { + 'grit_out_dir': '<(SHARED_INTERMEDIATE_DIR)/components', + }, + 'actions': [ + { + 'action_name': 'dom_distiller_resources', + 'variables': { + 'grit_grd_file': 'dom_distiller_resources.grd', + }, + 'includes': [ '../build/grit_action.gypi' ], + }, + ], + 'includes': [ '../build/grit_target.gypi' ], + }, + { + 'target_name': 'dom_distiller_core', + 'type': 'static_library', + 'dependencies': [ + 'dom_distiller_core_proto', + '../base/base.gyp:base', + '../third_party/protobuf/protobuf.gyp:protobuf_lite', + '../third_party/leveldatabase/leveldatabase.gyp:leveldatabase', + ], + 'include_dirs': [ + '..', + ], + 'sources': [ + 'dom_distiller/core/dom_distiller_constants.cc', + 'dom_distiller/core/dom_distiller_constants.h', + 'dom_distiller/core/dom_distiller_database.cc', + 'dom_distiller/core/dom_distiller_database.h', + ], + }, + { + 'target_name': 'dom_distiller_core_proto', + 'type': 'static_library', + 'sources': [ + 'dom_distiller/core/proto/article_entry.proto', + ], + 'variables': { + 'proto_in_dir': 'dom_distiller/core/proto', + 'proto_out_dir': 'components/dom_distiller/core/proto', + }, + 'includes': [ '../build/protoc.gypi', ], + }, + ], +} diff --git a/chromium/components/dom_distiller/DEPS b/chromium/components/dom_distiller/DEPS new file mode 100644 index 00000000000..c53eb5fd098 --- /dev/null +++ b/chromium/components/dom_distiller/DEPS @@ -0,0 +1,11 @@ +include_rules = [ + "+grit", # For generated headers. + "+third_party/leveldatabase/src/include", + + # The dom distiller is a layered component; subdirectories must explicitly + # introduce the ability to use the content layer as appropriate. + # http://www.chromium.org/developers/design-documents/layered-components-design + "-components/dom_distiller", + "+components/dom_distiller/core", + "-content/public", +] diff --git a/chromium/components/dom_distiller/OWNERS b/chromium/components/dom_distiller/OWNERS new file mode 100644 index 00000000000..f2ce81c073c --- /dev/null +++ b/chromium/components/dom_distiller/OWNERS @@ -0,0 +1,3 @@ +bengr@chromium.org +cjhopman@chromium.org +nyquist@chromium.org diff --git a/chromium/components/dom_distiller/README b/chromium/components/dom_distiller/README new file mode 100644 index 00000000000..e8d7bb37f6b --- /dev/null +++ b/chromium/components/dom_distiller/README @@ -0,0 +1,12 @@ +The DOM Distiller component contains code for an experimental prototype for +distilling the core part of a web page. + +To enable this feature, use the command line flag --enable-distiller. + +The DOM Distiller is a layered component. See: +http://www.chromium.org/developers/design-documents/layered-components-design + +Folder structure: + + core/ contains the business logic of the component. + webui/ contains the WebUI code and resources for the debug page. diff --git a/chromium/components/dom_distiller/core/dom_distiller_constants.cc b/chromium/components/dom_distiller/core/dom_distiller_constants.cc new file mode 100644 index 00000000000..5dea000fe1b --- /dev/null +++ b/chromium/components/dom_distiller/core/dom_distiller_constants.cc @@ -0,0 +1,12 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/dom_distiller/core/dom_distiller_constants.h" + +namespace dom_distiller { + +const char kChromeUIDomDistillerURL[] = "chrome://dom-distiller/"; +const char kChromeUIDomDistillerHost[] = "dom-distiller"; + +} // namespace dom_distiller diff --git a/chromium/components/dom_distiller/core/dom_distiller_constants.h b/chromium/components/dom_distiller/core/dom_distiller_constants.h new file mode 100644 index 00000000000..c39c88e359d --- /dev/null +++ b/chromium/components/dom_distiller/core/dom_distiller_constants.h @@ -0,0 +1,15 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_DOM_DISTILLER_CORE_DOM_DISTILLER_CONSTANTS_H_ +#define COMPONENTS_DOM_DISTILLER_CORE_DOM_DISTILLER_CONSTANTS_H_ + +namespace dom_distiller { + +extern const char kChromeUIDomDistillerURL[]; +extern const char kChromeUIDomDistillerHost[]; + +} // namespace dom_distiller + +#endif // COMPONENTS_DOM_DISTILLER_CORE_DOM_DISTILLER_CONSTANTS_H_ diff --git a/chromium/components/dom_distiller/core/dom_distiller_database.cc b/chromium/components/dom_distiller/core/dom_distiller_database.cc new file mode 100644 index 00000000000..ba85337b705 --- /dev/null +++ b/chromium/components/dom_distiller/core/dom_distiller_database.cc @@ -0,0 +1,228 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/dom_distiller/core/dom_distiller_database.h" + +#include "base/bind.h" +#include "base/file_util.h" +#include "base/message_loop/message_loop.h" +#include "base/sequenced_task_runner.h" +#include "base/strings/string_util.h" +#include "base/threading/sequenced_worker_pool.h" +#include "components/dom_distiller/core/proto/article_entry.pb.h" +#include "third_party/leveldatabase/src/include/leveldb/db.h" +#include "third_party/leveldatabase/src/include/leveldb/iterator.h" +#include "third_party/leveldatabase/src/include/leveldb/options.h" +#include "third_party/leveldatabase/src/include/leveldb/slice.h" +#include "third_party/leveldatabase/src/include/leveldb/status.h" +#include "third_party/leveldatabase/src/include/leveldb/write_batch.h" + +using base::MessageLoop; +using base::SequencedTaskRunner; + +namespace dom_distiller { + +DomDistillerDatabase::LevelDB::LevelDB() {} + +DomDistillerDatabase::LevelDB::~LevelDB() {} + +bool DomDistillerDatabase::LevelDB::Init(const base::FilePath& database_dir) { + leveldb::Options options; + options.create_if_missing = true; + options.max_open_files = 0; // Use minimum. + + std::string path = database_dir.AsUTF8Unsafe(); + + leveldb::DB* db = NULL; + leveldb::Status status = leveldb::DB::Open(options, path, &db); + if (status.IsCorruption()) { + LOG(WARNING) << "Deleting possibly-corrupt database"; + base::DeleteFile(database_dir, true); + status = leveldb::DB::Open(options, path, &db); + } + + if (status.ok()) { + CHECK(db); + db_.reset(db); + return true; + } + + LOG(WARNING) << "Unable to open " << database_dir.value() << ": " + << status.ToString(); + return false; +} + +bool DomDistillerDatabase::LevelDB::Save(const EntryVector& entries) { + leveldb::WriteBatch updates; + for (EntryVector::const_iterator it = entries.begin(); it != entries.end(); + ++it) { + updates.Put(leveldb::Slice(it->entry_id()), + leveldb::Slice(it->SerializeAsString())); + } + + leveldb::WriteOptions options; + options.sync = true; + leveldb::Status status = db_->Write(options, &updates); + if (status.ok()) + return true; + + LOG(WARNING) << "Failed writing dom_distiller entries: " << status.ToString(); + return false; +} + +bool DomDistillerDatabase::LevelDB::Load(EntryVector* entries) { + leveldb::ReadOptions options; + scoped_ptr<leveldb::Iterator> db_iterator(db_->NewIterator(options)); + for (db_iterator->SeekToFirst(); db_iterator->Valid(); db_iterator->Next()) { + leveldb::Slice value_slice = db_iterator->value(); + + ArticleEntry entry; + if (!entry.ParseFromArray(value_slice.data(), value_slice.size())) { + LOG(WARNING) << "Unable to parse dom_distiller entry " + << db_iterator->key().ToString(); + // TODO(cjhopman): Decide what to do about un-parseable entries. + } + entries->push_back(entry); + } + return true; +} + +DomDistillerDatabase::DomDistillerDatabase( + scoped_refptr<base::SequencedTaskRunner> task_runner) + : task_runner_(task_runner), weak_ptr_factory_(this) { + main_loop_ = MessageLoop::current(); +} + +void DomDistillerDatabase::Destroy() { + DCHECK(IsRunOnMainLoop()); + weak_ptr_factory_.InvalidateWeakPtrs(); + task_runner_->PostNonNestableTask( + FROM_HERE, + base::Bind(&DomDistillerDatabase::DestroyFromTaskRunner, + base::Unretained(this))); +} + +void DomDistillerDatabase::Init(const base::FilePath& database_dir, + InitCallback callback) { + InitWithDatabase(scoped_ptr<Database>(new LevelDB()), database_dir, callback); +} + +namespace { + +void RunInitCallback(DomDistillerDatabaseInterface::InitCallback callback, + const bool* success) { + callback.Run(*success); +} + +void RunSaveCallback(DomDistillerDatabaseInterface::SaveCallback callback, + const bool* success) { + callback.Run(*success); +} + +void RunLoadCallback(DomDistillerDatabaseInterface::LoadCallback callback, + const bool* success, + scoped_ptr<EntryVector> entries) { + callback.Run(*success, entries.Pass()); +} + +} // namespace + +void DomDistillerDatabase::InitWithDatabase(scoped_ptr<Database> database, + const base::FilePath& database_dir, + InitCallback callback) { + DCHECK(IsRunOnMainLoop()); + DCHECK(!db_); + DCHECK(database); + db_.reset(database.release()); + bool* success = new bool(false); + task_runner_->PostTaskAndReply( + FROM_HERE, + base::Bind(&DomDistillerDatabase::InitFromTaskRunner, + base::Unretained(this), + database_dir, + success), + base::Bind(RunInitCallback, callback, base::Owned(success))); +} + +void DomDistillerDatabase::SaveEntries(scoped_ptr<EntryVector> entries, + SaveCallback callback) { + DCHECK(IsRunOnMainLoop()); + bool* success = new bool(false); + task_runner_->PostTaskAndReply( + FROM_HERE, + base::Bind(&DomDistillerDatabase::SaveEntriesFromTaskRunner, + base::Unretained(this), + base::Passed(&entries), + success), + base::Bind(RunSaveCallback, callback, base::Owned(success))); +} + +void DomDistillerDatabase::LoadEntries(LoadCallback callback) { + DCHECK(IsRunOnMainLoop()); + + bool* success = new bool(false); + + scoped_ptr<EntryVector> entries(new EntryVector()); + // Get this pointer before entries is base::Passed() so we can use it below. + EntryVector* entries_ptr = entries.get(); + + task_runner_->PostTaskAndReply( + FROM_HERE, + base::Bind(&DomDistillerDatabase::LoadEntriesFromTaskRunner, + base::Unretained(this), + entries_ptr, + success), + base::Bind(RunLoadCallback, + callback, + base::Owned(success), + base::Passed(&entries))); +} + +DomDistillerDatabase::~DomDistillerDatabase() { DCHECK(IsRunByTaskRunner()); } + +bool DomDistillerDatabase::IsRunByTaskRunner() const { + return task_runner_->RunsTasksOnCurrentThread(); +} + +bool DomDistillerDatabase::IsRunOnMainLoop() const { + return MessageLoop::current() == main_loop_; +} + +void DomDistillerDatabase::DestroyFromTaskRunner() { + DCHECK(IsRunByTaskRunner()); + delete this; +} + +void DomDistillerDatabase::InitFromTaskRunner( + const base::FilePath& database_dir, + bool* success) { + DCHECK(IsRunByTaskRunner()); + DCHECK(success); + + VLOG(1) << "Opening " << database_dir.value(); + + // TODO(cjhopman): Histogram for database size. + *success = db_->Init(database_dir); +} + +void DomDistillerDatabase::SaveEntriesFromTaskRunner( + scoped_ptr<EntryVector> entries, + bool* success) { + DCHECK(IsRunByTaskRunner()); + DCHECK(success); + VLOG(1) << "Saving " << entries->size() << " entry(ies) to database "; + *success = db_->Save(*entries); +} + +void DomDistillerDatabase::LoadEntriesFromTaskRunner(EntryVector* entries, + bool* success) { + DCHECK(IsRunByTaskRunner()); + DCHECK(success); + DCHECK(entries); + + entries->clear(); + *success = db_->Load(entries); +} + +} // namespace dom_distiller diff --git a/chromium/components/dom_distiller/core/dom_distiller_database.h b/chromium/components/dom_distiller/core/dom_distiller_database.h new file mode 100644 index 00000000000..2fb06c61a6b --- /dev/null +++ b/chromium/components/dom_distiller/core/dom_distiller_database.h @@ -0,0 +1,144 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_DOM_DISTILLER_CORE_DOM_DISTILLER_DATABASE_H_ +#define COMPONENTS_DOM_DISTILLER_CORE_DOM_DISTILLER_DATABASE_H_ + +#include <string> +#include <vector> + +#include "base/callback.h" +#include "base/files/file_path.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/scoped_vector.h" +#include "base/memory/weak_ptr.h" +#include "base/message_loop/message_loop.h" + +namespace base { +class SequencedTaskRunner; +} + +namespace leveldb { +class DB; +} + +namespace dom_distiller { + +class ArticleEntry; +typedef std::vector<ArticleEntry> EntryVector; + +// Interface for classes providing persistent storage of DomDistiller entries. +class DomDistillerDatabaseInterface { + public: + typedef std::vector<std::string> ArticleEntryIds; + typedef base::Callback<void(bool success)> InitCallback; + typedef base::Callback<void(bool success)> SaveCallback; + typedef base::Callback<void(bool success, scoped_ptr<EntryVector>)> + LoadCallback; + + // Asynchronously destroys the object after all in-progress file operations + // have completed. The callbacks for in-progress operations will still be + // called. + virtual void Destroy() {} + + // Asynchronously initializes the object. |callback| will be invoked on the UI + // thread when complete. + virtual void Init(const base::FilePath& database_dir, + InitCallback callback) = 0; + + // Asynchronously saves |entries_to_save| database. |callback| will be invoked + // on the UI thread when complete. + virtual void SaveEntries(scoped_ptr<EntryVector> entries_to_save, + SaveCallback callback) = 0; + + // Asynchronously loads all entries from the database and invokes |callback| + // when complete. + virtual void LoadEntries(LoadCallback callback) = 0; + + protected: + virtual ~DomDistillerDatabaseInterface() {} +}; + +class DomDistillerDatabase + : public DomDistillerDatabaseInterface { + public: + // The underlying database. Calls to this type may be blocking. + class Database { + public: + virtual bool Init(const base::FilePath& database_dir) = 0; + virtual bool Save(const EntryVector& entries) = 0; + virtual bool Load(EntryVector* entries) = 0; + virtual ~Database() {} + }; + + class LevelDB : public Database { + public: + LevelDB(); + virtual ~LevelDB(); + virtual bool Init(const base::FilePath& database_dir) OVERRIDE; + virtual bool Save(const EntryVector& entries) OVERRIDE; + virtual bool Load(EntryVector* entries) OVERRIDE; + + private: + + scoped_ptr<leveldb::DB> db_; + }; + + DomDistillerDatabase(scoped_refptr<base::SequencedTaskRunner> task_runner); + + // DomDistillerDatabaseInterface implementation. + virtual void Destroy() OVERRIDE; + virtual void Init(const base::FilePath& database_dir, + InitCallback callback) OVERRIDE; + virtual void SaveEntries(scoped_ptr<EntryVector> entries_to_save, + SaveCallback callback) OVERRIDE; + virtual void LoadEntries(LoadCallback callback) OVERRIDE; + + // Allow callers to provide their own Database implementation. + void InitWithDatabase(scoped_ptr<Database> database, + const base::FilePath& database_dir, + InitCallback callback); + + protected: + virtual ~DomDistillerDatabase(); + + private: + // Whether currently being run by |task_runner_|. + bool IsRunByTaskRunner() const; + + // Whether currently being run on |main_loop_|. + bool IsRunOnMainLoop() const; + + // Deletes |this|. + void DestroyFromTaskRunner(); + + // Initializes the database in |database_dir| and updates |success|. + void InitFromTaskRunner(const base::FilePath& database_dir, bool* success); + + // Saves data to disk and updates |success|. + void SaveEntriesFromTaskRunner(scoped_ptr<EntryVector> entries_to_save, + bool* success); + + // Loads entries from disk and updates |success|. + void LoadEntriesFromTaskRunner(EntryVector* entries, bool* success); + + // The MessageLoop that the database was created on. + base::MessageLoop* main_loop_; + + // Used to run blocking tasks in-order. + scoped_refptr<base::SequencedTaskRunner> task_runner_; + + scoped_ptr<Database> db_; + + // Note: This should remain the last member so it'll be destroyed and + // invalidate its weak pointers before any other members are destroyed. + base::WeakPtrFactory<DomDistillerDatabase> weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(DomDistillerDatabase); +}; + +} // namespace dom_distiller + +#endif // COMPONENTS_DOM_DISTILLER_CORE_DOM_DISTILLER_DATABASE_H_ diff --git a/chromium/components/dom_distiller/core/dom_distiller_database_unittest.cc b/chromium/components/dom_distiller/core/dom_distiller_database_unittest.cc new file mode 100644 index 00000000000..906596800c7 --- /dev/null +++ b/chromium/components/dom_distiller/core/dom_distiller_database_unittest.cc @@ -0,0 +1,305 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/dom_distiller/core/dom_distiller_database.h" + +#include <map> + +#include "base/bind.h" +#include "base/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "base/run_loop.h" +#include "components/dom_distiller/core/proto/article_entry.pb.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using base::MessageLoop; +using base::ScopedTempDir; +using testing::Invoke; +using testing::Return; +using testing::_; + +namespace dom_distiller { + +namespace { + +typedef std::map<std::string, ArticleEntry> EntryMap; + +class MockDB : public DomDistillerDatabase::Database { + public: + MOCK_METHOD1(Init, bool(const base::FilePath&)); + MOCK_METHOD1(Save, bool(const EntryVector&)); + MOCK_METHOD1(Load, bool(EntryVector*)); + + MockDB() { + ON_CALL(*this, Init(_)).WillByDefault(Return(true)); + ON_CALL(*this, Save(_)).WillByDefault(Return(true)); + ON_CALL(*this, Load(_)).WillByDefault(Return(true)); + } + + bool LoadEntries(EntryVector* entries); +}; + +class MockDatabaseCaller { + public: + MOCK_METHOD1(InitCallback, void(bool)); + MOCK_METHOD1(SaveCallback, void(bool)); + void LoadCallback(bool success, scoped_ptr<EntryVector> entries) { + LoadCallback1(success, entries.get()); + } + MOCK_METHOD2(LoadCallback1, void(bool, EntryVector*)); +}; + +} // namespace + +EntryMap GetSmallModel() { + EntryMap model; + + model["key0"].set_entry_id("key0"); + model["key0"].add_pages()->set_url("http://foo.com/1"); + model["key0"].add_pages()->set_url("http://foo.com/2"); + model["key0"].add_pages()->set_url("http://foo.com/3"); + + model["key1"].set_entry_id("key1"); + model["key1"].add_pages()->set_url("http://bar.com/all"); + + model["key2"].set_entry_id("key2"); + model["key2"].add_pages()->set_url("http://baz.com/1"); + + return model; +} + +void ExpectEntryPointersEquals(EntryMap expected, const EntryVector& actual) { + EXPECT_EQ(expected.size(), actual.size()); + for (size_t i = 0; i < actual.size(); i++) { + EntryMap::iterator expected_it = + expected.find(std::string(actual[i].entry_id())); + EXPECT_TRUE(expected_it != expected.end()); + std::string serialized_expected = expected_it->second.SerializeAsString(); + std::string serialized_actual = actual[i].SerializeAsString(); + EXPECT_EQ(serialized_expected, serialized_actual); + expected.erase(expected_it); + } +} + +class DomDistillerDatabaseTest : public testing::Test { + public: + virtual void SetUp() { + main_loop_.reset(new MessageLoop()); + db_ = new DomDistillerDatabase(main_loop_->message_loop_proxy()); + } + + virtual void TearDown() { + DestroyDB(); + main_loop_.reset(NULL); + } + + void DestroyDB() { + if (db_) { + db_->Destroy(); + base::RunLoop().RunUntilIdle(); + db_ = NULL; + } + } + + DomDistillerDatabase* db_; + scoped_ptr<MessageLoop> main_loop_; +}; + +// Test that DomDistillerDatabase calls Init on the underlying database and that +// the caller's InitCallback is called with the correct value. +TEST_F(DomDistillerDatabaseTest, TestDBInitSuccess) { + base::FilePath path(FILE_PATH_LITERAL("/fake/path")); + + MockDB* mock_db = new MockDB(); + EXPECT_CALL(*mock_db, Init(path)).WillOnce(Return(true)); + + MockDatabaseCaller caller; + EXPECT_CALL(caller, InitCallback(true)); + + db_->InitWithDatabase( + scoped_ptr<DomDistillerDatabase::Database>(mock_db), + base::FilePath(path), + base::Bind(&MockDatabaseCaller::InitCallback, base::Unretained(&caller))); + + base::RunLoop().RunUntilIdle(); +} + +TEST_F(DomDistillerDatabaseTest, TestDBInitFailure) { + base::FilePath path(FILE_PATH_LITERAL("/fake/path")); + + MockDB* mock_db = new MockDB(); + EXPECT_CALL(*mock_db, Init(path)).WillOnce(Return(false)); + + MockDatabaseCaller caller; + EXPECT_CALL(caller, InitCallback(false)); + + db_->InitWithDatabase( + scoped_ptr<DomDistillerDatabase::Database>(mock_db), + base::FilePath(path), + base::Bind(&MockDatabaseCaller::InitCallback, base::Unretained(&caller))); + + base::RunLoop().RunUntilIdle(); +} + +ACTION_P(AppendLoadEntries, model) { + EntryVector* output = arg0; + for (EntryMap::const_iterator it = model.begin(); it != model.end(); ++it) { + output->push_back(it->second); + } + return true; +} + +ACTION_P(VerifyLoadEntries, expected) { + EntryVector* actual = arg1; + ExpectEntryPointersEquals(expected, *actual); +} + +// Test that DomDistillerDatabase calls Load on the underlying database and that +// the caller's LoadCallback is called with the correct success value. Also +// confirms that on success, the expected entries are passed to the caller's +// LoadCallback. +TEST_F(DomDistillerDatabaseTest, TestDBLoadSuccess) { + base::FilePath path(FILE_PATH_LITERAL("/fake/path")); + + MockDB* mock_db = new MockDB(); + MockDatabaseCaller caller; + EntryMap model = GetSmallModel(); + + EXPECT_CALL(*mock_db, Init(_)); + EXPECT_CALL(caller, InitCallback(_)); + db_->InitWithDatabase( + scoped_ptr<DomDistillerDatabase::Database>(mock_db), + base::FilePath(path), + base::Bind(&MockDatabaseCaller::InitCallback, base::Unretained(&caller))); + + EXPECT_CALL(*mock_db, Load(_)).WillOnce(AppendLoadEntries(model)); + EXPECT_CALL(caller, LoadCallback1(true, _)) + .WillOnce(VerifyLoadEntries(testing::ByRef(model))); + db_->LoadEntries( + base::Bind(&MockDatabaseCaller::LoadCallback, base::Unretained(&caller))); + + base::RunLoop().RunUntilIdle(); +} + +TEST_F(DomDistillerDatabaseTest, TestDBLoadFailure) { + base::FilePath path(FILE_PATH_LITERAL("/fake/path")); + + MockDB* mock_db = new MockDB(); + MockDatabaseCaller caller; + + EXPECT_CALL(*mock_db, Init(_)); + EXPECT_CALL(caller, InitCallback(_)); + db_->InitWithDatabase( + scoped_ptr<DomDistillerDatabase::Database>(mock_db), + base::FilePath(path), + base::Bind(&MockDatabaseCaller::InitCallback, base::Unretained(&caller))); + + EXPECT_CALL(*mock_db, Load(_)).WillOnce(Return(false)); + EXPECT_CALL(caller, LoadCallback1(false, _)); + db_->LoadEntries( + base::Bind(&MockDatabaseCaller::LoadCallback, base::Unretained(&caller))); + + base::RunLoop().RunUntilIdle(); +} + +ACTION_P(VerifySaveEntries, expected) { + const EntryVector& actual = arg0; + ExpectEntryPointersEquals(expected, actual); + return true; +} + +// Test that DomDistillerDatabase calls Save on the underlying database with the +// correct entries to save and that the caller's SaveCallback is called with the +// correct success value. +TEST_F(DomDistillerDatabaseTest, TestDBSaveSuccess) { + base::FilePath path(FILE_PATH_LITERAL("/fake/path")); + + MockDB* mock_db = new MockDB(); + MockDatabaseCaller caller; + EntryMap model = GetSmallModel(); + + EXPECT_CALL(*mock_db, Init(_)); + EXPECT_CALL(caller, InitCallback(_)); + db_->InitWithDatabase( + scoped_ptr<DomDistillerDatabase::Database>(mock_db), + base::FilePath(path), + base::Bind(&MockDatabaseCaller::InitCallback, base::Unretained(&caller))); + + scoped_ptr<EntryVector> entries(new EntryVector()); + for (EntryMap::iterator it = model.begin(); it != model.end(); ++it) { + entries->push_back(it->second); + } + + EXPECT_CALL(*mock_db, Save(_)).WillOnce(VerifySaveEntries(model)); + EXPECT_CALL(caller, SaveCallback(true)); + db_->SaveEntries( + entries.Pass(), + base::Bind(&MockDatabaseCaller::SaveCallback, base::Unretained(&caller))); + + base::RunLoop().RunUntilIdle(); +} + +TEST_F(DomDistillerDatabaseTest, TestDBSaveFailure) { + base::FilePath path(FILE_PATH_LITERAL("/fake/path")); + + MockDB* mock_db = new MockDB(); + MockDatabaseCaller caller; + scoped_ptr<EntryVector> entries(new EntryVector()); + + EXPECT_CALL(*mock_db, Init(_)); + EXPECT_CALL(caller, InitCallback(_)); + db_->InitWithDatabase( + scoped_ptr<DomDistillerDatabase::Database>(mock_db), + base::FilePath(path), + base::Bind(&MockDatabaseCaller::InitCallback, base::Unretained(&caller))); + + EXPECT_CALL(*mock_db, Save(_)).WillOnce(Return(false)); + EXPECT_CALL(caller, SaveCallback(false)); + db_->SaveEntries( + entries.Pass(), + base::Bind(&MockDatabaseCaller::SaveCallback, base::Unretained(&caller))); + + base::RunLoop().RunUntilIdle(); +} + +// Test that the LevelDB properly saves entries and that load returns the saved +// entries. If |close_after_save| is true, the database will be closed after +// saving and then re-opened to ensure that the data is properly persisted. +void TestLevelDBSaveAndLoad(bool close_after_save) { + ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + + EntryMap model = GetSmallModel(); + EntryVector save_entries; + EntryVector load_entries; + + for (EntryMap::iterator it = model.begin(); it != model.end(); ++it) { + save_entries.push_back(it->second); + } + + scoped_ptr<DomDistillerDatabase::LevelDB> db( + new DomDistillerDatabase::LevelDB()); + EXPECT_TRUE(db->Init(temp_dir.path())); + EXPECT_TRUE(db->Save(save_entries)); + + if (close_after_save) { + db.reset(new DomDistillerDatabase::LevelDB()); + EXPECT_TRUE(db->Init(temp_dir.path())); + } + + EXPECT_TRUE(db->Load(&load_entries)); + + ExpectEntryPointersEquals(model, load_entries); +} + +TEST(DomDistillerDatabaseLevelDBTest, TestDBSaveAndLoad) { + TestLevelDBSaveAndLoad(false); +} + +TEST(DomDistillerDatabaseLevelDBTest, TestDBCloseAndReopen) { + TestLevelDBSaveAndLoad(true); +} + +} // namespace dom_distiller diff --git a/chromium/components/dom_distiller/core/proto/article_entry.proto b/chromium/components/dom_distiller/core/proto/article_entry.proto new file mode 100644 index 00000000000..70b769d97b0 --- /dev/null +++ b/chromium/components/dom_distiller/core/proto/article_entry.proto @@ -0,0 +1,26 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// Protocol buffer definition for a DomDistiller entry. + +syntax = "proto2"; + +option optimize_for = LITE_RUNTIME; + +package dom_distiller; + +message ArticleEntry { + // Next ID to use: 3 + + optional string entry_id = 1; + + message Page { + // Next ID to use: 2 + + optional string url = 1; + } + + repeated Page pages = 2; +} + diff --git a/chromium/components/dom_distiller/webui/DEPS b/chromium/components/dom_distiller/webui/DEPS new file mode 100644 index 00000000000..db46e2e03e5 --- /dev/null +++ b/chromium/components/dom_distiller/webui/DEPS @@ -0,0 +1,13 @@ +include_rules = [ + "+components/dom_distiller/webui", + "+ui/webui/resources", + # The webui of this component needs to depend on content, but since it is + # also supposed to be working on iOS, and there is currently no concrete plan + # yet for extracting webui as something that is reusable across iOS and other + # platforms, this DEPS rule is kept instead. + # To ensure this DEPS rule is not a nuisance to engineers refactoring the + # content layer, it is currently quite broad instead of the more strict + # approach directly allowing the header files that are currently included from + # content. + "+content/public/browser", +] diff --git a/chromium/components/dom_distiller/webui/dom_distiller_handler.cc b/chromium/components/dom_distiller/webui/dom_distiller_handler.cc new file mode 100644 index 00000000000..c05cc0dec73 --- /dev/null +++ b/chromium/components/dom_distiller/webui/dom_distiller_handler.cc @@ -0,0 +1,42 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/dom_distiller/webui/dom_distiller_handler.h" + +#include "base/bind.h" +#include "base/values.h" +#include "content/public/browser/web_ui.h" + +namespace dom_distiller { + +DomDistillerHandler::DomDistillerHandler() + : weak_ptr_factory_(this) { +} + +DomDistillerHandler::~DomDistillerHandler() {} + +void DomDistillerHandler::RegisterMessages() { + web_ui()->RegisterMessageCallback( + "requestEntries", + base::Bind(&DomDistillerHandler::HandleRequestEntries, + base::Unretained(this))); +} + +void DomDistillerHandler::HandleRequestEntries(const ListValue* args) { + base::ListValue entries; + + // Add some temporary placeholder entries. + scoped_ptr<base::DictionaryValue> entry1(new base::DictionaryValue()); + entry1->SetString("title", "Google"); + entry1->SetString("url", "http://www.google.com/"); + entries.Append(entry1.release()); + scoped_ptr<base::DictionaryValue> entry2(new base::DictionaryValue()); + entry2->SetString("title", "Chrome"); + entry2->SetString("url", "http://www.chrome.com/"); + entries.Append(entry2.release()); + + web_ui()->CallJavascriptFunction("onGotEntries", entries); +} + +} // namespace dom_distiller diff --git a/chromium/components/dom_distiller/webui/dom_distiller_handler.h b/chromium/components/dom_distiller/webui/dom_distiller_handler.h new file mode 100644 index 00000000000..2af6d348b00 --- /dev/null +++ b/chromium/components/dom_distiller/webui/dom_distiller_handler.h @@ -0,0 +1,38 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_DOM_DISTILLER_WEBUI_DOM_DISTILLER_HANDLER_H_ +#define COMPONENTS_DOM_DISTILLER_WEBUI_DOM_DISTILLER_HANDLER_H_ + +#include <vector> + +#include "base/memory/weak_ptr.h" +#include "base/values.h" +#include "content/public/browser/web_ui_message_handler.h" + +namespace dom_distiller { + +// Handler class for DOM Distiller page operations. +class DomDistillerHandler : public content::WebUIMessageHandler { + public: + DomDistillerHandler(); + virtual ~DomDistillerHandler(); + + // content::WebUIMessageHandler implementation. + virtual void RegisterMessages() OVERRIDE; + + // Callback for the "requestEntries" message. This synchronously requests the + // list of entries and returns it to the front end. + virtual void HandleRequestEntries(const ListValue* args); + + private: + // Factory for the creating refs in callbacks. + base::WeakPtrFactory<DomDistillerHandler> weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(DomDistillerHandler); +}; + +} // namespace dom_distiller + +#endif // COMPONENTS_DOM_DISTILLER_WEBUI_DOM_DISTILLER_HANDLER_H_ diff --git a/chromium/components/dom_distiller/webui/dom_distiller_ui.cc b/chromium/components/dom_distiller/webui/dom_distiller_ui.cc new file mode 100644 index 00000000000..e1418434cc4 --- /dev/null +++ b/chromium/components/dom_distiller/webui/dom_distiller_ui.cc @@ -0,0 +1,42 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/dom_distiller/webui/dom_distiller_ui.h" + +#include "components/dom_distiller/core/dom_distiller_constants.h" +#include "components/dom_distiller/webui/dom_distiller_handler.h" +#include "content/public/browser/browser_context.h" +#include "content/public/browser/web_contents.h" +#include "content/public/browser/web_ui.h" +#include "content/public/browser/web_ui_data_source.h" +#include "grit/component_strings.h" +#include "grit/dom_distiller_resources.h" + +namespace dom_distiller { + +DomDistillerUI::DomDistillerUI(content::WebUI* web_ui) + : content::WebUIController(web_ui) { + // Set up WebUIDataSource. + content::WebUIDataSource* source = + content::WebUIDataSource::Create(kChromeUIDomDistillerHost); + source->SetDefaultResource(IDR_ABOUT_DOM_DISTILLER_HTML); + source->AddResourcePath("about_dom_distiller.css", + IDR_ABOUT_DOM_DISTILLER_CSS); + source->AddResourcePath("about_dom_distiller.js", + IDR_ABOUT_DOM_DISTILLER_JS); + + source->SetUseJsonJSFormatV2(); + source->AddLocalizedString("domDistillerTitle", IDS_DOM_DISTILLER_TITLE); + content::BrowserContext* browser_context = + web_ui->GetWebContents()->GetBrowserContext(); + content::WebUIDataSource::Add(browser_context, source); + source->SetJsonPath("strings.js"); + + // Add message handler. + web_ui->AddMessageHandler(new DomDistillerHandler()); +} + +DomDistillerUI::~DomDistillerUI() {} + +} // namespace dom_distiller diff --git a/chromium/components/dom_distiller/webui/dom_distiller_ui.h b/chromium/components/dom_distiller/webui/dom_distiller_ui.h new file mode 100644 index 00000000000..1b9f1c2a292 --- /dev/null +++ b/chromium/components/dom_distiller/webui/dom_distiller_ui.h @@ -0,0 +1,24 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_DOM_DISTILLER_WEBUI_DOM_DISTILLER_H_ +#define COMPONENTS_DOM_DISTILLER_WEBUI_DOM_DISTILLER_H_ + +#include "content/public/browser/web_ui_controller.h" + +namespace dom_distiller { + +// The WebUI handler for chrome://dom-distiller. +class DomDistillerUI : public content::WebUIController { + public: + explicit DomDistillerUI(content::WebUI* web_ui); + virtual ~DomDistillerUI(); + + private: + DISALLOW_COPY_AND_ASSIGN(DomDistillerUI); +}; + +} // namespace dom_distiller + +#endif // COMPONENTS_DOM_DISTILLER_WEBUI_DOM_DISTILLER_UI_H_ diff --git a/chromium/components/dom_distiller/webui/resources/about_dom_distiller.css b/chromium/components/dom_distiller/webui/resources/about_dom_distiller.css new file mode 100644 index 00000000000..088136a8204 --- /dev/null +++ b/chromium/components/dom_distiller/webui/resources/about_dom_distiller.css @@ -0,0 +1,8 @@ +/* Copyright 2013 The Chromium Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +a:visited { + color: orange; +} diff --git a/chromium/components/dom_distiller/webui/resources/about_dom_distiller.html b/chromium/components/dom_distiller/webui/resources/about_dom_distiller.html new file mode 100644 index 00000000000..1e3b537b302 --- /dev/null +++ b/chromium/components/dom_distiller/webui/resources/about_dom_distiller.html @@ -0,0 +1,27 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title i18n-content="domDistillerTitle"></title> + <link rel="stylesheet" href="chrome://resources/css/chrome_shared.css"> + <link rel="stylesheet" href="chrome://resources/css/overlay.css"> + <link rel="stylesheet" href="about_dom_distiller.css"> + + <script src="chrome://resources/js/cr.js"></script> + <script src="chrome://resources/js/util.js"></script> + <script src="chrome://resources/js/load_time_data.js"></script> + <script src="chrome://resources/js/cr/ui/overlay.js"></script> + <script src="about_dom_distiller.js"></script> + <script src="strings.js"></script> +</head> +<body> + <header> + <h1 i18n-content="domDistillerTitle"></h1> + </header> + <div id="entries-section"> + <div id="entries-list"></div> + </div> + <script src="chrome://resources/js/i18n_template2.js"></script> + <script src="chrome://resources/js/jstemplate_compiled.js"></script> +</body> +</html> diff --git a/chromium/components/dom_distiller/webui/resources/about_dom_distiller.js b/chromium/components/dom_distiller/webui/resources/about_dom_distiller.js new file mode 100644 index 00000000000..f558d7fa9fb --- /dev/null +++ b/chromium/components/dom_distiller/webui/resources/about_dom_distiller.js @@ -0,0 +1,32 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * Callback from the backend with the list of entries to display. + * This call will build the entries section of the DOM distiller page, or hide + * that section if there are none to display. + * @param {!Array.<string>} entries The entries. + */ +function onGotEntries(entries) { + $('entries-section').hidden = !entries.length; + if (entries.length > 0) { + var list = document.createElement('ul'); + for (var i = 0; i < entries.length; i++) { + var listItem = document.createElement('li'); + var link = document.createElement('a'); + link.innerText = entries[i].title; + link.setAttribute('href', entries[i].url); + listItem.appendChild(link); + list.appendChild(listItem); + } + $('entries-list').appendChild(list); + } +} + +/* All the work we do on load. */ +function onLoadWork() { + chrome.send('requestEntries'); +} + +document.addEventListener('DOMContentLoaded', onLoadWork); diff --git a/chromium/components/dom_distiller_resources.grd b/chromium/components/dom_distiller_resources.grd new file mode 100644 index 00000000000..dd2a5f57490 --- /dev/null +++ b/chromium/components/dom_distiller_resources.grd @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<grit latest_public_release="0" current_release="1"> + <outputs> + <output filename="grit/dom_distiller_resources.h" type="rc_header"> + <emit emit_type='prepend'></emit> + </output> + <output filename="dom_distiller_resources.pak" type="data_package" /> + <output filename="dom_distiller_resources.rc" type="rc_all" /> + </outputs> + <release seq="1"> + <includes> + <include name="IDR_ABOUT_DOM_DISTILLER_HTML" file="dom_distiller/webui/resources/about_dom_distiller.html" flattenhtml="true" allowexternalscript="true" type="BINDATA" /> + <include name="IDR_ABOUT_DOM_DISTILLER_CSS" file="dom_distiller/webui/resources/about_dom_distiller.css" type="BINDATA" /> + <include name="IDR_ABOUT_DOM_DISTILLER_JS" file="dom_distiller/webui/resources/about_dom_distiller.js" type="BINDATA" /> + </includes> + </release> +</grit> diff --git a/chromium/components/dom_distiller_strings.grdp b/chromium/components/dom_distiller_strings.grdp new file mode 100644 index 00000000000..3d81fae78a0 --- /dev/null +++ b/chromium/components/dom_distiller_strings.grdp @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8"?> +<grit-part> + + <message name="IDS_DOM_DISTILLER_TITLE" desc="The title to show on the DOM Distiller debug page."> + DOM Distiller + </message> + +</grit-part> diff --git a/chromium/components/json_schema.gypi b/chromium/components/json_schema.gypi new file mode 100644 index 00000000000..510f97d6fe4 --- /dev/null +++ b/chromium/components/json_schema.gypi @@ -0,0 +1,25 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +{ + 'targets': [ + { + 'target_name': 'json_schema', + 'type': 'static_library', + 'dependencies': [ + '../base/base.gyp:base', + '../ui/ui.gyp:ui', + ], + 'include_dirs': [ + '..', + ], + 'sources': [ + 'json_schema/json_schema_constants.cc', + 'json_schema/json_schema_constants.h', + 'json_schema/json_schema_validator.cc', + 'json_schema/json_schema_validator.h', + ], + }, + ], +} diff --git a/chromium/components/json_schema/DEPS b/chromium/components/json_schema/DEPS new file mode 100644 index 00000000000..e7cf2c6ff61 --- /dev/null +++ b/chromium/components/json_schema/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+ui/base", +] diff --git a/chromium/components/json_schema/OWNERS b/chromium/components/json_schema/OWNERS new file mode 100644 index 00000000000..f7e95c9f18f --- /dev/null +++ b/chromium/components/json_schema/OWNERS @@ -0,0 +1,5 @@ +asargent@chromium.org +calamity@chromium.org +kalman@chromium.org +koz@chromium.org +mpcomplete@chromium.org diff --git a/chromium/components/json_schema/README b/chromium/components/json_schema/README new file mode 100644 index 00000000000..c7453db06dc --- /dev/null +++ b/chromium/components/json_schema/README @@ -0,0 +1,6 @@ +The //components/json_schema component provides: + +a) JSON schema constants, which can be used to inspect schema objects. + +b) The JSONSchemaValidator class, which can be used to parse and validate JSON +schemas, and to validate JSON objects against the parsed schema. diff --git a/chromium/components/json_schema/json_schema_constants.cc b/chromium/components/json_schema/json_schema_constants.cc new file mode 100644 index 00000000000..0152cfc054d --- /dev/null +++ b/chromium/components/json_schema/json_schema_constants.cc @@ -0,0 +1,38 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/json_schema/json_schema_constants.h" + +namespace json_schema_constants { + +const char kAdditionalProperties[] = "additionalProperties"; +const char kAny[] = "any"; +const char kArray[] = "array"; +const char kBoolean[] = "boolean"; +const char kChoices[] = "choices"; +const char kDescription[] = "description"; +const char kEnum[] = "enum"; +const char kId[] = "id"; +const char kInteger[] = "integer"; +const char kItems[] = "items"; +const char kMaximum[] = "maximum"; +const char kMaxItems[] = "maxItems"; +const char kMaxLength[] = "maxLength"; +const char kMinimum[] = "minimum"; +const char kMinItems[] = "minItems"; +const char kMinLength[] = "minLength"; +const char kNull[] = "null"; +const char kNumber[] = "number"; +const char kObject[] = "object"; +const char kOptional[] = "optional"; +const char kPattern[] = "pattern"; +const char kPatternProperties[] = "patternProperties"; +const char kProperties[] = "properties"; +const char kRef[] = "$ref"; +const char kSchema[] = "$schema"; +const char kString[] = "string"; +const char kTitle[] = "title"; +const char kType[] = "type"; + +} // namespace json_schema_constants diff --git a/chromium/components/json_schema/json_schema_constants.h b/chromium/components/json_schema/json_schema_constants.h new file mode 100644 index 00000000000..5c64ebb5606 --- /dev/null +++ b/chromium/components/json_schema/json_schema_constants.h @@ -0,0 +1,42 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_JSON_SCHEMA_JSON_SCHEMA_CONSTANTS_H_ +#define COMPONENTS_JSON_SCHEMA_JSON_SCHEMA_CONSTANTS_H_ + +// These constants are shared by code that uses JSON schemas. +namespace json_schema_constants { + +extern const char kAdditionalProperties[]; +extern const char kAny[]; +extern const char kArray[]; +extern const char kBoolean[]; +extern const char kChoices[]; +extern const char kDescription[]; +extern const char kEnum[]; +extern const char kId[]; +extern const char kInteger[]; +extern const char kItems[]; +extern const char kMaximum[]; +extern const char kMaxItems[]; +extern const char kMaxLength[]; +extern const char kMinimum[]; +extern const char kMinItems[]; +extern const char kMinLength[]; +extern const char kNull[]; +extern const char kNumber[]; +extern const char kObject[]; +extern const char kOptional[]; +extern const char kPattern[]; +extern const char kPatternProperties[]; +extern const char kProperties[]; +extern const char kRef[]; +extern const char kSchema[]; +extern const char kString[]; +extern const char kTitle[]; +extern const char kType[]; + +} // namespace json_schema_constants + +#endif // COMPONENTS_JSON_SCHEMA_JSON_SCHEMA_CONSTANTS_H_ diff --git a/chromium/components/json_schema/json_schema_validator.cc b/chromium/components/json_schema/json_schema_validator.cc new file mode 100644 index 00000000000..3816a760970 --- /dev/null +++ b/chromium/components/json_schema/json_schema_validator.cc @@ -0,0 +1,727 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/json_schema/json_schema_validator.h" + +#include <algorithm> +#include <cfloat> +#include <cmath> + +#include "base/json/json_reader.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "base/values.h" +#include "components/json_schema/json_schema_constants.h" +#include "ui/base/l10n/l10n_util.h" + +namespace schema = json_schema_constants; + +namespace { + +double GetNumberValue(const base::Value* value) { + double result = 0; + CHECK(value->GetAsDouble(&result)) + << "Unexpected value type: " << value->GetType(); + return result; +} + +bool IsValidType(const std::string& type) { + static const char* kValidTypes[] = { + schema::kAny, + schema::kArray, + schema::kBoolean, + schema::kInteger, + schema::kNull, + schema::kNumber, + schema::kObject, + schema::kString, + }; + const char** end = kValidTypes + arraysize(kValidTypes); + return std::find(kValidTypes, end, type) != end; +} + +// Maps a schema attribute name to its expected type. +struct ExpectedType { + const char* key; + base::Value::Type type; +}; + +// Helper for std::lower_bound. +bool CompareToString(const ExpectedType& entry, const std::string& key) { + return entry.key < key; +} + +bool IsValidSchema(const base::DictionaryValue* dict, std::string* error) { + // This array must be sorted, so that std::lower_bound can perform a + // binary search. + static const ExpectedType kExpectedTypes[] = { + // Note: kRef == "$ref", kSchema == "$schema" + { schema::kRef, base::Value::TYPE_STRING }, + { schema::kSchema, base::Value::TYPE_STRING }, + + { schema::kAdditionalProperties, base::Value::TYPE_DICTIONARY }, + { schema::kChoices, base::Value::TYPE_LIST }, + { schema::kDescription, base::Value::TYPE_STRING }, + { schema::kEnum, base::Value::TYPE_LIST }, + { schema::kId, base::Value::TYPE_STRING }, + { schema::kMaxItems, base::Value::TYPE_INTEGER }, + { schema::kMaxLength, base::Value::TYPE_INTEGER }, + { schema::kMaximum, base::Value::TYPE_DOUBLE }, + { schema::kMinItems, base::Value::TYPE_INTEGER }, + { schema::kMinLength, base::Value::TYPE_INTEGER }, + { schema::kMinimum, base::Value::TYPE_DOUBLE }, + { schema::kOptional, base::Value::TYPE_BOOLEAN }, + { schema::kProperties, base::Value::TYPE_DICTIONARY }, + { schema::kTitle, base::Value::TYPE_STRING }, + }; + + bool has_type = false; + const base::ListValue* list_value = NULL; + const base::DictionaryValue* dictionary_value = NULL; + std::string string_value; + + for (base::DictionaryValue::Iterator it(*dict); !it.IsAtEnd(); it.Advance()) { + // Validate the "type" attribute, which may be a string or a list. + if (it.key() == schema::kType) { + switch (it.value().GetType()) { + case base::Value::TYPE_STRING: + it.value().GetAsString(&string_value); + if (!IsValidType(string_value)) { + *error = "Invalid value for type attribute"; + return false; + } + break; + case base::Value::TYPE_LIST: + it.value().GetAsList(&list_value); + for (size_t i = 0; i < list_value->GetSize(); ++i) { + if (!list_value->GetString(i, &string_value) || + !IsValidType(string_value)) { + *error = "Invalid value for type attribute"; + return false; + } + } + break; + default: + *error = "Invalid value for type attribute"; + return false; + } + has_type = true; + continue; + } + + // Validate the "items" attribute, which is a schema or a list of schemas. + if (it.key() == schema::kItems) { + if (it.value().GetAsDictionary(&dictionary_value)) { + if (!IsValidSchema(dictionary_value, error)) { + DCHECK(!error->empty()); + return false; + } + } else if (it.value().GetAsList(&list_value)) { + for (size_t i = 0; i < list_value->GetSize(); ++i) { + if (!list_value->GetDictionary(i, &dictionary_value)) { + *error = base::StringPrintf( + "Invalid entry in items attribute at index %d", + static_cast<int>(i)); + return false; + } + if (!IsValidSchema(dictionary_value, error)) { + DCHECK(!error->empty()); + return false; + } + } + } else { + *error = "Invalid value for items attribute"; + return false; + } + continue; + } + + // All the other attributes have a single valid type. + const ExpectedType* end = kExpectedTypes + arraysize(kExpectedTypes); + const ExpectedType* entry = std::lower_bound( + kExpectedTypes, end, it.key(), CompareToString); + if (entry == end || entry->key != it.key()) { + *error = base::StringPrintf("Invalid attribute %s", it.key().c_str()); + return false; + } + if (!it.value().IsType(entry->type)) { + *error = base::StringPrintf("Invalid value for %s attribute", + it.key().c_str()); + return false; + } + + // base::Value::TYPE_INTEGER attributes must be >= 0. + // This applies to "minItems", "maxItems", "minLength" and "maxLength". + if (it.value().IsType(base::Value::TYPE_INTEGER)) { + int integer_value; + it.value().GetAsInteger(&integer_value); + if (integer_value < 0) { + *error = base::StringPrintf("Value of %s must be >= 0, got %d", + it.key().c_str(), integer_value); + return false; + } + } + + // Validate the "properties" attribute. Each entry maps a key to a schema. + if (it.key() == schema::kProperties) { + it.value().GetAsDictionary(&dictionary_value); + for (base::DictionaryValue::Iterator it(*dictionary_value); + !it.IsAtEnd(); it.Advance()) { + if (!it.value().GetAsDictionary(&dictionary_value)) { + *error = "Invalid value for properties attribute"; + return false; + } + if (!IsValidSchema(dictionary_value, error)) { + DCHECK(!error->empty()); + return false; + } + } + } + + // Validate "additionalProperties" attribute, which is a schema. + if (it.key() == schema::kAdditionalProperties) { + it.value().GetAsDictionary(&dictionary_value); + if (!IsValidSchema(dictionary_value, error)) { + DCHECK(!error->empty()); + return false; + } + } + + // Validate the values contained in an "enum" attribute. + if (it.key() == schema::kEnum) { + it.value().GetAsList(&list_value); + for (size_t i = 0; i < list_value->GetSize(); ++i) { + const base::Value* value = NULL; + list_value->Get(i, &value); + switch (value->GetType()) { + case base::Value::TYPE_NULL: + case base::Value::TYPE_BOOLEAN: + case base::Value::TYPE_INTEGER: + case base::Value::TYPE_DOUBLE: + case base::Value::TYPE_STRING: + break; + default: + *error = "Invalid value in enum attribute"; + return false; + } + } + } + + // Validate the schemas contained in a "choices" attribute. + if (it.key() == schema::kChoices) { + it.value().GetAsList(&list_value); + for (size_t i = 0; i < list_value->GetSize(); ++i) { + if (!list_value->GetDictionary(i, &dictionary_value)) { + *error = "Invalid choices attribute"; + return false; + } + if (!IsValidSchema(dictionary_value, error)) { + DCHECK(!error->empty()); + return false; + } + } + } + } + + if (!has_type) { + *error = "Schema must have a type attribute"; + return false; + } + + return true; +} + +} // namespace + + +JSONSchemaValidator::Error::Error() { +} + +JSONSchemaValidator::Error::Error(const std::string& message) + : path(message) { +} + +JSONSchemaValidator::Error::Error(const std::string& path, + const std::string& message) + : path(path), message(message) { +} + + +const char JSONSchemaValidator::kUnknownTypeReference[] = + "Unknown schema reference: *."; +const char JSONSchemaValidator::kInvalidChoice[] = + "Value does not match any valid type choices."; +const char JSONSchemaValidator::kInvalidEnum[] = + "Value does not match any valid enum choices."; +const char JSONSchemaValidator::kObjectPropertyIsRequired[] = + "Property is required."; +const char JSONSchemaValidator::kUnexpectedProperty[] = + "Unexpected property."; +const char JSONSchemaValidator::kArrayMinItems[] = + "Array must have at least * items."; +const char JSONSchemaValidator::kArrayMaxItems[] = + "Array must not have more than * items."; +const char JSONSchemaValidator::kArrayItemRequired[] = + "Item is required."; +const char JSONSchemaValidator::kStringMinLength[] = + "String must be at least * characters long."; +const char JSONSchemaValidator::kStringMaxLength[] = + "String must not be more than * characters long."; +const char JSONSchemaValidator::kStringPattern[] = + "String must match the pattern: *."; +const char JSONSchemaValidator::kNumberMinimum[] = + "Value must not be less than *."; +const char JSONSchemaValidator::kNumberMaximum[] = + "Value must not be greater than *."; +const char JSONSchemaValidator::kInvalidType[] = + "Expected '*' but got '*'."; +const char JSONSchemaValidator::kInvalidTypeIntegerNumber[] = + "Expected 'integer' but got 'number', consider using Math.round()."; + + +// static +std::string JSONSchemaValidator::GetJSONSchemaType(const base::Value* value) { + switch (value->GetType()) { + case base::Value::TYPE_NULL: + return schema::kNull; + case base::Value::TYPE_BOOLEAN: + return schema::kBoolean; + case base::Value::TYPE_INTEGER: + return schema::kInteger; + case base::Value::TYPE_DOUBLE: { + double double_value = 0; + value->GetAsDouble(&double_value); + if (std::abs(double_value) <= std::pow(2.0, DBL_MANT_DIG) && + double_value == floor(double_value)) { + return schema::kInteger; + } else { + return schema::kNumber; + } + } + case base::Value::TYPE_STRING: + return schema::kString; + case base::Value::TYPE_DICTIONARY: + return schema::kObject; + case base::Value::TYPE_LIST: + return schema::kArray; + default: + NOTREACHED() << "Unexpected value type: " << value->GetType(); + return std::string(); + } +} + +// static +std::string JSONSchemaValidator::FormatErrorMessage(const std::string& format, + const std::string& s1) { + std::string ret_val = format; + ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s1); + return ret_val; +} + +// static +std::string JSONSchemaValidator::FormatErrorMessage(const std::string& format, + const std::string& s1, + const std::string& s2) { + std::string ret_val = format; + ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s1); + ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s2); + return ret_val; +} + +// static +scoped_ptr<base::DictionaryValue> JSONSchemaValidator::IsValidSchema( + const std::string& schema, + std::string* error) { + base::JSONParserOptions options = base::JSON_PARSE_RFC; + scoped_ptr<base::Value> json( + base::JSONReader::ReadAndReturnError(schema, options, NULL, error)); + if (!json) + return scoped_ptr<base::DictionaryValue>(); + base::DictionaryValue* dict = NULL; + if (!json->GetAsDictionary(&dict)) { + *error = "Schema must be a JSON object"; + return scoped_ptr<base::DictionaryValue>(); + } + if (!::IsValidSchema(dict, error)) + return scoped_ptr<base::DictionaryValue>(); + ignore_result(json.release()); + return make_scoped_ptr(dict); +} + +JSONSchemaValidator::JSONSchemaValidator(base::DictionaryValue* schema) + : schema_root_(schema), default_allow_additional_properties_(false) { +} + +JSONSchemaValidator::JSONSchemaValidator(base::DictionaryValue* schema, + base::ListValue* types) + : schema_root_(schema), default_allow_additional_properties_(false) { + if (!types) + return; + + for (size_t i = 0; i < types->GetSize(); ++i) { + base::DictionaryValue* type = NULL; + CHECK(types->GetDictionary(i, &type)); + + std::string id; + CHECK(type->GetString(schema::kId, &id)); + + CHECK(types_.find(id) == types_.end()); + types_[id] = type; + } +} + +JSONSchemaValidator::~JSONSchemaValidator() {} + +bool JSONSchemaValidator::Validate(const base::Value* instance) { + errors_.clear(); + Validate(instance, schema_root_, std::string()); + return errors_.empty(); +} + +void JSONSchemaValidator::Validate(const base::Value* instance, + const base::DictionaryValue* schema, + const std::string& path) { + // If this schema defines itself as reference type, save it in this.types. + std::string id; + if (schema->GetString(schema::kId, &id)) { + TypeMap::iterator iter = types_.find(id); + if (iter == types_.end()) + types_[id] = schema; + else + DCHECK(iter->second == schema); + } + + // If the schema has a $ref property, the instance must validate against + // that schema. It must be present in types_ to be referenced. + std::string ref; + if (schema->GetString(schema::kRef, &ref)) { + TypeMap::iterator type = types_.find(ref); + if (type == types_.end()) { + errors_.push_back( + Error(path, FormatErrorMessage(kUnknownTypeReference, ref))); + } else { + Validate(instance, type->second, path); + } + return; + } + + // If the schema has a choices property, the instance must validate against at + // least one of the items in that array. + const base::ListValue* choices = NULL; + if (schema->GetList(schema::kChoices, &choices)) { + ValidateChoices(instance, choices, path); + return; + } + + // If the schema has an enum property, the instance must be one of those + // values. + const base::ListValue* enumeration = NULL; + if (schema->GetList(schema::kEnum, &enumeration)) { + ValidateEnum(instance, enumeration, path); + return; + } + + std::string type; + schema->GetString(schema::kType, &type); + CHECK(!type.empty()); + if (type != schema::kAny) { + if (!ValidateType(instance, type, path)) + return; + + // These casts are safe because of checks in ValidateType(). + if (type == schema::kObject) { + ValidateObject(static_cast<const base::DictionaryValue*>(instance), + schema, + path); + } else if (type == schema::kArray) { + ValidateArray(static_cast<const base::ListValue*>(instance), + schema, path); + } else if (type == schema::kString) { + // Intentionally NOT downcasting to StringValue*. TYPE_STRING only implies + // GetAsString() can safely be carried out, not that it's a StringValue. + ValidateString(instance, schema, path); + } else if (type == schema::kNumber || type == schema::kInteger) { + ValidateNumber(instance, schema, path); + } else if (type != schema::kBoolean && type != schema::kNull) { + NOTREACHED() << "Unexpected type: " << type; + } + } +} + +void JSONSchemaValidator::ValidateChoices(const base::Value* instance, + const base::ListValue* choices, + const std::string& path) { + size_t original_num_errors = errors_.size(); + + for (size_t i = 0; i < choices->GetSize(); ++i) { + const base::DictionaryValue* choice = NULL; + CHECK(choices->GetDictionary(i, &choice)); + + Validate(instance, choice, path); + if (errors_.size() == original_num_errors) + return; + + // We discard the error from each choice. We only want to know if any of the + // validations succeeded. + errors_.resize(original_num_errors); + } + + // Now add a generic error that no choices matched. + errors_.push_back(Error(path, kInvalidChoice)); + return; +} + +void JSONSchemaValidator::ValidateEnum(const base::Value* instance, + const base::ListValue* choices, + const std::string& path) { + for (size_t i = 0; i < choices->GetSize(); ++i) { + const base::Value* choice = NULL; + CHECK(choices->Get(i, &choice)); + switch (choice->GetType()) { + case base::Value::TYPE_NULL: + case base::Value::TYPE_BOOLEAN: + case base::Value::TYPE_STRING: + if (instance->Equals(choice)) + return; + break; + + case base::Value::TYPE_INTEGER: + case base::Value::TYPE_DOUBLE: + if (instance->IsType(base::Value::TYPE_INTEGER) || + instance->IsType(base::Value::TYPE_DOUBLE)) { + if (GetNumberValue(choice) == GetNumberValue(instance)) + return; + } + break; + + default: + NOTREACHED() << "Unexpected type in enum: " << choice->GetType(); + } + } + + errors_.push_back(Error(path, kInvalidEnum)); +} + +void JSONSchemaValidator::ValidateObject(const base::DictionaryValue* instance, + const base::DictionaryValue* schema, + const std::string& path) { + const base::DictionaryValue* properties = NULL; + schema->GetDictionary(schema::kProperties, &properties); + if (properties) { + for (base::DictionaryValue::Iterator it(*properties); !it.IsAtEnd(); + it.Advance()) { + std::string prop_path = path.empty() ? it.key() : (path + "." + it.key()); + const base::DictionaryValue* prop_schema = NULL; + CHECK(it.value().GetAsDictionary(&prop_schema)); + + const base::Value* prop_value = NULL; + if (instance->Get(it.key(), &prop_value)) { + Validate(prop_value, prop_schema, prop_path); + } else { + // Properties are required unless there is an optional field set to + // 'true'. + bool is_optional = false; + prop_schema->GetBoolean(schema::kOptional, &is_optional); + if (!is_optional) { + errors_.push_back(Error(prop_path, kObjectPropertyIsRequired)); + } + } + } + } + + const base::DictionaryValue* additional_properties_schema = NULL; + if (SchemaAllowsAnyAdditionalItems(schema, &additional_properties_schema)) + return; + + // Validate additional properties. + for (base::DictionaryValue::Iterator it(*instance); !it.IsAtEnd(); + it.Advance()) { + if (properties && properties->HasKey(it.key())) + continue; + + std::string prop_path = path.empty() ? it.key() : path + "." + it.key(); + if (!additional_properties_schema) { + errors_.push_back(Error(prop_path, kUnexpectedProperty)); + } else { + Validate(&it.value(), additional_properties_schema, prop_path); + } + } +} + +void JSONSchemaValidator::ValidateArray(const base::ListValue* instance, + const base::DictionaryValue* schema, + const std::string& path) { + const base::DictionaryValue* single_type = NULL; + size_t instance_size = instance->GetSize(); + if (schema->GetDictionary(schema::kItems, &single_type)) { + int min_items = 0; + if (schema->GetInteger(schema::kMinItems, &min_items)) { + CHECK(min_items >= 0); + if (instance_size < static_cast<size_t>(min_items)) { + errors_.push_back(Error(path, FormatErrorMessage( + kArrayMinItems, base::IntToString(min_items)))); + } + } + + int max_items = 0; + if (schema->GetInteger(schema::kMaxItems, &max_items)) { + CHECK(max_items >= 0); + if (instance_size > static_cast<size_t>(max_items)) { + errors_.push_back(Error(path, FormatErrorMessage( + kArrayMaxItems, base::IntToString(max_items)))); + } + } + + // If the items property is a single schema, each item in the array must + // validate against that schema. + for (size_t i = 0; i < instance_size; ++i) { + const base::Value* item = NULL; + CHECK(instance->Get(i, &item)); + std::string i_str = base::Uint64ToString(i); + std::string item_path = path.empty() ? i_str : (path + "." + i_str); + Validate(item, single_type, item_path); + } + + return; + } + + // Otherwise, the list must be a tuple type, where each item in the list has a + // particular schema. + ValidateTuple(instance, schema, path); +} + +void JSONSchemaValidator::ValidateTuple(const base::ListValue* instance, + const base::DictionaryValue* schema, + const std::string& path) { + const base::ListValue* tuple_type = NULL; + schema->GetList(schema::kItems, &tuple_type); + size_t tuple_size = tuple_type ? tuple_type->GetSize() : 0; + if (tuple_type) { + for (size_t i = 0; i < tuple_size; ++i) { + std::string i_str = base::Uint64ToString(i); + std::string item_path = path.empty() ? i_str : (path + "." + i_str); + const base::DictionaryValue* item_schema = NULL; + CHECK(tuple_type->GetDictionary(i, &item_schema)); + const base::Value* item_value = NULL; + instance->Get(i, &item_value); + if (item_value && item_value->GetType() != base::Value::TYPE_NULL) { + Validate(item_value, item_schema, item_path); + } else { + bool is_optional = false; + item_schema->GetBoolean(schema::kOptional, &is_optional); + if (!is_optional) { + errors_.push_back(Error(item_path, kArrayItemRequired)); + return; + } + } + } + } + + const base::DictionaryValue* additional_properties_schema = NULL; + if (SchemaAllowsAnyAdditionalItems(schema, &additional_properties_schema)) + return; + + size_t instance_size = instance->GetSize(); + if (additional_properties_schema) { + // Any additional properties must validate against the additionalProperties + // schema. + for (size_t i = tuple_size; i < instance_size; ++i) { + std::string i_str = base::Uint64ToString(i); + std::string item_path = path.empty() ? i_str : (path + "." + i_str); + const base::Value* item_value = NULL; + CHECK(instance->Get(i, &item_value)); + Validate(item_value, additional_properties_schema, item_path); + } + } else if (instance_size > tuple_size) { + errors_.push_back(Error(path, FormatErrorMessage( + kArrayMaxItems, base::Uint64ToString(tuple_size)))); + } +} + +void JSONSchemaValidator::ValidateString(const base::Value* instance, + const base::DictionaryValue* schema, + const std::string& path) { + std::string value; + CHECK(instance->GetAsString(&value)); + + int min_length = 0; + if (schema->GetInteger(schema::kMinLength, &min_length)) { + CHECK(min_length >= 0); + if (value.size() < static_cast<size_t>(min_length)) { + errors_.push_back(Error(path, FormatErrorMessage( + kStringMinLength, base::IntToString(min_length)))); + } + } + + int max_length = 0; + if (schema->GetInteger(schema::kMaxLength, &max_length)) { + CHECK(max_length >= 0); + if (value.size() > static_cast<size_t>(max_length)) { + errors_.push_back(Error(path, FormatErrorMessage( + kStringMaxLength, base::IntToString(max_length)))); + } + } + + CHECK(!schema->HasKey(schema::kPattern)) << "Pattern is not supported."; +} + +void JSONSchemaValidator::ValidateNumber(const base::Value* instance, + const base::DictionaryValue* schema, + const std::string& path) { + double value = GetNumberValue(instance); + + // TODO(aa): It would be good to test that the double is not infinity or nan, + // but isnan and isinf aren't defined on Windows. + + double minimum = 0; + if (schema->GetDouble(schema::kMinimum, &minimum)) { + if (value < minimum) + errors_.push_back(Error(path, FormatErrorMessage( + kNumberMinimum, base::DoubleToString(minimum)))); + } + + double maximum = 0; + if (schema->GetDouble(schema::kMaximum, &maximum)) { + if (value > maximum) + errors_.push_back(Error(path, FormatErrorMessage( + kNumberMaximum, base::DoubleToString(maximum)))); + } +} + +bool JSONSchemaValidator::ValidateType(const base::Value* instance, + const std::string& expected_type, + const std::string& path) { + std::string actual_type = GetJSONSchemaType(instance); + if (expected_type == actual_type || + (expected_type == schema::kNumber && actual_type == schema::kInteger)) { + return true; + } else if (expected_type == schema::kInteger && + actual_type == schema::kNumber) { + errors_.push_back(Error(path, kInvalidTypeIntegerNumber)); + return false; + } else { + errors_.push_back(Error(path, FormatErrorMessage( + kInvalidType, expected_type, actual_type))); + return false; + } +} + +bool JSONSchemaValidator::SchemaAllowsAnyAdditionalItems( + const base::DictionaryValue* schema, + const base::DictionaryValue** additional_properties_schema) { + // If the validator allows additional properties globally, and this schema + // doesn't override, then we can exit early. + schema->GetDictionary(schema::kAdditionalProperties, + additional_properties_schema); + + if (*additional_properties_schema) { + std::string additional_properties_type(schema::kAny); + CHECK((*additional_properties_schema)->GetString( + schema::kType, &additional_properties_type)); + return additional_properties_type == schema::kAny; + } else { + return default_allow_additional_properties_; + } +} diff --git a/chromium/components/json_schema/json_schema_validator.h b/chromium/components/json_schema/json_schema_validator.h new file mode 100644 index 00000000000..4584a9da77a --- /dev/null +++ b/chromium/components/json_schema/json_schema_validator.h @@ -0,0 +1,235 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_JSON_SCHEMA_JSON_SCHEMA_VALIDATOR_H_ +#define COMPONENTS_JSON_SCHEMA_JSON_SCHEMA_VALIDATOR_H_ + +#include <map> +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" + +namespace base { +class DictionaryValue; +class ListValue; +class StringValue; +class Value; +} + +//============================================================================== +// This class implements a subset of JSON Schema. +// See: http://www.json.com/json-schema-proposal/ for more details. +// +// There is also an older JavaScript implementation of the same functionality in +// chrome/renderer/resources/json_schema.js. +// +// The following features of JSON Schema are not implemented: +// - requires +// - unique +// - disallow +// - union types (but replaced with 'choices') +// - number.maxDecimal +// - string.pattern +// +// The following properties are not applicable to the interface exposed by +// this class: +// - options +// - readonly +// - title +// - description +// - format +// - default +// - transient +// - hidden +// +// There are also these departures from the JSON Schema proposal: +// - null counts as 'unspecified' for optional values +// - added the 'choices' property, to allow specifying a list of possible types +// for a value +// - by default an "object" typed schema does not allow additional properties. +// if present, "additionalProperties" is to be a schema against which all +// additional properties will be validated. +//============================================================================== +class JSONSchemaValidator { + public: + // Details about a validation error. + struct Error { + Error(); + + explicit Error(const std::string& message); + + Error(const std::string& path, const std::string& message); + + // The path to the location of the error in the JSON structure. + std::string path; + + // An english message describing the error. + std::string message; + }; + + // Error messages. + static const char kUnknownTypeReference[]; + static const char kInvalidChoice[]; + static const char kInvalidEnum[]; + static const char kObjectPropertyIsRequired[]; + static const char kUnexpectedProperty[]; + static const char kArrayMinItems[]; + static const char kArrayMaxItems[]; + static const char kArrayItemRequired[]; + static const char kStringMinLength[]; + static const char kStringMaxLength[]; + static const char kStringPattern[]; + static const char kNumberMinimum[]; + static const char kNumberMaximum[]; + static const char kInvalidType[]; + static const char kInvalidTypeIntegerNumber[]; + + // Classifies a Value as one of the JSON schema primitive types. + static std::string GetJSONSchemaType(const base::Value* value); + + // Utility methods to format error messages. The first method can have one + // wildcard represented by '*', which is replaced with s1. The second method + // can have two, which are replaced by s1 and s2. + static std::string FormatErrorMessage(const std::string& format, + const std::string& s1); + static std::string FormatErrorMessage(const std::string& format, + const std::string& s1, + const std::string& s2); + + // Verifies if |schema| is a valid JSON v3 schema. When this validation passes + // then |schema| is valid JSON that can be parsed into a DictionaryValue, + // and that DictionaryValue can be used to build a JSONSchemaValidator. + // Returns the parsed DictionaryValue when |schema| validated, otherwise + // returns NULL. In that case, |error| contains an error description. + static scoped_ptr<base::DictionaryValue> IsValidSchema( + const std::string& schema, + std::string* error); + + // Creates a validator for the specified schema. + // + // NOTE: This constructor assumes that |schema| is well formed and valid. + // Errors will result in CHECK at runtime; this constructor should not be used + // with untrusted schemas. + explicit JSONSchemaValidator(base::DictionaryValue* schema); + + // Creates a validator for the specified schema and user-defined types. Each + // type must be a valid JSONSchema type description with an additional "id" + // field. Schema objects in |schema| can refer to these types with the "$ref" + // property. + // + // NOTE: This constructor assumes that |schema| and |types| are well-formed + // and valid. Errors will result in CHECK at runtime; this constructor should + // not be used with untrusted schemas. + JSONSchemaValidator(base::DictionaryValue* schema, base::ListValue* types); + + ~JSONSchemaValidator(); + + // Whether the validator allows additional items for objects and lists, beyond + // those defined by their schema, by default. + // + // This setting defaults to false: all items in an instance list or object + // must be defined by the corresponding schema. + // + // This setting can be overridden on individual object and list schemas by + // setting the "additionalProperties" field. + bool default_allow_additional_properties() const { + return default_allow_additional_properties_; + } + + void set_default_allow_additional_properties(bool val) { + default_allow_additional_properties_ = val; + } + + // Returns any errors from the last call to to Validate(). + const std::vector<Error>& errors() const { + return errors_; + } + + // Validates a JSON value. Returns true if the instance is valid, false + // otherwise. If false is returned any errors are available from the errors() + // getter. + bool Validate(const base::Value* instance); + + private: + typedef std::map<std::string, const base::DictionaryValue*> TypeMap; + + // Each of the below methods handle a subset of the validation process. The + // path paramater is the path to |instance| from the root of the instance tree + // and is used in error messages. + + // Validates any instance node against any schema node. This is called for + // every node in the instance tree, and it just decides which of the more + // detailed methods to call. + void Validate(const base::Value* instance, + const base::DictionaryValue* schema, + const std::string& path); + + // Validates a node against a list of possible schemas. If any one of the + // schemas match, the node is valid. + void ValidateChoices(const base::Value* instance, + const base::ListValue* choices, + const std::string& path); + + // Validates a node against a list of exact primitive values, eg 42, "foobar". + void ValidateEnum(const base::Value* instance, + const base::ListValue* choices, + const std::string& path); + + // Validates a JSON object against an object schema node. + void ValidateObject(const base::DictionaryValue* instance, + const base::DictionaryValue* schema, + const std::string& path); + + // Validates a JSON array against an array schema node. + void ValidateArray(const base::ListValue* instance, + const base::DictionaryValue* schema, + const std::string& path); + + // Validates a JSON array against an array schema node configured to be a + // tuple. In a tuple, there is one schema node for each item expected in the + // array. + void ValidateTuple(const base::ListValue* instance, + const base::DictionaryValue* schema, + const std::string& path); + + // Validate a JSON string against a string schema node. + void ValidateString(const base::Value* instance, + const base::DictionaryValue* schema, + const std::string& path); + + // Validate a JSON number against a number schema node. + void ValidateNumber(const base::Value* instance, + const base::DictionaryValue* schema, + const std::string& path); + + // Validates that the JSON node |instance| has |expected_type|. + bool ValidateType(const base::Value* instance, + const std::string& expected_type, + const std::string& path); + + // Returns true if |schema| will allow additional items of any type. + bool SchemaAllowsAnyAdditionalItems( + const base::DictionaryValue* schema, + const base::DictionaryValue** addition_items_schema); + + // The root schema node. + base::DictionaryValue* schema_root_; + + // Map of user-defined name to type. + TypeMap types_; + + // Whether we allow additional properties on objects by default. This can be + // overridden by the allow_additional_properties flag on an Object schema. + bool default_allow_additional_properties_; + + // Errors accumulated since the last call to Validate(). + std::vector<Error> errors_; + + + DISALLOW_COPY_AND_ASSIGN(JSONSchemaValidator); +}; + +#endif // COMPONENTS_JSON_SCHEMA_JSON_SCHEMA_VALIDATOR_H_ diff --git a/chromium/components/json_schema/json_schema_validator_unittest.cc b/chromium/components/json_schema/json_schema_validator_unittest.cc new file mode 100644 index 00000000000..4844ed1a888 --- /dev/null +++ b/chromium/components/json_schema/json_schema_validator_unittest.cc @@ -0,0 +1,129 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/values.h" +#include "components/json_schema/json_schema_validator.h" +#include "components/json_schema/json_schema_validator_unittest_base.h" +#include "testing/gtest/include/gtest/gtest.h" + +class JSONSchemaValidatorCPPTest : public JSONSchemaValidatorTestBase { + public: + JSONSchemaValidatorCPPTest() + : JSONSchemaValidatorTestBase(JSONSchemaValidatorTestBase::CPP) { + } + + protected: + virtual void ExpectValid(const std::string& test_source, + base::Value* instance, + base::DictionaryValue* schema, + base::ListValue* types) OVERRIDE { + JSONSchemaValidator validator(schema, types); + if (validator.Validate(instance)) + return; + + for (size_t i = 0; i < validator.errors().size(); ++i) { + ADD_FAILURE() << test_source << ": " + << validator.errors()[i].path << ": " + << validator.errors()[i].message; + } + } + + virtual void ExpectNotValid( + const std::string& test_source, + base::Value* instance, base::DictionaryValue* schema, + base::ListValue* types, + const std::string& expected_error_path, + const std::string& expected_error_message) OVERRIDE { + JSONSchemaValidator validator(schema, types); + if (validator.Validate(instance)) { + ADD_FAILURE() << test_source; + return; + } + + ASSERT_EQ(1u, validator.errors().size()) << test_source; + EXPECT_EQ(expected_error_path, validator.errors()[0].path) << test_source; + EXPECT_EQ(expected_error_message, validator.errors()[0].message) + << test_source; + } +}; + +TEST_F(JSONSchemaValidatorCPPTest, Test) { + RunTests(); +} + +TEST(JSONSchemaValidator, IsValidSchema) { + std::string error; + EXPECT_FALSE(JSONSchemaValidator::IsValidSchema("", &error)); + EXPECT_FALSE(JSONSchemaValidator::IsValidSchema("\0", &error)); + EXPECT_FALSE(JSONSchemaValidator::IsValidSchema("string", &error)); + EXPECT_FALSE(JSONSchemaValidator::IsValidSchema("\"string\"", &error)); + EXPECT_FALSE(JSONSchemaValidator::IsValidSchema("[]", &error)); + EXPECT_FALSE(JSONSchemaValidator::IsValidSchema("{}", &error)); + EXPECT_FALSE(JSONSchemaValidator::IsValidSchema( + "{ \"type\": 123 }", &error)); + EXPECT_FALSE(JSONSchemaValidator::IsValidSchema( + "{ \"type\": \"invalid\" }", &error)); + EXPECT_FALSE(JSONSchemaValidator::IsValidSchema( + "{" + " \"type\": \"object\"," + " \"properties\": []" // Invalid properties type. + "}", &error)); + EXPECT_FALSE(JSONSchemaValidator::IsValidSchema( + "{" + " \"type\": \"string\"," + " \"maxLength\": -1" // Must be >= 0. + "}", &error)); + EXPECT_FALSE(JSONSchemaValidator::IsValidSchema( + "{" + " \"type\": \"string\"," + " \"enum\": [ {} ]," // "enum" must contain simple values. + "}", &error)); + EXPECT_FALSE(JSONSchemaValidator::IsValidSchema( + "{" + " \"type\": \"array\"," + " \"items\": [ 123 ]," // "items" must contain a schema or schemas. + "}", &error)); + EXPECT_TRUE(JSONSchemaValidator::IsValidSchema( + "{ \"type\": \"object\" }", &error)) << error; + EXPECT_TRUE(JSONSchemaValidator::IsValidSchema( + "{ \"type\": [\"object\", \"array\"] }", &error)) << error; + EXPECT_TRUE(JSONSchemaValidator::IsValidSchema( + "{" + " \"type\": [\"object\", \"array\"]," + " \"properties\": {" + " \"string-property\": {" + " \"type\": \"string\"," + " \"minLength\": 1," + " \"maxLength\": 100," + " \"title\": \"The String Policy\"," + " \"description\": \"This policy controls the String widget.\"" + " }," + " \"integer-property\": {" + " \"type\": \"number\"," + " \"minimum\": 1000.0," + " \"maximum\": 9999.0" + " }," + " \"enum-property\": {" + " \"type\": \"integer\"," + " \"enum\": [0, 1, 10, 100]" + " }," + " \"items-property\": {" + " \"type\": \"array\"," + " \"items\": {" + " \"type\": \"string\"" + " }" + " }," + " \"items-list-property\": {" + " \"type\": \"array\"," + " \"items\": [" + " { \"type\": \"string\" }," + " { \"type\": \"integer\" }" + " ]" + " }" + " }," + " \"additionalProperties\": {" + " \"type\": \"any\"" + " }" + "}", &error)) << error; +} diff --git a/chromium/components/json_schema/json_schema_validator_unittest_base.cc b/chromium/components/json_schema/json_schema_validator_unittest_base.cc new file mode 100644 index 00000000000..2e936a2eb9a --- /dev/null +++ b/chromium/components/json_schema/json_schema_validator_unittest_base.cc @@ -0,0 +1,730 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/json_schema/json_schema_validator_unittest_base.h" + +#include <cfloat> +#include <cmath> +#include <limits> + +#include "base/base_paths.h" +#include "base/file_util.h" +#include "base/json/json_file_value_serializer.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/path_service.h" +#include "base/strings/stringprintf.h" +#include "base/values.h" +#include "components/json_schema/json_schema_constants.h" +#include "components/json_schema/json_schema_validator.h" + +namespace schema = json_schema_constants; + +namespace { + +#define TEST_SOURCE base::StringPrintf("%s:%i", __FILE__, __LINE__) + +base::Value* LoadValue(const std::string& filename) { + base::FilePath path; + PathService::Get(base::DIR_SOURCE_ROOT, &path); + path = path.AppendASCII("components") + .AppendASCII("test") + .AppendASCII("data") + .AppendASCII("json_schema") + .AppendASCII(filename); + EXPECT_TRUE(base::PathExists(path)); + + std::string error_message; + JSONFileValueSerializer serializer(path); + base::Value* result = serializer.Deserialize(NULL, &error_message); + if (!result) + ADD_FAILURE() << "Could not parse JSON: " << error_message; + return result; +} + +base::Value* LoadValue(const std::string& filename, base::Value::Type type) { + scoped_ptr<base::Value> result(LoadValue(filename)); + if (!result.get()) + return NULL; + if (!result->IsType(type)) { + ADD_FAILURE() << "Expected type " << type << ", got: " << result->GetType(); + return NULL; + } + return result.release(); +} + +base::ListValue* LoadList(const std::string& filename) { + return static_cast<base::ListValue*>( + LoadValue(filename, base::Value::TYPE_LIST)); +} + +base::DictionaryValue* LoadDictionary(const std::string& filename) { + return static_cast<base::DictionaryValue*>( + LoadValue(filename, base::Value::TYPE_DICTIONARY)); +} + +} // namespace + + +JSONSchemaValidatorTestBase::JSONSchemaValidatorTestBase( + JSONSchemaValidatorTestBase::ValidatorType type) + : type_(type) { +} + +void JSONSchemaValidatorTestBase::RunTests() { + TestComplex(); + TestStringPattern(); + TestEnum(); + TestChoices(); + TestExtends(); + TestObject(); + TestTypeReference(); + TestArrayTuple(); + TestArrayNonTuple(); + TestString(); + TestNumber(); + TestTypeClassifier(); + TestTypes(); +} + +void JSONSchemaValidatorTestBase::TestComplex() { + scoped_ptr<base::DictionaryValue> schema( + LoadDictionary("complex_schema.json")); + scoped_ptr<base::ListValue> instance(LoadList("complex_instance.json")); + + ASSERT_TRUE(schema.get()); + ASSERT_TRUE(instance.get()); + + ExpectValid(TEST_SOURCE, instance.get(), schema.get(), NULL); + instance->Remove(instance->GetSize() - 1, NULL); + ExpectValid(TEST_SOURCE, instance.get(), schema.get(), NULL); + instance->Append(new base::DictionaryValue()); + ExpectNotValid(TEST_SOURCE, instance.get(), schema.get(), NULL, "1", + JSONSchemaValidator::FormatErrorMessage( + JSONSchemaValidator::kInvalidType, + schema::kNumber, + schema::kObject)); + instance->Remove(instance->GetSize() - 1, NULL); + + base::DictionaryValue* item = NULL; + ASSERT_TRUE(instance->GetDictionary(0, &item)); + item->SetString("url", "xxxxxxxxxxx"); + + ExpectNotValid(TEST_SOURCE, instance.get(), schema.get(), NULL, + "0.url", + JSONSchemaValidator::FormatErrorMessage( + JSONSchemaValidator::kStringMaxLength, "10")); +} + +void JSONSchemaValidatorTestBase::TestStringPattern() { + // Regex patterns not supported in CPP validator. + if (type_ == CPP) + return; + + scoped_ptr<base::DictionaryValue> schema(new base::DictionaryValue()); + schema->SetString(schema::kType, schema::kString); + schema->SetString(schema::kPattern, "foo+"); + + ExpectValid(TEST_SOURCE, + scoped_ptr<base::Value>(new base::StringValue("foo")).get(), + schema.get(), NULL); + ExpectValid(TEST_SOURCE, + scoped_ptr<base::Value>(new base::StringValue("foooooo")).get(), + schema.get(), NULL); + ExpectNotValid(TEST_SOURCE, + scoped_ptr<base::Value>(new base::StringValue("bar")).get(), + schema.get(), + NULL, + std::string(), + JSONSchemaValidator::FormatErrorMessage( + JSONSchemaValidator::kStringPattern, "foo+")); +} + +void JSONSchemaValidatorTestBase::TestEnum() { + scoped_ptr<base::DictionaryValue> schema(LoadDictionary("enum_schema.json")); + + ExpectValid(TEST_SOURCE, + scoped_ptr<base::Value>(new base::StringValue("foo")).get(), + schema.get(), NULL); + ExpectValid(TEST_SOURCE, + scoped_ptr<base::Value>(new base::FundamentalValue(42)).get(), + schema.get(), NULL); + ExpectValid(TEST_SOURCE, + scoped_ptr<base::Value>(new base::FundamentalValue(false)).get(), + schema.get(), NULL); + + ExpectNotValid(TEST_SOURCE, + scoped_ptr<base::Value>(new base::StringValue("42")).get(), + schema.get(), + NULL, + std::string(), + JSONSchemaValidator::kInvalidEnum); + ExpectNotValid(TEST_SOURCE, + scoped_ptr<base::Value>(base::Value::CreateNullValue()).get(), + schema.get(), + NULL, + std::string(), + JSONSchemaValidator::kInvalidEnum); +} + +void JSONSchemaValidatorTestBase::TestChoices() { + scoped_ptr<base::DictionaryValue> schema( + LoadDictionary("choices_schema.json")); + + ExpectValid(TEST_SOURCE, + scoped_ptr<base::Value>(base::Value::CreateNullValue()).get(), + schema.get(), NULL); + ExpectValid(TEST_SOURCE, + scoped_ptr<base::Value>(new base::FundamentalValue(42)).get(), + schema.get(), NULL); + + scoped_ptr<base::DictionaryValue> instance(new base::DictionaryValue()); + instance->SetString("foo", "bar"); + ExpectValid(TEST_SOURCE, instance.get(), schema.get(), NULL); + + ExpectNotValid(TEST_SOURCE, + scoped_ptr<base::Value>(new base::StringValue("foo")).get(), + schema.get(), + NULL, + std::string(), + JSONSchemaValidator::kInvalidChoice); + ExpectNotValid(TEST_SOURCE, + scoped_ptr<base::Value>(new base::ListValue()).get(), + schema.get(), + NULL, + std::string(), + JSONSchemaValidator::kInvalidChoice); + + instance->SetInteger("foo", 42); + ExpectNotValid(TEST_SOURCE, + instance.get(), + schema.get(), + NULL, + std::string(), + JSONSchemaValidator::kInvalidChoice); +} + +void JSONSchemaValidatorTestBase::TestExtends() { + // TODO(aa): JS only +} + +void JSONSchemaValidatorTestBase::TestObject() { + scoped_ptr<base::DictionaryValue> schema(new base::DictionaryValue()); + schema->SetString(schema::kType, schema::kObject); + schema->SetString("properties.foo.type", schema::kString); + schema->SetString("properties.bar.type", schema::kInteger); + + scoped_ptr<base::DictionaryValue> instance(new base::DictionaryValue()); + instance->SetString("foo", "foo"); + instance->SetInteger("bar", 42); + + ExpectValid(TEST_SOURCE, instance.get(), schema.get(), NULL); + + instance->SetBoolean("extra", true); + ExpectNotValid(TEST_SOURCE, instance.get(), schema.get(), NULL, + "extra", JSONSchemaValidator::kUnexpectedProperty); + + instance->Remove("extra", NULL); + instance->Remove("bar", NULL); + ExpectNotValid(TEST_SOURCE, instance.get(), schema.get(), NULL, "bar", + JSONSchemaValidator::kObjectPropertyIsRequired); + + instance->SetString("bar", "42"); + ExpectNotValid(TEST_SOURCE, instance.get(), schema.get(), NULL, "bar", + JSONSchemaValidator::FormatErrorMessage( + JSONSchemaValidator::kInvalidType, + schema::kInteger, + schema::kString)); + + base::DictionaryValue* additional_properties = new base::DictionaryValue(); + additional_properties->SetString(schema::kType, schema::kAny); + schema->Set(schema::kAdditionalProperties, additional_properties); + + instance->SetInteger("bar", 42); + instance->SetBoolean("extra", true); + ExpectValid(TEST_SOURCE, instance.get(), schema.get(), NULL); + + instance->SetString("extra", "foo"); + ExpectValid(TEST_SOURCE, instance.get(), schema.get(), NULL); + + additional_properties->SetString(schema::kType, schema::kBoolean); + instance->SetBoolean("extra", true); + ExpectValid(TEST_SOURCE, instance.get(), schema.get(), NULL); + + instance->SetString("extra", "foo"); + ExpectNotValid(TEST_SOURCE, instance.get(), schema.get(), NULL, + "extra", JSONSchemaValidator::FormatErrorMessage( + JSONSchemaValidator::kInvalidType, + schema::kBoolean, + schema::kString)); + + base::DictionaryValue* properties = NULL; + base::DictionaryValue* bar_property = NULL; + ASSERT_TRUE(schema->GetDictionary(schema::kProperties, &properties)); + ASSERT_TRUE(properties->GetDictionary("bar", &bar_property)); + + bar_property->SetBoolean(schema::kOptional, true); + instance->Remove("extra", NULL); + ExpectValid(TEST_SOURCE, instance.get(), schema.get(), NULL); + instance->Remove("bar", NULL); + ExpectValid(TEST_SOURCE, instance.get(), schema.get(), NULL); + instance->Set("bar", base::Value::CreateNullValue()); + ExpectNotValid(TEST_SOURCE, instance.get(), schema.get(), NULL, + "bar", JSONSchemaValidator::FormatErrorMessage( + JSONSchemaValidator::kInvalidType, + schema::kInteger, + schema::kNull)); + instance->SetString("bar", "42"); + ExpectNotValid(TEST_SOURCE, instance.get(), schema.get(), NULL, + "bar", JSONSchemaValidator::FormatErrorMessage( + JSONSchemaValidator::kInvalidType, + schema::kInteger, + schema::kString)); +} + +void JSONSchemaValidatorTestBase::TestTypeReference() { + scoped_ptr<base::ListValue> types(LoadList("reference_types.json")); + ASSERT_TRUE(types.get()); + + scoped_ptr<base::DictionaryValue> schema(new base::DictionaryValue()); + schema->SetString(schema::kType, schema::kObject); + schema->SetString("properties.foo.type", schema::kString); + schema->SetString("properties.bar.$ref", "Max10Int"); + schema->SetString("properties.baz.$ref", "MinLengthString"); + + scoped_ptr<base::DictionaryValue> schema_inline(new base::DictionaryValue()); + schema_inline->SetString(schema::kType, schema::kObject); + schema_inline->SetString("properties.foo.type", schema::kString); + schema_inline->SetString("properties.bar.id", "NegativeInt"); + schema_inline->SetString("properties.bar.type", schema::kInteger); + schema_inline->SetInteger("properties.bar.maximum", 0); + schema_inline->SetString("properties.baz.$ref", "NegativeInt"); + + scoped_ptr<base::DictionaryValue> instance(new base::DictionaryValue()); + instance->SetString("foo", "foo"); + instance->SetInteger("bar", 4); + instance->SetString("baz", "ab"); + + scoped_ptr<base::DictionaryValue> instance_inline( + new base::DictionaryValue()); + instance_inline->SetString("foo", "foo"); + instance_inline->SetInteger("bar", -4); + instance_inline->SetInteger("baz", -2); + + ExpectValid(TEST_SOURCE, instance.get(), schema.get(), types.get()); + ExpectValid(TEST_SOURCE, instance_inline.get(), schema_inline.get(), NULL); + + // Validation failure, but successful schema reference. + instance->SetString("baz", "a"); + ExpectNotValid(TEST_SOURCE, instance.get(), schema.get(), types.get(), + "baz", JSONSchemaValidator::FormatErrorMessage( + JSONSchemaValidator::kStringMinLength, "2")); + + instance_inline->SetInteger("bar", 20); + ExpectNotValid(TEST_SOURCE, instance_inline.get(), schema_inline.get(), NULL, + "bar", JSONSchemaValidator::FormatErrorMessage( + JSONSchemaValidator::kNumberMaximum, "0")); + + // Remove MinLengthString type. + types->Remove(types->GetSize() - 1, NULL); + instance->SetString("baz", "ab"); + ExpectNotValid(TEST_SOURCE, instance.get(), schema.get(), types.get(), + "bar", JSONSchemaValidator::FormatErrorMessage( + JSONSchemaValidator::kUnknownTypeReference, + "Max10Int")); + + // Remove internal type "NegativeInt". + schema_inline->Remove("properties.bar", NULL); + instance_inline->Remove("bar", NULL); + ExpectNotValid(TEST_SOURCE, instance_inline.get(), schema_inline.get(), NULL, + "baz", JSONSchemaValidator::FormatErrorMessage( + JSONSchemaValidator::kUnknownTypeReference, + "NegativeInt")); +} + +void JSONSchemaValidatorTestBase::TestArrayTuple() { + scoped_ptr<base::DictionaryValue> schema( + LoadDictionary("array_tuple_schema.json")); + ASSERT_TRUE(schema.get()); + + scoped_ptr<base::ListValue> instance(new base::ListValue()); + instance->Append(new base::StringValue("42")); + instance->Append(new base::FundamentalValue(42)); + + ExpectValid(TEST_SOURCE, instance.get(), schema.get(), NULL); + + instance->Append(new base::StringValue("anything")); + ExpectNotValid(TEST_SOURCE, + instance.get(), + schema.get(), + NULL, + std::string(), + JSONSchemaValidator::FormatErrorMessage( + JSONSchemaValidator::kArrayMaxItems, "2")); + + instance->Remove(1, NULL); + instance->Remove(1, NULL); + ExpectNotValid(TEST_SOURCE, instance.get(), schema.get(), NULL, "1", + JSONSchemaValidator::kArrayItemRequired); + + instance->Set(0, new base::FundamentalValue(42)); + instance->Append(new base::FundamentalValue(42)); + ExpectNotValid(TEST_SOURCE, instance.get(), schema.get(), NULL, "0", + JSONSchemaValidator::FormatErrorMessage( + JSONSchemaValidator::kInvalidType, + schema::kString, + schema::kInteger)); + + base::DictionaryValue* additional_properties = new base::DictionaryValue(); + additional_properties->SetString(schema::kType, schema::kAny); + schema->Set(schema::kAdditionalProperties, additional_properties); + instance->Set(0, new base::StringValue("42")); + instance->Append(new base::StringValue("anything")); + ExpectValid(TEST_SOURCE, instance.get(), schema.get(), NULL); + instance->Set(2, new base::ListValue()); + ExpectValid(TEST_SOURCE, instance.get(), schema.get(), NULL); + + additional_properties->SetString(schema::kType, schema::kBoolean); + ExpectNotValid(TEST_SOURCE, instance.get(), schema.get(), NULL, "2", + JSONSchemaValidator::FormatErrorMessage( + JSONSchemaValidator::kInvalidType, + schema::kBoolean, + schema::kArray)); + instance->Set(2, new base::FundamentalValue(false)); + ExpectValid(TEST_SOURCE, instance.get(), schema.get(), NULL); + + base::ListValue* items_schema = NULL; + base::DictionaryValue* item0_schema = NULL; + ASSERT_TRUE(schema->GetList(schema::kItems, &items_schema)); + ASSERT_TRUE(items_schema->GetDictionary(0, &item0_schema)); + item0_schema->SetBoolean(schema::kOptional, true); + instance->Remove(2, NULL); + ExpectValid(TEST_SOURCE, instance.get(), schema.get(), NULL); + // TODO(aa): I think this is inconsistent with the handling of NULL+optional + // for objects. + instance->Set(0, base::Value::CreateNullValue()); + ExpectValid(TEST_SOURCE, instance.get(), schema.get(), NULL); + instance->Set(0, new base::FundamentalValue(42)); + ExpectNotValid(TEST_SOURCE, instance.get(), schema.get(), NULL, "0", + JSONSchemaValidator::FormatErrorMessage( + JSONSchemaValidator::kInvalidType, + schema::kString, + schema::kInteger)); +} + +void JSONSchemaValidatorTestBase::TestArrayNonTuple() { + scoped_ptr<base::DictionaryValue> schema(new base::DictionaryValue()); + schema->SetString(schema::kType, schema::kArray); + schema->SetString("items.type", schema::kString); + schema->SetInteger(schema::kMinItems, 2); + schema->SetInteger(schema::kMaxItems, 3); + + scoped_ptr<base::ListValue> instance(new base::ListValue()); + instance->Append(new base::StringValue("x")); + instance->Append(new base::StringValue("x")); + + ExpectValid(TEST_SOURCE, instance.get(), schema.get(), NULL); + instance->Append(new base::StringValue("x")); + ExpectValid(TEST_SOURCE, instance.get(), schema.get(), NULL); + + instance->Append(new base::StringValue("x")); + ExpectNotValid(TEST_SOURCE, + instance.get(), + schema.get(), + NULL, + std::string(), + JSONSchemaValidator::FormatErrorMessage( + JSONSchemaValidator::kArrayMaxItems, "3")); + instance->Remove(1, NULL); + instance->Remove(1, NULL); + instance->Remove(1, NULL); + ExpectNotValid(TEST_SOURCE, + instance.get(), + schema.get(), + NULL, + std::string(), + JSONSchemaValidator::FormatErrorMessage( + JSONSchemaValidator::kArrayMinItems, "2")); + + instance->Remove(1, NULL); + instance->Append(new base::FundamentalValue(42)); + ExpectNotValid(TEST_SOURCE, instance.get(), schema.get(), NULL, "1", + JSONSchemaValidator::FormatErrorMessage( + JSONSchemaValidator::kInvalidType, + schema::kString, + schema::kInteger)); +} + +void JSONSchemaValidatorTestBase::TestString() { + scoped_ptr<base::DictionaryValue> schema(new base::DictionaryValue()); + schema->SetString(schema::kType, schema::kString); + schema->SetInteger(schema::kMinLength, 1); + schema->SetInteger(schema::kMaxLength, 10); + + ExpectValid(TEST_SOURCE, + scoped_ptr<base::Value>(new base::StringValue("x")).get(), + schema.get(), NULL); + ExpectValid(TEST_SOURCE, + scoped_ptr<base::Value>( + new base::StringValue("xxxxxxxxxx")).get(), + schema.get(), NULL); + + ExpectNotValid( + TEST_SOURCE, + scoped_ptr<base::Value>(new base::StringValue(std::string())).get(), + schema.get(), + NULL, + std::string(), + JSONSchemaValidator::FormatErrorMessage( + JSONSchemaValidator::kStringMinLength, "1")); + ExpectNotValid( + TEST_SOURCE, + scoped_ptr<base::Value>(new base::StringValue("xxxxxxxxxxx")).get(), + schema.get(), + NULL, + std::string(), + JSONSchemaValidator::FormatErrorMessage( + JSONSchemaValidator::kStringMaxLength, "10")); +} + +void JSONSchemaValidatorTestBase::TestNumber() { + scoped_ptr<base::DictionaryValue> schema(new base::DictionaryValue()); + schema->SetString(schema::kType, schema::kNumber); + schema->SetInteger(schema::kMinimum, 1); + schema->SetInteger(schema::kMaximum, 100); + schema->SetInteger("maxDecimal", 2); + + ExpectValid(TEST_SOURCE, + scoped_ptr<base::Value>(new base::FundamentalValue(1)).get(), + schema.get(), NULL); + ExpectValid(TEST_SOURCE, + scoped_ptr<base::Value>(new base::FundamentalValue(50)).get(), + schema.get(), NULL); + ExpectValid(TEST_SOURCE, + scoped_ptr<base::Value>(new base::FundamentalValue(100)).get(), + schema.get(), NULL); + ExpectValid(TEST_SOURCE, + scoped_ptr<base::Value>(new base::FundamentalValue(88.88)).get(), + schema.get(), NULL); + + ExpectNotValid(TEST_SOURCE, + scoped_ptr<base::Value>(new base::FundamentalValue(0.5)).get(), + schema.get(), + NULL, + std::string(), + JSONSchemaValidator::FormatErrorMessage( + JSONSchemaValidator::kNumberMinimum, "1")); + ExpectNotValid( + TEST_SOURCE, + scoped_ptr<base::Value>(new base::FundamentalValue(100.1)).get(), + schema.get(), + NULL, + std::string(), + JSONSchemaValidator::FormatErrorMessage( + JSONSchemaValidator::kNumberMaximum, "100")); +} + +void JSONSchemaValidatorTestBase::TestTypeClassifier() { + EXPECT_EQ(std::string(schema::kBoolean), + JSONSchemaValidator::GetJSONSchemaType( + scoped_ptr<base::Value>( + new base::FundamentalValue(true)).get())); + EXPECT_EQ(std::string(schema::kBoolean), + JSONSchemaValidator::GetJSONSchemaType( + scoped_ptr<base::Value>( + new base::FundamentalValue(false)).get())); + + // It doesn't matter whether the C++ type is 'integer' or 'real'. If the + // number is integral and within the representable range of integers in + // double, it's classified as 'integer'. + EXPECT_EQ(std::string(schema::kInteger), + JSONSchemaValidator::GetJSONSchemaType( + scoped_ptr<base::Value>(new base::FundamentalValue(42)).get())); + EXPECT_EQ(std::string(schema::kInteger), + JSONSchemaValidator::GetJSONSchemaType( + scoped_ptr<base::Value>(new base::FundamentalValue(0)).get())); + EXPECT_EQ(std::string(schema::kInteger), + JSONSchemaValidator::GetJSONSchemaType( + scoped_ptr<base::Value>(new base::FundamentalValue(42)).get())); + EXPECT_EQ(std::string(schema::kInteger), + JSONSchemaValidator::GetJSONSchemaType(scoped_ptr<base::Value>( + new base::FundamentalValue(pow(2.0, DBL_MANT_DIG))).get())); + EXPECT_EQ(std::string(schema::kInteger), + JSONSchemaValidator::GetJSONSchemaType(scoped_ptr<base::Value>( + new base::FundamentalValue(pow(-2.0, DBL_MANT_DIG))).get())); + + // "number" is only used for non-integral numbers, or numbers beyond what + // double can accurately represent. + EXPECT_EQ(std::string(schema::kNumber), + JSONSchemaValidator::GetJSONSchemaType( + scoped_ptr<base::Value>( + new base::FundamentalValue(88.8)).get())); + EXPECT_EQ(std::string(schema::kNumber), + JSONSchemaValidator::GetJSONSchemaType(scoped_ptr<base::Value>( + new base::FundamentalValue(pow(2.0, DBL_MANT_DIG) * 2)).get())); + EXPECT_EQ(std::string(schema::kNumber), + JSONSchemaValidator::GetJSONSchemaType(scoped_ptr<base::Value>( + new base::FundamentalValue( + pow(-2.0, DBL_MANT_DIG) * 2)).get())); + + EXPECT_EQ(std::string(schema::kString), + JSONSchemaValidator::GetJSONSchemaType( + scoped_ptr<base::Value>(new base::StringValue("foo")).get())); + EXPECT_EQ(std::string(schema::kArray), + JSONSchemaValidator::GetJSONSchemaType( + scoped_ptr<base::Value>(new base::ListValue()).get())); + EXPECT_EQ(std::string(schema::kObject), + JSONSchemaValidator::GetJSONSchemaType( + scoped_ptr<base::Value>(new base::DictionaryValue()).get())); + EXPECT_EQ(std::string(schema::kNull), + JSONSchemaValidator::GetJSONSchemaType( + scoped_ptr<base::Value>(base::Value::CreateNullValue()).get())); +} + +void JSONSchemaValidatorTestBase::TestTypes() { + scoped_ptr<base::DictionaryValue> schema(new base::DictionaryValue()); + + // valid + schema->SetString(schema::kType, schema::kObject); + ExpectValid(TEST_SOURCE, + scoped_ptr<base::Value>(new base::DictionaryValue()).get(), + schema.get(), NULL); + + schema->SetString(schema::kType, schema::kArray); + ExpectValid(TEST_SOURCE, scoped_ptr<base::Value>(new base::ListValue()).get(), + schema.get(), NULL); + + schema->SetString(schema::kType, schema::kString); + ExpectValid(TEST_SOURCE, + scoped_ptr<base::Value>(new base::StringValue("foobar")).get(), + schema.get(), NULL); + + schema->SetString(schema::kType, schema::kNumber); + ExpectValid(TEST_SOURCE, + scoped_ptr<base::Value>(new base::FundamentalValue(88.8)).get(), + schema.get(), NULL); + ExpectValid(TEST_SOURCE, + scoped_ptr<base::Value>(new base::FundamentalValue(42)).get(), + schema.get(), NULL); + ExpectValid(TEST_SOURCE, + scoped_ptr<base::Value>(new base::FundamentalValue(42)).get(), + schema.get(), NULL); + ExpectValid(TEST_SOURCE, + scoped_ptr<base::Value>(new base::FundamentalValue(0)).get(), + schema.get(), NULL); + + schema->SetString(schema::kType, schema::kInteger); + ExpectValid(TEST_SOURCE, + scoped_ptr<base::Value>(new base::FundamentalValue(42)).get(), + schema.get(), NULL); + ExpectValid(TEST_SOURCE, + scoped_ptr<base::Value>(new base::FundamentalValue(42)).get(), + schema.get(), NULL); + ExpectValid(TEST_SOURCE, + scoped_ptr<base::Value>(new base::FundamentalValue(0)).get(), + schema.get(), NULL); + ExpectValid(TEST_SOURCE, + scoped_ptr<base::Value>( + new base::FundamentalValue(pow(2.0, DBL_MANT_DIG))).get(), + schema.get(), NULL); + ExpectValid(TEST_SOURCE, + scoped_ptr<base::Value>( + new base::FundamentalValue(pow(-2.0, DBL_MANT_DIG))).get(), + schema.get(), NULL); + + schema->SetString(schema::kType, schema::kBoolean); + ExpectValid(TEST_SOURCE, + scoped_ptr<base::Value>(new base::FundamentalValue(false)).get(), + schema.get(), NULL); + ExpectValid(TEST_SOURCE, + scoped_ptr<base::Value>(new base::FundamentalValue(true)).get(), + schema.get(), NULL); + + schema->SetString(schema::kType, schema::kNull); + ExpectValid(TEST_SOURCE, + scoped_ptr<base::Value>(base::Value::CreateNullValue()).get(), + schema.get(), NULL); + + // not valid + schema->SetString(schema::kType, schema::kObject); + ExpectNotValid( + TEST_SOURCE, + scoped_ptr<base::Value>(new base::ListValue()).get(), + schema.get(), + NULL, + std::string(), + JSONSchemaValidator::FormatErrorMessage( + JSONSchemaValidator::kInvalidType, schema::kObject, schema::kArray)); + + schema->SetString(schema::kType, schema::kObject); + ExpectNotValid( + TEST_SOURCE, + scoped_ptr<base::Value>(base::Value::CreateNullValue()).get(), + schema.get(), + NULL, + std::string(), + JSONSchemaValidator::FormatErrorMessage( + JSONSchemaValidator::kInvalidType, schema::kObject, schema::kNull)); + + schema->SetString(schema::kType, schema::kArray); + ExpectNotValid( + TEST_SOURCE, + scoped_ptr<base::Value>(new base::FundamentalValue(42)).get(), + schema.get(), + NULL, + std::string(), + JSONSchemaValidator::FormatErrorMessage( + JSONSchemaValidator::kInvalidType, schema::kArray, schema::kInteger)); + + schema->SetString(schema::kType, schema::kString); + ExpectNotValid( + TEST_SOURCE, + scoped_ptr<base::Value>(new base::FundamentalValue(42)).get(), + schema.get(), + NULL, + std::string(), + JSONSchemaValidator::FormatErrorMessage(JSONSchemaValidator::kInvalidType, + schema::kString, + schema::kInteger)); + + schema->SetString(schema::kType, schema::kNumber); + ExpectNotValid( + TEST_SOURCE, + scoped_ptr<base::Value>(new base::StringValue("42")).get(), + schema.get(), + NULL, + std::string(), + JSONSchemaValidator::FormatErrorMessage( + JSONSchemaValidator::kInvalidType, schema::kNumber, schema::kString)); + + schema->SetString(schema::kType, schema::kInteger); + ExpectNotValid( + TEST_SOURCE, + scoped_ptr<base::Value>(new base::FundamentalValue(88.8)).get(), + schema.get(), + NULL, + std::string(), + JSONSchemaValidator::kInvalidTypeIntegerNumber); + + schema->SetString(schema::kType, schema::kBoolean); + ExpectNotValid( + TEST_SOURCE, + scoped_ptr<base::Value>(new base::FundamentalValue(1)).get(), + schema.get(), + NULL, + std::string(), + JSONSchemaValidator::FormatErrorMessage(JSONSchemaValidator::kInvalidType, + schema::kBoolean, + schema::kInteger)); + + schema->SetString(schema::kType, schema::kNull); + ExpectNotValid( + TEST_SOURCE, + scoped_ptr<base::Value>(new base::FundamentalValue(false)).get(), + schema.get(), + NULL, + std::string(), + JSONSchemaValidator::FormatErrorMessage( + JSONSchemaValidator::kInvalidType, schema::kNull, schema::kBoolean)); +} diff --git a/chromium/components/json_schema/json_schema_validator_unittest_base.h b/chromium/components/json_schema/json_schema_validator_unittest_base.h new file mode 100644 index 00000000000..7b4854e21e8 --- /dev/null +++ b/chromium/components/json_schema/json_schema_validator_unittest_base.h @@ -0,0 +1,63 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_JSON_SCHEMA_JSON_SCHEMA_VALIDATOR_UNITTEST_BASE_H_ +#define COMPONENTS_JSON_SCHEMA_JSON_SCHEMA_VALIDATOR_UNITTEST_BASE_H_ + +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +class DictionaryValue; +class ListValue; +class Value; +} + +// Base class for unit tests for JSONSchemaValidator. There is currently only +// one implementation, JSONSchemaValidatorCPPTest. +// +// TODO(aa): Refactor chrome/test/data/json_schema_test.js into +// JSONSchemaValidatorJSTest that inherits from this. +class JSONSchemaValidatorTestBase : public testing::Test { + public: + enum ValidatorType { + CPP = 1, + JS = 2 + }; + + explicit JSONSchemaValidatorTestBase(ValidatorType type); + + void RunTests(); + + protected: + virtual void ExpectValid(const std::string& test_source, + base::Value* instance, + base::DictionaryValue* schema, + base::ListValue* types) = 0; + + virtual void ExpectNotValid(const std::string& test_source, + base::Value* instance, + base::DictionaryValue* schema, + base::ListValue* types, + const std::string& expected_error_path, + const std::string& expected_error_message) = 0; + + private: + void TestComplex(); + void TestStringPattern(); + void TestEnum(); + void TestChoices(); + void TestExtends(); + void TestObject(); + void TestTypeReference(); + void TestArrayTuple(); + void TestArrayNonTuple(); + void TestString(); + void TestNumber(); + void TestTypeClassifier(); + void TestTypes(); + + ValidatorType type_; +}; + +#endif // COMPONENTS_JSON_SCHEMA_JSON_SCHEMA_VALIDATOR_UNITTEST_BASE_H_ diff --git a/chromium/components/nacl/broker/nacl_broker_listener.cc b/chromium/components/nacl/broker/nacl_broker_listener.cc index cc365d64a7f..c4f268da3cd 100644 --- a/chromium/components/nacl/broker/nacl_broker_listener.cc +++ b/chromium/components/nacl/broker/nacl_broker_listener.cc @@ -105,9 +105,18 @@ void NaClBrokerListener::OnLaunchLoaderThroughBroker( loader_process = content::StartSandboxedProcess(this, cmd_line); if (loader_process) { + // Note: PROCESS_DUP_HANDLE is necessary here, because: + // 1) The current process is the broker, which is the loader's parent. + // 2) The browser is not the loader's parent, and so only gets the + // access rights we confer here. + // 3) The browser calls DuplicateHandle to set up communications with + // the loader. + // 4) The target process handle to DuplicateHandle needs to have + // PROCESS_DUP_HANDLE access rights. DuplicateHandle(::GetCurrentProcess(), loader_process, browser_handle_, &loader_handle_in_browser, - PROCESS_DUP_HANDLE | PROCESS_QUERY_INFORMATION , FALSE, 0); + PROCESS_DUP_HANDLE | PROCESS_QUERY_INFORMATION | PROCESS_TERMINATE, + FALSE, 0); base::CloseProcessHandle(loader_process); } } diff --git a/chromium/components/nacl/common/nacl_cmd_line.cc b/chromium/components/nacl/common/nacl_cmd_line.cc index b21a671ae1d..d9bbd6557f4 100644 --- a/chromium/components/nacl/common/nacl_cmd_line.cc +++ b/chromium/components/nacl/common/nacl_cmd_line.cc @@ -18,7 +18,6 @@ void CopyNaClCommandLineArguments(CommandLine* cmd_line) { // TODO(gregoryd): check which flags of those below can be supported. static const char* const kSwitchNames[] = { switches::kNoSandbox, - switches::kTestNaClSandbox, switches::kDisableBreakpad, switches::kFullMemoryCrashReport, switches::kEnableLogging, diff --git a/chromium/components/nacl/common/nacl_helper_linux.h b/chromium/components/nacl/common/nacl_helper_linux.h index 732b21570a9..a9324b3fa1d 100644 --- a/chromium/components/nacl/common/nacl_helper_linux.h +++ b/chromium/components/nacl/common/nacl_helper_linux.h @@ -9,10 +9,15 @@ // constants used to implement communication between the nacl_helper // process and the Chrome zygote. +#define kNaClMaxIPCMessageLength 2048 + // Used by Helper to tell Zygote it has started successfully. #define kNaClHelperStartupAck "NACLHELPER_OK" -// Used by Zygote to ask Helper to fork a new NaCl loader. -#define kNaClForkRequest "NACLFORK" + +enum NaClZygoteIPCCommand { + kNaClForkRequest, + kNaClGetTerminationStatusRequest, +}; // The next set of constants define global Linux file descriptors. // For communications between NaCl loader and browser. diff --git a/chromium/components/nacl/common/nacl_host_messages.h b/chromium/components/nacl/common/nacl_host_messages.h index d9a835ba28c..4afa1c727e7 100644 --- a/chromium/components/nacl/common/nacl_host_messages.h +++ b/chromium/components/nacl/common/nacl_host_messages.h @@ -26,6 +26,7 @@ IPC_STRUCT_TRAITS_BEGIN(nacl::NaClLaunchParams) IPC_STRUCT_TRAITS_MEMBER(uses_irt) IPC_STRUCT_TRAITS_MEMBER(enable_dyncode_syscalls) IPC_STRUCT_TRAITS_MEMBER(enable_exception_handling) + IPC_STRUCT_TRAITS_MEMBER(enable_crash_throttling) IPC_STRUCT_TRAITS_END() IPC_STRUCT_TRAITS_BEGIN(nacl::NaClLaunchResult) @@ -41,6 +42,7 @@ IPC_STRUCT_TRAITS_BEGIN(nacl::PnaclCacheInfo) IPC_STRUCT_TRAITS_MEMBER(opt_level) IPC_STRUCT_TRAITS_MEMBER(last_modified) IPC_STRUCT_TRAITS_MEMBER(etag) + IPC_STRUCT_TRAITS_MEMBER(has_no_store_header) IPC_STRUCT_TRAITS_END() // A renderer sends this to the browser process when it wants to start diff --git a/chromium/components/nacl/common/nacl_switches.cc b/chromium/components/nacl/common/nacl_switches.cc index b9db537b252..0dfdc949857 100644 --- a/chromium/components/nacl/common/nacl_switches.cc +++ b/chromium/components/nacl/common/nacl_switches.cc @@ -36,7 +36,4 @@ const char kNaClLoaderCmdPrefix[] = "nacl-loader-cmd-prefix"; // Causes the process to run as a NativeClient loader. const char kNaClLoaderProcess[] = "nacl-loader"; -// Runs the security test for the NaCl loader sandbox. -const char kTestNaClSandbox[] = "test-nacl-sandbox"; - } // namespace switches diff --git a/chromium/components/nacl/common/nacl_switches.h b/chromium/components/nacl/common/nacl_switches.h index 8b12206a21a..9bc1bcb4697 100644 --- a/chromium/components/nacl/common/nacl_switches.h +++ b/chromium/components/nacl/common/nacl_switches.h @@ -18,7 +18,6 @@ extern const char kNaClGdb[]; extern const char kNaClGdbScript[]; extern const char kNaClLoaderCmdPrefix[]; extern const char kNaClLoaderProcess[]; -extern const char kTestNaClSandbox[]; } // namespace switches diff --git a/chromium/components/nacl/common/nacl_types.cc b/chromium/components/nacl/common/nacl_types.cc index dea02391125..6b2f51ea4fb 100644 --- a/chromium/components/nacl/common/nacl_types.cc +++ b/chromium/components/nacl/common/nacl_types.cc @@ -24,7 +24,8 @@ NaClLaunchParams::NaClLaunchParams() permission_bits(0), uses_irt(false), enable_dyncode_syscalls(false), - enable_exception_handling(false) { + enable_exception_handling(false), + enable_crash_throttling(false) { } NaClLaunchParams::NaClLaunchParams(const std::string& manifest_url, @@ -32,13 +33,15 @@ NaClLaunchParams::NaClLaunchParams(const std::string& manifest_url, uint32 permission_bits, bool uses_irt, bool enable_dyncode_syscalls, - bool enable_exception_handling) + bool enable_exception_handling, + bool enable_crash_throttling) : manifest_url(manifest_url), render_view_id(render_view_id), permission_bits(permission_bits), uses_irt(uses_irt), enable_dyncode_syscalls(enable_dyncode_syscalls), - enable_exception_handling(enable_exception_handling) { + enable_exception_handling(enable_exception_handling), + enable_crash_throttling(enable_crash_throttling) { } NaClLaunchParams::NaClLaunchParams(const NaClLaunchParams& l) { @@ -48,6 +51,7 @@ NaClLaunchParams::NaClLaunchParams(const NaClLaunchParams& l) { uses_irt = l.uses_irt; enable_dyncode_syscalls = l.enable_dyncode_syscalls; enable_exception_handling = l.enable_exception_handling; + enable_crash_throttling = l.enable_crash_throttling; } NaClLaunchParams::~NaClLaunchParams() { diff --git a/chromium/components/nacl/common/nacl_types.h b/chromium/components/nacl/common/nacl_types.h index b3fee25c869..47fccddd963 100644 --- a/chromium/components/nacl/common/nacl_types.h +++ b/chromium/components/nacl/common/nacl_types.h @@ -70,7 +70,8 @@ struct NaClLaunchParams { NaClLaunchParams(); NaClLaunchParams(const std::string& u, int r, uint32 p, bool uses_irt, bool enable_dyncode_syscalls, - bool enable_exception_handling); + bool enable_exception_handling, + bool enable_crash_throttling); NaClLaunchParams(const NaClLaunchParams& l); ~NaClLaunchParams(); @@ -80,6 +81,7 @@ struct NaClLaunchParams { bool uses_irt; bool enable_dyncode_syscalls; bool enable_exception_handling; + bool enable_crash_throttling; }; struct NaClLaunchResult { diff --git a/chromium/components/nacl/common/pnacl_types.cc b/chromium/components/nacl/common/pnacl_types.cc index 6c43319c6b2..75384b6faed 100644 --- a/chromium/components/nacl/common/pnacl_types.cc +++ b/chromium/components/nacl/common/pnacl_types.cc @@ -6,7 +6,8 @@ namespace nacl { -PnaclCacheInfo::PnaclCacheInfo() {} +PnaclCacheInfo::PnaclCacheInfo() + : abi_version(0), opt_level(0), has_no_store_header(0) {} PnaclCacheInfo::~PnaclCacheInfo() {} // static diff --git a/chromium/components/nacl/common/pnacl_types.h b/chromium/components/nacl/common/pnacl_types.h index 3fc405a980d..d1a12c86b07 100644 --- a/chromium/components/nacl/common/pnacl_types.h +++ b/chromium/components/nacl/common/pnacl_types.h @@ -27,6 +27,7 @@ struct PnaclCacheInfo { int opt_level; base::Time last_modified; std::string etag; + bool has_no_store_header; }; // Progress information for PNaCl on-demand installs. diff --git a/chromium/components/nacl/loader/nacl_main.cc b/chromium/components/nacl/loader/nacl_main.cc index a9ec5c93c37..15b6fd2dc8c 100644 --- a/chromium/components/nacl/loader/nacl_main.cc +++ b/chromium/components/nacl/loader/nacl_main.cc @@ -29,10 +29,7 @@ int NaClMain(const content::MainFunctionParams& parameters) { #if defined(OS_WIN) || defined(OS_MACOSX) || defined(OS_LINUX) NaClMainPlatformDelegate platform(parameters); - - platform.PlatformInitialize(); bool no_sandbox = parsed_command_line.HasSwitch(switches::kNoSandbox); - platform.InitSandboxTests(no_sandbox); #if defined(OS_POSIX) // The number of cores must be obtained before the invocation of @@ -43,25 +40,14 @@ int NaClMain(const content::MainFunctionParams& parameters) { if (!no_sandbox) { platform.EnableSandbox(); } - bool sandbox_test_result = platform.RunSandboxTests(); - - if (sandbox_test_result) { - NaClListener listener; + NaClListener listener; #if defined(OS_POSIX) - listener.set_number_of_cores(number_of_cores); + listener.set_number_of_cores(number_of_cores); #endif - listener.Listen(); - } else { - // This indirectly prevents the test-harness-success-cookie from being set, - // as a way of communicating test failure, because the nexe won't reply. - // TODO(jvoung): find a better way to indicate failure that doesn't - // require waiting for a timeout. - VLOG(1) << "Sandbox test failed: Not launching NaCl process"; - } + + listener.Listen(); #else NOTIMPLEMENTED() << " not implemented startup, plugin startup dialog etc."; #endif - - platform.PlatformUninitialize(); return 0; } diff --git a/chromium/components/nacl/loader/nacl_main_platform_delegate.h b/chromium/components/nacl/loader/nacl_main_platform_delegate.h index 191831cfefe..ca740b85bcd 100644 --- a/chromium/components/nacl/loader/nacl_main_platform_delegate.h +++ b/chromium/components/nacl/loader/nacl_main_platform_delegate.h @@ -5,38 +5,20 @@ #ifndef CHROME_NACL_NACL_MAIN_PLATFORM_DELEGATE_H_ #define CHROME_NACL_NACL_MAIN_PLATFORM_DELEGATE_H_ -#include "base/native_library.h" +#include "base/basictypes.h" #include "content/public/common/main_function_params.h" -typedef bool (*RunNaClLoaderTests)(void); -const char kNaClLoaderTestCall[] = "RunNaClLoaderTests"; - class NaClMainPlatformDelegate { public: explicit NaClMainPlatformDelegate( const content::MainFunctionParams& parameters); ~NaClMainPlatformDelegate(); - // Called first thing and last thing in the process' lifecycle, i.e. before - // the sandbox is enabled. - void PlatformInitialize(); - void PlatformUninitialize(); - - // Gives us an opportunity to initialize state used for tests before enabling - // the sandbox. - void InitSandboxTests(bool no_sandbox); - // Initiate Lockdown. void EnableSandbox(); - // Runs the sandbox tests for the NaCl Loader, if tests supplied. - // Cannot run again, after this (resources freed). - // Returns false if the tests are supplied and fail. - bool RunSandboxTests(); - private: const content::MainFunctionParams& parameters_; - base::NativeLibrary sandbox_test_module_; DISALLOW_COPY_AND_ASSIGN(NaClMainPlatformDelegate); }; diff --git a/chromium/components/nacl/loader/nacl_main_platform_delegate_linux.cc b/chromium/components/nacl/loader/nacl_main_platform_delegate_linux.cc index e9d2f731787..cbe886bc006 100644 --- a/chromium/components/nacl/loader/nacl_main_platform_delegate_linux.cc +++ b/chromium/components/nacl/loader/nacl_main_platform_delegate_linux.cc @@ -4,28 +4,14 @@ #include "components/nacl/loader/nacl_main_platform_delegate.h" -#include "base/command_line.h" - NaClMainPlatformDelegate::NaClMainPlatformDelegate( const content::MainFunctionParams& parameters) - : parameters_(parameters), sandbox_test_module_(NULL) { + : parameters_(parameters) { } NaClMainPlatformDelegate::~NaClMainPlatformDelegate() { } -void NaClMainPlatformDelegate::PlatformInitialize() { -} - -void NaClMainPlatformDelegate::PlatformUninitialize() { -} - -void NaClMainPlatformDelegate::InitSandboxTests(bool no_sandbox) { - // The sandbox is started in the zygote process: zygote_main_linux.cc - // http://code.google.com/p/chromium/wiki/LinuxSUIDSandbox - return; -} - void NaClMainPlatformDelegate::EnableSandbox() { // The setuid sandbox is started in the zygote process: zygote_main_linux.cc // http://code.google.com/p/chromium/wiki/LinuxSUIDSandbox @@ -43,9 +29,3 @@ void NaClMainPlatformDelegate::EnableSandbox() { // At best, NaCl will not work. At worst, enabling the seccomp sandbox // could create a hole in the NaCl sandbox. } - -bool NaClMainPlatformDelegate::RunSandboxTests() { - // The sandbox is started in the zygote process: zygote_main_linux.cc - // http://code.google.com/p/chromium/wiki/LinuxSUIDSandbox - return true; -} diff --git a/chromium/components/nacl/loader/nacl_main_platform_delegate_mac.mm b/chromium/components/nacl/loader/nacl_main_platform_delegate_mac.mm index 67ddced4396..78fa5390454 100644 --- a/chromium/components/nacl/loader/nacl_main_platform_delegate_mac.mm +++ b/chromium/components/nacl/loader/nacl_main_platform_delegate_mac.mm @@ -5,78 +5,22 @@ #include "components/nacl/loader/nacl_main_platform_delegate.h" #import <Cocoa/Cocoa.h> -#include "base/command_line.h" #include "base/files/file_path.h" #include "base/logging.h" -#include "base/native_library.h" #include "components/nacl/common/nacl_sandbox_type_mac.h" #include "components/nacl/common/nacl_switches.h" #include "content/public/common/sandbox_init.h" NaClMainPlatformDelegate::NaClMainPlatformDelegate( const content::MainFunctionParams& parameters) - : parameters_(parameters), sandbox_test_module_(NULL) { + : parameters_(parameters) { } NaClMainPlatformDelegate::~NaClMainPlatformDelegate() { } -// TODO(jvoung): see if this old comment (from renderer_main_platform...) -// is relevant to the nacl loader. -// TODO(mac-port): Any code needed to initialize a process for purposes of -// running a NaClLoader needs to also be reflected in chrome_main.cc for -// --single-process support. -void NaClMainPlatformDelegate::PlatformInitialize() { -} - -void NaClMainPlatformDelegate::PlatformUninitialize() { -} - -void NaClMainPlatformDelegate::InitSandboxTests(bool no_sandbox) { - const CommandLine& command_line = parameters_.command_line; - - DVLOG(1) << "Started NaClLdr with "; - const std::vector<std::string>& argstrings = command_line.argv(); - for (std::vector<std::string>::const_iterator ii = argstrings.begin(); - ii != argstrings.end(); ++ii) - DVLOG(1) << *ii; - - // Be sure not to load the sandbox test DLL if the sandbox isn't on. - // Comment-out guard and recompile if you REALLY want to test w/out the SB. - // TODO(jvoung): allow testing without sandbox, but change expected ret vals. - if (!no_sandbox) { - base::FilePath test_dll_name = - command_line.GetSwitchValuePath(switches::kTestNaClSandbox); - if (!test_dll_name.empty()) { - sandbox_test_module_ = base::LoadNativeLibrary(test_dll_name, NULL); - CHECK(sandbox_test_module_); - } - } -} - void NaClMainPlatformDelegate::EnableSandbox() { CHECK(content::InitializeSandbox(NACL_SANDBOX_TYPE_NACL_LOADER, base::FilePath())) << "Error initializing sandbox for " << switches::kNaClLoaderProcess; } - -bool NaClMainPlatformDelegate::RunSandboxTests() { - // TODO(jvoung): Win and mac should share this identical code. - bool result = true; - if (sandbox_test_module_) { - RunNaClLoaderTests run_security_tests = - reinterpret_cast<RunNaClLoaderTests>( - base::GetFunctionPointerFromNativeLibrary(sandbox_test_module_, - kNaClLoaderTestCall)); - if (run_security_tests) { - DVLOG(1) << "Running NaCl Loader security tests"; - result = (*run_security_tests)(); - } else { - VLOG(1) << "Failed to get NaCl sandbox test function"; - result = false; - } - base::UnloadNativeLibrary(sandbox_test_module_); - sandbox_test_module_ = NULL; - } - return result; -} diff --git a/chromium/components/nacl/loader/nacl_main_platform_delegate_win.cc b/chromium/components/nacl/loader/nacl_main_platform_delegate_win.cc index e79fe17725c..f530961700f 100644 --- a/chromium/components/nacl/loader/nacl_main_platform_delegate_win.cc +++ b/chromium/components/nacl/loader/nacl_main_platform_delegate_win.cc @@ -4,59 +4,17 @@ #include "components/nacl/loader/nacl_main_platform_delegate.h" -#include "base/command_line.h" -#include "base/files/file_path.h" #include "base/logging.h" -#include "base/native_library.h" -#include "components/nacl/common/nacl_switches.h" #include "sandbox/win/src/sandbox.h" NaClMainPlatformDelegate::NaClMainPlatformDelegate( const content::MainFunctionParams& parameters) - : parameters_(parameters), sandbox_test_module_(NULL) { + : parameters_(parameters) { } NaClMainPlatformDelegate::~NaClMainPlatformDelegate() { } -void NaClMainPlatformDelegate::PlatformInitialize() { - // Be mindful of what resources you acquire here. They can be used by - // malicious code if the renderer gets compromised. -} - -void NaClMainPlatformDelegate::PlatformUninitialize() { -} - -void NaClMainPlatformDelegate::InitSandboxTests(bool no_sandbox) { - const CommandLine& command_line = parameters_.command_line; - - DVLOG(1) << "Started NaClLdr with " << command_line.GetCommandLineString(); - - sandbox::TargetServices* target_services = - parameters_.sandbox_info->target_services; - - if (target_services && !no_sandbox) { - base::FilePath test_dll_name = - command_line.GetSwitchValuePath(switches::kTestNaClSandbox); - if (!test_dll_name.empty()) { - // At this point, hack on the suffix according to with bitness - // of your windows process. -#if defined(_WIN64) - DVLOG(1) << "Using 64-bit test dll\n"; - test_dll_name = test_dll_name.InsertBeforeExtension(L"64"); - test_dll_name = test_dll_name.ReplaceExtension(L"dll"); -#else - DVLOG(1) << "Using 32-bit test dll\n"; - test_dll_name = test_dll_name.ReplaceExtension(L"dll"); -#endif - DVLOG(1) << "Loading test lib " << test_dll_name.value() << "\n"; - sandbox_test_module_ = base::LoadNativeLibrary(test_dll_name, NULL); - CHECK(sandbox_test_module_); - VLOG(1) << "Testing NaCl sandbox\n"; - } - } -} - void NaClMainPlatformDelegate::EnableSandbox() { sandbox::TargetServices* target_services = parameters_.sandbox_info->target_services; @@ -71,24 +29,3 @@ void NaClMainPlatformDelegate::EnableSandbox() { // Turn the sandbox on. target_services->LowerToken(); } - -bool NaClMainPlatformDelegate::RunSandboxTests() { - // TODO(jvoung): Win and mac should share this code. - bool result = true; - if (sandbox_test_module_) { - RunNaClLoaderTests run_security_tests = - reinterpret_cast<RunNaClLoaderTests>( - base::GetFunctionPointerFromNativeLibrary(sandbox_test_module_, - kNaClLoaderTestCall)); - if (run_security_tests) { - DVLOG(1) << "Running NaCl Loader security tests"; - result = (*run_security_tests)(); - } else { - VLOG(1) << "Failed to get NaCl sandbox test function"; - result = false; - } - base::UnloadNativeLibrary(sandbox_test_module_); - sandbox_test_module_ = NULL; - } - return result; -} diff --git a/chromium/components/nacl/zygote/nacl_fork_delegate_linux.cc b/chromium/components/nacl/zygote/nacl_fork_delegate_linux.cc index 8445342fdaa..d94ad6b9fdf 100644 --- a/chromium/components/nacl/zygote/nacl_fork_delegate_linux.cc +++ b/chromium/components/nacl/zygote/nacl_fork_delegate_linux.cc @@ -17,8 +17,10 @@ #include "base/files/file_path.h" #include "base/logging.h" #include "base/path_service.h" +#include "base/pickle.h" #include "base/posix/eintr_wrapper.h" #include "base/posix/unix_domain_socket_linux.h" +#include "base/process/kill.h" #include "base/process/launch.h" #include "base/third_party/dynamic_annotations/dynamic_annotations.h" #include "components/nacl/common/nacl_helper_linux.h" @@ -63,6 +65,41 @@ bool NonZeroSegmentBaseIsSlow() { } #endif +// Send an IPC request on |ipc_channel|. The request is contained in +// |request_pickle| and can have file descriptors attached in |attached_fds|. +// |reply_data_buffer| must be allocated by the caller and will contain the +// reply. The size of the reply will be written to |reply_size|. +// This code assumes that only one thread can write to |ipc_channel| to make +// requests. +bool SendIPCRequestAndReadReply(int ipc_channel, + const std::vector<int>& attached_fds, + const Pickle& request_pickle, + char* reply_data_buffer, + size_t reply_data_buffer_size, + ssize_t* reply_size) { + DCHECK_LE(static_cast<size_t>(kNaClMaxIPCMessageLength), + reply_data_buffer_size); + DCHECK(reply_size); + + if (!UnixDomainSocket::SendMsg(ipc_channel, request_pickle.data(), + request_pickle.size(), attached_fds)) { + LOG(ERROR) << "SendIPCRequestAndReadReply: SendMsg failed"; + return false; + } + + // Then read the remote reply. + std::vector<int> received_fds; + const ssize_t msg_len = + UnixDomainSocket::RecvMsg(ipc_channel, reply_data_buffer, + reply_data_buffer_size, &received_fds); + if (msg_len <= 0) { + LOG(ERROR) << "SendIPCRequestAndReadReply: RecvMsg failed"; + return false; + } + *reply_size = msg_len; + return true; +} + } // namespace. NaClForkDelegate::NaClForkDelegate() @@ -219,22 +256,34 @@ bool NaClForkDelegate::CanHelp(const std::string& process_type, } pid_t NaClForkDelegate::Fork(const std::vector<int>& fds) { - base::ProcessId naclchild; VLOG(1) << "NaClForkDelegate::Fork"; DCHECK(fds.size() == kNaClParentFDIndex + 1); - if (!UnixDomainSocket::SendMsg(fd_, kNaClForkRequest, - strlen(kNaClForkRequest), fds)) { - LOG(ERROR) << "NaClForkDelegate::Fork: SendMsg failed"; + + // First, send a remote fork request. + Pickle write_pickle; + write_pickle.WriteInt(kNaClForkRequest); + + char reply_buf[kNaClMaxIPCMessageLength]; + ssize_t reply_size = 0; + bool got_reply = + SendIPCRequestAndReadReply(fd_, fds, write_pickle, + reply_buf, sizeof(reply_buf), &reply_size); + if (!got_reply) { + LOG(ERROR) << "Could not perform remote fork."; return -1; } - int nread = HANDLE_EINTR(read(fd_, &naclchild, sizeof(naclchild))); - if (nread != sizeof(naclchild)) { - LOG(ERROR) << "NaClForkDelegate::Fork: read failed"; + + // Now see if the other end managed to fork. + Pickle reply_pickle(reply_buf, reply_size); + PickleIterator iter(reply_pickle); + pid_t nacl_child; + if (!iter.ReadInt(&nacl_child)) { + LOG(ERROR) << "NaClForkDelegate::Fork: pickle failed"; return -1; } - VLOG(1) << "nacl_child is " << naclchild << " (" << nread << " bytes)"; - return naclchild; + VLOG(1) << "nacl_child is " << nacl_child; + return nacl_child; } bool NaClForkDelegate::AckChild(const int fd, @@ -246,3 +295,47 @@ bool NaClForkDelegate::AckChild(const int fd, } return true; } + +bool NaClForkDelegate::GetTerminationStatus(pid_t pid, bool known_dead, + base::TerminationStatus* status, + int* exit_code) { + VLOG(1) << "NaClForkDelegate::GetTerminationStatus"; + DCHECK(status); + DCHECK(exit_code); + + Pickle write_pickle; + write_pickle.WriteInt(kNaClGetTerminationStatusRequest); + write_pickle.WriteInt(pid); + write_pickle.WriteBool(known_dead); + + const std::vector<int> empty_fds; + char reply_buf[kNaClMaxIPCMessageLength]; + ssize_t reply_size = 0; + bool got_reply = + SendIPCRequestAndReadReply(fd_, empty_fds, write_pickle, + reply_buf, sizeof(reply_buf), &reply_size); + if (!got_reply) { + LOG(ERROR) << "Could not perform remote GetTerminationStatus."; + return false; + } + + Pickle reply_pickle(reply_buf, reply_size); + PickleIterator iter(reply_pickle); + int termination_status; + if (!iter.ReadInt(&termination_status) || + termination_status < 0 || + termination_status >= base::TERMINATION_STATUS_MAX_ENUM) { + LOG(ERROR) << "GetTerminationStatus: pickle failed"; + return false; + } + + int remote_exit_code; + if (!iter.ReadInt(&remote_exit_code)) { + LOG(ERROR) << "GetTerminationStatus: pickle failed"; + return false; + } + + *status = static_cast<base::TerminationStatus>(termination_status); + *exit_code = remote_exit_code; + return true; +} diff --git a/chromium/components/nacl/zygote/nacl_fork_delegate_linux.h b/chromium/components/nacl/zygote/nacl_fork_delegate_linux.h index 5812f33fe26..a5ec534aa21 100644 --- a/chromium/components/nacl/zygote/nacl_fork_delegate_linux.h +++ b/chromium/components/nacl/zygote/nacl_fork_delegate_linux.h @@ -31,6 +31,9 @@ class NaClForkDelegate : public content::ZygoteForkDelegate { virtual pid_t Fork(const std::vector<int>& fds) OVERRIDE; virtual bool AckChild(int fd, const std::string& channel_switch) OVERRIDE; + virtual bool GetTerminationStatus(pid_t pid, bool known_dead, + base::TerminationStatus* status, + int* exit_code) OVERRIDE; private: // These values are reported via UMA and hence they become permanent diff --git a/chromium/components/navigation_interception/android/java/src/org/chromium/components/navigation_interception/InterceptNavigationDelegate.java b/chromium/components/navigation_interception/android/java/src/org/chromium/components/navigation_interception/InterceptNavigationDelegate.java deleted file mode 100644 index f2144675e1b..00000000000 --- a/chromium/components/navigation_interception/android/java/src/org/chromium/components/navigation_interception/InterceptNavigationDelegate.java +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) 2012 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package org.chromium.components.navigation_interception; - -import org.chromium.base.CalledByNative; - -public interface InterceptNavigationDelegate { - - /** - * This method is called for every top-level navigation within the associated WebContents. - * The method allows the embedder to ignore navigations. This is used on Android to 'convert' - * certain navigations to Intents to 3rd party applications. - * - * @param navigationParams parameters describing the navigation. - * @return true if the navigation should be ignored. - */ - @CalledByNative - boolean shouldIgnoreNavigation(NavigationParams navigationParams); -} diff --git a/chromium/components/navigation_interception/android/java/src/org/chromium/components/navigation_interception/NavigationParams.java b/chromium/components/navigation_interception/android/java/src/org/chromium/components/navigation_interception/NavigationParams.java deleted file mode 100644 index cdfd88313f7..00000000000 --- a/chromium/components/navigation_interception/android/java/src/org/chromium/components/navigation_interception/NavigationParams.java +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package org.chromium.components.navigation_interception; - -import org.chromium.base.CalledByNative; - -public class NavigationParams { - // Target url of the navigation. - public final String url; - // True if the the navigation method is "POST". - public final boolean isPost; - // True if the navigation was initiated by the user. - public final boolean hasUserGesture; - // Page transition type (e.g. link / typed). - public final int pageTransitionType; - // Is the navigation a redirect (in which case url is the "target" address). - public final boolean isRedirect; - - public NavigationParams(String url, boolean isPost, boolean hasUserGesture, - int pageTransitionType, boolean isRedirect) { - this.url = url; - this.isPost = isPost; - this.hasUserGesture = hasUserGesture; - this.pageTransitionType = pageTransitionType; - this.isRedirect = isRedirect; - } - - @CalledByNative - public static NavigationParams create(String url, boolean isPost, boolean hasUserGesture, - int pageTransitionType, boolean isRedirect) { - return new NavigationParams(url, isPost, hasUserGesture, pageTransitionType, - isRedirect); - } -} diff --git a/chromium/components/navigation_interception/component_jni_registrar.cc b/chromium/components/navigation_interception/component_jni_registrar.cc deleted file mode 100644 index 9663529d7cc..00000000000 --- a/chromium/components/navigation_interception/component_jni_registrar.cc +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) 2012 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "components/navigation_interception/component_jni_registrar.h" - -#include "base/android/jni_android.h" -#include "base/android/jni_registrar.h" -#include "components/navigation_interception/intercept_navigation_delegate.h" -#include "components/navigation_interception/navigation_params_android.h" - -namespace navigation_interception { - -static base::android::RegistrationMethod kComponentRegisteredMethods[] = { - { "InterceptNavigationDelegate", RegisterInterceptNavigationDelegate }, - { "NavigationParams", RegisterNavigationParams }, -}; - -bool RegisterNavigationInterceptionJni(JNIEnv* env) { - return RegisterNativeMethods( - env, kComponentRegisteredMethods, arraysize(kComponentRegisteredMethods)); -} - -} // namespace navigation_interception diff --git a/chromium/components/navigation_interception/component_jni_registrar.h b/chromium/components/navigation_interception/component_jni_registrar.h deleted file mode 100644 index c79f3778e9f..00000000000 --- a/chromium/components/navigation_interception/component_jni_registrar.h +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) 2012 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef COMPONENTS_NAVIGATION_INTERCEPTION_COMPONENT_JNI_REGISTRAR_H_ -#define COMPONENTS_NAVIGATION_INTERCEPTION_COMPONENT_JNI_REGISTRAR_H_ - -#include <jni.h> - -namespace navigation_interception { - -// Register all JNI bindings necessary for the navigation_interception -// component. -bool RegisterNavigationInterceptionJni(JNIEnv* env); - -} // namespace navigation_interception - -#endif // COMPONENTS_NAVIGATION_INTERCEPTION_COMPONENT_JNI_REGISTRAR_H_ diff --git a/chromium/components/policy.gypi b/chromium/components/policy.gypi new file mode 100644 index 00000000000..7471212c27e --- /dev/null +++ b/chromium/components/policy.gypi @@ -0,0 +1,46 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +{ + 'targets': [ + { + 'target_name': 'policy_component', + 'type': '<(component)', + 'dependencies': [ + '../base/base.gyp:base', + 'json_schema', + ], + 'defines': [ + 'POLICY_COMPONENT_IMPLEMENTATION', + ], + 'include_dirs': [ + '..', + ], + 'conditions': [ + ['configuration_policy==1', { + 'sources': [ + 'policy/core/common/policy_pref_names.cc', + 'policy/core/common/policy_pref_names.h', + 'policy/core/common/policy_schema.cc', + 'policy/core/common/policy_schema.h', + 'policy/core/common/schema.cc', + 'policy/core/common/schema.h', + 'policy/core/common/schema_internal.h', + 'policy/policy_export.h', + ], + }, { # configuration_policy==0 + # The target 'policy_component' always exists. Later it will include + # some stubs when configuration_policy==0. For now this stub file is + # compiled so that an output is produced, otherwise the shared build + # breaks on iOS. + # TODO(joaodasilva): remove this comment and the temporary stub after + # moving one of the real stubs. http://crbug.com/271392 + 'sources': [ + 'policy/stub_to_remove.cc', + ], + }], + ], + }, + ], +} diff --git a/chromium/components/policy/OWNERS b/chromium/components/policy/OWNERS new file mode 100644 index 00000000000..dbccb7c3a0c --- /dev/null +++ b/chromium/components/policy/OWNERS @@ -0,0 +1,6 @@ +mnissler@chromium.org +pastarmovj@chromium.org +joaodasilva@chromium.org +bartfab@chromium.org +atwilson@chromium.org +pneubeck@chromium.org diff --git a/chromium/components/policy/core/common/policy_pref_names.cc b/chromium/components/policy/core/common/policy_pref_names.cc new file mode 100644 index 00000000000..ca86fcd0870 --- /dev/null +++ b/chromium/components/policy/core/common/policy_pref_names.cc @@ -0,0 +1,15 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/policy/core/common/policy_pref_names.h" + +namespace policy { +namespace prefs { + +// 64-bit serialization of the time last policy usage statistics were collected +// by UMA_HISTOGRAM_ENUMERATION. +const char kLastPolicyStatisticsUpdate[] = "policy.last_statistics_update"; + +} // namespace prefs +} // namespace policy diff --git a/chromium/components/policy/core/common/policy_pref_names.h b/chromium/components/policy/core/common/policy_pref_names.h new file mode 100644 index 00000000000..0a65262fec0 --- /dev/null +++ b/chromium/components/policy/core/common/policy_pref_names.h @@ -0,0 +1,21 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_POLICY_CORE_COMMON_PREF_NAMES_H_ +#define COMPONENTS_POLICY_CORE_COMMON_PREF_NAMES_H_ + +#include "components/policy/policy_export.h" + +namespace policy { +namespace prefs { + +// Constants for the names of policy-related preferences. +// TODO(dconnelly): remove POLICY_EXPORT once the statistics collector moves +// to the policy component (crbug.com/271392). +POLICY_EXPORT extern const char kLastPolicyStatisticsUpdate[]; + +} // prefs +} // namespace policy + +#endif // COMPONENTS_POLICY_CORE_COMMON_PREF_NAMES_H_ diff --git a/chromium/components/policy/core/common/policy_schema.cc b/chromium/components/policy/core/common/policy_schema.cc new file mode 100644 index 00000000000..8c3145ce43a --- /dev/null +++ b/chromium/components/policy/core/common/policy_schema.cc @@ -0,0 +1,244 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/policy/core/common/policy_schema.h" + +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "base/stl_util.h" +#include "components/json_schema/json_schema_constants.h" +#include "components/json_schema/json_schema_validator.h" + +namespace policy { + +namespace { + +const char kJSONSchemaVersion[] = "http://json-schema.org/draft-03/schema#"; + +// Describes the properties of a TYPE_DICTIONARY policy schema. +class DictionaryPolicySchema : public PolicySchema { + public: + static scoped_ptr<PolicySchema> Parse(const base::DictionaryValue& schema, + std::string* error); + + virtual ~DictionaryPolicySchema(); + + virtual const PolicySchemaMap* GetProperties() const OVERRIDE; + virtual const PolicySchema* GetSchemaForAdditionalProperties() const OVERRIDE; + + private: + DictionaryPolicySchema(); + + PolicySchemaMap properties_; + scoped_ptr<PolicySchema> additional_properties_; + + DISALLOW_COPY_AND_ASSIGN(DictionaryPolicySchema); +}; + +// Describes the items of a TYPE_LIST policy schema. +class ListPolicySchema : public PolicySchema { + public: + static scoped_ptr<PolicySchema> Parse(const base::DictionaryValue& schema, + std::string* error); + + virtual ~ListPolicySchema(); + + virtual const PolicySchema* GetSchemaForItems() const OVERRIDE; + + private: + ListPolicySchema(); + + scoped_ptr<PolicySchema> items_schema_; + + DISALLOW_COPY_AND_ASSIGN(ListPolicySchema); +}; + +bool SchemaTypeToValueType(const std::string& type_string, + base::Value::Type* type) { + // Note: "any" is not an accepted type. + static const struct { + const char* schema_type; + base::Value::Type value_type; + } kSchemaToValueTypeMap[] = { + { json_schema_constants::kArray, base::Value::TYPE_LIST }, + { json_schema_constants::kBoolean, base::Value::TYPE_BOOLEAN }, + { json_schema_constants::kInteger, base::Value::TYPE_INTEGER }, + { json_schema_constants::kNull, base::Value::TYPE_NULL }, + { json_schema_constants::kNumber, base::Value::TYPE_DOUBLE }, + { json_schema_constants::kObject, base::Value::TYPE_DICTIONARY }, + { json_schema_constants::kString, base::Value::TYPE_STRING }, + }; + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kSchemaToValueTypeMap); ++i) { + if (kSchemaToValueTypeMap[i].schema_type == type_string) { + *type = kSchemaToValueTypeMap[i].value_type; + return true; + } + } + return false; +} + +scoped_ptr<PolicySchema> ParseSchema(const base::DictionaryValue& schema, + std::string* error) { + std::string type_string; + if (!schema.GetString(json_schema_constants::kType, &type_string)) { + *error = "The schema type must be declared."; + return scoped_ptr<PolicySchema>(); + } + + base::Value::Type type = base::Value::TYPE_NULL; + if (!SchemaTypeToValueType(type_string, &type)) { + *error = "The \"any\" type can't be used."; + return scoped_ptr<PolicySchema>(); + } + + switch (type) { + case base::Value::TYPE_DICTIONARY: + return DictionaryPolicySchema::Parse(schema, error); + case base::Value::TYPE_LIST: + return ListPolicySchema::Parse(schema, error); + default: + return make_scoped_ptr(new PolicySchema(type)); + } +} + +DictionaryPolicySchema::DictionaryPolicySchema() + : PolicySchema(base::Value::TYPE_DICTIONARY) {} + +DictionaryPolicySchema::~DictionaryPolicySchema() { + STLDeleteValues(&properties_); +} + +const PolicySchemaMap* DictionaryPolicySchema::GetProperties() const { + return &properties_; +} + +const PolicySchema* + DictionaryPolicySchema::GetSchemaForAdditionalProperties() const { + return additional_properties_.get(); +} + +// static +scoped_ptr<PolicySchema> DictionaryPolicySchema::Parse( + const base::DictionaryValue& schema, + std::string* error) { + scoped_ptr<DictionaryPolicySchema> dict_schema(new DictionaryPolicySchema()); + + const base::DictionaryValue* dict = NULL; + const base::DictionaryValue* properties = NULL; + if (schema.GetDictionary(json_schema_constants::kProperties, &properties)) { + for (base::DictionaryValue::Iterator it(*properties); + !it.IsAtEnd(); it.Advance()) { + // This should have been verified by the JSONSchemaValidator. + CHECK(it.value().GetAsDictionary(&dict)); + scoped_ptr<PolicySchema> sub_schema = ParseSchema(*dict, error); + if (!sub_schema) + return scoped_ptr<PolicySchema>(); + dict_schema->properties_[it.key()] = sub_schema.release(); + } + } + + if (schema.GetDictionary(json_schema_constants::kAdditionalProperties, + &dict)) { + scoped_ptr<PolicySchema> sub_schema = ParseSchema(*dict, error); + if (!sub_schema) + return scoped_ptr<PolicySchema>(); + dict_schema->additional_properties_ = sub_schema.Pass(); + } + + return dict_schema.PassAs<PolicySchema>(); +} + +ListPolicySchema::ListPolicySchema() + : PolicySchema(base::Value::TYPE_LIST) {} + +ListPolicySchema::~ListPolicySchema() {} + +const PolicySchema* ListPolicySchema::GetSchemaForItems() const { + return items_schema_.get(); +} + +scoped_ptr<PolicySchema> ListPolicySchema::Parse( + const base::DictionaryValue& schema, + std::string* error) { + const base::DictionaryValue* dict = NULL; + if (!schema.GetDictionary(json_schema_constants::kItems, &dict)) { + *error = "Arrays must declare a single schema for their items."; + return scoped_ptr<PolicySchema>(); + } + scoped_ptr<PolicySchema> items_schema = ParseSchema(*dict, error); + if (!items_schema) + return scoped_ptr<PolicySchema>(); + + scoped_ptr<ListPolicySchema> list_schema(new ListPolicySchema()); + list_schema->items_schema_ = items_schema.Pass(); + return list_schema.PassAs<PolicySchema>(); +} + +} // namespace + +PolicySchema::PolicySchema(base::Value::Type type) + : type_(type) {} + +PolicySchema::~PolicySchema() {} + +const PolicySchemaMap* PolicySchema::GetProperties() const { + NOTREACHED(); + return NULL; +} + +const PolicySchema* PolicySchema::GetSchemaForAdditionalProperties() const { + NOTREACHED(); + return NULL; +} + +const PolicySchema* PolicySchema::GetSchemaForProperty( + const std::string& key) const { + const PolicySchemaMap* properties = GetProperties(); + PolicySchemaMap::const_iterator it = properties->find(key); + return it == properties->end() ? GetSchemaForAdditionalProperties() + : it->second; +} + +const PolicySchema* PolicySchema::GetSchemaForItems() const { + NOTREACHED(); + return NULL; +} + +// static +scoped_ptr<PolicySchema> PolicySchema::Parse(const std::string& content, + std::string* error) { + // Validate as a generic JSON schema. + scoped_ptr<base::DictionaryValue> dict = + JSONSchemaValidator::IsValidSchema(content, error); + if (!dict) + return scoped_ptr<PolicySchema>(); + + // Validate the schema version. + std::string string_value; + if (!dict->GetString(json_schema_constants::kSchema, &string_value) || + string_value != kJSONSchemaVersion) { + *error = "Must declare JSON Schema v3 version in \"$schema\"."; + return scoped_ptr<PolicySchema>(); + } + + // Validate the main type. + if (!dict->GetString(json_schema_constants::kType, &string_value) || + string_value != json_schema_constants::kObject) { + *error = + "The main schema must have a type attribute with \"object\" value."; + return scoped_ptr<PolicySchema>(); + } + + // Checks for invalid attributes at the top-level. + if (dict->HasKey(json_schema_constants::kAdditionalProperties) || + dict->HasKey(json_schema_constants::kPatternProperties)) { + *error = "\"additionalProperties\" and \"patternProperties\" are not " + "supported at the main schema."; + return scoped_ptr<PolicySchema>(); + } + + return ParseSchema(*dict, error); +} + +} // namespace policy diff --git a/chromium/components/policy/core/common/policy_schema.h b/chromium/components/policy/core/common/policy_schema.h new file mode 100644 index 00000000000..c48ee6fc2b9 --- /dev/null +++ b/chromium/components/policy/core/common/policy_schema.h @@ -0,0 +1,70 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_POLICY_CORE_COMMON_POLICY_SCHEMA_H_ +#define COMPONENTS_POLICY_CORE_COMMON_POLICY_SCHEMA_H_ + +#include <map> +#include <string> + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "base/values.h" +#include "components/policy/policy_export.h" + +namespace policy { + +class PolicySchema; +typedef std::map<std::string, PolicySchema*> PolicySchemaMap; + +// Maps known policy keys to their expected types, and recursively describes +// the known keys within dictionary or list types. +class POLICY_EXPORT PolicySchema { + public: + + // Parses |schema| as a JSON v3 schema, and additionally verifies that: + // - the version is JSON schema v3; + // - the top-level entry is of type "object"; + // - the top-level object doesn't contain "additionalProperties" nor + // "patternProperties"; + // - each "property" maps to a schema with one "type"; + // - the type "any" is not used. + // If all the checks pass then the parsed PolicySchema is returned; otherwise + // returns NULL. + static scoped_ptr<PolicySchema> Parse(const std::string& schema, + std::string* error); + + explicit PolicySchema(base::Value::Type type); + virtual ~PolicySchema(); + + // Returns the expected type for this policy. At the top-level PolicySchema + // this is always TYPE_DICTIONARY. + base::Value::Type type() const { return type_; } + + // It is invalid to call these methods when type() is not TYPE_DICTIONARY. + // + // GetProperties() returns a map of the known property names to their schemas; + // the map is never NULL. + // GetSchemaForAdditionalProperties() returns the schema that should be used + // for keys not found in the map, and may be NULL. + // GetSchemaForProperty() is a utility method that combines both, returning + // the mapped schema if found in GetProperties(), otherwise returning + // GetSchemaForAdditionalProperties(). + virtual const PolicySchemaMap* GetProperties() const; + virtual const PolicySchema* GetSchemaForAdditionalProperties() const; + const PolicySchema* GetSchemaForProperty(const std::string& key) const; + + // It is invalid to call this method when type() is not TYPE_LIST. + // Returns the type of the entries of this "array", which is never NULL. + virtual const PolicySchema* GetSchemaForItems() const; + + private: + const base::Value::Type type_; + + DISALLOW_COPY_AND_ASSIGN(PolicySchema); +}; + +} // namespace policy + +#endif // COMPONENTS_POLICY_CORE_COMMON_POLICY_SCHEMA_H_ diff --git a/chromium/components/policy/core/common/policy_schema_unittest.cc b/chromium/components/policy/core/common/policy_schema_unittest.cc new file mode 100644 index 00000000000..bfbdd652159 --- /dev/null +++ b/chromium/components/policy/core/common/policy_schema_unittest.cc @@ -0,0 +1,193 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/policy/core/common/policy_schema.h" + +#include "testing/gtest/include/gtest/gtest.h" + +namespace policy { + +namespace { + +#define SCHEMA_VERSION "\"$schema\":\"http://json-schema.org/draft-03/schema#\"" +#define OBJECT_TYPE "\"type\":\"object\"" + +bool ParseFails(const std::string& content) { + std::string error; + scoped_ptr<PolicySchema> schema = PolicySchema::Parse(content, &error); + EXPECT_TRUE(schema || !error.empty()); + return !schema; +} + +} // namespace + +TEST(PolicySchemaTest, MinimalSchema) { + EXPECT_FALSE(ParseFails( + "{" + SCHEMA_VERSION "," + OBJECT_TYPE + "}")); +} + +TEST(PolicySchemaTest, InvalidSchemas) { + EXPECT_TRUE(ParseFails("")); + EXPECT_TRUE(ParseFails("omg")); + EXPECT_TRUE(ParseFails("\"omg\"")); + EXPECT_TRUE(ParseFails("123")); + EXPECT_TRUE(ParseFails("[]")); + EXPECT_TRUE(ParseFails("null")); + EXPECT_TRUE(ParseFails("{}")); + EXPECT_TRUE(ParseFails("{" SCHEMA_VERSION "}")); + EXPECT_TRUE(ParseFails("{" OBJECT_TYPE "}")); + + EXPECT_TRUE(ParseFails( + "{" + SCHEMA_VERSION "," + OBJECT_TYPE "," + "\"additionalProperties\": { \"type\":\"object\" }" + "}")); + + EXPECT_TRUE(ParseFails( + "{" + SCHEMA_VERSION "," + OBJECT_TYPE "," + "\"patternProperties\": { \"a+b*\": { \"type\": \"object\" } }" + "}")); + + EXPECT_TRUE(ParseFails( + "{" + SCHEMA_VERSION "," + OBJECT_TYPE "," + "\"properties\": { \"Policy\": { \"type\": \"bogus\" } }" + "}")); + + EXPECT_TRUE(ParseFails( + "{" + SCHEMA_VERSION "," + OBJECT_TYPE "," + "\"properties\": { \"Policy\": { \"type\": [\"string\", \"number\"] } }" + "}")); + + EXPECT_TRUE(ParseFails( + "{" + SCHEMA_VERSION "," + OBJECT_TYPE "," + "\"properties\": { \"Policy\": { \"type\": \"any\" } }" + "}")); +} + +TEST(PolicySchemaTest, ValidSchema) { + std::string error; + scoped_ptr<PolicySchema> schema = PolicySchema::Parse( + "{" + SCHEMA_VERSION "," + OBJECT_TYPE "," + "\"properties\": {" + " \"Boolean\": { \"type\": \"boolean\" }," + " \"Integer\": { \"type\": \"integer\" }," + " \"Null\": { \"type\": \"null\" }," + " \"Number\": { \"type\": \"number\" }," + " \"String\": { \"type\": \"string\" }," + " \"Array\": {" + " \"type\": \"array\"," + " \"items\": { \"type\": \"string\" }" + " }," + " \"ArrayOfObjects\": {" + " \"type\": \"array\"," + " \"items\": {" + " \"type\": \"object\"," + " \"properties\": {" + " \"one\": { \"type\": \"string\" }," + " \"two\": { \"type\": \"integer\" }" + " }" + " }" + " }," + " \"ArrayOfArray\": {" + " \"type\": \"array\"," + " \"items\": {" + " \"type\": \"array\"," + " \"items\": { \"type\": \"string\" }" + " }" + " }," + " \"Object\": {" + " \"type\": \"object\"," + " \"properties\": {" + " \"one\": { \"type\": \"boolean\" }," + " \"two\": { \"type\": \"integer\" }" + " }," + " \"additionalProperties\": { \"type\": \"string\" }" + " }" + "}" + "}", &error); + ASSERT_TRUE(schema) << error; + + ASSERT_EQ(base::Value::TYPE_DICTIONARY, schema->type()); + EXPECT_FALSE(schema->GetSchemaForProperty("invalid")); + + const PolicySchema* sub = schema->GetSchemaForProperty("Boolean"); + ASSERT_TRUE(sub); + EXPECT_EQ(base::Value::TYPE_BOOLEAN, sub->type()); + + sub = schema->GetSchemaForProperty("Integer"); + ASSERT_TRUE(sub); + EXPECT_EQ(base::Value::TYPE_INTEGER, sub->type()); + + sub = schema->GetSchemaForProperty("Null"); + ASSERT_TRUE(sub); + EXPECT_EQ(base::Value::TYPE_NULL, sub->type()); + + sub = schema->GetSchemaForProperty("Number"); + ASSERT_TRUE(sub); + EXPECT_EQ(base::Value::TYPE_DOUBLE, sub->type()); + sub = schema->GetSchemaForProperty("String"); + ASSERT_TRUE(sub); + EXPECT_EQ(base::Value::TYPE_STRING, sub->type()); + + sub = schema->GetSchemaForProperty("Array"); + ASSERT_TRUE(sub); + ASSERT_EQ(base::Value::TYPE_LIST, sub->type()); + sub = sub->GetSchemaForItems(); + ASSERT_TRUE(sub); + EXPECT_EQ(base::Value::TYPE_STRING, sub->type()); + + sub = schema->GetSchemaForProperty("ArrayOfObjects"); + ASSERT_TRUE(sub); + ASSERT_EQ(base::Value::TYPE_LIST, sub->type()); + sub = sub->GetSchemaForItems(); + ASSERT_TRUE(sub); + EXPECT_EQ(base::Value::TYPE_DICTIONARY, sub->type()); + const PolicySchema* subsub = sub->GetSchemaForProperty("one"); + ASSERT_TRUE(subsub); + EXPECT_EQ(base::Value::TYPE_STRING, subsub->type()); + subsub = sub->GetSchemaForProperty("two"); + ASSERT_TRUE(subsub); + EXPECT_EQ(base::Value::TYPE_INTEGER, subsub->type()); + subsub = sub->GetSchemaForProperty("invalid"); + EXPECT_FALSE(subsub); + + sub = schema->GetSchemaForProperty("ArrayOfArray"); + ASSERT_TRUE(sub); + ASSERT_EQ(base::Value::TYPE_LIST, sub->type()); + sub = sub->GetSchemaForItems(); + ASSERT_TRUE(sub); + ASSERT_EQ(base::Value::TYPE_LIST, sub->type()); + sub = sub->GetSchemaForItems(); + ASSERT_TRUE(sub); + EXPECT_EQ(base::Value::TYPE_STRING, sub->type()); + + sub = schema->GetSchemaForProperty("Object"); + ASSERT_TRUE(sub); + ASSERT_EQ(base::Value::TYPE_DICTIONARY, sub->type()); + subsub = sub->GetSchemaForProperty("one"); + ASSERT_TRUE(subsub); + EXPECT_EQ(base::Value::TYPE_BOOLEAN, subsub->type()); + subsub = sub->GetSchemaForProperty("two"); + ASSERT_TRUE(subsub); + EXPECT_EQ(base::Value::TYPE_INTEGER, subsub->type()); + subsub = sub->GetSchemaForProperty("undeclared"); + ASSERT_TRUE(subsub); + EXPECT_EQ(base::Value::TYPE_STRING, subsub->type()); +} + +} // namespace policy diff --git a/chromium/components/policy/core/common/schema.cc b/chromium/components/policy/core/common/schema.cc new file mode 100644 index 00000000000..cbfde46faf3 --- /dev/null +++ b/chromium/components/policy/core/common/schema.cc @@ -0,0 +1,276 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/policy/core/common/schema.h" + +#include <algorithm> + +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "components/json_schema/json_schema_constants.h" +#include "components/json_schema/json_schema_validator.h" +#include "components/policy/core/common/schema_internal.h" + +namespace policy { + +namespace { + +bool SchemaTypeToValueType(const std::string& type_string, + base::Value::Type* type) { + // Note: "any" is not an accepted type. + static const struct { + const char* schema_type; + base::Value::Type value_type; + } kSchemaToValueTypeMap[] = { + { json_schema_constants::kArray, base::Value::TYPE_LIST }, + { json_schema_constants::kBoolean, base::Value::TYPE_BOOLEAN }, + { json_schema_constants::kInteger, base::Value::TYPE_INTEGER }, + { json_schema_constants::kNull, base::Value::TYPE_NULL }, + { json_schema_constants::kNumber, base::Value::TYPE_DOUBLE }, + { json_schema_constants::kObject, base::Value::TYPE_DICTIONARY }, + { json_schema_constants::kString, base::Value::TYPE_STRING }, + }; + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kSchemaToValueTypeMap); ++i) { + if (kSchemaToValueTypeMap[i].schema_type == type_string) { + *type = kSchemaToValueTypeMap[i].value_type; + return true; + } + } + return false; +} + +} // namespace + +Schema::Iterator::Iterator(const internal::PropertiesNode* properties) + : it_(properties->begin), + end_(properties->end) {} + +Schema::Iterator::Iterator(const Iterator& iterator) + : it_(iterator.it_), + end_(iterator.end_) {} + +Schema::Iterator::~Iterator() {} + +Schema::Iterator& Schema::Iterator::operator=(const Iterator& iterator) { + it_ = iterator.it_; + end_ = iterator.end_; + return *this; +} + +bool Schema::Iterator::IsAtEnd() const { + return it_ == end_; +} + +void Schema::Iterator::Advance() { + ++it_; +} + +const char* Schema::Iterator::key() const { + return it_->key; +} + +Schema Schema::Iterator::schema() const { + return Schema(it_->schema); +} + +Schema::Schema(const internal::SchemaNode* schema) : schema_(schema) {} + +Schema::Schema(const Schema& schema) : schema_(schema.schema_) {} + +Schema& Schema::operator=(const Schema& schema) { + schema_ = schema.schema_; + return *this; +} + +base::Value::Type Schema::type() const { + CHECK(valid()); + return schema_->type; +} + +Schema::Iterator Schema::GetPropertiesIterator() const { + CHECK(valid()); + CHECK_EQ(base::Value::TYPE_DICTIONARY, type()); + return Iterator( + static_cast<const internal::PropertiesNode*>(schema_->extra)); +} + +namespace { + +bool CompareKeys(const internal::PropertyNode& node, const std::string& key) { + return node.key < key; +} + +} // namespace + +Schema Schema::GetKnownProperty(const std::string& key) const { + CHECK(valid()); + CHECK_EQ(base::Value::TYPE_DICTIONARY, type()); + const internal::PropertiesNode* properties_node = + static_cast<const internal::PropertiesNode*>(schema_->extra); + const internal::PropertyNode* it = std::lower_bound( + properties_node->begin, properties_node->end, key, CompareKeys); + if (it != properties_node->end && it->key == key) + return Schema(it->schema); + return Schema(NULL); +} + +Schema Schema::GetAdditionalProperties() const { + CHECK(valid()); + CHECK_EQ(base::Value::TYPE_DICTIONARY, type()); + return Schema( + static_cast<const internal::PropertiesNode*>(schema_->extra)->additional); +} + +Schema Schema::GetProperty(const std::string& key) const { + Schema schema = GetKnownProperty(key); + return schema.valid() ? schema : GetAdditionalProperties(); +} + +Schema Schema::GetItems() const { + CHECK(valid()); + CHECK_EQ(base::Value::TYPE_LIST, type()); + return Schema(static_cast<const internal::SchemaNode*>(schema_->extra)); +} + +SchemaOwner::SchemaOwner(const internal::SchemaNode* root) : root_(root) {} + +SchemaOwner::~SchemaOwner() { + for (size_t i = 0; i < property_nodes_.size(); ++i) + delete[] property_nodes_[i]; +} + +// static +scoped_ptr<SchemaOwner> SchemaOwner::Wrap(const internal::SchemaNode* schema) { + return scoped_ptr<SchemaOwner>(new SchemaOwner(schema)); +} + +// static +scoped_ptr<SchemaOwner> SchemaOwner::Parse(const std::string& content, + std::string* error) { + // Validate as a generic JSON schema. + scoped_ptr<base::DictionaryValue> dict = + JSONSchemaValidator::IsValidSchema(content, error); + if (!dict) + return scoped_ptr<SchemaOwner>(); + + // Validate the main type. + std::string string_value; + if (!dict->GetString(json_schema_constants::kType, &string_value) || + string_value != json_schema_constants::kObject) { + *error = + "The main schema must have a type attribute with \"object\" value."; + return scoped_ptr<SchemaOwner>(); + } + + // Checks for invalid attributes at the top-level. + if (dict->HasKey(json_schema_constants::kAdditionalProperties) || + dict->HasKey(json_schema_constants::kPatternProperties)) { + *error = "\"additionalProperties\" and \"patternProperties\" are not " + "supported at the main schema."; + return scoped_ptr<SchemaOwner>(); + } + + scoped_ptr<SchemaOwner> impl(new SchemaOwner(NULL)); + impl->root_ = impl->Parse(*dict, error); + if (!impl->root_) + impl.reset(); + return impl.PassAs<SchemaOwner>(); +} + +const internal::SchemaNode* SchemaOwner::Parse( + const base::DictionaryValue& schema, + std::string* error) { + std::string type_string; + if (!schema.GetString(json_schema_constants::kType, &type_string)) { + *error = "The schema type must be declared."; + return NULL; + } + + base::Value::Type type = base::Value::TYPE_NULL; + if (!SchemaTypeToValueType(type_string, &type)) { + *error = "Type not supported: " + type_string; + return NULL; + } + + if (type == base::Value::TYPE_DICTIONARY) + return ParseDictionary(schema, error); + if (type == base::Value::TYPE_LIST) + return ParseList(schema, error); + + internal::SchemaNode* node = new internal::SchemaNode; + node->type = type; + node->extra = NULL; + schema_nodes_.push_back(node); + return node; +} + +const internal::SchemaNode* SchemaOwner::ParseDictionary( + const base::DictionaryValue& schema, + std::string* error) { + internal::PropertiesNode* properties_node = new internal::PropertiesNode; + properties_node->begin = NULL; + properties_node->end = NULL; + properties_node->additional = NULL; + properties_nodes_.push_back(properties_node); + + const base::DictionaryValue* dict = NULL; + const base::DictionaryValue* properties = NULL; + if (schema.GetDictionary(json_schema_constants::kProperties, &properties)) { + internal::PropertyNode* property_nodes = + new internal::PropertyNode[properties->size()]; + property_nodes_.push_back(property_nodes); + + size_t index = 0; + for (base::DictionaryValue::Iterator it(*properties); + !it.IsAtEnd(); it.Advance(), ++index) { + // This should have been verified by the JSONSchemaValidator. + CHECK(it.value().GetAsDictionary(&dict)); + const internal::SchemaNode* sub_schema = Parse(*dict, error); + if (!sub_schema) + return NULL; + std::string* key = new std::string(it.key()); + keys_.push_back(key); + property_nodes[index].key = key->c_str(); + property_nodes[index].schema = sub_schema; + } + CHECK_EQ(properties->size(), index); + properties_node->begin = property_nodes; + properties_node->end = property_nodes + index; + } + + if (schema.GetDictionary(json_schema_constants::kAdditionalProperties, + &dict)) { + const internal::SchemaNode* sub_schema = Parse(*dict, error); + if (!sub_schema) + return NULL; + properties_node->additional = sub_schema; + } + + internal::SchemaNode* schema_node = new internal::SchemaNode; + schema_node->type = base::Value::TYPE_DICTIONARY; + schema_node->extra = properties_node; + schema_nodes_.push_back(schema_node); + return schema_node; +} + +const internal::SchemaNode* SchemaOwner::ParseList( + const base::DictionaryValue& schema, + std::string* error) { + const base::DictionaryValue* dict = NULL; + if (!schema.GetDictionary(json_schema_constants::kItems, &dict)) { + *error = "Arrays must declare a single schema for their items."; + return NULL; + } + const internal::SchemaNode* items_schema_node = Parse(*dict, error); + if (!items_schema_node) + return NULL; + + internal::SchemaNode* schema_node = new internal::SchemaNode; + schema_node->type = base::Value::TYPE_LIST; + schema_node->extra = items_schema_node; + schema_nodes_.push_back(schema_node); + return schema_node; +} + +} // namespace policy diff --git a/chromium/components/policy/core/common/schema.h b/chromium/components/policy/core/common/schema.h new file mode 100644 index 00000000000..a81b7adae30 --- /dev/null +++ b/chromium/components/policy/core/common/schema.h @@ -0,0 +1,150 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_POLICY_CORE_COMMON_SCHEMA_H_ +#define COMPONENTS_POLICY_CORE_COMMON_SCHEMA_H_ + +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/scoped_vector.h" +#include "base/values.h" +#include "components/policy/policy_export.h" + +namespace policy { +namespace internal { + +struct POLICY_EXPORT SchemaNode; +struct POLICY_EXPORT PropertyNode; +struct POLICY_EXPORT PropertiesNode; + +} // namespace internal + +// Describes the expected type of one policy. Also recursively describes the +// types of inner elements, for structured types. +// Objects of this class refer to external, immutable data and are cheap to +// copy. +// Use the SchemaOwner class to parse a schema and get Schema objects. +class POLICY_EXPORT Schema { + public: + explicit Schema(const internal::SchemaNode* schema); + Schema(const Schema& schema); + + Schema& operator=(const Schema& schema); + + // Returns true if this Schema is valid. Schemas returned by the methods below + // may be invalid, and in those cases the other methods must not be used. + bool valid() const { return schema_ != NULL; } + + base::Value::Type type() const; + + // Used to iterate over the known properties of TYPE_DICTIONARY schemas. + class POLICY_EXPORT Iterator { + public: + explicit Iterator(const internal::PropertiesNode* properties); + Iterator(const Iterator& iterator); + ~Iterator(); + + Iterator& operator=(const Iterator& iterator); + + // The other methods must not be called if the iterator is at the end. + bool IsAtEnd() const; + + // Advances the iterator to the next property. + void Advance(); + + // Returns the name of the current property. + const char* key() const; + + // Returns the Schema for the current property. This Schema is always valid. + Schema schema() const; + + private: + const internal::PropertyNode* it_; + const internal::PropertyNode* end_; + }; + + // These methods should be called only if type() == TYPE_DICTIONARY, + // otherwise invalid memory will be read. A CHECK is currently enforcing this. + + // Returns an iterator that goes over the named properties of this schema. + // The returned iterator is at the beginning. + Iterator GetPropertiesIterator() const; + + // Returns the Schema for the property named |key|. If |key| is not a known + // property name then the returned Schema is not valid. + Schema GetKnownProperty(const std::string& key) const; + + // Returns the Schema for additional properties. If additional properties are + // not allowed for this Schema then the Schema returned is not valid. + Schema GetAdditionalProperties() const; + + // Returns the Schema for |key| if it is a known property, otherwise returns + // the Schema for additional properties. + Schema GetProperty(const std::string& key) const; + + // Returns the Schema for items of an array. + // This method should be called only if type() == TYPE_LIST, + // otherwise invalid memory will be read. A CHECK is currently enforcing this. + Schema GetItems() const; + + private: + const internal::SchemaNode* schema_; +}; + +// Owns schemas for policies of a given component. +class POLICY_EXPORT SchemaOwner { + public: + ~SchemaOwner(); + + // The returned Schema is valid only during the lifetime of the SchemaOwner + // that created it. It may be obtained multiple times. + Schema schema() const { return Schema(root_); } + + // Returns a SchemaOwner that references static data. This can be used by + // the embedder to pass structures generated at compile time, which can then + // be quickly loaded at runtime. + // Note: PropertiesNodes must have their PropertyNodes sorted by key. + static scoped_ptr<SchemaOwner> Wrap(const internal::SchemaNode* schema); + + // Parses the JSON schema in |schema| and returns a SchemaOwner that owns + // the internal representation. If |schema| is invalid then NULL is returned + // and |error| contains a reason for the failure. + static scoped_ptr<SchemaOwner> Parse(const std::string& schema, + std::string* error); + + private: + explicit SchemaOwner(const internal::SchemaNode* root); + + // Parses the JSON schema in |schema| and returns the root SchemaNode if + // successful, otherwise returns NULL. Any intermediate objects built by + // this method are appended to the ScopedVectors. + const internal::SchemaNode* Parse(const base::DictionaryValue& schema, + std::string* error); + + // Helper for Parse(). + const internal::SchemaNode* ParseDictionary( + const base::DictionaryValue& schema, + std::string* error); + + // Helper for Parse(). + const internal::SchemaNode* ParseList(const base::DictionaryValue& schema, + std::string* error); + + const internal::SchemaNode* root_; + ScopedVector<internal::SchemaNode> schema_nodes_; + // Note: |property_nodes_| contains PropertyNode[] elements and must be + // cleared manually to properly use delete[]. + std::vector<internal::PropertyNode*> property_nodes_; + ScopedVector<internal::PropertiesNode> properties_nodes_; + ScopedVector<std::string> keys_; + + DISALLOW_COPY_AND_ASSIGN(SchemaOwner); +}; + +} // namespace policy + +#endif // COMPONENTS_POLICY_CORE_COMMON_SCHEMA_H_ diff --git a/chromium/components/policy/core/common/schema_internal.h b/chromium/components/policy/core/common/schema_internal.h new file mode 100644 index 00000000000..025a4373ee7 --- /dev/null +++ b/chromium/components/policy/core/common/schema_internal.h @@ -0,0 +1,45 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_POLICY_CORE_COMMON_SCHEMA_INTERNAL_H_ +#define COMPONENTS_POLICY_CORE_COMMON_SCHEMA_INTERNAL_H_ + +#include "base/values.h" +#include "components/policy/policy_export.h" + +namespace policy { +namespace internal { + +// These types are used internally by the SchemaOwner parser, and by the +// compile-time code generator. They shouldn't be used directly. + +struct POLICY_EXPORT SchemaNode { + base::Value::Type type; + + // If |type| is TYPE_LIST then this is a SchemaNode* describing the + // element type. + // + // If |type| is TYPE_DICTIONARY then this is a PropertiesNode* that can + // contain any number of named properties and optionally a SchemaNode* for + // additional properties. + // + // This is NULL if |type| has any other values. + const void* extra; +}; + +struct POLICY_EXPORT PropertyNode { + const char* key; + const SchemaNode* schema; +}; + +struct POLICY_EXPORT PropertiesNode { + const PropertyNode* begin; + const PropertyNode* end; + const SchemaNode* additional; +}; + +} // namespace internal +} // namespace policy + +#endif // COMPONENTS_POLICY_CORE_COMMON_SCHEMA_INTERNAL_H_ diff --git a/chromium/components/policy/core/common/schema_unittest.cc b/chromium/components/policy/core/common/schema_unittest.cc new file mode 100644 index 00000000000..06fb3bf34a3 --- /dev/null +++ b/chromium/components/policy/core/common/schema_unittest.cc @@ -0,0 +1,338 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/policy/core/common/schema.h" + +#include "components/policy/core/common/schema_internal.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace policy { + +namespace { + +#define OBJECT_TYPE "\"type\":\"object\"" + +const internal::SchemaNode kTypeBoolean = { base::Value::TYPE_BOOLEAN, NULL, }; +const internal::SchemaNode kTypeInteger = { base::Value::TYPE_INTEGER, NULL, }; +const internal::SchemaNode kTypeNumber = { base::Value::TYPE_DOUBLE, NULL, }; +const internal::SchemaNode kTypeString = { base::Value::TYPE_STRING, NULL, }; + +bool ParseFails(const std::string& content) { + std::string error; + scoped_ptr<SchemaOwner> schema = SchemaOwner::Parse(content, &error); + if (schema) + EXPECT_TRUE(schema->schema().valid()); + else + EXPECT_FALSE(error.empty()); + return !schema; +} + +} // namespace + +TEST(SchemaTest, MinimalSchema) { + EXPECT_FALSE(ParseFails( + "{" + OBJECT_TYPE + "}")); +} + +TEST(SchemaTest, InvalidSchemas) { + EXPECT_TRUE(ParseFails("")); + EXPECT_TRUE(ParseFails("omg")); + EXPECT_TRUE(ParseFails("\"omg\"")); + EXPECT_TRUE(ParseFails("123")); + EXPECT_TRUE(ParseFails("[]")); + EXPECT_TRUE(ParseFails("null")); + EXPECT_TRUE(ParseFails("{}")); + + EXPECT_TRUE(ParseFails( + "{" + OBJECT_TYPE "," + "\"additionalProperties\": { \"type\":\"object\" }" + "}")); + + EXPECT_TRUE(ParseFails( + "{" + OBJECT_TYPE "," + "\"patternProperties\": { \"a+b*\": { \"type\": \"object\" } }" + "}")); + + EXPECT_TRUE(ParseFails( + "{" + OBJECT_TYPE "," + "\"properties\": { \"Policy\": { \"type\": \"bogus\" } }" + "}")); + + EXPECT_TRUE(ParseFails( + "{" + OBJECT_TYPE "," + "\"properties\": { \"Policy\": { \"type\": [\"string\", \"number\"] } }" + "}")); + + EXPECT_TRUE(ParseFails( + "{" + OBJECT_TYPE "," + "\"properties\": { \"Policy\": { \"type\": \"any\" } }" + "}")); +} + +TEST(SchemaTest, ValidSchema) { + std::string error; + scoped_ptr<SchemaOwner> policy_schema = SchemaOwner::Parse( + "{" + OBJECT_TYPE "," + "\"properties\": {" + " \"Boolean\": { \"type\": \"boolean\" }," + " \"Integer\": { \"type\": \"integer\" }," + " \"Null\": { \"type\": \"null\" }," + " \"Number\": { \"type\": \"number\" }," + " \"String\": { \"type\": \"string\" }," + " \"Array\": {" + " \"type\": \"array\"," + " \"items\": { \"type\": \"string\" }" + " }," + " \"ArrayOfObjects\": {" + " \"type\": \"array\"," + " \"items\": {" + " \"type\": \"object\"," + " \"properties\": {" + " \"one\": { \"type\": \"string\" }," + " \"two\": { \"type\": \"integer\" }" + " }" + " }" + " }," + " \"ArrayOfArray\": {" + " \"type\": \"array\"," + " \"items\": {" + " \"type\": \"array\"," + " \"items\": { \"type\": \"string\" }" + " }" + " }," + " \"Object\": {" + " \"type\": \"object\"," + " \"properties\": {" + " \"one\": { \"type\": \"boolean\" }," + " \"two\": { \"type\": \"integer\" }" + " }," + " \"additionalProperties\": { \"type\": \"string\" }" + " }" + "}" + "}", &error); + ASSERT_TRUE(policy_schema) << error; + ASSERT_TRUE(policy_schema->schema().valid()); + + Schema schema = policy_schema->schema(); + ASSERT_EQ(base::Value::TYPE_DICTIONARY, schema.type()); + EXPECT_FALSE(schema.GetProperty("invalid").valid()); + + Schema sub = schema.GetProperty("Boolean"); + ASSERT_TRUE(sub.valid()); + EXPECT_EQ(base::Value::TYPE_BOOLEAN, sub.type()); + + sub = schema.GetProperty("Integer"); + ASSERT_TRUE(sub.valid()); + EXPECT_EQ(base::Value::TYPE_INTEGER, sub.type()); + + sub = schema.GetProperty("Null"); + ASSERT_TRUE(sub.valid()); + EXPECT_EQ(base::Value::TYPE_NULL, sub.type()); + + sub = schema.GetProperty("Number"); + ASSERT_TRUE(sub.valid()); + EXPECT_EQ(base::Value::TYPE_DOUBLE, sub.type()); + + sub = schema.GetProperty("String"); + ASSERT_TRUE(sub.valid()); + EXPECT_EQ(base::Value::TYPE_STRING, sub.type()); + + sub = schema.GetProperty("Array"); + ASSERT_TRUE(sub.valid()); + ASSERT_EQ(base::Value::TYPE_LIST, sub.type()); + sub = sub.GetItems(); + ASSERT_TRUE(sub.valid()); + EXPECT_EQ(base::Value::TYPE_STRING, sub.type()); + + sub = schema.GetProperty("ArrayOfObjects"); + ASSERT_TRUE(sub.valid()); + ASSERT_EQ(base::Value::TYPE_LIST, sub.type()); + sub = sub.GetItems(); + ASSERT_TRUE(sub.valid()); + EXPECT_EQ(base::Value::TYPE_DICTIONARY, sub.type()); + Schema subsub = sub.GetProperty("one"); + ASSERT_TRUE(subsub.valid()); + EXPECT_EQ(base::Value::TYPE_STRING, subsub.type()); + subsub = sub.GetProperty("two"); + ASSERT_TRUE(subsub.valid()); + EXPECT_EQ(base::Value::TYPE_INTEGER, subsub.type()); + subsub = sub.GetProperty("invalid"); + EXPECT_FALSE(subsub.valid()); + + sub = schema.GetProperty("ArrayOfArray"); + ASSERT_TRUE(sub.valid()); + ASSERT_EQ(base::Value::TYPE_LIST, sub.type()); + sub = sub.GetItems(); + ASSERT_TRUE(sub.valid()); + ASSERT_EQ(base::Value::TYPE_LIST, sub.type()); + sub = sub.GetItems(); + ASSERT_TRUE(sub.valid()); + EXPECT_EQ(base::Value::TYPE_STRING, sub.type()); + + sub = schema.GetProperty("Object"); + ASSERT_TRUE(sub.valid()); + ASSERT_EQ(base::Value::TYPE_DICTIONARY, sub.type()); + subsub = sub.GetProperty("one"); + ASSERT_TRUE(subsub.valid()); + EXPECT_EQ(base::Value::TYPE_BOOLEAN, subsub.type()); + subsub = sub.GetProperty("two"); + ASSERT_TRUE(subsub.valid()); + EXPECT_EQ(base::Value::TYPE_INTEGER, subsub.type()); + subsub = sub.GetProperty("undeclared"); + ASSERT_TRUE(subsub.valid()); + EXPECT_EQ(base::Value::TYPE_STRING, subsub.type()); + + struct { + const char* expected_key; + base::Value::Type expected_type; + } kExpectedProperties[] = { + { "Array", base::Value::TYPE_LIST }, + { "ArrayOfArray", base::Value::TYPE_LIST }, + { "ArrayOfObjects", base::Value::TYPE_LIST }, + { "Boolean", base::Value::TYPE_BOOLEAN }, + { "Integer", base::Value::TYPE_INTEGER }, + { "Null", base::Value::TYPE_NULL }, + { "Number", base::Value::TYPE_DOUBLE }, + { "Object", base::Value::TYPE_DICTIONARY }, + { "String", base::Value::TYPE_STRING }, + }; + Schema::Iterator it = schema.GetPropertiesIterator(); + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kExpectedProperties); ++i) { + ASSERT_FALSE(it.IsAtEnd()); + EXPECT_STREQ(kExpectedProperties[i].expected_key, it.key()); + ASSERT_TRUE(it.schema().valid()); + EXPECT_EQ(kExpectedProperties[i].expected_type, it.schema().type()); + it.Advance(); + } + EXPECT_TRUE(it.IsAtEnd()); +} + +TEST(SchemaTest, Lookups) { + std::string error; + scoped_ptr<SchemaOwner> policy_schema = SchemaOwner::Parse( + "{" + OBJECT_TYPE + "}", &error); + ASSERT_TRUE(policy_schema) << error; + Schema schema = policy_schema->schema(); + ASSERT_TRUE(schema.valid()); + ASSERT_EQ(base::Value::TYPE_DICTIONARY, schema.type()); + + // This empty schema should never find named properties. + EXPECT_FALSE(schema.GetKnownProperty("").valid()); + EXPECT_FALSE(schema.GetKnownProperty("xyz").valid()); + EXPECT_TRUE(schema.GetPropertiesIterator().IsAtEnd()); + + policy_schema = SchemaOwner::Parse( + "{" + OBJECT_TYPE "," + "\"properties\": {" + " \"Boolean\": { \"type\": \"boolean\" }" + "}" + "}", &error); + ASSERT_TRUE(policy_schema) << error; + schema = policy_schema->schema(); + ASSERT_TRUE(schema.valid()); + ASSERT_EQ(base::Value::TYPE_DICTIONARY, schema.type()); + + EXPECT_FALSE(schema.GetKnownProperty("").valid()); + EXPECT_FALSE(schema.GetKnownProperty("xyz").valid()); + EXPECT_TRUE(schema.GetKnownProperty("Boolean").valid()); + + policy_schema = SchemaOwner::Parse( + "{" + OBJECT_TYPE "," + "\"properties\": {" + " \"bb\" : { \"type\": \"null\" }," + " \"aa\" : { \"type\": \"boolean\" }," + " \"abab\" : { \"type\": \"string\" }," + " \"ab\" : { \"type\": \"number\" }," + " \"aba\" : { \"type\": \"integer\" }" + "}" + "}", &error); + ASSERT_TRUE(policy_schema) << error; + schema = policy_schema->schema(); + ASSERT_TRUE(schema.valid()); + ASSERT_EQ(base::Value::TYPE_DICTIONARY, schema.type()); + + EXPECT_FALSE(schema.GetKnownProperty("").valid()); + EXPECT_FALSE(schema.GetKnownProperty("xyz").valid()); + + struct { + const char* expected_key; + base::Value::Type expected_type; + } kExpectedKeys[] = { + { "aa", base::Value::TYPE_BOOLEAN }, + { "ab", base::Value::TYPE_DOUBLE }, + { "aba", base::Value::TYPE_INTEGER }, + { "abab", base::Value::TYPE_STRING }, + { "bb", base::Value::TYPE_NULL }, + }; + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kExpectedKeys); ++i) { + Schema sub = schema.GetKnownProperty(kExpectedKeys[i].expected_key); + ASSERT_TRUE(sub.valid()); + EXPECT_EQ(kExpectedKeys[i].expected_type, sub.type()); + } +} + +TEST(SchemaTest, WrapSimpleNode) { + scoped_ptr<SchemaOwner> policy_schema = SchemaOwner::Wrap(&kTypeString); + ASSERT_TRUE(policy_schema); + Schema schema = policy_schema->schema(); + ASSERT_TRUE(schema.valid()); + EXPECT_EQ(base::Value::TYPE_STRING, schema.type()); +} + +TEST(SchemaTest, WrapDictionary) { + const internal::SchemaNode kList = { + base::Value::TYPE_LIST, + &kTypeString, + }; + + const internal::PropertyNode kPropertyNodes[] = { + { "Boolean", &kTypeBoolean }, + { "Integer", &kTypeInteger }, + { "List", &kList }, + { "Number", &kTypeNumber }, + { "String", &kTypeString }, + }; + + const internal::PropertiesNode kProperties = { + kPropertyNodes, + kPropertyNodes + arraysize(kPropertyNodes), + NULL, + }; + + const internal::SchemaNode root = { + base::Value::TYPE_DICTIONARY, + &kProperties, + }; + + scoped_ptr<SchemaOwner> policy_schema = SchemaOwner::Wrap(&root); + ASSERT_TRUE(policy_schema); + Schema schema = policy_schema->schema(); + ASSERT_TRUE(schema.valid()); + EXPECT_EQ(base::Value::TYPE_DICTIONARY, schema.type()); + + Schema::Iterator it = schema.GetPropertiesIterator(); + for (size_t i = 0; i < arraysize(kPropertyNodes); ++i) { + ASSERT_FALSE(it.IsAtEnd()); + EXPECT_STREQ(kPropertyNodes[i].key, it.key()); + Schema sub = it.schema(); + ASSERT_TRUE(sub.valid()); + EXPECT_EQ(kPropertyNodes[i].schema->type, sub.type()); + it.Advance(); + } + EXPECT_TRUE(it.IsAtEnd()); +} + +} // namespace policy diff --git a/chromium/components/policy/policy_export.h b/chromium/components/policy/policy_export.h new file mode 100644 index 00000000000..47376acaa85 --- /dev/null +++ b/chromium/components/policy/policy_export.h @@ -0,0 +1,29 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_POLICY_POLICY_EXPORT_H_ +#define COMPONENTS_POLICY_POLICY_EXPORT_H_ + +#if defined(COMPONENT_BUILD) +#if defined(WIN32) + +#if defined(POLICY_COMPONENT_IMPLEMENTATION) +#define POLICY_EXPORT __declspec(dllexport) +#else +#define POLICY_EXPORT __declspec(dllimport) +#endif // defined(BASE_PREFS_IMPLEMENTATION) + +#else // defined(WIN32) +#if defined(POLICY_COMPONENT_IMPLEMENTATION) +#define POLICY_EXPORT __attribute__((visibility("default"))) +#else +#define POLICY_EXPORT +#endif +#endif + +#else // defined(COMPONENT_BUILD) +#define POLICY_EXPORT +#endif + +#endif // COMPONENTS_POLICY_POLICY_EXPORT_H_ diff --git a/chromium/components/policy/stub_to_remove.cc b/chromium/components/policy/stub_to_remove.cc new file mode 100644 index 00000000000..6e352ace9cd --- /dev/null +++ b/chromium/components/policy/stub_to_remove.cc @@ -0,0 +1,6 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// TODO(joaodasilva): remove this file and update the comment on policy.gypi. +// http://crbug.com/271392 diff --git a/chromium/components/sessions/serialized_navigation_entry.cc b/chromium/components/sessions/serialized_navigation_entry.cc index 5e4183cf5d2..0fc6ff184b3 100644 --- a/chromium/components/sessions/serialized_navigation_entry.cc +++ b/chromium/components/sessions/serialized_navigation_entry.cc @@ -26,6 +26,7 @@ SerializedNavigationEntry::SerializedNavigationEntry() has_post_data_(false), post_id_(-1), is_overriding_user_agent_(false), + http_status_code_(0), blocked_state_(STATE_INVALID) {} SerializedNavigationEntry::~SerializedNavigationEntry() {} @@ -53,6 +54,7 @@ SerializedNavigationEntry SerializedNavigationEntry::FromNavigationEntry( entry.GetExtraData(kSearchTermsKey, &navigation.search_terms_); if (entry.GetFavicon().valid) navigation.favicon_url_ = entry.GetFavicon().url; + navigation.http_status_code_ = entry.GetHttpStatusCode(); return navigation; } @@ -143,6 +145,8 @@ SerializedNavigationEntry SerializedNavigationEntry::FromSyncData( if (sync_data.has_favicon_url()) navigation.favicon_url_ = GURL(sync_data.favicon_url()); + navigation.http_status_code_ = sync_data.http_status_code(); + // We shouldn't sync session data for managed users down at the moment. DCHECK(!sync_data.has_blocked_state()); DCHECK_EQ(0, sync_data.content_pack_categories_size()); @@ -216,6 +220,7 @@ enum TypeMask { // is_overriding_user_agent_ // timestamp_ // search_terms_ +// http_status_code_ void SerializedNavigationEntry::WriteToPickle(int max_size, Pickle* pickle) const { @@ -255,6 +260,8 @@ void SerializedNavigationEntry::WriteToPickle(int max_size, pickle->WriteInt64(timestamp_.ToInternalValue()); WriteString16ToPickle(pickle, &bytes_written, max_size, search_terms_); + + pickle->WriteInt(http_status_code_); } bool SerializedNavigationEntry::ReadFromPickle(PickleIterator* iterator) { @@ -313,6 +320,9 @@ bool SerializedNavigationEntry::ReadFromPickle(PickleIterator* iterator) { // If the search terms field can't be found, leave it empty. if (!iterator->ReadString16(&search_terms_)) search_terms_.clear(); + + if (!iterator->ReadInt(&http_status_code_)) + http_status_code_ = 0; } return true; @@ -342,6 +352,7 @@ scoped_ptr<NavigationEntry> SerializedNavigationEntry::ToNavigationEntry( entry->SetIsOverridingUserAgent(is_overriding_user_agent_); entry->SetTimestamp(timestamp_); entry->SetExtraData(kSearchTermsKey, search_terms_); + entry->SetHttpStatusCode(http_status_code_); // These fields should have default values. DCHECK_EQ(STATE_INVALID, blocked_state_); @@ -440,6 +451,8 @@ sync_pb::TabNavigation SerializedNavigationEntry::ToSyncData() const { sync_data.set_search_terms(UTF16ToUTF8(search_terms_)); + sync_data.set_http_status_code(http_status_code_); + if (favicon_url_.is_valid()) sync_data.set_favicon_url(favicon_url_.spec()); diff --git a/chromium/components/sessions/serialized_navigation_entry.h b/chromium/components/sessions/serialized_navigation_entry.h index e5813dce0d8..80581cde3d5 100644 --- a/chromium/components/sessions/serialized_navigation_entry.h +++ b/chromium/components/sessions/serialized_navigation_entry.h @@ -100,6 +100,7 @@ class SESSIONS_EXPORT SerializedNavigationEntry { const content::PageState& page_state() const { return page_state_; } const string16& search_terms() const { return search_terms_; } const GURL& favicon_url() const { return favicon_url_; } + int http_status_code() const { return http_status_code_; } const content::Referrer& referrer() const { return referrer_; } content::PageTransition transition_type() const { return transition_type_; @@ -149,6 +150,7 @@ class SESSIONS_EXPORT SerializedNavigationEntry { base::Time timestamp_; string16 search_terms_; GURL favicon_url_; + int http_status_code_; // Additional information. BlockedState blocked_state_; diff --git a/chromium/components/sessions/serialized_navigation_entry_test_helper.cc b/chromium/components/sessions/serialized_navigation_entry_test_helper.cc index f4a7e2eba6a..64d3be27578 100644 --- a/chromium/components/sessions/serialized_navigation_entry_test_helper.cc +++ b/chromium/components/sessions/serialized_navigation_entry_test_helper.cc @@ -43,6 +43,7 @@ SerializedNavigationEntry SerializedNavigationEntryTestHelper::CreateNavigation( navigation.page_state_ = content::PageState::CreateFromEncodedData("fake_state"); navigation.timestamp_ = base::Time::Now(); + navigation.http_status_code_ = 200; return navigation; } diff --git a/chromium/components/sessions/serialized_navigation_entry_unittest.cc b/chromium/components/sessions/serialized_navigation_entry_unittest.cc index 5f052fe23e7..6aa35a13ae1 100644 --- a/chromium/components/sessions/serialized_navigation_entry_unittest.cc +++ b/chromium/components/sessions/serialized_navigation_entry_unittest.cc @@ -48,6 +48,7 @@ const bool kIsOverridingUserAgent = true; const base::Time kTimestamp = syncer::ProtoTimeToTime(100); const string16 kSearchTerms = ASCIIToUTF16("my search terms"); const GURL kFaviconURL("http://virtual-url.com/favicon.ico"); +const int kHttpStatusCode = 404; const int kPageID = 10; @@ -68,6 +69,7 @@ scoped_ptr<content::NavigationEntry> MakeNavigationEntryForTest() { navigation_entry->SetExtraData(kSearchTermsKey, kSearchTerms); navigation_entry->GetFavicon().valid = true; navigation_entry->GetFavicon().url = kFaviconURL; + navigation_entry->SetHttpStatusCode(kHttpStatusCode); return navigation_entry.Pass(); } @@ -86,6 +88,7 @@ sync_pb::TabNavigation MakeSyncDataForTest() { sync_data.set_navigation_home_page(true); sync_data.set_search_terms(UTF16ToUTF8(kSearchTerms)); sync_data.set_favicon_url(kFaviconURL.spec()); + sync_data.set_http_status_code(kHttpStatusCode); return sync_data; } @@ -108,6 +111,7 @@ TEST(SerializedNavigationEntryTest, DefaultInitializer) { EXPECT_TRUE(navigation.timestamp().is_null()); EXPECT_TRUE(navigation.search_terms().empty()); EXPECT_FALSE(navigation.favicon_url().is_valid()); + EXPECT_EQ(0, navigation.http_status_code()); } // Create a SerializedNavigationEntry from a NavigationEntry. All its fields @@ -134,6 +138,7 @@ TEST(SerializedNavigationEntryTest, FromNavigationEntry) { EXPECT_EQ(kIsOverridingUserAgent, navigation.is_overriding_user_agent()); EXPECT_EQ(kTimestamp, navigation.timestamp()); EXPECT_EQ(kFaviconURL, navigation.favicon_url()); + EXPECT_EQ(kHttpStatusCode, navigation.http_status_code()); } // Create a SerializedNavigationEntry from a sync_pb::TabNavigation. All its @@ -160,6 +165,7 @@ TEST(SerializedNavigationEntryTest, FromSyncData) { EXPECT_TRUE(navigation.timestamp().is_null()); EXPECT_EQ(kSearchTerms, navigation.search_terms()); EXPECT_EQ(kFaviconURL, navigation.favicon_url()); + EXPECT_EQ(kHttpStatusCode, navigation.http_status_code()); } // Create a SerializedNavigationEntry, pickle it, then create another one by @@ -192,6 +198,7 @@ TEST(SerializedNavigationEntryTest, Pickle) { EXPECT_EQ(kIsOverridingUserAgent, new_navigation.is_overriding_user_agent()); EXPECT_EQ(kTimestamp, new_navigation.timestamp()); EXPECT_EQ(kSearchTerms, new_navigation.search_terms()); + EXPECT_EQ(kHttpStatusCode, new_navigation.http_status_code()); } // Create a NavigationEntry, then create another one by converting to @@ -226,6 +233,7 @@ TEST(SerializedNavigationEntryTest, ToNavigationEntry) { string16 search_terms; new_navigation_entry->GetExtraData(kSearchTermsKey, &search_terms); EXPECT_EQ(kSearchTerms, search_terms); + EXPECT_EQ(kHttpStatusCode, new_navigation_entry->GetHttpStatusCode()); } // Create a NavigationEntry, convert it to a SerializedNavigationEntry, then @@ -251,6 +259,7 @@ TEST(SerializedNavigationEntryTest, ToSyncData) { EXPECT_EQ(syncer::TimeToProtoTime(kTimestamp), sync_data.timestamp_msec()); EXPECT_EQ(kTimestamp.ToInternalValue(), sync_data.global_id()); EXPECT_EQ(kFaviconURL.spec(), sync_data.favicon_url()); + EXPECT_EQ(kHttpStatusCode, sync_data.http_status_code()); } // Ensure all transition types and qualifiers are converted to/from the sync diff --git a/chromium/components/startup_metric_utils.gypi b/chromium/components/startup_metric_utils.gypi new file mode 100644 index 00000000000..55763c8db22 --- /dev/null +++ b/chromium/components/startup_metric_utils.gypi @@ -0,0 +1,22 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +{ + 'targets': [ + { + 'target_name': 'startup_metric_utils', + 'type': 'static_library', + 'dependencies': [ + '../base/base.gyp:base', + ], + 'include_dirs': [ + '..', + ], + 'sources': [ + 'startup_metric_utils/startup_metric_utils.cc', + 'startup_metric_utils/startup_metric_utils.h', + ], + }, + ], +} diff --git a/chromium/components/startup_metric_utils/OWNERS b/chromium/components/startup_metric_utils/OWNERS new file mode 100644 index 00000000000..b112a8471fb --- /dev/null +++ b/chromium/components/startup_metric_utils/OWNERS @@ -0,0 +1 @@ +jeremy@chromium.org
\ No newline at end of file diff --git a/chromium/components/startup_metric_utils/startup_metric_utils.cc b/chromium/components/startup_metric_utils/startup_metric_utils.cc new file mode 100644 index 00000000000..b2ac1bcada4 --- /dev/null +++ b/chromium/components/startup_metric_utils/startup_metric_utils.cc @@ -0,0 +1,169 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/startup_metric_utils/startup_metric_utils.h" + +#include "base/containers/hash_tables.h" +#include "base/logging.h" +#include "base/metrics/histogram.h" +#include "base/metrics/histogram_base.h" +#include "base/metrics/statistics_recorder.h" +#include "base/synchronization/lock.h" +#include "base/sys_info.h" +#include "base/time/time.h" + +namespace { + +// Mark as volatile to defensively make sure usage is thread-safe. +// Note that at the time of this writing, access is only on the UI thread. +volatile bool g_non_browser_ui_displayed = false; + +base::Time* MainEntryPointTimeInternal() { + static base::Time main_start_time = base::Time::Now(); + return &main_start_time; +} + +typedef base::hash_map<std::string,base::TimeDelta> SubsystemStartupTimeHash; + +SubsystemStartupTimeHash* GetSubsystemStartupTimeHash() { + static SubsystemStartupTimeHash* slow_startup_time_hash = + new SubsystemStartupTimeHash; + return slow_startup_time_hash; +} + +base::Lock* GetSubsystemStartupTimeHashLock() { + static base::Lock* slow_startup_time_hash_lock = new base::Lock; + return slow_startup_time_hash_lock; +} + +bool g_main_entry_time_was_recorded = false; +bool g_startup_stats_collection_finished = false; +bool g_was_slow_startup = false; + +} // namespace + +namespace startup_metric_utils { + +bool WasNonBrowserUIDisplayed() { + return g_non_browser_ui_displayed; +} + +void SetNonBrowserUIDisplayed() { + g_non_browser_ui_displayed = true; +} + +void RecordMainEntryPointTime() { + DCHECK(!g_main_entry_time_was_recorded); + g_main_entry_time_was_recorded = true; + MainEntryPointTimeInternal(); +} + +#if defined(OS_ANDROID) +void RecordSavedMainEntryPointTime(const base::Time& entry_point_time) { + DCHECK(!g_main_entry_time_was_recorded); + g_main_entry_time_was_recorded = true; + *MainEntryPointTimeInternal() = entry_point_time; +} +#endif // OS_ANDROID + +// Return the time recorded by RecordMainEntryPointTime(). +const base::Time MainEntryStartTime() { + DCHECK(g_main_entry_time_was_recorded); + return *MainEntryPointTimeInternal(); +} + +void OnBrowserStartupComplete(bool is_first_run) { + // Bail if uptime < 7 minutes, to filter out cases where Chrome may have been + // autostarted and the machine is under io pressure. + const int64 kSevenMinutesInMilliseconds = + base::TimeDelta::FromMinutes(7).InMilliseconds(); + if (base::SysInfo::Uptime() < kSevenMinutesInMilliseconds) { + g_startup_stats_collection_finished = true; + return; + } + + // The Startup.BrowserMessageLoopStartTime histogram recorded in + // chrome_browser_main.cc exhibits instability in the field which limits its + // usefulness in all scenarios except when we have a very large sample size. + // Attempt to mitigate this with a new metric: + // * Measure time from main entry rather than the OS' notion of process start + // time. + // * Only measure launches that occur 7 minutes after boot to try to avoid + // cases where Chrome is auto-started and IO is heavily loaded. + base::TimeDelta startup_time_from_main_entry = + base::Time::Now() - MainEntryStartTime(); + if (is_first_run) { + UMA_HISTOGRAM_LONG_TIMES( + "Startup.BrowserMessageLoopStartTimeFromMainEntry.FirstRun", + startup_time_from_main_entry); + } else { + UMA_HISTOGRAM_LONG_TIMES( + "Startup.BrowserMessageLoopStartTimeFromMainEntry", + startup_time_from_main_entry); + } + + // Record histograms for the subsystem times for startups > 10 seconds. + const base::TimeDelta kTenSeconds = base::TimeDelta::FromSeconds(10); + if (startup_time_from_main_entry < kTenSeconds) { + g_startup_stats_collection_finished = true; + return; + } + + // If we got here this was what we consider to be a slow startup which we + // want to record stats for. + g_was_slow_startup = true; +} + +void OnInitialPageLoadComplete() { + if (!g_was_slow_startup) + return; + DCHECK(!g_startup_stats_collection_finished); + + const base::TimeDelta kStartupTimeMin( + base::TimeDelta::FromMilliseconds(1)); + const base::TimeDelta kStartupTimeMax(base::TimeDelta::FromMinutes(5)); + static const size_t kStartupTimeBuckets = 100; + + // Set UMA flag for histograms outside chrome/ that can't use the + // ScopedSlowStartupUMA class. + base::HistogramBase* histogram = + base::StatisticsRecorder::FindHistogram("Startup.SlowStartupNSSInit"); + if (histogram) + histogram->SetFlags(base::HistogramBase::kUmaTargetedHistogramFlag); + + // Iterate over the stats recorded by ScopedSlowStartupUMA and create + // histograms for them. + base::AutoLock locker(*GetSubsystemStartupTimeHashLock()); + SubsystemStartupTimeHash* time_hash = GetSubsystemStartupTimeHash(); + for (SubsystemStartupTimeHash::iterator i = time_hash->begin(); + i != time_hash->end(); + ++i) { + const std::string histogram_name = i->first; + base::HistogramBase* counter = base::Histogram::FactoryTimeGet( + histogram_name, + kStartupTimeMin, + kStartupTimeMax, + kStartupTimeBuckets, + base::Histogram::kUmaTargetedHistogramFlag); + counter->AddTime(i->second); + } + + g_startup_stats_collection_finished = true; +} + +ScopedSlowStartupUMA::~ScopedSlowStartupUMA() { + if (g_startup_stats_collection_finished) + return; + + base::AutoLock locker(*GetSubsystemStartupTimeHashLock()); + SubsystemStartupTimeHash* hash = GetSubsystemStartupTimeHash(); + // Only record the initial sample for a given histogram. + if (hash->find(histogram_name_) != hash->end()) + return; + + (*hash)[histogram_name_] = + base::TimeTicks::Now() - start_time_; +} + +} // namespace startup_metric_utils diff --git a/chromium/components/startup_metric_utils/startup_metric_utils.h b/chromium/components/startup_metric_utils/startup_metric_utils.h new file mode 100644 index 00000000000..594ad15109a --- /dev/null +++ b/chromium/components/startup_metric_utils/startup_metric_utils.h @@ -0,0 +1,70 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_STARTUP_METRIC_UTILS_STARTUP_METRIC_UTILS_H_ +#define COMPONENTS_STARTUP_METRIC_UTILS_STARTUP_METRIC_UTILS_H_ + +#include <string> + +#include "base/time/time.h" + +// Utility functions to support metric collection for browser startup. + +namespace startup_metric_utils { + +// Returns true if any UI other than the browser window has been displayed +// so far. Useful to test if UI has been displayed before the first browser +// window was shown, which would invalidate any surrounding timing metrics. +bool WasNonBrowserUIDisplayed(); + +// Call this when displaying UI that might potentially delay the appearance +// of the initial browser window on Chrome startup. +// +// Note on usage: This function is idempotent and its overhead is low enough +// in comparison with UI display that it's OK to call it on every +// UI invocation regardless of whether the browser window has already +// been displayed or not. +void SetNonBrowserUIDisplayed(); + +// Call this as early as possible in the startup process to record a +// timestamp. +void RecordMainEntryPointTime(); + +#if defined(OS_ANDROID) +// On Android the entry point time is the time at which the Java code starts. +// This is recorded on the Java side, and then passed to the C++ side once the +// C++ library is loaded and running. +void RecordSavedMainEntryPointTime(const base::Time& entry_point_time); +#endif // OS_ANDROID + +// Called just before the message loop is about to start. Entry point to record +// startup stats. +// |is_first_run| - is the current launch part of a first run. +void OnBrowserStartupComplete(bool is_first_run); + +// Called when the initial page load has finished in order to record startup +// stats. +void OnInitialPageLoadComplete(); + +// Scoper that records the time period before it's destructed in a histogram +// with the given name. The histogram is only recorded for slow chrome startups. +// Useful for trying to figure out what parts of Chrome cause slow startup. +class ScopedSlowStartupUMA { + public: + explicit ScopedSlowStartupUMA(const std::string& histogram_name) + : start_time_(base::TimeTicks::Now()), + histogram_name_(histogram_name) {} + + ~ScopedSlowStartupUMA(); + + private: + const base::TimeTicks start_time_; + const std::string histogram_name_; + + DISALLOW_COPY_AND_ASSIGN(ScopedSlowStartupUMA); +}; + +} // namespace startup_metric_utils + +#endif // COMPONENTS_STARTUP_METRIC_UTILS_STARTUP_METRIC_UTILS_H_ diff --git a/chromium/components/tools/metrics/testdata/foo.cc b/chromium/components/tools/metrics/testdata/foo.cc deleted file mode 100644 index bd3d3c716d2..00000000000 --- a/chromium/components/tools/metrics/testdata/foo.cc +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) 2012 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -// Should not match -#ifndef FOO_OS_ANDROID_BLAT -#define FOO_OS_ANDROID_BLAT - -#if defined(OS_ANDROID) - -#if defined(OS_ANDROID) || defined(OS_IOS) || defined(OS_CHROMEOS) - -#if !defined(OS_CAT) - -#if defined(OS_WIN) - -#endif // !OS_ANDROID && !OS_IOS -#endif // OS_CAT - -#endif // FOO_OS_ANDROID_BLAT diff --git a/chromium/components/tools/metrics/testdata/foo_ignored.txt b/chromium/components/tools/metrics/testdata/foo_ignored.txt deleted file mode 100644 index 2b186f93da4..00000000000 --- a/chromium/components/tools/metrics/testdata/foo_ignored.txt +++ /dev/null @@ -1,4 +0,0 @@ -#if defined(OS_ANDROID) - -#if defined(OS_WIN) - diff --git a/chromium/components/tools/metrics/testdata/subdir/foo_test.mm b/chromium/components/tools/metrics/testdata/subdir/foo_test.mm deleted file mode 100644 index adf1f891ea9..00000000000 --- a/chromium/components/tools/metrics/testdata/subdir/foo_test.mm +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#if defined(OS_ANDROID) - -#if defined(OS_WIN) - diff --git a/chromium/components/tracing/child_trace_message_filter.cc b/chromium/components/tracing/child_trace_message_filter.cc index 154903a15d0..8310ec52db5 100644 --- a/chromium/components/tracing/child_trace_message_filter.cc +++ b/chromium/components/tracing/child_trace_message_filter.cc @@ -65,16 +65,12 @@ void ChildTraceMessageFilter::OnBeginTracing( void ChildTraceMessageFilter::OnEndTracing() { TraceLog::GetInstance()->SetDisabled(); - // Flush will generate one or more callbacks to OnTraceDataCollected. It's - // important that the last OnTraceDataCollected gets called before - // EndTracingAck below. We are already on the IO thread, so the + // Flush will generate one or more callbacks to OnTraceDataCollected + // synchronously or asynchronously. EndTracingAck will be sent in the last + // OnTraceDataCollected. We are already on the IO thread, so the // OnTraceDataCollected calls will not be deferred. TraceLog::GetInstance()->Flush( base::Bind(&ChildTraceMessageFilter::OnTraceDataCollected, this)); - - std::vector<std::string> category_groups; - TraceLog::GetInstance()->GetKnownCategoryGroups(&category_groups); - channel_->Send(new TracingHostMsg_EndTracingAck(category_groups)); } void ChildTraceMessageFilter::OnGetTraceBufferPercentFull() { @@ -94,15 +90,23 @@ void ChildTraceMessageFilter::OnCancelWatchEvent() { } void ChildTraceMessageFilter::OnTraceDataCollected( - const scoped_refptr<base::RefCountedString>& events_str_ptr) { + const scoped_refptr<base::RefCountedString>& events_str_ptr, + bool has_more_events) { if (!ipc_message_loop_->BelongsToCurrentThread()) { ipc_message_loop_->PostTask(FROM_HERE, base::Bind(&ChildTraceMessageFilter::OnTraceDataCollected, this, - events_str_ptr)); + events_str_ptr, has_more_events)); return; } - channel_->Send(new TracingHostMsg_TraceDataCollected( - events_str_ptr->data())); + if (events_str_ptr->data().size()) { + channel_->Send(new TracingHostMsg_TraceDataCollected( + events_str_ptr->data())); + } + if (!has_more_events) { + std::vector<std::string> category_groups; + TraceLog::GetInstance()->GetKnownCategoryGroups(&category_groups); + channel_->Send(new TracingHostMsg_EndTracingAck(category_groups)); + } } void ChildTraceMessageFilter::OnTraceNotification(int notification) { diff --git a/chromium/components/tracing/child_trace_message_filter.h b/chromium/components/tracing/child_trace_message_filter.h index 9631ced7b34..9f10b4a83a9 100644 --- a/chromium/components/tracing/child_trace_message_filter.h +++ b/chromium/components/tracing/child_trace_message_filter.h @@ -41,7 +41,8 @@ class ChildTraceMessageFilter : public IPC::ChannelProxy::MessageFilter { // Callback from trace subsystem. void OnTraceDataCollected( - const scoped_refptr<base::RefCountedString>& events_str_ptr); + const scoped_refptr<base::RefCountedString>& events_str_ptr, + bool has_more_events); void OnTraceNotification(int notification); IPC::Channel* channel_; diff --git a/chromium/components/tracing_untrusted.gyp b/chromium/components/tracing_untrusted.gyp index f2215de6a1b..1991ec28a08 100644 --- a/chromium/components/tracing_untrusted.gyp +++ b/chromium/components/tracing_untrusted.gyp @@ -26,7 +26,7 @@ 'nacl_untrusted_build': 1, 'nlib_target': 'libtracing_untrusted.a', 'build_glibc': 0, - 'build_newlib': 1, + 'build_newlib': 0, 'build_irt': 1, }, 'sources': [ diff --git a/chromium/components/variations.gypi b/chromium/components/variations.gypi new file mode 100644 index 00000000000..a5b9c6e6453 --- /dev/null +++ b/chromium/components/variations.gypi @@ -0,0 +1,36 @@ +# Copyright (c) 2012 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +{ + 'targets': [ + { + 'target_name': 'variations', + 'type': 'static_library', + 'include_dirs': [ + '..', + ], + 'dependencies': [ + '../base/base.gyp:base', + '../third_party/mt19937ar/mt19937ar.gyp:mt19937ar', + ], + 'sources': [ + 'variations/entropy_provider.cc', + 'variations/entropy_provider.h', + 'variations/proto/variations_seed.proto', + 'variations/proto/study.proto', + 'variations/metrics_util.cc', + 'variations/metrics_util.h', + 'variations/variations_associated_data.cc', + 'variations/variations_associated_data.h', + 'variations/variations_seed_processor.cc', + 'variations/variations_seed_processor.h', + ], + 'variables': { + 'proto_in_dir': 'variations/proto', + 'proto_out_dir': 'components/variations/proto', + }, + 'includes': [ '../build/protoc.gypi' ] + }, + ], +} diff --git a/chromium/components/variations/OWNERS b/chromium/components/variations/OWNERS new file mode 100644 index 00000000000..aadd23c5252 --- /dev/null +++ b/chromium/components/variations/OWNERS @@ -0,0 +1,3 @@ +asvitkine@chromium.org +jwd@chromium.org +stevet@chromium.org diff --git a/chromium/components/variations/entropy_provider.cc b/chromium/components/variations/entropy_provider.cc new file mode 100644 index 00000000000..a547cb1cf2b --- /dev/null +++ b/chromium/components/variations/entropy_provider.cc @@ -0,0 +1,119 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/variations/entropy_provider.h" + +#include <algorithm> +#include <limits> +#include <vector> + +#include "base/logging.h" +#include "base/rand_util.h" +#include "base/sha1.h" +#include "base/sys_byteorder.h" +#include "components/variations/metrics_util.h" + +namespace metrics { + +namespace internal { + +SeededRandGenerator::SeededRandGenerator(uint32 seed) { + mersenne_twister_.init_genrand(seed); +} + +SeededRandGenerator::~SeededRandGenerator() { +} + +uint32 SeededRandGenerator::operator()(uint32 range) { + // Based on base::RandGenerator(). + DCHECK_GT(range, 0u); + + // We must discard random results above this number, as they would + // make the random generator non-uniform (consider e.g. if + // MAX_UINT64 was 7 and |range| was 5, then a result of 1 would be twice + // as likely as a result of 3 or 4). + uint32 max_acceptable_value = + (std::numeric_limits<uint32>::max() / range) * range - 1; + + uint32 value; + do { + value = mersenne_twister_.genrand_int32(); + } while (value > max_acceptable_value); + + return value % range; +} + +void PermuteMappingUsingRandomizationSeed(uint32 randomization_seed, + std::vector<uint16>* mapping) { + for (size_t i = 0; i < mapping->size(); ++i) + (*mapping)[i] = static_cast<uint16>(i); + + SeededRandGenerator generator(randomization_seed); + std::random_shuffle(mapping->begin(), mapping->end(), generator); +} + +} // namespace internal + +SHA1EntropyProvider::SHA1EntropyProvider(const std::string& entropy_source) + : entropy_source_(entropy_source) { +} + +SHA1EntropyProvider::~SHA1EntropyProvider() { +} + +double SHA1EntropyProvider::GetEntropyForTrial( + const std::string& trial_name, + uint32 randomization_seed) const { + // Given enough input entropy, SHA-1 will produce a uniformly random spread + // in its output space. In this case, the input entropy that is used is the + // combination of the original |entropy_source_| and the |trial_name|. + // + // Note: If |entropy_source_| has very low entropy, such as 13 bits or less, + // it has been observed that this method does not result in a uniform + // distribution given the same |trial_name|. When using such a low entropy + // source, PermutedEntropyProvider should be used instead. + std::string input(entropy_source_ + trial_name); + unsigned char sha1_hash[base::kSHA1Length]; + base::SHA1HashBytes(reinterpret_cast<const unsigned char*>(input.c_str()), + input.size(), + sha1_hash); + + uint64 bits; + COMPILE_ASSERT(sizeof(bits) < sizeof(sha1_hash), need_more_data); + memcpy(&bits, sha1_hash, sizeof(bits)); + bits = base::ByteSwapToLE64(bits); + + return base::BitsToOpenEndedUnitInterval(bits); +} + +PermutedEntropyProvider::PermutedEntropyProvider( + uint16 low_entropy_source, + size_t low_entropy_source_max) + : low_entropy_source_(low_entropy_source), + low_entropy_source_max_(low_entropy_source_max) { + DCHECK_LT(low_entropy_source, low_entropy_source_max); + DCHECK_LE(low_entropy_source_max, std::numeric_limits<uint16>::max()); +} + +PermutedEntropyProvider::~PermutedEntropyProvider() { +} + +double PermutedEntropyProvider::GetEntropyForTrial( + const std::string& trial_name, + uint32 randomization_seed) const { + if (randomization_seed == 0) + randomization_seed = HashName(trial_name); + + return GetPermutedValue(randomization_seed) / + static_cast<double>(low_entropy_source_max_); +} + +uint16 PermutedEntropyProvider::GetPermutedValue( + uint32 randomization_seed) const { + std::vector<uint16> mapping(low_entropy_source_max_); + internal::PermuteMappingUsingRandomizationSeed(randomization_seed, &mapping); + return mapping[low_entropy_source_]; +} + +} // namespace metrics diff --git a/chromium/components/variations/entropy_provider.h b/chromium/components/variations/entropy_provider.h new file mode 100644 index 00000000000..786ae28165b --- /dev/null +++ b/chromium/components/variations/entropy_provider.h @@ -0,0 +1,94 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_VARIATIONS_ENTROPY_PROVIDER_H_ +#define COMPONENTS_VARIATIONS_ENTROPY_PROVIDER_H_ + +#include <functional> +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/metrics/field_trial.h" +#include "third_party/mt19937ar/mt19937ar.h" + +namespace metrics { + +// Internals of entropy_provider.cc exposed for testing. +namespace internal { + +// A functor that generates random numbers based on a seed, using the Mersenne +// Twister algorithm. Suitable for use with std::random_shuffle(). +struct SeededRandGenerator : std::unary_function<uint32, uint32> { + explicit SeededRandGenerator(uint32 seed); + ~SeededRandGenerator(); + + // Returns a random number in range [0, range). + uint32 operator()(uint32 range); + + MersenneTwister mersenne_twister_; +}; + +// Fills |mapping| to create a bijection of values in the range of +// [0, |mapping.size()|), permuted based on |randomization_seed|. +void PermuteMappingUsingRandomizationSeed(uint32 randomization_seed, + std::vector<uint16>* mapping); + +} // namespace internal + +// SHA1EntropyProvider is an entropy provider suitable for high entropy +// sources. It works by taking the first 64 bits of the SHA1 hash of the +// entropy source concatenated with the trial name and using that for the +// final entropy value. +class SHA1EntropyProvider : public base::FieldTrial::EntropyProvider { + public: + // Creates a SHA1EntropyProvider with the given |entropy_source|, which + // should contain a large amount of entropy - for example, a textual + // representation of a persistent randomly-generated 128-bit value. + explicit SHA1EntropyProvider(const std::string& entropy_source); + virtual ~SHA1EntropyProvider(); + + // base::FieldTrial::EntropyProvider implementation: + virtual double GetEntropyForTrial(const std::string& trial_name, + uint32 randomization_seed) const OVERRIDE; + + private: + std::string entropy_source_; + + DISALLOW_COPY_AND_ASSIGN(SHA1EntropyProvider); +}; + +// PermutedEntropyProvider is an entropy provider suitable for low entropy +// sources (below 16 bits). It uses the field trial name to generate a +// permutation of a mapping array from an initial entropy value to a new value. +// Note: This provider's performance is O(2^n), where n is the number of bits +// in the entropy source. +class PermutedEntropyProvider : public base::FieldTrial::EntropyProvider { + public: + // Creates a PermutedEntropyProvider with the given |low_entropy_source|, + // which should have a value in the range of [0, low_entropy_source_max). + PermutedEntropyProvider(uint16 low_entropy_source, + size_t low_entropy_source_max); + virtual ~PermutedEntropyProvider(); + + // base::FieldTrial::EntropyProvider implementation: + virtual double GetEntropyForTrial(const std::string& trial_name, + uint32 randomization_seed) const OVERRIDE; + + protected: + // Performs the permutation algorithm and returns the permuted value that + // corresponds to |low_entropy_source_|. + virtual uint16 GetPermutedValue(uint32 randomization_seed) const; + + private: + uint16 low_entropy_source_; + size_t low_entropy_source_max_; + + DISALLOW_COPY_AND_ASSIGN(PermutedEntropyProvider); +}; + +} // namespace metrics + +#endif // COMPONENTS_VARIATIONS_ENTROPY_PROVIDER_H_ diff --git a/chromium/components/variations/entropy_provider_unittest.cc b/chromium/components/variations/entropy_provider_unittest.cc new file mode 100644 index 00000000000..4e9a63718bf --- /dev/null +++ b/chromium/components/variations/entropy_provider_unittest.cc @@ -0,0 +1,369 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/variations/entropy_provider.h" + +#include <cmath> +#include <limits> +#include <numeric> + +#include "base/basictypes.h" +#include "base/guid.h" +#include "base/memory/scoped_ptr.h" +#include "base/rand_util.h" +#include "base/strings/string_number_conversions.h" +#include "components/variations/metrics_util.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace metrics { + +namespace { + +// Size of the low entropy source to use for the permuted entropy provider +// in tests. +const size_t kMaxLowEntropySize = 8000; + +// Field trial names used in unit tests. +const char* const kTestTrialNames[] = { "TestTrial", "AnotherTestTrial", + "NewTabButton" }; + +// Computes the Chi-Square statistic for |values| assuming they follow a uniform +// distribution, where each entry has expected value |expected_value|. +// +// The Chi-Square statistic is defined as Sum((O-E)^2/E) where O is the observed +// value and E is the expected value. +double ComputeChiSquare(const std::vector<int>& values, + double expected_value) { + double sum = 0; + for (size_t i = 0; i < values.size(); ++i) { + const double delta = values[i] - expected_value; + sum += (delta * delta) / expected_value; + } + return sum; +} + +// Computes SHA1-based entropy for the given |trial_name| based on +// |entropy_source| +double GenerateSHA1Entropy(const std::string& entropy_source, + const std::string& trial_name) { + SHA1EntropyProvider sha1_provider(entropy_source); + return sha1_provider.GetEntropyForTrial(trial_name, 0); +} + +// Generates permutation-based entropy for the given |trial_name| based on +// |entropy_source| which must be in the range [0, entropy_max). +double GeneratePermutedEntropy(uint16 entropy_source, + size_t entropy_max, + const std::string& trial_name) { + PermutedEntropyProvider permuted_provider(entropy_source, entropy_max); + return permuted_provider.GetEntropyForTrial(trial_name, 0); +} + +// Helper interface for testing used to generate entropy values for a given +// field trial. Unlike EntropyProvider, which keeps the low/high entropy source +// value constant and generates entropy for different trial names, instances +// of TrialEntropyGenerator keep the trial name constant and generate low/high +// entropy source values internally to produce each output entropy value. +class TrialEntropyGenerator { + public: + virtual ~TrialEntropyGenerator() {} + virtual double GenerateEntropyValue() const = 0; +}; + +// An TrialEntropyGenerator that uses the SHA1EntropyProvider with the high +// entropy source (random GUID with 128 bits of entropy + 13 additional bits of +// entropy corresponding to a low entropy source). +class SHA1EntropyGenerator : public TrialEntropyGenerator { + public: + explicit SHA1EntropyGenerator(const std::string& trial_name) + : trial_name_(trial_name) { + } + + virtual ~SHA1EntropyGenerator() { + } + + virtual double GenerateEntropyValue() const OVERRIDE { + // Use a random GUID + 13 additional bits of entropy to match how the + // SHA1EntropyProvider is used in metrics_service.cc. + const int low_entropy_source = + static_cast<uint16>(base::RandInt(0, kMaxLowEntropySize - 1)); + const std::string high_entropy_source = + base::GenerateGUID() + base::IntToString(low_entropy_source); + return GenerateSHA1Entropy(high_entropy_source, trial_name_); + } + + private: + std::string trial_name_; + + DISALLOW_COPY_AND_ASSIGN(SHA1EntropyGenerator); +}; + +// An TrialEntropyGenerator that uses the permuted entropy provider algorithm, +// using 13-bit low entropy source values. +class PermutedEntropyGenerator : public TrialEntropyGenerator { + public: + explicit PermutedEntropyGenerator(const std::string& trial_name) + : mapping_(kMaxLowEntropySize) { + // Note: Given a trial name, the computed mapping will be the same. + // As a performance optimization, pre-compute the mapping once per trial + // name and index into it for each entropy value. + const uint32 randomization_seed = HashName(trial_name); + internal::PermuteMappingUsingRandomizationSeed(randomization_seed, + &mapping_); + } + + virtual ~PermutedEntropyGenerator() { + } + + virtual double GenerateEntropyValue() const OVERRIDE { + const int low_entropy_source = + static_cast<uint16>(base::RandInt(0, kMaxLowEntropySize - 1)); + return mapping_[low_entropy_source] / + static_cast<double>(kMaxLowEntropySize); + } + + private: + std::vector<uint16> mapping_; + + DISALLOW_COPY_AND_ASSIGN(PermutedEntropyGenerator); +}; + +// Tests uniformity of a given |entropy_generator| using the Chi-Square Goodness +// of Fit Test. +void PerformEntropyUniformityTest( + const std::string& trial_name, + const TrialEntropyGenerator& entropy_generator) { + // Number of buckets in the simulated field trials. + const size_t kBucketCount = 20; + // Max number of iterations to perform before giving up and failing. + const size_t kMaxIterationCount = 100000; + // The number of iterations to perform before each time the statistical + // significance of the results is checked. + const size_t kCheckIterationCount = 10000; + // This is the Chi-Square threshold from the Chi-Square statistic table for + // 19 degrees of freedom (based on |kBucketCount|) with a 99.9% confidence + // level. See: http://www.medcalc.org/manual/chi-square-table.php + const double kChiSquareThreshold = 43.82; + + std::vector<int> distribution(kBucketCount); + + for (size_t i = 1; i <= kMaxIterationCount; ++i) { + const double entropy_value = entropy_generator.GenerateEntropyValue(); + const size_t bucket = static_cast<size_t>(kBucketCount * entropy_value); + ASSERT_LT(bucket, kBucketCount); + distribution[bucket] += 1; + + // After |kCheckIterationCount| iterations, compute the Chi-Square + // statistic of the distribution. If the resulting statistic is greater + // than |kChiSquareThreshold|, we can conclude with 99.9% confidence + // that the observed samples do not follow a uniform distribution. + // + // However, since 99.9% would still result in a false negative every + // 1000 runs of the test, do not treat it as a failure (else the test + // will be flaky). Instead, perform additional iterations to determine + // if the distribution will converge, up to |kMaxIterationCount|. + if ((i % kCheckIterationCount) == 0) { + const double expected_value_per_bucket = + static_cast<double>(i) / kBucketCount; + const double chi_square = + ComputeChiSquare(distribution, expected_value_per_bucket); + if (chi_square < kChiSquareThreshold) + break; + + // If |i == kMaxIterationCount|, the Chi-Square statistic did not + // converge after |kMaxIterationCount|. + EXPECT_NE(i, kMaxIterationCount) << "Failed for trial " << + trial_name << " with chi_square = " << chi_square << + " after " << kMaxIterationCount << " iterations."; + } + } +} + +} // namespace + +TEST(EntropyProviderTest, UseOneTimeRandomizationSHA1) { + // Simply asserts that two trials using one-time randomization + // that have different names, normally generate different results. + // + // Note that depending on the one-time random initialization, they + // _might_ actually give the same result, but we know that given + // the particular client_id we use for unit tests they won't. + base::FieldTrialList field_trial_list(new SHA1EntropyProvider("client_id")); + const int kNoExpirationYear = base::FieldTrialList::kNoExpirationYear; + scoped_refptr<base::FieldTrial> trials[] = { + base::FieldTrialList::FactoryGetFieldTrial( + "one", 100, "default", kNoExpirationYear, 1, 1, + base::FieldTrial::ONE_TIME_RANDOMIZED, NULL), + base::FieldTrialList::FactoryGetFieldTrial( + "two", 100, "default", kNoExpirationYear, 1, 1, + base::FieldTrial::ONE_TIME_RANDOMIZED, NULL), + }; + + for (size_t i = 0; i < arraysize(trials); ++i) { + for (int j = 0; j < 100; ++j) + trials[i]->AppendGroup(std::string(), 1); + } + + // The trials are most likely to give different results since they have + // different names. + EXPECT_NE(trials[0]->group(), trials[1]->group()); + EXPECT_NE(trials[0]->group_name(), trials[1]->group_name()); +} + +TEST(EntropyProviderTest, UseOneTimeRandomizationPermuted) { + // Simply asserts that two trials using one-time randomization + // that have different names, normally generate different results. + // + // Note that depending on the one-time random initialization, they + // _might_ actually give the same result, but we know that given + // the particular client_id we use for unit tests they won't. + base::FieldTrialList field_trial_list( + new PermutedEntropyProvider(1234, kMaxLowEntropySize)); + const int kNoExpirationYear = base::FieldTrialList::kNoExpirationYear; + scoped_refptr<base::FieldTrial> trials[] = { + base::FieldTrialList::FactoryGetFieldTrial( + "one", 100, "default", kNoExpirationYear, 1, 1, + base::FieldTrial::ONE_TIME_RANDOMIZED, NULL), + base::FieldTrialList::FactoryGetFieldTrial( + "two", 100, "default", kNoExpirationYear, 1, 1, + base::FieldTrial::ONE_TIME_RANDOMIZED, NULL), + }; + + for (size_t i = 0; i < arraysize(trials); ++i) { + for (int j = 0; j < 100; ++j) + trials[i]->AppendGroup(std::string(), 1); + } + + // The trials are most likely to give different results since they have + // different names. + EXPECT_NE(trials[0]->group(), trials[1]->group()); + EXPECT_NE(trials[0]->group_name(), trials[1]->group_name()); +} + +TEST(EntropyProviderTest, UseOneTimeRandomizationWithCustomSeedPermuted) { + // Ensures that two trials with different names but the same custom seed used + // for one time randomization produce the same group assignments. + base::FieldTrialList field_trial_list( + new PermutedEntropyProvider(1234, kMaxLowEntropySize)); + const int kNoExpirationYear = base::FieldTrialList::kNoExpirationYear; + const uint32 kCustomSeed = 9001; + scoped_refptr<base::FieldTrial> trials[] = { + base::FieldTrialList::FactoryGetFieldTrialWithRandomizationSeed( + "one", 100, "default", kNoExpirationYear, 1, 1, + base::FieldTrial::ONE_TIME_RANDOMIZED, kCustomSeed, NULL), + base::FieldTrialList::FactoryGetFieldTrialWithRandomizationSeed( + "two", 100, "default", kNoExpirationYear, 1, 1, + base::FieldTrial::ONE_TIME_RANDOMIZED, kCustomSeed, NULL), + }; + + for (size_t i = 0; i < arraysize(trials); ++i) { + for (int j = 0; j < 100; ++j) + trials[i]->AppendGroup(std::string(), 1); + } + + // Normally, these trials should produce different groups, but if the same + // custom seed is used, they should produce the same group assignment. + EXPECT_EQ(trials[0]->group(), trials[1]->group()); + EXPECT_EQ(trials[0]->group_name(), trials[1]->group_name()); +} + +TEST(EntropyProviderTest, SHA1Entropy) { + const double results[] = { GenerateSHA1Entropy("hi", "1"), + GenerateSHA1Entropy("there", "1") }; + + EXPECT_NE(results[0], results[1]); + for (size_t i = 0; i < arraysize(results); ++i) { + EXPECT_LE(0.0, results[i]); + EXPECT_GT(1.0, results[i]); + } + + EXPECT_EQ(GenerateSHA1Entropy("yo", "1"), + GenerateSHA1Entropy("yo", "1")); + EXPECT_NE(GenerateSHA1Entropy("yo", "something"), + GenerateSHA1Entropy("yo", "else")); +} + +TEST(EntropyProviderTest, PermutedEntropy) { + const double results[] = { + GeneratePermutedEntropy(1234, kMaxLowEntropySize, "1"), + GeneratePermutedEntropy(4321, kMaxLowEntropySize, "1") }; + + EXPECT_NE(results[0], results[1]); + for (size_t i = 0; i < arraysize(results); ++i) { + EXPECT_LE(0.0, results[i]); + EXPECT_GT(1.0, results[i]); + } + + EXPECT_EQ(GeneratePermutedEntropy(1234, kMaxLowEntropySize, "1"), + GeneratePermutedEntropy(1234, kMaxLowEntropySize, "1")); + EXPECT_NE(GeneratePermutedEntropy(1234, kMaxLowEntropySize, "something"), + GeneratePermutedEntropy(1234, kMaxLowEntropySize, "else")); +} + +TEST(EntropyProviderTest, PermutedEntropyProviderResults) { + // Verifies that PermutedEntropyProvider produces expected results. This + // ensures that the results are the same between platforms and ensures that + // changes to the implementation do not regress this accidentally. + + EXPECT_DOUBLE_EQ(2194 / static_cast<double>(kMaxLowEntropySize), + GeneratePermutedEntropy(1234, kMaxLowEntropySize, "XYZ")); + EXPECT_DOUBLE_EQ(5676 / static_cast<double>(kMaxLowEntropySize), + GeneratePermutedEntropy(1, kMaxLowEntropySize, "Test")); + EXPECT_DOUBLE_EQ(1151 / static_cast<double>(kMaxLowEntropySize), + GeneratePermutedEntropy(5000, kMaxLowEntropySize, "Foo")); +} + +TEST(EntropyProviderTest, SHA1EntropyIsUniform) { + for (size_t i = 0; i < arraysize(kTestTrialNames); ++i) { + SHA1EntropyGenerator entropy_generator(kTestTrialNames[i]); + PerformEntropyUniformityTest(kTestTrialNames[i], entropy_generator); + } +} + +TEST(EntropyProviderTest, PermutedEntropyIsUniform) { + for (size_t i = 0; i < arraysize(kTestTrialNames); ++i) { + PermutedEntropyGenerator entropy_generator(kTestTrialNames[i]); + PerformEntropyUniformityTest(kTestTrialNames[i], entropy_generator); + } +} + +TEST(EntropyProviderTest, SeededRandGeneratorIsUniform) { + // Verifies that SeededRandGenerator has a uniform distribution. + // + // Mirrors RandUtilTest.RandGeneratorIsUniform in base/rand_util_unittest.cc. + + const uint32 kTopOfRange = (std::numeric_limits<uint32>::max() / 4ULL) * 3ULL; + const uint32 kExpectedAverage = kTopOfRange / 2ULL; + const uint32 kAllowedVariance = kExpectedAverage / 50ULL; // +/- 2% + const int kMinAttempts = 1000; + const int kMaxAttempts = 1000000; + + for (size_t i = 0; i < arraysize(kTestTrialNames); ++i) { + const uint32 seed = HashName(kTestTrialNames[i]); + internal::SeededRandGenerator rand_generator(seed); + + double cumulative_average = 0.0; + int count = 0; + while (count < kMaxAttempts) { + uint32 value = rand_generator(kTopOfRange); + cumulative_average = (count * cumulative_average + value) / (count + 1); + + // Don't quit too quickly for things to start converging, or we may have + // a false positive. + if (count > kMinAttempts && + kExpectedAverage - kAllowedVariance < cumulative_average && + cumulative_average < kExpectedAverage + kAllowedVariance) { + break; + } + + ++count; + } + + ASSERT_LT(count, kMaxAttempts) << "Expected average was " << + kExpectedAverage << ", average ended at " << cumulative_average << + ", for trial " << kTestTrialNames[i]; + } +} + +} // namespace metrics diff --git a/chromium/components/variations/metrics_util.cc b/chromium/components/variations/metrics_util.cc new file mode 100644 index 00000000000..031c3d37a31 --- /dev/null +++ b/chromium/components/variations/metrics_util.cc @@ -0,0 +1,27 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/variations/metrics_util.h" + +#include "base/sha1.h" +#include "base/sys_byteorder.h" + +namespace metrics { + +uint32 HashName(const std::string& name) { + // SHA-1 is designed to produce a uniformly random spread in its output space, + // even for nearly-identical inputs. + unsigned char sha1_hash[base::kSHA1Length]; + base::SHA1HashBytes(reinterpret_cast<const unsigned char*>(name.c_str()), + name.size(), + sha1_hash); + + uint32 bits; + COMPILE_ASSERT(sizeof(bits) < sizeof(sha1_hash), need_more_data); + memcpy(&bits, sha1_hash, sizeof(bits)); + + return base::ByteSwapToLE32(bits); +} + +} // namespace metrics diff --git a/chromium/components/variations/metrics_util.h b/chromium/components/variations/metrics_util.h new file mode 100644 index 00000000000..b331d4eaf1f --- /dev/null +++ b/chromium/components/variations/metrics_util.h @@ -0,0 +1,20 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_VARIATIONS_METRICS_UTIL_H_ +#define COMPONENTS_VARIATIONS_METRICS_UTIL_H_ + +#include <string> + +#include "base/basictypes.h" + +namespace metrics { + +// Computes a uint32 hash of a given string based on its SHA1 hash. Suitable for +// uniquely identifying field trial names and group names. +uint32 HashName(const std::string& name); + +} // namespace metrics + +#endif // COMPONENTS_VARIATIONS_METRICS_UTIL_H_ diff --git a/chromium/components/variations/metrics_util_unittest.cc b/chromium/components/variations/metrics_util_unittest.cc new file mode 100644 index 00000000000..4f324947176 --- /dev/null +++ b/chromium/components/variations/metrics_util_unittest.cc @@ -0,0 +1,31 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/variations/metrics_util.h" + +#include "testing/gtest/include/gtest/gtest.h" + +namespace metrics { + +TEST(MetricsUtilTest, HashName) { + // Checks that hashing is stable on all platforms. + struct { + const char* name; + uint32 hash_value; + } known_hashes[] = { + {"a", 937752454u}, + {"1", 723085877u}, + {"Trial Name", 2713117220u}, + {"Group Name", 3201815843u}, + {"My Favorite Experiment", 3722155194u}, + {"My Awesome Group Name", 4109503236u}, + {"abcdefghijklmonpqrstuvwxyz", 787728696u}, + {"0123456789ABCDEF", 348858318U} + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(known_hashes); ++i) + EXPECT_EQ(known_hashes[i].hash_value, HashName(known_hashes[i].name)); +} + +} // namespace metrics diff --git a/chromium/components/variations/proto/study.proto b/chromium/components/variations/proto/study.proto new file mode 100644 index 00000000000..fdaa41f1ae2 --- /dev/null +++ b/chromium/components/variations/proto/study.proto @@ -0,0 +1,162 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +syntax = "proto2"; + +option optimize_for = LITE_RUNTIME; + +package chrome_variations; + +// This defines the Protocol Buffer representation of a Chrome Variations study +// as sent to clients of the Variations server. +// +// Next tag: 12 +message Study { + // The name of the study. Should not contain spaces or special characters. + // Ex: "my_study" + required string name = 1; + + // The expiry date of the study in Unix time format. (Seconds since midnight + // January 1, 1970 UTC). See: http://en.wikipedia.org/wiki/Unix_time + // + // A study that has expired will be disabled, which will take precedence over + // a corresponding hardcoded field trial in the client. + // + // Ex: 1330893974 (corresponds to 2012-03-04 20:46:14Z) + optional int64 expiry_date = 3; + + // Consistency setting for a study. + enum Consistency { + SESSION = 0; // Can't change within a session. + PERMANENT = 1; // Can't change for a given user. + } + + // Consistency setting for this study. Optional - defaults to SESSION. + // Ex: PERMANENT + optional Consistency consistency = 7 [default = SESSION]; + + // Name of the experiment that gets the default experience. This experiment + // must be included in the list below. + // Ex: "default" + optional string default_experiment_name = 8; + + // An experiment within the study. + // + // Next tag: 7 + message Experiment { + // A named parameter value for this experiment. + // + // Next tag: 3 + message Param { + // The name of the parameter. + optional string name = 1; + + // The value of the parameter. + optional string value = 2; + } + + // The name of the experiment within the study. + // Ex: "bucketA" + required string name = 1; + + // The cut of the total probability taken for this experiment (the x in + // x / N, where N is the sum of all x’s). Ex: "50" + required uint32 probability_weight = 2; + + // Optional id used to uniquely identify this experiment for Google web + // properties. + optional uint64 google_web_experiment_id = 3; + + // Optional id used to uniquely identify this experiment for Google Update. + optional uint64 google_update_experiment_id = 4; + + // Optional name of a Chrome flag that, when present, causes this experiment + // to be forced. If the forcing_flag field is set, users will not be + // assigned to this experiment unless that flag is present in Chrome's + // command line. + optional string forcing_flag = 5; + + // Parameter values for this experiment. + repeated Param param = 6; + } + + // List of experiments in this study. This list should include the default / + // control experiment. + // + // For example, to specify that 99% of users get the default behavior, while + // 0.5% of users get experience "A" and 0.5% of users get experience "B", + // specify the values below. + // Ex: { "default": 990, "A": 5, "B": 5 } + repeated Experiment experiment = 9; + + // Possible Chrome release channels. + // See: http://dev.chromium.org/getting-involved/dev-channel + enum Channel { + // UNKNOWN value is defined here for the benefit of code using this enum + // type, but is not actually meant to be encoded in the protobuf. + UNKNOWN = -1; + CANARY = 0; + DEV = 1; + BETA = 2; + STABLE = 3; + } + + // Possible Chrome operating system platforms. + enum Platform { + PLATFORM_WINDOWS = 0; + PLATFORM_MAC = 1; + PLATFORM_LINUX = 2; + PLATFORM_CHROMEOS = 3; + PLATFORM_ANDROID = 4; + PLATFORM_IOS = 5; + } + + // Filtering criteria specifying whether this study is applicable to a given + // Chrome instance. + // + // Next tag: 7 + message Filter { + // The start date of the study in Unix time format. (Seconds since midnight + // January 1, 1970 UTC). See: http://en.wikipedia.org/wiki/Unix_time + // Ex: 1330893974 (corresponds to 2012-03-04 20:46:14Z) + optional int64 start_date = 1; + + // The minimum Chrome version for this study, allowing a trailing '*' + // character for pattern matching. Inclusive. (To check for a match, iterate + // over each component checking >= until a * or end of string is reached.) + // Optional - if not specified, there is no minimum version. + // Ex: "17.0.963.46", "17.0.963.*", "17.*" + optional string min_version = 2; + + // The maximum Chrome version for this study; same formatting as + // |min_version| above. Inclusive. (To check for a match, iterate over each + // component checking <= until a * or end of string is reached.) + // Optional - if not specified, there is no maximum version. + // Ex: "19.*" + optional string max_version = 3; + + // List of channels that will receive this study. If omitted, the study + // applies to all channels. + // Ex: [BETA, STABLE] + repeated Channel channel = 4; + + // List of platforms that will receive this study. If omitted, the study + // applies to all platforms. + // Ex: [PLATFORM_WINDOWS, PLATFORM_MAC] + repeated Platform platform = 5; + + // List of locales that will receive this study. If omitted, the study + // applies to all locales. + // Ex: ["en-US", "en-CA"] + repeated string locale = 6; + } + + // Filtering criteria for this study. A study that is filtered out for a given + // client is equivalent to that study not being sent at all. + optional Filter filter = 10; + + // Randomization seed to be used when |consistency| is set to PERMANENT. If + // not specified, randomization will be done using the trial name. + optional uint32 randomization_seed = 11; +} diff --git a/chromium/components/variations/proto/variations_seed.proto b/chromium/components/variations/proto/variations_seed.proto new file mode 100644 index 00000000000..4d3ae35dd26 --- /dev/null +++ b/chromium/components/variations/proto/variations_seed.proto @@ -0,0 +1,22 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +syntax = "proto2"; + +option optimize_for = LITE_RUNTIME; + +package chrome_variations; + +import "study.proto"; + +// The VariationsSeed is a protobuf response from the server that contains the +// list of studies and a serial number to uniquely identify its contents. The +// serial number allows the client to easily determine if the list of +// experiments has changed from the previous VariationsSeed seen by the client. +// +// Next tag: 3 +message VariationsSeed { + optional string serial_number = 1; + repeated Study study = 2; +} diff --git a/chromium/components/variations/variations_associated_data.cc b/chromium/components/variations/variations_associated_data.cc new file mode 100644 index 00000000000..64a6d6e49a8 --- /dev/null +++ b/chromium/components/variations/variations_associated_data.cc @@ -0,0 +1,236 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/variations/variations_associated_data.h" + +#include <map> +#include <utility> +#include <vector> + +#include "base/memory/singleton.h" +#include "components/variations/metrics_util.h" + +namespace chrome_variations { + +namespace { + +// The internal singleton accessor for the map, used to keep it thread-safe. +class GroupMapAccessor { + public: + typedef std::map<ActiveGroupId, VariationID, ActiveGroupIdCompare> + GroupToIDMap; + + // Retrieve the singleton. + static GroupMapAccessor* GetInstance() { + return Singleton<GroupMapAccessor>::get(); + } + + // Note that this normally only sets the ID for a group the first time, unless + // |force| is set to true, in which case it will always override it. + void AssociateID(IDCollectionKey key, + const ActiveGroupId& group_identifier, + const VariationID id, + const bool force) { +#if !defined(NDEBUG) + // Validate that all collections with this |group_identifier| have the same + // associated ID. + DCHECK_EQ(2, ID_COLLECTION_COUNT); + IDCollectionKey other_key = GOOGLE_WEB_PROPERTIES; + if (key == GOOGLE_WEB_PROPERTIES) + other_key = GOOGLE_UPDATE_SERVICE; + VariationID other_id = GetID(other_key, group_identifier); + DCHECK(other_id == EMPTY_ID || other_id == id); +#endif + + base::AutoLock scoped_lock(lock_); + + GroupToIDMap* group_to_id_map = GetGroupToIDMap(key); + if (force || + group_to_id_map->find(group_identifier) == group_to_id_map->end()) + (*group_to_id_map)[group_identifier] = id; + } + + VariationID GetID(IDCollectionKey key, + const ActiveGroupId& group_identifier) { + base::AutoLock scoped_lock(lock_); + GroupToIDMap* group_to_id_map = GetGroupToIDMap(key); + GroupToIDMap::const_iterator it = group_to_id_map->find(group_identifier); + if (it == group_to_id_map->end()) + return EMPTY_ID; + return it->second; + } + + void ClearAllMapsForTesting() { + base::AutoLock scoped_lock(lock_); + + for (int i = 0; i < ID_COLLECTION_COUNT; ++i) { + GroupToIDMap* map = GetGroupToIDMap(static_cast<IDCollectionKey>(i)); + DCHECK(map); + map->clear(); + } + } + + private: + friend struct DefaultSingletonTraits<GroupMapAccessor>; + + // Retrieves the GroupToIDMap for |key|. + GroupToIDMap* GetGroupToIDMap(IDCollectionKey key) { + return &group_to_id_maps_[key]; + } + + GroupMapAccessor() { + group_to_id_maps_.resize(ID_COLLECTION_COUNT); + } + ~GroupMapAccessor() {} + + base::Lock lock_; + std::vector<GroupToIDMap> group_to_id_maps_; + + DISALLOW_COPY_AND_ASSIGN(GroupMapAccessor); +}; + +// Singleton helper class that keeps track of the parameters of all variations +// and ensures access to these is thread-safe. +class VariationsParamAssociator { + public: + typedef std::pair<std::string, std::string> VariationKey; + typedef std::map<std::string, std::string> VariationParams; + + // Retrieve the singleton. + static VariationsParamAssociator* GetInstance() { + return Singleton<VariationsParamAssociator>::get(); + } + + bool AssociateVariationParams(const std::string& trial_name, + const std::string& group_name, + const VariationParams& params) { + base::AutoLock scoped_lock(lock_); + + if (IsFieldTrialActive(trial_name)) + return false; + + const VariationKey key(trial_name, group_name); + if (ContainsKey(variation_params_, key)) + return false; + + variation_params_[key] = params; + return true; + } + + bool GetVariationParams(const std::string& trial_name, + VariationParams* params) { + base::AutoLock scoped_lock(lock_); + + const std::string group_name = + base::FieldTrialList::FindFullName(trial_name); + const VariationKey key(trial_name, group_name); + if (!ContainsKey(variation_params_, key)) + return false; + + *params = variation_params_[key]; + return true; + } + + void ClearAllParamsForTesting() { + base::AutoLock scoped_lock(lock_); + variation_params_.clear(); + } + + private: + friend struct DefaultSingletonTraits<VariationsParamAssociator>; + + VariationsParamAssociator() {} + ~VariationsParamAssociator() {} + + // Tests whether a field trial is active (i.e. group() has been called on it). + // TODO(asvitkine): Expose this as an API on base::FieldTrial. + bool IsFieldTrialActive(const std::string& trial_name) { + base::FieldTrial::ActiveGroups active_groups; + base::FieldTrialList::GetActiveFieldTrialGroups(&active_groups); + for (size_t i = 0; i < active_groups.size(); ++i) { + if (active_groups[i].trial_name == trial_name) + return true; + } + return false; + } + + base::Lock lock_; + std::map<VariationKey, VariationParams> variation_params_; + + DISALLOW_COPY_AND_ASSIGN(VariationsParamAssociator); +}; + +} // namespace + +ActiveGroupId MakeActiveGroupId(const std::string& trial_name, + const std::string& group_name) { + ActiveGroupId id; + id.name = metrics::HashName(trial_name); + id.group = metrics::HashName(group_name); + return id; +} + +void AssociateGoogleVariationID(IDCollectionKey key, + const std::string& trial_name, + const std::string& group_name, + VariationID id) { + GroupMapAccessor::GetInstance()->AssociateID( + key, MakeActiveGroupId(trial_name, group_name), id, false); +} + +void AssociateGoogleVariationIDForce(IDCollectionKey key, + const std::string& trial_name, + const std::string& group_name, + VariationID id) { + GroupMapAccessor::GetInstance()->AssociateID( + key, MakeActiveGroupId(trial_name, group_name), id, true); +} + +VariationID GetGoogleVariationID(IDCollectionKey key, + const std::string& trial_name, + const std::string& group_name) { + return GroupMapAccessor::GetInstance()->GetID( + key, MakeActiveGroupId(trial_name, group_name)); +} + +bool AssociateVariationParams( + const std::string& trial_name, + const std::string& group_name, + const std::map<std::string, std::string>& params) { + return VariationsParamAssociator::GetInstance()->AssociateVariationParams( + trial_name, group_name, params); +} + +bool GetVariationParams(const std::string& trial_name, + std::map<std::string, std::string>* params) { + return VariationsParamAssociator::GetInstance()->GetVariationParams( + trial_name, params); +} + +std::string GetVariationParamValue(const std::string& trial_name, + const std::string& param_name) { + std::map<std::string, std::string> params; + if (GetVariationParams(trial_name, ¶ms)) { + std::map<std::string, std::string>::iterator it = params.find(param_name); + if (it != params.end()) + return it->second; + } + return std::string(); +} + +// Functions below are exposed for testing explicitly behind this namespace. +// They simply wrap existing functions in this file. +namespace testing { + +void ClearAllVariationIDs() { + GroupMapAccessor::GetInstance()->ClearAllMapsForTesting(); +} + +void ClearAllVariationParams() { + VariationsParamAssociator::GetInstance()->ClearAllParamsForTesting(); +} + +} // namespace testing + +} // namespace chrome_variations diff --git a/chromium/components/variations/variations_associated_data.h b/chromium/components/variations/variations_associated_data.h new file mode 100644 index 00000000000..bfc4a8fd1f9 --- /dev/null +++ b/chromium/components/variations/variations_associated_data.h @@ -0,0 +1,155 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_VARIATIONS_VARIATIONS_ASSOCIATED_DATA_H_ +#define COMPONENTS_VARIATIONS_VARIATIONS_ASSOCIATED_DATA_H_ + +#include <map> +#include <string> + +#include "base/metrics/field_trial.h" + +// This file provides various helpers that extend the functionality around +// base::FieldTrial. +// +// This includes several simple APIs to handle getting and setting additional +// data related to Chrome variations, such as parameters and Google variation +// IDs. These APIs are meant to extend the base::FieldTrial APIs to offer extra +// functionality that is not offered by the simpler base::FieldTrial APIs. +// +// The AssociateGoogleVariationID and AssociateVariationParams functions are +// generally meant to be called by the VariationsService based on server-side +// variation configs, but may also be used for client-only field trials by +// invoking them directly after appending all the groups to a FieldTrial. +// +// Experiment code can then use the getter APIs to retrieve variation parameters +// or IDs: +// +// std::map<std::string, std::string> params; +// if (GetVariationParams("trial", ¶ms)) { +// // use |params| +// } +// +// std::string value = GetVariationParamValue("trial", "param_x"); +// // use |value|, which will be "" if it does not exist +// +// VariationID id = GetGoogleVariationID(GOOGLE_WEB_PROPERTIES, "trial", +// "group1"); +// if (id != chrome_variations::kEmptyID) { +// // use |id| +// } + +namespace chrome_variations { + +typedef int VariationID; + +const VariationID EMPTY_ID = 0; + +// The Unique ID of a trial and its active group, where the name and group +// identifiers are hashes of the trial and group name strings. +struct ActiveGroupId { + uint32 name; + uint32 group; +}; + +// Returns an ActiveGroupId struct for the given trial and group names. +ActiveGroupId MakeActiveGroupId(const std::string& trial_name, + const std::string& group_name); + +// We need to supply a Compare class for templates since ActiveGroupId is a +// user-defined type. +struct ActiveGroupIdCompare { + bool operator() (const ActiveGroupId& lhs, const ActiveGroupId& rhs) const { + // The group and name fields are just SHA-1 Hashes, so we just need to treat + // them as IDs and do a less-than comparison. We test group first, since + // name is more likely to collide. + if (lhs.group != rhs.group) + return lhs.group < rhs.group; + return lhs.name < rhs.name; + } +}; + +// A key into the Associate/Get methods for VariationIDs. This is used to create +// separate ID associations for separate parties interested in VariationIDs. +enum IDCollectionKey { + // This collection is used by Google web properties, transmitted through the + // X-Chrome-Variations header. + GOOGLE_WEB_PROPERTIES, + // This collection is used by Google update services, transmitted through the + // Google Update experiment labels. + GOOGLE_UPDATE_SERVICE, + // The total count of collections. + ID_COLLECTION_COUNT, +}; + +// Associate a chrome_variations::VariationID value with a FieldTrial group for +// collection |key|. If an id was previously set for |trial_name| and +// |group_name|, this does nothing. The group is denoted by |trial_name| and +// |group_name|. This must be called whenever a FieldTrial is prepared (create +// the trial and append groups) and needs to have a +// chrome_variations::VariationID associated with it so Google servers can +// recognize the FieldTrial. Thread safe. +void AssociateGoogleVariationID(IDCollectionKey key, + const std::string& trial_name, + const std::string& group_name, + VariationID id); + +// As above, but overwrites any previously set id. Thread safe. +void AssociateGoogleVariationIDForce(IDCollectionKey key, + const std::string& trial_name, + const std::string& group_name, + VariationID id); + +// Retrieve the chrome_variations::VariationID associated with a FieldTrial +// group for collection |key|. The group is denoted by |trial_name| and +// |group_name|. This will return chrome_variations::kEmptyID if there is +// currently no associated ID for the named group. This API can be nicely +// combined with FieldTrial::GetActiveFieldTrialGroups() to enumerate the +// variation IDs for all active FieldTrial groups. Thread safe. +VariationID GetGoogleVariationID(IDCollectionKey key, + const std::string& trial_name, + const std::string& group_name); + +// Associates the specified set of key-value |params| with the variation +// specified by |trial_name| and |group_name|. Fails and returns false if the +// specified variation already has params associated with it or the field trial +// is already active (group() has been called on it). Thread safe. +bool AssociateVariationParams(const std::string& trial_name, + const std::string& group_name, + const std::map<std::string, std::string>& params); + +// Retrieves the set of key-value |params| for the variation associated with +// the specified field trial, based on its selected group. If the field trial +// does not exist or its selected group does not have any parameters associated +// with it, returns false and does not modify |params|. Calling this function +// will result in the field trial being marked as active if found (i.e. group() +// will be called on it), if it wasn't already. Currently, this information is +// only available from the browser process. Thread safe. +bool GetVariationParams(const std::string& trial_name, + std::map<std::string, std::string>* params); + +// Retrieves a specific parameter value corresponding to |param_name| for the +// variation associated with the specified field trial, based on its selected +// group. If the field trial does not exist or the specified parameter does not +// exist, returns an empty string. Calling this function will result in the +// field trial being marked as active if found (i.e. group() will be called on +// it), if it wasn't already. Currently, this information is only available from +// the browser process. Thread safe. +std::string GetVariationParamValue(const std::string& trial_name, + const std::string& param_name); + +// Expose some functions for testing. +namespace testing { + +// Clears all of the mapped associations. +void ClearAllVariationIDs(); + +// Clears all of the associated params. +void ClearAllVariationParams(); + +} // namespace testing + +} // namespace chrome_variations + +#endif // COMPONENTS_VARIATIONS_VARIATIONS_ASSOCIATED_DATA_H_ diff --git a/chromium/components/variations/variations_associated_data_unittest.cc b/chromium/components/variations/variations_associated_data_unittest.cc new file mode 100644 index 00000000000..d9d4b5d2faa --- /dev/null +++ b/chromium/components/variations/variations_associated_data_unittest.cc @@ -0,0 +1,310 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/variations/variations_associated_data.h" + +#include "base/metrics/field_trial.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace chrome_variations { + +namespace { + +const VariationID TEST_VALUE_A = 3300200; +const VariationID TEST_VALUE_B = 3300201; + +// Convenience helper to retrieve the chrome_variations::VariationID for a +// FieldTrial. Note that this will do the group assignment in |trial| if not +// already done. +VariationID GetIDForTrial(IDCollectionKey key, base::FieldTrial* trial) { + return GetGoogleVariationID(key, trial->trial_name(), trial->group_name()); +} + +// Tests whether a field trial is active (i.e. group() has been called on it). +bool IsFieldTrialActive(const std::string& trial_name) { + base::FieldTrial::ActiveGroups active_groups; + base::FieldTrialList::GetActiveFieldTrialGroups(&active_groups); + for (size_t i = 0; i < active_groups.size(); ++i) { + if (active_groups[i].trial_name == trial_name) + return true; + } + return false; +} + +// Call FieldTrialList::FactoryGetFieldTrial() with a future expiry date. +scoped_refptr<base::FieldTrial> CreateFieldTrial( + const std::string& trial_name, + int total_probability, + const std::string& default_group_name, + int* default_group_number) { + return base::FieldTrialList::FactoryGetFieldTrial( + trial_name, total_probability, default_group_name, + base::FieldTrialList::kNoExpirationYear, 1, 1, + base::FieldTrial::SESSION_RANDOMIZED, default_group_number); +} + +} // namespace + +class VariationsAssociatedDataTest : public ::testing::Test { + public: + VariationsAssociatedDataTest() : field_trial_list_(NULL) { + } + + virtual ~VariationsAssociatedDataTest() { + // Ensure that the maps are cleared between tests, since they are stored as + // process singletons. + testing::ClearAllVariationIDs(); + } + + private: + base::FieldTrialList field_trial_list_; + + DISALLOW_COPY_AND_ASSIGN(VariationsAssociatedDataTest); +}; + +// Test that if the trial is immediately disabled, GetGoogleVariationID just +// returns the empty ID. +TEST_F(VariationsAssociatedDataTest, DisableImmediately) { + int default_group_number = -1; + scoped_refptr<base::FieldTrial> trial( + CreateFieldTrial("trial", 100, "default", &default_group_number)); + + ASSERT_EQ(default_group_number, trial->group()); + ASSERT_EQ(EMPTY_ID, GetIDForTrial(GOOGLE_WEB_PROPERTIES, trial.get())); +} + +// Test that successfully associating the FieldTrial with some ID, and then +// disabling the FieldTrial actually makes GetGoogleVariationID correctly +// return the empty ID. +TEST_F(VariationsAssociatedDataTest, DisableAfterInitialization) { + const std::string default_name = "default"; + const std::string non_default_name = "non_default"; + + scoped_refptr<base::FieldTrial> trial( + CreateFieldTrial("trial", 100, default_name, NULL)); + + trial->AppendGroup(non_default_name, 100); + AssociateGoogleVariationID(GOOGLE_WEB_PROPERTIES, trial->trial_name(), + default_name, TEST_VALUE_A); + AssociateGoogleVariationID(GOOGLE_WEB_PROPERTIES, trial->trial_name(), + non_default_name, TEST_VALUE_B); + trial->Disable(); + ASSERT_EQ(default_name, trial->group_name()); + ASSERT_EQ(TEST_VALUE_A, GetIDForTrial(GOOGLE_WEB_PROPERTIES, trial.get())); +} + +// Test various successful association cases. +TEST_F(VariationsAssociatedDataTest, AssociateGoogleVariationID) { + const std::string default_name1 = "default"; + scoped_refptr<base::FieldTrial> trial_true( + CreateFieldTrial("d1", 10, default_name1, NULL)); + const std::string winner = "TheWinner"; + int winner_group = trial_true->AppendGroup(winner, 10); + + // Set GoogleVariationIDs so we can verify that they were chosen correctly. + AssociateGoogleVariationID(GOOGLE_WEB_PROPERTIES, trial_true->trial_name(), + default_name1, TEST_VALUE_A); + AssociateGoogleVariationID(GOOGLE_WEB_PROPERTIES, trial_true->trial_name(), + winner, TEST_VALUE_B); + + EXPECT_EQ(winner_group, trial_true->group()); + EXPECT_EQ(winner, trial_true->group_name()); + EXPECT_EQ(TEST_VALUE_B, + GetIDForTrial(GOOGLE_WEB_PROPERTIES, trial_true.get())); + + const std::string default_name2 = "default2"; + scoped_refptr<base::FieldTrial> trial_false( + CreateFieldTrial("d2", 10, default_name2, NULL)); + const std::string loser = "ALoser"; + const int loser_group = trial_false->AppendGroup(loser, 0); + + AssociateGoogleVariationID(GOOGLE_WEB_PROPERTIES, trial_false->trial_name(), + default_name2, TEST_VALUE_A); + AssociateGoogleVariationID(GOOGLE_WEB_PROPERTIES, trial_false->trial_name(), + loser, TEST_VALUE_B); + + EXPECT_NE(loser_group, trial_false->group()); + EXPECT_EQ(TEST_VALUE_A, + GetIDForTrial(GOOGLE_WEB_PROPERTIES, trial_false.get())); +} + +// Test that not associating a FieldTrial with any IDs ensure that the empty ID +// will be returned. +TEST_F(VariationsAssociatedDataTest, NoAssociation) { + const std::string default_name = "default"; + scoped_refptr<base::FieldTrial> no_id_trial( + CreateFieldTrial("d3", 10, default_name, NULL)); + + const std::string winner = "TheWinner"; + const int winner_group = no_id_trial->AppendGroup(winner, 10); + + // Ensure that despite the fact that a normal winner is elected, it does not + // have a valid VariationID associated with it. + EXPECT_EQ(winner_group, no_id_trial->group()); + EXPECT_EQ(winner, no_id_trial->group_name()); + EXPECT_EQ(EMPTY_ID, GetIDForTrial(GOOGLE_WEB_PROPERTIES, no_id_trial.get())); +} + +// Ensure that the AssociateGoogleVariationIDForce works as expected. +TEST_F(VariationsAssociatedDataTest, ForceAssociation) { + EXPECT_EQ(EMPTY_ID, + GetGoogleVariationID(GOOGLE_WEB_PROPERTIES, "trial", "group")); + AssociateGoogleVariationID(GOOGLE_WEB_PROPERTIES, "trial", "group", + TEST_VALUE_A); + EXPECT_EQ(TEST_VALUE_A, + GetGoogleVariationID(GOOGLE_WEB_PROPERTIES, "trial", "group")); + AssociateGoogleVariationID(GOOGLE_WEB_PROPERTIES, "trial", "group", + TEST_VALUE_B); + EXPECT_EQ(TEST_VALUE_A, + GetGoogleVariationID(GOOGLE_WEB_PROPERTIES, "trial", "group")); + AssociateGoogleVariationIDForce(GOOGLE_WEB_PROPERTIES, "trial", "group", + TEST_VALUE_B); + EXPECT_EQ(TEST_VALUE_B, + GetGoogleVariationID(GOOGLE_WEB_PROPERTIES, "trial", "group")); +} + +// Ensure that two collections can coexist without affecting each other. +TEST_F(VariationsAssociatedDataTest, CollectionsCoexist) { + const std::string default_name = "default"; + int default_group_number = -1; + scoped_refptr<base::FieldTrial> trial_true( + CreateFieldTrial("d1", 10, default_name, &default_group_number)); + ASSERT_EQ(default_group_number, trial_true->group()); + ASSERT_EQ(default_name, trial_true->group_name()); + + EXPECT_EQ(EMPTY_ID, + GetIDForTrial(GOOGLE_WEB_PROPERTIES, trial_true.get())); + EXPECT_EQ(EMPTY_ID, + GetIDForTrial(GOOGLE_UPDATE_SERVICE, trial_true.get())); + + AssociateGoogleVariationID(GOOGLE_WEB_PROPERTIES, trial_true->trial_name(), + default_name, TEST_VALUE_A); + EXPECT_EQ(TEST_VALUE_A, + GetIDForTrial(GOOGLE_WEB_PROPERTIES, trial_true.get())); + EXPECT_EQ(EMPTY_ID, + GetIDForTrial(GOOGLE_UPDATE_SERVICE, trial_true.get())); + + AssociateGoogleVariationID(GOOGLE_UPDATE_SERVICE, trial_true->trial_name(), + default_name, TEST_VALUE_A); + EXPECT_EQ(TEST_VALUE_A, + GetIDForTrial(GOOGLE_WEB_PROPERTIES, trial_true.get())); + EXPECT_EQ(TEST_VALUE_A, + GetIDForTrial(GOOGLE_UPDATE_SERVICE, trial_true.get())); +} + +TEST_F(VariationsAssociatedDataTest, AssociateVariationParams) { + const std::string kTrialName = "AssociateVariationParams"; + + { + std::map<std::string, std::string> params; + params["a"] = "10"; + params["b"] = "test"; + ASSERT_TRUE(AssociateVariationParams(kTrialName, "A", params)); + } + { + std::map<std::string, std::string> params; + params["a"] = "5"; + ASSERT_TRUE(AssociateVariationParams(kTrialName, "B", params)); + } + + base::FieldTrialList::CreateFieldTrial(kTrialName, "B"); + EXPECT_EQ("5", GetVariationParamValue(kTrialName, "a")); + EXPECT_EQ(std::string(), GetVariationParamValue(kTrialName, "b")); + EXPECT_EQ(std::string(), GetVariationParamValue(kTrialName, "x")); + + std::map<std::string, std::string> params; + EXPECT_TRUE(GetVariationParams(kTrialName, ¶ms)); + EXPECT_EQ(1U, params.size()); + EXPECT_EQ("5", params["a"]); +} + +TEST_F(VariationsAssociatedDataTest, AssociateVariationParams_Fail) { + const std::string kTrialName = "AssociateVariationParams_Fail"; + const std::string kGroupName = "A"; + + std::map<std::string, std::string> params; + params["a"] = "10"; + ASSERT_TRUE(AssociateVariationParams(kTrialName, kGroupName, params)); + params["a"] = "1"; + params["b"] = "2"; + ASSERT_FALSE(AssociateVariationParams(kTrialName, kGroupName, params)); + + base::FieldTrialList::CreateFieldTrial(kTrialName, kGroupName); + EXPECT_EQ("10", GetVariationParamValue(kTrialName, "a")); + EXPECT_EQ(std::string(), GetVariationParamValue(kTrialName, "b")); +} + +TEST_F(VariationsAssociatedDataTest, AssociateVariationParams_TrialActiveFail) { + const std::string kTrialName = "AssociateVariationParams_TrialActiveFail"; + base::FieldTrialList::CreateFieldTrial(kTrialName, "A"); + ASSERT_EQ("A", base::FieldTrialList::FindFullName(kTrialName)); + + std::map<std::string, std::string> params; + params["a"] = "10"; + EXPECT_FALSE(AssociateVariationParams(kTrialName, "B", params)); + EXPECT_FALSE(AssociateVariationParams(kTrialName, "A", params)); +} + +TEST_F(VariationsAssociatedDataTest, + AssociateVariationParams_DoesntActivateTrial) { + const std::string kTrialName = "AssociateVariationParams_DoesntActivateTrial"; + + ASSERT_FALSE(IsFieldTrialActive(kTrialName)); + scoped_refptr<base::FieldTrial> trial( + CreateFieldTrial(kTrialName, 100, "A", NULL)); + ASSERT_FALSE(IsFieldTrialActive(kTrialName)); + + std::map<std::string, std::string> params; + params["a"] = "10"; + EXPECT_TRUE(AssociateVariationParams(kTrialName, "A", params)); + ASSERT_FALSE(IsFieldTrialActive(kTrialName)); +} + +TEST_F(VariationsAssociatedDataTest, GetVariationParams_NoTrial) { + const std::string kTrialName = "GetVariationParams_NoParams"; + + std::map<std::string, std::string> params; + EXPECT_FALSE(GetVariationParams(kTrialName, ¶ms)); + EXPECT_EQ(std::string(), GetVariationParamValue(kTrialName, "x")); + EXPECT_EQ(std::string(), GetVariationParamValue(kTrialName, "y")); +} + +TEST_F(VariationsAssociatedDataTest, GetVariationParams_NoParams) { + const std::string kTrialName = "GetVariationParams_NoParams"; + + base::FieldTrialList::CreateFieldTrial(kTrialName, "A"); + + std::map<std::string, std::string> params; + EXPECT_FALSE(GetVariationParams(kTrialName, ¶ms)); + EXPECT_EQ(std::string(), GetVariationParamValue(kTrialName, "x")); + EXPECT_EQ(std::string(), GetVariationParamValue(kTrialName, "y")); +} + +TEST_F(VariationsAssociatedDataTest, GetVariationParams_ActivatesTrial) { + const std::string kTrialName = "GetVariationParams_ActivatesTrial"; + + ASSERT_FALSE(IsFieldTrialActive(kTrialName)); + scoped_refptr<base::FieldTrial> trial( + CreateFieldTrial(kTrialName, 100, "A", NULL)); + ASSERT_FALSE(IsFieldTrialActive(kTrialName)); + + std::map<std::string, std::string> params; + EXPECT_FALSE(GetVariationParams(kTrialName, ¶ms)); + ASSERT_TRUE(IsFieldTrialActive(kTrialName)); +} + +TEST_F(VariationsAssociatedDataTest, GetVariationParamValue_ActivatesTrial) { + const std::string kTrialName = "GetVariationParamValue_ActivatesTrial"; + + ASSERT_FALSE(IsFieldTrialActive(kTrialName)); + scoped_refptr<base::FieldTrial> trial( + CreateFieldTrial(kTrialName, 100, "A", NULL)); + ASSERT_FALSE(IsFieldTrialActive(kTrialName)); + + std::map<std::string, std::string> params; + EXPECT_EQ(std::string(), GetVariationParamValue(kTrialName, "x")); + ASSERT_TRUE(IsFieldTrialActive(kTrialName)); +} + +} // namespace chrome_variations diff --git a/chromium/components/variations/variations_seed_processor.cc b/chromium/components/variations/variations_seed_processor.cc new file mode 100644 index 00000000000..d5d4e55399b --- /dev/null +++ b/chromium/components/variations/variations_seed_processor.cc @@ -0,0 +1,342 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/variations/variations_seed_processor.h" + +#include <map> +#include <set> +#include <vector> + +#include "base/command_line.h" +#include "base/metrics/field_trial.h" +#include "base/stl_util.h" +#include "base/version.h" +#include "components/variations/variations_associated_data.h" + +namespace chrome_variations { + +namespace { + +Study_Platform GetCurrentPlatform() { +#if defined(OS_WIN) + return Study_Platform_PLATFORM_WINDOWS; +#elif defined(OS_IOS) + return Study_Platform_PLATFORM_IOS; +#elif defined(OS_MACOSX) + return Study_Platform_PLATFORM_MAC; +#elif defined(OS_CHROMEOS) + return Study_Platform_PLATFORM_CHROMEOS; +#elif defined(OS_ANDROID) + return Study_Platform_PLATFORM_ANDROID; +#elif defined(OS_LINUX) || defined(OS_BSD) || defined(OS_SOLARIS) + // Default BSD and SOLARIS to Linux to not break those builds, although these + // platforms are not officially supported by Chrome. + return Study_Platform_PLATFORM_LINUX; +#else +#error Unknown platform +#endif +} + +// Converts |date_time| in Study date format to base::Time. +base::Time ConvertStudyDateToBaseTime(int64 date_time) { + return base::Time::UnixEpoch() + base::TimeDelta::FromSeconds(date_time); +} + +} // namespace + +VariationsSeedProcessor::VariationsSeedProcessor() { +} + +VariationsSeedProcessor::~VariationsSeedProcessor() { +} + +void VariationsSeedProcessor::CreateTrialsFromSeed( + const VariationsSeed& seed, + const std::string& locale, + const base::Time& reference_date, + const base::Version& version, + Study_Channel channel) { + DCHECK(version.IsValid()); + + // Add expired studies (in a disabled state) only after all the non-expired + // studies have been added (and do not add an expired study if a corresponding + // non-expired study got added). This way, if there's both an expired and a + // non-expired study that applies, the non-expired study takes priority. + std::set<std::string> created_studies; + std::vector<const Study*> expired_studies; + + for (int i = 0; i < seed.study_size(); ++i) { + const Study& study = seed.study(i); + if (!ShouldAddStudy(study, locale, reference_date, version, channel)) + continue; + + if (IsStudyExpired(study, reference_date)) { + expired_studies.push_back(&study); + } else { + CreateTrialFromStudy(study, false); + created_studies.insert(study.name()); + } + } + + for (size_t i = 0; i < expired_studies.size(); ++i) { + if (!ContainsKey(created_studies, expired_studies[i]->name())) + CreateTrialFromStudy(*expired_studies[i], true); + } +} + +bool VariationsSeedProcessor::CheckStudyChannel(const Study_Filter& filter, + Study_Channel channel) { + // An empty channel list matches all channels. + if (filter.channel_size() == 0) + return true; + + for (int i = 0; i < filter.channel_size(); ++i) { + if (filter.channel(i) == channel) + return true; + } + return false; +} + +bool VariationsSeedProcessor::CheckStudyLocale( + const Study_Filter& filter, + const std::string& locale) { + // An empty locale list matches all locales. + if (filter.locale_size() == 0) + return true; + + for (int i = 0; i < filter.locale_size(); ++i) { + if (filter.locale(i) == locale) + return true; + } + return false; +} + +bool VariationsSeedProcessor::CheckStudyPlatform( + const Study_Filter& filter, + Study_Platform platform) { + // An empty platform list matches all platforms. + if (filter.platform_size() == 0) + return true; + + for (int i = 0; i < filter.platform_size(); ++i) { + if (filter.platform(i) == platform) + return true; + } + return false; +} + +bool VariationsSeedProcessor::CheckStudyStartDate( + const Study_Filter& filter, + const base::Time& date_time) { + if (filter.has_start_date()) { + const base::Time start_date = + ConvertStudyDateToBaseTime(filter.start_date()); + return date_time >= start_date; + } + + return true; +} + +bool VariationsSeedProcessor::CheckStudyVersion( + const Study_Filter& filter, + const base::Version& version) { + if (filter.has_min_version()) { + if (version.CompareToWildcardString(filter.min_version()) < 0) + return false; + } + + if (filter.has_max_version()) { + if (version.CompareToWildcardString(filter.max_version()) > 0) + return false; + } + + return true; +} + +void VariationsSeedProcessor::CreateTrialFromStudy(const Study& study, + bool is_expired) { + base::FieldTrial::Probability total_probability = 0; + if (!ValidateStudyAndComputeTotalProbability(study, &total_probability)) + return; + + // Check if any experiments need to be forced due to a command line + // flag. Force the first experiment with an existing flag. + CommandLine* command_line = CommandLine::ForCurrentProcess(); + for (int i = 0; i < study.experiment_size(); ++i) { + const Study_Experiment& experiment = study.experiment(i); + if (experiment.has_forcing_flag() && + command_line->HasSwitch(experiment.forcing_flag())) { + base::FieldTrialList::CreateFieldTrial(study.name(), experiment.name()); + DVLOG(1) << "Trial " << study.name() << " forced by flag: " + << experiment.forcing_flag(); + return; + } + } + + uint32 randomization_seed = 0; + base::FieldTrial::RandomizationType randomization_type = + base::FieldTrial::SESSION_RANDOMIZED; + if (study.has_consistency() && + study.consistency() == Study_Consistency_PERMANENT) { + randomization_type = base::FieldTrial::ONE_TIME_RANDOMIZED; + if (study.has_randomization_seed()) + randomization_seed = study.randomization_seed(); + } + + // The trial is created without specifying an expiration date because the + // expiration check in field_trial.cc is based on the build date. Instead, + // the expiration check using |reference_date| is done explicitly below. + scoped_refptr<base::FieldTrial> trial( + base::FieldTrialList::FactoryGetFieldTrialWithRandomizationSeed( + study.name(), total_probability, study.default_experiment_name(), + base::FieldTrialList::kNoExpirationYear, 1, 1, randomization_type, + randomization_seed, NULL)); + + for (int i = 0; i < study.experiment_size(); ++i) { + const Study_Experiment& experiment = study.experiment(i); + + std::map<std::string, std::string> params; + for (int j = 0; j < experiment.param_size(); j++) { + if (experiment.param(j).has_name() && experiment.param(j).has_value()) + params[experiment.param(j).name()] = experiment.param(j).value(); + } + if (!params.empty()) + AssociateVariationParams(study.name(), experiment.name(), params); + + // Groups with flags can't be selected randomly, so we don't add them to + // the field trial. + if (experiment.has_forcing_flag()) + continue; + + if (experiment.name() != study.default_experiment_name()) + trial->AppendGroup(experiment.name(), experiment.probability_weight()); + + if (experiment.has_google_web_experiment_id()) { + const VariationID variation_id = + static_cast<VariationID>(experiment.google_web_experiment_id()); + AssociateGoogleVariationIDForce(GOOGLE_WEB_PROPERTIES, + study.name(), + experiment.name(), + variation_id); + } + if (experiment.has_google_update_experiment_id()) { + const VariationID variation_id = + static_cast<VariationID>(experiment.google_update_experiment_id()); + AssociateGoogleVariationIDForce(GOOGLE_UPDATE_SERVICE, + study.name(), + experiment.name(), + variation_id); + } + } + + trial->SetForced(); + if (is_expired) + trial->Disable(); +} + +bool VariationsSeedProcessor::IsStudyExpired(const Study& study, + const base::Time& date_time) { + if (study.has_expiry_date()) { + const base::Time expiry_date = + ConvertStudyDateToBaseTime(study.expiry_date()); + return date_time >= expiry_date; + } + + return false; +} + +bool VariationsSeedProcessor::ShouldAddStudy( + const Study& study, + const std::string& locale, + const base::Time& reference_date, + const base::Version& version, + Study_Channel channel) { + if (study.has_filter()) { + if (!CheckStudyChannel(study.filter(), channel)) { + DVLOG(1) << "Filtered out study " << study.name() << " due to channel."; + return false; + } + + if (!CheckStudyLocale(study.filter(), locale)) { + DVLOG(1) << "Filtered out study " << study.name() << " due to locale."; + return false; + } + + if (!CheckStudyPlatform(study.filter(), GetCurrentPlatform())) { + DVLOG(1) << "Filtered out study " << study.name() << " due to platform."; + return false; + } + + if (!CheckStudyVersion(study.filter(), version)) { + DVLOG(1) << "Filtered out study " << study.name() << " due to version."; + return false; + } + + if (!CheckStudyStartDate(study.filter(), reference_date)) { + DVLOG(1) << "Filtered out study " << study.name() << + " due to start date."; + return false; + } + } + + DVLOG(1) << "Kept study " << study.name() << "."; + return true; +} + +bool VariationsSeedProcessor::ValidateStudyAndComputeTotalProbability( + const Study& study, + base::FieldTrial::Probability* total_probability) { + // At the moment, a missing default_experiment_name makes the study invalid. + if (study.default_experiment_name().empty()) { + DVLOG(1) << study.name() << " has no default experiment defined."; + return false; + } + if (study.filter().has_min_version() && + !Version::IsValidWildcardString(study.filter().min_version())) { + DVLOG(1) << study.name() << " has invalid min version: " + << study.filter().min_version(); + return false; + } + if (study.filter().has_max_version() && + !Version::IsValidWildcardString(study.filter().max_version())) { + DVLOG(1) << study.name() << " has invalid max version: " + << study.filter().max_version(); + return false; + } + + const std::string& default_group_name = study.default_experiment_name(); + base::FieldTrial::Probability divisor = 0; + + bool found_default_group = false; + std::set<std::string> experiment_names; + for (int i = 0; i < study.experiment_size(); ++i) { + if (study.experiment(i).name().empty()) { + DVLOG(1) << study.name() << " is missing experiment " << i << " name"; + return false; + } + if (!experiment_names.insert(study.experiment(i).name()).second) { + DVLOG(1) << study.name() << " has a repeated experiment name " + << study.experiment(i).name(); + return false; + } + + if (!study.experiment(i).has_forcing_flag()) + divisor += study.experiment(i).probability_weight(); + if (study.experiment(i).name() == default_group_name) + found_default_group = true; + } + + if (!found_default_group) { + DVLOG(1) << study.name() << " is missing default experiment in its " + << "experiment list"; + // The default group was not found in the list of groups. This study is not + // valid. + return false; + } + + *total_probability = divisor; + return true; +} + +} // namespace chrome_variations diff --git a/chromium/components/variations/variations_seed_processor.h b/chromium/components/variations/variations_seed_processor.h new file mode 100644 index 00000000000..cba26214af9 --- /dev/null +++ b/chromium/components/variations/variations_seed_processor.h @@ -0,0 +1,96 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_VARIATIONS_VARIATIONS_SEED_PROCESSOR_H_ +#define COMPONENTS_VARIATIONS_VARIATIONS_SEED_PROCESSOR_H_ + +#include <string> + +#include "base/compiler_specific.h" +#include "base/gtest_prod_util.h" +#include "base/metrics/field_trial.h" +#include "base/time/time.h" +#include "base/version.h" +#include "components/variations/proto/study.pb.h" +#include "components/variations/proto/variations_seed.pb.h" + +namespace chrome_variations { + +// Helper class to instantiate field trials from a variations seed. +class VariationsSeedProcessor { + public: + VariationsSeedProcessor(); + virtual ~VariationsSeedProcessor(); + + // Creates field trials from the specified variations |seed|, based on the + // specified configuration (locale, current date, version and channel). + void CreateTrialsFromSeed(const VariationsSeed& seed, + const std::string& locale, + const base::Time& reference_date, + const base::Version& version, + Study_Channel channel); + + private: + FRIEND_TEST_ALL_PREFIXES(VariationsSeedProcessorTest, CheckStudyChannel); + FRIEND_TEST_ALL_PREFIXES(VariationsSeedProcessorTest, CheckStudyLocale); + FRIEND_TEST_ALL_PREFIXES(VariationsSeedProcessorTest, CheckStudyPlatform); + FRIEND_TEST_ALL_PREFIXES(VariationsSeedProcessorTest, CheckStudyStartDate); + FRIEND_TEST_ALL_PREFIXES(VariationsSeedProcessorTest, CheckStudyVersion); + FRIEND_TEST_ALL_PREFIXES(VariationsSeedProcessorTest, + CheckStudyVersionWildcards); + FRIEND_TEST_ALL_PREFIXES(VariationsSeedProcessorTest, ForceGroupWithFlag1); + FRIEND_TEST_ALL_PREFIXES(VariationsSeedProcessorTest, ForceGroupWithFlag2); + FRIEND_TEST_ALL_PREFIXES(VariationsSeedProcessorTest, + ForceGroup_ChooseFirstGroupWithFlag); + FRIEND_TEST_ALL_PREFIXES(VariationsSeedProcessorTest, + ForceGroup_DontChooseGroupWithFlag); + FRIEND_TEST_ALL_PREFIXES(VariationsSeedProcessorTest, IsStudyExpired); + FRIEND_TEST_ALL_PREFIXES(VariationsSeedProcessorTest, ValidateStudy); + FRIEND_TEST_ALL_PREFIXES(VariationsSeedProcessorTest, VariationParams); + + // Checks whether a study is applicable for the given |channel| per |filter|. + bool CheckStudyChannel(const Study_Filter& filter, Study_Channel channel); + + // Checks whether a study is applicable for the given |locale| per |filter|. + bool CheckStudyLocale(const Study_Filter& filter, const std::string& locale); + + // Checks whether a study is applicable for the given |platform| per |filter|. + bool CheckStudyPlatform(const Study_Filter& filter, Study_Platform platform); + + // Checks whether a study is applicable for the given date/time per |filter|. + bool CheckStudyStartDate(const Study_Filter& filter, + const base::Time& date_time); + + // Checks whether a study is applicable for the given version per |filter|. + bool CheckStudyVersion(const Study_Filter& filter, + const base::Version& version); + + // Creates and registers a field trial from the |study| data. Disables the + // trial if |is_expired| is true. + void CreateTrialFromStudy(const Study& study, bool is_expired); + + // Checks whether |study| is expired using the given date/time. + bool IsStudyExpired(const Study& study, const base::Time& date_time); + + // Returns whether |study| should be disabled according to its restriction + // parameters. Uses |version_info| for min / max version checks, + // |reference_date| for the start date check and |channel| for channel + // checks. + bool ShouldAddStudy(const Study& study, + const std::string& locale, + const base::Time& reference_date, + const base::Version& version, + Study_Channel channel); + + // Validates the sanity of |study| and computes the total probability. + bool ValidateStudyAndComputeTotalProbability( + const Study& study, + base::FieldTrial::Probability* total_probability); + + DISALLOW_COPY_AND_ASSIGN(VariationsSeedProcessor); +}; + +} // namespace chrome_variations + +#endif // COMPONENTS_VARIATIONS_VARIATIONS_SEED_PROCESSOR_H_ diff --git a/chromium/components/variations/variations_seed_processor_unittest.cc b/chromium/components/variations/variations_seed_processor_unittest.cc new file mode 100644 index 00000000000..85692f9265d --- /dev/null +++ b/chromium/components/variations/variations_seed_processor_unittest.cc @@ -0,0 +1,531 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/variations/variations_seed_processor.h" + +#include <vector> + +#include "base/command_line.h" +#include "base/strings/string_split.h" +#include "components/variations/variations_associated_data.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace chrome_variations { + +namespace { + +// Converts |time| to Study proto format. +int64 TimeToProtoTime(const base::Time& time) { + return (time - base::Time::UnixEpoch()).InSeconds(); +} + +// Constants for testing associating command line flags with trial groups. +const char kFlagStudyName[] = "flag_test_trial"; +const char kFlagGroup1Name[] = "flag_group1"; +const char kFlagGroup2Name[] = "flag_group2"; +const char kNonFlagGroupName[] = "non_flag_group"; +const char kForcingFlag1[] = "flag_test1"; +const char kForcingFlag2[] = "flag_test2"; + +// Adds an experiment to |study| with the specified |name| and |probability|. +Study_Experiment* AddExperiment(const std::string& name, int probability, + Study* study) { + Study_Experiment* experiment = study->add_experiment(); + experiment->set_name(name); + experiment->set_probability_weight(probability); + return experiment; +} + +// Populates |study| with test data used for testing associating command line +// flags with trials groups. The study will contain three groups, a default +// group that isn't associated with a flag, and two other groups, both +// associated with different flags. +Study CreateStudyWithFlagGroups(int default_group_probability, + int flag_group1_probability, + int flag_group2_probability) { + DCHECK_GE(default_group_probability, 0); + DCHECK_GE(flag_group1_probability, 0); + DCHECK_GE(flag_group2_probability, 0); + Study study; + study.set_name(kFlagStudyName); + study.set_default_experiment_name(kNonFlagGroupName); + + AddExperiment(kNonFlagGroupName, default_group_probability, &study); + AddExperiment(kFlagGroup1Name, flag_group1_probability, &study) + ->set_forcing_flag(kForcingFlag1); + AddExperiment(kFlagGroup2Name, flag_group2_probability, &study) + ->set_forcing_flag(kForcingFlag2); + + return study; +} + +} // namespace + +TEST(VariationsSeedProcessorTest, CheckStudyChannel) { + VariationsSeedProcessor seed_processor; + + const Study_Channel channels[] = { + Study_Channel_CANARY, + Study_Channel_DEV, + Study_Channel_BETA, + Study_Channel_STABLE, + }; + bool channel_added[arraysize(channels)] = { 0 }; + + Study_Filter filter; + + // Check in the forwarded order. The loop cond is <= arraysize(channels) + // instead of < so that the result of adding the last channel gets checked. + for (size_t i = 0; i <= arraysize(channels); ++i) { + for (size_t j = 0; j < arraysize(channels); ++j) { + const bool expected = channel_added[j] || filter.channel_size() == 0; + const bool result = seed_processor.CheckStudyChannel(filter, channels[j]); + EXPECT_EQ(expected, result) << "Case " << i << "," << j << " failed!"; + } + + if (i < arraysize(channels)) { + filter.add_channel(channels[i]); + channel_added[i] = true; + } + } + + // Do the same check in the reverse order. + filter.clear_channel(); + memset(&channel_added, 0, sizeof(channel_added)); + for (size_t i = 0; i <= arraysize(channels); ++i) { + for (size_t j = 0; j < arraysize(channels); ++j) { + const bool expected = channel_added[j] || filter.channel_size() == 0; + const bool result = seed_processor.CheckStudyChannel(filter, channels[j]); + EXPECT_EQ(expected, result) << "Case " << i << "," << j << " failed!"; + } + + if (i < arraysize(channels)) { + const int index = arraysize(channels) - i - 1; + filter.add_channel(channels[index]); + channel_added[index] = true; + } + } +} + +TEST(VariationsSeedProcessorTest, CheckStudyLocale) { + VariationsSeedProcessor seed_processor; + + struct { + const char* filter_locales; + bool en_us_result; + bool en_ca_result; + bool fr_result; + } test_cases[] = { + {"en-US", true, false, false}, + {"en-US,en-CA,fr", true, true, true}, + {"en-US,en-CA,en-GB", true, true, false}, + {"en-GB,en-CA,en-US", true, true, false}, + {"ja,kr,vi", false, false, false}, + {"fr-CA", false, false, false}, + {"", true, true, true}, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) { + std::vector<std::string> filter_locales; + Study_Filter filter; + base::SplitString(test_cases[i].filter_locales, ',', &filter_locales); + for (size_t j = 0; j < filter_locales.size(); ++j) + filter.add_locale(filter_locales[j]); + EXPECT_EQ(test_cases[i].en_us_result, + seed_processor.CheckStudyLocale(filter, "en-US")); + EXPECT_EQ(test_cases[i].en_ca_result, + seed_processor.CheckStudyLocale(filter, "en-CA")); + EXPECT_EQ(test_cases[i].fr_result, + seed_processor.CheckStudyLocale(filter, "fr")); + } +} + +TEST(VariationsSeedProcessorTest, CheckStudyPlatform) { + VariationsSeedProcessor seed_processor; + + const Study_Platform platforms[] = { + Study_Platform_PLATFORM_WINDOWS, + Study_Platform_PLATFORM_MAC, + Study_Platform_PLATFORM_LINUX, + Study_Platform_PLATFORM_CHROMEOS, + Study_Platform_PLATFORM_ANDROID, + Study_Platform_PLATFORM_IOS, + }; + ASSERT_EQ(Study_Platform_Platform_ARRAYSIZE, + static_cast<int>(arraysize(platforms))); + bool platform_added[arraysize(platforms)] = { 0 }; + + Study_Filter filter; + + // Check in the forwarded order. The loop cond is <= arraysize(platforms) + // instead of < so that the result of adding the last channel gets checked. + for (size_t i = 0; i <= arraysize(platforms); ++i) { + for (size_t j = 0; j < arraysize(platforms); ++j) { + const bool expected = platform_added[j] || filter.platform_size() == 0; + const bool result = seed_processor.CheckStudyPlatform(filter, + platforms[j]); + EXPECT_EQ(expected, result) << "Case " << i << "," << j << " failed!"; + } + + if (i < arraysize(platforms)) { + filter.add_platform(platforms[i]); + platform_added[i] = true; + } + } + + // Do the same check in the reverse order. + filter.clear_platform(); + memset(&platform_added, 0, sizeof(platform_added)); + for (size_t i = 0; i <= arraysize(platforms); ++i) { + for (size_t j = 0; j < arraysize(platforms); ++j) { + const bool expected = platform_added[j] || filter.platform_size() == 0; + const bool result = seed_processor.CheckStudyPlatform(filter, + platforms[j]); + EXPECT_EQ(expected, result) << "Case " << i << "," << j << " failed!"; + } + + if (i < arraysize(platforms)) { + const int index = arraysize(platforms) - i - 1; + filter.add_platform(platforms[index]); + platform_added[index] = true; + } + } +} + +TEST(VariationsSeedProcessorTest, CheckStudyStartDate) { + VariationsSeedProcessor seed_processor; + + const base::Time now = base::Time::Now(); + const base::TimeDelta delta = base::TimeDelta::FromHours(1); + const struct { + const base::Time start_date; + bool expected_result; + } start_test_cases[] = { + { now - delta, true }, + { now, true }, + { now + delta, false }, + }; + + Study_Filter filter; + + // Start date not set should result in true. + EXPECT_TRUE(seed_processor.CheckStudyStartDate(filter, now)); + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(start_test_cases); ++i) { + filter.set_start_date(TimeToProtoTime(start_test_cases[i].start_date)); + const bool result = seed_processor.CheckStudyStartDate(filter, now); + EXPECT_EQ(start_test_cases[i].expected_result, result) + << "Case " << i << " failed!"; + } +} + +TEST(VariationsSeedProcessorTest, CheckStudyVersion) { + VariationsSeedProcessor seed_processor; + + const struct { + const char* min_version; + const char* version; + bool expected_result; + } min_test_cases[] = { + { "1.2.2", "1.2.3", true }, + { "1.2.3", "1.2.3", true }, + { "1.2.4", "1.2.3", false }, + { "1.3.2", "1.2.3", false }, + { "2.1.2", "1.2.3", false }, + { "0.3.4", "1.2.3", true }, + // Wildcards. + { "1.*", "1.2.3", true }, + { "1.2.*", "1.2.3", true }, + { "1.2.3.*", "1.2.3", true }, + { "1.2.4.*", "1.2.3", false }, + { "2.*", "1.2.3", false }, + { "0.3.*", "1.2.3", true }, + }; + + const struct { + const char* max_version; + const char* version; + bool expected_result; + } max_test_cases[] = { + { "1.2.2", "1.2.3", false }, + { "1.2.3", "1.2.3", true }, + { "1.2.4", "1.2.3", true }, + { "2.1.1", "1.2.3", true }, + { "2.1.1", "2.3.4", false }, + // Wildcards + { "2.1.*", "2.3.4", false }, + { "2.*", "2.3.4", true }, + { "2.3.*", "2.3.4", true }, + { "2.3.4.*", "2.3.4", true }, + { "2.3.4.0.*", "2.3.4", true }, + { "2.4.*", "2.3.4", true }, + { "1.3.*", "2.3.4", false }, + { "1.*", "2.3.4", false }, + }; + + Study_Filter filter; + + // Min/max version not set should result in true. + EXPECT_TRUE(seed_processor.CheckStudyVersion(filter, base::Version("1.2.3"))); + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(min_test_cases); ++i) { + filter.set_min_version(min_test_cases[i].min_version); + const bool result = + seed_processor.CheckStudyVersion(filter, + Version(min_test_cases[i].version)); + EXPECT_EQ(min_test_cases[i].expected_result, result) << + "Min. version case " << i << " failed!"; + } + filter.clear_min_version(); + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(max_test_cases); ++i) { + filter.set_max_version(max_test_cases[i].max_version); + const bool result = + seed_processor.CheckStudyVersion(filter, + Version(max_test_cases[i].version)); + EXPECT_EQ(max_test_cases[i].expected_result, result) << + "Max version case " << i << " failed!"; + } + + // Check intersection semantics. + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(min_test_cases); ++i) { + for (size_t j = 0; j < ARRAYSIZE_UNSAFE(max_test_cases); ++j) { + filter.set_min_version(min_test_cases[i].min_version); + filter.set_max_version(max_test_cases[j].max_version); + + if (!min_test_cases[i].expected_result) { + const bool result = + seed_processor.CheckStudyVersion( + filter, Version(min_test_cases[i].version)); + EXPECT_FALSE(result) << "Case " << i << "," << j << " failed!"; + } + + if (!max_test_cases[j].expected_result) { + const bool result = + seed_processor.CheckStudyVersion( + filter, Version(max_test_cases[j].version)); + EXPECT_FALSE(result) << "Case " << i << "," << j << " failed!"; + } + } + } +} + +// Test that the group for kForcingFlag1 is forced. +TEST(VariationsSeedProcessorTest, ForceGroupWithFlag1) { + CommandLine::ForCurrentProcess()->AppendSwitch(kForcingFlag1); + + base::FieldTrialList field_trial_list(NULL); + + Study study = CreateStudyWithFlagGroups(100, 0, 0); + VariationsSeedProcessor().CreateTrialFromStudy(study, false); + + EXPECT_EQ(kFlagGroup1Name, + base::FieldTrialList::FindFullName(kFlagStudyName)); +} + +// Test that the group for kForcingFlag2 is forced. +TEST(VariationsSeedProcessorTest, ForceGroupWithFlag2) { + CommandLine::ForCurrentProcess()->AppendSwitch(kForcingFlag2); + + base::FieldTrialList field_trial_list(NULL); + + Study study = CreateStudyWithFlagGroups(100, 0, 0); + VariationsSeedProcessor().CreateTrialFromStudy(study, false); + + EXPECT_EQ(kFlagGroup2Name, + base::FieldTrialList::FindFullName(kFlagStudyName)); +} + +TEST(VariationsSeedProcessorTest, ForceGroup_ChooseFirstGroupWithFlag) { + // Add the flag to the command line arguments so the flag group is forced. + CommandLine::ForCurrentProcess()->AppendSwitch(kForcingFlag1); + CommandLine::ForCurrentProcess()->AppendSwitch(kForcingFlag2); + + base::FieldTrialList field_trial_list(NULL); + + Study study = CreateStudyWithFlagGroups(100, 0, 0); + VariationsSeedProcessor().CreateTrialFromStudy(study, false); + + EXPECT_EQ(kFlagGroup1Name, + base::FieldTrialList::FindFullName(kFlagStudyName)); +} + +TEST(VariationsSeedProcessorTest, ForceGroup_DontChooseGroupWithFlag) { + base::FieldTrialList field_trial_list(NULL); + + // The two flag groups are given high probability, which would normally make + // them very likely to be chosen. They won't be chosen since flag groups are + // never chosen when their flag isn't present. + Study study = CreateStudyWithFlagGroups(1, 999, 999); + VariationsSeedProcessor().CreateTrialFromStudy(study, false); + EXPECT_EQ(kNonFlagGroupName, + base::FieldTrialList::FindFullName(kFlagStudyName)); +} + +TEST(VariationsSeedProcessorTest, IsStudyExpired) { + VariationsSeedProcessor seed_processor; + + const base::Time now = base::Time::Now(); + const base::TimeDelta delta = base::TimeDelta::FromHours(1); + const struct { + const base::Time expiry_date; + bool expected_result; + } expiry_test_cases[] = { + { now - delta, true }, + { now, true }, + { now + delta, false }, + }; + + Study study; + + // Expiry date not set should result in false. + EXPECT_FALSE(seed_processor.IsStudyExpired(study, now)); + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(expiry_test_cases); ++i) { + study.set_expiry_date(TimeToProtoTime(expiry_test_cases[i].expiry_date)); + const bool result = seed_processor.IsStudyExpired(study, now); + EXPECT_EQ(expiry_test_cases[i].expected_result, result) + << "Case " << i << " failed!"; + } +} + +TEST(VariationsSeedProcessorTest, NonExpiredStudyPrioritizedOverExpiredStudy) { + VariationsSeedProcessor seed_processor; + + const std::string kTrialName = "A"; + const std::string kGroup1Name = "Group1"; + + VariationsSeed seed; + Study* study1 = seed.add_study(); + study1->set_name(kTrialName); + study1->set_default_experiment_name("Default"); + AddExperiment(kGroup1Name, 100, study1); + AddExperiment("Default", 0, study1); + Study* study2 = seed.add_study(); + *study2 = *study1; + ASSERT_EQ(seed.study(0).name(), seed.study(1).name()); + + const base::Time year_ago = + base::Time::Now() - base::TimeDelta::FromDays(365); + + const base::Version version("20.0.0.0"); + + // Check that adding [expired, non-expired] activates the non-expired one. + ASSERT_EQ(std::string(), base::FieldTrialList::FindFullName(kTrialName)); + { + base::FieldTrialList field_trial_list(NULL); + study1->set_expiry_date(TimeToProtoTime(year_ago)); + seed_processor.CreateTrialsFromSeed(seed, "en-CA", base::Time::Now(), + version, Study_Channel_STABLE); + EXPECT_EQ(kGroup1Name, base::FieldTrialList::FindFullName(kTrialName)); + } + + // Check that adding [non-expired, expired] activates the non-expired one. + ASSERT_EQ(std::string(), base::FieldTrialList::FindFullName(kTrialName)); + { + base::FieldTrialList field_trial_list(NULL); + study1->clear_expiry_date(); + study2->set_expiry_date(TimeToProtoTime(year_ago)); + seed_processor.CreateTrialsFromSeed(seed, "en-CA", base::Time::Now(), + version, Study_Channel_STABLE); + EXPECT_EQ(kGroup1Name, base::FieldTrialList::FindFullName(kTrialName)); + } +} + +TEST(VariationsSeedProcessorTest, ValidateStudy) { + VariationsSeedProcessor seed_processor; + + Study study; + study.set_default_experiment_name("def"); + AddExperiment("abc", 100, &study); + Study_Experiment* default_group = AddExperiment("def", 200, &study); + + base::FieldTrial::Probability total_probability = 0; + bool valid = seed_processor.ValidateStudyAndComputeTotalProbability( + study, &total_probability); + EXPECT_TRUE(valid); + EXPECT_EQ(300, total_probability); + + // Min version checks. + study.mutable_filter()->set_min_version("1.2.3.*"); + valid = seed_processor.ValidateStudyAndComputeTotalProbability( + study, &total_probability); + EXPECT_TRUE(valid); + study.mutable_filter()->set_min_version("1.*.3"); + valid = seed_processor.ValidateStudyAndComputeTotalProbability( + study, &total_probability); + EXPECT_FALSE(valid); + study.mutable_filter()->set_min_version("1.2.3"); + valid = seed_processor.ValidateStudyAndComputeTotalProbability( + study, &total_probability); + EXPECT_TRUE(valid); + + // Max version checks. + study.mutable_filter()->set_max_version("2.3.4.*"); + valid = seed_processor.ValidateStudyAndComputeTotalProbability( + study, &total_probability); + EXPECT_TRUE(valid); + study.mutable_filter()->set_max_version("*.3"); + valid = seed_processor.ValidateStudyAndComputeTotalProbability( + study, &total_probability); + EXPECT_FALSE(valid); + study.mutable_filter()->set_max_version("2.3.4"); + valid = seed_processor.ValidateStudyAndComputeTotalProbability( + study, &total_probability); + EXPECT_TRUE(valid); + + study.clear_default_experiment_name(); + valid = seed_processor.ValidateStudyAndComputeTotalProbability(study, + &total_probability); + EXPECT_FALSE(valid); + + study.set_default_experiment_name("xyz"); + valid = seed_processor.ValidateStudyAndComputeTotalProbability(study, + &total_probability); + EXPECT_FALSE(valid); + + study.set_default_experiment_name("def"); + default_group->clear_name(); + valid = seed_processor.ValidateStudyAndComputeTotalProbability(study, + &total_probability); + EXPECT_FALSE(valid); + + default_group->set_name("def"); + valid = seed_processor.ValidateStudyAndComputeTotalProbability(study, + &total_probability); + ASSERT_TRUE(valid); + Study_Experiment* repeated_group = study.add_experiment(); + repeated_group->set_name("abc"); + repeated_group->set_probability_weight(1); + valid = seed_processor.ValidateStudyAndComputeTotalProbability(study, + &total_probability); + EXPECT_FALSE(valid); +} + +TEST(VariationsSeedProcessorTest, VariationParams) { + base::FieldTrialList field_trial_list(NULL); + VariationsSeedProcessor seed_processor; + + Study study; + study.set_name("Study1"); + study.set_default_experiment_name("B"); + + Study_Experiment* experiment1 = AddExperiment("A", 1, &study); + Study_Experiment_Param* param = experiment1->add_param(); + param->set_name("x"); + param->set_value("y"); + + Study_Experiment* experiment2 = AddExperiment("B", 0, &study); + + seed_processor.CreateTrialFromStudy(study, false); + EXPECT_EQ("y", GetVariationParamValue("Study1", "x")); + + study.set_name("Study2"); + experiment1->set_probability_weight(0); + experiment2->set_probability_weight(1); + seed_processor.CreateTrialFromStudy(study, false); + EXPECT_EQ(std::string(), GetVariationParamValue("Study2", "x")); +} + +} // namespace chrome_variations diff --git a/chromium/components/web_contents_delegate_android/android/java/src/org/chromium/components/web_contents_delegate_android/ColorChooserAndroid.java b/chromium/components/web_contents_delegate_android/android/java/src/org/chromium/components/web_contents_delegate_android/ColorChooserAndroid.java deleted file mode 100644 index babf91ea359..00000000000 --- a/chromium/components/web_contents_delegate_android/android/java/src/org/chromium/components/web_contents_delegate_android/ColorChooserAndroid.java +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) 2012 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package org.chromium.components.web_contents_delegate_android; - -import android.content.Context; - -import org.chromium.base.CalledByNative; -import org.chromium.base.JNINamespace; -import org.chromium.content.browser.ContentViewCore; -import org.chromium.ui.ColorPickerDialog; -import org.chromium.ui.OnColorChangedListener; - -/** - * ColorChooserAndroid communicates with the java ColorPickerDialog and the - * native color_chooser_android.cc - */ -@JNINamespace("web_contents_delegate_android") -public class ColorChooserAndroid { - private final ColorPickerDialog mDialog; - private final int mNativeColorChooserAndroid; - - private ColorChooserAndroid(int nativeColorChooserAndroid, - Context context, int initialColor) { - OnColorChangedListener listener = new OnColorChangedListener() { - @Override - public void onColorChanged(int color) { - mDialog.dismiss(); - nativeOnColorChosen(mNativeColorChooserAndroid, color); - } - }; - - mNativeColorChooserAndroid = nativeColorChooserAndroid; - mDialog = new ColorPickerDialog(context, listener, initialColor); - } - - private void openColorChooser() { - mDialog.show(); - } - - @CalledByNative - public void closeColorChooser() { - mDialog.dismiss(); - } - - @CalledByNative - public static ColorChooserAndroid createColorChooserAndroid( - int nativeColorChooserAndroid, - ContentViewCore contentViewCore, - int initialColor) { - ColorChooserAndroid chooser = new ColorChooserAndroid(nativeColorChooserAndroid, - contentViewCore.getContext(), initialColor); - chooser.openColorChooser(); - return chooser; - } - - // Implemented in color_chooser_android.cc - private native void nativeOnColorChosen(int nativeColorChooserAndroid, int color); -} diff --git a/chromium/components/web_contents_delegate_android/android/java/src/org/chromium/components/web_contents_delegate_android/WebContentsDelegateAndroid.java b/chromium/components/web_contents_delegate_android/android/java/src/org/chromium/components/web_contents_delegate_android/WebContentsDelegateAndroid.java deleted file mode 100644 index c09cb89629c..00000000000 --- a/chromium/components/web_contents_delegate_android/android/java/src/org/chromium/components/web_contents_delegate_android/WebContentsDelegateAndroid.java +++ /dev/null @@ -1,167 +0,0 @@ -// Copyright (c) 2012 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package org.chromium.components.web_contents_delegate_android; - -import android.graphics.Rect; -import android.view.KeyEvent; - -import org.chromium.base.CalledByNative; -import org.chromium.base.JNINamespace; -import org.chromium.content.browser.ContentViewCore; - -/** - * Java peer of the native class of the same name. - */ -@JNINamespace("web_contents_delegate_android") -public class WebContentsDelegateAndroid { - - // Equivalent of WebCore::WebConsoleMessage::LevelTip. - public static final int LOG_LEVEL_TIP = 0; - // Equivalent of WebCore::WebConsoleMessage::LevelLog. - public static final int LOG_LEVEL_LOG = 1; - // Equivalent of WebCore::WebConsoleMessage::LevelWarning. - public static final int LOG_LEVEL_WARNING = 2; - // Equivalent of WebCore::WebConsoleMessage::LevelError. - public static final int LOG_LEVEL_ERROR = 3; - - // Flags passed to the WebContentsDelegateAndroid.navigationStateChanged to tell it - // what has changed. Should match the values in invalidate_type.h. - // Equivalent of InvalidateTypes::INVALIDATE_TYPE_URL. - public static final int INVALIDATE_TYPE_URL = 1 << 0; - // Equivalent of InvalidateTypes::INVALIDATE_TYPE_TAB. - public static final int INVALIDATE_TYPE_TAB = 1 << 1; - // Equivalent of InvalidateTypes::INVALIDATE_TYPE_LOAD. - public static final int INVALIDATE_TYPE_LOAD = 1 << 2; - // Equivalent of InvalidateTypes::INVALIDATE_TYPE_PAGE_ACTIONS. - public static final int INVALIDATE_TYPE_PAGE_ACTIONS = 1 << 3; - // Equivalent of InvalidateTypes::INVALIDATE_TYPE_TITLE. - public static final int INVALIDATE_TYPE_TITLE = 1 << 4; - - // The most recent load progress callback received from WebContents, as a percentage. - // Initialize to 100 to indicate that we're not in a loading state. - private int mMostRecentProgress = 100; - - public int getMostRecentProgress() { - return mMostRecentProgress; - } - - /** - * @param disposition The new tab disposition as per the constants in - * org.chromium.ui.WindowOpenDisposition (See window_open_disposition_list.h - * for the enumeration definitions). - */ - @CalledByNative - public void openNewTab(String url, String extraHeaders, byte[] postData, int disposition) { - } - - @CalledByNative - public boolean addNewContents(int nativeSourceWebContents, int nativeWebContents, - int disposition, Rect initialPosition, boolean userGesture) { - return false; - } - - @CalledByNative - public void activateContents() { - } - - @CalledByNative - public void closeContents() { - } - - @CalledByNative - public void onLoadStarted() { - } - - @CalledByNative - public void onLoadStopped() { - } - - @CalledByNative - public void navigationStateChanged(int flags) { - } - - @SuppressWarnings("unused") - @CalledByNative - private final void notifyLoadProgressChanged(double progress) { - mMostRecentProgress = (int) (100.0 * progress); - onLoadProgressChanged(mMostRecentProgress); - } - - /** - * @param progress The load progress [0, 100] for the current web contents. - */ - public void onLoadProgressChanged(int progress) { - } - - /** - * Signaled when the renderer has been deemed to be unresponsive. - */ - @CalledByNative - public void rendererUnresponsive() { - } - - /** - * Signaled when the render has been deemed to be responsive. - */ - @CalledByNative - public void rendererResponsive() { - } - - @CalledByNative - public void onUpdateUrl(String url) { - } - - @CalledByNative - public boolean takeFocus(boolean reverse) { - return false; - } - - @CalledByNative - public void handleKeyboardEvent(KeyEvent event) { - // TODO(bulach): we probably want to re-inject the KeyEvent back into - // the system. Investigate if this is at all possible. - } - - /** - * Report a JavaScript console message. - * - * @param level message level. One of WebContentsDelegateAndroid.LOG_LEVEL*. - * @param message the error message. - * @param lineNumber the line number int the source file at which the error is reported. - * @param sourceId the name of the source file that caused the error. - * @return true if the client will handle logging the message. - */ - @CalledByNative - public boolean addMessageToConsole(int level, String message, int lineNumber, - String sourceId) { - return false; - } - - /** - * Report a form resubmission. The overwriter of this function should eventually call - * either of ContentViewCore.ContinuePendingReload or ContentViewCore.CancelPendingReload. - */ - @CalledByNative - public void showRepostFormWarningDialog(ContentViewCore contentViewCore) { - } - - @CalledByNative - public void toggleFullscreenModeForTab(boolean enterFullscreen) { - } - - @CalledByNative - public boolean isFullscreenForTabOrPending() { - return false; - } - - /** - * Called from WebKit to request that the top controls be shown or hidden. - * The implementation should call ContentViewCore.showTopControls to actually - * show or hide the top controls. - */ - @CalledByNative - public void didProgrammaticallyScroll(int scrollX, int scrollY) { - } -} diff --git a/chromium/components/web_contents_delegate_android/component_jni_registrar.cc b/chromium/components/web_contents_delegate_android/component_jni_registrar.cc deleted file mode 100644 index b5c5e1590e8..00000000000 --- a/chromium/components/web_contents_delegate_android/component_jni_registrar.cc +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) 2012 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "components/web_contents_delegate_android/component_jni_registrar.h" - -#include "base/android/jni_android.h" -#include "base/android/jni_registrar.h" -#include "components/web_contents_delegate_android/color_chooser_android.h" -#include "components/web_contents_delegate_android/web_contents_delegate_android.h" - -namespace web_contents_delegate_android { - -static base::android::RegistrationMethod kComponentRegisteredMethods[] = { - { "ColorChooserAndroid", RegisterColorChooserAndroid }, - { "WebContentsDelegateAndroid", RegisterWebContentsDelegateAndroid }, -}; - -bool RegisterWebContentsDelegateAndroidJni(JNIEnv* env) { - return RegisterNativeMethods(env, - kComponentRegisteredMethods, arraysize(kComponentRegisteredMethods)); -} - -} // namespace web_contents_delegate_android - diff --git a/chromium/components/web_contents_delegate_android/component_jni_registrar.h b/chromium/components/web_contents_delegate_android/component_jni_registrar.h deleted file mode 100644 index 203585d4972..00000000000 --- a/chromium/components/web_contents_delegate_android/component_jni_registrar.h +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) 2012 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef COMPONENTS_WEB_CONTENTS_DELEGATE_ANDROID_COMPONENT_JNI_REGISTRAR_H_ -#define COMPONENTS_WEB_CONTENTS_DELEGATE_ANDROID_COMPONENT_JNI_REGISTRAR_H_ - -#include <jni.h> - -namespace web_contents_delegate_android { - -// Register all JNI bindings necessary for the web_contents_delegate_android -// component. -bool RegisterWebContentsDelegateAndroidJni(JNIEnv* env); - -} // namespace web_contents_delegate_android - -#endif // COMPONENTS_WEB_CONTENTS_DELEGATE_ANDROID_COMPONENT_JNI_REGISTRAR_H_ - diff --git a/chromium/components/web_contents_delegate_android/web_contents_delegate_android.cc b/chromium/components/web_contents_delegate_android/web_contents_delegate_android.cc index 8f599a16b03..c908f6a61a9 100644 --- a/chromium/components/web_contents_delegate_android/web_contents_delegate_android.cc +++ b/chromium/components/web_contents_delegate_android/web_contents_delegate_android.cc @@ -124,30 +124,6 @@ void WebContentsDelegateAndroid::NavigationStateChanged( changed_flags); } -void WebContentsDelegateAndroid::AddNewContents( - WebContents* source, - WebContents* new_contents, - WindowOpenDisposition disposition, - const gfx::Rect& initial_pos, - bool user_gesture, - bool* was_blocked) { - JNIEnv* env = AttachCurrentThread(); - ScopedJavaLocalRef<jobject> obj = GetJavaDelegate(env); - bool handled = false; - if (!obj.is_null()) { - handled = Java_WebContentsDelegateAndroid_addNewContents( - env, - obj.obj(), - reinterpret_cast<jint>(source), - reinterpret_cast<jint>(new_contents), - static_cast<jint>(disposition), - NULL, - user_gesture); - } - if (!handled) - delete new_contents; -} - void WebContentsDelegateAndroid::ActivateContents(WebContents* contents) { JNIEnv* env = AttachCurrentThread(); ScopedJavaLocalRef<jobject> obj = GetJavaDelegate(env); diff --git a/chromium/components/web_contents_delegate_android/web_contents_delegate_android.h b/chromium/components/web_contents_delegate_android/web_contents_delegate_android.h index 68bca15cf44..d375be9ab49 100644 --- a/chromium/components/web_contents_delegate_android/web_contents_delegate_android.h +++ b/chromium/components/web_contents_delegate_android/web_contents_delegate_android.h @@ -57,12 +57,6 @@ class WebContentsDelegateAndroid : public content::WebContentsDelegate { SkColor color) OVERRIDE; virtual void NavigationStateChanged(const content::WebContents* source, unsigned changed_flags) OVERRIDE; - virtual void AddNewContents(content::WebContents* source, - content::WebContents* new_contents, - WindowOpenDisposition disposition, - const gfx::Rect& initial_pos, - bool user_gesture, - bool* was_blocked) OVERRIDE; virtual void ActivateContents(content::WebContents* contents) OVERRIDE; virtual void DeactivateContents(content::WebContents* contents) OVERRIDE; virtual void LoadingStateChanged(content::WebContents* source) OVERRIDE; diff --git a/chromium/components/web_modal/OWNERS b/chromium/components/web_modal/OWNERS new file mode 100644 index 00000000000..ddd924dfa6a --- /dev/null +++ b/chromium/components/web_modal/OWNERS @@ -0,0 +1,2 @@ +wittman@chromium.org +ben@chromium.org diff --git a/chromium/components/web_modal/native_web_contents_modal_dialog_manager.h b/chromium/components/web_modal/native_web_contents_modal_dialog_manager.h index 7145a08c088..4df36800c71 100644 --- a/chromium/components/web_modal/native_web_contents_modal_dialog_manager.h +++ b/chromium/components/web_modal/native_web_contents_modal_dialog_manager.h @@ -13,6 +13,8 @@ class WebContents; namespace web_modal { +class WebContentsModalDialogHost; + // Interface from NativeWebContentsModalDialogManager to // WebContentsModalDialogManager. class NativeWebContentsModalDialogManagerDelegate { @@ -54,6 +56,9 @@ class NativeWebContentsModalDialogManager { // Runs a pulse animation for the web contents modal dialog. virtual void PulseDialog(NativeWebContentsModalDialog dialog) = 0; + // Called when the host view for the dialog has changed. + virtual void HostChanged(WebContentsModalDialogHost* new_host) = 0; + protected: NativeWebContentsModalDialogManager() {} diff --git a/chromium/components/web_modal/web_contents_modal_dialog_host.h b/chromium/components/web_modal/web_contents_modal_dialog_host.h index 499925cac21..73a3a7a0da6 100644 --- a/chromium/components/web_modal/web_contents_modal_dialog_host.h +++ b/chromium/components/web_modal/web_contents_modal_dialog_host.h @@ -6,8 +6,11 @@ #define COMPONENTS_WEB_MODAL_WEB_CONTENTS_MODAL_DIALOG_HOST_H_ #include "ui/gfx/native_widget_types.h" -#include "ui/gfx/point.h" -#include "ui/gfx/size.h" + +namespace gfx { +class Point; +class Size; +} namespace web_modal { @@ -18,6 +21,7 @@ class WebContentsModalDialogHostObserver { virtual ~WebContentsModalDialogHostObserver(); virtual void OnPositionRequiresUpdate() = 0; + virtual void OnHostDestroying() = 0; protected: WebContentsModalDialogHostObserver(); @@ -38,6 +42,9 @@ class WebContentsModalDialogHost { // view. virtual gfx::Point GetDialogPosition(const gfx::Size& size) = 0; + // Returns the maximum dimensions a dialog can have. + virtual gfx::Size GetMaximumDialogSize() = 0; + // Add/remove observer. virtual void AddObserver(WebContentsModalDialogHostObserver* observer) = 0; virtual void RemoveObserver(WebContentsModalDialogHostObserver* observer) = 0; diff --git a/chromium/components/web_modal/web_contents_modal_dialog_manager.cc b/chromium/components/web_modal/web_contents_modal_dialog_manager.cc index 0e07f8dd1d9..4d8aca714c5 100644 --- a/chromium/components/web_modal/web_contents_modal_dialog_manager.cc +++ b/chromium/components/web_modal/web_contents_modal_dialog_manager.cc @@ -25,9 +25,16 @@ WebContentsModalDialogManager::~WebContentsModalDialogManager() { DCHECK(child_dialogs_.empty()); } +void WebContentsModalDialogManager::SetDelegate( + WebContentsModalDialogManagerDelegate* d) { + delegate_ = d; + // Delegate can be NULL on Views/Win32 during tab drag. + native_manager_->HostChanged(d ? d->GetWebContentsModalDialogHost() : NULL); +} + void WebContentsModalDialogManager::ShowDialog( NativeWebContentsModalDialog dialog) { - child_dialogs_.push_back(dialog); + child_dialogs_.push_back(DialogState(dialog)); native_manager_->ManageDialog(dialog); @@ -38,13 +45,21 @@ void WebContentsModalDialogManager::ShowDialog( } } -bool WebContentsModalDialogManager::IsShowingDialog() const { +bool WebContentsModalDialogManager::IsDialogActive() const { return !child_dialogs_.empty(); } void WebContentsModalDialogManager::FocusTopmostDialog() { DCHECK(!child_dialogs_.empty()); - native_manager_->FocusDialog(child_dialogs_.front()); + native_manager_->FocusDialog(child_dialogs_.front().dialog); +} + +void WebContentsModalDialogManager::SetCloseOnInterstitialWebUI( + NativeWebContentsModalDialog dialog, + bool close) { + WebContentsModalDialogList::iterator loc = FindDialogState(dialog); + DCHECK(loc != child_dialogs_.end()); + loc->close_on_interstitial_webui = close; } content::WebContents* WebContentsModalDialogManager::GetWebContents() const { @@ -53,8 +68,7 @@ content::WebContents* WebContentsModalDialogManager::GetWebContents() const { void WebContentsModalDialogManager::WillClose( NativeWebContentsModalDialog dialog) { - WebContentsModalDialogList::iterator i( - std::find(child_dialogs_.begin(), child_dialogs_.end(), dialog)); + WebContentsModalDialogList::iterator i = FindDialogState(dialog); // The Views tab contents modal dialog calls WillClose twice. Ignore the // second invocation. @@ -65,7 +79,7 @@ void WebContentsModalDialogManager::WillClose( child_dialogs_.erase(i); if (!child_dialogs_.empty() && removed_topmost_dialog && !closing_all_dialogs_) - native_manager_->ShowDialog(child_dialogs_.front()); + native_manager_->ShowDialog(child_dialogs_.front().dialog); BlockWebContentsInteraction(!child_dialogs_.empty()); } @@ -75,15 +89,14 @@ void WebContentsModalDialogManager::Observe( const content::NotificationSource& source, const content::NotificationDetails& details) { DCHECK(type == content::NOTIFICATION_WEB_CONTENTS_VISIBILITY_CHANGED); - if (child_dialogs_.empty()) return; bool visible = *content::Details<bool>(details).ptr(); if (visible) - native_manager_->ShowDialog(child_dialogs_.front()); + native_manager_->ShowDialog(child_dialogs_.front().dialog); else - native_manager_->HideDialog(child_dialogs_.front()); + native_manager_->HideDialog(child_dialogs_.front().dialog); } WebContentsModalDialogManager::WebContentsModalDialogManager( @@ -98,6 +111,24 @@ WebContentsModalDialogManager::WebContentsModalDialogManager( content::Source<content::WebContents>(web_contents)); } +WebContentsModalDialogManager::DialogState::DialogState( + NativeWebContentsModalDialog dialog) + : dialog(dialog), + close_on_interstitial_webui(false) { +} + +WebContentsModalDialogManager::WebContentsModalDialogList::iterator + WebContentsModalDialogManager::FindDialogState( + NativeWebContentsModalDialog dialog) { + WebContentsModalDialogList::iterator i; + for (i = child_dialogs_.begin(); i != child_dialogs_.end(); ++i) { + if (i->dialog == dialog) + break; + } + + return i; +} + void WebContentsModalDialogManager::BlockWebContentsInteraction(bool blocked) { WebContents* contents = web_contents(); if (!contents) { @@ -118,7 +149,7 @@ void WebContentsModalDialogManager::CloseAllDialogs() { // Clear out any dialogs since we are leaving this page entirely. while (!child_dialogs_.empty()) - native_manager_->CloseDialog(child_dialogs_.front()); + native_manager_->CloseDialog(child_dialogs_.front().dialog); closing_all_dialogs_ = false; } @@ -135,7 +166,7 @@ void WebContentsModalDialogManager::DidNavigateMainFrame( void WebContentsModalDialogManager::DidGetIgnoredUIEvent() { if (!child_dialogs_.empty()) - native_manager_->FocusDialog(child_dialogs_.front()); + native_manager_->FocusDialog(child_dialogs_.front().dialog); } void WebContentsModalDialogManager::WebContentsDestroyed(WebContents* tab) { @@ -146,4 +177,15 @@ void WebContentsModalDialogManager::WebContentsDestroyed(WebContents* tab) { CloseAllDialogs(); } +void WebContentsModalDialogManager::DidAttachInterstitialPage() { + // Copy the dialogs so we can close and remove them while iterating over the + // list. + WebContentsModalDialogList dialogs(child_dialogs_); + for (WebContentsModalDialogList::iterator it = dialogs.begin(); + it != dialogs.end(); ++it) { + if (it->close_on_interstitial_webui) + native_manager_->CloseDialog(it->dialog); + } +} + } // namespace web_modal diff --git a/chromium/components/web_modal/web_contents_modal_dialog_manager.h b/chromium/components/web_modal/web_contents_modal_dialog_manager.h index e2548c04e5c..58d5520b9fc 100644 --- a/chromium/components/web_modal/web_contents_modal_dialog_manager.h +++ b/chromium/components/web_modal/web_contents_modal_dialog_manager.h @@ -29,7 +29,7 @@ class WebContentsModalDialogManager virtual ~WebContentsModalDialogManager(); WebContentsModalDialogManagerDelegate* delegate() const { return delegate_; } - void set_delegate(WebContentsModalDialogManagerDelegate* d) { delegate_ = d; } + void SetDelegate(WebContentsModalDialogManagerDelegate* d); static NativeWebContentsModalDialogManager* CreateNativeManager( NativeWebContentsModalDialogManagerDelegate* native_delegate); @@ -38,13 +38,17 @@ class WebContentsModalDialogManager // WillClose() when it is being destroyed. void ShowDialog(NativeWebContentsModalDialog dialog); - // Returns true if a dialog is currently being shown. - bool IsShowingDialog() const; + // Returns true if any dialogs are active and not closed. + bool IsDialogActive() const; - // Focus the topmost modal dialog. IsShowingDialog() must be true when - // calling this function. + // Focus the topmost modal dialog. IsDialogActive() must be true when calling + // this function. void FocusTopmostDialog(); + // Set to true to close the window when a page load starts on the WebContents. + void SetCloseOnInterstitialWebUI(NativeWebContentsModalDialog dialog, + bool close); + // Overriden from NativeWebContentsModalDialogManagerDelegate: virtual content::WebContents* GetWebContents() const OVERRIDE; // Called when a WebContentsModalDialogs we own is about to be closed. @@ -62,6 +66,7 @@ class WebContentsModalDialogManager : manager_(manager) {} void CloseAllDialogs() { manager_->CloseAllDialogs(); } + void DidAttachInterstitialPage() { manager_->DidAttachInterstitialPage(); } void ResetNativeManager(NativeWebContentsModalDialogManager* delegate) { manager_->native_manager_.reset(delegate); } @@ -76,7 +81,18 @@ class WebContentsModalDialogManager explicit WebContentsModalDialogManager(content::WebContents* web_contents); friend class content::WebContentsUserData<WebContentsModalDialogManager>; - typedef std::deque<NativeWebContentsModalDialog> WebContentsModalDialogList; + struct DialogState { + explicit DialogState(NativeWebContentsModalDialog dialog); + + NativeWebContentsModalDialog dialog; + bool close_on_interstitial_webui; + }; + + typedef std::deque<DialogState> WebContentsModalDialogList; + + // Utility function to get the dialog state for a dialog. + WebContentsModalDialogList::iterator FindDialogState( + NativeWebContentsModalDialog dialog); // Blocks/unblocks interaction with renderer process. void BlockWebContentsInteraction(bool blocked); @@ -92,6 +108,7 @@ class WebContentsModalDialogManager const content::FrameNavigateParams& params) OVERRIDE; virtual void DidGetIgnoredUIEvent() OVERRIDE; virtual void WebContentsDestroyed(content::WebContents* tab) OVERRIDE; + virtual void DidAttachInterstitialPage() OVERRIDE; // Delegate for notifying our owner about stuff. Not owned by us. WebContentsModalDialogManagerDelegate* delegate_; diff --git a/chromium/components/web_modal/web_contents_modal_dialog_manager_unittest.cc b/chromium/components/web_modal/web_contents_modal_dialog_manager_unittest.cc index 1268896d6af..bb866916264 100644 --- a/chromium/components/web_modal/web_contents_modal_dialog_manager_unittest.cc +++ b/chromium/components/web_modal/web_contents_modal_dialog_manager_unittest.cc @@ -2,80 +2,340 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include <map> + +#include "base/memory/scoped_ptr.h" #include "components/web_modal/native_web_contents_modal_dialog_manager.h" #include "components/web_modal/web_contents_modal_dialog_manager.h" -#include "content/public/browser/browser_thread.h" +#include "components/web_modal/web_contents_modal_dialog_manager_delegate.h" +#include "content/public/browser/notification_details.h" +#include "content/public/browser/notification_service.h" +#include "content/public/browser/notification_source.h" +#include "content/public/browser/notification_types.h" #include "content/public/test/test_renderer_host.h" #include "testing/gtest/include/gtest/gtest.h" -using content::BrowserThread; - namespace web_modal { -class WebContentsModalDialogManagerTest - : public content::RenderViewHostTestHarness { - public: - virtual void SetUp() { - content::RenderViewHostTestHarness::SetUp(); - WebContentsModalDialogManager::CreateForWebContents(web_contents()); - } -}; - -class NativeWebContentsModalDialogManagerCloseTest +class TestNativeWebContentsModalDialogManager : public NativeWebContentsModalDialogManager { public: - NativeWebContentsModalDialogManagerCloseTest( + enum DialogState { + UNKNOWN, + NOT_SHOWN, + SHOWN, + HIDDEN, + CLOSED + }; + + explicit TestNativeWebContentsModalDialogManager( NativeWebContentsModalDialogManagerDelegate* delegate) : delegate_(delegate) {} virtual void ManageDialog(NativeWebContentsModalDialog dialog) OVERRIDE { + dialog_state_[dialog] = NOT_SHOWN; } virtual void ShowDialog(NativeWebContentsModalDialog dialog) OVERRIDE { + dialog_state_[dialog] = SHOWN; } virtual void HideDialog(NativeWebContentsModalDialog dialog) OVERRIDE { + dialog_state_[dialog] = HIDDEN; } virtual void CloseDialog(NativeWebContentsModalDialog dialog) OVERRIDE { delegate_->WillClose(dialog); - close_count++; + dialog_state_[dialog] = CLOSED; } virtual void FocusDialog(NativeWebContentsModalDialog dialog) OVERRIDE { } virtual void PulseDialog(NativeWebContentsModalDialog dialog) OVERRIDE { } + virtual void HostChanged(WebContentsModalDialogHost* new_host) OVERRIDE { + } + + int GetCloseCount() const { + int count = 0; + for (DialogStateMap::const_iterator it = dialog_state_.begin(); + it != dialog_state_.end(); ++it) { + if (it->second == CLOSED) + count++; + } + return count; + } + + DialogState GetDialogState(NativeWebContentsModalDialog dialog) const { + DialogStateMap::const_iterator loc = dialog_state_.find(dialog); + return loc == dialog_state_.end() ? UNKNOWN : loc->second; + } + + private: + typedef std::map<NativeWebContentsModalDialog, DialogState> DialogStateMap; - int close_count; NativeWebContentsModalDialogManagerDelegate* delegate_; + DialogStateMap dialog_state_; + + DISALLOW_COPY_AND_ASSIGN(TestNativeWebContentsModalDialogManager); +}; + +class TestWebContentsModalDialogManagerDelegate + : public WebContentsModalDialogManagerDelegate { + public: + TestWebContentsModalDialogManagerDelegate() + : web_contents_visible_(true), + web_contents_blocked_(false) { + } + + // WebContentsModalDialogManagerDelegate overrides + virtual void SetWebContentsBlocked(content::WebContents* web_contents, + bool blocked) OVERRIDE { + web_contents_blocked_ = blocked; + } + + virtual WebContentsModalDialogHost* GetWebContentsModalDialogHost() OVERRIDE { + return NULL; + } + + virtual bool IsWebContentsVisible( + content::WebContents* web_contents) OVERRIDE { + return web_contents_visible_; + } + + void set_web_contents_visible(bool visible) { + web_contents_visible_ = visible; + } + + bool web_contents_blocked() const { return web_contents_blocked_; } + + private: + bool web_contents_visible_; + bool web_contents_blocked_; + + DISALLOW_COPY_AND_ASSIGN(TestWebContentsModalDialogManagerDelegate); }; -NativeWebContentsModalDialogManager* WebContentsModalDialogManager:: -CreateNativeManager( +class WebContentsModalDialogManagerTest + : public content::RenderViewHostTestHarness { + public: + WebContentsModalDialogManagerTest() + : next_dialog_id(1), + manager(NULL), + native_manager(NULL) { + } + + virtual void SetUp() { + content::RenderViewHostTestHarness::SetUp(); + + delegate.reset(new TestWebContentsModalDialogManagerDelegate); + WebContentsModalDialogManager::CreateForWebContents(web_contents()); + manager = WebContentsModalDialogManager::FromWebContents(web_contents()); + manager->SetDelegate(delegate.get()); + test_api.reset(new WebContentsModalDialogManager::TestApi(manager)); + native_manager = new TestNativeWebContentsModalDialogManager(manager); + + // |manager| owns |native_manager| as a result. + test_api->ResetNativeManager(native_manager); + } + + virtual void TearDown() { + test_api.reset(); + content::RenderViewHostTestHarness::TearDown(); + } + + protected: + NativeWebContentsModalDialog MakeFakeDialog() { + // WebContentsModalDialogManager treats the NativeWebContentsModalDialog as + // an opaque type, so creating fake NativeWebContentsModalDialogs using + // reinterpret_cast is valid. + return reinterpret_cast<NativeWebContentsModalDialog>(next_dialog_id++); + } + + int next_dialog_id; + scoped_ptr<TestWebContentsModalDialogManagerDelegate> delegate; + WebContentsModalDialogManager* manager; + scoped_ptr<WebContentsModalDialogManager::TestApi> test_api; + TestNativeWebContentsModalDialogManager* native_manager; + + DISALLOW_COPY_AND_ASSIGN(WebContentsModalDialogManagerTest); +}; + +NativeWebContentsModalDialogManager* +WebContentsModalDialogManager::CreateNativeManager( NativeWebContentsModalDialogManagerDelegate* native_delegate) { - return new NativeWebContentsModalDialogManagerCloseTest(native_delegate); + return new TestNativeWebContentsModalDialogManager(native_delegate); +} + +// Test that the dialog is shown immediately when the delegate indicates the web +// contents is visible. +TEST_F(WebContentsModalDialogManagerTest, WebContentsVisible) { + // Dialog should be shown while WebContents is visible. + const NativeWebContentsModalDialog dialog1 = MakeFakeDialog(); + + manager->ShowDialog(dialog1); + + EXPECT_EQ(TestNativeWebContentsModalDialogManager::SHOWN, + native_manager->GetDialogState(dialog1)); + EXPECT_TRUE(manager->IsDialogActive()); + EXPECT_TRUE(delegate->web_contents_blocked()); } -TEST_F(WebContentsModalDialogManagerTest, WebContentsModalDialogs) { - WebContentsModalDialogManager* web_contents_modal_dialog_manager = - WebContentsModalDialogManager::FromWebContents(web_contents()); - WebContentsModalDialogManager::TestApi test_api( - web_contents_modal_dialog_manager); +// Test that the dialog is not shown immediately when the delegate indicates the +// web contents is not visible. +TEST_F(WebContentsModalDialogManagerTest, WebContentsNotVisible) { + // Dialog should not be shown while WebContents is not visible. + delegate->set_web_contents_visible(false); + + const NativeWebContentsModalDialog dialog1 = MakeFakeDialog(); + + manager->ShowDialog(dialog1); + + EXPECT_EQ(TestNativeWebContentsModalDialogManager::NOT_SHOWN, + native_manager->GetDialogState(dialog1)); + EXPECT_TRUE(manager->IsDialogActive()); + EXPECT_TRUE(delegate->web_contents_blocked()); +} + +// Test that only the first of multiple dialogs is shown. +TEST_F(WebContentsModalDialogManagerTest, ShowDialogs) { + const NativeWebContentsModalDialog dialog1 = MakeFakeDialog(); + const NativeWebContentsModalDialog dialog2 = MakeFakeDialog(); + const NativeWebContentsModalDialog dialog3 = MakeFakeDialog(); + + manager->ShowDialog(dialog1); + manager->ShowDialog(dialog2); + manager->ShowDialog(dialog3); + + EXPECT_TRUE(delegate->web_contents_blocked()); + EXPECT_EQ(TestNativeWebContentsModalDialogManager::SHOWN, + native_manager->GetDialogState(dialog1)); + EXPECT_EQ(TestNativeWebContentsModalDialogManager::NOT_SHOWN, + native_manager->GetDialogState(dialog2)); + EXPECT_EQ(TestNativeWebContentsModalDialogManager::NOT_SHOWN, + native_manager->GetDialogState(dialog3)); +} + +// Test that the dialog is shown/hidden on +// NOTIFICATION_WEB_CONTENTS_VISIBILITY_CHANGED. +TEST_F(WebContentsModalDialogManagerTest, VisibilityObservation) { + const NativeWebContentsModalDialog dialog1 = MakeFakeDialog(); + bool web_contents_visible = true; + + manager->ShowDialog(dialog1); + + EXPECT_TRUE(manager->IsDialogActive()); + EXPECT_TRUE(delegate->web_contents_blocked()); + EXPECT_EQ(TestNativeWebContentsModalDialogManager::SHOWN, + native_manager->GetDialogState(dialog1)); + + web_contents_visible = false; + manager->Observe(content::NOTIFICATION_WEB_CONTENTS_VISIBILITY_CHANGED, + content::NotificationService::AllSources(), + content::Details<bool>(&web_contents_visible)); - NativeWebContentsModalDialogManagerCloseTest* native_manager = - new NativeWebContentsModalDialogManagerCloseTest( - web_contents_modal_dialog_manager); - native_manager->close_count = 0; + EXPECT_TRUE(manager->IsDialogActive()); + EXPECT_TRUE(delegate->web_contents_blocked()); + EXPECT_EQ(TestNativeWebContentsModalDialogManager::HIDDEN, + native_manager->GetDialogState(dialog1)); - test_api.ResetNativeManager(native_manager); + web_contents_visible = true; + manager->Observe(content::NOTIFICATION_WEB_CONTENTS_VISIBILITY_CHANGED, + content::NotificationService::AllSources(), + content::Details<bool>(&web_contents_visible)); + EXPECT_TRUE(manager->IsDialogActive()); + EXPECT_TRUE(delegate->web_contents_blocked()); + EXPECT_EQ(TestNativeWebContentsModalDialogManager::SHOWN, + native_manager->GetDialogState(dialog1)); +} + +// Test that attaching an interstitial WebUI page closes dialogs configured to +// close on interstitial WebUI. +TEST_F(WebContentsModalDialogManagerTest, InterstitialWebUI) { + const NativeWebContentsModalDialog dialog1 = MakeFakeDialog(); + const NativeWebContentsModalDialog dialog2 = MakeFakeDialog(); + const NativeWebContentsModalDialog dialog3 = MakeFakeDialog(); + + manager->ShowDialog(dialog1); + manager->ShowDialog(dialog2); + manager->ShowDialog(dialog3); + + manager->SetCloseOnInterstitialWebUI(dialog1, true); + manager->SetCloseOnInterstitialWebUI(dialog3, true); + + test_api->DidAttachInterstitialPage(); + EXPECT_EQ(TestNativeWebContentsModalDialogManager::CLOSED, + native_manager->GetDialogState(dialog1)); + EXPECT_EQ(TestNativeWebContentsModalDialogManager::SHOWN, + native_manager->GetDialogState(dialog2)); + EXPECT_EQ(TestNativeWebContentsModalDialogManager::CLOSED, + native_manager->GetDialogState(dialog3)); +} + + +// Test that the first dialog is always shown, regardless of the order in which +// dialogs are closed. +TEST_F(WebContentsModalDialogManagerTest, CloseDialogs) { + // The front dialog is always shown regardless of dialog close order. + const NativeWebContentsModalDialog dialog1 = MakeFakeDialog(); + const NativeWebContentsModalDialog dialog2 = MakeFakeDialog(); + const NativeWebContentsModalDialog dialog3 = MakeFakeDialog(); + const NativeWebContentsModalDialog dialog4 = MakeFakeDialog(); + + manager->ShowDialog(dialog1); + manager->ShowDialog(dialog2); + manager->ShowDialog(dialog3); + manager->ShowDialog(dialog4); + + native_manager->CloseDialog(dialog1); + + EXPECT_TRUE(manager->IsDialogActive()); + EXPECT_TRUE(delegate->web_contents_blocked()); + EXPECT_EQ(TestNativeWebContentsModalDialogManager::CLOSED, + native_manager->GetDialogState(dialog1)); + EXPECT_EQ(TestNativeWebContentsModalDialogManager::SHOWN, + native_manager->GetDialogState(dialog2)); + EXPECT_EQ(TestNativeWebContentsModalDialogManager::NOT_SHOWN, + native_manager->GetDialogState(dialog3)); + EXPECT_EQ(TestNativeWebContentsModalDialogManager::NOT_SHOWN, + native_manager->GetDialogState(dialog4)); + + native_manager->CloseDialog(dialog3); + + EXPECT_TRUE(manager->IsDialogActive()); + EXPECT_TRUE(delegate->web_contents_blocked()); + EXPECT_EQ(TestNativeWebContentsModalDialogManager::SHOWN, + native_manager->GetDialogState(dialog2)); + EXPECT_EQ(TestNativeWebContentsModalDialogManager::CLOSED, + native_manager->GetDialogState(dialog3)); + EXPECT_EQ(TestNativeWebContentsModalDialogManager::NOT_SHOWN, + native_manager->GetDialogState(dialog4)); + + native_manager->CloseDialog(dialog2); + + EXPECT_TRUE(manager->IsDialogActive()); + EXPECT_TRUE(delegate->web_contents_blocked()); + EXPECT_EQ(TestNativeWebContentsModalDialogManager::CLOSED, + native_manager->GetDialogState(dialog2)); + EXPECT_EQ(TestNativeWebContentsModalDialogManager::SHOWN, + native_manager->GetDialogState(dialog4)); + + native_manager->CloseDialog(dialog4); + + EXPECT_FALSE(manager->IsDialogActive()); + EXPECT_FALSE(delegate->web_contents_blocked()); + EXPECT_EQ(TestNativeWebContentsModalDialogManager::CLOSED, + native_manager->GetDialogState(dialog4)); +} + +// Test that CloseAllDialogs does what it says. +TEST_F(WebContentsModalDialogManagerTest, CloseAllDialogs) { const int kWindowCount = 4; for (int i = 0; i < kWindowCount; i++) - // WebContentsModalDialogManager treats the NativeWebContentsModalDialog as - // an opaque type, so creating fake NativeWebContentsModalDialogs using - // reinterpret_cast is valid. - web_contents_modal_dialog_manager->ShowDialog( - reinterpret_cast<NativeWebContentsModalDialog>(i)); - EXPECT_EQ(native_manager->close_count, 0); + manager->ShowDialog(MakeFakeDialog()); + + EXPECT_EQ(0, native_manager->GetCloseCount()); - test_api.CloseAllDialogs(); - EXPECT_EQ(native_manager->close_count, kWindowCount); + test_api->CloseAllDialogs(); + EXPECT_FALSE(delegate->web_contents_blocked()); + EXPECT_FALSE(manager->IsDialogActive()); + EXPECT_EQ(kWindowCount, native_manager->GetCloseCount()); } } // namespace web_modal diff --git a/chromium/components/webdata/common/web_database.cc b/chromium/components/webdata/common/web_database.cc index e4e44ef1e03..161fb80bae4 100644 --- a/chromium/components/webdata/common/web_database.cc +++ b/chromium/components/webdata/common/web_database.cc @@ -14,7 +14,7 @@ // corresponding changes must happen in the unit tests, and new migration test // added. See |WebDatabaseMigrationTest::kCurrentTestedVersionNumber|. // static -const int WebDatabase::kCurrentVersionNumber = 52; +const int WebDatabase::kCurrentVersionNumber = 53; namespace { diff --git a/chromium/components/webdata/common/web_database_migration_unittest.cc b/chromium/components/webdata/common/web_database_migration_unittest.cc index 5e6ef6e9dc3..57ef02dc175 100644 --- a/chromium/components/webdata/common/web_database_migration_unittest.cc +++ b/chromium/components/webdata/common/web_database_migration_unittest.cc @@ -220,7 +220,7 @@ class WebDatabaseMigrationTest : public testing::Test { source_path = source_path.AppendASCII("web_database"); source_path = source_path.Append(file); return base::PathExists(source_path) && - file_util::ReadFileToString(source_path, contents); + base::ReadFileToString(source_path, contents); } static int VersionFromConnection(sql::Connection* connection) { @@ -247,7 +247,7 @@ class WebDatabaseMigrationTest : public testing::Test { DISALLOW_COPY_AND_ASSIGN(WebDatabaseMigrationTest); }; -const int WebDatabaseMigrationTest::kCurrentTestedVersionNumber = 52; +const int WebDatabaseMigrationTest::kCurrentTestedVersionNumber = 53; void WebDatabaseMigrationTest::LoadDatabase( const base::FilePath::StringType& file) { @@ -2029,8 +2029,9 @@ TEST_F(WebDatabaseMigrationTest, MigrateVersion49ToCurrent) { } // Tests that the columns |image_url|, |search_url_post_params|, -// |suggest_url_post_params|, |instant_url_post_params|, |image_url_post_params| -// are added to the keyword table schema for a version 52 database. +// |suggest_url_post_params|, |instant_url_post_params|, and +// |image_url_post_params| are added to the keyword table schema for a version +// 50 database. TEST_F(WebDatabaseMigrationTest, MigrateVersion50ToCurrent) { ASSERT_NO_FATAL_FAILURE( LoadDatabase(FILE_PATH_LITERAL("version_50.sql"))); @@ -2080,3 +2081,39 @@ TEST_F(WebDatabaseMigrationTest, MigrateVersion50ToCurrent) { "image_url_post_params")); } } + +// Tests that the column |new_tab_url| is added to the keyword table schema for +// a version 52 database. +TEST_F(WebDatabaseMigrationTest, MigrateVersion52ToCurrent) { + ASSERT_NO_FATAL_FAILURE( + LoadDatabase(FILE_PATH_LITERAL("version_52.sql"))); + + // Verify pre-conditions. These are expectations for version 52 of the + // database. + { + sql::Connection connection; + ASSERT_TRUE(connection.Open(GetDatabasePath())); + ASSERT_TRUE(sql::MetaTable::DoesTableExist(&connection)); + + sql::MetaTable meta_table; + ASSERT_TRUE(meta_table.Init(&connection, 52, 52)); + + ASSERT_FALSE(connection.DoesColumnExist("keywords", "new_tab_url")); + } + + DoMigration(); + + // Verify post-conditions. These are expectations for current version of the + // database. + { + sql::Connection connection; + ASSERT_TRUE(connection.Open(GetDatabasePath())); + ASSERT_TRUE(sql::MetaTable::DoesTableExist(&connection)); + + // Check version. + EXPECT_EQ(kCurrentTestedVersionNumber, VersionFromConnection(&connection)); + + // New columns should have been created. + EXPECT_TRUE(connection.DoesColumnExist("keywords", "new_tab_url")); + } +} |