2021-05-15 07:42:07 +00:00
import { app , ipcMain , session , webFrameMain } from 'electron/main' ;
2021-03-04 17:27:05 +00:00
import type { BrowserWindowConstructorOptions , LoadURLOptions } from 'electron/main' ;
2020-07-06 17:50:03 +00:00
import * as url from 'url' ;
import * as path from 'path' ;
2021-04-13 19:35:27 +00:00
import { openGuestWindow , makeWebPreferences , parseContentTypeFormat } from '@electron/internal/browser/guest-window-manager' ;
2020-07-13 16:58:49 +00:00
import { ipcMainInternal } from '@electron/internal/browser/ipc-main-internal' ;
import * as ipcMainUtils from '@electron/internal/browser/ipc-main-internal-utils' ;
import { MessagePortMain } from '@electron/internal/browser/message-port-main' ;
2020-10-13 21:11:06 +00:00
import { IPC_MESSAGES } from '@electron/internal/common/ipc-messages' ;
2018-09-11 09:56:00 +00:00
2016-06-01 05:57:35 +00:00
// session is not used here, the purpose is to make sure session is initalized
// before the webContents module.
2017-11-23 21:42:48 +00:00
// eslint-disable-next-line
2016-06-01 05:57:35 +00:00
session
2016-01-12 02:40:23 +00:00
2020-03-20 20:28:31 +00:00
let nextId = 0 ;
2016-06-01 06:08:51 +00:00
const getNextId = function ( ) {
2020-03-20 20:28:31 +00:00
return ++ nextId ;
} ;
2016-01-12 02:40:23 +00:00
2020-11-10 17:06:03 +00:00
type PostData = LoadURLOptions [ 'postData' ]
2016-06-01 06:24:53 +00:00
// Stock page sizes
2021-01-29 20:41:59 +00:00
const PDFPageSizes : Record < string , ElectronInternal.MediaSize > = {
2016-01-12 02:40:23 +00:00
A5 : {
2016-03-24 20:15:04 +00:00
custom_display_name : 'A5' ,
2016-01-12 02:40:23 +00:00
height_microns : 210000 ,
2016-03-24 20:15:04 +00:00
name : 'ISO_A5' ,
2016-01-12 02:40:23 +00:00
width_microns : 148000
} ,
A4 : {
2016-03-24 20:15:04 +00:00
custom_display_name : 'A4' ,
2016-01-12 02:40:23 +00:00
height_microns : 297000 ,
2016-03-24 20:15:04 +00:00
name : 'ISO_A4' ,
is_default : 'true' ,
2016-01-12 02:40:23 +00:00
width_microns : 210000
} ,
A3 : {
2016-03-24 20:15:04 +00:00
custom_display_name : 'A3' ,
2016-01-12 02:40:23 +00:00
height_microns : 420000 ,
2016-03-24 20:15:04 +00:00
name : 'ISO_A3' ,
2016-01-12 02:40:23 +00:00
width_microns : 297000
} ,
Legal : {
2016-03-24 20:15:04 +00:00
custom_display_name : 'Legal' ,
2016-01-12 02:40:23 +00:00
height_microns : 355600 ,
2016-03-24 20:15:04 +00:00
name : 'NA_LEGAL' ,
2016-01-12 02:40:23 +00:00
width_microns : 215900
} ,
Letter : {
2016-03-24 20:15:04 +00:00
custom_display_name : 'Letter' ,
2016-01-12 02:40:23 +00:00
height_microns : 279400 ,
2016-03-24 20:15:04 +00:00
name : 'NA_LETTER' ,
2016-01-12 02:40:23 +00:00
width_microns : 215900
} ,
Tabloid : {
height_microns : 431800 ,
2016-03-24 20:15:04 +00:00
name : 'NA_LEDGER' ,
2016-01-12 02:40:23 +00:00
width_microns : 279400 ,
2016-03-24 20:15:04 +00:00
custom_display_name : 'Tabloid'
2016-01-12 02:40:23 +00:00
}
2021-05-04 21:12:49 +00:00
} as const ;
2016-01-12 02:40:23 +00:00
2020-07-10 16:42:22 +00:00
// The minimum micron size Chromium accepts is that where:
// Per printing/units.h:
// * kMicronsPerInch - Length of an inch in 0.001mm unit.
// * kPointsPerInch - Length of an inch in CSS's 1pt unit.
//
// Formula: (kPointsPerInch / kMicronsPerInch) * size >= 1
//
// Practically, this means microns need to be > 352 microns.
// We therefore need to verify this or it will silently fail.
const isValidCustomPageSize = ( width : number , height : number ) = > {
return [ width , height ] . every ( x = > x > 352 ) ;
} ;
2016-06-01 06:24:53 +00:00
// Default printing setting
const defaultPrintingSetting = {
2020-01-28 20:47:24 +00:00
// Customizable.
2020-07-06 17:50:03 +00:00
pageRange : [ ] as { from : number , to : number } [ ] ,
2021-01-29 20:41:59 +00:00
mediaSize : { } as ElectronInternal . MediaSize ,
2016-06-01 06:24:53 +00:00
landscape : false ,
headerFooterEnabled : false ,
marginsType : 0 ,
2020-01-28 20:47:24 +00:00
scaleFactor : 100 ,
shouldPrintBackgrounds : false ,
shouldPrintSelectionOnly : false ,
// Non-customizable.
2016-06-01 06:24:53 +00:00
printWithCloudPrint : false ,
printWithPrivet : false ,
printWithExtension : false ,
2019-01-27 16:02:56 +00:00
pagesPerSheet : 1 ,
2020-01-28 20:47:24 +00:00
isFirstRequest : false ,
previewUIID : 0 ,
2021-03-23 14:40:37 +00:00
// True, if the document source is modifiable. e.g. HTML and not PDF.
2020-01-28 20:47:24 +00:00
previewModifiable : true ,
printToPDF : true ,
2016-06-01 06:24:53 +00:00
deviceName : 'Save as PDF' ,
generateDraftData : true ,
2017-08-22 04:54:49 +00:00
dpiHorizontal : 72 ,
dpiVertical : 72 ,
2017-01-31 08:41:05 +00:00
rasterizePDF : false ,
2016-06-01 06:24:53 +00:00
duplex : 0 ,
copies : 1 ,
2020-01-28 20:47:24 +00:00
// 2 = color - see ColorModel in //printing/print_job_constants.h
color : 2 ,
2020-07-06 17:50:03 +00:00
collate : true ,
printerType : 2 ,
title : undefined as string | undefined ,
url : undefined as string | undefined
2021-05-04 21:12:49 +00:00
} as const ;
2016-06-01 06:24:53 +00:00
2016-08-02 11:52:07 +00:00
// JavaScript implementations of WebContents.
2020-06-23 03:32:45 +00:00
const binding = process . _linkedBinding ( 'electron_browser_web_contents' ) ;
2020-11-17 22:14:09 +00:00
const printing = process . _linkedBinding ( 'electron_browser_printing' ) ;
2020-10-02 02:52:29 +00:00
const { WebContents } = binding as { WebContents : { prototype : Electron.WebContents } } ;
2016-08-02 11:38:35 +00:00
2021-01-15 00:00:37 +00:00
WebContents . prototype . postMessage = function ( . . . args ) {
return this . mainFrame . postMessage ( . . . args ) ;
} ;
2016-08-02 11:52:07 +00:00
WebContents . prototype . send = function ( channel , . . . args ) {
2021-02-09 20:16:21 +00:00
return this . mainFrame . send ( channel , . . . args ) ;
2020-03-20 20:28:31 +00:00
} ;
2019-01-21 07:40:27 +00:00
2018-10-06 11:48:00 +00:00
WebContents . prototype . _sendInternal = function ( channel , . . . args ) {
2021-02-09 20:16:21 +00:00
return this . mainFrame . _sendInternal ( channel , . . . args ) ;
2020-03-20 20:28:31 +00:00
} ;
2021-01-15 00:00:37 +00:00
function getWebFrame ( contents : Electron.WebContents , frame : number | [ number , number ] ) {
if ( typeof frame === 'number' ) {
return webFrameMain . fromId ( contents . mainFrame . processId , frame ) ;
} else if ( Array . isArray ( frame ) && frame . length === 2 && frame . every ( value = > typeof value === 'number' ) ) {
return webFrameMain . fromId ( frame [ 0 ] , frame [ 1 ] ) ;
} else {
throw new Error ( 'Missing required frame argument (must be number or [processId, frameId])' ) ;
2019-01-22 19:24:46 +00:00
}
2021-01-15 00:00:37 +00:00
}
2019-01-22 19:24:46 +00:00
2021-01-15 00:00:37 +00:00
WebContents . prototype . sendToFrame = function ( frameId , channel , . . . args ) {
const frame = getWebFrame ( this , frameId ) ;
if ( ! frame ) return false ;
frame . send ( channel , . . . args ) ;
return true ;
2020-03-20 20:28:31 +00:00
} ;
2018-12-10 00:37:42 +00:00
2021-01-15 00:00:37 +00:00
WebContents . prototype . _sendToFrameInternal = function ( frameId , channel , . . . args ) {
const frame = getWebFrame ( this , frameId ) ;
if ( ! frame ) return false ;
frame . _sendInternal ( channel , . . . args ) ;
return true ;
2020-03-20 20:28:31 +00:00
} ;
2016-08-02 11:52:07 +00:00
2016-01-13 03:55:49 +00:00
// Following methods are mapped to webFrame.
const webFrameMethods = [
2016-12-19 23:50:47 +00:00
'insertCSS' ,
2016-01-13 03:55:49 +00:00
'insertText' ,
2019-06-17 15:39:36 +00:00
'removeInsertedCSS' ,
2018-02-20 13:57:48 +00:00
'setVisualZoomLevelLimits'
2020-07-06 17:50:03 +00:00
] as ( 'insertCSS' | 'insertText' | 'removeInsertedCSS' | 'setVisualZoomLevelLimits' ) [ ] ;
2017-11-18 08:51:14 +00:00
2016-08-02 11:52:07 +00:00
for ( const method of webFrameMethods ) {
2020-07-06 17:50:03 +00:00
WebContents . prototype [ method ] = function ( . . . args : any [ ] ) : Promise < any > {
2020-12-03 06:55:50 +00:00
return ipcMainUtils . invokeInWebContents ( this , IPC_MESSAGES . RENDERER_WEB_FRAME_METHOD , method , . . . args ) ;
2020-03-20 20:28:31 +00:00
} ;
2016-08-02 11:52:07 +00:00
}
2016-01-12 02:40:23 +00:00
2020-10-02 02:52:29 +00:00
const waitTillCanExecuteJavaScript = async ( webContents : Electron.WebContents ) = > {
2020-03-20 20:28:31 +00:00
if ( webContents . getURL ( ) && ! webContents . isLoadingMainFrame ( ) ) return ;
2019-11-22 23:33:55 +00:00
2021-01-22 19:25:47 +00:00
return new Promise < void > ( ( resolve ) = > {
2019-11-22 23:33:55 +00:00
webContents . once ( 'did-stop-loading' , ( ) = > {
2020-03-20 20:28:31 +00:00
resolve ( ) ;
} ) ;
} ) ;
} ;
2019-03-14 19:08:54 +00:00
2016-08-02 11:52:07 +00:00
// Make sure WebContents::executeJavaScript would run the code only when the
// WebContents has been loaded.
2019-11-22 23:33:55 +00:00
WebContents . prototype . executeJavaScript = async function ( code , hasUserGesture ) {
2020-03-20 20:28:31 +00:00
await waitTillCanExecuteJavaScript ( this ) ;
2020-12-03 06:55:50 +00:00
return ipcMainUtils . invokeInWebContents ( this , IPC_MESSAGES . RENDERER_WEB_FRAME_METHOD , 'executeJavaScript' , String ( code ) , ! ! hasUserGesture ) ;
2020-03-20 20:28:31 +00:00
} ;
2020-07-08 23:13:50 +00:00
WebContents . prototype . executeJavaScriptInIsolatedWorld = async function ( worldId , code , hasUserGesture ) {
2020-03-20 20:28:31 +00:00
await waitTillCanExecuteJavaScript ( this ) ;
2020-12-03 06:55:50 +00:00
return ipcMainUtils . invokeInWebContents ( this , IPC_MESSAGES . RENDERER_WEB_FRAME_METHOD , 'executeJavaScriptInIsolatedWorld' , worldId , code , ! ! hasUserGesture ) ;
2020-03-20 20:28:31 +00:00
} ;
2016-08-02 03:02:55 +00:00
2016-08-02 11:52:07 +00:00
// Translate the options of printToPDF.
2020-07-14 01:13:34 +00:00
let pendingPromise : Promise < any > | undefined ;
WebContents . prototype . printToPDF = async function ( options ) {
2021-05-04 21:12:49 +00:00
const printSettings : Record < string , any > = {
2019-10-29 02:36:29 +00:00
. . . defaultPrintingSetting ,
requestID : getNextId ( )
2020-03-20 20:28:31 +00:00
} ;
2020-01-28 20:47:24 +00:00
if ( options . landscape !== undefined ) {
if ( typeof options . landscape !== 'boolean' ) {
2020-03-20 20:28:31 +00:00
const error = new Error ( 'landscape must be a Boolean' ) ;
return Promise . reject ( error ) ;
2020-01-28 20:47:24 +00:00
}
2020-03-20 20:28:31 +00:00
printSettings . landscape = options . landscape ;
2016-08-02 11:52:07 +00:00
}
2020-01-28 20:47:24 +00:00
if ( options . scaleFactor !== undefined ) {
if ( typeof options . scaleFactor !== 'number' ) {
2020-03-20 20:28:31 +00:00
const error = new Error ( 'scaleFactor must be a Number' ) ;
return Promise . reject ( error ) ;
2020-01-28 20:47:24 +00:00
}
2020-03-20 20:28:31 +00:00
printSettings . scaleFactor = options . scaleFactor ;
2019-10-18 00:40:19 +00:00
}
2020-01-28 20:47:24 +00:00
if ( options . marginsType !== undefined ) {
if ( typeof options . marginsType !== 'number' ) {
2020-03-20 20:28:31 +00:00
const error = new Error ( 'marginsType must be a Number' ) ;
return Promise . reject ( error ) ;
2020-01-28 20:47:24 +00:00
}
2020-03-20 20:28:31 +00:00
printSettings . marginsType = options . marginsType ;
2019-10-18 00:40:19 +00:00
}
2020-01-28 20:47:24 +00:00
if ( options . printSelectionOnly !== undefined ) {
if ( typeof options . printSelectionOnly !== 'boolean' ) {
2020-03-20 20:28:31 +00:00
const error = new Error ( 'printSelectionOnly must be a Boolean' ) ;
return Promise . reject ( error ) ;
2020-01-28 20:47:24 +00:00
}
2020-03-20 20:28:31 +00:00
printSettings . shouldPrintSelectionOnly = options . printSelectionOnly ;
2016-08-02 11:52:07 +00:00
}
2020-01-28 20:47:24 +00:00
if ( options . printBackground !== undefined ) {
if ( typeof options . printBackground !== 'boolean' ) {
2020-03-20 20:28:31 +00:00
const error = new Error ( 'printBackground must be a Boolean' ) ;
return Promise . reject ( error ) ;
2020-01-28 20:47:24 +00:00
}
2020-03-20 20:28:31 +00:00
printSettings . shouldPrintBackgrounds = options . printBackground ;
2016-08-02 11:52:07 +00:00
}
2020-01-28 20:47:24 +00:00
if ( options . pageRanges !== undefined ) {
2020-03-20 20:28:31 +00:00
const pageRanges = options . pageRanges ;
2020-03-20 15:12:18 +00:00
if ( ! Object . prototype . hasOwnProperty . call ( pageRanges , 'from' ) || ! Object . prototype . hasOwnProperty . call ( pageRanges , 'to' ) ) {
2020-03-20 20:28:31 +00:00
const error = new Error ( 'pageRanges must be an Object with \'from\' and \'to\' properties' ) ;
return Promise . reject ( error ) ;
2020-01-28 20:47:24 +00:00
}
if ( typeof pageRanges . from !== 'number' ) {
2020-03-20 20:28:31 +00:00
const error = new Error ( 'pageRanges.from must be a Number' ) ;
return Promise . reject ( error ) ;
2020-01-28 20:47:24 +00:00
}
if ( typeof pageRanges . to !== 'number' ) {
2020-03-20 20:28:31 +00:00
const error = new Error ( 'pageRanges.to must be a Number' ) ;
return Promise . reject ( error ) ;
2020-01-28 20:47:24 +00:00
}
// Chromium uses 1-based page ranges, so increment each by 1.
printSettings . pageRange = [ {
from : pageRanges . from + 1 ,
to : pageRanges.to + 1
2020-03-20 20:28:31 +00:00
} ] ;
2020-01-28 20:47:24 +00:00
}
if ( options . headerFooter !== undefined ) {
2020-03-20 20:28:31 +00:00
const headerFooter = options . headerFooter ;
printSettings . headerFooterEnabled = true ;
2020-01-28 20:47:24 +00:00
if ( typeof headerFooter === 'object' ) {
if ( ! headerFooter . url || ! headerFooter . title ) {
2020-03-20 20:28:31 +00:00
const error = new Error ( 'url and title properties are required for headerFooter' ) ;
return Promise . reject ( error ) ;
2020-01-28 20:47:24 +00:00
}
if ( typeof headerFooter . title !== 'string' ) {
2020-03-20 20:28:31 +00:00
const error = new Error ( 'headerFooter.title must be a String' ) ;
return Promise . reject ( error ) ;
2020-01-28 20:47:24 +00:00
}
2020-03-20 20:28:31 +00:00
printSettings . title = headerFooter . title ;
2020-01-28 20:47:24 +00:00
if ( typeof headerFooter . url !== 'string' ) {
2020-03-20 20:28:31 +00:00
const error = new Error ( 'headerFooter.url must be a String' ) ;
return Promise . reject ( error ) ;
2020-01-28 20:47:24 +00:00
}
2020-03-20 20:28:31 +00:00
printSettings . url = headerFooter . url ;
2020-01-28 20:47:24 +00:00
} else {
2020-03-20 20:28:31 +00:00
const error = new Error ( 'headerFooter must be an Object' ) ;
return Promise . reject ( error ) ;
2020-01-28 20:47:24 +00:00
}
2016-01-13 03:55:49 +00:00
}
2020-01-28 20:47:24 +00:00
// Optionally set size for PDF.
if ( options . pageSize !== undefined ) {
2020-03-20 20:28:31 +00:00
const pageSize = options . pageSize ;
2016-08-02 11:52:07 +00:00
if ( typeof pageSize === 'object' ) {
if ( ! pageSize . height || ! pageSize . width ) {
2020-03-20 20:28:31 +00:00
const error = new Error ( 'height and width properties are required for pageSize' ) ;
return Promise . reject ( error ) ;
2016-08-02 11:52:07 +00:00
}
2020-07-10 16:42:22 +00:00
// Dimensions in Microns - 1 meter = 10^6 microns
const height = Math . ceil ( pageSize . height ) ;
const width = Math . ceil ( pageSize . width ) ;
if ( ! isValidCustomPageSize ( width , height ) ) {
const error = new Error ( 'height and width properties must be minimum 352 microns.' ) ;
return Promise . reject ( error ) ;
}
2020-01-28 20:47:24 +00:00
printSettings . mediaSize = {
2016-08-02 11:52:07 +00:00
name : 'CUSTOM' ,
custom_display_name : 'Custom' ,
2020-07-10 16:42:22 +00:00
height_microns : height ,
width_microns : width
2020-03-20 20:28:31 +00:00
} ;
2020-07-06 17:50:03 +00:00
} else if ( Object . prototype . hasOwnProperty . call ( PDFPageSizes , pageSize ) ) {
2020-03-20 20:28:31 +00:00
printSettings . mediaSize = PDFPageSizes [ pageSize ] ;
2016-08-02 11:52:07 +00:00
} else {
2020-03-20 20:28:31 +00:00
const error = new Error ( ` Unsupported pageSize: ${ pageSize } ` ) ;
return Promise . reject ( error ) ;
2016-08-02 03:02:55 +00:00
}
2016-08-02 11:52:07 +00:00
} else {
2020-03-20 20:28:31 +00:00
printSettings . mediaSize = PDFPageSizes . A4 ;
2016-03-24 20:15:04 +00:00
}
2016-02-22 14:00:21 +00:00
2018-10-13 01:57:04 +00:00
// Chromium expects this in a 0-100 range number, not as float
2020-03-20 20:28:31 +00:00
printSettings . scaleFactor = Math . ceil ( printSettings . scaleFactor ) % 100 ;
2019-10-28 22:12:35 +00:00
// PrinterType enum from //printing/print_job_constants.h
2020-03-20 20:28:31 +00:00
printSettings . printerType = 2 ;
2020-05-10 23:06:07 +00:00
if ( this . _printToPDF ) {
2020-07-14 01:13:34 +00:00
if ( pendingPromise ) {
pendingPromise = pendingPromise . then ( ( ) = > this . _printToPDF ( printSettings ) ) ;
} else {
pendingPromise = this . _printToPDF ( printSettings ) ;
}
return pendingPromise ;
2018-11-09 03:42:34 +00:00
} else {
2020-03-20 20:28:31 +00:00
const error = new Error ( 'Printing feature is disabled' ) ;
return Promise . reject ( error ) ;
2018-11-09 03:42:34 +00:00
}
2020-03-20 20:28:31 +00:00
} ;
2018-11-09 03:42:34 +00:00
2021-01-29 20:41:59 +00:00
WebContents . prototype . print = function ( options : ElectronInternal.WebContentsPrintOptions = { } , callback ) {
2020-02-05 04:25:02 +00:00
// TODO(codebytere): deduplicate argument sanitization by moving rest of
// print param logic into new file shared between printToPDF and print
if ( typeof options === 'object' ) {
// Optionally set size for PDF.
if ( options . pageSize !== undefined ) {
2020-03-20 20:28:31 +00:00
const pageSize = options . pageSize ;
2020-02-05 04:25:02 +00:00
if ( typeof pageSize === 'object' ) {
if ( ! pageSize . height || ! pageSize . width ) {
2020-03-20 20:28:31 +00:00
throw new Error ( 'height and width properties are required for pageSize' ) ;
2020-02-05 04:25:02 +00:00
}
2020-07-10 16:42:22 +00:00
2020-02-05 04:25:02 +00:00
// Dimensions in Microns - 1 meter = 10^6 microns
2020-07-10 16:42:22 +00:00
const height = Math . ceil ( pageSize . height ) ;
const width = Math . ceil ( pageSize . width ) ;
if ( ! isValidCustomPageSize ( width , height ) ) {
throw new Error ( 'height and width properties must be minimum 352 microns.' ) ;
}
2021-01-29 20:41:59 +00:00
options . mediaSize = {
2020-02-05 04:25:02 +00:00
name : 'CUSTOM' ,
custom_display_name : 'Custom' ,
2020-07-10 16:42:22 +00:00
height_microns : height ,
width_microns : width
2020-03-20 20:28:31 +00:00
} ;
2020-02-05 04:25:02 +00:00
} else if ( PDFPageSizes [ pageSize ] ) {
2021-01-29 20:41:59 +00:00
options . mediaSize = PDFPageSizes [ pageSize ] ;
2020-02-05 04:25:02 +00:00
} else {
2020-03-20 20:28:31 +00:00
throw new Error ( ` Unsupported pageSize: ${ pageSize } ` ) ;
2020-02-05 04:25:02 +00:00
}
}
}
2020-05-10 23:06:07 +00:00
if ( this . _print ) {
2020-02-05 04:25:02 +00:00
if ( callback ) {
2020-03-20 20:28:31 +00:00
this . _print ( options , callback ) ;
2020-02-05 04:25:02 +00:00
} else {
2020-03-20 20:28:31 +00:00
this . _print ( options ) ;
2020-02-05 04:25:02 +00:00
}
2018-11-09 03:42:34 +00:00
} else {
2020-03-20 20:28:31 +00:00
console . error ( 'Error: Printing feature is disabled.' ) ;
2018-11-09 03:42:34 +00:00
}
2020-03-20 20:28:31 +00:00
} ;
2018-11-09 03:42:34 +00:00
WebContents . prototype . getPrinters = function ( ) {
2020-11-17 22:14:09 +00:00
// TODO(nornagon): this API has nothing to do with WebContents and should be
// moved.
if ( printing . getPrinterList ) {
return printing . getPrinterList ( ) ;
2018-11-09 03:42:34 +00:00
} else {
2020-03-20 20:28:31 +00:00
console . error ( 'Error: Printing feature is disabled.' ) ;
return [ ] ;
2018-11-09 03:42:34 +00:00
}
2020-03-20 20:28:31 +00:00
} ;
2016-08-02 11:52:07 +00:00
2018-09-11 07:56:49 +00:00
WebContents . prototype . loadFile = function ( filePath , options = { } ) {
2018-01-03 22:38:56 +00:00
if ( typeof filePath !== 'string' ) {
2020-03-20 20:28:31 +00:00
throw new Error ( 'Must pass filePath as a string' ) ;
2018-01-03 22:38:56 +00:00
}
2020-03-20 20:28:31 +00:00
const { query , search , hash } = options ;
2018-09-11 07:56:49 +00:00
2018-01-03 22:38:56 +00:00
return this . loadURL ( url . format ( {
protocol : 'file' ,
slashes : true ,
2018-09-11 07:56:49 +00:00
pathname : path.resolve ( app . getAppPath ( ) , filePath ) ,
query ,
search ,
hash
2020-03-20 20:28:31 +00:00
} ) ) ;
} ;
2018-01-03 22:38:56 +00:00
2021-04-27 23:11:18 +00:00
WebContents . prototype . loadURL = function ( url , options ) {
if ( ! options ) {
options = { } ;
}
const p = new Promise < void > ( ( resolve , reject ) = > {
const resolveAndCleanup = ( ) = > {
removeListeners ( ) ;
resolve ( ) ;
} ;
const rejectAndCleanup = ( errorCode : number , errorDescription : string , url : string ) = > {
const err = new Error ( ` ${ errorDescription } ( ${ errorCode } ) loading ' ${ typeof url === 'string' ? url . substr ( 0 , 2048 ) : url } ' ` ) ;
Object . assign ( err , { errno : errorCode , code : errorDescription , url } ) ;
removeListeners ( ) ;
reject ( err ) ;
} ;
const finishListener = ( ) = > {
resolveAndCleanup ( ) ;
} ;
const failListener = ( event : Electron.Event , errorCode : number , errorDescription : string , validatedURL : string , isMainFrame : boolean ) = > {
if ( isMainFrame ) {
rejectAndCleanup ( errorCode , errorDescription , validatedURL ) ;
}
} ;
let navigationStarted = false ;
const navigationListener = ( event : Electron.Event , url : string , isSameDocument : boolean , isMainFrame : boolean ) = > {
if ( isMainFrame ) {
if ( navigationStarted && ! isSameDocument ) {
// the webcontents has started another unrelated navigation in the
// main frame (probably from the app calling `loadURL` again); reject
// the promise
// We should only consider the request aborted if the "navigation" is
// actually navigating and not simply transitioning URL state in the
// current context. E.g. pushState and `location.hash` changes are
// considered navigation events but are triggered with isSameDocument.
// We can ignore these to allow virtual routing on page load as long
// as the routing does not leave the document
return rejectAndCleanup ( - 3 , 'ERR_ABORTED' , url ) ;
}
navigationStarted = true ;
}
} ;
const stopLoadingListener = ( ) = > {
// By the time we get here, either 'finish' or 'fail' should have fired
// if the navigation occurred. However, in some situations (e.g. when
// attempting to load a page with a bad scheme), loading will stop
// without emitting finish or fail. In this case, we reject the promise
// with a generic failure.
// TODO(jeremy): enumerate all the cases in which this can happen. If
// the only one is with a bad scheme, perhaps ERR_INVALID_ARGUMENT
// would be more appropriate.
rejectAndCleanup ( - 2 , 'ERR_FAILED' , url ) ;
} ;
const removeListeners = ( ) = > {
this . removeListener ( 'did-finish-load' , finishListener ) ;
this . removeListener ( 'did-fail-load' , failListener ) ;
this . removeListener ( 'did-start-navigation' , navigationListener ) ;
this . removeListener ( 'did-stop-loading' , stopLoadingListener ) ;
this . removeListener ( 'destroyed' , stopLoadingListener ) ;
} ;
this . on ( 'did-finish-load' , finishListener ) ;
this . on ( 'did-fail-load' , failListener ) ;
this . on ( 'did-start-navigation' , navigationListener ) ;
this . on ( 'did-stop-loading' , stopLoadingListener ) ;
this . on ( 'destroyed' , stopLoadingListener ) ;
} ) ;
// Add a no-op rejection handler to silence the unhandled rejection error.
p . catch ( ( ) = > { } ) ;
this . _loadURL ( url , options ) ;
this . emit ( 'load-url' , url , options ) ;
return p ;
} ;
2020-11-10 17:06:03 +00:00
WebContents . prototype . setWindowOpenHandler = function ( handler : ( details : Electron.HandlerDetails ) = > ( { action : 'allow' } | { action : 'deny' , overrideBrowserWindowOptions? : BrowserWindowConstructorOptions } ) ) {
this . _windowOpenHandler = handler ;
} ;
2021-04-13 19:35:27 +00:00
WebContents . prototype . _callWindowOpenHandler = function ( event : Electron.Event , details : Electron.HandlerDetails ) : BrowserWindowConstructorOptions | null {
2020-11-10 17:06:03 +00:00
if ( ! this . _windowOpenHandler ) {
return null ;
}
2021-04-13 19:35:27 +00:00
const response = this . _windowOpenHandler ( details ) ;
2020-11-10 17:06:03 +00:00
if ( typeof response !== 'object' ) {
event . preventDefault ( ) ;
console . error ( ` The window open handler response must be an object, but was instead of type ' ${ typeof response } '. ` ) ;
return null ;
}
if ( response === null ) {
event . preventDefault ( ) ;
console . error ( 'The window open handler response must be an object, but was instead null.' ) ;
return null ;
}
if ( response . action === 'deny' ) {
event . preventDefault ( ) ;
return null ;
} else if ( response . action === 'allow' ) {
2021-04-06 08:04:14 +00:00
if ( typeof response . overrideBrowserWindowOptions === 'object' && response . overrideBrowserWindowOptions !== null ) {
return response . overrideBrowserWindowOptions ;
} else {
return { } ;
}
2020-11-10 17:06:03 +00:00
} else {
event . preventDefault ( ) ;
console . error ( 'The window open handler response must be an object with an \'action\' property of \'allow\' or \'deny\'.' ) ;
return null ;
}
} ;
2021-01-29 20:41:59 +00:00
const addReplyToEvent = ( event : Electron.IpcMainEvent ) = > {
2020-12-09 20:48:16 +00:00
const { processId , frameId } = event ;
2021-01-29 20:41:59 +00:00
event . reply = ( channel : string , . . . args : any [ ] ) = > {
event . sender . sendToFrame ( [ processId , frameId ] , channel , . . . args ) ;
2020-03-20 20:28:31 +00:00
} ;
} ;
2019-01-22 19:24:46 +00:00
2021-01-29 20:41:59 +00:00
const addSenderFrameToEvent = ( event : Electron.IpcMainEvent | Electron . IpcMainInvokeEvent ) = > {
2020-12-09 23:34:06 +00:00
const { processId , frameId } = event ;
Object . defineProperty ( event , 'senderFrame' , {
get : ( ) = > webFrameMain . fromId ( processId , frameId )
} ) ;
} ;
2021-01-29 20:41:59 +00:00
const addReturnValueToEvent = ( event : Electron.IpcMainEvent ) = > {
2019-01-23 16:24:57 +00:00
Object . defineProperty ( event , 'returnValue' , {
2020-10-28 15:48:20 +00:00
set : ( value ) = > event . sendReply ( value ) ,
2019-01-23 16:24:57 +00:00
get : ( ) = > { }
2020-03-20 20:28:31 +00:00
} ) ;
} ;
2019-01-23 16:24:57 +00:00
2020-10-06 00:58:31 +00:00
const commandLine = process . _linkedBinding ( 'electron_common_command_line' ) ;
const environment = process . _linkedBinding ( 'electron_common_environment' ) ;
2020-09-15 19:01:50 +00:00
const loggingEnabled = ( ) = > {
2020-10-06 00:58:31 +00:00
return environment . hasVar ( 'ELECTRON_ENABLE_LOGGING' ) || commandLine . hasSwitch ( 'enable-logging' ) ;
2020-09-15 19:01:50 +00:00
} ;
2016-08-02 11:52:07 +00:00
// Add JavaScript wrappers for WebContents class.
WebContents . prototype . _init = function ( ) {
2020-12-17 20:10:29 +00:00
// Read off the ID at construction time, so that it's accessible even after
// the underlying C++ WebContents is destroyed.
const id = this . id ;
Object . defineProperty ( this , 'id' , {
value : id ,
writable : false
} ) ;
2016-08-02 11:55:56 +00:00
2020-11-10 17:06:03 +00:00
this . _windowOpenHandler = null ;
2016-01-14 18:35:29 +00:00
// Dispatch IPC messages to the ipc module.
2021-01-29 20:41:59 +00:00
this . on ( '-ipc-message' as any , function ( this : Electron . WebContents , event : Electron.IpcMainEvent , internal : boolean , channel : string , args : any [ ] ) {
2020-12-09 23:34:06 +00:00
addSenderFrameToEvent ( event ) ;
2019-01-23 16:24:57 +00:00
if ( internal ) {
2020-03-20 20:28:31 +00:00
ipcMainInternal . emit ( channel , event , . . . args ) ;
2019-01-23 16:24:57 +00:00
} else {
2020-03-20 20:28:31 +00:00
addReplyToEvent ( event ) ;
this . emit ( 'ipc-message' , event , channel , . . . args ) ;
ipcMain . emit ( channel , event , . . . args ) ;
2019-01-23 16:24:57 +00:00
}
2020-03-20 20:28:31 +00:00
} ) ;
2018-11-28 04:50:53 +00:00
2021-01-29 20:41:59 +00:00
this . on ( '-ipc-invoke' as any , function ( event : Electron.IpcMainInvokeEvent , internal : boolean , channel : string , args : any [ ] ) {
2020-12-09 23:34:06 +00:00
addSenderFrameToEvent ( event ) ;
2020-07-06 17:50:03 +00:00
event . _reply = ( result : any ) = > event . sendReply ( { result } ) ;
event . _throw = ( error : Error ) = > {
2020-03-20 20:28:31 +00:00
console . error ( ` Error occurred in handler for ' ${ channel } ': ` , error ) ;
event . sendReply ( { error : error.toString ( ) } ) ;
} ;
const target = internal ? ipcMainInternal : ipcMain ;
2020-07-06 17:50:03 +00:00
if ( ( target as any ) . _invokeHandlers . has ( channel ) ) {
( target as any ) . _invokeHandlers . get ( channel ) ( event , . . . args ) ;
2019-05-31 17:25:19 +00:00
} else {
2020-03-20 20:28:31 +00:00
event . _throw ( ` No handler registered for ' ${ channel } ' ` ) ;
2019-05-31 17:25:19 +00:00
}
2020-03-20 20:28:31 +00:00
} ) ;
2019-05-31 17:25:19 +00:00
2021-01-29 20:41:59 +00:00
this . on ( '-ipc-message-sync' as any , function ( this : Electron . WebContents , event : Electron.IpcMainEvent , internal : boolean , channel : string , args : any [ ] ) {
2020-12-09 23:34:06 +00:00
addSenderFrameToEvent ( event ) ;
2020-03-20 20:28:31 +00:00
addReturnValueToEvent ( event ) ;
2019-01-23 16:24:57 +00:00
if ( internal ) {
2020-03-20 20:28:31 +00:00
ipcMainInternal . emit ( channel , event , . . . args ) ;
2019-01-23 16:24:57 +00:00
} else {
2020-03-20 20:28:31 +00:00
addReplyToEvent ( event ) ;
this . emit ( 'ipc-message-sync' , event , channel , . . . args ) ;
ipcMain . emit ( channel , event , . . . args ) ;
2019-01-23 16:24:57 +00:00
}
2020-03-20 20:28:31 +00:00
} ) ;
2018-10-06 11:48:00 +00:00
2021-01-29 20:41:59 +00:00
this . on ( '-ipc-ports' as any , function ( event : Electron.IpcMainEvent , internal : boolean , channel : string , message : any , ports : any [ ] ) {
2020-03-20 20:28:31 +00:00
event . ports = ports . map ( p = > new MessagePortMain ( p ) ) ;
ipcMain . emit ( channel , event , message ) ;
} ) ;
2020-03-12 01:07:54 +00:00
2019-03-11 23:17:24 +00:00
this . on ( 'crashed' , ( event , . . . args ) = > {
2020-03-20 20:28:31 +00:00
app . emit ( 'renderer-process-crashed' , event , this , . . . args ) ;
2020-09-24 15:22:17 +00:00
} ) ;
this . on ( 'render-process-gone' , ( event , details ) = > {
app . emit ( 'render-process-gone' , event , this , details ) ;
2020-09-15 19:01:50 +00:00
// Log out a hint to help users better debug renderer crashes.
if ( loggingEnabled ( ) ) {
2020-09-24 15:22:17 +00:00
console . info ( ` Renderer process ${ details . reason } - see https://www.electronjs.org/docs/tutorial/application-debugging for potential debugging information. ` ) ;
2020-09-15 19:01:50 +00:00
}
2020-03-20 20:28:31 +00:00
} ) ;
2019-03-11 23:17:24 +00:00
2016-04-12 07:36:12 +00:00
// The devtools requests the webContents to reload.
2020-10-02 02:52:29 +00:00
this . on ( 'devtools-reload-page' , function ( this : Electron . WebContents ) {
2020-03-20 20:28:31 +00:00
this . reload ( ) ;
} ) ;
2016-04-12 07:36:12 +00:00
2020-03-26 18:05:45 +00:00
if ( this . getType ( ) !== 'remote' ) {
2018-10-04 11:00:34 +00:00
// Make new windows requested by links behave like "window.open".
2021-04-13 19:35:27 +00:00
this . on ( '-new-window' as any , ( event : ElectronInternal.Event , url : string , frameName : string , disposition : Electron.HandlerDetails [ 'disposition' ] ,
2021-01-29 20:41:59 +00:00
rawFeatures : string , referrer : Electron.Referrer , postData : PostData ) = > {
2021-04-13 19:35:27 +00:00
const postBody = postData ? {
data : postData ,
. . . parseContentTypeFormat ( postData )
} : undefined ;
const details : Electron.HandlerDetails = {
url ,
frameName ,
features : rawFeatures ,
referrer ,
postBody ,
disposition
} ;
const options = this . _callWindowOpenHandler ( event , details ) ;
2021-04-06 08:04:14 +00:00
if ( ! event . defaultPrevented ) {
openGuestWindow ( {
event ,
embedder : event.sender ,
disposition ,
referrer ,
postData ,
overrideBrowserWindowOptions : options || { } ,
2021-04-13 19:35:27 +00:00
windowOpenArgs : details
2021-04-06 08:04:14 +00:00
} ) ;
}
2020-11-10 17:06:03 +00:00
} ) ;
2020-03-26 18:05:45 +00:00
2020-11-10 17:06:03 +00:00
let windowOpenOverriddenOptions : BrowserWindowConstructorOptions | null = null ;
2021-04-13 19:35:27 +00:00
this . on ( '-will-add-new-contents' as any , ( event : ElectronInternal.Event , url : string , frameName : string , rawFeatures : string , disposition : Electron.HandlerDetails [ 'disposition' ] , referrer : Electron.Referrer , postData : PostData ) = > {
const postBody = postData ? {
data : postData ,
. . . parseContentTypeFormat ( postData )
} : undefined ;
const details : Electron.HandlerDetails = {
url ,
frameName ,
features : rawFeatures ,
disposition ,
referrer ,
postBody
} ;
windowOpenOverriddenOptions = this . _callWindowOpenHandler ( event , details ) ;
2020-11-10 17:06:03 +00:00
if ( ! event . defaultPrevented ) {
const secureOverrideWebPreferences = windowOpenOverriddenOptions ? {
// Allow setting of backgroundColor as a webPreference even though
// it's technically a BrowserWindowConstructorOptions option because
// we need to access it in the renderer at init time.
backgroundColor : windowOpenOverriddenOptions.backgroundColor ,
2021-03-10 20:44:36 +00:00
transparent : windowOpenOverriddenOptions.transparent ,
2020-11-10 17:06:03 +00:00
. . . windowOpenOverriddenOptions . webPreferences
} : undefined ;
this . _setNextChildWebPreferences (
makeWebPreferences ( { embedder : event.sender , secureOverrideWebPreferences } )
) ;
}
2020-03-20 20:28:31 +00:00
} ) ;
2018-10-04 11:00:34 +00:00
// Create a new browser window for the native implementation of
// "window.open", used in sandbox and nativeWindowOpen mode.
2021-01-29 20:41:59 +00:00
this . on ( '-add-new-contents' as any , ( event : ElectronInternal.Event , webContents : Electron.WebContents , disposition : string ,
2020-11-10 17:06:03 +00:00
_userGesture : boolean , _left : number , _top : number , _width : number , _height : number , url : string , frameName : string ,
referrer : Electron.Referrer , rawFeatures : string , postData : PostData ) = > {
const overriddenOptions = windowOpenOverriddenOptions || undefined ;
windowOpenOverriddenOptions = null ;
2018-10-04 11:00:34 +00:00
if ( ( disposition !== 'foreground-tab' && disposition !== 'new-window' &&
disposition !== 'background-tab' ) ) {
2020-03-20 20:28:31 +00:00
event . preventDefault ( ) ;
return ;
2018-10-04 11:00:34 +00:00
}
2020-11-10 17:06:03 +00:00
openGuestWindow ( {
event ,
embedder : event.sender ,
guest : webContents ,
overrideBrowserWindowOptions : overriddenOptions ,
disposition ,
referrer ,
postData ,
windowOpenArgs : {
url ,
frameName ,
features : rawFeatures
}
} ) ;
2020-03-20 20:28:31 +00:00
} ) ;
2018-10-04 11:00:34 +00:00
}
2019-11-11 17:47:01 +00:00
this . on ( 'login' , ( event , . . . args ) = > {
2020-03-20 20:28:31 +00:00
app . emit ( 'login' , event , this , . . . args ) ;
} ) ;
2019-11-11 17:47:01 +00:00
2020-09-28 20:36:45 +00:00
this . on ( 'ready-to-show' as any , ( ) = > {
const owner = this . getOwnerBrowserWindow ( ) ;
if ( owner && ! owner . isDestroyed ( ) ) {
process . nextTick ( ( ) = > {
owner . emit ( 'ready-to-show' ) ;
} ) ;
}
} ) ;
2020-06-23 03:32:45 +00:00
const event = process . _linkedBinding ( 'electron_browser_event' ) . createEmpty ( ) ;
2020-03-20 20:28:31 +00:00
app . emit ( 'web-contents-created' , event , this ) ;
2016-01-12 02:40:23 +00:00
2020-03-13 17:16:08 +00:00
// Properties
Object . defineProperty ( this , 'audioMuted' , {
get : ( ) = > this . isAudioMuted ( ) ,
set : ( muted ) = > this . setAudioMuted ( muted )
2020-03-20 20:28:31 +00:00
} ) ;
2020-03-13 17:16:08 +00:00
Object . defineProperty ( this , 'userAgent' , {
get : ( ) = > this . getUserAgent ( ) ,
set : ( agent ) = > this . setUserAgent ( agent )
2020-03-20 20:28:31 +00:00
} ) ;
2020-03-13 17:16:08 +00:00
Object . defineProperty ( this , 'zoomLevel' , {
get : ( ) = > this . getZoomLevel ( ) ,
set : ( level ) = > this . setZoomLevel ( level )
2020-03-20 20:28:31 +00:00
} ) ;
2020-03-13 17:16:08 +00:00
Object . defineProperty ( this , 'zoomFactor' , {
get : ( ) = > this . getZoomFactor ( ) ,
set : ( factor ) = > this . setZoomFactor ( factor )
2020-03-20 20:28:31 +00:00
} ) ;
2020-03-13 17:16:08 +00:00
Object . defineProperty ( this , 'frameRate' , {
get : ( ) = > this . getFrameRate ( ) ,
set : ( rate ) = > this . setFrameRate ( rate )
2020-03-20 20:28:31 +00:00
} ) ;
2020-05-14 13:11:45 +00:00
Object . defineProperty ( this , 'backgroundThrottling' , {
get : ( ) = > this . getBackgroundThrottling ( ) ,
set : ( allowed ) = > this . setBackgroundThrottling ( allowed )
} ) ;
2020-03-20 20:28:31 +00:00
} ;
2019-07-03 15:57:10 +00:00
2016-08-02 11:38:35 +00:00
// Public APIs.
2020-11-17 22:14:33 +00:00
export function create ( options = { } ) : Electron . WebContents {
return new ( WebContents as any ) ( options ) ;
2020-07-06 17:50:03 +00:00
}
2016-07-13 19:15:30 +00:00
2020-07-06 17:50:03 +00:00
export function fromId ( id : string ) {
return binding . fromId ( id ) ;
}
2016-07-14 15:59:49 +00:00
2020-07-06 17:50:03 +00:00
export function getFocusedWebContents ( ) {
let focused = null ;
for ( const contents of binding . getAllWebContents ( ) ) {
if ( ! contents . isFocused ( ) ) continue ;
if ( focused == null ) focused = contents ;
// Return webview web contents which may be embedded inside another
// web contents that is also reporting as focused
if ( contents . getType ( ) === 'webview' ) return contents ;
2016-06-13 15:59:03 +00:00
}
2020-07-06 17:50:03 +00:00
return focused ;
}
export function getAllWebContents ( ) {
return binding . getAllWebContents ( ) ;
}