web-contents.ts 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692
  1. import { app, ipcMain, session, deprecate } from 'electron/main';
  2. import type { MenuItem, MenuItemConstructorOptions } from 'electron/main';
  3. import * as url from 'url';
  4. import * as path from 'path';
  5. import { internalWindowOpen } from '@electron/internal/browser/guest-window-manager';
  6. import { NavigationController } from '@electron/internal/browser/navigation-controller';
  7. import { ipcMainInternal } from '@electron/internal/browser/ipc-main-internal';
  8. import * as ipcMainUtils from '@electron/internal/browser/ipc-main-internal-utils';
  9. import { parseFeatures } from '@electron/internal/common/parse-features-string';
  10. import { MessagePortMain } from '@electron/internal/browser/message-port-main';
  11. // session is not used here, the purpose is to make sure session is initalized
  12. // before the webContents module.
  13. // eslint-disable-next-line
  14. session
  15. let nextId = 0;
  16. const getNextId = function () {
  17. return ++nextId;
  18. };
  19. /* eslint-disable camelcase */
  20. type MediaSize = {
  21. name: string,
  22. custom_display_name: string,
  23. height_microns: number,
  24. width_microns: number,
  25. is_default?: 'true',
  26. }
  27. /* eslint-enable camelcase */
  28. // Stock page sizes
  29. const PDFPageSizes: Record<string, MediaSize> = {
  30. A5: {
  31. custom_display_name: 'A5',
  32. height_microns: 210000,
  33. name: 'ISO_A5',
  34. width_microns: 148000
  35. },
  36. A4: {
  37. custom_display_name: 'A4',
  38. height_microns: 297000,
  39. name: 'ISO_A4',
  40. is_default: 'true',
  41. width_microns: 210000
  42. },
  43. A3: {
  44. custom_display_name: 'A3',
  45. height_microns: 420000,
  46. name: 'ISO_A3',
  47. width_microns: 297000
  48. },
  49. Legal: {
  50. custom_display_name: 'Legal',
  51. height_microns: 355600,
  52. name: 'NA_LEGAL',
  53. width_microns: 215900
  54. },
  55. Letter: {
  56. custom_display_name: 'Letter',
  57. height_microns: 279400,
  58. name: 'NA_LETTER',
  59. width_microns: 215900
  60. },
  61. Tabloid: {
  62. height_microns: 431800,
  63. name: 'NA_LEDGER',
  64. width_microns: 279400,
  65. custom_display_name: 'Tabloid'
  66. }
  67. };
  68. // The minimum micron size Chromium accepts is that where:
  69. // Per printing/units.h:
  70. // * kMicronsPerInch - Length of an inch in 0.001mm unit.
  71. // * kPointsPerInch - Length of an inch in CSS's 1pt unit.
  72. //
  73. // Formula: (kPointsPerInch / kMicronsPerInch) * size >= 1
  74. //
  75. // Practically, this means microns need to be > 352 microns.
  76. // We therefore need to verify this or it will silently fail.
  77. const isValidCustomPageSize = (width: number, height: number) => {
  78. return [width, height].every(x => x > 352);
  79. };
  80. // Default printing setting
  81. const defaultPrintingSetting = {
  82. // Customizable.
  83. pageRange: [] as {from: number, to: number}[],
  84. mediaSize: {} as MediaSize,
  85. landscape: false,
  86. headerFooterEnabled: false,
  87. marginsType: 0,
  88. scaleFactor: 100,
  89. shouldPrintBackgrounds: false,
  90. shouldPrintSelectionOnly: false,
  91. // Non-customizable.
  92. printWithCloudPrint: false,
  93. printWithPrivet: false,
  94. printWithExtension: false,
  95. pagesPerSheet: 1,
  96. isFirstRequest: false,
  97. previewUIID: 0,
  98. previewModifiable: true,
  99. printToPDF: true,
  100. deviceName: 'Save as PDF',
  101. generateDraftData: true,
  102. dpiHorizontal: 72,
  103. dpiVertical: 72,
  104. rasterizePDF: false,
  105. duplex: 0,
  106. copies: 1,
  107. // 2 = color - see ColorModel in //printing/print_job_constants.h
  108. color: 2,
  109. collate: true,
  110. printerType: 2,
  111. title: undefined as string | undefined,
  112. url: undefined as string | undefined
  113. };
  114. // JavaScript implementations of WebContents.
  115. const binding = process._linkedBinding('electron_browser_web_contents');
  116. const { WebContents } = binding as { WebContents: { prototype: Electron.WebContentsInternal } };
  117. WebContents.prototype.send = function (channel, ...args) {
  118. if (typeof channel !== 'string') {
  119. throw new Error('Missing required channel argument');
  120. }
  121. const internal = false;
  122. const sendToAll = false;
  123. return this._send(internal, sendToAll, channel, args);
  124. };
  125. WebContents.prototype.postMessage = function (...args) {
  126. if (Array.isArray(args[2])) {
  127. args[2] = args[2].map(o => o instanceof MessagePortMain ? o._internalPort : o);
  128. }
  129. this._postMessage(...args);
  130. };
  131. WebContents.prototype._sendInternal = function (channel, ...args) {
  132. if (typeof channel !== 'string') {
  133. throw new Error('Missing required channel argument');
  134. }
  135. const internal = true;
  136. const sendToAll = false;
  137. return this._send(internal, sendToAll, channel, args);
  138. };
  139. WebContents.prototype._sendInternalToAll = function (channel, ...args) {
  140. if (typeof channel !== 'string') {
  141. throw new Error('Missing required channel argument');
  142. }
  143. const internal = true;
  144. const sendToAll = true;
  145. return this._send(internal, sendToAll, channel, args);
  146. };
  147. WebContents.prototype.sendToFrame = function (frame, channel, ...args) {
  148. if (typeof channel !== 'string') {
  149. throw new Error('Missing required channel argument');
  150. } else if (!(typeof frame === 'number' || Array.isArray(frame))) {
  151. throw new Error('Missing required frame argument (must be number or array)');
  152. }
  153. const internal = false;
  154. const sendToAll = false;
  155. return this._sendToFrame(internal, sendToAll, frame, channel, args);
  156. };
  157. WebContents.prototype._sendToFrameInternal = function (frame, channel, ...args) {
  158. if (typeof channel !== 'string') {
  159. throw new Error('Missing required channel argument');
  160. } else if (!(typeof frame === 'number' || Array.isArray(frame))) {
  161. throw new Error('Missing required frame argument (must be number or array)');
  162. }
  163. const internal = true;
  164. const sendToAll = false;
  165. return this._sendToFrame(internal, sendToAll, frame, channel, args);
  166. };
  167. // Following methods are mapped to webFrame.
  168. const webFrameMethods = [
  169. 'insertCSS',
  170. 'insertText',
  171. 'removeInsertedCSS',
  172. 'setVisualZoomLevelLimits'
  173. ] as ('insertCSS' | 'insertText' | 'removeInsertedCSS' | 'setVisualZoomLevelLimits')[];
  174. for (const method of webFrameMethods) {
  175. WebContents.prototype[method] = function (...args: any[]): Promise<any> {
  176. return ipcMainUtils.invokeInWebContents(this, false, 'ELECTRON_INTERNAL_RENDERER_WEB_FRAME_METHOD', method, ...args);
  177. };
  178. }
  179. const waitTillCanExecuteJavaScript = async (webContents: Electron.WebContentsInternal) => {
  180. if (webContents.getURL() && !webContents.isLoadingMainFrame()) return;
  181. return new Promise((resolve) => {
  182. webContents.once('did-stop-loading', () => {
  183. resolve();
  184. });
  185. });
  186. };
  187. // Make sure WebContents::executeJavaScript would run the code only when the
  188. // WebContents has been loaded.
  189. WebContents.prototype.executeJavaScript = async function (code, hasUserGesture) {
  190. await waitTillCanExecuteJavaScript(this);
  191. return ipcMainUtils.invokeInWebContents(this, false, 'ELECTRON_INTERNAL_RENDERER_WEB_FRAME_METHOD', 'executeJavaScript', String(code), !!hasUserGesture);
  192. };
  193. WebContents.prototype.executeJavaScriptInIsolatedWorld = async function (worldId, code, hasUserGesture) {
  194. await waitTillCanExecuteJavaScript(this);
  195. return ipcMainUtils.invokeInWebContents(this, false, 'ELECTRON_INTERNAL_RENDERER_WEB_FRAME_METHOD', 'executeJavaScriptInIsolatedWorld', worldId, code, !!hasUserGesture);
  196. };
  197. // Translate the options of printToPDF.
  198. let pendingPromise: Promise<any> | undefined;
  199. WebContents.prototype.printToPDF = async function (options) {
  200. const printSettings = {
  201. ...defaultPrintingSetting,
  202. requestID: getNextId()
  203. };
  204. if (options.landscape !== undefined) {
  205. if (typeof options.landscape !== 'boolean') {
  206. const error = new Error('landscape must be a Boolean');
  207. return Promise.reject(error);
  208. }
  209. printSettings.landscape = options.landscape;
  210. }
  211. if (options.scaleFactor !== undefined) {
  212. if (typeof options.scaleFactor !== 'number') {
  213. const error = new Error('scaleFactor must be a Number');
  214. return Promise.reject(error);
  215. }
  216. printSettings.scaleFactor = options.scaleFactor;
  217. }
  218. if (options.marginsType !== undefined) {
  219. if (typeof options.marginsType !== 'number') {
  220. const error = new Error('marginsType must be a Number');
  221. return Promise.reject(error);
  222. }
  223. printSettings.marginsType = options.marginsType;
  224. }
  225. if (options.printSelectionOnly !== undefined) {
  226. if (typeof options.printSelectionOnly !== 'boolean') {
  227. const error = new Error('printSelectionOnly must be a Boolean');
  228. return Promise.reject(error);
  229. }
  230. printSettings.shouldPrintSelectionOnly = options.printSelectionOnly;
  231. }
  232. if (options.printBackground !== undefined) {
  233. if (typeof options.printBackground !== 'boolean') {
  234. const error = new Error('printBackground must be a Boolean');
  235. return Promise.reject(error);
  236. }
  237. printSettings.shouldPrintBackgrounds = options.printBackground;
  238. }
  239. if (options.pageRanges !== undefined) {
  240. const pageRanges = options.pageRanges;
  241. if (!Object.prototype.hasOwnProperty.call(pageRanges, 'from') || !Object.prototype.hasOwnProperty.call(pageRanges, 'to')) {
  242. const error = new Error('pageRanges must be an Object with \'from\' and \'to\' properties');
  243. return Promise.reject(error);
  244. }
  245. if (typeof pageRanges.from !== 'number') {
  246. const error = new Error('pageRanges.from must be a Number');
  247. return Promise.reject(error);
  248. }
  249. if (typeof pageRanges.to !== 'number') {
  250. const error = new Error('pageRanges.to must be a Number');
  251. return Promise.reject(error);
  252. }
  253. // Chromium uses 1-based page ranges, so increment each by 1.
  254. printSettings.pageRange = [{
  255. from: pageRanges.from + 1,
  256. to: pageRanges.to + 1
  257. }];
  258. }
  259. if (options.headerFooter !== undefined) {
  260. const headerFooter = options.headerFooter;
  261. printSettings.headerFooterEnabled = true;
  262. if (typeof headerFooter === 'object') {
  263. if (!headerFooter.url || !headerFooter.title) {
  264. const error = new Error('url and title properties are required for headerFooter');
  265. return Promise.reject(error);
  266. }
  267. if (typeof headerFooter.title !== 'string') {
  268. const error = new Error('headerFooter.title must be a String');
  269. return Promise.reject(error);
  270. }
  271. printSettings.title = headerFooter.title;
  272. if (typeof headerFooter.url !== 'string') {
  273. const error = new Error('headerFooter.url must be a String');
  274. return Promise.reject(error);
  275. }
  276. printSettings.url = headerFooter.url;
  277. } else {
  278. const error = new Error('headerFooter must be an Object');
  279. return Promise.reject(error);
  280. }
  281. }
  282. // Optionally set size for PDF.
  283. if (options.pageSize !== undefined) {
  284. const pageSize = options.pageSize;
  285. if (typeof pageSize === 'object') {
  286. if (!pageSize.height || !pageSize.width) {
  287. const error = new Error('height and width properties are required for pageSize');
  288. return Promise.reject(error);
  289. }
  290. // Dimensions in Microns - 1 meter = 10^6 microns
  291. const height = Math.ceil(pageSize.height);
  292. const width = Math.ceil(pageSize.width);
  293. if (!isValidCustomPageSize(width, height)) {
  294. const error = new Error('height and width properties must be minimum 352 microns.');
  295. return Promise.reject(error);
  296. }
  297. printSettings.mediaSize = {
  298. name: 'CUSTOM',
  299. custom_display_name: 'Custom',
  300. height_microns: height,
  301. width_microns: width
  302. };
  303. } else if (Object.prototype.hasOwnProperty.call(PDFPageSizes, pageSize)) {
  304. printSettings.mediaSize = PDFPageSizes[pageSize];
  305. } else {
  306. const error = new Error(`Unsupported pageSize: ${pageSize}`);
  307. return Promise.reject(error);
  308. }
  309. } else {
  310. printSettings.mediaSize = PDFPageSizes.A4;
  311. }
  312. // Chromium expects this in a 0-100 range number, not as float
  313. printSettings.scaleFactor = Math.ceil(printSettings.scaleFactor) % 100;
  314. // PrinterType enum from //printing/print_job_constants.h
  315. printSettings.printerType = 2;
  316. if (this._printToPDF) {
  317. if (pendingPromise) {
  318. pendingPromise = pendingPromise.then(() => this._printToPDF(printSettings));
  319. } else {
  320. pendingPromise = this._printToPDF(printSettings);
  321. }
  322. return pendingPromise;
  323. } else {
  324. const error = new Error('Printing feature is disabled');
  325. return Promise.reject(error);
  326. }
  327. };
  328. WebContents.prototype.print = function (options = {}, callback) {
  329. // TODO(codebytere): deduplicate argument sanitization by moving rest of
  330. // print param logic into new file shared between printToPDF and print
  331. if (typeof options === 'object') {
  332. // Optionally set size for PDF.
  333. if (options.pageSize !== undefined) {
  334. const pageSize = options.pageSize;
  335. if (typeof pageSize === 'object') {
  336. if (!pageSize.height || !pageSize.width) {
  337. throw new Error('height and width properties are required for pageSize');
  338. }
  339. // Dimensions in Microns - 1 meter = 10^6 microns
  340. const height = Math.ceil(pageSize.height);
  341. const width = Math.ceil(pageSize.width);
  342. if (!isValidCustomPageSize(width, height)) {
  343. throw new Error('height and width properties must be minimum 352 microns.');
  344. }
  345. (options as any).mediaSize = {
  346. name: 'CUSTOM',
  347. custom_display_name: 'Custom',
  348. height_microns: height,
  349. width_microns: width
  350. };
  351. } else if (PDFPageSizes[pageSize]) {
  352. (options as any).mediaSize = PDFPageSizes[pageSize];
  353. } else {
  354. throw new Error(`Unsupported pageSize: ${pageSize}`);
  355. }
  356. }
  357. }
  358. if (this._print) {
  359. if (callback) {
  360. this._print(options, callback);
  361. } else {
  362. this._print(options);
  363. }
  364. } else {
  365. console.error('Error: Printing feature is disabled.');
  366. }
  367. };
  368. WebContents.prototype.getPrinters = function () {
  369. if (this._getPrinters) {
  370. return this._getPrinters();
  371. } else {
  372. console.error('Error: Printing feature is disabled.');
  373. return [];
  374. }
  375. };
  376. WebContents.prototype.loadFile = function (filePath, options = {}) {
  377. if (typeof filePath !== 'string') {
  378. throw new Error('Must pass filePath as a string');
  379. }
  380. const { query, search, hash } = options;
  381. return this.loadURL(url.format({
  382. protocol: 'file',
  383. slashes: true,
  384. pathname: path.resolve(app.getAppPath(), filePath),
  385. query,
  386. search,
  387. hash
  388. }));
  389. };
  390. const addReplyToEvent = (event: any) => {
  391. const { processId, frameId } = event;
  392. event.reply = (...args: any[]) => {
  393. event.sender.sendToFrame([processId, frameId], ...args);
  394. };
  395. };
  396. const addReplyInternalToEvent = (event: any) => {
  397. Object.defineProperty(event, '_replyInternal', {
  398. configurable: false,
  399. enumerable: false,
  400. value: (...args: any[]) => {
  401. event.sender._sendToFrameInternal(event.frameId, ...args);
  402. }
  403. });
  404. };
  405. const addReturnValueToEvent = (event: any) => {
  406. Object.defineProperty(event, 'returnValue', {
  407. set: (value) => event.sendReply([value]),
  408. get: () => {}
  409. });
  410. };
  411. const loggingEnabled = () => {
  412. return process.env.ELECTRON_ENABLE_LOGGING || app.commandLine.hasSwitch('enable-logging');
  413. };
  414. // Add JavaScript wrappers for WebContents class.
  415. WebContents.prototype._init = function () {
  416. // The navigation controller.
  417. const navigationController = new NavigationController(this);
  418. this.loadURL = navigationController.loadURL.bind(navigationController);
  419. this.getURL = navigationController.getURL.bind(navigationController);
  420. this.stop = navigationController.stop.bind(navigationController);
  421. this.reload = navigationController.reload.bind(navigationController);
  422. this.reloadIgnoringCache = navigationController.reloadIgnoringCache.bind(navigationController);
  423. this.canGoBack = navigationController.canGoBack.bind(navigationController);
  424. this.canGoForward = navigationController.canGoForward.bind(navigationController);
  425. this.canGoToIndex = navigationController.canGoToIndex.bind(navigationController);
  426. this.canGoToOffset = navigationController.canGoToOffset.bind(navigationController);
  427. this.clearHistory = navigationController.clearHistory.bind(navigationController);
  428. this.goBack = navigationController.goBack.bind(navigationController);
  429. this.goForward = navigationController.goForward.bind(navigationController);
  430. this.goToIndex = navigationController.goToIndex.bind(navigationController);
  431. this.goToOffset = navigationController.goToOffset.bind(navigationController);
  432. this.getActiveIndex = navigationController.getActiveIndex.bind(navigationController);
  433. this.length = navigationController.length.bind(navigationController);
  434. // Read off the ID at construction time, so that it's accessible even after
  435. // the underlying C++ WebContents is destroyed.
  436. const id = this.id;
  437. Object.defineProperty(this, 'id', {
  438. value: id,
  439. writable: false
  440. });
  441. // Every remote callback from renderer process would add a listener to the
  442. // render-view-deleted event, so ignore the listeners warning.
  443. this.setMaxListeners(0);
  444. // Dispatch IPC messages to the ipc module.
  445. this.on('-ipc-message' as any, function (this: Electron.WebContentsInternal, event: any, internal: boolean, channel: string, args: any[]) {
  446. if (internal) {
  447. addReplyInternalToEvent(event);
  448. ipcMainInternal.emit(channel, event, ...args);
  449. } else {
  450. addReplyToEvent(event);
  451. this.emit('ipc-message', event, channel, ...args);
  452. ipcMain.emit(channel, event, ...args);
  453. }
  454. });
  455. this.on('-ipc-invoke' as any, function (event: any, internal: boolean, channel: string, args: any[]) {
  456. event._reply = (result: any) => event.sendReply({ result });
  457. event._throw = (error: Error) => {
  458. console.error(`Error occurred in handler for '${channel}':`, error);
  459. event.sendReply({ error: error.toString() });
  460. };
  461. const target = internal ? ipcMainInternal : ipcMain;
  462. if ((target as any)._invokeHandlers.has(channel)) {
  463. (target as any)._invokeHandlers.get(channel)(event, ...args);
  464. } else {
  465. event._throw(`No handler registered for '${channel}'`);
  466. }
  467. });
  468. this.on('-ipc-message-sync' as any, function (this: Electron.WebContentsInternal, event: any, internal: boolean, channel: string, args: any[]) {
  469. addReturnValueToEvent(event);
  470. if (internal) {
  471. addReplyInternalToEvent(event);
  472. ipcMainInternal.emit(channel, event, ...args);
  473. } else {
  474. addReplyToEvent(event);
  475. this.emit('ipc-message-sync', event, channel, ...args);
  476. ipcMain.emit(channel, event, ...args);
  477. }
  478. });
  479. this.on('-ipc-ports' as any, function (event: any, internal: boolean, channel: string, message: any, ports: any[]) {
  480. event.ports = ports.map(p => new MessagePortMain(p));
  481. ipcMain.emit(channel, event, message);
  482. });
  483. // Handle context menu action request from pepper plugin.
  484. this.on('pepper-context-menu' as any, function (event: any, params: {x: number, y: number, menu: Array<(MenuItemConstructorOptions) | (MenuItem)>}, callback: () => void) {
  485. // Access Menu via electron.Menu to prevent circular require.
  486. const menu = require('electron').Menu.buildFromTemplate(params.menu);
  487. menu.popup({
  488. window: event.sender.getOwnerBrowserWindow(),
  489. x: params.x,
  490. y: params.y,
  491. callback
  492. });
  493. });
  494. this.on('crashed', (event, ...args) => {
  495. app.emit('renderer-process-crashed', event, this, ...args);
  496. });
  497. this.on('render-process-gone', (event, details) => {
  498. app.emit('render-process-gone', event, this, details);
  499. // Log out a hint to help users better debug renderer crashes.
  500. if (loggingEnabled()) {
  501. console.info(`Renderer process ${details.reason} - see https://www.electronjs.org/docs/tutorial/application-debugging for potential debugging information.`);
  502. }
  503. });
  504. // The devtools requests the webContents to reload.
  505. this.on('devtools-reload-page', function (this: Electron.WebContentsInternal) {
  506. this.reload();
  507. });
  508. if (this.getType() !== 'remote') {
  509. // Make new windows requested by links behave like "window.open".
  510. this.on('-new-window' as any, (event: any, url: string, frameName: string, disposition: string,
  511. rawFeatures: string, referrer: string, postData: string) => {
  512. const { options, webPreferences, additionalFeatures } = parseFeatures(rawFeatures);
  513. const mergedOptions = {
  514. show: true,
  515. width: 800,
  516. height: 600,
  517. title: frameName,
  518. webPreferences,
  519. ...options
  520. };
  521. internalWindowOpen(event, url, referrer, frameName, disposition, mergedOptions, additionalFeatures, postData);
  522. });
  523. // Create a new browser window for the native implementation of
  524. // "window.open", used in sandbox and nativeWindowOpen mode.
  525. this.on('-add-new-contents' as any, (event: any, webContents: Electron.WebContentsInternal, disposition: string,
  526. userGesture: boolean, left: number, top: number, width: number, height: number, url: string, frameName: string,
  527. referrer: string, rawFeatures: string, postData: string) => {
  528. if ((disposition !== 'foreground-tab' && disposition !== 'new-window' &&
  529. disposition !== 'background-tab')) {
  530. event.preventDefault();
  531. return;
  532. }
  533. const { options, webPreferences, additionalFeatures } = parseFeatures(rawFeatures);
  534. const mergedOptions = {
  535. show: true,
  536. width: 800,
  537. height: 600,
  538. webContents,
  539. webPreferences,
  540. ...options
  541. };
  542. internalWindowOpen(event, url, referrer, frameName, disposition, mergedOptions, additionalFeatures, postData);
  543. });
  544. const prefs = this.getWebPreferences() || {};
  545. if (prefs.webviewTag && prefs.contextIsolation) {
  546. deprecate.log('Security Warning: A WebContents was just created with both webviewTag and contextIsolation enabled. This combination is fundamentally less secure and effectively bypasses the protections of contextIsolation. We strongly recommend you move away from webviews to OOPIF or BrowserView in order for your app to be more secure');
  547. }
  548. }
  549. this.on('login', (event, ...args) => {
  550. app.emit('login', event, this, ...args);
  551. });
  552. this.on('ready-to-show' as any, () => {
  553. const owner = this.getOwnerBrowserWindow();
  554. if (owner && !owner.isDestroyed()) {
  555. process.nextTick(() => {
  556. owner.emit('ready-to-show');
  557. });
  558. }
  559. });
  560. const event = process._linkedBinding('electron_browser_event').createEmpty();
  561. app.emit('web-contents-created', event, this);
  562. // Properties
  563. Object.defineProperty(this, 'audioMuted', {
  564. get: () => this.isAudioMuted(),
  565. set: (muted) => this.setAudioMuted(muted)
  566. });
  567. Object.defineProperty(this, 'userAgent', {
  568. get: () => this.getUserAgent(),
  569. set: (agent) => this.setUserAgent(agent)
  570. });
  571. Object.defineProperty(this, 'zoomLevel', {
  572. get: () => this.getZoomLevel(),
  573. set: (level) => this.setZoomLevel(level)
  574. });
  575. Object.defineProperty(this, 'zoomFactor', {
  576. get: () => this.getZoomFactor(),
  577. set: (factor) => this.setZoomFactor(factor)
  578. });
  579. Object.defineProperty(this, 'frameRate', {
  580. get: () => this.getFrameRate(),
  581. set: (rate) => this.setFrameRate(rate)
  582. });
  583. Object.defineProperty(this, 'backgroundThrottling', {
  584. get: () => this.getBackgroundThrottling(),
  585. set: (allowed) => this.setBackgroundThrottling(allowed)
  586. });
  587. };
  588. // Public APIs.
  589. export function create (options = {}) {
  590. return binding.create(options);
  591. }
  592. export function fromId (id: string) {
  593. return binding.fromId(id);
  594. }
  595. export function getFocusedWebContents () {
  596. let focused = null;
  597. for (const contents of binding.getAllWebContents()) {
  598. if (!contents.isFocused()) continue;
  599. if (focused == null) focused = contents;
  600. // Return webview web contents which may be embedded inside another
  601. // web contents that is also reporting as focused
  602. if (contents.getType() === 'webview') return contents;
  603. }
  604. return focused;
  605. }
  606. export function getAllWebContents () {
  607. return binding.getAllWebContents();
  608. }