blob: 804c6646df5e9fc77719ed4900e4ccb6b4c215d2 [file] [log] [blame]
// 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 <stddef.h>
#include <set>
#include "content/public/renderer/render_frame.h"
#include "content/public/renderer/render_frame_observer.h"
#include "content/public/renderer/render_frame_observer_tracker.h"
#include "content/public/renderer/render_frame_visitor.h"
#include "content/public/renderer/render_view.h"
#include "extensions/common/extension_messages.h"
#include "third_party/blink/public/web/web_document.h"
#include "third_party/blink/public/web/web_element.h"
#include "third_party/blink/public/web/web_local_frame.h"
#include "third_party/blink/public/web/web_view.h"
namespace extensions {
namespace {
class FrameContentWatcher
: public content::RenderFrameObserver,
public content::RenderFrameObserverTracker<FrameContentWatcher> {
public:
FrameContentWatcher(content::RenderFrame* render_frame,
const blink::WebVector<blink::WebString>& css_selectors);
~FrameContentWatcher() override;
// content::RenderFrameObserver:
void OnDestruct() override;
void DidCreateDocumentElement() override;
void DidMatchCSS(
const blink::WebVector<blink::WebString>& newly_matching_selectors,
const blink::WebVector<blink::WebString>& stopped_matching_selectors)
override;
void UpdateCSSSelectors(const blink::WebVector<blink::WebString>& selectors);
private:
// Given that we saw a change in the CSS selectors that the associated frame
// matched, tells the browser about the new set of matching selectors in its
// top-level page. We filter this so that if an extension were to be granted
// activeTab permission on that top-level page, we only send CSS selectors for
// frames that it could run on.
// Note: Currently, this works with OOPIFs because, since we only send this
// for a matching selector found in a frame that the top frame can access,
// that frame is guaranteed to be local. If we ever isolate frames regardless
// of whether the top frame could access them, or if we notify of matches for
// frames the top frame cannot access, we may have to rethink this.
void NotifyBrowserOfChange();
blink::WebVector<blink::WebString> css_selectors_;
std::set<std::string> matching_selectors_;
bool document_created_ = false;
DISALLOW_COPY_AND_ASSIGN(FrameContentWatcher);
};
FrameContentWatcher::FrameContentWatcher(
content::RenderFrame* render_frame,
const blink::WebVector<blink::WebString>& css_selectors)
: content::RenderFrameObserver(render_frame),
content::RenderFrameObserverTracker<FrameContentWatcher>(render_frame),
css_selectors_(css_selectors) {}
FrameContentWatcher::~FrameContentWatcher() {}
void FrameContentWatcher::OnDestruct() {
delete this;
}
void FrameContentWatcher::DidCreateDocumentElement() {
document_created_ = true;
render_frame()->GetWebFrame()->GetDocument().WatchCSSSelectors(
css_selectors_);
}
void FrameContentWatcher::DidMatchCSS(
const blink::WebVector<blink::WebString>& newly_matching_selectors,
const blink::WebVector<blink::WebString>& stopped_matching_selectors) {
for (size_t i = 0; i < stopped_matching_selectors.size(); ++i)
matching_selectors_.erase(stopped_matching_selectors[i].Utf8());
for (size_t i = 0; i < newly_matching_selectors.size(); ++i)
matching_selectors_.insert(newly_matching_selectors[i].Utf8());
NotifyBrowserOfChange();
}
void FrameContentWatcher::UpdateCSSSelectors(
const blink::WebVector<blink::WebString>& selectors) {
css_selectors_ = selectors;
if (document_created_) {
render_frame()->GetWebFrame()->GetDocument().WatchCSSSelectors(
css_selectors_);
}
}
void FrameContentWatcher::NotifyBrowserOfChange() {
blink::WebLocalFrame* changed_frame = render_frame()->GetWebFrame();
blink::WebFrame* const top_frame = changed_frame->Top();
const blink::WebSecurityOrigin top_origin = top_frame->GetSecurityOrigin();
// 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->GetSecurityOrigin())) {
// 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()) {
if (frame->IsWebLocalFrame() &&
top_origin.CanAccess(frame->GetSecurityOrigin())) {
FrameContentWatcher* watcher = FrameContentWatcher::Get(
content::RenderFrame::FromWebFrame(frame->ToWebLocalFrame()));
if (watcher && !watcher->matching_selectors_.empty()) {
transitive_selectors.insert(watcher->matching_selectors_.begin(),
watcher->matching_selectors_.end());
}
}
}
std::vector<std::string> selector_strings;
for (const base::StringPiece& selector : transitive_selectors)
selector_strings.push_back(selector.as_string());
// TODO(devlin): Frame-ify this message.
content::RenderView* view =
content::RenderView::FromWebView(top_frame->View());
view->Send(new ExtensionHostMsg_OnWatchedPageChange(view->GetRoutingID(),
selector_strings));
}
} // namespace
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::RenderFrameVisitor {
explicit WatchSelectors(
const blink::WebVector<blink::WebString>& css_selectors)
: css_selectors(css_selectors) {}
bool Visit(content::RenderFrame* frame) override {
FrameContentWatcher::Get(frame)->UpdateCSSSelectors(css_selectors);
return true; // Continue visiting.
}
const blink::WebVector<blink::WebString>& css_selectors;
};
WatchSelectors visitor(css_selectors_);
content::RenderFrame::ForEach(&visitor);
}
void ContentWatcher::OnRenderFrameCreated(content::RenderFrame* render_frame) {
// Manages its own lifetime.
new FrameContentWatcher(render_frame, css_selectors_);
}
} // namespace extensions