scripting_api.cc 50 KB

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