text: // Copyright 2015 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/api/declarative_content/declarative_content_css_condition_tracker.h"
  
  #include <stddef.h>
  
  #include "base/memory/ptr_util.h"
  #include "base/stl_util.h"
  #include "base/strings/stringprintf.h"
  #include "base/values.h"
  #include "chrome/browser/chrome_notification_types.h"
  #include "chrome/browser/extensions/api/declarative_content/content_constants.h"
  #include "chrome/browser/extensions/api/declarative_content/content_predicate_evaluator.h"
  #include "chrome/browser/profiles/profile.h"
  #include "content/public/browser/navigation_details.h"
  #include "content/public/browser/notification_service.h"
  #include "content/public/browser/notification_source.h"
  #include "content/public/browser/render_process_host.h"
  #include "extensions/common/extension_messages.h"
  #include "ipc/ipc_message.h"
  #include "ipc/ipc_message_macros.h"
  
  namespace extensions {
  
  namespace {
  
  const char kInvalidTypeOfParameter[] = "Attribute '%s' has an invalid type";
  
  }  // namespace
  
  //
  // DeclarativeContentCssPredicate
  //
  
  DeclarativeContentCssPredicate::~DeclarativeContentCssPredicate() {
  }
  
  // static
  std::unique_ptr<DeclarativeContentCssPredicate>
  DeclarativeContentCssPredicate::Create(ContentPredicateEvaluator* evaluator,
                                         const base::Value& value,
                                         std::string* error) {
    std::vector<std::string> css_rules;
    const base::ListValue* css_rules_value = nullptr;
    if (value.GetAsList(&css_rules_value)) {
      for (size_t i = 0; i < css_rules_value->GetSize(); ++i) {
        std::string css_rule;
        if (!css_rules_value->GetString(i, &css_rule)) {
          *error = base::StringPrintf(kInvalidTypeOfParameter,
                                      declarative_content_constants::kCss);
          return std::unique_ptr<DeclarativeContentCssPredicate>();
        }
        css_rules.push_back(css_rule);
      }
    } else {
      *error = base::StringPrintf(kInvalidTypeOfParameter,
                                  declarative_content_constants::kCss);
      return std::unique_ptr<DeclarativeContentCssPredicate>();
    }
  
    return !css_rules.empty()
               ? base::WrapUnique(
                     new DeclarativeContentCssPredicate(evaluator, css_rules))
               : std::unique_ptr<DeclarativeContentCssPredicate>();
  }
  
  ContentPredicateEvaluator*
  DeclarativeContentCssPredicate::GetEvaluator() const {
    return evaluator_;
  }
  
  DeclarativeContentCssPredicate::DeclarativeContentCssPredicate(
      ContentPredicateEvaluator* evaluator,
      const std::vector<std::string>& css_selectors)
      : evaluator_(evaluator),
        css_selectors_(css_selectors) {
    DCHECK(!css_selectors.empty());
  }
  
  //
  // PerWebContentsTracker
  //
  
  DeclarativeContentCssConditionTracker::PerWebContentsTracker::
  PerWebContentsTracker(
      content::WebContents* contents,
      const RequestEvaluationCallback& request_evaluation,
      const WebContentsDestroyedCallback& web_contents_destroyed)
      : WebContentsObserver(contents),
        request_evaluation_(request_evaluation),
        web_contents_destroyed_(web_contents_destroyed) {
  }
  
  DeclarativeContentCssConditionTracker::PerWebContentsTracker::
  ~PerWebContentsTracker() {
  }
  
  void DeclarativeContentCssConditionTracker::PerWebContentsTracker::
  OnWebContentsNavigation(const content::LoadCommittedDetails& details,
                          const content::FrameNavigateParams& params) {
    if (details.is_in_page) {
      // Within-page navigations don't change the set of elements that
      // exist, and we only support filtering on the top-level URL, so
      // this can't change which rules match.
      return;
    }
  
    // Top-level navigation produces a new document. Initially, the
    // document's empty, so no CSS rules match.  The renderer will send
    // an ExtensionHostMsg_OnWatchedPageChange later if any CSS rules
    // match.
    matching_css_selectors_.clear();
    request_evaluation_.Run(web_contents());
  }
  
  bool
  DeclarativeContentCssConditionTracker::PerWebContentsTracker::
  OnMessageReceived(
      const IPC::Message& message) {
    bool handled = true;
    IPC_BEGIN_MESSAGE_MAP(PerWebContentsTracker, message)
      IPC_MESSAGE_HANDLER(ExtensionHostMsg_OnWatchedPageChange,
                          OnWatchedPageChange)
      IPC_MESSAGE_UNHANDLED(handled = false)
    IPC_END_MESSAGE_MAP()
    return handled;
  }
  
  void DeclarativeContentCssConditionTracker::PerWebContentsTracker::
  WebContentsDestroyed() {
    web_contents_destroyed_.Run(web_contents());
  }
  
  void
  DeclarativeContentCssConditionTracker::PerWebContentsTracker::
  OnWatchedPageChange(
      const std::vector<std::string>& css_selectors) {
    matching_css_selectors_.clear();
    matching_css_selectors_.insert(css_selectors.begin(), css_selectors.end());
    request_evaluation_.Run(web_contents());
  }
  
  //
  // DeclarativeContentCssConditionTracker
  //
  
  DeclarativeContentCssConditionTracker::DeclarativeContentCssConditionTracker(
      Delegate* delegate)
      : delegate_(delegate) {
    registrar_.Add(this,
                   content::NOTIFICATION_RENDERER_PROCESS_CREATED,
                   content::NotificationService::AllBrowserContextsAndSources());
  }
  
  DeclarativeContentCssConditionTracker::
  ~DeclarativeContentCssConditionTracker() {}
  
  std::string DeclarativeContentCssConditionTracker::
  GetPredicateApiAttributeName() const {
    return declarative_content_constants::kCss;
  }
  
  std::unique_ptr<const ContentPredicate>
  DeclarativeContentCssConditionTracker::CreatePredicate(
      const Extension* extension,
      const base::Value& value,
      std::string* error) {
    return DeclarativeContentCssPredicate::Create(this, value, error);
  }
  
  void DeclarativeContentCssConditionTracker::TrackPredicates(
      const std::map<const void*, std::vector<const ContentPredicate*>>&
          predicates) {
    bool watched_selectors_updated = false;
    for (const auto& group_predicates_pair : predicates) {
      for (const ContentPredicate* predicate : group_predicates_pair.second) {
        DCHECK_EQ(this, predicate->GetEvaluator());
        const DeclarativeContentCssPredicate* typed_predicate =
            static_cast<const DeclarativeContentCssPredicate*>(predicate);
        tracked_predicates_[group_predicates_pair.first].push_back(
            typed_predicate);
        for (const std::string& selector : typed_predicate->css_selectors()) {
          if (watched_css_selector_predicate_count_[selector]++ == 0)
            watched_selectors_updated = true;
        }
      }
    }
  
    if (watched_selectors_updated)
      UpdateRenderersWatchedCssSelectors(GetWatchedCssSelectors());
  }
  
  void DeclarativeContentCssConditionTracker::StopTrackingPredicates(
      const std::vector<const void*>& predicate_groups) {
    bool watched_selectors_updated = false;
    for (const void* group : predicate_groups) {
      auto loc = tracked_predicates_.find(group);
      if (loc == tracked_predicates_.end())
        continue;
      for (const DeclarativeContentCssPredicate* predicate : loc->second) {
        for (const std::string& selector : predicate->css_selectors()) {
          std::map<std::string, int>::iterator loc =
              watched_css_selector_predicate_count_.find(selector);
          DCHECK(loc != watched_css_selector_predicate_count_.end());
          if (--loc->second == 0) {
            watched_css_selector_predicate_count_.erase(loc);
            watched_selectors_updated = true;
          }
        }
      }
      tracked_predicates_.erase(group);
    }
  
    if (watched_selectors_updated)
      UpdateRenderersWatchedCssSelectors(GetWatchedCssSelectors());
  }
  
  void DeclarativeContentCssConditionTracker::TrackForWebContents(
      content::WebContents* contents) {
    per_web_contents_tracker_[contents] =
        make_linked_ptr(new PerWebContentsTracker(
            contents,
            base::Bind(&Delegate::RequestEvaluation, base::Unretained(delegate_)),
            base::Bind(&DeclarativeContentCssConditionTracker::
                       DeletePerWebContentsTracker,
                       base::Unretained(this))));
    // Note: the condition is always false until we receive OnWatchedPageChange,
    // so there's no need to evaluate it here.
  }
  
  void DeclarativeContentCssConditionTracker::OnWebContentsNavigation(
      content::WebContents* contents,
      const content::LoadCommittedDetails& details,
      const content::FrameNavigateParams& params) {
    DCHECK(base::ContainsKey(per_web_contents_tracker_, contents));
    per_web_contents_tracker_[contents]->OnWebContentsNavigation(details, params);
  }
  
  bool DeclarativeContentCssConditionTracker::EvaluatePredicate(
      const ContentPredicate* predicate,
      content::WebContents* tab) const {
    DCHECK_EQ(this, predicate->GetEvaluator());
    const DeclarativeContentCssPredicate* typed_predicate =
        static_cast<const DeclarativeContentCssPredicate*>(predicate);
    auto loc = per_web_contents_tracker_.find(tab);
    DCHECK(loc != per_web_contents_tracker_.end());
    const base::hash_set<std::string>& matching_css_selectors =
        loc->second->matching_css_selectors();
    for (const std::string& predicate_css_selector :
             typed_predicate->css_selectors()) {
      if (!base::ContainsKey(matching_css_selectors, predicate_css_selector))
        return false;
    }
  
    return true;
  }
  
  void DeclarativeContentCssConditionTracker::Observe(
      int type,
      const content::NotificationSource& source,
      const content::NotificationDetails& details) {
    DCHECK_EQ(content::NOTIFICATION_RENDERER_PROCESS_CREATED, type);
  
    content::RenderProcessHost* process =
        content::Source<content::RenderProcessHost>(source).ptr();
    InstructRenderProcessIfManagingBrowserContext(process,
                                                  GetWatchedCssSelectors());
  }
  
  void DeclarativeContentCssConditionTracker::
  UpdateRenderersWatchedCssSelectors(
      const std::vector<std::string>& watched_css_selectors) {
    for (content::RenderProcessHost::iterator it(
             content::RenderProcessHost::AllHostsIterator());
         !it.IsAtEnd();
         it.Advance()) {
      InstructRenderProcessIfManagingBrowserContext(it.GetCurrentValue(),
                                                    watched_css_selectors);
    }
  }
  
  std::vector<std::string> DeclarativeContentCssConditionTracker::
  GetWatchedCssSelectors() const {
    std::vector<std::string> selectors;
    selectors.reserve(watched_css_selector_predicate_count_.size());
    for (const std::pair<std::string, int>& selector_pair :
             watched_css_selector_predicate_count_) {
      selectors.push_back(selector_pair.first);
    }
    return selectors;
  }
  
  void DeclarativeContentCssConditionTracker::
  InstructRenderProcessIfManagingBrowserContext(
      content::RenderProcessHost* process,
      std::vector<std::string> watched_css_selectors) {
    if (delegate_->ShouldManageConditionsForBrowserContext(
            process->GetBrowserContext())) {
      process->Send(new ExtensionMsg_WatchPages(watched_css_selectors));
    }
  }
  
  void DeclarativeContentCssConditionTracker::DeletePerWebContentsTracker(
      content::WebContents* contents) {
    DCHECK(base::ContainsKey(per_web_contents_tracker_, contents));
    per_web_contents_tracker_.erase(contents);
  }
  
  }  // namespace extensions
  