scripting_api.cc 52 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375
  1. // Copyright 2023 Microsoft, GmbH
  2. // Use of this source code is governed by the MIT license that can be
  3. // found in the LICENSE file.
  4. #include "shell/browser/extensions/api/scripting/scripting_api.h"
  5. #include <algorithm>
  6. #include "base/check.h"
  7. #include "base/containers/contains.h"
  8. #include "base/json/json_writer.h"
  9. #include "base/strings/string_util.h"
  10. #include "base/strings/stringprintf.h"
  11. #include "base/strings/utf_string_conversions.h"
  12. #include "base/types/optional_util.h"
  13. #include "chrome/common/extensions/api/scripting.h"
  14. #include "content/public/browser/browser_task_traits.h"
  15. #include "content/public/browser/navigation_controller.h"
  16. #include "content/public/browser/navigation_entry.h"
  17. #include "extensions/browser/extension_api_frame_id_map.h"
  18. #include "extensions/browser/extension_file_task_runner.h"
  19. #include "extensions/browser/extension_registry.h"
  20. #include "extensions/browser/extension_system.h"
  21. #include "extensions/browser/extension_user_script_loader.h"
  22. #include "extensions/browser/extension_util.h"
  23. #include "extensions/browser/load_and_localize_file.h"
  24. #include "extensions/browser/script_executor.h"
  25. #include "extensions/browser/scripting_constants.h"
  26. #include "extensions/browser/scripting_utils.h"
  27. #include "extensions/browser/user_script_manager.h"
  28. #include "extensions/common/api/extension_types.h"
  29. #include "extensions/common/api/scripts_internal.h"
  30. #include "extensions/common/api/scripts_internal/script_serialization.h"
  31. #include "extensions/common/error_utils.h"
  32. #include "extensions/common/extension.h"
  33. #include "extensions/common/manifest_constants.h"
  34. #include "extensions/common/mojom/css_origin.mojom-shared.h"
  35. #include "extensions/common/mojom/execution_world.mojom-shared.h"
  36. #include "extensions/common/mojom/host_id.mojom.h"
  37. #include "extensions/common/mojom/run_location.mojom-shared.h"
  38. #include "extensions/common/permissions/api_permission.h"
  39. #include "extensions/common/permissions/permissions_data.h"
  40. #include "extensions/common/script_constants.h"
  41. #include "extensions/common/utils/content_script_utils.h"
  42. #include "extensions/common/utils/extension_types_utils.h"
  43. #include "shell/browser/api/electron_api_web_contents.h"
  44. namespace extensions {
  45. namespace {
  46. constexpr char kCouldNotLoadFileError[] = "Could not load file: '*'.";
  47. constexpr char kDuplicateFileSpecifiedError[] =
  48. "Duplicate file specified: '*'.";
  49. constexpr char kExactlyOneOfCssAndFilesError[] =
  50. "Exactly one of 'css' and 'files' must be specified.";
  51. constexpr char kNonExistentScriptIdError[] = "Nonexistent script ID '*'";
  52. // Note: CSS always injects as soon as possible, so we default to
  53. // document_start. Because of tab loading, there's no guarantee this will
  54. // *actually* inject before page load, but it will at least inject "soon".
  55. constexpr mojom::RunLocation kCSSRunLocation =
  56. mojom::RunLocation::kDocumentStart;
  57. // Converts the given `style_origin` to a CSSOrigin.
  58. mojom::CSSOrigin ConvertStyleOriginToCSSOrigin(
  59. api::scripting::StyleOrigin style_origin) {
  60. mojom::CSSOrigin css_origin = mojom::CSSOrigin::kAuthor;
  61. switch (style_origin) {
  62. case api::scripting::StyleOrigin::kNone:
  63. case api::scripting::StyleOrigin::kAuthor:
  64. css_origin = mojom::CSSOrigin::kAuthor;
  65. break;
  66. case api::scripting::StyleOrigin::kUser:
  67. css_origin = mojom::CSSOrigin::kUser;
  68. break;
  69. }
  70. return css_origin;
  71. }
  72. mojom::ExecutionWorld ConvertExecutionWorld(
  73. api::scripting::ExecutionWorld world) {
  74. mojom::ExecutionWorld execution_world = mojom::ExecutionWorld::kIsolated;
  75. switch (world) {
  76. case api::scripting::ExecutionWorld::kNone:
  77. case api::scripting::ExecutionWorld::kIsolated:
  78. break; // Default to mojom::ExecutionWorld::kIsolated.
  79. case api::scripting::ExecutionWorld::kMain:
  80. execution_world = mojom::ExecutionWorld::kMain;
  81. }
  82. return execution_world;
  83. }
  84. std::string InjectionKeyForCode(const mojom::HostID& host_id,
  85. const std::string& code) {
  86. return ScriptExecutor::GenerateInjectionKey(host_id, /*script_url=*/GURL(),
  87. code);
  88. }
  89. std::string InjectionKeyForFile(const mojom::HostID& host_id,
  90. const GURL& resource_url) {
  91. return ScriptExecutor::GenerateInjectionKey(host_id, resource_url,
  92. /*code=*/std::string());
  93. }
  94. // Constructs an array of file sources from the read file `data`.
  95. std::vector<InjectedFileSource> ConstructFileSources(
  96. std::vector<std::unique_ptr<std::string>> data,
  97. std::vector<std::string> file_names) {
  98. // Note: CHECK (and not DCHECK) because if it fails, we have an out-of-bounds
  99. // access.
  100. CHECK_EQ(data.size(), file_names.size());
  101. const size_t num_sources = data.size();
  102. std::vector<InjectedFileSource> sources;
  103. sources.reserve(num_sources);
  104. for (size_t i = 0; i < num_sources; ++i)
  105. sources.emplace_back(std::move(file_names[i]), std::move(data[i]));
  106. return sources;
  107. }
  108. std::vector<mojom::JSSourcePtr> FileSourcesToJSSources(
  109. const Extension& extension,
  110. std::vector<InjectedFileSource> file_sources) {
  111. std::vector<mojom::JSSourcePtr> js_sources;
  112. js_sources.reserve(file_sources.size());
  113. for (auto& file_source : file_sources) {
  114. js_sources.push_back(
  115. mojom::JSSource::New(std::move(*file_source.data),
  116. extension.GetResourceURL(file_source.file_name)));
  117. }
  118. return js_sources;
  119. }
  120. std::vector<mojom::CSSSourcePtr> FileSourcesToCSSSources(
  121. const Extension& extension,
  122. std::vector<InjectedFileSource> file_sources) {
  123. mojom::HostID host_id(mojom::HostID::HostType::kExtensions, extension.id());
  124. std::vector<mojom::CSSSourcePtr> css_sources;
  125. css_sources.reserve(file_sources.size());
  126. for (auto& file_source : file_sources) {
  127. css_sources.push_back(mojom::CSSSource::New(
  128. std::move(*file_source.data),
  129. InjectionKeyForFile(host_id,
  130. extension.GetResourceURL(file_source.file_name))));
  131. }
  132. return css_sources;
  133. }
  134. // Checks `files` and populates `resources_out` with the appropriate extension
  135. // resource. Returns true on success; on failure, populates `error_out`.
  136. bool GetFileResources(const std::vector<std::string>& files,
  137. const Extension& extension,
  138. std::vector<ExtensionResource>* resources_out,
  139. std::string* error_out) {
  140. if (files.empty()) {
  141. static constexpr char kAtLeastOneFileError[] =
  142. "At least one file must be specified.";
  143. *error_out = kAtLeastOneFileError;
  144. return false;
  145. }
  146. std::vector<ExtensionResource> resources;
  147. for (const auto& file : files) {
  148. ExtensionResource resource = extension.GetResource(file);
  149. if (resource.extension_root().empty() || resource.relative_path().empty()) {
  150. *error_out = ErrorUtils::FormatErrorMessage(kCouldNotLoadFileError, file);
  151. return false;
  152. }
  153. // ExtensionResource doesn't implement an operator==.
  154. if (base::Contains(resources, resource.relative_path(),
  155. &ExtensionResource::relative_path)) {
  156. // Disallow duplicates. Note that we could allow this, if we wanted (and
  157. // there *might* be reason to with JS injection, to perform an operation
  158. // twice?). However, this matches content script behavior, and injecting
  159. // twice can be done by chaining calls to executeScript() / insertCSS().
  160. // This isn't a robust check, and could probably be circumvented by
  161. // passing two paths that look different but are the same - but in that
  162. // case, we just try to load and inject the script twice, which is
  163. // inefficient, but safe.
  164. *error_out =
  165. ErrorUtils::FormatErrorMessage(kDuplicateFileSpecifiedError, file);
  166. return false;
  167. }
  168. resources.push_back(std::move(resource));
  169. }
  170. resources_out->swap(resources);
  171. return true;
  172. }
  173. using ResourcesLoadedCallback =
  174. base::OnceCallback<void(std::vector<InjectedFileSource>,
  175. std::optional<std::string>)>;
  176. // Checks the loaded content of extension resources. Invokes `callback` with
  177. // the constructed file sources on success or with an error on failure.
  178. void CheckLoadedResources(std::vector<std::string> file_names,
  179. ResourcesLoadedCallback callback,
  180. std::vector<std::unique_ptr<std::string>> file_data,
  181. std::optional<std::string> load_error) {
  182. if (load_error) {
  183. std::move(callback).Run({}, std::move(load_error));
  184. return;
  185. }
  186. std::vector<InjectedFileSource> file_sources =
  187. ConstructFileSources(std::move(file_data), std::move(file_names));
  188. for (const auto& source : file_sources) {
  189. DCHECK(source.data);
  190. // TODO(devlin): What necessitates this encoding requirement? Is it needed
  191. // for blink injection?
  192. if (!base::IsStringUTF8(*source.data)) {
  193. static constexpr char kBadFileEncodingError[] =
  194. "Could not load file '*'. It isn't UTF-8 encoded.";
  195. std::string error = ErrorUtils::FormatErrorMessage(kBadFileEncodingError,
  196. source.file_name);
  197. std::move(callback).Run({}, std::move(error));
  198. return;
  199. }
  200. }
  201. std::move(callback).Run(std::move(file_sources), std::nullopt);
  202. }
  203. // Checks the specified `files` for validity, and attempts to load and localize
  204. // them, invoking `callback` with the result. Returns true on success; on
  205. // failure, populates `error`.
  206. bool CheckAndLoadFiles(std::vector<std::string> files,
  207. const Extension& extension,
  208. bool requires_localization,
  209. ResourcesLoadedCallback callback,
  210. std::string* error) {
  211. std::vector<ExtensionResource> resources;
  212. if (!GetFileResources(files, extension, &resources, error))
  213. return false;
  214. LoadAndLocalizeResources(
  215. extension, resources, requires_localization,
  216. script_parsing::GetMaxScriptLength(),
  217. base::BindOnce(&CheckLoadedResources, std::move(files),
  218. std::move(callback)));
  219. return true;
  220. }
  221. // Returns an error message string for when an extension cannot access a page it
  222. // is attempting to.
  223. std::string GetCannotAccessPageErrorMessage(const PermissionsData& permissions,
  224. const GURL& url) {
  225. if (permissions.HasAPIPermission(mojom::APIPermissionID::kTab)) {
  226. return ErrorUtils::FormatErrorMessage(
  227. manifest_errors::kCannotAccessPageWithUrl, url.spec());
  228. }
  229. return manifest_errors::kCannotAccessPage;
  230. }
  231. // Returns true if the `permissions` allow for injection into the given `frame`.
  232. // If false, populates `error`.
  233. bool HasPermissionToInjectIntoFrame(const PermissionsData& permissions,
  234. int tab_id,
  235. content::RenderFrameHost* frame,
  236. std::string* error) {
  237. GURL committed_url = frame->GetLastCommittedURL();
  238. if (committed_url.is_empty()) {
  239. if (!frame->IsInPrimaryMainFrame()) {
  240. // We can't check the pending URL for subframes from the //chrome layer.
  241. // Assume the injection is allowed; the renderer has additional checks
  242. // later on.
  243. return true;
  244. }
  245. // Unknown URL, e.g. because no load was committed yet. In this case we look
  246. // for any pending entry on the NavigationController associated with the
  247. // WebContents for the frame.
  248. content::WebContents* web_contents =
  249. content::WebContents::FromRenderFrameHost(frame);
  250. content::NavigationEntry* pending_entry =
  251. web_contents->GetController().GetPendingEntry();
  252. if (!pending_entry) {
  253. *error = manifest_errors::kCannotAccessPage;
  254. return false;
  255. }
  256. GURL pending_url = pending_entry->GetURL();
  257. if (pending_url.SchemeIsHTTPOrHTTPS() &&
  258. !permissions.CanAccessPage(pending_url, tab_id, error)) {
  259. // This catches the majority of cases where an extension tried to inject
  260. // on a newly-created navigating tab, saving us a potentially-costly IPC
  261. // and, maybe, slightly reducing (but not by any stretch eliminating) an
  262. // attack surface.
  263. *error = GetCannotAccessPageErrorMessage(permissions, pending_url);
  264. return false;
  265. }
  266. // Otherwise allow for now. The renderer has additional checks and will
  267. // fail the injection if needed.
  268. return true;
  269. }
  270. // TODO(devlin): Add more schemes here, in line with
  271. // https://crbug.com/55084.
  272. if (committed_url.SchemeIs(url::kAboutScheme) ||
  273. committed_url.SchemeIs(url::kDataScheme)) {
  274. url::Origin origin = frame->GetLastCommittedOrigin();
  275. const url::SchemeHostPort& tuple_or_precursor_tuple =
  276. origin.GetTupleOrPrecursorTupleIfOpaque();
  277. if (!tuple_or_precursor_tuple.IsValid()) {
  278. *error = GetCannotAccessPageErrorMessage(permissions, committed_url);
  279. return false;
  280. }
  281. committed_url = tuple_or_precursor_tuple.GetURL();
  282. }
  283. return permissions.CanAccessPage(committed_url, tab_id, error);
  284. }
  285. // Collects the frames for injection. Method will return false if an error is
  286. // encountered.
  287. bool CollectFramesForInjection(const api::scripting::InjectionTarget& target,
  288. content::WebContents* tab,
  289. std::set<int>& frame_ids,
  290. std::set<content::RenderFrameHost*>& frames,
  291. std::string* error_out) {
  292. if (target.document_ids) {
  293. for (const auto& id : *target.document_ids) {
  294. ExtensionApiFrameIdMap::DocumentId document_id =
  295. ExtensionApiFrameIdMap::DocumentIdFromString(id);
  296. if (!document_id) {
  297. *error_out = base::StringPrintf("Invalid document id %s", id.c_str());
  298. return false;
  299. }
  300. content::RenderFrameHost* frame =
  301. ExtensionApiFrameIdMap::Get()->GetRenderFrameHostByDocumentId(
  302. document_id);
  303. // If the frame was not found or it matched another tab reject this
  304. // request.
  305. if (!frame || content::WebContents::FromRenderFrameHost(frame) != tab) {
  306. *error_out =
  307. base::StringPrintf("No document with id %s in tab with id %d",
  308. id.c_str(), target.tab_id);
  309. return false;
  310. }
  311. // Convert the documentId into a frameId since the content will be
  312. // injected synchronously.
  313. frame_ids.insert(ExtensionApiFrameIdMap::GetFrameId(frame));
  314. frames.insert(frame);
  315. }
  316. } else {
  317. if (target.frame_ids) {
  318. frame_ids.insert(target.frame_ids->begin(), target.frame_ids->end());
  319. } else {
  320. frame_ids.insert(ExtensionApiFrameIdMap::kTopFrameId);
  321. }
  322. for (int frame_id : frame_ids) {
  323. content::RenderFrameHost* frame =
  324. ExtensionApiFrameIdMap::GetRenderFrameHostById(tab, frame_id);
  325. if (!frame) {
  326. *error_out = base::StringPrintf("No frame with id %d in tab with id %d",
  327. frame_id, target.tab_id);
  328. return false;
  329. }
  330. frames.insert(frame);
  331. }
  332. }
  333. return true;
  334. }
  335. // Returns true if the `target` can be accessed with the given `permissions`.
  336. // If the target can be accessed, populates `script_executor_out`,
  337. // `frame_scope_out`, and `frame_ids_out` with the appropriate values;
  338. // if the target cannot be accessed, populates `error_out`.
  339. bool CanAccessTarget(const PermissionsData& permissions,
  340. const api::scripting::InjectionTarget& target,
  341. content::BrowserContext* browser_context,
  342. bool include_incognito_information,
  343. ScriptExecutor** script_executor_out,
  344. ScriptExecutor::FrameScope* frame_scope_out,
  345. std::set<int>* frame_ids_out,
  346. std::string* error_out) {
  347. auto* contents = electron::api::WebContents::FromID(target.tab_id);
  348. if (!contents) {
  349. *error_out = base::StringPrintf("No tab with id: %d", target.tab_id);
  350. return false;
  351. }
  352. content::WebContents* tab = contents->web_contents();
  353. if ((target.all_frames && *target.all_frames == true) &&
  354. (target.frame_ids || target.document_ids)) {
  355. *error_out =
  356. "Cannot specify 'allFrames' if either 'frameIds' or 'documentIds' is "
  357. "specified.";
  358. return false;
  359. }
  360. if (target.frame_ids && target.document_ids) {
  361. *error_out = "Cannot specify both 'frameIds' and 'documentIds'.";
  362. return false;
  363. }
  364. ScriptExecutor* script_executor = contents->script_executor();
  365. DCHECK(script_executor);
  366. ScriptExecutor::FrameScope frame_scope =
  367. target.all_frames && *target.all_frames == true
  368. ? ScriptExecutor::INCLUDE_SUB_FRAMES
  369. : ScriptExecutor::SPECIFIED_FRAMES;
  370. std::set<int> frame_ids;
  371. std::set<content::RenderFrameHost*> frames;
  372. if (!CollectFramesForInjection(target, tab, frame_ids, frames, error_out))
  373. return false;
  374. // TODO(devlin): If `allFrames` is true, we error out if the extension
  375. // doesn't have access to the top frame (even if it may inject in child
  376. // frames). This is inconsistent with content scripts (which can execute on
  377. // child frames), but consistent with the old tabs.executeScript() API.
  378. for (content::RenderFrameHost* frame : frames) {
  379. DCHECK_EQ(content::WebContents::FromRenderFrameHost(frame), tab);
  380. if (!HasPermissionToInjectIntoFrame(permissions, target.tab_id, frame,
  381. error_out)) {
  382. return false;
  383. }
  384. }
  385. *frame_ids_out = std::move(frame_ids);
  386. *frame_scope_out = frame_scope;
  387. *script_executor_out = script_executor;
  388. return true;
  389. }
  390. api::scripts_internal::SerializedUserScript
  391. ConvertRegisteredContentScriptToSerializedUserScript(
  392. api::scripting::RegisteredContentScript content_script) {
  393. auto convert_execution_world = [](api::scripting::ExecutionWorld world) {
  394. switch (world) {
  395. case api::scripting::ExecutionWorld::kNone:
  396. case api::scripting::ExecutionWorld::kIsolated:
  397. return api::extension_types::ExecutionWorld::kIsolated;
  398. case api::scripting::ExecutionWorld::kMain:
  399. return api::extension_types::ExecutionWorld::kMain;
  400. }
  401. };
  402. api::scripts_internal::SerializedUserScript serialized_script;
  403. serialized_script.source =
  404. api::scripts_internal::Source::kDynamicContentScript;
  405. // Note: IDs have already been prefixed appropriately.
  406. serialized_script.id = std::move(content_script.id);
  407. // Note: `matches` are guaranteed to be non-null.
  408. serialized_script.matches = std::move(*content_script.matches);
  409. serialized_script.exclude_matches = std::move(content_script.exclude_matches);
  410. if (content_script.css) {
  411. serialized_script.css = script_serialization::GetSourcesFromFileNames(
  412. std::move(*content_script.css));
  413. }
  414. if (content_script.js) {
  415. serialized_script.js = script_serialization::GetSourcesFromFileNames(
  416. std::move(*content_script.js));
  417. }
  418. serialized_script.all_frames = content_script.all_frames;
  419. serialized_script.match_origin_as_fallback =
  420. content_script.match_origin_as_fallback;
  421. serialized_script.run_at = content_script.run_at;
  422. serialized_script.world = convert_execution_world(content_script.world);
  423. return serialized_script;
  424. }
  425. std::unique_ptr<UserScript> ParseUserScript(
  426. content::BrowserContext* browser_context,
  427. const Extension& extension,
  428. api::scripting::RegisteredContentScript content_script,
  429. std::u16string* error) {
  430. api::scripts_internal::SerializedUserScript serialized_script =
  431. ConvertRegisteredContentScriptToSerializedUserScript(
  432. std::move(content_script));
  433. std::unique_ptr<UserScript> user_script =
  434. script_serialization::ParseSerializedUserScript(serialized_script,
  435. extension, error);
  436. if (!user_script) {
  437. return nullptr; // Parsing failed.
  438. }
  439. // Post conversion validation and values.
  440. // TODO(https://crbug.com/1494155): See which of these can be moved into
  441. // script_serialization::ParseSerializedUserScript().
  442. if (!script_parsing::ValidateMatchOriginAsFallback(
  443. user_script->match_origin_as_fallback(), user_script->url_patterns(),
  444. error)) {
  445. return nullptr;
  446. }
  447. user_script->set_incognito_enabled(
  448. util::IsIncognitoEnabled(extension.id(), browser_context));
  449. return user_script;
  450. }
  451. // Converts a UserScript object to a api::scripting::RegisteredContentScript
  452. // object, used for getRegisteredContentScripts.
  453. api::scripting::RegisteredContentScript CreateRegisteredContentScriptInfo(
  454. const UserScript& script) {
  455. CHECK_EQ(UserScript::Source::kDynamicContentScript, script.GetSource());
  456. // To convert a `UserScript`, we first go through our script_internal
  457. // serialization; this allows us to do simple conversions and avoid any
  458. // complex logic.
  459. api::scripts_internal::SerializedUserScript serialized_script =
  460. script_serialization::SerializeUserScript(script);
  461. auto convert_serialized_script_sources =
  462. [](std::vector<api::scripts_internal::ScriptSource> sources) {
  463. std::vector<std::string> converted;
  464. converted.reserve(sources.size());
  465. for (auto& source : sources) {
  466. CHECK(source.file)
  467. << "Content scripts don't allow arbtirary code strings";
  468. converted.push_back(std::move(*source.file));
  469. }
  470. return converted;
  471. };
  472. auto convert_execution_world =
  473. [](api::extension_types::ExecutionWorld world) {
  474. switch (world) {
  475. case api::extension_types::ExecutionWorld::kNone:
  476. NOTREACHED_NORETURN()
  477. << "Execution world should always be present in serialization.";
  478. case api::extension_types::ExecutionWorld::kIsolated:
  479. return api::scripting::ExecutionWorld::kIsolated;
  480. case api::extension_types::ExecutionWorld::kUserScript:
  481. NOTREACHED_NORETURN()
  482. << "ISOLATED worlds are not supported in this API.";
  483. case api::extension_types::ExecutionWorld::kMain:
  484. return api::scripting::ExecutionWorld::kMain;
  485. }
  486. };
  487. api::scripting::RegisteredContentScript content_script;
  488. content_script.id = std::move(serialized_script.id);
  489. content_script.matches = std::move(serialized_script.matches);
  490. content_script.exclude_matches = std::move(serialized_script.exclude_matches);
  491. if (serialized_script.css) {
  492. content_script.css =
  493. convert_serialized_script_sources(std::move(*serialized_script.css));
  494. }
  495. if (serialized_script.js) {
  496. content_script.js =
  497. convert_serialized_script_sources(std::move(*serialized_script.js));
  498. }
  499. content_script.all_frames = serialized_script.all_frames;
  500. content_script.match_origin_as_fallback =
  501. serialized_script.match_origin_as_fallback;
  502. content_script.run_at = serialized_script.run_at;
  503. content_script.world = convert_execution_world(serialized_script.world);
  504. return content_script;
  505. }
  506. } // namespace
  507. InjectedFileSource::InjectedFileSource(std::string file_name,
  508. std::unique_ptr<std::string> data)
  509. : file_name(std::move(file_name)), data(std::move(data)) {}
  510. InjectedFileSource::InjectedFileSource(InjectedFileSource&&) = default;
  511. InjectedFileSource::~InjectedFileSource() = default;
  512. ScriptingExecuteScriptFunction::ScriptingExecuteScriptFunction() = default;
  513. ScriptingExecuteScriptFunction::~ScriptingExecuteScriptFunction() = default;
  514. ExtensionFunction::ResponseAction ScriptingExecuteScriptFunction::Run() {
  515. std::optional<api::scripting::ExecuteScript::Params> params =
  516. api::scripting::ExecuteScript::Params::Create(args());
  517. EXTENSION_FUNCTION_VALIDATE(params);
  518. injection_ = std::move(params->injection);
  519. // Silently alias `function` to `func` for backwards compatibility.
  520. // TODO(devlin): Remove this in M95.
  521. if (injection_.function) {
  522. if (injection_.func) {
  523. return RespondNow(
  524. Error("Both 'func' and 'function' were specified. "
  525. "Only 'func' should be used."));
  526. }
  527. injection_.func = std::move(injection_.function);
  528. }
  529. if ((injection_.files && injection_.func) ||
  530. (!injection_.files && !injection_.func)) {
  531. return RespondNow(
  532. Error("Exactly one of 'func' and 'files' must be specified"));
  533. }
  534. if (injection_.files) {
  535. if (injection_.args)
  536. return RespondNow(Error("'args' may not be used with file injections."));
  537. // JS files don't require localization.
  538. constexpr bool kRequiresLocalization = false;
  539. std::string error;
  540. if (!CheckAndLoadFiles(
  541. std::move(*injection_.files), *extension(), kRequiresLocalization,
  542. base::BindOnce(&ScriptingExecuteScriptFunction::DidLoadResources,
  543. this),
  544. &error)) {
  545. return RespondNow(Error(std::move(error)));
  546. }
  547. return RespondLater();
  548. }
  549. DCHECK(injection_.func);
  550. // TODO(devlin): This (wrapping a function to create an IIFE) is pretty hacky,
  551. // and along with the JSON-serialization of the arguments to curry in.
  552. // Add support to the ScriptExecutor to better support this case.
  553. std::string args_expression;
  554. if (injection_.args) {
  555. std::vector<std::string> string_args;
  556. string_args.reserve(injection_.args->size());
  557. for (const auto& arg : *injection_.args) {
  558. if (auto json = base::WriteJson(arg)) {
  559. string_args.push_back(std::move(*json));
  560. } else {
  561. return RespondNow(Error("Unserializable argument passed."));
  562. }
  563. }
  564. args_expression = base::JoinString(string_args, ",");
  565. }
  566. std::string code_to_execute = base::StringPrintf(
  567. "(%s)(%s)", injection_.func->c_str(), args_expression.c_str());
  568. std::vector<mojom::JSSourcePtr> sources;
  569. sources.push_back(mojom::JSSource::New(std::move(code_to_execute), GURL()));
  570. std::string error;
  571. if (!Execute(std::move(sources), &error))
  572. return RespondNow(Error(std::move(error)));
  573. return RespondLater();
  574. }
  575. void ScriptingExecuteScriptFunction::DidLoadResources(
  576. std::vector<InjectedFileSource> file_sources,
  577. std::optional<std::string> load_error) {
  578. if (load_error) {
  579. Respond(Error(std::move(*load_error)));
  580. return;
  581. }
  582. DCHECK(!file_sources.empty());
  583. std::string error;
  584. if (!Execute(FileSourcesToJSSources(*extension(), std::move(file_sources)),
  585. &error)) {
  586. Respond(Error(std::move(error)));
  587. }
  588. }
  589. bool ScriptingExecuteScriptFunction::Execute(
  590. std::vector<mojom::JSSourcePtr> sources,
  591. std::string* error) {
  592. ScriptExecutor* script_executor = nullptr;
  593. ScriptExecutor::FrameScope frame_scope = ScriptExecutor::SPECIFIED_FRAMES;
  594. std::set<int> frame_ids;
  595. if (!CanAccessTarget(*extension()->permissions_data(), injection_.target,
  596. browser_context(), include_incognito_information(),
  597. &script_executor, &frame_scope, &frame_ids, error)) {
  598. return false;
  599. }
  600. mojom::ExecutionWorld execution_world =
  601. ConvertExecutionWorld(injection_.world);
  602. // Extensions can specify that the script should be injected "immediately".
  603. // In this case, we specify kDocumentStart as the injection time. Due to
  604. // inherent raciness between tab creation and load and this function
  605. // execution, there is no guarantee that it will actually happen at
  606. // document start, but the renderer will appropriately inject it
  607. // immediately if document start has already passed.
  608. mojom::RunLocation run_location =
  609. injection_.inject_immediately && *injection_.inject_immediately
  610. ? mojom::RunLocation::kDocumentStart
  611. : mojom::RunLocation::kDocumentIdle;
  612. script_executor->ExecuteScript(
  613. mojom::HostID(mojom::HostID::HostType::kExtensions, extension()->id()),
  614. mojom::CodeInjection::NewJs(mojom::JSInjection::New(
  615. std::move(sources), execution_world, /*world_id=*/std::nullopt,
  616. blink::mojom::WantResultOption::kWantResult,
  617. user_gesture() ? blink::mojom::UserActivationOption::kActivate
  618. : blink::mojom::UserActivationOption::kDoNotActivate,
  619. blink::mojom::PromiseResultOption::kAwait)),
  620. frame_scope, frame_ids, ScriptExecutor::MATCH_ABOUT_BLANK, run_location,
  621. ScriptExecutor::DEFAULT_PROCESS,
  622. /* webview_src */ GURL(),
  623. base::BindOnce(&ScriptingExecuteScriptFunction::OnScriptExecuted, this));
  624. return true;
  625. }
  626. void ScriptingExecuteScriptFunction::OnScriptExecuted(
  627. std::vector<ScriptExecutor::FrameResult> frame_results) {
  628. // If only a single frame was included and the injection failed, respond with
  629. // an error.
  630. if (frame_results.size() == 1 && !frame_results[0].error.empty()) {
  631. Respond(Error(std::move(frame_results[0].error)));
  632. return;
  633. }
  634. // Otherwise, respond successfully. We currently just skip over individual
  635. // frames that failed. In the future, we can bubble up these error messages
  636. // to the extension.
  637. std::vector<api::scripting::InjectionResult> injection_results;
  638. for (auto& result : frame_results) {
  639. if (!result.error.empty())
  640. continue;
  641. api::scripting::InjectionResult injection_result;
  642. injection_result.result = std::move(result.value);
  643. injection_result.frame_id = result.frame_id;
  644. if (result.document_id)
  645. injection_result.document_id = result.document_id.ToString();
  646. // Put the top frame first; otherwise, any order.
  647. if (result.frame_id == ExtensionApiFrameIdMap::kTopFrameId) {
  648. injection_results.insert(injection_results.begin(),
  649. std::move(injection_result));
  650. } else {
  651. injection_results.push_back(std::move(injection_result));
  652. }
  653. }
  654. Respond(ArgumentList(
  655. api::scripting::ExecuteScript::Results::Create(injection_results)));
  656. }
  657. ScriptingInsertCSSFunction::ScriptingInsertCSSFunction() = default;
  658. ScriptingInsertCSSFunction::~ScriptingInsertCSSFunction() = default;
  659. ExtensionFunction::ResponseAction ScriptingInsertCSSFunction::Run() {
  660. std::optional<api::scripting::InsertCSS::Params> params =
  661. api::scripting::InsertCSS::Params::Create(args());
  662. EXTENSION_FUNCTION_VALIDATE(params);
  663. injection_ = std::move(params->injection);
  664. if ((injection_.files && injection_.css) ||
  665. (!injection_.files && !injection_.css)) {
  666. return RespondNow(Error(kExactlyOneOfCssAndFilesError));
  667. }
  668. if (injection_.files) {
  669. // CSS files require localization.
  670. constexpr bool kRequiresLocalization = true;
  671. std::string error;
  672. if (!CheckAndLoadFiles(
  673. std::move(*injection_.files), *extension(), kRequiresLocalization,
  674. base::BindOnce(&ScriptingInsertCSSFunction::DidLoadResources, this),
  675. &error)) {
  676. return RespondNow(Error(std::move(error)));
  677. }
  678. return RespondLater();
  679. }
  680. DCHECK(injection_.css);
  681. mojom::HostID host_id(mojom::HostID::HostType::kExtensions,
  682. extension()->id());
  683. std::vector<mojom::CSSSourcePtr> sources;
  684. sources.push_back(
  685. mojom::CSSSource::New(std::move(*injection_.css),
  686. InjectionKeyForCode(host_id, *injection_.css)));
  687. std::string error;
  688. if (!Execute(std::move(sources), &error)) {
  689. return RespondNow(Error(std::move(error)));
  690. }
  691. return RespondLater();
  692. }
  693. void ScriptingInsertCSSFunction::DidLoadResources(
  694. std::vector<InjectedFileSource> file_sources,
  695. std::optional<std::string> load_error) {
  696. if (load_error) {
  697. Respond(Error(std::move(*load_error)));
  698. return;
  699. }
  700. DCHECK(!file_sources.empty());
  701. std::vector<mojom::CSSSourcePtr> sources =
  702. FileSourcesToCSSSources(*extension(), std::move(file_sources));
  703. std::string error;
  704. if (!Execute(std::move(sources), &error))
  705. Respond(Error(std::move(error)));
  706. }
  707. bool ScriptingInsertCSSFunction::Execute(
  708. std::vector<mojom::CSSSourcePtr> sources,
  709. std::string* error) {
  710. ScriptExecutor* script_executor = nullptr;
  711. ScriptExecutor::FrameScope frame_scope = ScriptExecutor::SPECIFIED_FRAMES;
  712. std::set<int> frame_ids;
  713. if (!CanAccessTarget(*extension()->permissions_data(), injection_.target,
  714. browser_context(), include_incognito_information(),
  715. &script_executor, &frame_scope, &frame_ids, error)) {
  716. return false;
  717. }
  718. DCHECK(script_executor);
  719. script_executor->ExecuteScript(
  720. mojom::HostID(mojom::HostID::HostType::kExtensions, extension()->id()),
  721. mojom::CodeInjection::NewCss(mojom::CSSInjection::New(
  722. std::move(sources), ConvertStyleOriginToCSSOrigin(injection_.origin),
  723. mojom::CSSInjection::Operation::kAdd)),
  724. frame_scope, frame_ids, ScriptExecutor::MATCH_ABOUT_BLANK,
  725. kCSSRunLocation, ScriptExecutor::DEFAULT_PROCESS,
  726. /* webview_src */ GURL(),
  727. base::BindOnce(&ScriptingInsertCSSFunction::OnCSSInserted, this));
  728. return true;
  729. }
  730. void ScriptingInsertCSSFunction::OnCSSInserted(
  731. std::vector<ScriptExecutor::FrameResult> results) {
  732. // If only a single frame was included and the injection failed, respond with
  733. // an error.
  734. if (results.size() == 1 && !results[0].error.empty()) {
  735. Respond(Error(std::move(results[0].error)));
  736. return;
  737. }
  738. Respond(NoArguments());
  739. }
  740. ScriptingRemoveCSSFunction::ScriptingRemoveCSSFunction() = default;
  741. ScriptingRemoveCSSFunction::~ScriptingRemoveCSSFunction() = default;
  742. ExtensionFunction::ResponseAction ScriptingRemoveCSSFunction::Run() {
  743. std::optional<api::scripting::RemoveCSS::Params> params =
  744. api::scripting::RemoveCSS::Params::Create(args());
  745. EXTENSION_FUNCTION_VALIDATE(params);
  746. api::scripting::CSSInjection& injection = params->injection;
  747. if ((injection.files && injection.css) ||
  748. (!injection.files && !injection.css)) {
  749. return RespondNow(Error(kExactlyOneOfCssAndFilesError));
  750. }
  751. ScriptExecutor* script_executor = nullptr;
  752. ScriptExecutor::FrameScope frame_scope = ScriptExecutor::SPECIFIED_FRAMES;
  753. std::set<int> frame_ids;
  754. std::string error;
  755. if (!CanAccessTarget(*extension()->permissions_data(), injection.target,
  756. browser_context(), include_incognito_information(),
  757. &script_executor, &frame_scope, &frame_ids, &error)) {
  758. return RespondNow(Error(std::move(error)));
  759. }
  760. DCHECK(script_executor);
  761. mojom::HostID host_id(mojom::HostID::HostType::kExtensions,
  762. extension()->id());
  763. std::vector<mojom::CSSSourcePtr> sources;
  764. if (injection.files) {
  765. std::vector<ExtensionResource> resources;
  766. if (!GetFileResources(*injection.files, *extension(), &resources, &error))
  767. return RespondNow(Error(std::move(error)));
  768. // Note: Since we're just removing the CSS, we don't actually need to load
  769. // the file here. It's okay for `code` to be empty in this case.
  770. const std::string empty_code;
  771. sources.reserve(injection.files->size());
  772. for (const auto& file : *injection.files) {
  773. sources.push_back(mojom::CSSSource::New(
  774. empty_code,
  775. InjectionKeyForFile(host_id, extension()->GetResourceURL(file))));
  776. }
  777. } else {
  778. DCHECK(injection.css);
  779. sources.push_back(
  780. mojom::CSSSource::New(std::move(*injection.css),
  781. InjectionKeyForCode(host_id, *injection.css)));
  782. }
  783. script_executor->ExecuteScript(
  784. std::move(host_id),
  785. mojom::CodeInjection::NewCss(mojom::CSSInjection::New(
  786. std::move(sources), ConvertStyleOriginToCSSOrigin(injection.origin),
  787. mojom::CSSInjection::Operation::kRemove)),
  788. frame_scope, frame_ids, ScriptExecutor::MATCH_ABOUT_BLANK,
  789. kCSSRunLocation, ScriptExecutor::DEFAULT_PROCESS,
  790. /* webview_src */ GURL(),
  791. base::BindOnce(&ScriptingRemoveCSSFunction::OnCSSRemoved, this));
  792. return RespondLater();
  793. }
  794. void ScriptingRemoveCSSFunction::OnCSSRemoved(
  795. std::vector<ScriptExecutor::FrameResult> results) {
  796. // If only a single frame was included and the injection failed, respond with
  797. // an error.
  798. if (results.size() == 1 && !results[0].error.empty()) {
  799. Respond(Error(std::move(results[0].error)));
  800. return;
  801. }
  802. Respond(NoArguments());
  803. }
  804. ScriptingRegisterContentScriptsFunction::
  805. ScriptingRegisterContentScriptsFunction() = default;
  806. ScriptingRegisterContentScriptsFunction::
  807. ~ScriptingRegisterContentScriptsFunction() = default;
  808. ExtensionFunction::ResponseAction ScriptingUpdateContentScriptsFunction::Run() {
  809. std::optional<api::scripting::UpdateContentScripts::Params> params =
  810. api::scripting::UpdateContentScripts::Params::Create(args());
  811. EXTENSION_FUNCTION_VALIDATE(params);
  812. std::vector<api::scripting::RegisteredContentScript>& scripts =
  813. params->scripts;
  814. std::string error;
  815. // Add the prefix for dynamic content scripts onto the IDs of all scripts in
  816. // `scripts` before continuing.
  817. std::set<std::string> ids_to_update = scripting::CreateDynamicScriptIds(
  818. scripts, UserScript::Source::kDynamicContentScript,
  819. std::set<std::string>(), &error);
  820. if (!error.empty()) {
  821. CHECK(ids_to_update.empty());
  822. return RespondNow(Error(std::move(error)));
  823. }
  824. ExtensionUserScriptLoader* loader =
  825. ExtensionSystem::Get(browser_context())
  826. ->user_script_manager()
  827. ->GetUserScriptLoaderForExtension(extension()->id());
  828. std::map<std::string, api::scripting::RegisteredContentScript>
  829. loaded_scripts_metadata;
  830. const UserScriptList& dynamic_scripts = loader->GetLoadedDynamicScripts();
  831. for (const std::unique_ptr<UserScript>& script : dynamic_scripts) {
  832. if (script->GetSource() == UserScript::Source::kDynamicContentScript) {
  833. loaded_scripts_metadata.emplace(
  834. script->id(), CreateRegisteredContentScriptInfo(*script));
  835. }
  836. }
  837. for (const auto& script : scripts) {
  838. std::string error_script_id = UserScript::TrimPrefixFromScriptID(script.id);
  839. if (loaded_scripts_metadata.find(script.id) ==
  840. loaded_scripts_metadata.end()) {
  841. return RespondNow(
  842. Error(base::StringPrintf("Script with ID '%s' does not exist "
  843. "or is not fully registered",
  844. error_script_id.c_str())));
  845. }
  846. }
  847. std::u16string parse_error;
  848. UserScriptList parsed_scripts;
  849. std::set<std::string> updated_script_ids_to_persist;
  850. std::set<std::string> persistent_script_ids =
  851. loader->GetPersistentDynamicScriptIDs();
  852. parsed_scripts.reserve(scripts.size());
  853. for (size_t i = 0; i < scripts.size(); ++i) {
  854. api::scripting::RegisteredContentScript& update_delta = scripts[i];
  855. DCHECK(base::Contains(loaded_scripts_metadata, update_delta.id));
  856. api::scripting::RegisteredContentScript& updated_script =
  857. loaded_scripts_metadata[update_delta.id];
  858. if (update_delta.matches)
  859. updated_script.matches = std::move(update_delta.matches);
  860. if (update_delta.exclude_matches)
  861. updated_script.exclude_matches = std::move(update_delta.exclude_matches);
  862. if (update_delta.js)
  863. updated_script.js = std::move(update_delta.js);
  864. if (update_delta.css)
  865. updated_script.css = std::move(update_delta.css);
  866. if (update_delta.all_frames)
  867. *updated_script.all_frames = *update_delta.all_frames;
  868. if (update_delta.match_origin_as_fallback) {
  869. *updated_script.match_origin_as_fallback =
  870. *update_delta.match_origin_as_fallback;
  871. }
  872. if (update_delta.run_at != api::extension_types::RunAt::kNone) {
  873. updated_script.run_at = update_delta.run_at;
  874. }
  875. // Parse/Create user script.
  876. std::unique_ptr<UserScript> user_script =
  877. ParseUserScript(browser_context(), *extension(),
  878. std::move(updated_script), &parse_error);
  879. if (!user_script)
  880. return RespondNow(Error(base::UTF16ToASCII(parse_error)));
  881. // Persist the updated script if the flag is specified as true, or if the
  882. // original script is persisted and the flag is not specified.
  883. if ((update_delta.persist_across_sessions &&
  884. *update_delta.persist_across_sessions) ||
  885. (!update_delta.persist_across_sessions &&
  886. base::Contains(persistent_script_ids, update_delta.id))) {
  887. updated_script_ids_to_persist.insert(update_delta.id);
  888. }
  889. parsed_scripts.push_back(std::move(user_script));
  890. }
  891. // Add new script IDs now in case another call with the same script IDs is
  892. // made immediately following this one.
  893. loader->AddPendingDynamicScriptIDs(std::move(ids_to_update));
  894. GetExtensionFileTaskRunner()->PostTaskAndReplyWithResult(
  895. FROM_HERE,
  896. base::BindOnce(&scripting::ValidateParsedScriptsOnFileThread,
  897. script_parsing::GetSymlinkPolicy(extension()),
  898. std::move(parsed_scripts)),
  899. base::BindOnce(
  900. &ScriptingUpdateContentScriptsFunction::OnContentScriptFilesValidated,
  901. this, std::move(updated_script_ids_to_persist)));
  902. // Balanced in `OnContentScriptFilesValidated()` or
  903. // `OnContentScriptsRegistered()`.
  904. AddRef();
  905. return RespondLater();
  906. }
  907. void ScriptingRegisterContentScriptsFunction::OnContentScriptFilesValidated(
  908. std::set<std::string> persistent_script_ids,
  909. scripting::ValidateScriptsResult result) {
  910. // We cannot proceed if the `browser_context` is not valid as the
  911. // `ExtensionSystem` will not exist.
  912. if (!browser_context()) {
  913. Release(); // Matches the `AddRef()` in `Run()`.
  914. return;
  915. }
  916. // We cannot proceed if the extension is uninstalled or unloaded in the middle
  917. // of validating its script files.
  918. ExtensionRegistry* registry = ExtensionRegistry::Get(browser_context());
  919. if (!extension() ||
  920. !registry->enabled_extensions().Contains(extension_id())) {
  921. // Note: a Respond() is not needed if the system is shutting down or if the
  922. // extension is no longer enabled.
  923. Release(); // Matches the `AddRef()` in `Run()`.
  924. return;
  925. }
  926. auto error = std::move(result.second);
  927. auto scripts = std::move(result.first);
  928. ExtensionUserScriptLoader* loader =
  929. ExtensionSystem::Get(browser_context())
  930. ->user_script_manager()
  931. ->GetUserScriptLoaderForExtension(extension()->id());
  932. if (error.has_value()) {
  933. std::set<std::string> ids_to_remove;
  934. for (const auto& script : scripts) {
  935. ids_to_remove.insert(script->id());
  936. }
  937. loader->RemovePendingDynamicScriptIDs(std::move(ids_to_remove));
  938. Respond(Error(std::move(*error)));
  939. Release(); // Matches the `AddRef()` in `Run()`.
  940. return;
  941. }
  942. loader->AddDynamicScripts(
  943. std::move(scripts), std::move(persistent_script_ids),
  944. base::BindOnce(
  945. &ScriptingRegisterContentScriptsFunction::OnContentScriptsRegistered,
  946. this));
  947. }
  948. void ScriptingRegisterContentScriptsFunction::OnContentScriptsRegistered(
  949. const std::optional<std::string>& error) {
  950. if (error.has_value())
  951. Respond(Error(std::move(*error)));
  952. else
  953. Respond(NoArguments());
  954. Release(); // Matches the `AddRef()` in `Run()`.
  955. }
  956. ScriptingGetRegisteredContentScriptsFunction::
  957. ScriptingGetRegisteredContentScriptsFunction() = default;
  958. ScriptingGetRegisteredContentScriptsFunction::
  959. ~ScriptingGetRegisteredContentScriptsFunction() = default;
  960. ExtensionFunction::ResponseAction
  961. ScriptingGetRegisteredContentScriptsFunction::Run() {
  962. std::optional<api::scripting::GetRegisteredContentScripts::Params> params =
  963. api::scripting::GetRegisteredContentScripts::Params::Create(args());
  964. EXTENSION_FUNCTION_VALIDATE(params);
  965. const std::optional<api::scripting::ContentScriptFilter>& filter =
  966. params->filter;
  967. std::set<std::string> id_filter;
  968. if (filter && filter->ids) {
  969. for (const std::string& id : *(filter->ids)) {
  970. id_filter.insert(scripting::AddPrefixToDynamicScriptId(
  971. id, UserScript::Source::kDynamicContentScript));
  972. }
  973. }
  974. ExtensionUserScriptLoader* loader =
  975. ExtensionSystem::Get(browser_context())
  976. ->user_script_manager()
  977. ->GetUserScriptLoaderForExtension(extension()->id());
  978. const UserScriptList& dynamic_scripts = loader->GetLoadedDynamicScripts();
  979. std::vector<api::scripting::RegisteredContentScript> script_infos;
  980. std::set<std::string> persistent_script_ids =
  981. loader->GetPersistentDynamicScriptIDs();
  982. for (const std::unique_ptr<UserScript>& script : dynamic_scripts) {
  983. if (script->GetSource() != UserScript::Source::kDynamicContentScript) {
  984. continue;
  985. }
  986. if (!id_filter.empty() && !base::Contains(id_filter, script->id())) {
  987. continue;
  988. }
  989. auto registered_script = CreateRegisteredContentScriptInfo(*script);
  990. registered_script.persist_across_sessions =
  991. base::Contains(persistent_script_ids, script->id());
  992. // Remove the internally used prefix from the `script`'s ID before
  993. // returning.
  994. registered_script.id = script->GetIDWithoutPrefix();
  995. script_infos.push_back(std::move(registered_script));
  996. }
  997. return RespondNow(
  998. ArgumentList(api::scripting::GetRegisteredContentScripts::Results::Create(
  999. script_infos)));
  1000. }
  1001. ScriptingUnregisterContentScriptsFunction::
  1002. ScriptingUnregisterContentScriptsFunction() = default;
  1003. ScriptingUnregisterContentScriptsFunction::
  1004. ~ScriptingUnregisterContentScriptsFunction() = default;
  1005. ExtensionFunction::ResponseAction
  1006. ScriptingUnregisterContentScriptsFunction::Run() {
  1007. auto params =
  1008. api::scripting::UnregisterContentScripts::Params::Create(args());
  1009. EXTENSION_FUNCTION_VALIDATE(params);
  1010. std::optional<api::scripting::ContentScriptFilter>& filter = params->filter;
  1011. ExtensionUserScriptLoader* loader =
  1012. ExtensionSystem::Get(browser_context())
  1013. ->user_script_manager()
  1014. ->GetUserScriptLoaderForExtension(extension()->id());
  1015. // TODO(crbug.com/1300657): Only clear all scripts if `filter` did not specify
  1016. // the list of scripts ids to remove.
  1017. if (!filter || !filter->ids || filter->ids->empty()) {
  1018. loader->ClearDynamicScripts(
  1019. UserScript::Source::kDynamicContentScript,
  1020. base::BindOnce(&ScriptingUnregisterContentScriptsFunction::
  1021. OnContentScriptsUnregistered,
  1022. this));
  1023. return RespondLater();
  1024. }
  1025. std::set<std::string> ids_to_remove;
  1026. std::set<std::string> existing_script_ids =
  1027. loader->GetDynamicScriptIDs(UserScript::Source::kDynamicContentScript);
  1028. std::string error;
  1029. for (const auto& provided_id : *filter->ids) {
  1030. if (!scripting::IsScriptIdValid(provided_id, &error)) {
  1031. return RespondNow(Error(std::move(error)));
  1032. }
  1033. // Add the dynamic content script prefix to `provided_id` before checking
  1034. // against `existing_script_ids`.
  1035. std::string id_with_prefix = scripting::AddPrefixToDynamicScriptId(
  1036. provided_id, UserScript::Source::kDynamicContentScript);
  1037. if (!base::Contains(existing_script_ids, id_with_prefix)) {
  1038. return RespondNow(Error(ErrorUtils::FormatErrorMessage(
  1039. kNonExistentScriptIdError, provided_id.c_str())));
  1040. }
  1041. ids_to_remove.insert(id_with_prefix);
  1042. }
  1043. loader->RemoveDynamicScripts(
  1044. std::move(ids_to_remove),
  1045. base::BindOnce(&ScriptingUnregisterContentScriptsFunction::
  1046. OnContentScriptsUnregistered,
  1047. this));
  1048. return RespondLater();
  1049. }
  1050. void ScriptingUnregisterContentScriptsFunction::OnContentScriptsUnregistered(
  1051. const std::optional<std::string>& error) {
  1052. if (error.has_value())
  1053. Respond(Error(std::move(*error)));
  1054. else
  1055. Respond(NoArguments());
  1056. }
  1057. ScriptingUpdateContentScriptsFunction::ScriptingUpdateContentScriptsFunction() =
  1058. default;
  1059. ScriptingUpdateContentScriptsFunction::
  1060. ~ScriptingUpdateContentScriptsFunction() = default;
  1061. ExtensionFunction::ResponseAction
  1062. ScriptingRegisterContentScriptsFunction::Run() {
  1063. std::optional<api::scripting::RegisterContentScripts::Params> params =
  1064. api::scripting::RegisterContentScripts::Params::Create(args());
  1065. EXTENSION_FUNCTION_VALIDATE(params);
  1066. std::vector<api::scripting::RegisteredContentScript>& scripts =
  1067. params->scripts;
  1068. ExtensionUserScriptLoader* loader =
  1069. ExtensionSystem::Get(browser_context())
  1070. ->user_script_manager()
  1071. ->GetUserScriptLoaderForExtension(extension()->id());
  1072. // Create script ids for dynamic content scripts.
  1073. std::string error;
  1074. std::set<std::string> existing_script_ids =
  1075. loader->GetDynamicScriptIDs(UserScript::Source::kDynamicContentScript);
  1076. std::set<std::string> new_script_ids = scripting::CreateDynamicScriptIds(
  1077. scripts, UserScript::Source::kDynamicContentScript, existing_script_ids,
  1078. &error);
  1079. if (!error.empty()) {
  1080. CHECK(new_script_ids.empty());
  1081. return RespondNow(Error(std::move(error)));
  1082. }
  1083. // Parse content scripts.
  1084. std::u16string parse_error;
  1085. UserScriptList parsed_scripts;
  1086. std::set<std::string> persistent_script_ids;
  1087. parsed_scripts.reserve(scripts.size());
  1088. for (auto& script : scripts) {
  1089. if (!script.matches) {
  1090. std::string error_script_id =
  1091. UserScript::TrimPrefixFromScriptID(script.id);
  1092. return RespondNow(
  1093. Error(base::StringPrintf("Script with ID '%s' must specify 'matches'",
  1094. error_script_id.c_str())));
  1095. }
  1096. // Scripts will persist across sessions by default.
  1097. bool persist_across_sessions =
  1098. script.persist_across_sessions.value_or(true);
  1099. std::unique_ptr<UserScript> user_script = ParseUserScript(
  1100. browser_context(), *extension(), std::move(script), &parse_error);
  1101. if (!user_script)
  1102. return RespondNow(Error(base::UTF16ToASCII(parse_error)));
  1103. // Scripts will persist across sessions by default.
  1104. if (persist_across_sessions) {
  1105. persistent_script_ids.insert(user_script->id());
  1106. }
  1107. parsed_scripts.push_back(std::move(user_script));
  1108. }
  1109. // The contents of `scripts` have all been std::move()'d.
  1110. scripts.clear();
  1111. // Add new script IDs now in case another call with the same script IDs is
  1112. // made immediately following this one.
  1113. loader->AddPendingDynamicScriptIDs(std::move(new_script_ids));
  1114. GetExtensionFileTaskRunner()->PostTaskAndReplyWithResult(
  1115. FROM_HERE,
  1116. base::BindOnce(&scripting::ValidateParsedScriptsOnFileThread,
  1117. script_parsing::GetSymlinkPolicy(extension()),
  1118. std::move(parsed_scripts)),
  1119. base::BindOnce(&ScriptingRegisterContentScriptsFunction::
  1120. OnContentScriptFilesValidated,
  1121. this, std::move(persistent_script_ids)));
  1122. // Balanced in `OnContentScriptFilesValidated()` or
  1123. // `OnContentScriptsRegistered()`.
  1124. AddRef();
  1125. return RespondLater();
  1126. }
  1127. void ScriptingUpdateContentScriptsFunction::OnContentScriptFilesValidated(
  1128. std::set<std::string> persistent_script_ids,
  1129. scripting::ValidateScriptsResult result) {
  1130. // We cannot proceed if the `browser_context` is not valid as the
  1131. // `ExtensionSystem` will not exist.
  1132. if (!browser_context()) {
  1133. Release(); // Matches the `AddRef()` in `Run()`.
  1134. return;
  1135. }
  1136. // We cannot proceed if the extension is uninstalled or unloaded in the middle
  1137. // of validating its script files.
  1138. ExtensionRegistry* registry = ExtensionRegistry::Get(browser_context());
  1139. if (!extension() ||
  1140. !registry->enabled_extensions().Contains(extension_id())) {
  1141. // Note: a Respond() is not needed if the system is shutting down or if the
  1142. // extension is no longer enabled.
  1143. Release(); // Matches the `AddRef()` in `Run()`.
  1144. return;
  1145. }
  1146. auto error = std::move(result.second);
  1147. auto scripts = std::move(result.first);
  1148. ExtensionUserScriptLoader* loader =
  1149. ExtensionSystem::Get(browser_context())
  1150. ->user_script_manager()
  1151. ->GetUserScriptLoaderForExtension(extension()->id());
  1152. std::set<std::string> script_ids;
  1153. for (const auto& script : scripts)
  1154. script_ids.insert(script->id());
  1155. if (error.has_value()) {
  1156. loader->RemovePendingDynamicScriptIDs(script_ids);
  1157. Respond(Error(std::move(*error)));
  1158. Release(); // Matches the `AddRef()` in `Run()`.
  1159. return;
  1160. }
  1161. // To guarantee that scripts are updated, they need to be removed then added
  1162. // again. It should be guaranteed that the new scripts are added after the old
  1163. // ones are removed.
  1164. loader->RemoveDynamicScripts(script_ids, /*callback=*/base::DoNothing());
  1165. // Since RemoveDynamicScripts will remove pending script IDs, but
  1166. // AddDynamicScripts will only add scripts that are marked as pending, we must
  1167. // mark `script_ids` as pending again here.
  1168. loader->AddPendingDynamicScriptIDs(std::move(script_ids));
  1169. loader->AddDynamicScripts(
  1170. std::move(scripts), std::move(persistent_script_ids),
  1171. base::BindOnce(
  1172. &ScriptingUpdateContentScriptsFunction::OnContentScriptsUpdated,
  1173. this));
  1174. }
  1175. void ScriptingUpdateContentScriptsFunction::OnContentScriptsUpdated(
  1176. const std::optional<std::string>& error) {
  1177. if (error.has_value())
  1178. Respond(Error(std::move(*error)));
  1179. else
  1180. Respond(NoArguments());
  1181. Release(); // Matches the `AddRef()` in `Run()`.
  1182. }
  1183. } // namespace extensions