Index: chrome/browser/extensions/extension_web_ui.cc |
diff --git a/chrome/browser/extensions/extension_web_ui.cc b/chrome/browser/extensions/extension_web_ui.cc |
index d5667eef16abd62360d90bb2ad3ccf8d46117314..ad28ac771f875bfaab70dbdc96072dfbed7e0acc 100644 |
--- a/chrome/browser/extensions/extension_web_ui.cc |
+++ b/chrome/browser/extensions/extension_web_ui.cc |
@@ -49,23 +49,96 @@ using extensions::URLOverrides; |
namespace { |
-// De-dupes the items in |list|. Assumes the values are strings. |
-void CleanUpDuplicates(base::ListValue* list) { |
- std::set<std::string> seen_values; |
- |
- // Loop backwards as we may be removing items. |
- for (size_t i = list->GetSize() - 1; (i + 1) > 0; --i) { |
- std::string value; |
- if (!list->GetString(i, &value)) { |
+// The key to the override value for a page. |
+const char kEntry[] = "entry"; |
+// The key to whether or not the override is active (i.e., can be used). |
+// Overrides may be inactive e.g. when an extension is disabled. |
+const char kActive[] = "active"; |
+ |
+// Iterates over |list| and: |
+// - Converts any entries of the form <entry> to |
+// { 'entry': <entry>, 'active': true }. |
+// - Removes any duplicate entries. |
+// We do the conversion because we previously stored these values as strings |
+// rather than objects. |
+// TODO(devlin): Remove the conversion once everyone's updated. |
+void InitializeOverridesList(base::ListValue* list) { |
+ base::ListValue migrated; |
+ std::set<std::string> seen_entries; |
+ for (base::Value* val : *list) { |
+ scoped_ptr<base::DictionaryValue> new_dict(new base::DictionaryValue()); |
+ std::string entry_name; |
+ base::DictionaryValue* existing_dict = nullptr; |
+ if (val->GetAsDictionary(&existing_dict)) { |
+ bool success = existing_dict->GetString(kEntry, &entry_name); |
+ CHECK(success); |
+ new_dict->Swap(existing_dict); |
+ } else if (val->GetAsString(&entry_name)) { |
+ new_dict->SetString(kEntry, entry_name); |
+ new_dict->SetBoolean(kActive, true); |
+ } else { |
NOTREACHED(); |
continue; |
} |
- if (seen_values.find(value) == seen_values.end()) |
- seen_values.insert(value); |
- else |
- list->Remove(i, NULL); |
+ if (seen_entries.count(entry_name) == 0) { |
+ seen_entries.insert(entry_name); |
+ migrated.Append(std::move(new_dict)); |
+ } |
} |
+ |
+ list->Swap(&migrated); |
+} |
+ |
+// Adds |override| to |list|, or, if there's already an entry for the override, |
+// marks it as active. |
+void AddOverridesToList(base::ListValue* list, |
+ const std::string& override) { |
+ for (base::Value* val : *list) { |
+ base::DictionaryValue* dict = nullptr; |
+ std::string entry; |
+ if (!val->GetAsDictionary(&dict) || !dict->GetString(kEntry, &entry)) { |
+ NOTREACHED(); |
+ continue; |
+ } |
+ if (entry == override) { |
+ dict->SetBoolean(kActive, true); |
+ return; // All done! |
+ } |
+ } |
+ |
+ scoped_ptr<base::DictionaryValue> dict(new base::DictionaryValue()); |
+ dict->SetString(kEntry, override); |
+ dict->SetBoolean(kActive, true); |
+ // Add the entry to the front of the list. |
+ list->Insert(0, dict.release()); |
+} |
+ |
+// Validates that each entry in |list| contains a valid url and points to an |
+// extension contained in |all_extensions| (and, if not, removes it). |
+void ValidateOverridesList(base::ListValue* list, |
+ const extensions::ExtensionSet& all_extensions) { |
+ base::ListValue migrated; |
+ for (base::Value* val : *list) { |
+ base::DictionaryValue* dict = nullptr; |
+ std::string entry; |
+ if (!val->GetAsDictionary(&dict) || !dict->GetString(kEntry, &entry)) { |
+ NOTREACHED(); |
+ continue; |
+ } |
+ scoped_ptr<base::DictionaryValue> new_dict(new base::DictionaryValue()); |
+ new_dict->Swap(dict); |
+ GURL override_url(entry); |
+ if (!override_url.is_valid()) |
+ continue; |
+ |
+ if (!all_extensions.GetByID(override_url.host())) |
+ continue; |
+ |
+ migrated.Append(std::move(new_dict)); |
+ } |
+ |
+ list->Swap(&migrated); |
} |
// Reloads the page in |web_contents| if it uses the same profile as |profile| |
@@ -88,6 +161,71 @@ void UnregisterAndReplaceOverrideForWebContents(const std::string& page, |
ui::PAGE_TRANSITION_RELOAD, std::string()); |
} |
+enum UpdateBehavior { |
+ UPDATE_DEACTIVATE, // Mark 'active' as false. |
+ UPDATE_REMOVE, // Remove the entry from the list. |
+}; |
+ |
+// Updates the entry (if any) for |override_url| in |overrides_list| according |
+// to |behavior|. Returns true if anything changed. |
+bool UpdateOverridesList(base::ListValue* overrides_list, |
+ const std::string& override_url, |
+ UpdateBehavior behavior) { |
+ base::ListValue::iterator iter = |
+ std::find_if(overrides_list->begin(), overrides_list->end(), |
+ [&override_url](const base::Value* value) { |
+ std::string entry; |
+ const base::DictionaryValue* dict = nullptr; |
+ return value->GetAsDictionary(&dict) && |
+ dict->GetString(kEntry, &entry) && |
+ entry == override_url; |
+ }); |
+ if (iter != overrides_list->end()) { |
+ switch (behavior) { |
+ case UPDATE_DEACTIVATE: { |
+ base::DictionaryValue* dict = nullptr; |
+ bool success = (*iter)->GetAsDictionary(&dict); |
+ CHECK(success); |
+ dict->SetBoolean(kActive, false); |
+ break; |
+ } |
+ case UPDATE_REMOVE: |
+ overrides_list->Erase(iter, nullptr); |
+ break; |
+ } |
+ return true; |
+ } |
+ return false; |
+} |
+ |
+// Updates each list referenced in |overrides| according to |behavior|. |
+void UpdateOverridesLists(Profile* profile, |
+ const URLOverrides::URLOverrideMap& overrides, |
+ UpdateBehavior behavior) { |
+ if (overrides.empty()) |
+ return; |
+ PrefService* prefs = profile->GetPrefs(); |
+ DictionaryPrefUpdate update(prefs, ExtensionWebUI::kExtensionURLOverrides); |
+ base::DictionaryValue* all_overrides = update.Get(); |
+ for (const auto& page_override_pair : overrides) { |
+ base::ListValue* page_overrides = nullptr; |
+ // If it's being unregistered, it should already be in the list. |
+ if (!all_overrides->GetList(page_override_pair.first, &page_overrides)) { |
+ NOTREACHED(); |
+ continue; |
+ } |
+ if (UpdateOverridesList(page_overrides, page_override_pair.second.spec(), |
+ behavior)) { |
+ // This is the active override, so we need to find all existing |
+ // tabs for this override and get them to reload the original URL. |
+ base::Callback<void(WebContents*)> callback = |
+ base::Bind(&UnregisterAndReplaceOverrideForWebContents, |
+ page_override_pair.first, profile); |
+ extensions::ExtensionTabUtil::ForEachTab(callback); |
+ } |
+ } |
+} |
+ |
// Run favicon callbck with image result. If no favicon was available then |
// |image| will be empty. |
void RunFaviconCallbackAsync( |
@@ -129,8 +267,12 @@ bool ValidateOverrideURL(const base::Value* override_url_value, |
const extensions::ExtensionSet& extensions, |
GURL* override_url, |
const Extension** extension) { |
+ const base::DictionaryValue* dict = nullptr; |
std::string override; |
- if (!override_url_value || !override_url_value->GetAsString(&override)) { |
+ bool is_active = false; |
+ if (!override_url_value || !override_url_value->GetAsDictionary(&dict) || |
+ !dict->GetBoolean(kActive, &is_active) || !is_active || |
+ !dict->GetString(kEntry, &override)) { |
return false; |
} |
if (!source_url.query().empty()) |
@@ -226,10 +368,7 @@ bool ExtensionWebUI::HandleChromeURLOverride( |
const Extension* extension; |
if (!ValidateOverrideURL( |
val, *url, extensions, &override_url, &extension)) { |
- LOG(WARNING) << "Invalid chrome URL override"; |
- UnregisterChromeURLOverride(url_host, profile, val); |
- // The above Unregister call will remove this item from url_list. |
- --i; |
+ // Invalid overrides are cleaned up on startup. |
continue; |
} |
@@ -301,108 +440,85 @@ bool ExtensionWebUI::HandleChromeURLOverrideReverse( |
} |
// static |
-void ExtensionWebUI::RegisterChromeURLOverrides( |
- Profile* profile, const URLOverrides::URLOverrideMap& overrides) { |
- if (overrides.empty()) |
- return; |
- |
+void ExtensionWebUI::InitializeChromeURLOverrides(Profile* profile) { |
PrefService* prefs = profile->GetPrefs(); |
DictionaryPrefUpdate update(prefs, kExtensionURLOverrides); |
base::DictionaryValue* all_overrides = update.Get(); |
- // For each override provided by the extension, add it to the front of |
- // the override list if it's not already in the list. |
- URLOverrides::URLOverrideMap::const_iterator iter = overrides.begin(); |
- for (; iter != overrides.end(); ++iter) { |
- const std::string& key = iter->first; |
- base::ListValue* page_overrides = NULL; |
- if (!all_overrides->GetList(key, &page_overrides)) { |
- page_overrides = new base::ListValue(); |
- all_overrides->Set(key, page_overrides); |
- } else { |
- CleanUpDuplicates(page_overrides); |
- |
- // Verify that the override isn't already in the list. |
- base::ListValue::iterator i = page_overrides->begin(); |
- for (; i != page_overrides->end(); ++i) { |
- std::string override_val; |
- if (!(*i)->GetAsString(&override_val)) { |
- NOTREACHED(); |
- continue; |
- } |
- if (override_val == iter->second.spec()) |
- break; |
- } |
- // This value is already in the list, leave it alone. |
- if (i != page_overrides->end()) |
- continue; |
- } |
- // Insert the override at the front of the list. Last registered override |
- // wins. |
- page_overrides->Insert(0, new base::StringValue(iter->second.spec())); |
+ // DictionaryValue::Iterator cannot be used to modify the list. Generate the |
+ // set of keys instead. |
+ std::vector<std::string> keys; |
+ for (base::DictionaryValue::Iterator iter(*all_overrides); |
+ !iter.IsAtEnd(); iter.Advance()) { |
+ keys.push_back(iter.key()); |
} |
-} |
- |
-// static |
-void ExtensionWebUI::UnregisterAndReplaceOverride(const std::string& page, |
- Profile* profile, |
- base::ListValue* list, |
- const base::Value* override) { |
- size_t index = 0; |
- bool found = list->Remove(*override, &index); |
- if (found && index == 0) { |
- // This is the active override, so we need to find all existing |
- // tabs for this override and get them to reload the original URL. |
- base::Callback<void(WebContents*)> callback = |
- base::Bind(&UnregisterAndReplaceOverrideForWebContents, page, profile); |
- extensions::ExtensionTabUtil::ForEachTab(callback); |
+ for (const std::string& key : keys) { |
+ base::ListValue* list = nullptr; |
+ bool success = all_overrides->GetList(key, &list); |
+ CHECK(success); |
+ InitializeOverridesList(list); |
} |
} |
// static |
-void ExtensionWebUI::UnregisterChromeURLOverride(const std::string& page, |
- Profile* profile, |
- const base::Value* override) { |
- if (!override) |
- return; |
+void ExtensionWebUI::ValidateChromeURLOverrides(Profile* profile) { |
+ scoped_ptr<extensions::ExtensionSet> all_extensions = |
+ extensions::ExtensionRegistry::Get(profile)-> |
+ GenerateInstalledExtensionsSet(); |
+ |
PrefService* prefs = profile->GetPrefs(); |
DictionaryPrefUpdate update(prefs, kExtensionURLOverrides); |
base::DictionaryValue* all_overrides = update.Get(); |
- base::ListValue* page_overrides = NULL; |
- if (!all_overrides->GetList(page, &page_overrides)) { |
- // If it's being unregistered, it should already be in the list. |
- NOTREACHED(); |
- return; |
- } else { |
- UnregisterAndReplaceOverride(page, profile, page_overrides, override); |
+ |
+ // DictionaryValue::Iterator cannot be used to modify the list. Generate the |
+ // set of keys instead. |
+ std::vector<std::string> keys; |
+ for (base::DictionaryValue::Iterator iter(*all_overrides); |
+ !iter.IsAtEnd(); iter.Advance()) { |
+ keys.push_back(iter.key()); |
+ } |
+ for (const std::string& key : keys) { |
+ base::ListValue* list = nullptr; |
+ bool success = all_overrides->GetList(key, &list); |
+ CHECK(success); |
+ ValidateOverridesList(list, *all_extensions); |
} |
} |
// static |
-void ExtensionWebUI::UnregisterChromeURLOverrides( |
- Profile* profile, const URLOverrides::URLOverrideMap& overrides) { |
+void ExtensionWebUI::RegisterOrActivateChromeURLOverrides( |
+ Profile* profile, |
+ const URLOverrides::URLOverrideMap& overrides) { |
if (overrides.empty()) |
return; |
PrefService* prefs = profile->GetPrefs(); |
DictionaryPrefUpdate update(prefs, kExtensionURLOverrides); |
base::DictionaryValue* all_overrides = update.Get(); |
- URLOverrides::URLOverrideMap::const_iterator iter = overrides.begin(); |
- for (; iter != overrides.end(); ++iter) { |
- const std::string& page = iter->first; |
- base::ListValue* page_overrides = NULL; |
- if (!all_overrides->GetList(page, &page_overrides)) { |
- // If it's being unregistered, it should already be in the list. |
- NOTREACHED(); |
- continue; |
- } else { |
- base::StringValue override(iter->second.spec()); |
- UnregisterAndReplaceOverride(iter->first, profile, |
- page_overrides, &override); |
+ for (const auto& page_override_pair : overrides) { |
+ base::ListValue* page_overrides = nullptr; |
+ if (!all_overrides->GetList(page_override_pair.first, &page_overrides)) { |
+ page_overrides = new base::ListValue(); |
+ all_overrides->Set(page_override_pair.first, page_overrides); |
} |
+ AddOverridesToList(page_overrides, page_override_pair.second.spec()); |
} |
} |
// static |
+void ExtensionWebUI::DeactivateChromeURLOverrides( |
+ Profile* profile, |
+ const URLOverrides::URLOverrideMap& overrides) { |
+ UpdateOverridesLists(profile, overrides, UPDATE_DEACTIVATE); |
+} |
+ |
+// static |
+void ExtensionWebUI::UnregisterChromeURLOverrides( |
+ Profile* profile, |
+ const URLOverrides::URLOverrideMap& overrides) { |
+ UpdateOverridesLists(profile, overrides, UPDATE_REMOVE); |
+} |
+ |
+// static |
void ExtensionWebUI::GetFaviconForURL( |
Profile* profile, |
const GURL& page_url, |