// Copyright 2014 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 "extensions/renderer/content_watcher.h"
#include "content/public/renderer/render_view.h"
#include "content/public/renderer/render_view_visitor.h"
#include "extensions/common/extension_messages.h"
#include "third_party/WebKit/public/web/WebDocument.h"
#include "third_party/WebKit/public/web/WebElement.h"
#include "third_party/WebKit/public/web/WebFrame.h"
#include "third_party/WebKit/public/web/WebScriptBindings.h"
#include "third_party/WebKit/public/web/WebView.h"
namespace extensions {
using blink::WebString;
using blink::WebVector;
using blink::WebView;
ContentWatcher::ContentWatcher() {}
ContentWatcher::~ContentWatcher() {}
void ContentWatcher::OnWatchPages(
const std::vector<std::string>& new_css_selectors_utf8) {
blink::WebVector<blink::WebString> new_css_selectors(
new_css_selectors_utf8.size());
bool changed = new_css_selectors.size() != css_selectors_.size();
for (size_t i = 0; i < new_css_selectors.size(); ++i) {
new_css_selectors[i] =
blink::WebString::fromUTF8(new_css_selectors_utf8[i]);
if (!changed && new_css_selectors[i] != css_selectors_[i])
changed = true;
}
if (!changed)
return;
css_selectors_.swap(new_css_selectors);
// Tell each frame's document about the new set of watched selectors. These
// will trigger calls to DidMatchCSS after Blink has a chance to apply the new
// style, which will in turn notify the browser about the changes.
struct WatchSelectors : public content::RenderViewVisitor {
explicit WatchSelectors(const WebVector<WebString>& css_selectors)
: css_selectors_(css_selectors) {}
virtual bool Visit(content::RenderView* view) OVERRIDE {
for (blink::WebFrame* frame = view->GetWebView()->mainFrame(); frame;
frame = frame->traverseNext(/*wrap=*/false))
frame->document().watchCSSSelectors(css_selectors_);
return true; // Continue visiting.
}
const WebVector<WebString>& css_selectors_;
};
WatchSelectors visitor(css_selectors_);
content::RenderView::ForEach(&visitor);
}
void ContentWatcher::DidCreateDocumentElement(blink::WebFrame* frame) {
frame->document().watchCSSSelectors(css_selectors_);
}
void ContentWatcher::DidMatchCSS(
blink::WebFrame* frame,
const WebVector<WebString>& newly_matching_selectors,
const WebVector<WebString>& stopped_matching_selectors) {
std::set<std::string>& frame_selectors = matching_selectors_[frame];
for (size_t i = 0; i < stopped_matching_selectors.size(); ++i)
frame_selectors.erase(stopped_matching_selectors[i].utf8());
for (size_t i = 0; i < newly_matching_selectors.size(); ++i)
frame_selectors.insert(newly_matching_selectors[i].utf8());
if (frame_selectors.empty())
matching_selectors_.erase(frame);
NotifyBrowserOfChange(frame);
}
void ContentWatcher::NotifyBrowserOfChange(
blink::WebFrame* changed_frame) const {
blink::WebFrame* const top_frame = changed_frame->top();
const blink::WebSecurityOrigin top_origin =
top_frame->document().securityOrigin();
// Want to aggregate matched selectors from all frames where an
// extension with access to top_origin could run on the frame.
if (!top_origin.canAccess(changed_frame->document().securityOrigin())) {
// If the changed frame can't be accessed by the top frame, then
// no change in it could affect the set of selectors we'd send back.
return;
}
std::set<base::StringPiece> transitive_selectors;
for (blink::WebFrame* frame = top_frame; frame;
frame = frame->traverseNext(/*wrap=*/false)) {
if (top_origin.canAccess(frame->document().securityOrigin())) {
std::map<blink::WebFrame*, std::set<std::string> >::const_iterator
frame_selectors = matching_selectors_.find(frame);
if (frame_selectors != matching_selectors_.end()) {
transitive_selectors.insert(frame_selectors->second.begin(),
frame_selectors->second.end());
}
}
}
std::vector<std::string> selector_strings;
for (std::set<base::StringPiece>::const_iterator it =
transitive_selectors.begin();
it != transitive_selectors.end();
++it)
selector_strings.push_back(it->as_string());
content::RenderView* view =
content::RenderView::FromWebView(top_frame->view());
view->Send(new ExtensionHostMsg_OnWatchedPageChange(view->GetRoutingID(),
selector_strings));
}
} // namespace extensions