blob: 30fe8a252c41f9de93ffcfe6f43d790f593f0892 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/extensions/user_script_listener.h"
#include "base/bind.h"
#include "base/macros.h"
#include "base/metrics/histogram_macros.h"
#include "base/timer/elapsed_timer.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/navigation_throttle.h"
#include "content/public/browser/notification_service.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/common/extension.h"
#include "extensions/common/manifest_handlers/content_scripts_handler.h"
#include "extensions/common/url_pattern.h"
using content::BrowserThread;
using content::NavigationThrottle;
using content::ResourceType;
namespace extensions {
class UserScriptListener::Throttle
: public NavigationThrottle,
public base::SupportsWeakPtr<UserScriptListener::Throttle> {
public:
explicit Throttle(content::NavigationHandle* navigation_handle)
: NavigationThrottle(navigation_handle) {}
void ResumeIfDeferred() {
DCHECK(should_defer_);
should_defer_ = false;
// Only resume the request if |this| has deferred it.
if (did_defer_) {
UMA_HISTOGRAM_TIMES("Extensions.ThrottledNetworkRequestDelay",
timer_->Elapsed());
Resume();
}
}
// NavigationThrottle implementation:
ThrottleCheckResult WillStartRequest() override {
// Only defer requests if Resume has not yet been called.
if (should_defer_) {
did_defer_ = true;
timer_.reset(new base::ElapsedTimer());
return DEFER;
}
return PROCEED;
}
const char* GetNameForLogging() override {
return "UserScriptListener::Throttle";
}
private:
bool should_defer_ = true;
bool did_defer_ = false;
std::unique_ptr<base::ElapsedTimer> timer_;
DISALLOW_COPY_AND_ASSIGN(Throttle);
};
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() : extension_registry_observer_(this) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
// Profile manager can be null in unit tests.
if (g_browser_process->profile_manager()) {
for (auto* profile :
g_browser_process->profile_manager()->GetLoadedProfiles()) {
extension_registry_observer_.Add(ExtensionRegistry::Get(profile));
}
}
registrar_.Add(this, chrome::NOTIFICATION_PROFILE_ADDED,
content::NotificationService::AllSources());
registrar_.Add(this, chrome::NOTIFICATION_PROFILE_DESTROYED,
content::NotificationService::AllSources());
registrar_.Add(this,
extensions::NOTIFICATION_USER_SCRIPTS_UPDATED,
content::NotificationService::AllSources());
}
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::SetUserScriptsNotReadyForTesting(
content::BrowserContext* context) {
AppendNewURLPatterns(context, {URLPattern(URLPattern::SCHEME_ALL,
URLPattern::kAllUrlsPattern)});
}
UserScriptListener::~UserScriptListener() {}
bool UserScriptListener::ShouldDelayRequest(const GURL& url) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
// 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() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
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_CURRENTLY_ON(BrowserThread::UI);
profile_data_[context].user_scripts_ready = true;
CheckIfAllUserScriptsReady();
}
void UserScriptListener::ProfileDestroyed(content::BrowserContext* context) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
profile_data_.erase(context);
// We may have deleted the only profile we were waiting on.
CheckIfAllUserScriptsReady();
}
void UserScriptListener::AppendNewURLPatterns(content::BrowserContext* context,
const URLPatterns& new_patterns) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
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_CURRENTLY_ON(BrowserThread::UI);
ProfileData& data = profile_data_[context];
data.url_patterns = patterns;
}
void UserScriptListener::CollectURLPatterns(const Extension* extension,
URLPatterns* patterns) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
for (const std::unique_ptr<UserScript>& script :
ContentScriptsInfo::GetContentScripts(extension)) {
patterns->insert(patterns->end(), script->url_patterns().begin(),
script->url_patterns().end());
}
}
void UserScriptListener::Observe(int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
switch (type) {
case chrome::NOTIFICATION_PROFILE_ADDED: {
auto* registry =
ExtensionRegistry::Get(content::Source<Profile>(source).ptr());
DCHECK(!extension_registry_observer_.IsObserving(registry));
extension_registry_observer_.Add(registry);
break;
}
case chrome::NOTIFICATION_PROFILE_DESTROYED: {
Profile* profile = content::Source<Profile>(source).ptr();
ProfileDestroyed(profile);
break;
}
case extensions::NOTIFICATION_USER_SCRIPTS_UPDATED: {
Profile* profile = content::Source<Profile>(source).ptr();
UserScriptsReady(profile);
break;
}
default:
NOTREACHED();
}
}
void UserScriptListener::OnExtensionLoaded(
content::BrowserContext* browser_context,
const Extension* extension) {
if (ContentScriptsInfo::GetContentScripts(extension).empty())
return; // no new patterns from this extension.
URLPatterns new_patterns;
CollectURLPatterns(extension, &new_patterns);
if (!new_patterns.empty()) {
AppendNewURLPatterns(browser_context, new_patterns);
}
}
void UserScriptListener::OnExtensionUnloaded(
content::BrowserContext* browser_context,
const Extension* extension,
UnloadedExtensionReason reason) {
if (ContentScriptsInfo::GetContentScripts(extension).empty())
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(it->get(), &new_patterns);
}
ReplaceURLPatterns(browser_context, new_patterns);
}
void UserScriptListener::OnShutdown(ExtensionRegistry* registry) {
extension_registry_observer_.Remove(registry);
}
} // namespace extensions