devtools.js 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. 'use strict'
  2. const { dialog, Menu } = require('electron')
  3. const fs = require('fs')
  4. const url = require('url')
  5. const util = require('util')
  6. const ipcMainUtils = require('@electron/internal/browser/ipc-main-internal-utils')
  7. const readFile = util.promisify(fs.readFile)
  8. const convertToMenuTemplate = function (items, handler) {
  9. return items.map(function (item) {
  10. const transformed = item.type === 'subMenu' ? {
  11. type: 'submenu',
  12. label: item.label,
  13. enabled: item.enabled,
  14. submenu: convertToMenuTemplate(item.subItems, handler)
  15. } : item.type === 'separator' ? {
  16. type: 'separator'
  17. } : item.type === 'checkbox' ? {
  18. type: 'checkbox',
  19. label: item.label,
  20. enabled: item.enabled,
  21. checked: item.checked
  22. } : {
  23. type: 'normal',
  24. label: item.label,
  25. enabled: item.enabled
  26. }
  27. if (item.id != null) {
  28. transformed.click = () => handler(item.id)
  29. }
  30. return transformed
  31. })
  32. }
  33. const getEditMenuItems = function () {
  34. return [
  35. { role: 'undo' },
  36. { role: 'redo' },
  37. { type: 'separator' },
  38. { role: 'cut' },
  39. { role: 'copy' },
  40. { role: 'paste' },
  41. { role: 'pasteAndMatchStyle' },
  42. { role: 'delete' },
  43. { role: 'selectAll' }
  44. ]
  45. }
  46. const isChromeDevTools = function (pageURL) {
  47. const { protocol } = url.parse(pageURL)
  48. return protocol === 'devtools:'
  49. }
  50. const assertChromeDevTools = function (contents, api) {
  51. const pageURL = contents._getURL()
  52. if (!isChromeDevTools(pageURL)) {
  53. console.error(`Blocked ${pageURL} from calling ${api}`)
  54. throw new Error(`Blocked ${api}`)
  55. }
  56. }
  57. ipcMainUtils.handle('ELECTRON_INSPECTOR_CONTEXT_MENU', function (event, items, isEditMenu) {
  58. return new Promise(resolve => {
  59. assertChromeDevTools(event.sender, 'window.InspectorFrontendHost.showContextMenuAtPoint()')
  60. const template = isEditMenu ? getEditMenuItems() : convertToMenuTemplate(items, resolve)
  61. const menu = Menu.buildFromTemplate(template)
  62. const window = event.sender.getOwnerBrowserWindow()
  63. menu.once('menu-will-close', () => {
  64. // menu-will-close is emitted before the click handler, but needs to be sent after.
  65. // otherwise, DevToolsAPI.contextMenuCleared() would be called before
  66. // DevToolsAPI.contextMenuItemSelected(id) and the menu will not work properly.
  67. setTimeout(() => resolve())
  68. })
  69. menu.popup({ window })
  70. })
  71. })
  72. ipcMainUtils.handle('ELECTRON_INSPECTOR_SELECT_FILE', async function (event) {
  73. assertChromeDevTools(event.sender, 'window.UI.createFileSelectorElement()')
  74. const result = await dialog.showOpenDialog({})
  75. if (result.canceled) return []
  76. const path = result.filePaths[0]
  77. const data = await readFile(path)
  78. return [path, data]
  79. })
  80. ipcMainUtils.handle('ELECTRON_INSPECTOR_CONFIRM', async function (event, message = '', title = '') {
  81. assertChromeDevTools(event.sender, 'window.confirm()')
  82. const options = {
  83. message: String(message),
  84. title: String(title),
  85. buttons: ['OK', 'Cancel'],
  86. cancelId: 1
  87. }
  88. const window = event.sender.getOwnerBrowserWindow()
  89. const { response } = await dialog.showMessageBox(window, options)
  90. return response === 0
  91. })