1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "extensions/renderer/content_watcher.h"
6
7#include "content/public/renderer/render_view.h"
8#include "content/public/renderer/render_view_visitor.h"
9#include "extensions/common/extension_messages.h"
10#include "third_party/WebKit/public/web/WebDocument.h"
11#include "third_party/WebKit/public/web/WebElement.h"
12#include "third_party/WebKit/public/web/WebFrame.h"
13#include "third_party/WebKit/public/web/WebScriptBindings.h"
14#include "third_party/WebKit/public/web/WebView.h"
15
16namespace extensions {
17
18using blink::WebString;
19using blink::WebVector;
20using blink::WebView;
21
22ContentWatcher::ContentWatcher() {}
23ContentWatcher::~ContentWatcher() {}
24
25void ContentWatcher::OnWatchPages(
26    const std::vector<std::string>& new_css_selectors_utf8) {
27  blink::WebVector<blink::WebString> new_css_selectors(
28      new_css_selectors_utf8.size());
29  bool changed = new_css_selectors.size() != css_selectors_.size();
30  for (size_t i = 0; i < new_css_selectors.size(); ++i) {
31    new_css_selectors[i] =
32        blink::WebString::fromUTF8(new_css_selectors_utf8[i]);
33    if (!changed && new_css_selectors[i] != css_selectors_[i])
34      changed = true;
35  }
36
37  if (!changed)
38    return;
39
40  css_selectors_.swap(new_css_selectors);
41
42  // Tell each frame's document about the new set of watched selectors. These
43  // will trigger calls to DidMatchCSS after Blink has a chance to apply the new
44  // style, which will in turn notify the browser about the changes.
45  struct WatchSelectors : public content::RenderViewVisitor {
46    explicit WatchSelectors(const WebVector<WebString>& css_selectors)
47        : css_selectors_(css_selectors) {}
48
49    virtual bool Visit(content::RenderView* view) OVERRIDE {
50      for (blink::WebFrame* frame = view->GetWebView()->mainFrame(); frame;
51           frame = frame->traverseNext(/*wrap=*/false))
52        frame->document().watchCSSSelectors(css_selectors_);
53
54      return true;  // Continue visiting.
55    }
56
57    const WebVector<WebString>& css_selectors_;
58  };
59  WatchSelectors visitor(css_selectors_);
60  content::RenderView::ForEach(&visitor);
61}
62
63void ContentWatcher::DidCreateDocumentElement(blink::WebFrame* frame) {
64  frame->document().watchCSSSelectors(css_selectors_);
65}
66
67void ContentWatcher::DidMatchCSS(
68    blink::WebFrame* frame,
69    const WebVector<WebString>& newly_matching_selectors,
70    const WebVector<WebString>& stopped_matching_selectors) {
71  std::set<std::string>& frame_selectors = matching_selectors_[frame];
72  for (size_t i = 0; i < stopped_matching_selectors.size(); ++i)
73    frame_selectors.erase(stopped_matching_selectors[i].utf8());
74  for (size_t i = 0; i < newly_matching_selectors.size(); ++i)
75    frame_selectors.insert(newly_matching_selectors[i].utf8());
76
77  if (frame_selectors.empty())
78    matching_selectors_.erase(frame);
79
80  NotifyBrowserOfChange(frame);
81}
82
83void ContentWatcher::NotifyBrowserOfChange(
84    blink::WebFrame* changed_frame) const {
85  blink::WebFrame* const top_frame = changed_frame->top();
86  const blink::WebSecurityOrigin top_origin =
87      top_frame->document().securityOrigin();
88  // Want to aggregate matched selectors from all frames where an
89  // extension with access to top_origin could run on the frame.
90  if (!top_origin.canAccess(changed_frame->document().securityOrigin())) {
91    // If the changed frame can't be accessed by the top frame, then
92    // no change in it could affect the set of selectors we'd send back.
93    return;
94  }
95
96  std::set<base::StringPiece> transitive_selectors;
97  for (blink::WebFrame* frame = top_frame; frame;
98       frame = frame->traverseNext(/*wrap=*/false)) {
99    if (top_origin.canAccess(frame->document().securityOrigin())) {
100      std::map<blink::WebFrame*, std::set<std::string> >::const_iterator
101          frame_selectors = matching_selectors_.find(frame);
102      if (frame_selectors != matching_selectors_.end()) {
103        transitive_selectors.insert(frame_selectors->second.begin(),
104                                    frame_selectors->second.end());
105      }
106    }
107  }
108  std::vector<std::string> selector_strings;
109  for (std::set<base::StringPiece>::const_iterator it =
110           transitive_selectors.begin();
111       it != transitive_selectors.end();
112       ++it)
113    selector_strings.push_back(it->as_string());
114  content::RenderView* view =
115      content::RenderView::FromWebView(top_frame->view());
116  view->Send(new ExtensionHostMsg_OnWatchedPageChange(view->GetRoutingID(),
117                                                      selector_strings));
118}
119
120}  // namespace extensions
121