blob: c190763094123f19f5a53e6029862fa77a58e4d3 [file] [log] [blame]
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/extensions/user_script_listener.h"
#include <memory>
#include "base/functional/bind.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/extensions/chrome_content_browser_client_extensions_part.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/navigation_throttle.h"
#include "extensions/browser/api/scripting/scripting_utils.h"
#include "extensions/browser/extension_system.h"
#include "extensions/browser/user_script_manager.h"
#include "extensions/common/extension.h"
#include "extensions/common/manifest_handlers/content_scripts_handler.h"
#include "extensions/common/url_pattern.h"
#include "extensions/common/url_pattern_set.h"
using content::NavigationThrottle;
namespace extensions {
class UserScriptListener::Throttle : public NavigationThrottle {
public:
explicit Throttle(content::NavigationHandle* navigation_handle)
: NavigationThrottle(navigation_handle) {}
Throttle(const Throttle&) = delete;
Throttle& operator=(const Throttle&) = delete;
void ResumeIfDeferred() {
DCHECK(should_defer_);
should_defer_ = false;
// Only resume the request if |this| has deferred it.
if (did_defer_) {
Resume();
}
}
// NavigationThrottle implementation:
ThrottleCheckResult WillStartRequest() override {
// Only defer requests if Resume has not yet been called.
if (should_defer_) {
did_defer_ = true;
return DEFER;
}
return PROCEED;
}
const char* GetNameForLogging() override {
return "UserScriptListener::Throttle";
}
base::WeakPtr<Throttle> AsWeakPtr() { return weak_ptr_factory_.GetWeakPtr(); }
private:
bool should_defer_ = true;
bool did_defer_ = false;
base::WeakPtrFactory<Throttle> weak_ptr_factory_{this};
};
struct UserScriptListener::ProfileData {
// True if the user scripts contained in |url_patterns| are ready for
// injection.
bool user_scripts_ready = false;
// A list of URL patterns that have will have user scripts applied to them.
URLPatterns url_patterns;
};
UserScriptListener::UserScriptListener() {
// Profile manager can be null in unit tests.
if (g_browser_process->profile_manager()) {
for (auto* profile :
g_browser_process->profile_manager()->GetLoadedProfiles()) {
// Some profiles cannot have extensions, such as the System Profile.
if (ChromeContentBrowserClientExtensionsPart::
AreExtensionsDisabledForProfile(profile)) {
continue;
}
extension_registry_observations_.AddObservation(
ExtensionRegistry::Get(profile));
}
profile_manager_observation_.Observe(g_browser_process->profile_manager());
}
}
std::unique_ptr<NavigationThrottle>
UserScriptListener::CreateNavigationThrottle(
content::NavigationHandle* navigation_handle) {
if (!ShouldDelayRequest(navigation_handle->GetURL()))
return nullptr;
auto throttle = std::make_unique<Throttle>(navigation_handle);
throttles_.push_back(throttle->AsWeakPtr());
return throttle;
}
void UserScriptListener::OnScriptsLoaded(content::BrowserContext* context) {
UserScriptsReady(context);
}
void UserScriptListener::StartTearDown() {
profile_manager_observation_.Reset();
}
void UserScriptListener::SetUserScriptsNotReadyForTesting(
content::BrowserContext* context) {
AppendNewURLPatterns(context, {URLPattern(URLPattern::SCHEME_ALL,
URLPattern::kAllUrlsPattern)});
}
void UserScriptListener::TriggerUserScriptsReadyForTesting(
content::BrowserContext* context) {
UserScriptsReady(context);
}
UserScriptListener::~UserScriptListener() {}
bool UserScriptListener::ShouldDelayRequest(const GURL& url) {
// Note: we could delay only requests made by the profile who is causing the
// delay, but it's a little more complicated to associate requests with the
// right profile. Since this is a rare case, we'll just take the easy way
// out.
if (user_scripts_ready_)
return false;
for (ProfileDataMap::const_iterator pt = profile_data_.begin();
pt != profile_data_.end(); ++pt) {
for (auto it = pt->second.url_patterns.begin();
it != pt->second.url_patterns.end(); ++it) {
if ((*it).MatchesURL(url)) {
// One of the user scripts wants to inject into this request, but the
// script isn't ready yet. Delay the request.
return true;
}
}
}
return false;
}
void UserScriptListener::StartDelayedRequests() {
WeakThrottleList::const_iterator it;
for (it = throttles_.begin(); it != throttles_.end(); ++it) {
if (it->get())
(*it)->ResumeIfDeferred();
}
throttles_.clear();
}
void UserScriptListener::CheckIfAllUserScriptsReady() {
bool was_ready = user_scripts_ready_;
user_scripts_ready_ = true;
for (ProfileDataMap::const_iterator it = profile_data_.begin();
it != profile_data_.end(); ++it) {
if (!it->second.user_scripts_ready)
user_scripts_ready_ = false;
}
if (user_scripts_ready_ && !was_ready)
StartDelayedRequests();
}
void UserScriptListener::UserScriptsReady(content::BrowserContext* context) {
DCHECK(!context->IsOffTheRecord());
profile_data_[context].user_scripts_ready = true;
CheckIfAllUserScriptsReady();
}
void UserScriptListener::AppendNewURLPatterns(content::BrowserContext* context,
const URLPatterns& new_patterns) {
DCHECK(!context->IsOffTheRecord());
user_scripts_ready_ = false;
ProfileData& data = profile_data_[context];
data.user_scripts_ready = false;
data.url_patterns.insert(data.url_patterns.end(),
new_patterns.begin(), new_patterns.end());
}
void UserScriptListener::ReplaceURLPatterns(content::BrowserContext* context,
const URLPatterns& patterns) {
DCHECK_EQ(1U, profile_data_.count(context));
profile_data_[context].url_patterns = patterns;
}
void UserScriptListener::CollectURLPatterns(content::BrowserContext* context,
const Extension* extension,
URLPatterns* patterns) {
for (const std::unique_ptr<UserScript>& script :
ContentScriptsInfo::GetContentScripts(extension)) {
patterns->insert(patterns->end(), script->url_patterns().begin(),
script->url_patterns().end());
}
// Retrieve patterns from persistent dynamic user scripts.
// TODO(crbug.com/40205839): Intersect these patterns with the extension's
// host permissions.
URLPatternSet dynamic_patterns =
scripting::GetPersistentScriptURLPatterns(context, extension->id());
patterns->insert(patterns->end(), dynamic_patterns.begin(),
dynamic_patterns.end());
}
void UserScriptListener::OnProfileAdded(Profile* profile) {
if (ChromeContentBrowserClientExtensionsPart::AreExtensionsDisabledForProfile(
profile)) {
return;
}
auto* registry = ExtensionRegistry::Get(profile);
DCHECK(registry);
DCHECK(!extension_registry_observations_.IsObservingSource(registry));
extension_registry_observations_.AddObservation(registry);
}
void UserScriptListener::OnExtensionLoaded(
content::BrowserContext* browser_context,
const Extension* extension) {
URLPatterns new_patterns;
CollectURLPatterns(browser_context, extension, &new_patterns);
if (new_patterns.empty())
return; // No new patterns from this extension.
AppendNewURLPatterns(browser_context, new_patterns);
}
void UserScriptListener::OnExtensionUnloaded(
content::BrowserContext* browser_context,
const Extension* extension,
UnloadedExtensionReason reason) {
// It's possible to unload extensions before loading extensions when the
// ExtensionService uninstalls an orphaned extension. In this case we don't
// need to update |profile_data_|. See crbug.com/1036028
if (profile_data_.count(browser_context) == 0)
return;
// TODO(crbug.com/40206239): These patterns may have changed since the
// extension was loaded as they are associated with dynamic scripts. Once this
// class is split so URLPatterns are maintained per (profile, extension), we
// would only look up these patterns when the extension is loaded.
bool has_persistent_dynamic_scripts =
!scripting::GetPersistentScriptURLPatterns(browser_context,
extension->id())
.is_empty();
if (ContentScriptsInfo::GetContentScripts(extension).empty() &&
!has_persistent_dynamic_scripts) {
return; // No patterns to delete for this extension.
}
// Clear all our patterns and reregister all the still-loaded extensions.
const ExtensionSet& extensions =
ExtensionRegistry::Get(browser_context)->enabled_extensions();
URLPatterns new_patterns;
for (ExtensionSet::const_iterator it = extensions.begin();
it != extensions.end(); ++it) {
if (it->get() != extension)
CollectURLPatterns(browser_context, it->get(), &new_patterns);
}
ReplaceURLPatterns(browser_context, new_patterns);
}
void UserScriptListener::OnShutdown(ExtensionRegistry* registry) {
extension_registry_observations_.RemoveObservation(registry);
}
} // namespace extensions