scripting_api.cc 50 KB

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