summaryrefslogtreecommitdiffstats
path: root/chrome/browser/ui/search_engines
diff options
context:
space:
mode:
authoravi@chromium.org <avi@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-02-23 19:45:42 +0000
committeravi@chromium.org <avi@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-02-23 19:45:42 +0000
commit62f7777cb7c9dba41286c17bab10e30152a5a841 (patch)
tree9eebf968b2c840b1536d4321ed0f086d96135eaf /chrome/browser/ui/search_engines
parent4d8af92fa3a43f203679bbbd37968c127d056a4d (diff)
downloadchromium_src-62f7777cb7c9dba41286c17bab10e30152a5a841.zip
chromium_src-62f7777cb7c9dba41286c17bab10e30152a5a841.tar.gz
chromium_src-62f7777cb7c9dba41286c17bab10e30152a5a841.tar.bz2
Move the UI portions of search engine management to ui/search_engines.
BUG=none TEST=no visible change Review URL: http://codereview.chromium.org/6573008 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@75774 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/ui/search_engines')
-rw-r--r--chrome/browser/ui/search_engines/edit_search_engine_controller.cc149
-rw-r--r--chrome/browser/ui/search_engines/edit_search_engine_controller.h91
-rw-r--r--chrome/browser/ui/search_engines/keyword_editor_controller.cc114
-rw-r--r--chrome/browser/ui/search_engines/keyword_editor_controller.h80
-rw-r--r--chrome/browser/ui/search_engines/keyword_editor_controller_unittest.cc258
-rw-r--r--chrome/browser/ui/search_engines/template_url_table_model.cc381
-rw-r--r--chrome/browser/ui/search_engines/template_url_table_model.h117
7 files changed, 1190 insertions, 0 deletions
diff --git a/chrome/browser/ui/search_engines/edit_search_engine_controller.cc b/chrome/browser/ui/search_engines/edit_search_engine_controller.cc
new file mode 100644
index 0000000..bd397f2
--- /dev/null
+++ b/chrome/browser/ui/search_engines/edit_search_engine_controller.cc
@@ -0,0 +1,149 @@
+// 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.
+
+#include "chrome/browser/ui/search_engines/edit_search_engine_controller.h"
+
+#include "base/string_util.h"
+#include "base/utf_string_conversions.h"
+#include "chrome/browser/metrics/user_metrics.h"
+#include "chrome/browser/net/url_fixer_upper.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/search_engines/template_url.h"
+#include "chrome/browser/search_engines/template_url_model.h"
+#include "googleurl/src/gurl.h"
+
+EditSearchEngineController::EditSearchEngineController(
+ const TemplateURL* template_url,
+ EditSearchEngineControllerDelegate* edit_keyword_delegate,
+ Profile* profile)
+ : template_url_(template_url),
+ edit_keyword_delegate_(edit_keyword_delegate),
+ profile_(profile) {
+ DCHECK(profile_);
+}
+
+bool EditSearchEngineController::IsTitleValid(
+ const string16& title_input) const {
+ return !CollapseWhitespace(title_input, true).empty();
+}
+
+bool EditSearchEngineController::IsURLValid(
+ const std::string& url_input) const {
+ std::string url = GetFixedUpURL(url_input);
+ if (url.empty())
+ return false;
+
+ // Use TemplateURLRef to extract the search placeholder.
+ TemplateURLRef template_ref(url, 0, 0);
+ if (!template_ref.IsValid())
+ return false;
+
+ if (!template_ref.SupportsReplacement()) {
+ // If this is the default search engine, there must be a search term
+ // placeholder.
+ if (template_url_ ==
+ profile_->GetTemplateURLModel()->GetDefaultSearchProvider())
+ return false;
+ return GURL(url).is_valid();
+ }
+
+ // If the url has a search term, replace it with a random string and make
+ // sure the resulting URL is valid. We don't check the validity of the url
+ // with the search term as that is not necessarily valid.
+ return GURL(template_ref.ReplaceSearchTerms(TemplateURL(), ASCIIToUTF16("a"),
+ TemplateURLRef::NO_SUGGESTIONS_AVAILABLE, string16())).is_valid();
+}
+
+bool EditSearchEngineController::IsKeywordValid(
+ const string16& keyword_input) const {
+ string16 keyword_input_trimmed(CollapseWhitespace(keyword_input, true));
+ if (keyword_input_trimmed.empty())
+ return false; // Do not allow empty keyword.
+ const TemplateURL* turl_with_keyword =
+ profile_->GetTemplateURLModel()->GetTemplateURLForKeyword(
+ keyword_input_trimmed);
+ return (turl_with_keyword == NULL || turl_with_keyword == template_url_);
+}
+
+void EditSearchEngineController::AcceptAddOrEdit(
+ const string16& title_input,
+ const string16& keyword_input,
+ const std::string& url_input) {
+ std::string url_string = GetFixedUpURL(url_input);
+ DCHECK(!url_string.empty());
+
+ const TemplateURL* existing =
+ profile_->GetTemplateURLModel()->GetTemplateURLForKeyword(
+ keyword_input);
+ if (existing &&
+ (!edit_keyword_delegate_ || existing != template_url_)) {
+ // An entry may have been added with the same keyword string while the
+ // user edited the dialog, either automatically or by the user (if we're
+ // confirming a JS addition, they could have the Options dialog open at the
+ // same time). If so, just ignore this add.
+ // TODO(pamg): Really, we should modify the entry so this later one
+ // overwrites it. But we don't expect this case to be common.
+ CleanUpCancelledAdd();
+ return;
+ }
+
+ if (!edit_keyword_delegate_) {
+ // Confiming an entry we got from JS. We have a template_url_, but it
+ // hasn't yet been added to the model.
+ DCHECK(template_url_);
+ // const_cast is ugly, but this is the same thing the TemplateURLModel
+ // does in a similar situation (updating an existing TemplateURL with
+ // data from a new one).
+ TemplateURL* modifiable_url = const_cast<TemplateURL*>(template_url_);
+ modifiable_url->set_short_name(title_input);
+ modifiable_url->set_keyword(keyword_input);
+ modifiable_url->SetURL(url_string, 0, 0);
+ // TemplateURLModel takes ownership of template_url_.
+ profile_->GetTemplateURLModel()->Add(modifiable_url);
+ UserMetrics::RecordAction(UserMetricsAction("KeywordEditor_AddKeywordJS"),
+ profile_);
+ } else {
+ // Adding or modifying an entry via the Delegate.
+ edit_keyword_delegate_->OnEditedKeyword(template_url_,
+ title_input,
+ keyword_input,
+ url_string);
+ }
+}
+
+void EditSearchEngineController::CleanUpCancelledAdd() {
+ if (!edit_keyword_delegate_ && template_url_) {
+ // When we have no Delegate, we know that the template_url_ hasn't yet been
+ // added to the model, so we need to clean it up.
+ delete template_url_;
+ template_url_ = NULL;
+ }
+}
+
+std::string EditSearchEngineController::GetFixedUpURL(
+ const std::string& url_input) const {
+ std::string url;
+ TrimWhitespace(TemplateURLRef::DisplayURLToURLRef(UTF8ToUTF16(url_input)),
+ TRIM_ALL, &url);
+ if (url.empty())
+ return url;
+
+ // Parse the string as a URL to determine the scheme. If we need to, add the
+ // scheme. As the scheme may be expanded (as happens with {google:baseURL})
+ // we need to replace the search terms before testing for the scheme.
+ TemplateURL t_url;
+ t_url.SetURL(url, 0, 0);
+ std::string expanded_url =
+ t_url.url()->ReplaceSearchTerms(t_url, ASCIIToUTF16("x"), 0, string16());
+ url_parse::Parsed parts;
+ std::string scheme(
+ URLFixerUpper::SegmentURL(expanded_url, &parts));
+ if (!parts.scheme.is_valid()) {
+ scheme.append("://");
+ url.insert(0, scheme);
+ }
+
+ return url;
+}
+
diff --git a/chrome/browser/ui/search_engines/edit_search_engine_controller.h b/chrome/browser/ui/search_engines/edit_search_engine_controller.h
new file mode 100644
index 0000000..5d57c1d
--- /dev/null
+++ b/chrome/browser/ui/search_engines/edit_search_engine_controller.h
@@ -0,0 +1,91 @@
+// 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.
+
+#ifndef CHROME_BROWSER_UI_SEARCH_ENGINES_EDIT_SEARCH_ENGINE_CONTROLLER_H_
+#define CHROME_BROWSER_UI_SEARCH_ENGINES_EDIT_SEARCH_ENGINE_CONTROLLER_H_
+#pragma once
+
+#include <string>
+
+#include "base/string16.h"
+#include "ui/gfx/native_widget_types.h"
+
+class Profile;
+class TemplateURL;
+
+class EditSearchEngineControllerDelegate {
+ public:
+ // Invoked from the EditSearchEngineController when the user accepts the
+ // edits. NOTE: |template_url| is the value supplied to
+ // EditSearchEngineController's constructor, and may be NULL. A NULL value
+ // indicates a new TemplateURL should be created rather than modifying an
+ // existing TemplateURL.
+ virtual void OnEditedKeyword(const TemplateURL* template_url,
+ const string16& title,
+ const string16& keyword,
+ const std::string& url) = 0;
+
+ protected:
+ virtual ~EditSearchEngineControllerDelegate() {}
+};
+
+// EditSearchEngineController provides the core platform independent logic
+// for the Edit Search Engine dialog.
+class EditSearchEngineController {
+ public:
+ // The |template_url| and/or |edit_keyword_delegate| may be NULL.
+ EditSearchEngineController(
+ const TemplateURL* template_url,
+ EditSearchEngineControllerDelegate* edit_keyword_delegate,
+ Profile* profile);
+ ~EditSearchEngineController() {}
+
+ // Returns true if the value of |title_input| is a valid search engine name.
+ bool IsTitleValid(const string16& title_input) const;
+
+ // Returns true if the value of |url_input| represents a valid search engine
+ // URL. The URL is valid if it contains no search terms and is a valid
+ // url, or if it contains a search term and replacing that search term with a
+ // character results in a valid url.
+ bool IsURLValid(const std::string& url_input) const;
+
+ // Returns true if the value of |keyword_input| represents a valid keyword.
+ // The keyword is valid if it is non-empty and does not conflict with an
+ // existing entry. NOTE: this is just the keyword, not the title and url.
+ bool IsKeywordValid(const string16& keyword_input) const;
+
+ // Completes the add or edit of a search engine.
+ void AcceptAddOrEdit(const string16& title_input,
+ const string16& keyword_input,
+ const std::string& url_input);
+
+ // Deletes an unused TemplateURL, if its add was cancelled and it's not
+ // already owned by the TemplateURLModel.
+ void CleanUpCancelledAdd();
+
+ // Accessors.
+ const TemplateURL* template_url() const { return template_url_; }
+ const Profile* profile() const { return profile_; }
+
+ private:
+ // Fixes up and returns the URL the user has input. The returned URL is
+ // suitable for use by TemplateURL.
+ std::string GetFixedUpURL(const std::string& url_input) const;
+
+ // The TemplateURL we're displaying information for. It may be NULL. If we
+ // have a keyword_editor_view, we assume that this TemplateURL is already in
+ // the TemplateURLModel; if not, we assume it isn't.
+ const TemplateURL* template_url_;
+
+ // We may have been created by this, in which case we will call back to it on
+ // success to add/modify the entry. May be NULL.
+ EditSearchEngineControllerDelegate* edit_keyword_delegate_;
+
+ // Profile whose TemplateURLModel we're modifying.
+ Profile* profile_;
+
+ DISALLOW_COPY_AND_ASSIGN(EditSearchEngineController);
+};
+
+#endif // CHROME_BROWSER_UI_SEARCH_ENGINES_EDIT_SEARCH_ENGINE_CONTROLLER_H_
diff --git a/chrome/browser/ui/search_engines/keyword_editor_controller.cc b/chrome/browser/ui/search_engines/keyword_editor_controller.cc
new file mode 100644
index 0000000..0c98b0f
--- /dev/null
+++ b/chrome/browser/ui/search_engines/keyword_editor_controller.cc
@@ -0,0 +1,114 @@
+// 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.
+
+#include "chrome/browser/ui/search_engines/keyword_editor_controller.h"
+
+#include "base/utf_string_conversions.h"
+#include "chrome/browser/metrics/user_metrics.h"
+#include "chrome/browser/prefs/pref_service.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/search_engines/template_url.h"
+#include "chrome/browser/search_engines/template_url_model.h"
+#include "chrome/browser/ui/search_engines/template_url_table_model.h"
+#include "chrome/common/pref_names.h"
+
+KeywordEditorController::KeywordEditorController(Profile* profile)
+ : profile_(profile) {
+ table_model_.reset(new TemplateURLTableModel(profile->GetTemplateURLModel()));
+}
+
+KeywordEditorController::~KeywordEditorController() {
+}
+
+// static
+// TODO(rsesek): Other platforms besides Mac should remember window
+// placement. http://crbug.com/22269
+void KeywordEditorController::RegisterPrefs(PrefService* prefs) {
+ prefs->RegisterDictionaryPref(prefs::kKeywordEditorWindowPlacement);
+}
+
+int KeywordEditorController::AddTemplateURL(const string16& title,
+ const string16& keyword,
+ const std::string& url) {
+ DCHECK(!url.empty());
+
+ UserMetrics::RecordAction(UserMetricsAction("KeywordEditor_AddKeyword"),
+ profile_);
+
+ TemplateURL* template_url = new TemplateURL();
+ template_url->set_short_name(title);
+ template_url->set_keyword(keyword);
+ template_url->SetURL(url, 0, 0);
+
+ // There's a bug (1090726) in TableView with groups enabled such that newly
+ // added items in groups ALWAYS appear at the end, regardless of the index
+ // passed in. Worse yet, the selected rows get messed up when this happens
+ // causing other problems. As a work around we always add the item to the end
+ // of the list.
+ const int new_index = table_model_->RowCount();
+ table_model_->Add(new_index, template_url);
+
+ return new_index;
+}
+
+void KeywordEditorController::ModifyTemplateURL(const TemplateURL* template_url,
+ const string16& title,
+ const string16& keyword,
+ const std::string& url) {
+ const int index = table_model_->IndexOfTemplateURL(template_url);
+ if (index == -1) {
+ // Will happen if url was deleted out from under us while the user was
+ // editing it.
+ return;
+ }
+
+ // Don't do anything if the entry didn't change.
+ if (template_url->short_name() == title &&
+ template_url->keyword() == keyword &&
+ ((url.empty() && !template_url->url()) ||
+ (!url.empty() && template_url->url() &&
+ template_url->url()->url() == url))) {
+ return;
+ }
+
+ table_model_->ModifyTemplateURL(index, title, keyword, url);
+
+ UserMetrics::RecordAction(UserMetricsAction("KeywordEditor_ModifiedKeyword"),
+ profile_);
+}
+
+bool KeywordEditorController::CanEdit(const TemplateURL* url) const {
+ return !url_model()->is_default_search_managed() ||
+ url != url_model()->GetDefaultSearchProvider();
+}
+
+bool KeywordEditorController::CanMakeDefault(const TemplateURL* url) const {
+ return url_model()->CanMakeDefault(url);
+}
+
+bool KeywordEditorController::CanRemove(const TemplateURL* url) const {
+ return url != url_model()->GetDefaultSearchProvider();
+}
+
+void KeywordEditorController::RemoveTemplateURL(int index) {
+ table_model_->Remove(index);
+ UserMetrics::RecordAction(UserMetricsAction("KeywordEditor_RemoveKeyword"),
+ profile_);
+}
+
+int KeywordEditorController::MakeDefaultTemplateURL(int index) {
+ return table_model_->MakeDefaultTemplateURL(index);
+}
+
+bool KeywordEditorController::loaded() const {
+ return url_model()->loaded();
+}
+
+const TemplateURL* KeywordEditorController::GetTemplateURL(int index) const {
+ return &table_model_->GetTemplateURL(index);
+}
+
+TemplateURLModel* KeywordEditorController::url_model() const {
+ return table_model_->template_url_model();
+}
diff --git a/chrome/browser/ui/search_engines/keyword_editor_controller.h b/chrome/browser/ui/search_engines/keyword_editor_controller.h
new file mode 100644
index 0000000..6b8ff74
--- /dev/null
+++ b/chrome/browser/ui/search_engines/keyword_editor_controller.h
@@ -0,0 +1,80 @@
+// 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.
+
+#ifndef CHROME_BROWSER_UI_SEARCH_ENGINES_KEYWORD_EDITOR_CONTROLLER_H_
+#define CHROME_BROWSER_UI_SEARCH_ENGINES_KEYWORD_EDITOR_CONTROLLER_H_
+#pragma once
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/scoped_ptr.h"
+#include "base/string16.h"
+
+class PrefService;
+class Profile;
+class TemplateURL;
+class TemplateURLModel;
+class TemplateURLTableModel;
+
+class KeywordEditorController {
+ public:
+ explicit KeywordEditorController(Profile* profile);
+ ~KeywordEditorController();
+
+ static void RegisterPrefs(PrefService* prefs);
+
+ // Invoked when the user succesfully fills out the add keyword dialog.
+ // Propagates the change to the TemplateURLModel and updates the table model.
+ // Returns the index of the added URL.
+ int AddTemplateURL(const string16& title,
+ const string16& keyword,
+ const std::string& url);
+
+ // Invoked when the user modifies a TemplateURL. Updates the TemplateURLModel
+ // and table model appropriately.
+ void ModifyTemplateURL(const TemplateURL* template_url,
+ const string16& title,
+ const string16& keyword,
+ const std::string& url);
+
+ // Return true if the given |url| can be edited.
+ bool CanEdit(const TemplateURL* url) const;
+
+ // Return true if the given |url| can be made the default.
+ bool CanMakeDefault(const TemplateURL* url) const;
+
+ // Return true if the given |url| can be removed.
+ bool CanRemove(const TemplateURL* url) const;
+
+ // Remove the TemplateURL at the specified index in the TableModel.
+ void RemoveTemplateURL(int index);
+
+ // Make the TemplateURL at the specified index (into the TableModel) the
+ // default search provider. Return the new index, or -1 if nothing was done.
+ int MakeDefaultTemplateURL(int index);
+
+ // Return true if the |url_model_| data is loaded.
+ bool loaded() const;
+
+ // Return the TemplateURL corresponding to the |index| in the model.
+ const TemplateURL* GetTemplateURL(int index) const;
+
+ TemplateURLTableModel* table_model() {
+ return table_model_.get();
+ }
+
+ TemplateURLModel* url_model() const;
+
+ private:
+ // The profile.
+ Profile* profile_;
+
+ // Model for the TableView.
+ scoped_ptr<TemplateURLTableModel> table_model_;
+
+ DISALLOW_COPY_AND_ASSIGN(KeywordEditorController);
+};
+
+#endif // CHROME_BROWSER_UI_SEARCH_ENGINES_KEYWORD_EDITOR_CONTROLLER_H_
diff --git a/chrome/browser/ui/search_engines/keyword_editor_controller_unittest.cc b/chrome/browser/ui/search_engines/keyword_editor_controller_unittest.cc
new file mode 100644
index 0000000..72b5bc1
--- /dev/null
+++ b/chrome/browser/ui/search_engines/keyword_editor_controller_unittest.cc
@@ -0,0 +1,258 @@
+// 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.
+
+#include "base/string16.h"
+#include "base/utf_string_conversions.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/search_engines/template_url.h"
+#include "chrome/browser/search_engines/template_url_model.h"
+#include "chrome/browser/ui/search_engines/keyword_editor_controller.h"
+#include "chrome/browser/ui/search_engines/template_url_table_model.h"
+#include "chrome/common/notification_details.h"
+#include "chrome/common/notification_source.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/test/testing_pref_service.h"
+#include "chrome/test/testing_profile.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/models/table_model_observer.h"
+
+static const string16 kA(ASCIIToUTF16("a"));
+static const string16 kA1(ASCIIToUTF16("a1"));
+static const string16 kB(ASCIIToUTF16("b"));
+static const string16 kB1(ASCIIToUTF16("b1"));
+
+// Base class for keyword editor tests. Creates a profile containing an
+// empty TemplateURLModel.
+class KeywordEditorControllerTest : public testing::Test,
+ public ui::TableModelObserver {
+ public:
+ // Initializes all of the state.
+ void Init(bool simulate_load_failure);
+
+ virtual void SetUp() {
+ Init(false);
+ }
+
+ virtual void OnModelChanged() {
+ model_changed_count_++;
+ }
+
+ virtual void OnItemsChanged(int start, int length) {
+ items_changed_count_++;
+ }
+
+ virtual void OnItemsAdded(int start, int length) {
+ added_count_++;
+ }
+
+ virtual void OnItemsRemoved(int start, int length) {
+ removed_count_++;
+ }
+
+ void VerifyChangeCount(int model_changed_count, int item_changed_count,
+ int added_count, int removed_count) {
+ ASSERT_EQ(model_changed_count, model_changed_count_);
+ ASSERT_EQ(item_changed_count, items_changed_count_);
+ ASSERT_EQ(added_count, added_count_);
+ ASSERT_EQ(removed_count, removed_count_);
+ ClearChangeCount();
+ }
+
+ void ClearChangeCount() {
+ model_changed_count_ = items_changed_count_ = added_count_ =
+ removed_count_ = 0;
+ }
+
+ void SimulateDefaultSearchIsManaged(const std::string& url) {
+ ASSERT_FALSE(url.empty());
+ TestingPrefService* service = profile_->GetTestingPrefService();
+ service->SetManagedPref(
+ prefs::kDefaultSearchProviderEnabled,
+ Value::CreateBooleanValue(true));
+ service->SetManagedPref(
+ prefs::kDefaultSearchProviderSearchURL,
+ Value::CreateStringValue(url));
+ service->SetManagedPref(
+ prefs::kDefaultSearchProviderName,
+ Value::CreateStringValue("managed"));
+ // Clear the IDs that are not specified via policy.
+ service->SetManagedPref(
+ prefs::kDefaultSearchProviderID, new StringValue(""));
+ service->SetManagedPref(
+ prefs::kDefaultSearchProviderPrepopulateID, new StringValue(""));
+ model_->Observe(
+ NotificationType::PREF_CHANGED,
+ Source<PrefService>(profile_->GetTestingPrefService()),
+ Details<std::string>(NULL));
+ }
+
+ TemplateURLTableModel* table_model() const {
+ return controller_->table_model();
+ }
+
+ protected:
+ MessageLoopForUI message_loop_;
+ scoped_ptr<TestingProfile> profile_;
+ scoped_ptr<KeywordEditorController> controller_;
+ TemplateURLModel* model_;
+
+ int model_changed_count_;
+ int items_changed_count_;
+ int added_count_;
+ int removed_count_;
+};
+
+void KeywordEditorControllerTest::Init(bool simulate_load_failure) {
+ ClearChangeCount();
+
+ // If init is called twice, make sure that the controller is destroyed before
+ // the profile is.
+ controller_.reset();
+ profile_.reset(new TestingProfile());
+ profile_->CreateTemplateURLModel();
+
+ model_ = profile_->GetTemplateURLModel();
+ if (simulate_load_failure)
+ model_->OnWebDataServiceRequestDone(NULL, NULL);
+
+ controller_.reset(new KeywordEditorController(profile_.get()));
+ controller_->table_model()->SetObserver(this);
+}
+
+// Tests adding a TemplateURL.
+TEST_F(KeywordEditorControllerTest, Add) {
+ controller_->AddTemplateURL(kA, kB, "http://c");
+
+ // Verify the observer was notified.
+ VerifyChangeCount(0, 0, 1, 0);
+ if (HasFatalFailure())
+ return;
+
+ // Verify the TableModel has the new data.
+ ASSERT_EQ(1, table_model()->RowCount());
+
+ // Verify the TemplateURLModel has the new entry.
+ ASSERT_EQ(1U, model_->GetTemplateURLs().size());
+
+ // Verify the entry is what we added.
+ const TemplateURL* turl = model_->GetTemplateURLs()[0];
+ EXPECT_EQ(ASCIIToUTF16("a"), turl->short_name());
+ EXPECT_EQ(ASCIIToUTF16("b"), turl->keyword());
+ ASSERT_TRUE(turl->url() != NULL);
+ EXPECT_EQ("http://c", turl->url()->url());
+}
+
+// Tests modifying a TemplateURL.
+TEST_F(KeywordEditorControllerTest, Modify) {
+ controller_->AddTemplateURL(kA, kB, "http://c");
+ ClearChangeCount();
+
+ // Modify the entry.
+ const TemplateURL* turl = model_->GetTemplateURLs()[0];
+ controller_->ModifyTemplateURL(turl, kA1, kB1, "http://c1");
+
+ // Make sure it was updated appropriately.
+ VerifyChangeCount(0, 1, 0, 0);
+ EXPECT_EQ(ASCIIToUTF16("a1"), turl->short_name());
+ EXPECT_EQ(ASCIIToUTF16("b1"), turl->keyword());
+ ASSERT_TRUE(turl->url() != NULL);
+ EXPECT_EQ("http://c1", turl->url()->url());
+}
+
+// Tests making a TemplateURL the default search provider.
+TEST_F(KeywordEditorControllerTest, MakeDefault) {
+ controller_->AddTemplateURL(kA, kB, "http://c{searchTerms}");
+ ClearChangeCount();
+
+ const TemplateURL* turl = model_->GetTemplateURLs()[0];
+ int new_default = controller_->MakeDefaultTemplateURL(0);
+ EXPECT_EQ(0, new_default);
+ // Making an item the default sends a handful of changes. Which are sent isn't
+ // important, what is important is 'something' is sent.
+ ASSERT_TRUE(items_changed_count_ > 0 || added_count_ > 0 ||
+ removed_count_ > 0);
+ ASSERT_TRUE(model_->GetDefaultSearchProvider() == turl);
+
+ // Making it default a second time should fail.
+ new_default = controller_->MakeDefaultTemplateURL(0);
+ EXPECT_EQ(-1, new_default);
+}
+
+// Tests that a TemplateURL can't be made the default if the default search
+// provider is managed via policy.
+TEST_F(KeywordEditorControllerTest, CannotSetDefaultWhileManaged) {
+ controller_->AddTemplateURL(kA, kB, "http://c{searchTerms}");
+ controller_->AddTemplateURL(kA1, kB1, "http://d{searchTerms}");
+ ClearChangeCount();
+
+ const TemplateURL* turl1 =
+ model_->GetTemplateURLForKeyword(ASCIIToUTF16("b"));
+ ASSERT_TRUE(turl1 != NULL);
+ const TemplateURL* turl2 =
+ model_->GetTemplateURLForKeyword(ASCIIToUTF16("b1"));
+ ASSERT_TRUE(turl2 != NULL);
+
+ EXPECT_TRUE(controller_->CanMakeDefault(turl1));
+ EXPECT_TRUE(controller_->CanMakeDefault(turl2));
+
+ SimulateDefaultSearchIsManaged(turl2->url()->url());
+ EXPECT_TRUE(model_->is_default_search_managed());
+
+ EXPECT_FALSE(controller_->CanMakeDefault(turl1));
+ EXPECT_FALSE(controller_->CanMakeDefault(turl2));
+}
+
+// Tests that a TemplateURL can't be edited if it is the managed default search
+// provider.
+TEST_F(KeywordEditorControllerTest, EditManagedDefault) {
+ controller_->AddTemplateURL(kA, kB, "http://c{searchTerms}");
+ controller_->AddTemplateURL(kA1, kB1, "http://d{searchTerms}");
+ ClearChangeCount();
+
+ const TemplateURL* turl1 =
+ model_->GetTemplateURLForKeyword(ASCIIToUTF16("b"));
+ ASSERT_TRUE(turl1 != NULL);
+ const TemplateURL* turl2 =
+ model_->GetTemplateURLForKeyword(ASCIIToUTF16("b1"));
+ ASSERT_TRUE(turl2 != NULL);
+
+ EXPECT_TRUE(controller_->CanEdit(turl1));
+ EXPECT_TRUE(controller_->CanEdit(turl2));
+
+ // Simulate setting a managed default. This will add another template URL to
+ // the model.
+ SimulateDefaultSearchIsManaged(turl2->url()->url());
+ EXPECT_TRUE(model_->is_default_search_managed());
+ EXPECT_TRUE(controller_->CanEdit(turl1));
+ EXPECT_TRUE(controller_->CanEdit(turl2));
+ EXPECT_FALSE(controller_->CanEdit(model_->GetDefaultSearchProvider()));
+}
+
+TEST_F(KeywordEditorControllerTest, MakeDefaultNoWebData) {
+ // Simulate a failure to load Web Data.
+ Init(true);
+
+ controller_->AddTemplateURL(kA, kB, "http://c{searchTerms}");
+ ClearChangeCount();
+
+ // This should not result in a crash.
+ int new_default = controller_->MakeDefaultTemplateURL(0);
+ EXPECT_EQ(0, new_default);
+}
+
+// Mutates the TemplateURLModel and make sure table model is updating
+// appropriately.
+TEST_F(KeywordEditorControllerTest, MutateTemplateURLModel) {
+ TemplateURL* turl = new TemplateURL();
+ turl->set_keyword(ASCIIToUTF16("a"));
+ turl->set_short_name(ASCIIToUTF16("b"));
+ model_->Add(turl);
+
+ // Table model should have updated.
+ VerifyChangeCount(1, 0, 0, 0);
+
+ // And should contain the newly added TemplateURL.
+ ASSERT_EQ(1, table_model()->RowCount());
+ ASSERT_EQ(0, table_model()->IndexOfTemplateURL(turl));
+}
diff --git a/chrome/browser/ui/search_engines/template_url_table_model.cc b/chrome/browser/ui/search_engines/template_url_table_model.cc
new file mode 100644
index 0000000..6c82c85
--- /dev/null
+++ b/chrome/browser/ui/search_engines/template_url_table_model.cc
@@ -0,0 +1,381 @@
+// 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.
+
+#include "chrome/browser/ui/search_engines/template_url_table_model.h"
+
+#include "base/callback.h"
+#include "base/i18n/rtl.h"
+#include "base/stl_util-inl.h"
+#include "base/utf_string_conversions.h"
+#include "chrome/browser/favicon_service.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/search_engines/template_url.h"
+#include "chrome/browser/search_engines/template_url_model.h"
+#include "grit/app_resources.h"
+#include "grit/generated_resources.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/models/table_model_observer.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/gfx/codec/png_codec.h"
+
+// Group IDs used by TemplateURLTableModel.
+static const int kMainGroupID = 0;
+static const int kOtherGroupID = 1;
+
+// ModelEntry ----------------------------------------------------
+
+// ModelEntry wraps a TemplateURL as returned from the TemplateURL.
+// ModelEntry also tracks state information about the URL.
+
+// Icon used while loading, or if a specific favicon can't be found.
+static SkBitmap* default_icon = NULL;
+
+class ModelEntry {
+ public:
+ explicit ModelEntry(TemplateURLTableModel* model,
+ const TemplateURL& template_url)
+ : template_url_(template_url),
+ load_state_(NOT_LOADED),
+ model_(model) {
+ if (!default_icon) {
+ default_icon = ResourceBundle::GetSharedInstance().
+ GetBitmapNamed(IDR_DEFAULT_FAVICON);
+ }
+ }
+
+ const TemplateURL& template_url() {
+ return template_url_;
+ }
+
+ SkBitmap GetIcon() {
+ if (load_state_ == NOT_LOADED)
+ LoadFavIcon();
+ if (!fav_icon_.isNull())
+ return fav_icon_;
+ return *default_icon;
+ }
+
+ // Resets internal status so that the next time the icon is asked for its
+ // fetched again. This should be invoked if the url is modified.
+ void ResetIcon() {
+ load_state_ = NOT_LOADED;
+ fav_icon_ = SkBitmap();
+ }
+
+ private:
+ // State of the favicon.
+ enum LoadState {
+ NOT_LOADED,
+ LOADING,
+ LOADED
+ };
+
+ void LoadFavIcon() {
+ load_state_ = LOADED;
+ FaviconService* favicon_service =
+ model_->template_url_model()->profile()->GetFaviconService(
+ Profile::EXPLICIT_ACCESS);
+ if (!favicon_service)
+ return;
+ GURL fav_icon_url = template_url().GetFavIconURL();
+ if (!fav_icon_url.is_valid()) {
+ // The favicon url isn't always set. Guess at one here.
+ if (template_url_.url() && template_url_.url()->IsValid()) {
+ GURL url = GURL(template_url_.url()->url());
+ if (url.is_valid())
+ fav_icon_url = TemplateURL::GenerateFaviconURL(url);
+ }
+ if (!fav_icon_url.is_valid())
+ return;
+ }
+ load_state_ = LOADING;
+ favicon_service->GetFavicon(fav_icon_url,
+ &request_consumer_,
+ NewCallback(this, &ModelEntry::OnFavIconDataAvailable));
+ }
+
+ void OnFavIconDataAvailable(
+ FaviconService::Handle handle,
+ bool know_favicon,
+ scoped_refptr<RefCountedMemory> data,
+ bool expired,
+ GURL icon_url) {
+ load_state_ = LOADED;
+ if (know_favicon && data.get() &&
+ gfx::PNGCodec::Decode(data->front(), data->size(), &fav_icon_)) {
+ model_->FavIconAvailable(this);
+ }
+ }
+
+ const TemplateURL& template_url_;
+ SkBitmap fav_icon_;
+ LoadState load_state_;
+ TemplateURLTableModel* model_;
+ CancelableRequestConsumer request_consumer_;
+
+ DISALLOW_COPY_AND_ASSIGN(ModelEntry);
+};
+
+// TemplateURLTableModel -----------------------------------------
+
+TemplateURLTableModel::TemplateURLTableModel(
+ TemplateURLModel* template_url_model)
+ : observer_(NULL),
+ template_url_model_(template_url_model) {
+ DCHECK(template_url_model);
+ template_url_model_->Load();
+ template_url_model_->AddObserver(this);
+ Reload();
+}
+
+TemplateURLTableModel::~TemplateURLTableModel() {
+ template_url_model_->RemoveObserver(this);
+ STLDeleteElements(&entries_);
+ entries_.clear();
+}
+
+void TemplateURLTableModel::Reload() {
+ STLDeleteElements(&entries_);
+ entries_.clear();
+
+ std::vector<const TemplateURL*> urls = template_url_model_->GetTemplateURLs();
+
+ // Keywords that can be made the default first.
+ for (std::vector<const TemplateURL*>::iterator i = urls.begin();
+ i != urls.end(); ++i) {
+ const TemplateURL& template_url = *(*i);
+ // NOTE: we don't use ShowInDefaultList here to avoid items bouncing around
+ // the lists while editing.
+ if (template_url.show_in_default_list())
+ entries_.push_back(new ModelEntry(this, template_url));
+ }
+
+ last_search_engine_index_ = static_cast<int>(entries_.size());
+
+ // Then the rest.
+ for (std::vector<const TemplateURL*>::iterator i = urls.begin();
+ i != urls.end(); ++i) {
+ const TemplateURL* template_url = *i;
+ // NOTE: we don't use ShowInDefaultList here to avoid things bouncing
+ // the lists while editing.
+ if (!template_url->show_in_default_list() &&
+ !template_url->IsExtensionKeyword()) {
+ entries_.push_back(new ModelEntry(this, *template_url));
+ }
+ }
+
+ if (observer_)
+ observer_->OnModelChanged();
+}
+
+int TemplateURLTableModel::RowCount() {
+ return static_cast<int>(entries_.size());
+}
+
+string16 TemplateURLTableModel::GetText(int row, int col_id) {
+ DCHECK(row >= 0 && row < RowCount());
+ const TemplateURL& url = entries_[row]->template_url();
+
+ switch (col_id) {
+ case IDS_SEARCH_ENGINES_EDITOR_DESCRIPTION_COLUMN: {
+ string16 url_short_name = url.short_name();
+ // TODO(xji): Consider adding a special case if the short name is a URL,
+ // since those should always be displayed LTR. Please refer to
+ // http://crbug.com/6726 for more information.
+ base::i18n::AdjustStringForLocaleDirection(&url_short_name);
+ if (template_url_model_->GetDefaultSearchProvider() == &url) {
+ return l10n_util::GetStringFUTF16(
+ IDS_SEARCH_ENGINES_EDITOR_DEFAULT_ENGINE,
+ url_short_name);
+ }
+ return url_short_name;
+ }
+
+ case IDS_SEARCH_ENGINES_EDITOR_KEYWORD_COLUMN: {
+ // Keyword should be domain name. Force it to have LTR directionality.
+ string16 keyword = url.keyword();
+ keyword = base::i18n::GetDisplayStringInLTRDirectionality(keyword);
+ return keyword;
+ }
+
+ default:
+ NOTREACHED();
+ return string16();
+ }
+}
+
+SkBitmap TemplateURLTableModel::GetIcon(int row) {
+ DCHECK(row >= 0 && row < RowCount());
+ return entries_[row]->GetIcon();
+}
+
+void TemplateURLTableModel::SetObserver(ui::TableModelObserver* observer) {
+ observer_ = observer;
+}
+
+bool TemplateURLTableModel::HasGroups() {
+ return true;
+}
+
+TemplateURLTableModel::Groups TemplateURLTableModel::GetGroups() {
+ Groups groups;
+
+ Group search_engine_group;
+ search_engine_group.title =
+ l10n_util::GetStringUTF16(IDS_SEARCH_ENGINES_EDITOR_MAIN_SEPARATOR);
+ search_engine_group.id = kMainGroupID;
+ groups.push_back(search_engine_group);
+
+ Group other_group;
+ other_group.title =
+ l10n_util::GetStringUTF16(IDS_SEARCH_ENGINES_EDITOR_OTHER_SEPARATOR);
+ other_group.id = kOtherGroupID;
+ groups.push_back(other_group);
+
+ return groups;
+}
+
+int TemplateURLTableModel::GetGroupID(int row) {
+ DCHECK(row >= 0 && row < RowCount());
+ return row < last_search_engine_index_ ? kMainGroupID : kOtherGroupID;
+}
+
+void TemplateURLTableModel::Remove(int index) {
+ // Remove the observer while we modify the model, that way we don't need to
+ // worry about the model calling us back when we mutate it.
+ template_url_model_->RemoveObserver(this);
+ const TemplateURL* template_url = &GetTemplateURL(index);
+
+ scoped_ptr<ModelEntry> entry(entries_[index]);
+ entries_.erase(entries_.begin() + index);
+ if (index < last_search_engine_index_)
+ last_search_engine_index_--;
+ if (observer_)
+ observer_->OnItemsRemoved(index, 1);
+
+ // Make sure to remove from the table model first, otherwise the
+ // TemplateURL would be freed.
+ template_url_model_->Remove(template_url);
+ template_url_model_->AddObserver(this);
+}
+
+void TemplateURLTableModel::Add(int index, TemplateURL* template_url) {
+ DCHECK(index >= 0 && index <= RowCount());
+ ModelEntry* entry = new ModelEntry(this, *template_url);
+ entries_.insert(entries_.begin() + index, entry);
+ if (observer_)
+ observer_->OnItemsAdded(index, 1);
+ template_url_model_->RemoveObserver(this);
+ template_url_model_->Add(template_url);
+ template_url_model_->AddObserver(this);
+}
+
+void TemplateURLTableModel::ModifyTemplateURL(int index,
+ const string16& title,
+ const string16& keyword,
+ const std::string& url) {
+ DCHECK(index >= 0 && index <= RowCount());
+ const TemplateURL* template_url = &GetTemplateURL(index);
+ template_url_model_->RemoveObserver(this);
+ template_url_model_->ResetTemplateURL(template_url, title, keyword, url);
+ if (template_url_model_->GetDefaultSearchProvider() == template_url &&
+ !TemplateURL::SupportsReplacement(template_url)) {
+ // The entry was the default search provider, but the url has been modified
+ // so that it no longer supports replacement. Reset the default search
+ // provider so that it doesn't point to a bogus entry.
+ template_url_model_->SetDefaultSearchProvider(NULL);
+ }
+ template_url_model_->AddObserver(this);
+ ReloadIcon(index); // Also calls NotifyChanged().
+}
+
+void TemplateURLTableModel::ReloadIcon(int index) {
+ DCHECK(index >= 0 && index < RowCount());
+
+ entries_[index]->ResetIcon();
+
+ NotifyChanged(index);
+}
+
+const TemplateURL& TemplateURLTableModel::GetTemplateURL(int index) {
+ return entries_[index]->template_url();
+}
+
+int TemplateURLTableModel::IndexOfTemplateURL(
+ const TemplateURL* template_url) {
+ for (std::vector<ModelEntry*>::iterator i = entries_.begin();
+ i != entries_.end(); ++i) {
+ ModelEntry* entry = *i;
+ if (&(entry->template_url()) == template_url)
+ return static_cast<int>(i - entries_.begin());
+ }
+ return -1;
+}
+
+int TemplateURLTableModel::MoveToMainGroup(int index) {
+ if (index < last_search_engine_index_)
+ return index; // Already in the main group.
+
+ ModelEntry* current_entry = entries_[index];
+ entries_.erase(index + entries_.begin());
+ if (observer_)
+ observer_->OnItemsRemoved(index, 1);
+
+ const int new_index = last_search_engine_index_++;
+ entries_.insert(entries_.begin() + new_index, current_entry);
+ if (observer_)
+ observer_->OnItemsAdded(new_index, 1);
+ return new_index;
+}
+
+int TemplateURLTableModel::MakeDefaultTemplateURL(int index) {
+ if (index < 0 || index >= RowCount()) {
+ NOTREACHED();
+ return -1;
+ }
+
+ const TemplateURL* keyword = &GetTemplateURL(index);
+ const TemplateURL* current_default =
+ template_url_model_->GetDefaultSearchProvider();
+ if (current_default == keyword)
+ return -1;
+
+ template_url_model_->RemoveObserver(this);
+ template_url_model_->SetDefaultSearchProvider(keyword);
+ template_url_model_->AddObserver(this);
+
+ // The formatting of the default engine is different; notify the table that
+ // both old and new entries have changed.
+ if (current_default != NULL) {
+ int old_index = IndexOfTemplateURL(current_default);
+ // current_default may not be in the list of TemplateURLs if the database is
+ // corrupt and the default TemplateURL is used from preferences
+ if (old_index >= 0)
+ NotifyChanged(old_index);
+ }
+ const int new_index = IndexOfTemplateURL(keyword);
+ NotifyChanged(new_index);
+
+ // Make sure the new default is in the main group.
+ return MoveToMainGroup(index);
+}
+
+void TemplateURLTableModel::NotifyChanged(int index) {
+ if (observer_) {
+ DCHECK_GE(index, 0);
+ observer_->OnItemsChanged(index, 1);
+ }
+}
+
+void TemplateURLTableModel::FavIconAvailable(ModelEntry* entry) {
+ std::vector<ModelEntry*>::iterator i =
+ find(entries_.begin(), entries_.end(), entry);
+ DCHECK(i != entries_.end());
+ NotifyChanged(static_cast<int>(i - entries_.begin()));
+}
+
+void TemplateURLTableModel::OnTemplateURLModelChanged() {
+ Reload();
+}
diff --git a/chrome/browser/ui/search_engines/template_url_table_model.h b/chrome/browser/ui/search_engines/template_url_table_model.h
new file mode 100644
index 0000000..c002908
--- /dev/null
+++ b/chrome/browser/ui/search_engines/template_url_table_model.h
@@ -0,0 +1,117 @@
+// 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.
+
+#ifndef CHROME_BROWSER_UI_SEARCH_ENGINES_TEMPLATE_URL_TABLE_MODEL_H_
+#define CHROME_BROWSER_UI_SEARCH_ENGINES_TEMPLATE_URL_TABLE_MODEL_H_
+#pragma once
+
+#include <string>
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/string16.h"
+#include "chrome/browser/search_engines/template_url_model_observer.h"
+#include "ui/base/models/table_model.h"
+
+class ModelEntry;
+class SkBitmap;
+class TemplateURL;
+class TemplateURLModel;
+
+// TemplateURLTableModel is the TableModel implementation used by
+// KeywordEditorView to show the keywords in a TableView.
+//
+// TemplateURLTableModel has two columns, the first showing the description,
+// the second the keyword.
+//
+// TemplateURLTableModel maintains a vector of ModelEntrys that correspond to
+// each row in the tableview. Each ModelEntry wraps a TemplateURL, providing
+// the favicon. The entries in the model are sorted such that non-generated
+// appear first (grouped together) and are followed by generated keywords.
+
+class TemplateURLTableModel : public ui::TableModel,
+ TemplateURLModelObserver {
+ public:
+ explicit TemplateURLTableModel(TemplateURLModel* template_url_model);
+
+ virtual ~TemplateURLTableModel();
+
+ // Reloads the entries from the TemplateURLModel. This should ONLY be invoked
+ // if the TemplateURLModel wasn't initially loaded and has been loaded.
+ void Reload();
+
+ // ui::TableModel overrides.
+ virtual int RowCount() OVERRIDE;
+ virtual string16 GetText(int row, int column) OVERRIDE;
+ virtual SkBitmap GetIcon(int row) OVERRIDE;
+ virtual void SetObserver(ui::TableModelObserver* observer) OVERRIDE;
+ virtual bool HasGroups() OVERRIDE;
+ virtual Groups GetGroups() OVERRIDE;
+ virtual int GetGroupID(int row) OVERRIDE;
+
+ // Removes the entry at the specified index.
+ void Remove(int index);
+
+ // Adds a new entry at the specified index.
+ void Add(int index, TemplateURL* template_url);
+
+ // Update the entry at the specified index.
+ void ModifyTemplateURL(int index,
+ const string16& title,
+ const string16& keyword,
+ const std::string& url);
+
+ // Reloads the icon at the specified index.
+ void ReloadIcon(int index);
+
+ // Returns The TemplateURL at the specified index.
+ const TemplateURL& GetTemplateURL(int index);
+
+ // Returns the index of the TemplateURL, or -1 if it the TemplateURL is not
+ // found.
+ int IndexOfTemplateURL(const TemplateURL* template_url);
+
+ // Moves the keyword at the specified index to be at the end of the main
+ // group. Returns the new index. If the entry is already in the main group,
+ // no action is taken, and the current index is returned.
+ int MoveToMainGroup(int index);
+
+ // Make the TemplateURL at |index| the default. Returns the new index, or -1
+ // if the index is invalid or it is already the default.
+ int MakeDefaultTemplateURL(int index);
+
+ // If there is an observer, it's notified the selected row has changed.
+ void NotifyChanged(int index);
+
+ TemplateURLModel* template_url_model() const { return template_url_model_; }
+
+ // Returns the index of the last entry shown in the search engines group.
+ int last_search_engine_index() const { return last_search_engine_index_; }
+
+ private:
+ friend class ModelEntry;
+
+ // Notification that a model entry has fetched its icon.
+ void FavIconAvailable(ModelEntry* entry);
+
+ // TemplateURLModelObserver notification.
+ virtual void OnTemplateURLModelChanged();
+
+ ui::TableModelObserver* observer_;
+
+ // The entries.
+ std::vector<ModelEntry*> entries_;
+
+ // The model we're displaying entries from.
+ TemplateURLModel* template_url_model_;
+
+ // Index of the last search engine in entries_. This is used to determine the
+ // group boundaries.
+ int last_search_engine_index_;
+
+ DISALLOW_COPY_AND_ASSIGN(TemplateURLTableModel);
+};
+
+
+#endif // CHROME_BROWSER_UI_SEARCH_ENGINES_TEMPLATE_URL_TABLE_MODEL_H_