| // 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 |