Resubmit: Block content scripts from executing until user grants permission

Original CL: https://codereview.chromium.org/288053002/
Original Description:
Prevent extensions with <all_urls> from running content scripts without user
consent if the scripts-require-action switch is on.

-----------------------------------------------

This had a problem in that when user scripts are updated, the old versions
are invalidated (as they rely on StringPieces, which do not actually own
content). Fix is to update all user scripts, even if they didn't actually
change.

Also add in ActiveScriptController removing actions for unloaded extensions.

[email protected] (for extension_messages.h, no change from original patch)
BUG=362353

Review URL: https://codereview.chromium.org/313453002

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@274659 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/extensions/renderer/script_injection.cc b/extensions/renderer/script_injection.cc
index 4db534a1..d3113cda 100644
--- a/extensions/renderer/script_injection.cc
+++ b/extensions/renderer/script_injection.cc
@@ -9,17 +9,21 @@
 #include "base/lazy_instance.h"
 #include "base/metrics/histogram.h"
 #include "content/public/common/url_constants.h"
+#include "content/public/renderer/render_view.h"
 #include "extensions/common/extension.h"
 #include "extensions/common/extension_messages.h"
+#include "extensions/common/feature_switch.h"
 #include "extensions/common/permissions/permissions_data.h"
 #include "extensions/renderer/dom_activity_logger.h"
 #include "extensions/renderer/extension_groups.h"
+#include "extensions/renderer/extension_helper.h"
 #include "extensions/renderer/script_context.h"
 #include "extensions/renderer/user_script_slave.h"
 #include "grit/renderer_resources.h"
 #include "third_party/WebKit/public/web/WebDocument.h"
 #include "third_party/WebKit/public/web/WebFrame.h"
 #include "third_party/WebKit/public/web/WebScriptSource.h"
+#include "third_party/WebKit/public/web/WebView.h"
 #include "ui/base/resource/resource_bundle.h"
 #include "url/gurl.h"
 
