content-scripts-injector.ts 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. import { webFrame } from 'electron'
  2. import * as ipcRendererUtils from '@electron/internal/renderer/ipc-renderer-internal-utils'
  3. const v8Util = process.electronBinding('v8_util')
  4. const IsolatedWorldIDs = {
  5. /**
  6. * Start of extension isolated world IDs, as defined in
  7. * atom_render_frame_observer.h
  8. */
  9. ISOLATED_WORLD_EXTENSIONS: 1 << 20
  10. }
  11. let isolatedWorldIds = IsolatedWorldIDs.ISOLATED_WORLD_EXTENSIONS
  12. const extensionWorldId: {[key: string]: number | undefined} = {}
  13. // https://cs.chromium.org/chromium/src/extensions/renderer/script_injection.cc?type=cs&sq=package:chromium&g=0&l=52
  14. const getIsolatedWorldIdForInstance = () => {
  15. // TODO(samuelmaddock): allocate and cleanup IDs
  16. return isolatedWorldIds++
  17. }
  18. const escapePattern = function (pattern: string) {
  19. return pattern.replace(/[\\^$+?.()|[\]{}]/g, '\\$&')
  20. }
  21. // Check whether pattern matches.
  22. // https://developer.chrome.com/extensions/match_patterns
  23. const matchesPattern = function (pattern: string) {
  24. if (pattern === '<all_urls>') return true
  25. const regexp = new RegExp(`^${pattern.split('*').map(escapePattern).join('.*')}$`)
  26. const url = `${location.protocol}//${location.host}${location.pathname}`
  27. return url.match(regexp)
  28. }
  29. // Run the code with chrome API integrated.
  30. const runContentScript = function (this: any, extensionId: string, url: string, code: string) {
  31. // Assign unique world ID to each extension
  32. const worldId = extensionWorldId[extensionId] ||
  33. (extensionWorldId[extensionId] = getIsolatedWorldIdForInstance())
  34. // store extension ID for content script to read in isolated world
  35. v8Util.setHiddenValue(global, `extension-${worldId}`, extensionId)
  36. webFrame.setIsolatedWorldInfo(worldId, {
  37. name: `${extensionId} [${worldId}]`
  38. // TODO(samuelmaddock): read `content_security_policy` from extension manifest
  39. // csp: manifest.content_security_policy,
  40. })
  41. const sources = [{ code, url }]
  42. return webFrame.executeJavaScriptInIsolatedWorld(worldId, sources)
  43. }
  44. const runAllContentScript = function (scripts: Array<Electron.InjectionBase>, extensionId: string) {
  45. for (const { url, code } of scripts) {
  46. runContentScript.call(window, extensionId, url, code)
  47. }
  48. }
  49. const runStylesheet = function (this: any, url: string, code: string) {
  50. webFrame.insertCSS(code)
  51. }
  52. const runAllStylesheet = function (css: Array<Electron.InjectionBase>) {
  53. for (const { url, code } of css) {
  54. runStylesheet.call(window, url, code)
  55. }
  56. }
  57. // Run injected scripts.
  58. // https://developer.chrome.com/extensions/content_scripts
  59. const injectContentScript = function (extensionId: string, script: Electron.ContentScript) {
  60. if (!process.isMainFrame && !script.allFrames) return
  61. if (!script.matches.some(matchesPattern)) return
  62. if (script.js) {
  63. const fire = runAllContentScript.bind(window, script.js, extensionId)
  64. if (script.runAt === 'document_start') {
  65. process.once('document-start', fire)
  66. } else if (script.runAt === 'document_end') {
  67. process.once('document-end', fire)
  68. } else {
  69. document.addEventListener('DOMContentLoaded', fire)
  70. }
  71. }
  72. if (script.css) {
  73. const fire = runAllStylesheet.bind(window, script.css)
  74. if (script.runAt === 'document_start') {
  75. process.once('document-start', fire)
  76. } else if (script.runAt === 'document_end') {
  77. process.once('document-end', fire)
  78. } else {
  79. document.addEventListener('DOMContentLoaded', fire)
  80. }
  81. }
  82. }
  83. // Handle the request of chrome.tabs.executeJavaScript.
  84. ipcRendererUtils.handle('CHROME_TABS_EXECUTE_SCRIPT', function (
  85. event: Electron.Event,
  86. extensionId: string,
  87. url: string,
  88. code: string
  89. ) {
  90. return runContentScript.call(window, extensionId, url, code)
  91. })
  92. module.exports = (getRenderProcessPreferences: typeof process.getRenderProcessPreferences) => {
  93. // Read the renderer process preferences.
  94. const preferences = getRenderProcessPreferences()
  95. if (preferences) {
  96. for (const pref of preferences) {
  97. if (pref.contentScripts) {
  98. for (const script of pref.contentScripts) {
  99. injectContentScript(pref.extensionId, script)
  100. }
  101. }
  102. }
  103. }
  104. }