user_script_injector.cc 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  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/user_script_injector.h"
  5. #include <tuple>
  6. #include <vector>
  7. #include "base/check.h"
  8. #include "base/lazy_instance.h"
  9. #include "base/no_destructor.h"
  10. #include "content/public/common/url_constants.h"
  11. #include "content/public/renderer/render_frame.h"
  12. #include "content/public/renderer/render_thread.h"
  13. #include "extensions/common/extension.h"
  14. #include "extensions/common/mojom/guest_view.mojom.h"
  15. #include "extensions/common/permissions/permissions_data.h"
  16. #include "extensions/grit/extensions_renderer_resources.h"
  17. #include "extensions/renderer/extension_frame_helper.h"
  18. #include "extensions/renderer/injection_host.h"
  19. #include "extensions/renderer/script_context.h"
  20. #include "extensions/renderer/scripts_run_info.h"
  21. #include "ipc/ipc_sync_channel.h"
  22. #include "third_party/blink/public/web/web_document.h"
  23. #include "third_party/blink/public/web/web_local_frame.h"
  24. #include "third_party/blink/public/web/web_script_source.h"
  25. #include "ui/base/resource/resource_bundle.h"
  26. #include "url/gurl.h"
  27. namespace extensions {
  28. namespace {
  29. struct RoutingInfoKey {
  30. int routing_id;
  31. std::string script_id;
  32. RoutingInfoKey(int routing_id, std::string script_id)
  33. : routing_id(routing_id), script_id(std::move(script_id)) {}
  34. bool operator<(const RoutingInfoKey& other) const {
  35. return std::tie(routing_id, script_id) <
  36. std::tie(other.routing_id, other.script_id);
  37. }
  38. };
  39. using RoutingInfoMap = std::map<RoutingInfoKey, bool>;
  40. // A map records whether a given |script_id| from a webview-added user script
  41. // is allowed to inject on the render of given |routing_id|.
  42. // Once a script is added, the decision of whether or not allowed to inject
  43. // won't be changed.
  44. // After removed by the webview, the user scipt will also be removed
  45. // from the render. Therefore, there won't be any query from the same
  46. // |script_id| and |routing_id| pair.
  47. base::LazyInstance<RoutingInfoMap>::DestructorAtExit g_routing_info_map =
  48. LAZY_INSTANCE_INITIALIZER;
  49. // Greasemonkey API source that is injected with the scripts.
  50. struct GreasemonkeyApiJsString {
  51. GreasemonkeyApiJsString();
  52. blink::WebScriptSource GetSource() const;
  53. private:
  54. blink::WebString source_;
  55. };
  56. // The below constructor, monstrous as it is, just makes a WebScriptSource from
  57. // the GreasemonkeyApiJs resource.
  58. GreasemonkeyApiJsString::GreasemonkeyApiJsString() {
  59. std::string greasemonky_api_js(
  60. ui::ResourceBundle::GetSharedInstance().LoadDataResourceString(
  61. IDR_GREASEMONKEY_API_JS));
  62. source_ = blink::WebString::FromUTF8(greasemonky_api_js);
  63. }
  64. blink::WebScriptSource GreasemonkeyApiJsString::GetSource() const {
  65. return blink::WebScriptSource(source_);
  66. }
  67. base::LazyInstance<GreasemonkeyApiJsString>::Leaky g_greasemonkey_api =
  68. LAZY_INSTANCE_INITIALIZER;
  69. bool ShouldInjectScripts(const UserScript::FileList& scripts,
  70. const std::set<std::string>& injected_files) {
  71. for (const std::unique_ptr<UserScript::File>& file : scripts) {
  72. // Check if the script is already injected.
  73. if (injected_files.count(file->url().path()) == 0) {
  74. return true;
  75. }
  76. }
  77. return false;
  78. }
  79. mojom::GuestView* GetGuestView() {
  80. static base::NoDestructor<mojo::AssociatedRemote<mojom::GuestView>>
  81. guest_view;
  82. if (!*guest_view) {
  83. content::RenderThread::Get()->GetChannel()->GetRemoteAssociatedInterface(
  84. guest_view.get());
  85. }
  86. return guest_view->get();
  87. }
  88. } // namespace
  89. UserScriptInjector::UserScriptInjector(const UserScript* script,
  90. UserScriptSet* script_list,
  91. bool is_declarative)
  92. : script_(script),
  93. user_script_set_(script_list),
  94. script_id_(script_->id()),
  95. host_id_(script_->host_id()),
  96. is_declarative_(is_declarative) {
  97. user_script_set_observation_.Observe(script_list);
  98. }
  99. UserScriptInjector::~UserScriptInjector() {
  100. }
  101. void UserScriptInjector::OnUserScriptsUpdated() {
  102. // When user scripts are updated, this means the host causing this injection
  103. // has changed. All old script pointers are invalidated and this injection
  104. // will be removed as there's no guarantee the backing script still exists.
  105. script_ = nullptr;
  106. }
  107. void UserScriptInjector::OnUserScriptSetDestroyed() {
  108. user_script_set_observation_.Reset();
  109. // Invalidate the script pointer as the UserScriptSet which this script
  110. // belongs to has been destroyed.
  111. script_ = nullptr;
  112. }
  113. mojom::InjectionType UserScriptInjector::script_type() const {
  114. return mojom::InjectionType::kContentScript;
  115. }
  116. blink::mojom::UserActivationOption UserScriptInjector::IsUserGesture() const {
  117. return blink::mojom::UserActivationOption::kDoNotActivate;
  118. }
  119. mojom::ExecutionWorld UserScriptInjector::GetExecutionWorld() const {
  120. return script_->execution_world();
  121. }
  122. blink::mojom::WantResultOption UserScriptInjector::ExpectsResults() const {
  123. return blink::mojom::WantResultOption::kNoResult;
  124. }
  125. blink::mojom::PromiseResultOption UserScriptInjector::ShouldWaitForPromise()
  126. const {
  127. return blink::mojom::PromiseResultOption::kDoNotWait;
  128. }
  129. mojom::CSSOrigin UserScriptInjector::GetCssOrigin() const {
  130. return mojom::CSSOrigin::kAuthor;
  131. }
  132. mojom::CSSInjection::Operation UserScriptInjector::GetCSSInjectionOperation()
  133. const {
  134. DCHECK(script_);
  135. DCHECK(!script_->css_scripts().empty());
  136. return mojom::CSSInjection::Operation::kAdd;
  137. }
  138. bool UserScriptInjector::ShouldInjectJs(
  139. mojom::RunLocation run_location,
  140. const std::set<std::string>& executing_scripts) const {
  141. return script_ && script_->run_location() == run_location &&
  142. !script_->js_scripts().empty() &&
  143. ShouldInjectScripts(script_->js_scripts(), executing_scripts);
  144. }
  145. bool UserScriptInjector::ShouldInjectOrRemoveCss(
  146. mojom::RunLocation run_location,
  147. const std::set<std::string>& injected_stylesheets) const {
  148. return script_ && run_location == mojom::RunLocation::kDocumentStart &&
  149. !script_->css_scripts().empty() &&
  150. ShouldInjectScripts(script_->css_scripts(), injected_stylesheets);
  151. }
  152. PermissionsData::PageAccess UserScriptInjector::CanExecuteOnFrame(
  153. const InjectionHost* injection_host,
  154. blink::WebLocalFrame* web_frame,
  155. int tab_id) {
  156. // There is no harm in allowing the injection when the script is gone,
  157. // because there is nothing to inject.
  158. if (!script_)
  159. return PermissionsData::PageAccess::kAllowed;
  160. if (script_->consumer_instance_type() ==
  161. UserScript::ConsumerInstanceType::WEBVIEW) {
  162. int routing_id =
  163. content::RenderFrame::FromWebFrame(web_frame)->GetRoutingID();
  164. RoutingInfoKey key(routing_id, script_->id());
  165. RoutingInfoMap& map = g_routing_info_map.Get();
  166. auto iter = map.find(key);
  167. bool allowed = false;
  168. if (iter != map.end()) {
  169. allowed = iter->second;
  170. } else {
  171. // Perform a sync mojo call to the browser to check if this is allowed.
  172. // This is not ideal, but is mitigated by the fact that this is only done
  173. // for webviews, and then only once per host.
  174. // TODO(hanxi): Find a more efficient way to do this.
  175. auto* guest_view = GetGuestView();
  176. if (guest_view) {
  177. guest_view->CanExecuteContentScript(routing_id, script_->id(),
  178. &allowed);
  179. }
  180. map.insert(std::pair<RoutingInfoKey, bool>(key, allowed));
  181. }
  182. return allowed ? PermissionsData::PageAccess::kAllowed
  183. : PermissionsData::PageAccess::kDenied;
  184. }
  185. GURL effective_document_url =
  186. ScriptContext::GetEffectiveDocumentURLForInjection(
  187. web_frame, web_frame->GetDocument().Url(),
  188. script_->match_origin_as_fallback());
  189. return injection_host->CanExecuteOnFrame(
  190. effective_document_url,
  191. content::RenderFrame::FromWebFrame(web_frame),
  192. tab_id,
  193. is_declarative_);
  194. }
  195. std::vector<blink::WebScriptSource> UserScriptInjector::GetJsSources(
  196. mojom::RunLocation run_location,
  197. std::set<std::string>* executing_scripts,
  198. size_t* num_injected_js_scripts) const {
  199. DCHECK(script_);
  200. std::vector<blink::WebScriptSource> sources;
  201. DCHECK_EQ(script_->run_location(), run_location);
  202. const UserScript::FileList& js_scripts = script_->js_scripts();
  203. sources.reserve(js_scripts.size() +
  204. (script_->emulate_greasemonkey() ? 1 : 0));
  205. // Emulate Greasemonkey API for scripts that were converted to extension
  206. // user scripts.
  207. if (script_->emulate_greasemonkey())
  208. sources.push_back(g_greasemonkey_api.Get().GetSource());
  209. for (const std::unique_ptr<UserScript::File>& file : js_scripts) {
  210. const GURL& script_url = file->url();
  211. // Check if the script is already injected.
  212. if (executing_scripts->count(script_url.path()) != 0)
  213. continue;
  214. sources.push_back(blink::WebScriptSource(
  215. user_script_set_->GetJsSource(*file, script_->emulate_greasemonkey()),
  216. script_url));
  217. ++*num_injected_js_scripts;
  218. executing_scripts->insert(script_url.path());
  219. }
  220. return sources;
  221. }
  222. std::vector<ScriptInjector::CSSSource> UserScriptInjector::GetCssSources(
  223. mojom::RunLocation run_location,
  224. std::set<std::string>* injected_stylesheets,
  225. size_t* num_injected_stylesheets) const {
  226. DCHECK(script_);
  227. DCHECK_EQ(mojom::RunLocation::kDocumentStart, run_location);
  228. std::vector<CSSSource> sources;
  229. const UserScript::FileList& css_scripts = script_->css_scripts();
  230. sources.reserve(css_scripts.size());
  231. for (const std::unique_ptr<UserScript::File>& file : script_->css_scripts()) {
  232. const std::string& stylesheet_path = file->url().path();
  233. // Check if the stylesheet is already injected.
  234. if (injected_stylesheets->count(stylesheet_path) != 0)
  235. continue;
  236. sources.push_back(CSSSource{user_script_set_->GetCssSource(*file),
  237. blink::WebStyleSheetKey()});
  238. injected_stylesheets->insert(stylesheet_path);
  239. }
  240. *num_injected_stylesheets += sources.size();
  241. return sources;
  242. }
  243. void UserScriptInjector::OnInjectionComplete(
  244. std::unique_ptr<base::Value> execution_result,
  245. mojom::RunLocation run_location) {}
  246. void UserScriptInjector::OnWillNotInject(InjectFailureReason reason) {}
  247. } // namespace extensions