@@ -27,6 +31,13 @@
 
 namespace {
 
+// The id of the next pending injection.
+int64 g_next_pending_id = 0;
+
+// The number of an invalid request, which is used if the feature to delay
+// script injection is not enabled.
+const int64 kInvalidRequestId = -1;
+
 // These two strings are injected before and after the Greasemonkey API and
 // user script to wrap it in an anonymous scope.
 const char kUserScriptHead[] = "(function (unsafeWindow) {\n";
@@ -57,6 +68,42 @@
 ScriptInjection::ScriptsRunInfo::~ScriptsRunInfo() {
 }
 
+struct ScriptInjection::PendingInjection {
+  PendingInjection(blink::WebFrame* web_frame,
+                   UserScript::RunLocation run_location,
+                   int page_id);
+  ~PendingInjection();
+
+  // The globally-unique id of this request.
+  int64 id;
+
+  // The pointer to the web frame into which the script should be injected.
+  // This is weak, but safe because we remove pending requests when a frame is
+  // terminated.
+  blink::WebFrame* web_frame;
+
+  // The run location to inject at.
+  // Note: This could be a lie - we might inject well after this run location
+  // has come and gone. But we need to know it to know which scripts to inject.
+  UserScript::RunLocation run_location;
+
+  // The corresponding page id, to protect against races.
+  int page_id;
+};
+
+ScriptInjection::PendingInjection::PendingInjection(
+    blink::WebFrame* web_frame,
+    UserScript::RunLocation run_location,
+    int page_id)
+    : id(g_next_pending_id++),
+      web_frame(web_frame),
+      run_location(run_location),
+      page_id(page_id) {
+}
+
+ScriptInjection::PendingInjection::~PendingInjection() {
+}
+
 // static
 GURL ScriptInjection::GetDocumentUrlForFrame(blink::WebFrame* frame) {
   GURL data_source_url = ScriptContext::GetDataSourceURLForFrame(frame);
@@ -81,6 +128,114 @@
 ScriptInjection::~ScriptInjection() {
 }
 
+void ScriptInjection::InjectIfAllowed(blink::WebFrame* frame,
+                                      UserScript::RunLocation run_location,
+                                      const GURL& document_url,
+                                      ScriptsRunInfo* scripts_run_info) {
+  if (!WantsToRun(frame, run_location, document_url))
+    return;
+
+  const Extension* extension = user_script_slave_->GetExtension(extension_id_);
+  DCHECK(extension);  // WantsToRun() should be false if there's no extension.
+
+  // We use the top render view here (instead of the render view for the
+  // frame), because script injection on any frame requires permission for
+  // the top frame. Additionally, if we have to show any UI for permissions,
+  // it should only be done on the top frame.
+  content::RenderView* top_render_view =
+      content::RenderView::FromWebView(frame->top()->view());
+
+  int tab_id = ExtensionHelper::Get(top_render_view)->tab_id();
+
+  // By default, we allow injection.
+  bool should_inject = true;
+
+  // Check if the extension requires user consent for injection *and* we have a
+  // valid tab id (if we don't have a tab id, we have no UI surface to ask for
+  // user consent).
+  if (tab_id != -1 &&
+      PermissionsData::RequiresActionForScriptExecution(
+          extension,
+          tab_id,
+          frame->top()->document().url())) {
+    int64 request_id = kInvalidRequestId;
+    int page_id = top_render_view->GetPageId();
+
+    // We only delay the injection if the feature is enabled.
+    // Otherwise, we simply treat this as a notification by passing an invalid
+    // id.
+    if (FeatureSwitch::scripts_require_action()->IsEnabled()) {
+      should_inject = false;
+      ScopedVector<PendingInjection>::iterator pending_injection =
+          pending_injections_.insert(
+              pending_injections_.end(),
+              new PendingInjection(frame, run_location, page_id));
+      request_id = (*pending_injection)->id;
+    }
+
+    top_render_view->Send(
+        new ExtensionHostMsg_RequestContentScriptPermission(
+            top_render_view->GetRoutingID(),
+            extension->id(),
+            page_id,
+            request_id));
+  }
+
+  if (should_inject)
+    Inject(frame, run_location, scripts_run_info);
+}
+
+bool ScriptInjection::NotifyScriptPermitted(
+    int64 request_id,
+    content::RenderView* render_view,
+    ScriptsRunInfo* scripts_run_info,
+    blink::WebFrame** frame_out) {
+  ScopedVector<PendingInjection>::iterator iter = pending_injections_.begin();
+  while (iter != pending_injections_.end() && (*iter)->id != request_id)
+    ++iter;
+
+  // No matching request.
+  if (iter == pending_injections_.end())
+    return false;
+
+  // We found the request, so pull it out of the pending list.
+  scoped_ptr<PendingInjection> pending_injection(*iter);
+  pending_injections_.weak_erase(iter);
+
+  // Ensure the Page ID and Extension are still valid. Otherwise, don't inject.
+  if (render_view->GetPageId() != pending_injection->page_id)
+    return false;
+
+  const Extension* extension = user_script_slave_->GetExtension(extension_id_);
+  if (!extension)
+    return false;
+
+  // Everything matches! Inject the script.
+  if (frame_out)
+    *frame_out = pending_injection->web_frame;
+  Inject(pending_injection->web_frame,
+         pending_injection->run_location,
+         scripts_run_info);
+  return true;
+}
+
+void ScriptInjection::FrameDetached(blink::WebFrame* frame) {
+  // Any pending injections associated with the given frame will never run.
+  // Remove them.
+  for (ScopedVector<PendingInjection>::iterator iter =
+           pending_injections_.begin();
+       iter != pending_injections_.end();) {
+    if ((*iter)->web_frame == frame)
+      iter = pending_injections_.erase(iter);
+    else
+      ++iter;
+  }
+}
+
+void ScriptInjection::SetScript(scoped_ptr<UserScript> script) {
+  script_.reset(script.release());
+}
+
 bool ScriptInjection::WantsToRun(blink::WebFrame* frame,
                                  UserScript::RunLocation run_location,
                                  const GURL& document_url) const {