script_executor.cc 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412
  1. // Copyright 2014 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. #include "extensions/browser/script_executor.h"
  5. #include <map>
  6. #include <set>
  7. #include <string>
  8. #include "base/bind.h"
  9. #include "base/check_op.h"
  10. #include "base/containers/contains.h"
  11. #include "base/containers/cxx20_erase.h"
  12. #include "base/dcheck_is_on.h"
  13. #include "base/hash/hash.h"
  14. #include "base/memory/weak_ptr.h"
  15. #include "base/pickle.h"
  16. #include "base/ranges/algorithm.h"
  17. #include "base/strings/stringprintf.h"
  18. #include "base/types/pass_key.h"
  19. #include "content/public/browser/render_frame_host.h"
  20. #include "content/public/browser/render_process_host.h"
  21. #include "content/public/browser/render_view_host.h"
  22. #include "content/public/browser/web_contents.h"
  23. #include "content/public/browser/web_contents_observer.h"
  24. #include "extensions/browser/content_script_tracker.h"
  25. #include "extensions/browser/extension_api_frame_id_map.h"
  26. #include "extensions/browser/extension_registry.h"
  27. #include "extensions/browser/extension_web_contents_observer.h"
  28. #include "extensions/common/extension_messages.h"
  29. #include "extensions/common/mojom/host_id.mojom.h"
  30. #include "ipc/ipc_message.h"
  31. #include "ipc/ipc_message_macros.h"
  32. namespace base {
  33. class ListValue;
  34. } // namespace base
  35. namespace extensions {
  36. namespace {
  37. // A handler for a single injection request. On creation this will send the
  38. // injection request to the renderer, and it will be destroyed after either the
  39. // corresponding response comes from the renderer, or the renderer is destroyed.
  40. class Handler : public content::WebContentsObserver {
  41. public:
  42. // OnceCallback version of ScriptExecutor::ScriptsExecutedNotification:
  43. using ScriptsExecutedOnceCallback = base::OnceCallback<
  44. void(content::WebContents*, const ExecutingScriptsMap&, const GURL&)>;
  45. Handler(base::PassKey<ScriptExecutor> pass_key,
  46. ScriptsExecutedOnceCallback observer,
  47. content::WebContents* web_contents,
  48. mojom::ExecuteCodeParamsPtr params,
  49. ScriptExecutor::FrameScope scope,
  50. const std::set<int>& frame_ids,
  51. ScriptExecutor::ScriptFinishedCallback callback)
  52. : content::WebContentsObserver(web_contents),
  53. observer_(std::move(observer)),
  54. host_id_(params->host_id->type, params->host_id->id),
  55. callback_(std::move(callback)) {
  56. for (int frame_id : frame_ids) {
  57. content::RenderFrameHost* frame =
  58. ExtensionApiFrameIdMap::GetRenderFrameHostById(web_contents,
  59. frame_id);
  60. if (!frame) {
  61. AddWillNotInjectResult(
  62. frame_id, ExtensionApiFrameIdMap::DocumentId(),
  63. base::StringPrintf("No frame with ID: %d", frame_id));
  64. continue;
  65. }
  66. DCHECK(!base::Contains(pending_render_frames_, frame));
  67. if (!frame->IsRenderFrameLive()) {
  68. ExtensionApiFrameIdMap::DocumentId document_id =
  69. ExtensionApiFrameIdMap::GetDocumentId(frame);
  70. AddWillNotInjectResult(
  71. frame_id, document_id,
  72. base::StringPrintf("Frame with ID %d is not ready", frame_id));
  73. continue;
  74. }
  75. if (frame->IsErrorDocument()) {
  76. ExtensionApiFrameIdMap::DocumentId document_id =
  77. ExtensionApiFrameIdMap::GetDocumentId(frame);
  78. AddWillNotInjectResult(
  79. frame_id, document_id,
  80. base::StringPrintf("Frame with ID %d is showing error page",
  81. frame_id));
  82. continue;
  83. }
  84. // `frame_id` can be a FrameTreeNodeId of the primary main frame. In such
  85. // cases, ExtensionApiFrameIdMap::GetFrameId(frame) resolves the given
  86. // `frame` as 0. To keep the original ID as is, pass `frame_id` and use it
  87. // directly to prepare a relevant FrameResult.
  88. PushPendingRenderFrame(frame, frame_id);
  89. }
  90. // If there is a single frame specified (and it was valid), we consider it
  91. // the "root" frame, which is used in result ordering and error collection.
  92. if (frame_ids.size() == 1 && pending_render_frames_.size() == 1)
  93. root_frame_token_ = pending_render_frames_[0]->GetFrameToken();
  94. // If we are to include subframes, iterate over all descendants of frames in
  95. // `pending_render_frames_` and add them if they are alive (and not already
  96. // contained in `pending_frames`).
  97. if (scope == ScriptExecutor::INCLUDE_SUB_FRAMES) {
  98. // We iterate over the requested frames. Note we can't use an iterator
  99. // as the for loop will mutate `pending_render_frames_`.
  100. const size_t requested_frame_count = pending_render_frames_.size();
  101. for (size_t i = 0; i < requested_frame_count; ++i) {
  102. pending_render_frames_.at(i)->ForEachRenderFrameHost(
  103. base::BindRepeating(&Handler::MaybeAddSubFrame,
  104. base::Unretained(this)));
  105. }
  106. }
  107. for (content::RenderFrameHost* frame : pending_render_frames_)
  108. SendExecuteCode(pass_key, params.Clone(), frame);
  109. if (pending_render_frames_.empty())
  110. Finish();
  111. }
  112. Handler(const Handler&) = delete;
  113. Handler& operator=(const Handler&) = delete;
  114. private:
  115. // This class manages its own lifetime.
  116. ~Handler() override {}
  117. // content::WebContentsObserver:
  118. // TODO(devlin): Could we just rely on the RenderFrameDeleted() notification?
  119. // If so, we could remove this.
  120. void WebContentsDestroyed() override {
  121. for (content::RenderFrameHost* frame : pending_render_frames_) {
  122. UpdateResultWithErrorFormat(
  123. frame, "Tab containing frame with ID %d was removed.");
  124. }
  125. pending_render_frames_.clear();
  126. Finish();
  127. }
  128. void RenderFrameDeleted(
  129. content::RenderFrameHost* render_frame_host) override {
  130. int erased_count = base::Erase(pending_render_frames_, render_frame_host);
  131. DCHECK_LE(erased_count, 1);
  132. if (erased_count == 0)
  133. return;
  134. UpdateResultWithErrorFormat(render_frame_host,
  135. "Frame with ID %d was removed.");
  136. if (pending_render_frames_.empty())
  137. Finish();
  138. }
  139. content::RenderFrameHost::FrameIterationAction MaybeAddSubFrame(
  140. content::RenderFrameHost* frame) {
  141. // Avoid inner web contents. If we need to execute scripts on inner
  142. // WebContents this class needs to be updated.
  143. // See https://crbug.com/1301320.
  144. if (content::WebContents::FromRenderFrameHost(frame) != web_contents()) {
  145. return content::RenderFrameHost::FrameIterationAction::kSkipChildren;
  146. }
  147. if (!frame->IsRenderFrameLive() ||
  148. base::Contains(pending_render_frames_, frame)) {
  149. return content::RenderFrameHost::FrameIterationAction::kContinue;
  150. }
  151. PushPendingRenderFrame(frame, ExtensionApiFrameIdMap::GetFrameId(frame));
  152. return content::RenderFrameHost::FrameIterationAction::kContinue;
  153. }
  154. void PushPendingRenderFrame(raw_ptr<content::RenderFrameHost> frame,
  155. int frame_id) {
  156. pending_render_frames_.push_back(frame);
  157. // Preallocate the results to hold the initial `frame_id` and `document_id`.
  158. // As the primary main frame uses a magic number 0 for the `frame_id`, it
  159. // can be changed if the primary page is changed. It happens on pre-rendered
  160. // page activation or portal page activation on MPArch. The `document_id`
  161. // can be stale if navigation happens and the same renderer is reused in the
  162. // case, e.g. navigation from about:blank, or same-origin navigation.
  163. ScriptExecutor::FrameResult result;
  164. result.frame_id = frame_id;
  165. result.document_id = ExtensionApiFrameIdMap::GetDocumentId(frame);
  166. DCHECK(!base::Contains(results_, frame->GetFrameToken()));
  167. results_[frame->GetFrameToken()] = std::move(result);
  168. }
  169. void AddWillNotInjectResult(
  170. int frame_id,
  171. const ExtensionApiFrameIdMap::DocumentId& document_id,
  172. std::string error) {
  173. ScriptExecutor::FrameResult result;
  174. result.frame_id = frame_id;
  175. result.document_id = document_id;
  176. result.error = std::move(error);
  177. invalid_injection_results_.push_back(std::move(result));
  178. }
  179. void UpdateResult(content::RenderFrameHost* render_frame_host,
  180. const std::string& error,
  181. const GURL& url,
  182. absl::optional<base::Value> result) {
  183. ScriptExecutor::FrameResult& frame_result =
  184. GetFrameResult(render_frame_host->GetFrameToken());
  185. frame_result.frame_responded = true;
  186. frame_result.error = error;
  187. frame_result.url = url;
  188. if (result.has_value())
  189. frame_result.value = std::move(*result);
  190. }
  191. void UpdateResultWithErrorFormat(content::RenderFrameHost* render_frame_host,
  192. const char* format) {
  193. ScriptExecutor::FrameResult& frame_result =
  194. GetFrameResult(render_frame_host->GetFrameToken());
  195. frame_result.error = base::StringPrintf(format, frame_result.frame_id);
  196. }
  197. ScriptExecutor::FrameResult& GetFrameResult(
  198. const blink::LocalFrameToken& frame_token) {
  199. DCHECK(base::Contains(results_, frame_token));
  200. return results_[frame_token];
  201. }
  202. // Sends an ExecuteCode message to the given frame host, and increments
  203. // the number of pending messages.
  204. void SendExecuteCode(base::PassKey<ScriptExecutor> pass_key,
  205. mojom::ExecuteCodeParamsPtr params,
  206. content::RenderFrameHost* frame) {
  207. DCHECK(frame->IsRenderFrameLive());
  208. DCHECK(base::Contains(pending_render_frames_, frame));
  209. ContentScriptTracker::WillExecuteCode(pass_key, frame, host_id_);
  210. ExtensionWebContentsObserver::GetForWebContents(web_contents())
  211. ->GetLocalFrame(frame)
  212. ->ExecuteCode(std::move(params),
  213. base::BindOnce(&Handler::OnExecuteCodeFinished,
  214. weak_ptr_factory_.GetWeakPtr(),
  215. frame->GetProcess()->GetID(),
  216. frame->GetRoutingID()));
  217. }
  218. // Handles the ExecuteCodeFinished message.
  219. void OnExecuteCodeFinished(int render_process_id,
  220. int render_frame_id,
  221. const std::string& error,
  222. const GURL& on_url,
  223. absl::optional<base::Value> result) {
  224. auto* render_frame_host =
  225. content::RenderFrameHost::FromID(render_process_id, render_frame_id);
  226. if (!render_frame_host)
  227. return;
  228. DCHECK(!pending_render_frames_.empty());
  229. size_t erased = base::Erase(pending_render_frames_, render_frame_host);
  230. DCHECK_EQ(1u, erased);
  231. // TODO(devlin): Do we need to trust the renderer for the URL here? Is there
  232. // a risk of the frame having navigated since the injection happened?
  233. UpdateResult(render_frame_host, error, on_url, std::move(result));
  234. // Wait until the final request finishes before reporting back.
  235. if (pending_render_frames_.empty())
  236. Finish();
  237. }
  238. void Finish() {
  239. DCHECK(pending_render_frames_.empty());
  240. DCHECK(!results_.empty() || !invalid_injection_results_.empty());
  241. // TODO(devlin): This would be simpler (and more thorough) if we could just
  242. // invoke the observer for each frame. Investigate.
  243. if (observer_ && root_frame_token_.has_value()) {
  244. ScriptExecutor::FrameResult& root_frame_result =
  245. GetFrameResult(*root_frame_token_);
  246. if (root_frame_result.error.empty() &&
  247. host_id_.type == mojom::HostID::HostType::kExtensions) {
  248. std::move(observer_).Run(web_contents(), {{host_id_.id, {}}},
  249. root_frame_result.url);
  250. }
  251. }
  252. if (callback_) {
  253. std::vector<ScriptExecutor::FrameResult> all_results =
  254. std::move(invalid_injection_results_);
  255. all_results.reserve(invalid_injection_results_.size() + results_.size());
  256. for (auto& kv : results_)
  257. all_results.push_back(std::move(kv.second));
  258. std::move(callback_).Run(std::move(all_results));
  259. }
  260. delete this;
  261. }
  262. ScriptsExecutedOnceCallback observer_;
  263. // The id of the host (the extension or the webui) doing the injection.
  264. mojom::HostID host_id_;
  265. // The the root frame key to search FrameResult, if only a single frame is
  266. // explicitly specified.
  267. absl::optional<blink::LocalFrameToken> root_frame_token_;
  268. // The hosts of the still-running injections. Note: this is a vector because
  269. // order matters (some tests - and therefore perhaps some extensions - rely on
  270. // the execution mirroring the frame tree hierarchy). The contents, however,
  271. // should be unique (i.e., no duplicated frames).
  272. // TODO(devlin): Extensions *shouldn't* rely on order here, because there's
  273. // never a guarantee. We should probably just adjust the test and disregard
  274. // order (except the root frame).
  275. std::vector<raw_ptr<content::RenderFrameHost>> pending_render_frames_;
  276. // The results of script injections into frames, keyed by LocalFrameToken.
  277. // Note that the keying host here may be invalid if the host was since
  278. // destroyed, and should never be accessed.
  279. // We key these by LocalFrameToken rather than frame ID because the frame ID
  280. // for a given frame may change if the frame changes lifecycle state (such as
  281. // pre-rendered page being activated).
  282. std::map<blink::LocalFrameToken, ScriptExecutor::FrameResult> results_;
  283. // A collection of results for frames that will never be injected into;
  284. // these are separate from `results_` because they may not be a valid
  285. // RenderFrameHost* to key them by (if there's no corresponding frame).
  286. std::vector<ScriptExecutor::FrameResult> invalid_injection_results_;
  287. // The callback to run after all injections complete.
  288. ScriptExecutor::ScriptFinishedCallback callback_;
  289. base::WeakPtrFactory<Handler> weak_ptr_factory_{this};
  290. };
  291. } // namespace
  292. ScriptExecutor::FrameResult::FrameResult() = default;
  293. ScriptExecutor::FrameResult::FrameResult(FrameResult&&) = default;
  294. ScriptExecutor::FrameResult& ScriptExecutor::FrameResult::operator=(
  295. FrameResult&&) = default;
  296. ScriptExecutor::ScriptExecutor(content::WebContents* web_contents)
  297. : web_contents_(web_contents) {
  298. CHECK(web_contents_);
  299. }
  300. ScriptExecutor::~ScriptExecutor() {}
  301. // static
  302. std::string ScriptExecutor::GenerateInjectionKey(const mojom::HostID& host_id,
  303. const GURL& script_url,
  304. const std::string& code) {
  305. const std::string& source = script_url.is_valid() ? script_url.spec() : code;
  306. return base::StringPrintf("%c%s%zu", script_url.is_valid() ? 'F' : 'C',
  307. host_id.id.c_str(), base::FastHash(source));
  308. }
  309. void ScriptExecutor::ExecuteScript(const mojom::HostID& host_id,
  310. mojom::CodeInjectionPtr injection,
  311. ScriptExecutor::FrameScope frame_scope,
  312. const std::set<int>& frame_ids,
  313. ScriptExecutor::MatchAboutBlank about_blank,
  314. mojom::RunLocation run_at,
  315. ScriptExecutor::ProcessType process_type,
  316. const GURL& webview_src,
  317. ScriptFinishedCallback callback) {
  318. if (host_id.type == mojom::HostID::HostType::kExtensions) {
  319. // Don't execute if the extension has been unloaded.
  320. const Extension* extension =
  321. ExtensionRegistry::Get(web_contents_->GetBrowserContext())
  322. ->enabled_extensions()
  323. .GetByID(host_id.id);
  324. if (!extension)
  325. return;
  326. } else {
  327. CHECK(process_type == WEB_VIEW_PROCESS);
  328. }
  329. #if DCHECK_IS_ON()
  330. if (injection->is_css()) {
  331. bool expect_injection_key =
  332. host_id.type == mojom::HostID::HostType::kExtensions;
  333. if (injection->get_css()->operation ==
  334. mojom::CSSInjection::Operation::kRemove) {
  335. DCHECK(expect_injection_key)
  336. << "Only extensions (with injection keys supplied) can remove CSS.";
  337. }
  338. DCHECK(base::ranges::all_of(
  339. injection->get_css()->sources,
  340. [expect_injection_key](const mojom::CSSSourcePtr& source) {
  341. return expect_injection_key == source->key.has_value();
  342. }));
  343. }
  344. #endif
  345. auto params = mojom::ExecuteCodeParams::New();
  346. params->host_id = host_id.Clone();
  347. params->injection = std::move(injection);
  348. params->match_about_blank = (about_blank == MATCH_ABOUT_BLANK);
  349. params->run_at = run_at;
  350. params->is_web_view = (process_type == WEB_VIEW_PROCESS);
  351. params->webview_src = webview_src;
  352. // Handler handles IPCs and deletes itself on completion.
  353. new Handler(base::PassKey<ScriptExecutor>(), observer_, web_contents_,
  354. std::move(params), frame_scope, frame_ids, std::move(callback));
  355. }
  356. } // namespace extensions