зеркало из https://github.com/electron/electron.git
refactor: remove electron.asar and embed JS in binary (#18577)
* refactor: remove electron.asar and embed JS in binary * chore: update DEPS to merged node sha * chore: remove unneeded eslint ignore
This commit is contained in:
Родитель
901cdb22e3
Коммит
24b3d66767
32
BUILD.gn
32
BUILD.gn
|
@ -79,7 +79,7 @@ webpack_build("electron_browser_bundle") {
|
||||||
inputs = auto_filenames.browser_bundle_deps
|
inputs = auto_filenames.browser_bundle_deps
|
||||||
|
|
||||||
config_file = "//electron/build/webpack/webpack.config.browser.js"
|
config_file = "//electron/build/webpack/webpack.config.browser.js"
|
||||||
out_file = "$target_gen_dir/electron_asar/browser/init.js"
|
out_file = "$target_gen_dir/js2c/browser_init.js"
|
||||||
}
|
}
|
||||||
|
|
||||||
webpack_build("electron_renderer_bundle") {
|
webpack_build("electron_renderer_bundle") {
|
||||||
|
@ -90,7 +90,7 @@ webpack_build("electron_renderer_bundle") {
|
||||||
inputs = auto_filenames.renderer_bundle_deps
|
inputs = auto_filenames.renderer_bundle_deps
|
||||||
|
|
||||||
config_file = "//electron/build/webpack/webpack.config.renderer.js"
|
config_file = "//electron/build/webpack/webpack.config.renderer.js"
|
||||||
out_file = "$target_gen_dir/electron_asar/renderer/init.js"
|
out_file = "$target_gen_dir/js2c/renderer_init.js"
|
||||||
}
|
}
|
||||||
|
|
||||||
webpack_build("electron_worker_bundle") {
|
webpack_build("electron_worker_bundle") {
|
||||||
|
@ -101,7 +101,7 @@ webpack_build("electron_worker_bundle") {
|
||||||
inputs = auto_filenames.worker_bundle_deps
|
inputs = auto_filenames.worker_bundle_deps
|
||||||
|
|
||||||
config_file = "//electron/build/webpack/webpack.config.worker.js"
|
config_file = "//electron/build/webpack/webpack.config.worker.js"
|
||||||
out_file = "$target_gen_dir/electron_asar/worker/init.js"
|
out_file = "$target_gen_dir/js2c/worker_init.js"
|
||||||
}
|
}
|
||||||
|
|
||||||
webpack_build("electron_sandboxed_renderer_bundle") {
|
webpack_build("electron_sandboxed_renderer_bundle") {
|
||||||
|
@ -150,12 +150,18 @@ copy("atom_js2c_copy") {
|
||||||
action("atom_js2c") {
|
action("atom_js2c") {
|
||||||
deps = [
|
deps = [
|
||||||
":atom_js2c_copy",
|
":atom_js2c_copy",
|
||||||
|
":electron_browser_bundle",
|
||||||
":electron_content_script_bundle",
|
":electron_content_script_bundle",
|
||||||
":electron_isolated_renderer_bundle",
|
":electron_isolated_renderer_bundle",
|
||||||
|
":electron_renderer_bundle",
|
||||||
":electron_sandboxed_renderer_bundle",
|
":electron_sandboxed_renderer_bundle",
|
||||||
|
":electron_worker_bundle",
|
||||||
]
|
]
|
||||||
|
|
||||||
webpack_sources = [
|
webpack_sources = [
|
||||||
|
"$target_gen_dir/js2c/browser_init.js",
|
||||||
|
"$target_gen_dir/js2c/renderer_init.js",
|
||||||
|
"$target_gen_dir/js2c/worker_init.js",
|
||||||
"$target_gen_dir/js2c/content_script_bundle.js",
|
"$target_gen_dir/js2c/content_script_bundle.js",
|
||||||
"$target_gen_dir/js2c/isolated_bundle.js",
|
"$target_gen_dir/js2c/isolated_bundle.js",
|
||||||
"$target_gen_dir/js2c/sandbox_bundle.js",
|
"$target_gen_dir/js2c/sandbox_bundle.js",
|
||||||
|
@ -179,22 +185,6 @@ action("atom_js2c") {
|
||||||
|
|
||||||
target_gen_default_app_js = "$target_gen_dir/js/default_app"
|
target_gen_default_app_js = "$target_gen_dir/js/default_app"
|
||||||
|
|
||||||
asar("electron_asar") {
|
|
||||||
deps = [
|
|
||||||
":electron_browser_bundle",
|
|
||||||
":electron_renderer_bundle",
|
|
||||||
":electron_worker_bundle",
|
|
||||||
]
|
|
||||||
|
|
||||||
root = "$target_gen_dir/electron_asar"
|
|
||||||
sources = get_target_outputs(":electron_browser_bundle") +
|
|
||||||
get_target_outputs(":electron_renderer_bundle") +
|
|
||||||
get_target_outputs(":electron_worker_bundle")
|
|
||||||
outputs = [
|
|
||||||
"$root_out_dir/resources/electron.asar",
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
typescript_build("default_app_js") {
|
typescript_build("default_app_js") {
|
||||||
deps = [
|
deps = [
|
||||||
":build_electron_definitions",
|
":build_electron_definitions",
|
||||||
|
@ -845,11 +835,9 @@ if (is_mac) {
|
||||||
public_deps = [
|
public_deps = [
|
||||||
":default_app_asar",
|
":default_app_asar",
|
||||||
":electron_app_strings_bundle_data",
|
":electron_app_strings_bundle_data",
|
||||||
":electron_asar",
|
|
||||||
]
|
]
|
||||||
sources = [
|
sources = [
|
||||||
"$root_out_dir/resources/default_app.asar",
|
"$root_out_dir/resources/default_app.asar",
|
||||||
"$root_out_dir/resources/electron.asar",
|
|
||||||
"atom/browser/resources/mac/electron.icns",
|
"atom/browser/resources/mac/electron.icns",
|
||||||
]
|
]
|
||||||
outputs = [
|
outputs = [
|
||||||
|
@ -893,7 +881,6 @@ if (is_mac) {
|
||||||
deps = [
|
deps = [
|
||||||
":default_app_asar",
|
":default_app_asar",
|
||||||
":electron_app_manifest",
|
":electron_app_manifest",
|
||||||
":electron_asar",
|
|
||||||
":electron_lib",
|
":electron_lib",
|
||||||
":packed_resources",
|
":packed_resources",
|
||||||
"//content:sandbox_helper_win",
|
"//content:sandbox_helper_win",
|
||||||
|
@ -914,7 +901,6 @@ if (is_mac) {
|
||||||
|
|
||||||
if (!is_mac) {
|
if (!is_mac) {
|
||||||
data += [ "$root_out_dir/resources/default_app.asar" ]
|
data += [ "$root_out_dir/resources/default_app.asar" ]
|
||||||
data += [ "$root_out_dir/resources/electron.asar" ]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public_deps = [
|
public_deps = [
|
||||||
|
|
2
DEPS
2
DEPS
|
@ -12,7 +12,7 @@ vars = {
|
||||||
'chromium_version':
|
'chromium_version':
|
||||||
'ab588d36191964c4bca8de5c320534d95606c861',
|
'ab588d36191964c4bca8de5c320534d95606c861',
|
||||||
'node_version':
|
'node_version':
|
||||||
'dee0db9864a001ffc16440f725f4952a1a417069',
|
'b823596192bb790f9ea2a61022b55bf50e6daa83',
|
||||||
'nan_version':
|
'nan_version':
|
||||||
'960dd6c70fc9eb136efdf37b4bef18fadbc3436f',
|
'960dd6c70fc9eb136efdf37b4bef18fadbc3436f',
|
||||||
|
|
||||||
|
|
|
@ -307,25 +307,23 @@ node::Environment* NodeBindings::CreateEnvironment(
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Feed node the path to initialization script.
|
// Feed node the path to initialization script.
|
||||||
base::FilePath::StringType process_type;
|
std::string process_type;
|
||||||
switch (browser_env_) {
|
switch (browser_env_) {
|
||||||
case BrowserEnvironment::BROWSER:
|
case BrowserEnvironment::BROWSER:
|
||||||
process_type = FILE_PATH_LITERAL("browser");
|
process_type = "browser";
|
||||||
break;
|
break;
|
||||||
case BrowserEnvironment::RENDERER:
|
case BrowserEnvironment::RENDERER:
|
||||||
process_type = FILE_PATH_LITERAL("renderer");
|
process_type = "renderer";
|
||||||
break;
|
break;
|
||||||
case BrowserEnvironment::WORKER:
|
case BrowserEnvironment::WORKER:
|
||||||
process_type = FILE_PATH_LITERAL("worker");
|
process_type = "worker";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
base::FilePath resources_path =
|
base::FilePath resources_path =
|
||||||
GetResourcesPath(browser_env_ == BrowserEnvironment::BROWSER);
|
GetResourcesPath(browser_env_ == BrowserEnvironment::BROWSER);
|
||||||
base::FilePath script_path =
|
std::string init_script = "electron/js2c/" + process_type + "_init";
|
||||||
resources_path.Append(FILE_PATH_LITERAL("electron.asar"))
|
|
||||||
.Append(process_type)
|
args.insert(args.begin() + 1, init_script);
|
||||||
.Append(FILE_PATH_LITERAL("init.js"));
|
|
||||||
args.insert(args.begin() + 1, script_path.AsUTF8Unsafe());
|
|
||||||
|
|
||||||
std::unique_ptr<const char*[]> c_argv = StringVectorToArgArray(args);
|
std::unique_ptr<const char*[]> c_argv = StringVectorToArgArray(args);
|
||||||
node::Environment* env = node::CreateEnvironment(
|
node::Environment* env = node::CreateEnvironment(
|
||||||
|
@ -347,7 +345,7 @@ node::Environment* NodeBindings::CreateEnvironment(
|
||||||
process.Set("resourcesPath", resources_path);
|
process.Set("resourcesPath", resources_path);
|
||||||
// Do not set DOM globals for renderer process.
|
// Do not set DOM globals for renderer process.
|
||||||
if (browser_env_ != BrowserEnvironment::BROWSER)
|
if (browser_env_ != BrowserEnvironment::BROWSER)
|
||||||
process.Set("_noBrowserGlobals", resources_path);
|
process.Set("_noBrowserGlobals", true);
|
||||||
// The path to helper app.
|
// The path to helper app.
|
||||||
base::FilePath helper_exec_path;
|
base::FilePath helper_exec_path;
|
||||||
base::PathService::Get(content::CHILD_PROCESS_EXE, &helper_exec_path);
|
base::PathService::Get(content::CHILD_PROCESS_EXE, &helper_exec_path);
|
||||||
|
|
|
@ -106,7 +106,7 @@ if (process.resourcesPath) {
|
||||||
for (packagePath of searchPaths) {
|
for (packagePath of searchPaths) {
|
||||||
try {
|
try {
|
||||||
packagePath = path.join(process.resourcesPath, packagePath)
|
packagePath = path.join(process.resourcesPath, packagePath)
|
||||||
packageJson = __non_webpack_require__(path.join(packagePath, 'package.json')) // eslint-disable-line
|
packageJson = Module._load(path.join(packagePath, 'package.json'))
|
||||||
break
|
break
|
||||||
} catch {
|
} catch {
|
||||||
continue
|
continue
|
||||||
|
@ -197,6 +197,7 @@ Promise.all([
|
||||||
|
|
||||||
if (packagePath) {
|
if (packagePath) {
|
||||||
// Finally load app's main.js and transfer control to C++.
|
// Finally load app's main.js and transfer control to C++.
|
||||||
|
process._firstFileName = Module._resolveFilename(path.join(packagePath, mainStartupScript), null, false)
|
||||||
Module._load(path.join(packagePath, mainStartupScript), Module, true)
|
Module._load(path.join(packagePath, mainStartupScript), Module, true)
|
||||||
} else {
|
} else {
|
||||||
console.error('Failed to locate a valid package to load (app, app.asar or default_app.asar)')
|
console.error('Failed to locate a valid package to load (app, app.asar or default_app.asar)')
|
||||||
|
|
|
@ -5,12 +5,6 @@ const Module = require('module')
|
||||||
// Clear Node's global search paths.
|
// Clear Node's global search paths.
|
||||||
Module.globalPaths.length = 0
|
Module.globalPaths.length = 0
|
||||||
|
|
||||||
// Clear current bundles search paths.
|
|
||||||
const currentNodeModule = Module._cache[__filename]
|
|
||||||
if (currentNodeModule) {
|
|
||||||
currentNodeModule.paths = []
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prevent Node from adding paths outside this app to search paths.
|
// Prevent Node from adding paths outside this app to search paths.
|
||||||
const resourcesPathWithTrailingSlash = process.resourcesPath + path.sep
|
const resourcesPathWithTrailingSlash = process.resourcesPath + path.sep
|
||||||
const originalNodeModulePaths = Module._nodeModulePaths
|
const originalNodeModulePaths = Module._nodeModulePaths
|
||||||
|
|
|
@ -27,7 +27,7 @@ class CallbacksRegistry {
|
||||||
const location = match[1]
|
const location = match[1]
|
||||||
if (location.includes('(native)')) continue
|
if (location.includes('(native)')) continue
|
||||||
if (location.includes('(<anonymous>)')) continue
|
if (location.includes('(<anonymous>)')) continue
|
||||||
if (location.includes('electron.asar')) continue
|
if (location.includes('electron/js2c')) continue
|
||||||
|
|
||||||
const ref = /([^/^)]*)\)?$/gi.exec(location)
|
const ref = /([^/^)]*)\)?$/gi.exec(location)
|
||||||
filenameAndLine = ref[1]
|
filenameAndLine = ref[1]
|
||||||
|
|
|
@ -128,8 +128,9 @@ if (contextIsolation) {
|
||||||
|
|
||||||
if (nodeIntegration) {
|
if (nodeIntegration) {
|
||||||
// Export node bindings to global.
|
// Export node bindings to global.
|
||||||
global.require = __non_webpack_require__ // eslint-disable-line
|
const { makeRequireFunction } = __non_webpack_require__('internal/modules/cjs/helpers') // eslint-disable-line
|
||||||
global.module = Module._cache[__filename]
|
global.module = new Module('electron/js2c/renderer_init')
|
||||||
|
global.require = makeRequireFunction(global.module)
|
||||||
|
|
||||||
// Set the __filename to the path of html file if it is file: protocol.
|
// Set the __filename to the path of html file if it is file: protocol.
|
||||||
if (window.location.protocol === 'file:') {
|
if (window.location.protocol === 'file:') {
|
||||||
|
@ -139,7 +140,7 @@ if (nodeIntegration) {
|
||||||
if (process.platform === 'win32') {
|
if (process.platform === 'win32') {
|
||||||
if (pathname[0] === '/') pathname = pathname.substr(1)
|
if (pathname[0] === '/') pathname = pathname.substr(1)
|
||||||
|
|
||||||
const isWindowsNetworkSharePath = location.hostname.length > 0 && __filename.startsWith('\\')
|
const isWindowsNetworkSharePath = location.hostname.length > 0 && process.resourcesPath.startsWith('\\')
|
||||||
if (isWindowsNetworkSharePath) {
|
if (isWindowsNetworkSharePath) {
|
||||||
pathname = `//${location.host}/${pathname}`
|
pathname = `//${location.host}/${pathname}`
|
||||||
}
|
}
|
||||||
|
@ -152,14 +153,15 @@ if (nodeIntegration) {
|
||||||
global.module.filename = global.__filename
|
global.module.filename = global.__filename
|
||||||
|
|
||||||
// Also search for module under the html file.
|
// Also search for module under the html file.
|
||||||
global.module.paths = global.module.paths.concat(Module._nodeModulePaths(global.__dirname))
|
global.module.paths = Module._nodeModulePaths(global.__dirname)
|
||||||
} else {
|
} else {
|
||||||
global.__filename = __filename
|
// For backwards compatibility we fake these two paths here
|
||||||
global.__dirname = __dirname
|
global.__filename = path.join(process.resourcesPath, 'electron.asar', 'renderer', 'init.js')
|
||||||
|
global.__dirname = path.join(process.resourcesPath, 'electron.asar', 'renderer')
|
||||||
|
|
||||||
if (appPath) {
|
if (appPath) {
|
||||||
// Search for module under the app directory
|
// Search for module under the app directory
|
||||||
global.module.paths = global.module.paths.concat(Module._nodeModulePaths(appPath))
|
global.module.paths = Module._nodeModulePaths(appPath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -203,7 +205,7 @@ for (const preloadScript of preloadScripts) {
|
||||||
if (!isParentDir(getAppPath(), fs.realpathSync(preloadScript))) {
|
if (!isParentDir(getAppPath(), fs.realpathSync(preloadScript))) {
|
||||||
throw new Error('Preload scripts outside of app path are not allowed')
|
throw new Error('Preload scripts outside of app path are not allowed')
|
||||||
}
|
}
|
||||||
__non_webpack_require__(preloadScript) // eslint-disable-line
|
Module._load(preloadScript)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Unable to load preload script: ${preloadScript}`)
|
console.error(`Unable to load preload script: ${preloadScript}`)
|
||||||
console.error(`${error}`)
|
console.error(`${error}`)
|
||||||
|
|
|
@ -14,8 +14,9 @@ require('../common/reset-search-paths')
|
||||||
require('@electron/internal/common/init')
|
require('@electron/internal/common/init')
|
||||||
|
|
||||||
// Export node bindings to global.
|
// Export node bindings to global.
|
||||||
global.require = __non_webpack_require__ // eslint-disable-line
|
const { makeRequireFunction } = __non_webpack_require__('internal/modules/cjs/helpers') // eslint-disable-line
|
||||||
global.module = Module._cache[__filename]
|
global.module = new Module('electron/js2c/worker_init')
|
||||||
|
global.require = makeRequireFunction(global.module)
|
||||||
|
|
||||||
// Set the __filename to the path of html file if it is file: protocol.
|
// Set the __filename to the path of html file if it is file: protocol.
|
||||||
if (self.location.protocol === 'file:') {
|
if (self.location.protocol === 'file:') {
|
||||||
|
@ -27,8 +28,9 @@ if (self.location.protocol === 'file:') {
|
||||||
global.module.filename = global.__filename
|
global.module.filename = global.__filename
|
||||||
|
|
||||||
// Also search for module under the html file.
|
// Also search for module under the html file.
|
||||||
global.module.paths = global.module.paths.concat(Module._nodeModulePaths(global.__dirname))
|
global.module.paths = Module._nodeModulePaths(global.__dirname)
|
||||||
} else {
|
} else {
|
||||||
global.__filename = __filename
|
// For backwards compatibility we fake these two paths here
|
||||||
global.__dirname = __dirname
|
global.__filename = path.join(process.resourcesPath, 'electron.asar', 'worker', 'init.js')
|
||||||
|
global.__dirname = path.join(process.resourcesPath, 'electron.asar', 'worker')
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,7 +64,6 @@ locales/zh-TW.pak
|
||||||
natives_blob.bin
|
natives_blob.bin
|
||||||
resources.pak
|
resources.pak
|
||||||
resources/default_app.asar
|
resources/default_app.asar
|
||||||
resources/electron.asar
|
|
||||||
snapshot_blob.bin
|
snapshot_blob.bin
|
||||||
v8_context_snapshot.bin
|
v8_context_snapshot.bin
|
||||||
version
|
version
|
||||||
|
|
|
@ -64,7 +64,6 @@ locales/zh-TW.pak
|
||||||
natives_blob.bin
|
natives_blob.bin
|
||||||
resources.pak
|
resources.pak
|
||||||
resources/default_app.asar
|
resources/default_app.asar
|
||||||
resources/electron.asar
|
|
||||||
snapshot_blob.bin
|
snapshot_blob.bin
|
||||||
swiftshader/libEGL.so
|
swiftshader/libEGL.so
|
||||||
swiftshader/libGLESv2.so
|
swiftshader/libGLESv2.so
|
||||||
|
|
|
@ -64,7 +64,6 @@ locales/zh-TW.pak
|
||||||
natives_blob.bin
|
natives_blob.bin
|
||||||
resources.pak
|
resources.pak
|
||||||
resources/default_app.asar
|
resources/default_app.asar
|
||||||
resources/electron.asar
|
|
||||||
snapshot_blob.bin
|
snapshot_blob.bin
|
||||||
swiftshader/libEGL.so
|
swiftshader/libEGL.so
|
||||||
swiftshader/libGLESv2.so
|
swiftshader/libGLESv2.so
|
||||||
|
|
|
@ -64,7 +64,6 @@ locales/zh-TW.pak
|
||||||
natives_blob.bin
|
natives_blob.bin
|
||||||
resources.pak
|
resources.pak
|
||||||
resources/default_app.asar
|
resources/default_app.asar
|
||||||
resources/electron.asar
|
|
||||||
snapshot_blob.bin
|
snapshot_blob.bin
|
||||||
swiftshader/libEGL.so
|
swiftshader/libEGL.so
|
||||||
swiftshader/libGLESv2.so
|
swiftshader/libGLESv2.so
|
||||||
|
|
|
@ -261,7 +261,6 @@ Electron.app/Contents/Resources/da.lproj/
|
||||||
Electron.app/Contents/Resources/de.lproj/
|
Electron.app/Contents/Resources/de.lproj/
|
||||||
Electron.app/Contents/Resources/default_app.asar
|
Electron.app/Contents/Resources/default_app.asar
|
||||||
Electron.app/Contents/Resources/el.lproj/
|
Electron.app/Contents/Resources/el.lproj/
|
||||||
Electron.app/Contents/Resources/electron.asar
|
|
||||||
Electron.app/Contents/Resources/electron.icns
|
Electron.app/Contents/Resources/electron.icns
|
||||||
Electron.app/Contents/Resources/en.lproj/
|
Electron.app/Contents/Resources/en.lproj/
|
||||||
Electron.app/Contents/Resources/en_GB.lproj/
|
Electron.app/Contents/Resources/en_GB.lproj/
|
||||||
|
|
|
@ -155,7 +155,6 @@ Electron.app/Contents/Resources/da.lproj/
|
||||||
Electron.app/Contents/Resources/de.lproj/
|
Electron.app/Contents/Resources/de.lproj/
|
||||||
Electron.app/Contents/Resources/default_app.asar
|
Electron.app/Contents/Resources/default_app.asar
|
||||||
Electron.app/Contents/Resources/el.lproj/
|
Electron.app/Contents/Resources/el.lproj/
|
||||||
Electron.app/Contents/Resources/electron.asar
|
|
||||||
Electron.app/Contents/Resources/electron.icns
|
Electron.app/Contents/Resources/electron.icns
|
||||||
Electron.app/Contents/Resources/en.lproj/
|
Electron.app/Contents/Resources/en.lproj/
|
||||||
Electron.app/Contents/Resources/en_GB.lproj/
|
Electron.app/Contents/Resources/en_GB.lproj/
|
||||||
|
|
|
@ -42,7 +42,7 @@ describe('asar package', function () {
|
||||||
it('does not leak fd', function () {
|
it('does not leak fd', function () {
|
||||||
let readCalls = 1
|
let readCalls = 1
|
||||||
while (readCalls <= 10000) {
|
while (readCalls <= 10000) {
|
||||||
fs.readFileSync(path.join(process.resourcesPath, 'electron.asar', 'renderer', 'init.js'))
|
fs.readFileSync(path.join(process.resourcesPath, 'default_app.asar', 'index.js'))
|
||||||
readCalls++
|
readCalls++
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
declare var internalBinding: any;
|
||||||
|
|
||||||
declare namespace NodeJS {
|
declare namespace NodeJS {
|
||||||
interface FeaturesBinding {
|
interface FeaturesBinding {
|
||||||
isDesktopCapturerEnabled(): boolean;
|
isDesktopCapturerEnabled(): boolean;
|
||||||
|
@ -35,6 +37,9 @@ declare namespace NodeJS {
|
||||||
// Additional events
|
// Additional events
|
||||||
once(event: 'document-start', listener: () => any): this;
|
once(event: 'document-start', listener: () => any): this;
|
||||||
once(event: 'document-end', listener: () => any): this;
|
once(event: 'document-end', listener: () => any): this;
|
||||||
|
|
||||||
|
// Additional properties
|
||||||
|
_firstFileName?: string;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Загрузка…
Ссылка в новой задаче