Home | History | Annotate | Download | only in extensions
      1 // Copyright (c) 2012 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 "chrome/common/extensions/extension_messages.h"
      6 #include "chrome/renderer/extensions/chrome_v8_context.h"
      7 #include "chrome/renderer/extensions/chrome_v8_extension.h"
      8 #include "chrome/renderer/extensions/content_watcher.h"
      9 #include "chrome/renderer/extensions/dispatcher.h"
     10 #include "content/public/renderer/render_view.h"
     11 #include "content/public/renderer/render_view_visitor.h"
     12 #include "third_party/WebKit/public/web/WebDocument.h"
     13 #include "third_party/WebKit/public/web/WebFrame.h"
     14 #include "third_party/WebKit/public/web/WebView.h"
     15 
     16 namespace extensions {
     17 
     18 namespace {
     19 
     20 class MutationHandler : public ChromeV8Extension {
     21  public:
     22   explicit MutationHandler(Dispatcher* dispatcher,
     23                            ChromeV8Context* context,
     24                            base::WeakPtr<ContentWatcher> content_watcher)
     25       : ChromeV8Extension(dispatcher, context),
     26         content_watcher_(content_watcher) {
     27     RouteFunction("FrameMutated",
     28                   base::Bind(&MutationHandler::FrameMutated,
     29                              base::Unretained(this)));
     30   }
     31 
     32  private:
     33   void FrameMutated(const v8::FunctionCallbackInfo<v8::Value>& args) {
     34     if (content_watcher_.get()) {
     35       content_watcher_->ScanAndNotify(
     36           WebKit::WebFrame::frameForContext(context()->v8_context()));
     37     }
     38   }
     39 
     40   base::WeakPtr<ContentWatcher> content_watcher_;
     41 };
     42 
     43 }  // namespace
     44 
     45 ContentWatcher::ContentWatcher(Dispatcher* dispatcher)
     46     : weak_ptr_factory_(this),
     47       dispatcher_(dispatcher) {}
     48 ContentWatcher::~ContentWatcher() {}
     49 
     50 scoped_ptr<NativeHandler> ContentWatcher::MakeNatives(
     51     ChromeV8Context* context) {
     52   return scoped_ptr<NativeHandler>(new MutationHandler(
     53       dispatcher_, context, weak_ptr_factory_.GetWeakPtr()));
     54 }
     55 
     56 void ContentWatcher::OnWatchPages(
     57     const std::vector<std::string>& new_css_selectors) {
     58   if (new_css_selectors == css_selectors_)
     59     return;
     60 
     61   css_selectors_ = new_css_selectors;
     62 
     63   for (std::map<WebKit::WebFrame*,
     64                 std::vector<base::StringPiece> >::iterator
     65            it = matching_selectors_.begin();
     66        it != matching_selectors_.end(); ++it) {
     67     WebKit::WebFrame* frame = it->first;
     68     if (!css_selectors_.empty())
     69       EnsureWatchingMutations(frame);
     70 
     71     // Make sure to replace the contents of it->second because it contains
     72     // dangling StringPieces that referred into the old css_selectors_ content.
     73     it->second = FindMatchingSelectors(frame);
     74   }
     75 
     76   // For each top-level frame, inform the browser about its new matching set of
     77   // selectors.
     78   struct NotifyVisitor : public content::RenderViewVisitor {
     79     explicit NotifyVisitor(ContentWatcher* watcher) : watcher_(watcher) {}
     80     virtual bool Visit(content::RenderView* view) OVERRIDE {
     81       watcher_->NotifyBrowserOfChange(view->GetWebView()->mainFrame());
     82       return true;  // Continue visiting.
     83     }
     84     ContentWatcher* watcher_;
     85   };
     86   NotifyVisitor visitor(this);
     87   content::RenderView::ForEach(&visitor);
     88 }
     89 
     90 void ContentWatcher::DidCreateDocumentElement(WebKit::WebFrame* frame) {
     91   // Make sure the frame is represented in the matching_selectors_ map.
     92   matching_selectors_[frame];
     93 
     94   if (!css_selectors_.empty()) {
     95     EnsureWatchingMutations(frame);
     96   }
     97 }
     98 
     99 void ContentWatcher::EnsureWatchingMutations(WebKit::WebFrame* frame) {
    100   v8::HandleScope scope;
    101   v8::Context::Scope context_scope(frame->mainWorldScriptContext());
    102   if (ModuleSystem* module_system = GetModuleSystem(frame)) {
    103     ModuleSystem::NativesEnabledScope scope(module_system);
    104     module_system->Require("contentWatcher");
    105   }
    106 }
    107 
    108 ModuleSystem* ContentWatcher::GetModuleSystem(WebKit::WebFrame* frame) const {
    109   ChromeV8Context* v8_context =
    110       dispatcher_->v8_context_set().GetByV8Context(
    111           frame->mainWorldScriptContext());
    112 
    113   if (!v8_context)
    114     return NULL;
    115   return v8_context->module_system();
    116 }
    117 
    118 void ContentWatcher::ScanAndNotify(WebKit::WebFrame* frame) {
    119   std::vector<base::StringPiece> new_matches = FindMatchingSelectors(frame);
    120   std::vector<base::StringPiece>& old_matches = matching_selectors_[frame];
    121   if (new_matches == old_matches)
    122     return;
    123 
    124   using std::swap;
    125   swap(old_matches, new_matches);
    126   NotifyBrowserOfChange(frame);
    127 }
    128 
    129 std::vector<base::StringPiece> ContentWatcher::FindMatchingSelectors(
    130     WebKit::WebFrame* frame) const {
    131   std::vector<base::StringPiece> result;
    132   v8::HandleScope scope;
    133 
    134   // Get the indices within |css_selectors_| that match elements in
    135   // |frame|, as a JS Array.
    136   v8::Local<v8::Value> selector_indices;
    137   if (ModuleSystem* module_system = GetModuleSystem(frame)) {
    138     v8::Context::Scope context_scope(frame->mainWorldScriptContext());
    139     v8::Local<v8::Array> js_css_selectors =
    140         v8::Array::New(css_selectors_.size());
    141     for (size_t i = 0; i < css_selectors_.size(); ++i) {
    142       js_css_selectors->Set(i, v8::String::New(css_selectors_[i].data(),
    143                                                css_selectors_[i].size()));
    144     }
    145     std::vector<v8::Handle<v8::Value> > find_selectors_args;
    146     find_selectors_args.push_back(js_css_selectors);
    147     selector_indices = module_system->CallModuleMethod("contentWatcher",
    148                                                        "FindMatchingSelectors",
    149                                                        &find_selectors_args);
    150   }
    151   if (selector_indices.IsEmpty() || !selector_indices->IsArray())
    152     return result;
    153 
    154   // Iterate over the array, and extract the indices, laboriously
    155   // converting them back to integers.
    156   v8::Local<v8::Array> index_array = selector_indices.As<v8::Array>();
    157   const size_t length = index_array->Length();
    158   result.reserve(length);
    159   for (size_t i = 0; i < length; ++i) {
    160     v8::Local<v8::Value> index_value = index_array->Get(i);
    161     if (!index_value->IsNumber())
    162       continue;
    163     double index = index_value->NumberValue();
    164     // Make sure the index is within bounds.
    165     if (index < 0 || css_selectors_.size() <= index)
    166       continue;
    167     // Push a StringPiece referring to the CSS selector onto the result.
    168     result.push_back(
    169         base::StringPiece(css_selectors_[static_cast<size_t>(index)]));
    170   }
    171   return result;
    172 }
    173 
    174 void ContentWatcher::NotifyBrowserOfChange(
    175     WebKit::WebFrame* changed_frame) const {
    176   WebKit::WebFrame* const top_frame = changed_frame->top();
    177   const WebKit::WebSecurityOrigin top_origin =
    178       top_frame->document().securityOrigin();
    179   // Want to aggregate matched selectors from all frames where an
    180   // extension with access to top_origin could run on the frame.
    181   if (!top_origin.canAccess(changed_frame->document().securityOrigin())) {
    182     // If the changed frame can't be accessed by the top frame, then
    183     // no change in it could affect the set of selectors we'd send back.
    184     return;
    185   }
    186 
    187   std::set<base::StringPiece> transitive_selectors;
    188   for (WebKit::WebFrame* frame = top_frame; frame;
    189        frame = frame->traverseNext(/*wrap=*/false)) {
    190     if (top_origin.canAccess(frame->document().securityOrigin())) {
    191       std::map<WebKit::WebFrame*,
    192                std::vector<base::StringPiece> >::const_iterator
    193           frame_selectors = matching_selectors_.find(frame);
    194       if (frame_selectors != matching_selectors_.end()) {
    195         transitive_selectors.insert(frame_selectors->second.begin(),
    196                                     frame_selectors->second.end());
    197       }
    198     }
    199   }
    200   std::vector<std::string> selector_strings;
    201   for (std::set<base::StringPiece>::const_iterator
    202            it = transitive_selectors.begin();
    203        it != transitive_selectors.end(); ++it)
    204     selector_strings.push_back(it->as_string());
    205   content::RenderView* view =
    206       content::RenderView::FromWebView(top_frame->view());
    207   view->Send(new ExtensionHostMsg_OnWatchedPageChange(
    208       view->GetRoutingID(), selector_strings));
    209 }
    210 
    211 }  // namespace extensions
    212