summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorfinnur@chromium.org <finnur@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-03-26 20:57:55 +0000
committerfinnur@chromium.org <finnur@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-03-26 20:57:55 +0000
commit94b8a51a414c82d66d5196ce53c621aecc19952d (patch)
tree3a9c12a86ddae4cbcd04456cc8677800a10b1442
parentac2063d48d85a5da2e1a610b74907caeda20a616 (diff)
downloadchromium_src-94b8a51a414c82d66d5196ce53c621aecc19952d.zip
chromium_src-94b8a51a414c82d66d5196ce53c621aecc19952d.tar.gz
chromium_src-94b8a51a414c82d66d5196ce53c621aecc19952d.tar.bz2
Settings API first-run bubble.
Pop up a bubble the first time a user encounters that the settings have been changed by an extension. Settings includes: home page, search engine or startup pages. BUG=356204 R=mad@chromium.org, pkasting@chromium.org, yoz@chromium.org Review URL: https://codereview.chromium.org/202923006 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@259684 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/app/chromium_strings.grd14
-rw-r--r--chrome/app/generated_resources.grd63
-rw-r--r--chrome/app/google_chrome_strings.grd14
-rw-r--r--chrome/browser/extensions/extension_message_bubble_controller.cc2
-rw-r--r--chrome/browser/extensions/extension_message_bubble_controller.h3
-rw-r--r--chrome/browser/extensions/settings_api_bubble_controller.cc300
-rw-r--r--chrome/browser/extensions/settings_api_bubble_controller.h42
-rw-r--r--chrome/browser/ui/omnibox/omnibox_view.cc13
-rw-r--r--chrome/browser/ui/omnibox/omnibox_view.h6
-rw-r--r--chrome/browser/ui/views/extensions/extension_message_bubble_view.cc146
-rw-r--r--chrome/browser/ui/views/extensions/extension_message_bubble_view.h88
-rw-r--r--chrome/browser/ui/views/omnibox/omnibox_view_views.cc8
-rw-r--r--chrome/browser/ui/views/omnibox/omnibox_view_views.h3
-rw-r--r--chrome/browser/ui/views/settings_api_bubble_helper_views.cc157
-rw-r--r--chrome/browser/ui/views/settings_api_bubble_helper_views.h64
-rw-r--r--chrome/browser/ui/views/toolbar/home_button.cc6
-rw-r--r--chrome/browser/ui/views/toolbar/home_button.h5
-rw-r--r--chrome/browser/ui/views/toolbar/toolbar_view.h1
-rw-r--r--chrome/chrome_browser_extensions.gypi2
-rw-r--r--chrome/chrome_browser_ui.gypi2
-rw-r--r--chrome/common/extensions/manifest_handlers/settings_overrides_handler.cc6
-rw-r--r--chrome/common/extensions/manifest_handlers/settings_overrides_handler.h6
-rw-r--r--chrome/common/url_constants.cc4
-rw-r--r--chrome/common/url_constants.h3
-rw-r--r--extensions/browser/extension_prefs.cc15
-rw-r--r--extensions/browser/extension_prefs.h7
26 files changed, 894 insertions, 86 deletions
diff --git a/chrome/app/chromium_strings.grd b/chrome/app/chromium_strings.grd
index 59a5427..aa1818a 100644
--- a/chrome/app/chromium_strings.grd
+++ b/chrome/app/chromium_strings.grd
@@ -1168,6 +1168,20 @@ Signing in anyway will merge Chromium information like bookmarks, history, and o
</message>
</if>
+ <!-- Settings API bubble -->
+ <message name="IDS_EXTENSIONS_SETTINGS_API_FIRST_LINE_START_PAGES" desc="Text displayed in the Settings API bubble as first line when an extension has changed the start pages.">
+ An extension has changed what page is shown when you start Chromium.
+ </message>
+ <message name="IDS_EXTENSIONS_SETTINGS_API_SECOND_LINE_START_PAGES" desc="Second line in the Settings API bubble. Only shown if the secondary change by the extension was just the start pages. The triple single quotes are needed to preserve the space before and after the sentence which is needed when the language (Chrome is being translated to) uses space as word separator. Please preserve them, unless the language being translated to does not use space as word separator.">
+ ''' It also controls what page is shown when you start Chromium. '''
+ </message>
+ <message name="IDS_EXTENSIONS_SETTINGS_API_SECOND_LINE_START_AND_HOME" desc="Second line in the Settings API bubble. Only shown if the secondary change by the extension was both the start page and the home page. See IDS_EXTENSIONS_SETTINGS_API_SECOND_LINE_START_PAGES for reason for triple quotes.">
+ ''' It also controls what page is shown when you start Chromium or click the Home button. '''
+ </message>
+ <message name="IDS_EXTENSIONS_SETTINGS_API_SECOND_LINE_START_AND_SEARCH" desc="Second line in the Settings API bubble. Only shown if the secondary change by the extension was both the start page and the search engine. See IDS_EXTENSIONS_SETTINGS_API_SECOND_LINE_START_PAGES for reason for triple quotes.">
+ ''' It also controls what page is shown when you start Chromium or search from the Omnibox. '''
+ </message>
+
<!-- Update bubble -->
<message name="IDS_REINSTALL_APP" desc="Text for the button the user clicks to reinstall the app.">
Reinstall Chromium
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index 9fa76c6..b1401df 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -5395,6 +5395,69 @@ Keep your key file in a safe place. You will need it to create new versions of y
Extensions running in developer mode can harm your computer. If you're not a developer, you should disable these extensions running in developer mode to stay safe.
</message>
+ <!-- Settings API bubble -->
+ <message name="IDS_EXTENSIONS_DISABLE_EXTENSIONS" desc="Text for the Disable Extensions (note: plural) button">
+ Disable Extensions
+ </message>
+ <if expr="use_titlecase">
+ <message name="IDS_EXTENSIONS_SETTINGS_API_TITLE_HOME_PAGE_BUBBLE" desc="In Title Case: Title of a bubble warning users that an extension has overridden their home page setting">
+ Is This the Home Page You Were Expecting?
+ </message>
+ </if>
+ <if expr="not use_titlecase">
+ <message name="IDS_EXTENSIONS_SETTINGS_API_TITLE_HOME_PAGE_BUBBLE" desc="Title of a bubble warning users that an extension has overridden their home page setting">
+ Is this the home page you were expecting?
+ </message>
+ </if>
+ <if expr="use_titlecase">
+ <message name="IDS_EXTENSIONS_SETTINGS_API_TITLE_STARTUP_PAGES_BUBBLE" desc="In Title Case: Title of a bubble warning users that an extension has overridden their startup pages setting">
+ Is This the Startup Page You Were Expecting?
+ </message>
+ </if>
+ <if expr="not use_titlecase">
+ <message name="IDS_EXTENSIONS_SETTINGS_API_TITLE_STARTUP_PAGES_BUBBLE" desc="Title of a bubble warning users that an extension has overridden their startup pages setting">
+ Is this the startup page you were expecting?
+ </message>
+ </if>
+ <if expr="use_titlecase">
+ <message name="IDS_EXTENSIONS_SETTINGS_API_TITLE_SEARCH_ENGINE_BUBBLE" desc="In Title Case: Title of a bubble warning users that an extension has overridden their default search engine setting">
+ Is This the Search Page You Were Expecting?
+ </message>
+ </if>
+ <if expr="not use_titlecase">
+ <message name="IDS_EXTENSIONS_SETTINGS_API_TITLE_SEARCH_ENGINE_BUBBLE" desc="Title of a bubble warning users that an extension has overridden their default search engine setting">
+ Is this the search page you were expecting?
+ </message>
+ </if>
+
+ <message name="IDS_EXTENSIONS_SETTINGS_API_FIRST_LINE_SEARCH_ENGINE" desc="Text displayed in the Settings API bubble as first line when an extension has changed the search engine.">
+ An extension has changed what page is shown when you search from the Omnibox.
+ </message>
+ <message name="IDS_EXTENSIONS_SETTINGS_API_FIRST_LINE_HOME_PAGE" desc="Text displayed in the Settings API bubble as first line when an extension has changed the home page.">
+ An extension has changed what page is shown when you click the Home button.
+ </message>
+
+ <message name="IDS_EXTENSIONS_SETTINGS_API_SECOND_LINE_SEARCH_ENGINE" desc="Second line in the Settings API bubble. Only shown if the secondary change by the extension was just the search engine. The triple single quotes are needed to preserve the space before and after the sentence which is needed when the language (Chrome is being translated to) uses space as word separator. Please preserve them, unless the language being translated to does not use space as word separator.">
+ ''' It also controls what page is shown when you search from the Omnibox.'''
+ </message>
+ <message name="IDS_EXTENSIONS_SETTINGS_API_SECOND_LINE_HOME_PAGE" desc="Second line in the Settings API bubble. Only shown if the secondary change by the extension was the just home page. See IDS_EXTENSIONS_SETTINGS_API_SECOND_LINE_SEARCH_ENGINE for reason for triple quotes.">
+ ''' It also controls what page is shown when you click the Home button. '''
+ </message>
+ <message name="IDS_EXTENSIONS_SETTINGS_API_SECOND_LINE_HOME_AND_SEARCH" desc="Second line in the Settings API bubble. Only shown if the secondary change by the extension was both the home page and the search engine. See IDS_EXTENSIONS_SETTINGS_API_SECOND_LINE_SEARCH_ENGINE for reason for triple quotes.">
+ ''' It also controls what page is shown when you click the Home button or search from the Omnibox. '''
+ </message>
+
+ <message name="IDS_EXTENSIONS_SETTINGS_API_THIRD_LINE_CONFIRMATION" desc="Third line in the Settings API bubble, always appended after the first (and optional second) line to make one paragraph. See IDS_EXTENSIONS_SETTINGS_API_SECOND_LINE_SEARCH_ENGINE for reason for triple quotes.">
+ ''' If you didn't want these changes, you can restore your previous settings.'''
+ </message>
+
+ <message name="IDS_EXTENSIONS_SETTINGS_API_RESTORE_SETTINGS" desc="The button in the Settings API bubble that reverts the settings changes made by the extension and restores what the user had before.">
+ Restore settings
+ </message>
+ <message name="IDS_EXTENSIONS_SETTINGS_API_KEEP_CHANGES" desc="The button in the Settings API bubble that cancels the bubble without action.">
+ Keep changes
+ </message>
+
<!-- chrome://settings-frame/options_settings_app.html for the App Launcher Settings App -->
<if expr="enable_settings_app">
<message name="IDS_SETTINGS_APP_LAUNCHER_PRODUCT_NAME" desc="Product name to use when referring to applications running in the App Launcher inside the non-browser Settings App">
diff --git a/chrome/app/google_chrome_strings.grd b/chrome/app/google_chrome_strings.grd
index d20cfd6..bd63291 100644
--- a/chrome/app/google_chrome_strings.grd
+++ b/chrome/app/google_chrome_strings.grd
@@ -1092,6 +1092,20 @@ Signing in anyway will merge Chrome information like bookmarks, history, and oth
</message>
</if>
+ <!-- Settings API bubble -->
+ <message name="IDS_EXTENSIONS_SETTINGS_API_FIRST_LINE_START_PAGES" desc="Text displayed in the Settings API bubble as first line when an extension has changed the start pages.">
+ An extension has changed what page is shown when you start Chrome.
+ </message>
+ <message name="IDS_EXTENSIONS_SETTINGS_API_SECOND_LINE_START_PAGES" desc="Second line in the Settings API bubble. Only shown if the secondary change by the extension was just the start pages. The triple single quotes are needed to preserve the space before and after the sentence which is needed when the language (Chrome is being translated to) uses space as word separator. Please preserve them, unless the language being translated to does not use space as word separator.">
+ ''' It also controls what page is shown when you start Chrome. '''
+ </message>
+ <message name="IDS_EXTENSIONS_SETTINGS_API_SECOND_LINE_START_AND_HOME" desc="Second line in the Settings API bubble. Only shown if the secondary change by the extension was both the start page and the home page. See IDS_EXTENSIONS_SETTINGS_API_SECOND_LINE_START_PAGES for reason for triple quotes.">
+ ''' It also controls what page is shown when you start Chrome or click the Home button. '''
+ </message>
+ <message name="IDS_EXTENSIONS_SETTINGS_API_SECOND_LINE_START_AND_SEARCH" desc="Second line in the Settings API bubble. Only shown if the secondary change by the extension was both the start page and the search engine. See IDS_EXTENSIONS_SETTINGS_API_SECOND_LINE_START_PAGES for reason for triple quotes.">
+ ''' It also controls what page is shown when you start Chrome or search from the Omnibox. '''
+ </message>
+
<!-- Update bubble -->
<message name="IDS_REINSTALL_APP" desc="Text for the button the user clicks to reinstall the app.">
Reinstall Chrome
diff --git a/chrome/browser/extensions/extension_message_bubble_controller.cc b/chrome/browser/extensions/extension_message_bubble_controller.cc
index ef5e06b..20e18ca 100644
--- a/chrome/browser/extensions/extension_message_bubble_controller.cc
+++ b/chrome/browser/extensions/extension_message_bubble_controller.cc
@@ -70,6 +70,8 @@ const ExtensionIdList& ExtensionMessageBubbleController::GetExtensionIdList() {
return *GetOrCreateExtensionList();
}
+bool ExtensionMessageBubbleController::CloseOnDeactivate() { return false; }
+
void ExtensionMessageBubbleController::Show(ExtensionMessageBubble* bubble) {
// Wire up all the callbacks, to get notified what actions the user took.
base::Closure dismiss_button_callback =
diff --git a/chrome/browser/extensions/extension_message_bubble_controller.h b/chrome/browser/extensions/extension_message_bubble_controller.h
index 8083377..a6ec56e8 100644
--- a/chrome/browser/extensions/extension_message_bubble_controller.h
+++ b/chrome/browser/extensions/extension_message_bubble_controller.h
@@ -69,6 +69,9 @@ class ExtensionMessageBubbleController {
// Obtains a list of all extensions (by id) the controller knows about.
const ExtensionIdList& GetExtensionIdList();
+ // Whether to close the bubble when it loses focus.
+ virtual bool CloseOnDeactivate();
+
// Sets up the callbacks and shows the bubble.
virtual void Show(ExtensionMessageBubble* bubble);
diff --git a/chrome/browser/extensions/settings_api_bubble_controller.cc b/chrome/browser/extensions/settings_api_bubble_controller.cc
new file mode 100644
index 0000000..a5819bc
--- /dev/null
+++ b/chrome/browser/extensions/settings_api_bubble_controller.cc
@@ -0,0 +1,300 @@
+// Copyright (c) 2014 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/extensions/settings_api_bubble_controller.h"
+
+#include "base/metrics/histogram.h"
+#include "chrome/browser/extensions/extension_service.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/startup/startup_browser_creator.h"
+#include "chrome/common/extensions/manifest_handlers/settings_overrides_handler.h"
+#include "chrome/common/url_constants.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/browser/extension_system.h"
+#include "grit/chromium_strings.h"
+#include "grit/generated_resources.h"
+#include "ui/base/l10n/l10n_util.h"
+
+using extensions::ExtensionMessageBubbleController;
+using extensions::SettingsApiBubbleController;
+using extensions::SettingsOverrides;
+
+namespace {
+
+////////////////////////////////////////////////////////////////////////////////
+// SettingsApiBubbleDelegate
+
+class SettingsApiBubbleDelegate
+ : public extensions::ExtensionMessageBubbleController::Delegate {
+ public:
+ explicit SettingsApiBubbleDelegate(ExtensionService* service,
+ Profile* profile,
+ extensions::SettingsApiOverrideType type);
+ virtual ~SettingsApiBubbleDelegate();
+
+ // ExtensionMessageBubbleController::Delegate methods.
+ virtual bool ShouldIncludeExtension(const std::string& extension_id) OVERRIDE;
+ virtual void AcknowledgeExtension(
+ const std::string& extension_id,
+ extensions::ExtensionMessageBubbleController::BubbleAction user_action)
+ OVERRIDE;
+ virtual void PerformAction(const extensions::ExtensionIdList& list) OVERRIDE;
+ virtual base::string16 GetTitle() const OVERRIDE;
+ virtual base::string16 GetMessageBody() const OVERRIDE;
+ virtual base::string16 GetOverflowText(
+ const base::string16& overflow_count) const OVERRIDE;
+ virtual base::string16 GetLearnMoreLabel() const OVERRIDE;
+ virtual GURL GetLearnMoreUrl() const OVERRIDE;
+ virtual base::string16 GetActionButtonLabel() const OVERRIDE;
+ virtual base::string16 GetDismissButtonLabel() const OVERRIDE;
+ virtual bool ShouldShowExtensionList() const OVERRIDE;
+ virtual void LogExtensionCount(size_t count) OVERRIDE;
+ virtual void LogAction(
+ extensions::ExtensionMessageBubbleController::BubbleAction action)
+ OVERRIDE;
+
+ private:
+ // Our extension service. Weak, not owned by us.
+ ExtensionService* service_;
+
+ // A weak pointer to the profile we are associated with. Not owned by us.
+ Profile* profile_;
+
+ // The type of settings override this bubble will report on. This can be, for
+ // example, a bubble to notify the user that the search engine has been
+ // changed by an extension (or homepage/startup pages/etc).
+ extensions::SettingsApiOverrideType type_;
+
+ // The ID of the extension we are showing the bubble for.
+ std::string extension_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(SettingsApiBubbleDelegate);
+};
+
+SettingsApiBubbleDelegate::SettingsApiBubbleDelegate(
+ ExtensionService* service,
+ Profile* profile,
+ extensions::SettingsApiOverrideType type)
+ : service_(service), profile_(profile), type_(type) {}
+
+SettingsApiBubbleDelegate::~SettingsApiBubbleDelegate() {}
+
+bool SettingsApiBubbleDelegate::ShouldIncludeExtension(
+ const std::string& extension_id) {
+ using extensions::ExtensionRegistry;
+ ExtensionRegistry* registry = ExtensionRegistry::Get(profile_);
+ const extensions::Extension* extension =
+ registry->GetExtensionById(extension_id, ExtensionRegistry::ENABLED);
+ if (!extension)
+ return false; // The extension provided is no longer enabled.
+
+ extensions::ExtensionPrefs* prefs = extensions::ExtensionPrefs::Get(profile_);
+ if (prefs->HasSettingsApiBubbleBeenAcknowledged(extension_id))
+ return false;
+
+ const SettingsOverrides* settings = SettingsOverrides::Get(extension);
+ if (!settings)
+ return false;
+
+ bool should_include = false;
+ switch (type_) {
+ case extensions::BUBBLE_TYPE_HOME_PAGE:
+ should_include = settings->homepage != NULL;
+ break;
+ case extensions::BUBBLE_TYPE_STARTUP_PAGES:
+ should_include = !settings->startup_pages.empty();
+ break;
+ case extensions::BUBBLE_TYPE_SEARCH_ENGINE:
+ should_include = settings->search_engine != NULL;
+ break;
+ }
+
+ if (should_include && extension_id_ != extension_id) {
+ DCHECK(extension_id_.empty());
+ extension_id_ = extension_id;
+ }
+ return should_include;
+}
+
+void SettingsApiBubbleDelegate::AcknowledgeExtension(
+ const std::string& extension_id,
+ ExtensionMessageBubbleController::BubbleAction user_action) {
+ extensions::ExtensionPrefs* prefs = extensions::ExtensionPrefs::Get(profile_);
+ prefs->SetSettingsApiBubbleBeenAcknowledged(extension_id, true);
+}
+
+void SettingsApiBubbleDelegate::PerformAction(
+ const extensions::ExtensionIdList& list) {
+ for (size_t i = 0; i < list.size(); ++i) {
+ service_->DisableExtension(list[i],
+ extensions::Extension::DISABLE_USER_ACTION);
+ }
+}
+
+base::string16 SettingsApiBubbleDelegate::GetTitle() const {
+ switch (type_) {
+ case extensions::BUBBLE_TYPE_HOME_PAGE:
+ return l10n_util::GetStringUTF16(
+ IDS_EXTENSIONS_SETTINGS_API_TITLE_HOME_PAGE_BUBBLE);
+ case extensions::BUBBLE_TYPE_STARTUP_PAGES:
+ return l10n_util::GetStringUTF16(
+ IDS_EXTENSIONS_SETTINGS_API_TITLE_STARTUP_PAGES_BUBBLE);
+ case extensions::BUBBLE_TYPE_SEARCH_ENGINE:
+ return l10n_util::GetStringUTF16(
+ IDS_EXTENSIONS_SETTINGS_API_TITLE_SEARCH_ENGINE_BUBBLE);
+ }
+ NOTREACHED();
+ return base::string16();
+}
+
+base::string16 SettingsApiBubbleDelegate::GetMessageBody() const {
+ using extensions::ExtensionRegistry;
+ ExtensionRegistry* registry = ExtensionRegistry::Get(profile_);
+ const extensions::Extension* extension =
+ registry->GetExtensionById(extension_id_, ExtensionRegistry::ENABLED);
+ const SettingsOverrides* settings =
+ extension ? SettingsOverrides::Get(extension) : NULL;
+ if (!extension || !settings) {
+ NOTREACHED();
+ return base::string16();
+ }
+
+ bool home_change = settings->homepage != NULL;
+ bool startup_change = !settings->startup_pages.empty();
+ bool search_change = settings->search_engine != NULL;
+
+ base::string16 body;
+ switch (type_) {
+ case extensions::BUBBLE_TYPE_HOME_PAGE:
+ body = l10n_util::GetStringUTF16(
+ IDS_EXTENSIONS_SETTINGS_API_FIRST_LINE_HOME_PAGE);
+ if (startup_change && search_change) {
+ body += l10n_util::GetStringUTF16(
+ IDS_EXTENSIONS_SETTINGS_API_SECOND_LINE_START_AND_SEARCH);
+ } else if (startup_change) {
+ body += l10n_util::GetStringUTF16(
+ IDS_EXTENSIONS_SETTINGS_API_SECOND_LINE_START_PAGES);
+ } else if (search_change) {
+ body += l10n_util::GetStringUTF16(
+ IDS_EXTENSIONS_SETTINGS_API_SECOND_LINE_SEARCH_ENGINE);
+ }
+ break;
+ case extensions::BUBBLE_TYPE_STARTUP_PAGES:
+ body = l10n_util::GetStringUTF16(
+ IDS_EXTENSIONS_SETTINGS_API_FIRST_LINE_START_PAGES);
+ if (home_change && search_change) {
+ body += l10n_util::GetStringUTF16(
+ IDS_EXTENSIONS_SETTINGS_API_SECOND_LINE_HOME_AND_SEARCH);
+ } else if (home_change) {
+ body += l10n_util::GetStringUTF16(
+ IDS_EXTENSIONS_SETTINGS_API_SECOND_LINE_HOME_PAGE);
+ } else if (search_change) {
+ body += l10n_util::GetStringUTF16(
+ IDS_EXTENSIONS_SETTINGS_API_SECOND_LINE_SEARCH_ENGINE);
+ }
+ break;
+ case extensions::BUBBLE_TYPE_SEARCH_ENGINE:
+ body = l10n_util::GetStringUTF16(
+ IDS_EXTENSIONS_SETTINGS_API_FIRST_LINE_SEARCH_ENGINE);
+ if (startup_change && home_change) {
+ body += l10n_util::GetStringUTF16(
+ IDS_EXTENSIONS_SETTINGS_API_SECOND_LINE_START_AND_HOME);
+ } else if (startup_change) {
+ body += l10n_util::GetStringUTF16(
+ IDS_EXTENSIONS_SETTINGS_API_SECOND_LINE_START_PAGES);
+ } else if (home_change) {
+ body += l10n_util::GetStringUTF16(
+ IDS_EXTENSIONS_SETTINGS_API_SECOND_LINE_HOME_PAGE);
+ }
+ break;
+ }
+ if (!body.empty())
+ body += l10n_util::GetStringUTF16(
+ IDS_EXTENSIONS_SETTINGS_API_THIRD_LINE_CONFIRMATION);
+ return body;
+}
+
+base::string16 SettingsApiBubbleDelegate::GetOverflowText(
+ const base::string16& overflow_count) const {
+ // Does not have more than one extension in the list at a time.
+ NOTREACHED();
+ return base::string16();
+}
+
+base::string16 SettingsApiBubbleDelegate::GetLearnMoreLabel() const {
+ return l10n_util::GetStringUTF16(IDS_LEARN_MORE);
+}
+
+GURL SettingsApiBubbleDelegate::GetLearnMoreUrl() const {
+ return GURL(chrome::kSettingsApiLearnMoreURL);
+}
+
+base::string16 SettingsApiBubbleDelegate::GetActionButtonLabel() const {
+ return l10n_util::GetStringUTF16(
+ IDS_EXTENSIONS_SETTINGS_API_RESTORE_SETTINGS);
+}
+
+base::string16 SettingsApiBubbleDelegate::GetDismissButtonLabel() const {
+ return l10n_util::GetStringUTF16(IDS_EXTENSIONS_SETTINGS_API_KEEP_CHANGES);
+}
+
+bool SettingsApiBubbleDelegate::ShouldShowExtensionList() const {
+ return false;
+}
+
+void SettingsApiBubbleDelegate::LogExtensionCount(size_t count) {
+ UMA_HISTOGRAM_COUNTS_100("SettingsApiBubble.ExtensionCount", count);
+}
+
+void SettingsApiBubbleDelegate::LogAction(
+ ExtensionMessageBubbleController::BubbleAction action) {
+ UMA_HISTOGRAM_ENUMERATION("SettingsApiBubble.UserSelection",
+ action,
+ ExtensionMessageBubbleController::ACTION_BOUNDARY);
+}
+
+} // namespace
+
+namespace extensions {
+
+////////////////////////////////////////////////////////////////////////////////
+// SettingsApiBubbleController
+
+SettingsApiBubbleController::SettingsApiBubbleController(
+ Profile* profile,
+ SettingsApiOverrideType type)
+ : ExtensionMessageBubbleController(
+ new SettingsApiBubbleDelegate(
+ ExtensionSystem::Get(profile)->extension_service(),
+ profile,
+ type),
+ profile),
+ profile_(profile),
+ type_(type) {}
+
+SettingsApiBubbleController::~SettingsApiBubbleController() {}
+
+bool SettingsApiBubbleController::ShouldShow(const std::string& extension_id) {
+ extensions::ExtensionPrefs* prefs = extensions::ExtensionPrefs::Get(profile_);
+ if (prefs->HasSettingsApiBubbleBeenAcknowledged(extension_id))
+ return false;
+
+ if (!delegate()->ShouldIncludeExtension(extension_id))
+ return false;
+
+ // If the browser is showing the 'Chrome crashed' infobar, it won't be showing
+ // the startup pages, so there's no point in showing the bubble now.
+ if (type_ == BUBBLE_TYPE_STARTUP_PAGES)
+ return profile_->GetLastSessionExitType() != Profile::EXIT_CRASHED;
+
+ return true;
+}
+
+bool SettingsApiBubbleController::CloseOnDeactivate() {
+ // Startup bubbles tend to get lost in the focus storm that happens on
+ // startup. Other types should dismiss on focus loss.
+ return type_ != BUBBLE_TYPE_STARTUP_PAGES;
+}
+
+} // namespace extensions
diff --git a/chrome/browser/extensions/settings_api_bubble_controller.h b/chrome/browser/extensions/settings_api_bubble_controller.h
new file mode 100644
index 0000000..db734b4
--- /dev/null
+++ b/chrome/browser/extensions/settings_api_bubble_controller.h
@@ -0,0 +1,42 @@
+// ::Copyright (c) 2014 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_EXTENSIONS_SETTINGS_API_BUBBLE_CONTROLLER_H_
+#define CHROME_BROWSER_EXTENSIONS_SETTINGS_API_BUBBLE_CONTROLLER_H_
+
+#include <string>
+#include "chrome/browser/extensions/extension_message_bubble_controller.h"
+#include "chrome/common/extensions/manifest_handlers/settings_overrides_handler.h"
+
+class ExtensionService;
+
+namespace extensions {
+
+class SettingsApiBubble;
+
+class SettingsApiBubbleController : public ExtensionMessageBubbleController {
+ public:
+ SettingsApiBubbleController(Profile* profile, SettingsApiOverrideType type);
+ virtual ~SettingsApiBubbleController();
+
+ // Whether the controller knows that we should show the bubble for extension
+ // with |extension_id|. Returns true if so.
+ bool ShouldShow(const std::string& extension_id);
+
+ // ExtensionMessageBubbleController:
+ virtual bool CloseOnDeactivate() OVERRIDE;
+
+ private:
+ // A weak pointer to the profile we are associated with. Not owned by us.
+ Profile* profile_;
+
+ // The type of settings override this bubble will report on.
+ SettingsApiOverrideType type_;
+
+ DISALLOW_COPY_AND_ASSIGN(SettingsApiBubbleController);
+};
+
+} // namespace extensions
+
+#endif // CHROME_BROWSER_EXTENSIONS_SETTINGS_API_BUBBLE_CONTROLLER_H_
diff --git a/chrome/browser/ui/omnibox/omnibox_view.cc b/chrome/browser/ui/omnibox/omnibox_view.cc
index d5c6847..b39fe9f 100644
--- a/chrome/browser/ui/omnibox/omnibox_view.cc
+++ b/chrome/browser/ui/omnibox/omnibox_view.cc
@@ -119,10 +119,11 @@ void OmniboxView::OpenMatch(const AutocompleteMatch& match,
const base::string16& pasted_text,
size_t selected_line) {
// Invalid URLs such as chrome://history can end up here.
- if (match.destination_url.is_valid() && model_) {
- model_->OpenMatch(match, disposition, alternate_nav_url, pasted_text,
- selected_line);
- }
+ if (!match.destination_url.is_valid() || !model_)
+ return;
+ model_->OpenMatch(
+ match, disposition, alternate_nav_url, pasted_text, selected_line);
+ OnMatchOpened(match, model_->profile(), controller_->GetWebContents());
}
bool OmniboxView::IsEditingOrEmpty() const {
@@ -211,6 +212,10 @@ bool OmniboxView::IsIndicatingQueryRefinement() const {
return false;
}
+void OmniboxView::OnMatchOpened(const AutocompleteMatch& match,
+ Profile* profile,
+ content::WebContents* web_contents) const {}
+
OmniboxView::OmniboxView(Profile* profile,
OmniboxEditController* controller,
CommandUpdater* command_updater)
diff --git a/chrome/browser/ui/omnibox/omnibox_view.h b/chrome/browser/ui/omnibox/omnibox_view.h
index d88125c..6e538a7 100644
--- a/chrome/browser/ui/omnibox/omnibox_view.h
+++ b/chrome/browser/ui/omnibox/omnibox_view.h
@@ -232,6 +232,12 @@ class OmniboxView {
// only ever return true on mobile ports.
virtual bool IsIndicatingQueryRefinement() const;
+ // Called after a |match| has been opened for the given |profile| and
+ // |web_contents|.
+ virtual void OnMatchOpened(const AutocompleteMatch& match,
+ Profile* profile,
+ content::WebContents* web_contents) const;
+
// Returns |text| with any leading javascript schemas stripped.
static base::string16 StripJavascriptSchemas(const base::string16& text);
diff --git a/chrome/browser/ui/views/extensions/extension_message_bubble_view.cc b/chrome/browser/ui/views/extensions/extension_message_bubble_view.cc
index f4a982a..75392d6 100644
--- a/chrome/browser/ui/views/extensions/extension_message_bubble_view.cc
+++ b/chrome/browser/ui/views/extensions/extension_message_bubble_view.cc
@@ -9,12 +9,13 @@
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/extensions/dev_mode_bubble_controller.h"
#include "chrome/browser/extensions/extension_action_manager.h"
-#include "chrome/browser/extensions/extension_message_bubble.h"
#include "chrome/browser/extensions/extension_message_bubble_controller.h"
#include "chrome/browser/extensions/extension_service.h"
+#include "chrome/browser/extensions/settings_api_bubble_controller.h"
#include "chrome/browser/extensions/suspicious_extension_bubble_controller.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
+#include "chrome/browser/ui/views/settings_api_bubble_helper_views.h"
#include "chrome/browser/ui/views/toolbar/browser_actions_container.h"
#include "chrome/browser/ui/views/toolbar/browser_actions_container_observer.h"
#include "chrome/browser/ui/views/toolbar/toolbar_view.h"
@@ -23,20 +24,18 @@
#include "grit/locale_settings.h"
#include "ui/accessibility/ax_view_state.h"
#include "ui/base/resource/resource_bundle.h"
-#include "ui/views/bubble/bubble_delegate.h"
-#include "ui/views/controls/button/button.h"
#include "ui/views/controls/button/label_button.h"
#include "ui/views/controls/label.h"
#include "ui/views/controls/link.h"
-#include "ui/views/controls/link_listener.h"
#include "ui/views/layout/grid_layout.h"
#include "ui/views/view.h"
#include "ui/views/widget/widget.h"
-namespace extensions {
-
namespace {
+base::LazyInstance<std::set<Profile*> > g_profiles_evaluated =
+ LAZY_INSTANCE_INITIALIZER;
+
// Layout constants.
const int kExtensionListPadding = 10;
const int kInsetBottomRight = 13;
@@ -52,75 +51,15 @@ const size_t kMaxExtensionsToShow = 7;
// How long to wait until showing the bubble (in seconds).
const int kBubbleAppearanceWaitTime = 5;
-// This is a class that implements the UI for the bubble showing which
-// extensions look suspicious and have therefore been automatically disabled.
-class ExtensionMessageBubbleView : public ExtensionMessageBubble,
- public views::BubbleDelegateView,
- public views::ButtonListener,
- public views::LinkListener {
- public:
- ExtensionMessageBubbleView(
- views::View* anchor_view,
- scoped_ptr<ExtensionMessageBubbleController> controller);
-
- // ExtensionMessageBubble methods.
- virtual void OnActionButtonClicked(const base::Closure& callback) OVERRIDE;
- virtual void OnDismissButtonClicked(const base::Closure& callback) OVERRIDE;
- virtual void OnLinkClicked(const base::Closure& callback) OVERRIDE;
- virtual void Show() OVERRIDE;
-
- // WidgetObserver methods.
- virtual void OnWidgetDestroying(views::Widget* widget) OVERRIDE;
-
- private:
- virtual ~ExtensionMessageBubbleView();
-
- // Shows the bubble and updates the counter for how often it has been shown.
- void ShowBubble();
-
- // views::BubbleDelegateView overrides:
- virtual void Init() OVERRIDE;
-
- // views::ButtonListener implementation.
- virtual void ButtonPressed(views::Button* sender,
- const ui::Event& event) OVERRIDE;
-
- // views::LinkListener implementation.
- virtual void LinkClicked(views::Link* source, int event_flags) OVERRIDE;
-
- // views::View implementation.
- virtual void GetAccessibleState(ui::AXViewState* state) OVERRIDE;
- virtual void ViewHierarchyChanged(const ViewHierarchyChangedDetails& details)
- OVERRIDE;
-
- base::WeakPtrFactory<ExtensionMessageBubbleView> weak_factory_;
-
- // The controller for this bubble.
- scoped_ptr<ExtensionMessageBubbleController> controller_;
-
- // The headline, labels and buttons on the bubble.
- views::Label* headline_;
- views::Link* learn_more_;
- views::LabelButton* action_button_;
- views::LabelButton* dismiss_button_;
-
- // All actions (link, button, esc) close the bubble, but we need to
- // make sure we don't send dismiss if the link was clicked.
- bool link_clicked_;
- bool action_taken_;
-
- // Callbacks into the controller.
- base::Closure action_callback_;
- base::Closure dismiss_callback_;
- base::Closure link_callback_;
+} // namespace
- DISALLOW_COPY_AND_ASSIGN(ExtensionMessageBubbleView);
-};
+namespace extensions {
ExtensionMessageBubbleView::ExtensionMessageBubbleView(
views::View* anchor_view,
- scoped_ptr<ExtensionMessageBubbleController> controller)
- : BubbleDelegateView(anchor_view, views::BubbleBorder::TOP_RIGHT),
+ views::BubbleBorder::Arrow arrow_location,
+ scoped_ptr<extensions::ExtensionMessageBubbleController> controller)
+ : BubbleDelegateView(anchor_view, arrow_location),
weak_factory_(this),
controller_(controller.Pass()),
headline_(NULL),
@@ -129,7 +68,7 @@ ExtensionMessageBubbleView::ExtensionMessageBubbleView(
link_clicked_(false),
action_taken_(false) {
DCHECK(anchor_view->GetWidget());
- set_close_on_deactivate(false);
+ set_close_on_deactivate(controller_->CloseOnDeactivate());
set_move_with_anchor(true);
set_close_on_esc(true);
@@ -324,8 +263,6 @@ void ExtensionMessageBubbleView::ViewHierarchyChanged(
NotifyAccessibilityEvent(ui::AX_EVENT_ALERT, true);
}
-} // namespace
-
////////////////////////////////////////////////////////////////////////////////
// ExtensionMessageBubbleFactory
@@ -335,6 +272,7 @@ ExtensionMessageBubbleFactory::ExtensionMessageBubbleFactory(
: profile_(profile),
toolbar_view_(toolbar_view),
shown_suspicious_extensions_bubble_(false),
+ shown_startup_override_extensions_bubble_(false),
shown_dev_mode_extensions_bubble_(false),
is_observing_(false),
stage_(STAGE_START),
@@ -346,18 +284,28 @@ ExtensionMessageBubbleFactory::~ExtensionMessageBubbleFactory() {
}
void ExtensionMessageBubbleFactory::MaybeShow(views::View* anchor_view) {
- // The list of suspicious extensions takes priority over the dev mode bubble,
- // since that needs to be shown as soon as we disable something. The dev mode
- // bubble is not as time sensitive so we'll catch the dev mode extensions on
- // the next startup/next window that opens. That way, we're not too spammy
- // with the bubbles.
+ // The list of suspicious extensions takes priority over the dev mode bubble
+ // and the settings API bubble, since that needs to be shown as soon as we
+ // disable something. The settings API bubble is shown on first startup after
+ // an extension has changed the startup pages and it is acceptable if that
+ // waits until the next startup because of the suspicious extension bubble.
+ // The dev mode bubble is not time sensitive like the other two so we'll catch
+ // the dev mode extensions on the next startup/next window that opens. That
+ // way, we're not too spammy with the bubbles.
if (!shown_suspicious_extensions_bubble_) {
if (MaybeShowSuspiciousExtensionsBubble(anchor_view))
return;
}
+ if (!shown_startup_override_extensions_bubble_ &&
+ IsInitialProfileCheck(profile_->GetOriginalProfile()) &&
+ MaybeShowStartupOverrideExtensionsBubble(anchor_view))
+ return;
+
if (!shown_dev_mode_extensions_bubble_)
MaybeShowDevModeExtensionsBubble(anchor_view);
+
+ RecordProfileCheck(profile_->GetOriginalProfile());
}
bool ExtensionMessageBubbleFactory::MaybeShowSuspiciousExtensionsBubble(
@@ -374,6 +322,7 @@ bool ExtensionMessageBubbleFactory::MaybeShowSuspiciousExtensionsBubble(
suspicious_extensions.get();
ExtensionMessageBubbleView* bubble_delegate = new ExtensionMessageBubbleView(
anchor_view,
+ views::BubbleBorder::TOP_RIGHT,
suspicious_extensions.PassAs<ExtensionMessageBubbleController>());
views::BubbleDelegateView::CreateBubble(bubble_delegate);
@@ -382,6 +331,36 @@ bool ExtensionMessageBubbleFactory::MaybeShowSuspiciousExtensionsBubble(
return true;
}
+bool ExtensionMessageBubbleFactory::MaybeShowStartupOverrideExtensionsBubble(
+ views::View* anchor_view) {
+#if !defined(OS_WIN)
+ return false;
+#endif
+
+ DCHECK(!shown_startup_override_extensions_bubble_);
+
+ const Extension* extension = OverridesStartupPages(profile_, NULL);
+ if (!extension)
+ return false;
+
+ scoped_ptr<SettingsApiBubbleController> settings_api_bubble(
+ new SettingsApiBubbleController(profile_,
+ BUBBLE_TYPE_STARTUP_PAGES));
+ if (!settings_api_bubble->ShouldShow(extension->id()))
+ return false;
+
+ shown_startup_override_extensions_bubble_ = true;
+ SettingsApiBubbleController* weak_controller = settings_api_bubble.get();
+ ExtensionMessageBubbleView* bubble_delegate = new ExtensionMessageBubbleView(
+ anchor_view,
+ views::BubbleBorder::TOP_RIGHT,
+ settings_api_bubble.PassAs<ExtensionMessageBubbleController>());
+ views::BubbleDelegateView::CreateBubble(bubble_delegate);
+ weak_controller->Show(bubble_delegate);
+
+ return true;
+}
+
bool ExtensionMessageBubbleFactory::MaybeShowDevModeExtensionsBubble(
views::View* anchor_view) {
DCHECK(!shown_dev_mode_extensions_bubble_);
@@ -429,6 +408,14 @@ void ExtensionMessageBubbleFactory::MaybeStopObserving() {
}
}
+void ExtensionMessageBubbleFactory::RecordProfileCheck(Profile* profile) {
+ g_profiles_evaluated.Get().insert(profile);
+}
+
+bool ExtensionMessageBubbleFactory::IsInitialProfileCheck(Profile* profile) {
+ return g_profiles_evaluated.Get().count(profile) == 0;
+}
+
void ExtensionMessageBubbleFactory::OnBrowserActionsContainerAnimationEnded() {
MaybeStopObserving();
if (stage_ == STAGE_START) {
@@ -473,6 +460,7 @@ void ExtensionMessageBubbleFactory::ShowDevModeBubble() {
DevModeBubbleController* weak_controller = controller_.get();
ExtensionMessageBubbleView* bubble_delegate = new ExtensionMessageBubbleView(
anchor_view_,
+ views::BubbleBorder::TOP_RIGHT,
scoped_ptr<ExtensionMessageBubbleController>(controller_.release()));
views::BubbleDelegateView::CreateBubble(bubble_delegate);
weak_controller->Show(bubble_delegate);
diff --git a/chrome/browser/ui/views/extensions/extension_message_bubble_view.h b/chrome/browser/ui/views/extensions/extension_message_bubble_view.h
index 5043b5e..c615c18 100644
--- a/chrome/browser/ui/views/extensions/extension_message_bubble_view.h
+++ b/chrome/browser/ui/views/extensions/extension_message_bubble_view.h
@@ -8,18 +8,26 @@
#include "base/compiler_specific.h"
#include "base/macros.h"
#include "base/memory/scoped_ptr.h"
+#include "chrome/browser/extensions/extension_message_bubble.h"
#include "chrome/browser/ui/views/toolbar/browser_actions_container_observer.h"
+#include "ui/views/bubble/bubble_delegate.h"
+#include "ui/views/controls/button/button.h"
+#include "ui/views/controls/link_listener.h"
class Profile;
class BrowserActionsContainer;
class ToolbarView;
namespace views {
+class Label;
+class LabelButton;
class View;
}
namespace extensions {
+
class DevModeBubbleController;
+class ExtensionMessageBubbleController;
// Create and show ExtensionMessageBubbles for either extensions that look
// suspicious and have therefore been disabled, or for extensions that are
@@ -47,6 +55,11 @@ class ExtensionMessageBubbleFactory : public BrowserActionsContainerObserver {
// Returns true if we have show the view.
bool MaybeShowSuspiciousExtensionsBubble(views::View* anchor_view);
+ // Shows the settings API extensions bubble, if there are extensions
+ // overriding the startup pages and we have not done so already.
+ // Returns true if we show the view (or start the process).
+ bool MaybeShowStartupOverrideExtensionsBubble(views::View* anchor_view);
+
// Shows the developer mode extensions bubble, if there are extensions running
// in developer mode and we have not done so already.
// Returns true if we show the view (or start the process).
@@ -56,6 +69,12 @@ class ExtensionMessageBubbleFactory : public BrowserActionsContainerObserver {
void MaybeObserve();
void MaybeStopObserving();
+ // Adds |profile| to the list of profiles that have been evaluated for showing
+ // a bubble. Handy for things that only want to check once per profile.
+ void RecordProfileCheck(Profile* profile);
+ // Returns false if this profile has been evaluated before.
+ bool IsInitialProfileCheck(Profile* profile);
+
// BrowserActionsContainer::Observer implementation.
virtual void OnBrowserActionsContainerAnimationEnded() OVERRIDE;
virtual void OnBrowserActionsContainerDestroyed() OVERRIDE;
@@ -78,6 +97,10 @@ class ExtensionMessageBubbleFactory : public BrowserActionsContainerObserver {
// Whether or not we have shown the suspicious extensions bubble.
bool shown_suspicious_extensions_bubble_;
+ // Whether or not we have shown the Settings API extensions bubble notifying
+ // the user about the startup pages being overridden.
+ bool shown_startup_override_extensions_bubble_;
+
// Whether or not we have shown the developer mode extensions bubble.
bool shown_dev_mode_extensions_bubble_;
@@ -103,6 +126,71 @@ class ExtensionMessageBubbleFactory : public BrowserActionsContainerObserver {
DISALLOW_COPY_AND_ASSIGN(ExtensionMessageBubbleFactory);
};
+// This is a class that implements the UI for the bubble showing which
+// extensions look suspicious and have therefore been automatically disabled.
+class ExtensionMessageBubbleView : public ExtensionMessageBubble,
+ public views::BubbleDelegateView,
+ public views::ButtonListener,
+ public views::LinkListener {
+ public:
+ ExtensionMessageBubbleView(
+ views::View* anchor_view,
+ views::BubbleBorder::Arrow arrow_location,
+ scoped_ptr<ExtensionMessageBubbleController> controller);
+
+ // ExtensionMessageBubble methods.
+ virtual void OnActionButtonClicked(const base::Closure& callback) OVERRIDE;
+ virtual void OnDismissButtonClicked(const base::Closure& callback) OVERRIDE;
+ virtual void OnLinkClicked(const base::Closure& callback) OVERRIDE;
+ virtual void Show() OVERRIDE;
+
+ // WidgetObserver methods.
+ virtual void OnWidgetDestroying(views::Widget* widget) OVERRIDE;
+
+ private:
+ virtual ~ExtensionMessageBubbleView();
+
+ void ShowBubble();
+
+ // views::BubbleDelegateView overrides:
+ virtual void Init() OVERRIDE;
+
+ // views::ButtonListener implementation.
+ virtual void ButtonPressed(views::Button* sender,
+ const ui::Event& event) OVERRIDE;
+
+ // views::LinkListener implementation.
+ virtual void LinkClicked(views::Link* source, int event_flags) OVERRIDE;
+
+ // views::View implementation.
+ virtual void GetAccessibleState(ui::AXViewState* state) OVERRIDE;
+ virtual void ViewHierarchyChanged(const ViewHierarchyChangedDetails& details)
+ OVERRIDE;
+
+ base::WeakPtrFactory<ExtensionMessageBubbleView> weak_factory_;
+
+ // The controller for this bubble.
+ scoped_ptr<ExtensionMessageBubbleController> controller_;
+
+ // The headline, labels and buttons on the bubble.
+ views::Label* headline_;
+ views::Link* learn_more_;
+ views::LabelButton* action_button_;
+ views::LabelButton* dismiss_button_;
+
+ // All actions (link, button, esc) close the bubble, but we need to
+ // make sure we don't send dismiss if the link was clicked.
+ bool link_clicked_;
+ bool action_taken_;
+
+ // Callbacks into the controller.
+ base::Closure action_callback_;
+ base::Closure dismiss_callback_;
+ base::Closure link_callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(ExtensionMessageBubbleView);
+};
+
} // namespace extensions
#endif // CHROME_BROWSER_UI_VIEWS_EXTENSIONS_EXTENSION_MESSAGE_BUBBLE_VIEW_H_
diff --git a/chrome/browser/ui/views/omnibox/omnibox_view_views.cc b/chrome/browser/ui/views/omnibox/omnibox_view_views.cc
index 4c724b1..02c2d7a 100644
--- a/chrome/browser/ui/views/omnibox/omnibox_view_views.cc
+++ b/chrome/browser/ui/views/omnibox/omnibox_view_views.cc
@@ -21,6 +21,7 @@
#include "chrome/browser/ui/view_ids.h"
#include "chrome/browser/ui/views/location_bar/location_bar_view.h"
#include "chrome/browser/ui/views/omnibox/omnibox_popup_contents_view.h"
+#include "chrome/browser/ui/views/settings_api_bubble_helper_views.h"
#include "chrome/browser/ui/views/website_settings/website_settings_popup_view.h"
#include "chrome/common/chrome_switches.h"
#include "content/public/browser/web_contents.h"
@@ -720,6 +721,13 @@ void OmniboxViewViews::ShowImeIfNeeded() {
GetInputMethod()->ShowImeIfNeeded();
}
+void OmniboxViewViews::OnMatchOpened(const AutocompleteMatch& match,
+ Profile* profile,
+ content::WebContents* web_contents) const {
+ extensions::MaybeShowExtensionControlledSearchNotification(
+ profile, web_contents, match);
+}
+
bool OmniboxViewViews::IsCommandIdEnabled(int command_id) const {
if (command_id == IDS_APP_PASTE)
return !read_only() && !GetClipboardText().empty();
diff --git a/chrome/browser/ui/views/omnibox/omnibox_view_views.h b/chrome/browser/ui/views/omnibox/omnibox_view_views.h
index 350c7cf..311a5aa 100644
--- a/chrome/browser/ui/views/omnibox/omnibox_view_views.h
+++ b/chrome/browser/ui/views/omnibox/omnibox_view_views.h
@@ -110,6 +110,9 @@ class OmniboxViewViews
virtual bool IsImeComposing() const OVERRIDE;
virtual bool IsImeShowingPopup() const OVERRIDE;
virtual void ShowImeIfNeeded() OVERRIDE;
+ virtual void OnMatchOpened(const AutocompleteMatch& match,
+ Profile* profile,
+ content::WebContents* web_contents) const OVERRIDE;
virtual bool IsCommandIdEnabled(int command_id) const OVERRIDE;
virtual bool IsItemForCommandIdDynamic(int command_id) const OVERRIDE;
virtual base::string16 GetLabelForCommandId(int command_id) const OVERRIDE;
diff --git a/chrome/browser/ui/views/settings_api_bubble_helper_views.cc b/chrome/browser/ui/views/settings_api_bubble_helper_views.cc
new file mode 100644
index 0000000..5a91d26
--- /dev/null
+++ b/chrome/browser/ui/views/settings_api_bubble_helper_views.cc
@@ -0,0 +1,157 @@
+// Copyright (c) 2014 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/views/settings_api_bubble_helper_views.h"
+
+#include "chrome/browser/extensions/settings_api_bubble_controller.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_finder.h"
+#include "chrome/browser/ui/views/extensions/extension_message_bubble_view.h"
+#include "chrome/browser/ui/views/frame/browser_view.h"
+#include "chrome/browser/ui/views/settings_api_bubble_helper_views.h"
+#include "chrome/browser/ui/views/toolbar/home_button.h"
+#include "chrome/browser/ui/views/toolbar/toolbar_view.h"
+#include "chrome/common/extensions/manifest_handlers/settings_overrides_handler.h"
+#include "content/public/browser/browser_context.h"
+#include "extensions/browser/extension_registry.h"
+
+namespace {
+
+void ShowSettingsApiBubble(extensions::SettingsApiOverrideType type,
+ const std::string& extension_id,
+ Profile* profile,
+ views::View* anchor_view,
+ views::BubbleBorder::Arrow arrow) {
+ scoped_ptr<extensions::SettingsApiBubbleController> settings_api_bubble(
+ new extensions::SettingsApiBubbleController(profile, type));
+ if (!settings_api_bubble->ShouldShow(extension_id))
+ return;
+
+ extensions::SettingsApiBubbleController* controller =
+ settings_api_bubble.get();
+ extensions::ExtensionMessageBubbleView* bubble_delegate =
+ new extensions::ExtensionMessageBubbleView(
+ anchor_view,
+ arrow,
+ settings_api_bubble.PassAs<
+ extensions::ExtensionMessageBubbleController>());
+ views::BubbleDelegateView::CreateBubble(bubble_delegate);
+ controller->Show(bubble_delegate);
+}
+
+} // namespace
+
+namespace extensions {
+
+void MaybeShowExtensionControlledHomeNotification(Browser* browser) {
+#if !defined(OS_WIN)
+ return;
+#endif
+
+ const Extension* extension = OverridesHomepage(browser->profile(), NULL);
+ if (extension) {
+ // The bubble will try to anchor itself against the home button
+ views::View* anchor_view = BrowserView::GetBrowserViewForBrowser(browser)->
+ toolbar()->home_button();
+ ShowSettingsApiBubble(BUBBLE_TYPE_HOME_PAGE,
+ extension->id(),
+ browser->profile(),
+ anchor_view,
+ views::BubbleBorder::TOP_LEFT);
+ }
+}
+
+void MaybeShowExtensionControlledSearchNotification(
+ Profile* profile,
+ content::WebContents* web_contents,
+ const AutocompleteMatch& match) {
+#if !defined(OS_WIN)
+ return;
+#endif
+
+ if (match.provider &&
+ match.provider->type() == AutocompleteProvider::TYPE_SEARCH) {
+ const extensions::Extension* extension =
+ OverridesSearchEngine(profile, NULL);
+ if (extension) {
+ ToolbarView* toolbar =
+ BrowserView::GetBrowserViewForBrowser(
+ chrome::FindBrowserWithWebContents(web_contents))->toolbar();
+ ShowSettingsApiBubble(BUBBLE_TYPE_SEARCH_ENGINE,
+ extension->id(),
+ profile,
+ toolbar->app_menu(),
+ views::BubbleBorder::TOP_RIGHT);
+ }
+ }
+}
+
+const extensions::SettingsOverrides* FindOverridingExtension(
+ content::BrowserContext* browser_context,
+ SettingsApiOverrideType type,
+ const Extension** extension) {
+ const extensions::ExtensionSet& extensions =
+ extensions::ExtensionRegistry::Get(browser_context)->enabled_extensions();
+
+ for (extensions::ExtensionSet::const_iterator it = extensions.begin();
+ it != extensions.end();
+ ++it) {
+ const extensions::SettingsOverrides* settings =
+ extensions::SettingsOverrides::Get(*it);
+ if (settings) {
+ if ((type == BUBBLE_TYPE_HOME_PAGE && settings->homepage) ||
+ (type == BUBBLE_TYPE_STARTUP_PAGES &&
+ !settings->startup_pages.empty()) ||
+ (type == BUBBLE_TYPE_SEARCH_ENGINE && settings->search_engine)) {
+ *extension = *it;
+ return settings;
+ }
+ }
+ }
+
+ return NULL;
+}
+
+const Extension* OverridesHomepage(content::BrowserContext* browser_context,
+ GURL* home_page_url) {
+ const extensions::Extension* extension = NULL;
+ const extensions::SettingsOverrides* settings =
+ FindOverridingExtension(
+ browser_context, BUBBLE_TYPE_HOME_PAGE, &extension);
+ if (settings && home_page_url)
+ *home_page_url = *settings->homepage;
+ return extension;
+}
+
+const Extension* OverridesStartupPages(content::BrowserContext* browser_context,
+ std::vector<GURL>* startup_pages) {
+ const extensions::Extension* extension = NULL;
+ const extensions::SettingsOverrides* settings =
+ FindOverridingExtension(
+ browser_context, BUBBLE_TYPE_STARTUP_PAGES, &extension);
+ if (settings && startup_pages) {
+ startup_pages->clear();
+ for (std::vector<GURL>::const_iterator it = settings->startup_pages.begin();
+ it != settings->startup_pages.end();
+ ++it)
+ startup_pages->push_back(GURL(*it));
+ }
+ return extension;
+}
+
+const Extension* OverridesSearchEngine(
+ content::BrowserContext* browser_context,
+ api::manifest_types::ChromeSettingsOverrides::Search_provider*
+ search_provider) {
+ const extensions::Extension* extension = NULL;
+ const extensions::SettingsOverrides* settings =
+ FindOverridingExtension(
+ browser_context, BUBBLE_TYPE_SEARCH_ENGINE, &extension);
+ if (settings && search_provider)
+ search_provider = settings->search_engine.get();
+ return extension;
+}
+
+} // namespace extensions
diff --git a/chrome/browser/ui/views/settings_api_bubble_helper_views.h b/chrome/browser/ui/views/settings_api_bubble_helper_views.h
new file mode 100644
index 0000000..a36a806
--- /dev/null
+++ b/chrome/browser/ui/views/settings_api_bubble_helper_views.h
@@ -0,0 +1,64 @@
+// Copyright (c) 2014 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_VIEWS_SETTINGS_API_BUBBLE_HELPER_VIEWS_H_
+#define CHROME_BROWSER_UI_VIEWS_SETTINGS_API_BUBBLE_HELPER_VIEWS_H_
+
+#include "chrome/common/extensions/manifest_handlers/settings_overrides_handler.h"
+
+struct AutocompleteMatch;
+class Browser;
+class Profile;
+
+namespace content {
+class BrowserContext;
+class WebContents;
+}
+
+namespace extensions {
+
+// Shows a bubble notifying the user that the homepage is controlled by an
+// extension. This bubble is shown only on the first use of the Home button
+// after the controlling extension takes effect.
+void MaybeShowExtensionControlledHomeNotification(Browser* browser);
+
+// Shows a bubble notifying the user that the search engine is controlled by an
+// extension. This bubble is shown only on the first search after the
+// controlling extension takes effect.
+void MaybeShowExtensionControlledSearchNotification(
+ Profile* profile,
+ content::WebContents* web_contents,
+ const AutocompleteMatch& match);
+
+// Find which |extension| is overriding a particular |type| of setting. Returns
+// the SettingsOverride object, or NULL if no |extension| is overriding that
+// particular setting.
+const extensions::SettingsOverrides* FindOverridingExtension(
+ content::BrowserContext* browser_context,
+ SettingsApiOverrideType type,
+ const Extension** extension);
+
+// Returns which extension is overriding the homepage in a given
+// |browser_context|. |home_page_url|, if non-NULL, will contain the home_page
+// value the extension has set.
+const Extension* OverridesHomepage(content::BrowserContext* browser_context,
+ GURL* home_page_url);
+
+// Returns which extension is overriding the homepage in a given
+// |browser_context|. |startup_pages|, if non-NULL, will contain the vector of
+// startup page URLs the extension has set.
+const Extension* OverridesStartupPages(content::BrowserContext* browser_context,
+ std::vector<GURL>* startup_pages);
+
+// Returns which extension is overriding the search engine in a given
+// |browser_context|. |search_provider|, if non-NULL, will contain the search
+// provider the extension has set.
+const Extension* OverridesSearchEngine(
+ content::BrowserContext* browser_context,
+ api::manifest_types::ChromeSettingsOverrides::Search_provider*
+ search_provider);
+
+} // namespace extensions
+
+#endif // CHROME_BROWSER_UI_VIEWS_SETTINGS_API_BUBBLE_HELPER_VIEWS_H_
diff --git a/chrome/browser/ui/views/toolbar/home_button.cc b/chrome/browser/ui/views/toolbar/home_button.cc
index d3768ca..2c56077 100644
--- a/chrome/browser/ui/views/toolbar/home_button.cc
+++ b/chrome/browser/ui/views/toolbar/home_button.cc
@@ -7,6 +7,7 @@
#include "base/prefs/pref_service.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/views/settings_api_bubble_helper_views.h"
#include "chrome/common/pref_names.h"
#include "components/user_prefs/user_prefs.h"
#include "grit/generated_resources.h"
@@ -178,3 +179,8 @@ int HomeButton::OnPerformDrop(const ui::DropTargetEvent& event) {
}
return ui::DragDropTypes::DRAG_NONE;
}
+
+void HomeButton::NotifyClick(const ui::Event& event) {
+ ToolbarButton::NotifyClick(event);
+ extensions::MaybeShowExtensionControlledHomeNotification(browser_);
+}
diff --git a/chrome/browser/ui/views/toolbar/home_button.h b/chrome/browser/ui/views/toolbar/home_button.h
index b532182..014ab3b 100644
--- a/chrome/browser/ui/views/toolbar/home_button.h
+++ b/chrome/browser/ui/views/toolbar/home_button.h
@@ -16,7 +16,7 @@ class HomeButton : public ToolbarButton {
HomeButton(views::ButtonListener* listener, Browser* browser);
virtual ~HomeButton();
- // views::ImageButton:
+ // ToolbarButton:
virtual bool GetDropFormats(
int* formats,
std::set<OSExchangeData::CustomFormat>* custom_formats) OVERRIDE;
@@ -25,6 +25,9 @@ class HomeButton : public ToolbarButton {
virtual int OnPerformDrop(const ui::DropTargetEvent& event) OVERRIDE;
private:
+ // ToolbarButton:
+ virtual void NotifyClick(const ui::Event& event) OVERRIDE;
+
Browser* browser_;
DISALLOW_COPY_AND_ASSIGN(HomeButton);
diff --git a/chrome/browser/ui/views/toolbar/toolbar_view.h b/chrome/browser/ui/views/toolbar/toolbar_view.h
index 59f6557c..59a361c 100644
--- a/chrome/browser/ui/views/toolbar/toolbar_view.h
+++ b/chrome/browser/ui/views/toolbar/toolbar_view.h
@@ -105,6 +105,7 @@ class ToolbarView : public views::AccessiblePaneView,
LocationBarView* location_bar() const { return location_bar_; }
ToolbarOriginChipView* origin_chip() const { return origin_chip_view_; }
views::MenuButton* app_menu() const;
+ HomeButton* home_button() const { return home_; }
// Overridden from AccessiblePaneView
virtual bool SetPaneFocus(View* initial_focus) OVERRIDE;
diff --git a/chrome/chrome_browser_extensions.gypi b/chrome/chrome_browser_extensions.gypi
index 3d8aa51..15c4699 100644
--- a/chrome/chrome_browser_extensions.gypi
+++ b/chrome/chrome_browser_extensions.gypi
@@ -822,6 +822,8 @@
'browser/extensions/sandboxed_unpacker.h',
'browser/extensions/script_executor.cc',
'browser/extensions/script_executor.h',
+ 'browser/extensions/settings_api_bubble_controller.cc',
+ 'browser/extensions/settings_api_bubble_controller.h',
'browser/extensions/standard_management_policy_provider.cc',
'browser/extensions/standard_management_policy_provider.h',
'browser/extensions/startup_helper.cc',
diff --git a/chrome/chrome_browser_ui.gypi b/chrome/chrome_browser_ui.gypi
index 7f112b8..396df69 100644
--- a/chrome/chrome_browser_ui.gypi
+++ b/chrome/chrome_browser_ui.gypi
@@ -2113,6 +2113,8 @@
'browser/ui/views/select_file_dialog_extension.h',
'browser/ui/views/select_file_dialog_extension_factory.cc',
'browser/ui/views/select_file_dialog_extension_factory.h',
+ 'browser/ui/views/settings_api_bubble_helper_views.cc',
+ 'browser/ui/views/settings_api_bubble_helper_views.h',
'browser/ui/views/signed_certificate_timestamps_views.cc',
'browser/ui/views/signed_certificate_timestamps_views.h',
'browser/ui/views/signed_certificate_timestamp_info_view.cc',
diff --git a/chrome/common/extensions/manifest_handlers/settings_overrides_handler.cc b/chrome/common/extensions/manifest_handlers/settings_overrides_handler.cc
index 0909360b..b36b40a 100644
--- a/chrome/common/extensions/manifest_handlers/settings_overrides_handler.cc
+++ b/chrome/common/extensions/manifest_handlers/settings_overrides_handler.cc
@@ -10,7 +10,7 @@
#include "base/strings/utf_string_conversions.h"
#include "base/values.h"
#include "extensions/common/error_utils.h"
-#include "extensions/common/extension_messages.h"
+#include "extensions/common/extension_set.h"
#include "extensions/common/feature_switch.h"
#include "extensions/common/manifest_constants.h"
#include "extensions/common/permissions/api_permission_set.h"
@@ -20,6 +20,7 @@
#include "extensions/common/permissions/settings_override_permission.h"
#include "grit/generated_resources.h"
#include "ipc/ipc_message.h"
+#include "ipc/ipc_message_utils.h"
#include "ui/base/l10n/l10n_util.h"
#include "url/gurl.h"
@@ -214,12 +215,14 @@ SettingsOverrides::SettingsOverrides() {}
SettingsOverrides::~SettingsOverrides() {}
+// static
const SettingsOverrides* SettingsOverrides::Get(
const Extension* extension) {
return static_cast<SettingsOverrides*>(
extension->GetManifestData(manifest_keys::kSettingsOverride));
}
+// static
bool SettingsOverrides::RemovesBookmarkButton(
const SettingsOverrides& settings_overrides) {
return settings_overrides.bookmarks_ui &&
@@ -227,6 +230,7 @@ bool SettingsOverrides::RemovesBookmarkButton(
*settings_overrides.bookmarks_ui->remove_button;
}
+// static
bool SettingsOverrides::RemovesBookmarkShortcut(
const SettingsOverrides& settings_overrides) {
return settings_overrides.bookmarks_ui &&
diff --git a/chrome/common/extensions/manifest_handlers/settings_overrides_handler.h b/chrome/common/extensions/manifest_handlers/settings_overrides_handler.h
index 8172562..b819a88 100644
--- a/chrome/common/extensions/manifest_handlers/settings_overrides_handler.h
+++ b/chrome/common/extensions/manifest_handlers/settings_overrides_handler.h
@@ -11,6 +11,12 @@
namespace extensions {
+enum SettingsApiOverrideType {
+ BUBBLE_TYPE_HOME_PAGE = 0,
+ BUBBLE_TYPE_SEARCH_ENGINE,
+ BUBBLE_TYPE_STARTUP_PAGES,
+};
+
class ManifestPermission;
// SettingsOverride is associated with "chrome_settings_overrides" manifest key.
diff --git a/chrome/common/url_constants.cc b/chrome/common/url_constants.cc
index 3e103bd..2d253b7e 100644
--- a/chrome/common/url_constants.cc
+++ b/chrome/common/url_constants.cc
@@ -343,7 +343,6 @@ const char kSyncGoogleDashboardURL[] =
const char kAutoPasswordGenerationLearnMoreURL[] =
"https://support.google.com/chrome/?p=ui_generate_password";
-
const char kPasswordManagerLearnMoreURL[] =
#if defined(OS_CHROMEOS)
"https://support.google.com/chromeos/?p=settings_password";
@@ -351,6 +350,9 @@ const char kPasswordManagerLearnMoreURL[] =
"https://support.google.com/chrome/?p=settings_password";
#endif
+const char kSettingsApiLearnMoreURL[] =
+ "https://support.google.com/chrome/?p=ui_settings_api_extension";
+
const char kChromeHelpViaKeyboardURL[] =
#if defined(OS_CHROMEOS)
#if defined(OFFICIAL_BUILD)
diff --git a/chrome/common/url_constants.h b/chrome/common/url_constants.h
index 861f993..7294397 100644
--- a/chrome/common/url_constants.h
+++ b/chrome/common/url_constants.h
@@ -333,6 +333,9 @@ extern const char kAutoPasswordGenerationLearnMoreURL[];
extern const char kPasswordManagerLearnMoreURL[];
+// "Learn more" URL for the Settings API bubble.
+extern const char kSettingsApiLearnMoreURL[];
+
// General help links for Chrome, opened using various actions.
extern const char kChromeHelpViaKeyboardURL[];
extern const char kChromeHelpViaMenuURL[];
diff --git a/extensions/browser/extension_prefs.cc b/extensions/browser/extension_prefs.cc
index 67e5f73..99b975b 100644
--- a/extensions/browser/extension_prefs.cc
+++ b/extensions/browser/extension_prefs.cc
@@ -77,6 +77,7 @@ const char kPrefAcknowledgePromptCount[] = "ack_prompt_count";
const char kPrefExternalAcknowledged[] = "ack_external";
const char kPrefBlacklistAcknowledged[] = "ack_blacklist";
const char kPrefWipeoutAcknowledged[] = "ack_wiped";
+const char kPrefSettingsBubbleAcknowledged[] = "ack_settings_bubble";
// Indicates whether the external extension was installed during the first
// run of this profile.
@@ -696,6 +697,20 @@ void ExtensionPrefs::SetWipeoutAcknowledged(
value ? base::Value::CreateBooleanValue(value) : NULL);
}
+bool ExtensionPrefs::HasSettingsApiBubbleBeenAcknowledged(
+ const std::string& extension_id) {
+ return ReadPrefAsBooleanAndReturn(extension_id,
+ kPrefSettingsBubbleAcknowledged);
+}
+
+void ExtensionPrefs::SetSettingsApiBubbleBeenAcknowledged(
+ const std::string& extension_id,
+ bool value) {
+ UpdateExtensionPref(extension_id,
+ kPrefSettingsBubbleAcknowledged,
+ value ? base::Value::CreateBooleanValue(value) : NULL);
+}
+
bool ExtensionPrefs::SetAlertSystemFirstRun() {
if (prefs_->GetBoolean(pref_names::kAlertsInitialized)) {
return true;
diff --git a/extensions/browser/extension_prefs.h b/extensions/browser/extension_prefs.h
index 5fc2f83..66b3a6b 100644
--- a/extensions/browser/extension_prefs.h
+++ b/extensions/browser/extension_prefs.h
@@ -306,6 +306,13 @@ class ExtensionPrefs : public ExtensionScopedPrefs, public KeyedService {
bool HasWipeoutBeenAcknowledged(const std::string& extension_id);
void SetWipeoutAcknowledged(const std::string& extension_id, bool value);
+ // Whether the user has been notified about extension with |extension_id|
+ // taking over some aspect of the user's settings (homepage, startup pages,
+ // or search engine).
+ bool HasSettingsApiBubbleBeenAcknowledged(const std::string& extension_id);
+ void SetSettingsApiBubbleBeenAcknowledged(const std::string& extension_id,
+ bool value);
+
// Returns true if the extension notification code has already run for the
// first time for this profile. Currently we use this flag to mean that any
// extensions that would trigger notifications should get silently