script_injection_manager.cc 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513
  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/renderer/script_injection_manager.h"
  5. #include <memory>
  6. #include <utility>
  7. #include "base/auto_reset.h"
  8. #include "base/bind.h"
  9. #include "base/containers/cxx20_erase.h"
  10. #include "base/feature_list.h"
  11. #include "base/memory/weak_ptr.h"
  12. #include "base/threading/thread_task_runner_handle.h"
  13. #include "base/values.h"
  14. #include "content/public/renderer/render_frame.h"
  15. #include "content/public/renderer/render_frame_observer.h"
  16. #include "content/public/renderer/render_thread.h"
  17. #include "extensions/common/extension.h"
  18. #include "extensions/common/extension_features.h"
  19. #include "extensions/common/extension_messages.h"
  20. #include "extensions/common/extension_set.h"
  21. #include "extensions/common/mojom/host_id.mojom.h"
  22. #include "extensions/renderer/extension_frame_helper.h"
  23. #include "extensions/renderer/extension_injection_host.h"
  24. #include "extensions/renderer/programmatic_script_injector.h"
  25. #include "extensions/renderer/renderer_extension_registry.h"
  26. #include "extensions/renderer/script_injection.h"
  27. #include "extensions/renderer/scripts_run_info.h"
  28. #include "extensions/renderer/web_ui_injection_host.h"
  29. #include "ipc/ipc_message_macros.h"
  30. #include "third_party/abseil-cpp/absl/types/optional.h"
  31. #include "third_party/blink/public/platform/web_url_error.h"
  32. #include "third_party/blink/public/web/web_document.h"
  33. #include "third_party/blink/public/web/web_frame.h"
  34. #include "third_party/blink/public/web/web_local_frame.h"
  35. #include "third_party/blink/public/web/web_view.h"
  36. #include "url/gurl.h"
  37. namespace extensions {
  38. namespace {
  39. // The length of time to wait after the DOM is complete to try and run user
  40. // scripts.
  41. const int kScriptIdleTimeoutInMs = 200;
  42. // Returns the RunLocation that follows |run_location|.
  43. absl::optional<mojom::RunLocation> NextRunLocation(
  44. mojom::RunLocation run_location) {
  45. switch (run_location) {
  46. case mojom::RunLocation::kDocumentStart:
  47. return mojom::RunLocation::kDocumentEnd;
  48. case mojom::RunLocation::kDocumentEnd:
  49. return mojom::RunLocation::kDocumentIdle;
  50. case mojom::RunLocation::kDocumentIdle:
  51. return absl::nullopt;
  52. case mojom::RunLocation::kUndefined:
  53. case mojom::RunLocation::kRunDeferred:
  54. case mojom::RunLocation::kBrowserDriven:
  55. return absl::nullopt;
  56. }
  57. NOTREACHED();
  58. }
  59. } // namespace
  60. class ScriptInjectionManager::RFOHelper : public content::RenderFrameObserver {
  61. public:
  62. RFOHelper(content::RenderFrame* render_frame,
  63. ScriptInjectionManager* manager);
  64. ~RFOHelper() override;
  65. void Initialize();
  66. private:
  67. // RenderFrameObserver implementation.
  68. void DidCreateNewDocument() override;
  69. void DidCreateDocumentElement() override;
  70. void DidFailProvisionalLoad() override;
  71. void DidDispatchDOMContentLoadedEvent() override;
  72. void WillDetach() override;
  73. void OnDestruct() override;
  74. void OnStop() override;
  75. // Tells the ScriptInjectionManager to run tasks associated with
  76. // document_idle.
  77. void RunIdle();
  78. void StartInjectScripts(mojom::RunLocation run_location);
  79. // Indicate that the frame is no longer valid because it is starting
  80. // a new load or closing.
  81. void InvalidateAndResetFrame(bool force_reset);
  82. // The owning ScriptInjectionManager.
  83. ScriptInjectionManager* manager_;
  84. bool should_run_idle_ = true;
  85. base::WeakPtrFactory<RFOHelper> weak_factory_{this};
  86. };
  87. ScriptInjectionManager::RFOHelper::RFOHelper(content::RenderFrame* render_frame,
  88. ScriptInjectionManager* manager)
  89. : content::RenderFrameObserver(render_frame), manager_(manager) {}
  90. ScriptInjectionManager::RFOHelper::~RFOHelper() {
  91. }
  92. void ScriptInjectionManager::RFOHelper::Initialize() {
  93. // Set up for the initial empty document, for which the Document created
  94. // events do not happen as it's already present.
  95. DidCreateNewDocument();
  96. // The initial empty document for a main frame may have scripts attached to it
  97. // but we do not want to invalidate the frame and lose them when the next
  98. // document loads. For example the IncognitoApiTest.IncognitoSplitMode test
  99. // does `chrome.tabs.create()` with a script to be run, which is added to the
  100. // frame before it navigates, so it needs to be preserved. However scripts in
  101. // child frames are expected to be run inside the initial empty document. For
  102. // example the ExecuteScriptApiTest.FrameWithHttp204 test creates a child
  103. // frame at about:blank and expects to run injected scripts inside it.
  104. // This is all quite inconsistent however tests both depend on us queuing and
  105. // not queueing the kDocumentStart events in the initial empty document.
  106. if (!render_frame()->IsMainFrame()) {
  107. DidCreateDocumentElement();
  108. }
  109. }
  110. void ScriptInjectionManager::RFOHelper::DidCreateNewDocument() {
  111. // A new document is going to be shown, so invalidate the old document state.
  112. // Don't force-reset the frame, because it is possible that a script injection
  113. // was scheduled before the page was loaded, e.g. by navigating to a
  114. // javascript: URL before the page has loaded.
  115. constexpr bool kForceReset = false;
  116. InvalidateAndResetFrame(kForceReset);
  117. }
  118. void ScriptInjectionManager::RFOHelper::DidCreateDocumentElement() {
  119. ExtensionFrameHelper::Get(render_frame())
  120. ->ScheduleAtDocumentStart(base::BindOnce(
  121. &ScriptInjectionManager::RFOHelper::StartInjectScripts,
  122. weak_factory_.GetWeakPtr(), mojom::RunLocation::kDocumentStart));
  123. }
  124. void ScriptInjectionManager::RFOHelper::DidFailProvisionalLoad() {
  125. auto it = manager_->frame_statuses_.find(render_frame());
  126. if (it != manager_->frame_statuses_.end() &&
  127. it->second == mojom::RunLocation::kDocumentStart) {
  128. // Since the provisional load failed, the frame stays at its previous loaded
  129. // state and origin (or the parent's origin for new/about:blank frames).
  130. // Reset the frame to kDocumentIdle in order to reflect that the frame is
  131. // done loading, and avoid any deadlock in the system.
  132. //
  133. // We skip injection of kDocumentEnd and kDocumentIdle scripts, because the
  134. // injections closely follow the DOMContentLoaded (and onload) events, which
  135. // are not triggered after a failed provisional load.
  136. // This assumption is verified in the checkDOMContentLoadedEvent subtest of
  137. // ExecuteScriptApiTest.FrameWithHttp204 (browser_tests).
  138. constexpr bool kForceReset = true;
  139. InvalidateAndResetFrame(kForceReset);
  140. should_run_idle_ = false;
  141. manager_->frame_statuses_[render_frame()] =
  142. mojom::RunLocation::kDocumentIdle;
  143. }
  144. }
  145. void ScriptInjectionManager::RFOHelper::DidDispatchDOMContentLoadedEvent() {
  146. DCHECK(content::RenderThread::Get());
  147. ExtensionFrameHelper::Get(render_frame())
  148. ->ScheduleAtDocumentEnd(base::BindOnce(
  149. &ScriptInjectionManager::RFOHelper::StartInjectScripts,
  150. weak_factory_.GetWeakPtr(), mojom::RunLocation::kDocumentEnd));
  151. // We try to run idle in two places: a delayed task here and in response to
  152. // ContentRendererClient::RunScriptsAtDocumentIdle().
  153. // DidDispatchDOMContentLoadedEvent() corresponds to completing the document's
  154. // load, whereas RunScriptsAtDocumentIdle() corresponds to completing the
  155. // document and all subresources' load (but before the window.onload event).
  156. // We don't want to hold up script injection for a particularly slow
  157. // subresource, so we set a delayed task from here - but if we finish
  158. // everything before that point (i.e., RunScriptsAtDocumentIdle() is
  159. // triggered), then there's no reason to keep waiting.
  160. render_frame()
  161. ->GetTaskRunner(blink::TaskType::kInternalDefault)
  162. ->PostDelayedTask(
  163. FROM_HERE,
  164. base::BindOnce(&ScriptInjectionManager::RFOHelper::RunIdle,
  165. weak_factory_.GetWeakPtr()),
  166. base::Milliseconds(kScriptIdleTimeoutInMs));
  167. ExtensionFrameHelper::Get(render_frame())
  168. ->ScheduleAtDocumentIdle(
  169. base::BindOnce(&ScriptInjectionManager::RFOHelper::RunIdle,
  170. weak_factory_.GetWeakPtr()));
  171. }
  172. void ScriptInjectionManager::RFOHelper::WillDetach() {
  173. // The frame is closing - invalidate.
  174. constexpr bool kForceReset = true;
  175. InvalidateAndResetFrame(kForceReset);
  176. }
  177. void ScriptInjectionManager::RFOHelper::OnDestruct() {
  178. manager_->RemoveObserver(this);
  179. }
  180. void ScriptInjectionManager::RFOHelper::OnStop() {
  181. // If the navigation request fails (e.g. 204/205/downloads), notify the
  182. // extension to avoid keeping the frame in a START state indefinitely which
  183. // leads to deadlocks.
  184. DidFailProvisionalLoad();
  185. }
  186. void ScriptInjectionManager::RFOHelper::RunIdle() {
  187. // Only notify the manager if the frame hasn't already had idle run since the
  188. // task to RunIdle() was posted.
  189. if (should_run_idle_) {
  190. should_run_idle_ = false;
  191. manager_->StartInjectScripts(render_frame(),
  192. mojom::RunLocation::kDocumentIdle);
  193. }
  194. }
  195. void ScriptInjectionManager::RFOHelper::StartInjectScripts(
  196. mojom::RunLocation run_location) {
  197. manager_->StartInjectScripts(render_frame(), run_location);
  198. }
  199. void ScriptInjectionManager::RFOHelper::InvalidateAndResetFrame(
  200. bool force_reset) {
  201. // Invalidate any pending idle injections, and reset the frame inject on idle.
  202. weak_factory_.InvalidateWeakPtrs();
  203. // We reset to inject on idle, because the frame can be reused (in the case of
  204. // navigation).
  205. should_run_idle_ = true;
  206. // Reset the frame if either |force_reset| is true, or if the manager is
  207. // keeping track of the state of the frame (in which case we need to clean it
  208. // up).
  209. if (force_reset || manager_->frame_statuses_.count(render_frame()) != 0)
  210. manager_->InvalidateForFrame(render_frame());
  211. }
  212. ScriptInjectionManager::ScriptInjectionManager(
  213. UserScriptSetManager* user_script_set_manager)
  214. : user_script_set_manager_(user_script_set_manager) {
  215. user_script_set_manager_observation_.Observe(user_script_set_manager_);
  216. }
  217. ScriptInjectionManager::~ScriptInjectionManager() {
  218. for (const auto& injection : pending_injections_)
  219. injection->invalidate_render_frame();
  220. for (const auto& injection : running_injections_)
  221. injection->invalidate_render_frame();
  222. }
  223. void ScriptInjectionManager::OnRenderFrameCreated(
  224. content::RenderFrame* render_frame) {
  225. rfo_helpers_.push_back(std::make_unique<RFOHelper>(render_frame, this));
  226. rfo_helpers_.back()->Initialize();
  227. }
  228. void ScriptInjectionManager::OnExtensionUnloaded(
  229. const std::string& extension_id) {
  230. for (auto iter = pending_injections_.begin();
  231. iter != pending_injections_.end();) {
  232. if ((*iter)->host_id().id == extension_id) {
  233. (*iter)->OnHostRemoved();
  234. iter = pending_injections_.erase(iter);
  235. } else {
  236. ++iter;
  237. }
  238. }
  239. }
  240. void ScriptInjectionManager::OnInjectionFinished(ScriptInjection* injection) {
  241. base::EraseIf(running_injections_,
  242. [&injection](const std::unique_ptr<ScriptInjection>& mode) {
  243. return injection == mode.get();
  244. });
  245. }
  246. void ScriptInjectionManager::OnUserScriptsUpdated(
  247. const mojom::HostID& changed_host) {
  248. base::EraseIf(
  249. pending_injections_,
  250. [&changed_host](const std::unique_ptr<ScriptInjection>& injection) {
  251. return changed_host == injection->host_id();
  252. });
  253. }
  254. void ScriptInjectionManager::RemoveObserver(RFOHelper* helper) {
  255. for (auto iter = rfo_helpers_.begin(); iter != rfo_helpers_.end(); ++iter) {
  256. if (iter->get() == helper) {
  257. rfo_helpers_.erase(iter);
  258. break;
  259. }
  260. }
  261. }
  262. void ScriptInjectionManager::InvalidateForFrame(content::RenderFrame* frame) {
  263. // If the frame invalidated is the frame being injected into, we need to
  264. // note it.
  265. active_injection_frames_.erase(frame);
  266. base::EraseIf(pending_injections_,
  267. [&frame](const std::unique_ptr<ScriptInjection>& injection) {
  268. return injection->render_frame() == frame;
  269. });
  270. frame_statuses_.erase(frame);
  271. }
  272. void ScriptInjectionManager::StartInjectScripts(
  273. content::RenderFrame* frame,
  274. mojom::RunLocation run_location) {
  275. auto iter = frame_statuses_.find(frame);
  276. // We also don't execute if we detect that the run location is somehow out of
  277. // order. This can happen if:
  278. // - The first run location reported for the frame isn't kDocumentStart, or
  279. // - The run location reported doesn't immediately follow the previous
  280. // reported run location.
  281. // We don't want to run because extensions may have requirements that scripts
  282. // running in an earlier run location have run by the time a later script
  283. // runs. Better to just not run.
  284. // Note that we check run_location > NextRunLocation() in the second clause
  285. // (as opposed to !=) because earlier signals (like DidCreateDocumentElement)
  286. // can happen multiple times, so we can receive earlier/equal run locations.
  287. bool invalid_run_order = false;
  288. if (iter == frame_statuses_.end()) {
  289. invalid_run_order = (run_location != mojom::RunLocation::kDocumentStart);
  290. } else {
  291. absl::optional<mojom::RunLocation> next = NextRunLocation(iter->second);
  292. if (next)
  293. invalid_run_order = run_location > next.value();
  294. }
  295. if (invalid_run_order) {
  296. // We also invalidate the frame, because the run order of pending injections
  297. // may also be bad.
  298. InvalidateForFrame(frame);
  299. return;
  300. } else if (iter != frame_statuses_.end() && iter->second >= run_location) {
  301. // Certain run location signals (like DidCreateDocumentElement) can happen
  302. // multiple times. Ignore the subsequent signals.
  303. return;
  304. }
  305. // Otherwise, all is right in the world, and we can get on with the
  306. // injections!
  307. frame_statuses_[frame] = run_location;
  308. InjectScripts(frame, run_location);
  309. }
  310. void ScriptInjectionManager::InjectScripts(content::RenderFrame* frame,
  311. mojom::RunLocation run_location) {
  312. // Find any injections that want to run on the given frame.
  313. ScriptInjectionVector frame_injections;
  314. for (auto iter = pending_injections_.begin();
  315. iter != pending_injections_.end();) {
  316. if ((*iter)->render_frame() == frame) {
  317. frame_injections.push_back(std::move(*iter));
  318. iter = pending_injections_.erase(iter);
  319. } else {
  320. ++iter;
  321. }
  322. }
  323. // Add any injections for user scripts.
  324. int tab_id = ExtensionFrameHelper::Get(frame)->tab_id();
  325. user_script_set_manager_->GetAllInjections(&frame_injections, frame, tab_id,
  326. run_location);
  327. // Note that we are running in |frame|.
  328. active_injection_frames_.insert(frame);
  329. ScriptsRunInfo scripts_run_info(frame, run_location);
  330. for (auto iter = frame_injections.begin(); iter != frame_injections.end();) {
  331. // It's possible for the frame to be invalidated in the course of injection
  332. // (if a script removes its own frame, for example). If this happens, abort.
  333. if (!active_injection_frames_.count(frame))
  334. break;
  335. std::unique_ptr<ScriptInjection> injection(std::move(*iter));
  336. iter = frame_injections.erase(iter);
  337. TryToInject(std::move(injection), run_location, &scripts_run_info);
  338. }
  339. // We are done running in the frame.
  340. active_injection_frames_.erase(frame);
  341. scripts_run_info.LogRun(activity_logging_enabled_);
  342. }
  343. void ScriptInjectionManager::OnInjectionStatusUpdated(
  344. ScriptInjection::InjectionStatus status,
  345. ScriptInjection* injection) {
  346. switch (status) {
  347. case ScriptInjection::InjectionStatus::kPermitted:
  348. ScriptInjectionManager::OnPermitScriptInjectionHandled(injection);
  349. break;
  350. case ScriptInjection::InjectionStatus::kFinished:
  351. ScriptInjectionManager::OnInjectionFinished(injection);
  352. break;
  353. }
  354. }
  355. void ScriptInjectionManager::TryToInject(
  356. std::unique_ptr<ScriptInjection> injection,
  357. mojom::RunLocation run_location,
  358. ScriptsRunInfo* scripts_run_info) {
  359. // Try to inject the script. If the injection is waiting (i.e., for
  360. // permission), add it to the list of pending injections. If the injection
  361. // has blocked, add it to the list of running injections.
  362. // The Unretained below is safe because this object owns all the
  363. // ScriptInjections, so is guaranteed to outlive them.
  364. switch (injection->TryToInject(
  365. run_location, scripts_run_info,
  366. base::BindOnce(&ScriptInjectionManager::OnInjectionStatusUpdated,
  367. base::Unretained(this)))) {
  368. case ScriptInjection::INJECTION_WAITING:
  369. pending_injections_.push_back(std::move(injection));
  370. break;
  371. case ScriptInjection::INJECTION_BLOCKED:
  372. running_injections_.push_back(std::move(injection));
  373. break;
  374. case ScriptInjection::INJECTION_FINISHED:
  375. break;
  376. }
  377. }
  378. void ScriptInjectionManager::HandleExecuteCode(
  379. mojom::ExecuteCodeParamsPtr params,
  380. mojom::LocalFrame::ExecuteCodeCallback callback,
  381. content::RenderFrame* render_frame) {
  382. std::unique_ptr<const InjectionHost> injection_host;
  383. if (params->host_id->type == mojom::HostID::HostType::kExtensions) {
  384. injection_host = ExtensionInjectionHost::Create(params->host_id->id);
  385. if (!injection_host) {
  386. std::move(callback).Run(base::EmptyString(), GURL::EmptyGURL(),
  387. absl::nullopt);
  388. return;
  389. }
  390. } else if (params->host_id->type == mojom::HostID::HostType::kWebUi) {
  391. injection_host = std::make_unique<WebUIInjectionHost>(*params->host_id);
  392. }
  393. mojom::RunLocation run_at = params->run_at;
  394. auto injection = std::make_unique<ScriptInjection>(
  395. std::make_unique<ProgrammaticScriptInjector>(std::move(params),
  396. std::move(callback)),
  397. render_frame, std::move(injection_host), run_at,
  398. activity_logging_enabled_);
  399. FrameStatusMap::const_iterator iter = frame_statuses_.find(render_frame);
  400. mojom::RunLocation run_location = iter == frame_statuses_.end()
  401. ? mojom::RunLocation::kUndefined
  402. : iter->second;
  403. ScriptsRunInfo scripts_run_info(render_frame, run_location);
  404. TryToInject(std::move(injection), run_location, &scripts_run_info);
  405. }
  406. void ScriptInjectionManager::ExecuteDeclarativeScript(
  407. content::RenderFrame* render_frame,
  408. int tab_id,
  409. const ExtensionId& extension_id,
  410. const std::string& script_id,
  411. const GURL& url) {
  412. std::unique_ptr<ScriptInjection> injection =
  413. user_script_set_manager_->GetInjectionForDeclarativeScript(
  414. script_id, render_frame, tab_id, url, extension_id);
  415. if (injection.get()) {
  416. ScriptsRunInfo scripts_run_info(render_frame,
  417. mojom::RunLocation::kBrowserDriven);
  418. // TODO(https://crbug.com/1186525): Use return value of TryToInject for
  419. // error handling.
  420. TryToInject(std::move(injection), mojom::RunLocation::kBrowserDriven,
  421. &scripts_run_info);
  422. scripts_run_info.LogRun(activity_logging_enabled_);
  423. }
  424. }
  425. void ScriptInjectionManager::OnPermitScriptInjectionHandled(
  426. ScriptInjection* injection) {
  427. auto iter =
  428. std::find_if(pending_injections_.begin(), pending_injections_.end(),
  429. [injection](const std::unique_ptr<ScriptInjection>& mode) {
  430. return injection == mode.get();
  431. });
  432. if (iter == pending_injections_.end())
  433. return;
  434. DCHECK((*iter)->host_id().type == mojom::HostID::HostType::kExtensions);
  435. // At this point, because the injection is present in pending_injections_, we
  436. // know that this is the same page that issued the request (otherwise,
  437. // RFOHelper::InvalidateAndResetFrame would have caused it to be cleared out).
  438. std::unique_ptr<ScriptInjection> script_injection(std::move(*iter));
  439. pending_injections_.erase(iter);
  440. ScriptsRunInfo scripts_run_info(script_injection->render_frame(),
  441. mojom::RunLocation::kRunDeferred);
  442. ScriptInjection::InjectionResult res =
  443. script_injection->OnPermissionGranted(&scripts_run_info);
  444. if (res == ScriptInjection::INJECTION_BLOCKED)
  445. running_injections_.push_back(std::move(script_injection));
  446. scripts_run_info.LogRun(activity_logging_enabled_);
  447. }
  448. } // namespace extensions