Merge remote-tracking branch 'electron/master' into certificate-addition-windows
This commit is contained in:
commit
253a4b0114
21 changed files with 240 additions and 91 deletions
|
@ -655,6 +655,14 @@ void App::OnGpuProcessCrashed(base::TerminationStatus status) {
|
||||||
status == base::TERMINATION_STATUS_PROCESS_WAS_KILLED);
|
status == base::TERMINATION_STATUS_PROCESS_WAS_KILLED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
base::FilePath App::GetAppPath() const {
|
||||||
|
return app_path_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void App::SetAppPath(const base::FilePath& app_path) {
|
||||||
|
app_path_ = app_path;
|
||||||
|
}
|
||||||
|
|
||||||
base::FilePath App::GetPath(mate::Arguments* args, const std::string& name) {
|
base::FilePath App::GetPath(mate::Arguments* args, const std::string& name) {
|
||||||
bool succeed = false;
|
bool succeed = false;
|
||||||
base::FilePath path;
|
base::FilePath path;
|
||||||
|
@ -959,6 +967,8 @@ void App::BuildPrototype(
|
||||||
.SetMethod("isUnityRunning",
|
.SetMethod("isUnityRunning",
|
||||||
base::Bind(&Browser::IsUnityRunning, browser))
|
base::Bind(&Browser::IsUnityRunning, browser))
|
||||||
#endif
|
#endif
|
||||||
|
.SetMethod("setAppPath", &App::SetAppPath)
|
||||||
|
.SetMethod("getAppPath", &App::GetAppPath)
|
||||||
.SetMethod("setPath", &App::SetPath)
|
.SetMethod("setPath", &App::SetPath)
|
||||||
.SetMethod("getPath", &App::GetPath)
|
.SetMethod("getPath", &App::GetPath)
|
||||||
.SetMethod("setDesktopName", &App::SetDesktopName)
|
.SetMethod("setDesktopName", &App::SetDesktopName)
|
||||||
|
|
|
@ -70,6 +70,8 @@ class App : public AtomBrowserClient::Delegate,
|
||||||
std::unique_ptr<CertificateManagerModel> model);
|
std::unique_ptr<CertificateManagerModel> model);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
base::FilePath GetAppPath() const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
explicit App(v8::Isolate* isolate);
|
explicit App(v8::Isolate* isolate);
|
||||||
~App() override;
|
~App() override;
|
||||||
|
@ -115,6 +117,8 @@ class App : public AtomBrowserClient::Delegate,
|
||||||
void OnGpuProcessCrashed(base::TerminationStatus status) override;
|
void OnGpuProcessCrashed(base::TerminationStatus status) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void SetAppPath(const base::FilePath& app_path);
|
||||||
|
|
||||||
// Get/Set the pre-defined path in PathService.
|
// Get/Set the pre-defined path in PathService.
|
||||||
base::FilePath GetPath(mate::Arguments* args, const std::string& name);
|
base::FilePath GetPath(mate::Arguments* args, const std::string& name);
|
||||||
void SetPath(mate::Arguments* args,
|
void SetPath(mate::Arguments* args,
|
||||||
|
@ -154,6 +158,8 @@ class App : public AtomBrowserClient::Delegate,
|
||||||
// Tracks tasks requesting file icons.
|
// Tracks tasks requesting file icons.
|
||||||
base::CancelableTaskTracker cancelable_task_tracker_;
|
base::CancelableTaskTracker cancelable_task_tracker_;
|
||||||
|
|
||||||
|
base::FilePath app_path_;
|
||||||
|
|
||||||
DISALLOW_COPY_AND_ASSIGN(App);
|
DISALLOW_COPY_AND_ASSIGN(App);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -263,6 +263,14 @@ void Window::OnWindowSwipe(const std::string& direction) {
|
||||||
Emit("swipe", direction);
|
Emit("swipe", direction);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Window::OnWindowSheetBegin() {
|
||||||
|
Emit("sheet-begin");
|
||||||
|
}
|
||||||
|
|
||||||
|
void Window::OnWindowSheetEnd() {
|
||||||
|
Emit("sheet-end");
|
||||||
|
}
|
||||||
|
|
||||||
void Window::OnWindowEnterHtmlFullScreen() {
|
void Window::OnWindowEnterHtmlFullScreen() {
|
||||||
Emit("enter-html-full-screen");
|
Emit("enter-html-full-screen");
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,6 +79,8 @@ class Window : public mate::TrackableObject<Window>,
|
||||||
void OnWindowScrollTouchEnd() override;
|
void OnWindowScrollTouchEnd() override;
|
||||||
void OnWindowScrollTouchEdge() override;
|
void OnWindowScrollTouchEdge() override;
|
||||||
void OnWindowSwipe(const std::string& direction) override;
|
void OnWindowSwipe(const std::string& direction) override;
|
||||||
|
void OnWindowSheetBegin() override;
|
||||||
|
void OnWindowSheetEnd() override;
|
||||||
void OnWindowEnterFullScreen() override;
|
void OnWindowEnterFullScreen() override;
|
||||||
void OnWindowLeaveFullScreen() override;
|
void OnWindowLeaveFullScreen() override;
|
||||||
void OnWindowEnterHtmlFullScreen() override;
|
void OnWindowEnterHtmlFullScreen() override;
|
||||||
|
|
|
@ -235,6 +235,11 @@ void AtomBrowserClient::AppendExtraCommandLineSwitches(
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
if (delegate_) {
|
||||||
|
auto app_path = static_cast<api::App*>(delegate_)->GetAppPath();
|
||||||
|
command_line->AppendSwitchPath(switches::kAppPath, app_path);
|
||||||
|
}
|
||||||
|
|
||||||
content::WebContents* web_contents = GetWebContentsFromProcessID(process_id);
|
content::WebContents* web_contents = GetWebContentsFromProcessID(process_id);
|
||||||
if (!web_contents)
|
if (!web_contents)
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -554,6 +554,16 @@ void NativeWindow::NotifyWindowSwipe(const std::string& direction) {
|
||||||
observer.OnWindowSwipe(direction);
|
observer.OnWindowSwipe(direction);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NativeWindow::NotifyWindowSheetBegin() {
|
||||||
|
for (NativeWindowObserver& observer : observers_)
|
||||||
|
observer.OnWindowSheetBegin();
|
||||||
|
}
|
||||||
|
|
||||||
|
void NativeWindow::NotifyWindowSheetEnd() {
|
||||||
|
for (NativeWindowObserver& observer : observers_)
|
||||||
|
observer.OnWindowSheetEnd();
|
||||||
|
}
|
||||||
|
|
||||||
void NativeWindow::NotifyWindowLeaveFullScreen() {
|
void NativeWindow::NotifyWindowLeaveFullScreen() {
|
||||||
for (NativeWindowObserver& observer : observers_)
|
for (NativeWindowObserver& observer : observers_)
|
||||||
observer.OnWindowLeaveFullScreen();
|
observer.OnWindowLeaveFullScreen();
|
||||||
|
|
|
@ -233,6 +233,8 @@ class NativeWindow : public base::SupportsUserData,
|
||||||
void NotifyWindowScrollTouchEnd();
|
void NotifyWindowScrollTouchEnd();
|
||||||
void NotifyWindowScrollTouchEdge();
|
void NotifyWindowScrollTouchEdge();
|
||||||
void NotifyWindowSwipe(const std::string& direction);
|
void NotifyWindowSwipe(const std::string& direction);
|
||||||
|
void NotifyWindowSheetBegin();
|
||||||
|
void NotifyWindowSheetEnd();
|
||||||
void NotifyWindowEnterFullScreen();
|
void NotifyWindowEnterFullScreen();
|
||||||
void NotifyWindowLeaveFullScreen();
|
void NotifyWindowLeaveFullScreen();
|
||||||
void NotifyWindowEnterHtmlFullScreen();
|
void NotifyWindowEnterHtmlFullScreen();
|
||||||
|
|
|
@ -313,6 +313,14 @@ bool ScopedDisableResize::disable_resize_ = false;
|
||||||
return rect;
|
return rect;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)windowWillBeginSheet:(NSNotification *)notification {
|
||||||
|
shell_->NotifyWindowSheetBegin();
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)windowDidEndSheet:(NSNotification *)notification {
|
||||||
|
shell_->NotifyWindowSheetEnd();
|
||||||
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@interface AtomPreviewItem : NSObject <QLPreviewItem>
|
@interface AtomPreviewItem : NSObject <QLPreviewItem>
|
||||||
|
|
|
@ -67,6 +67,8 @@ class NativeWindowObserver {
|
||||||
virtual void OnWindowScrollTouchEnd() {}
|
virtual void OnWindowScrollTouchEnd() {}
|
||||||
virtual void OnWindowScrollTouchEdge() {}
|
virtual void OnWindowScrollTouchEdge() {}
|
||||||
virtual void OnWindowSwipe(const std::string& direction) {}
|
virtual void OnWindowSwipe(const std::string& direction) {}
|
||||||
|
virtual void OnWindowSheetBegin() {}
|
||||||
|
virtual void OnWindowSheetEnd() {}
|
||||||
virtual void OnWindowEnterFullScreen() {}
|
virtual void OnWindowEnterFullScreen() {}
|
||||||
virtual void OnWindowLeaveFullScreen() {}
|
virtual void OnWindowLeaveFullScreen() {}
|
||||||
virtual void OnWindowEnterHtmlFullScreen() {}
|
virtual void OnWindowEnterHtmlFullScreen() {}
|
||||||
|
|
|
@ -159,6 +159,9 @@ const char kSecureSchemes[] = "secure-schemes";
|
||||||
// The browser process app model ID
|
// The browser process app model ID
|
||||||
const char kAppUserModelId[] = "app-user-model-id";
|
const char kAppUserModelId[] = "app-user-model-id";
|
||||||
|
|
||||||
|
// The application path
|
||||||
|
const char kAppPath[] = "app-path";
|
||||||
|
|
||||||
// The command line switch versions of the options.
|
// The command line switch versions of the options.
|
||||||
const char kBackgroundColor[] = "background-color";
|
const char kBackgroundColor[] = "background-color";
|
||||||
const char kPreloadScript[] = "preload";
|
const char kPreloadScript[] = "preload";
|
||||||
|
|
|
@ -81,6 +81,7 @@ extern const char kStandardSchemes[];
|
||||||
extern const char kRegisterServiceWorkerSchemes[];
|
extern const char kRegisterServiceWorkerSchemes[];
|
||||||
extern const char kSecureSchemes[];
|
extern const char kSecureSchemes[];
|
||||||
extern const char kAppUserModelId[];
|
extern const char kAppUserModelId[];
|
||||||
|
extern const char kAppPath[];
|
||||||
|
|
||||||
extern const char kBackgroundColor[];
|
extern const char kBackgroundColor[];
|
||||||
extern const char kPreloadScript[];
|
extern const char kPreloadScript[];
|
||||||
|
|
|
@ -498,6 +498,14 @@ Returns:
|
||||||
|
|
||||||
Emitted on 3-finger swipe. Possible directions are `up`, `right`, `down`, `left`.
|
Emitted on 3-finger swipe. Possible directions are `up`, `right`, `down`, `left`.
|
||||||
|
|
||||||
|
#### Event: 'sheet-begin' _macOS_
|
||||||
|
|
||||||
|
Emitted when the window opens a sheet.
|
||||||
|
|
||||||
|
#### Event: 'sheet-end' _macOS_
|
||||||
|
|
||||||
|
Emitted when the window has closed a sheet.
|
||||||
|
|
||||||
### Static Methods
|
### Static Methods
|
||||||
|
|
||||||
The `BrowserWindow` class has the following static methods:
|
The `BrowserWindow` class has the following static methods:
|
||||||
|
|
|
@ -28,6 +28,10 @@ effect on macOS.
|
||||||
|
|
||||||
Returns `Menu` - The application menu, if set, or `null`, if not set.
|
Returns `Menu` - The application menu, if set, or `null`, if not set.
|
||||||
|
|
||||||
|
**Note:** The returned `Menu` instance doesn't support dynamic addition or
|
||||||
|
removal of menu items. [Instance properties](#instance-properties) can still
|
||||||
|
be dynamically modified.
|
||||||
|
|
||||||
#### `Menu.sendActionToFirstResponder(action)` _macOS_
|
#### `Menu.sendActionToFirstResponder(action)` _macOS_
|
||||||
|
|
||||||
* `action` String
|
* `action` String
|
||||||
|
|
|
@ -12,7 +12,8 @@ rendered.
|
||||||
Unlike an `iframe`, the `webview` runs in a separate process than your
|
Unlike an `iframe`, the `webview` runs in a separate process than your
|
||||||
app. It doesn't have the same permissions as your web page and all interactions
|
app. It doesn't have the same permissions as your web page and all interactions
|
||||||
between your app and embedded content will be asynchronous. This keeps your app
|
between your app and embedded content will be asynchronous. This keeps your app
|
||||||
safe from the embedded content.
|
safe from the embedded content. **Note:** Most methods called on the
|
||||||
|
webview from the host page require a syncronous call to the main process.
|
||||||
|
|
||||||
For security purposes, `webview` can only be used in `BrowserWindow`s that have
|
For security purposes, `webview` can only be used in `BrowserWindow`s that have
|
||||||
`nodeIntegration` enabled.
|
`nodeIntegration` enabled.
|
||||||
|
|
|
@ -10,6 +10,9 @@ Follow the guidelines below for building Electron on Windows.
|
||||||
* [Python 2.7](http://www.python.org/download/releases/2.7/)
|
* [Python 2.7](http://www.python.org/download/releases/2.7/)
|
||||||
* [Node.js](http://nodejs.org/download/)
|
* [Node.js](http://nodejs.org/download/)
|
||||||
* [Git](http://git-scm.com)
|
* [Git](http://git-scm.com)
|
||||||
|
* [Debugging Tools for Windows](https://msdn.microsoft.com/en-us/library/windows/hardware/ff551063.aspx)
|
||||||
|
if you plan on creating a full distribution since `symstore.exe` is used for
|
||||||
|
creating a symbol store from `.pdb` files.
|
||||||
|
|
||||||
If you don't currently have a Windows installation,
|
If you don't currently have a Windows installation,
|
||||||
[dev.microsoftedge.com](https://developer.microsoft.com/en-us/microsoft-edge/tools/vms/)
|
[dev.microsoftedge.com](https://developer.microsoft.com/en-us/microsoft-edge/tools/vms/)
|
||||||
|
|
|
@ -12,11 +12,7 @@ const {EventEmitter} = require('events')
|
||||||
|
|
||||||
Object.setPrototypeOf(App.prototype, EventEmitter.prototype)
|
Object.setPrototypeOf(App.prototype, EventEmitter.prototype)
|
||||||
|
|
||||||
let appPath = null
|
|
||||||
|
|
||||||
Object.assign(app, {
|
Object.assign(app, {
|
||||||
getAppPath () { return appPath },
|
|
||||||
setAppPath (path) { appPath = path },
|
|
||||||
setApplicationMenu (menu) {
|
setApplicationMenu (menu) {
|
||||||
return Menu.setApplicationMenu(menu)
|
return Menu.setApplicationMenu(menu)
|
||||||
},
|
},
|
||||||
|
|
|
@ -56,6 +56,7 @@ electron.ipcRenderer.on('ELECTRON_INTERNAL_RENDERER_ASYNC_WEB_FRAME_METHOD', (ev
|
||||||
let nodeIntegration = 'false'
|
let nodeIntegration = 'false'
|
||||||
let preloadScript = null
|
let preloadScript = null
|
||||||
let isBackgroundPage = false
|
let isBackgroundPage = false
|
||||||
|
let appPath = null
|
||||||
for (let arg of process.argv) {
|
for (let arg of process.argv) {
|
||||||
if (arg.indexOf('--guest-instance-id=') === 0) {
|
if (arg.indexOf('--guest-instance-id=') === 0) {
|
||||||
// This is a guest web view.
|
// This is a guest web view.
|
||||||
|
@ -69,6 +70,8 @@ for (let arg of process.argv) {
|
||||||
preloadScript = arg.substr(arg.indexOf('=') + 1)
|
preloadScript = arg.substr(arg.indexOf('=') + 1)
|
||||||
} else if (arg === '--background-page') {
|
} else if (arg === '--background-page') {
|
||||||
isBackgroundPage = true
|
isBackgroundPage = true
|
||||||
|
} else if (arg.indexOf('--app-path=') === 0) {
|
||||||
|
appPath = arg.substr(arg.indexOf('=') + 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,6 +119,11 @@ if (nodeIntegration === 'true') {
|
||||||
} else {
|
} else {
|
||||||
global.__filename = __filename
|
global.__filename = __filename
|
||||||
global.__dirname = __dirname
|
global.__dirname = __dirname
|
||||||
|
|
||||||
|
if (appPath) {
|
||||||
|
// Search for module under the app directory
|
||||||
|
module.paths = module.paths.concat(Module._nodeModulePaths(appPath))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Redirect window.onerror to uncaughtException.
|
// Redirect window.onerror to uncaughtException.
|
||||||
|
|
|
@ -86,8 +86,6 @@ def main():
|
||||||
run_script('create-dist.py')
|
run_script('create-dist.py')
|
||||||
run_script('upload.py')
|
run_script('upload.py')
|
||||||
else:
|
else:
|
||||||
if PLATFORM == 'win32':
|
|
||||||
os.environ['OUTPUT_TO_FILE'] = 'output.log'
|
|
||||||
run_script('build.py', ['-c', 'D'])
|
run_script('build.py', ['-c', 'D'])
|
||||||
if PLATFORM == 'win32' or target_arch == 'x64':
|
if PLATFORM == 'win32' or target_arch == 'x64':
|
||||||
run_script('test.py', ['--ci'])
|
run_script('test.py', ['--ci'])
|
||||||
|
|
|
@ -1191,6 +1191,54 @@ describe('BrowserWindow module', function () {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('sheet-begin event', function () {
|
||||||
|
if (process.platform !== 'darwin') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let sheet = null
|
||||||
|
|
||||||
|
afterEach(function () {
|
||||||
|
return closeWindow(sheet, {assertSingleWindow: false}).then(function () { sheet = null })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('emits when window opens a sheet', function (done) {
|
||||||
|
w.show()
|
||||||
|
w.once('sheet-begin', function () {
|
||||||
|
sheet.close()
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
sheet = new BrowserWindow({
|
||||||
|
modal: true,
|
||||||
|
parent: w
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('sheet-end event', function () {
|
||||||
|
if (process.platform !== 'darwin') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let sheet = null
|
||||||
|
|
||||||
|
afterEach(function () {
|
||||||
|
return closeWindow(sheet, {assertSingleWindow: false}).then(function () { sheet = null })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('emits when window has closed a sheet', function (done) {
|
||||||
|
w.show()
|
||||||
|
sheet = new BrowserWindow({
|
||||||
|
modal: true,
|
||||||
|
parent: w
|
||||||
|
})
|
||||||
|
w.once('sheet-end', function () {
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
sheet.close()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('beginFrameSubscription method', function () {
|
describe('beginFrameSubscription method', function () {
|
||||||
// This test is too slow, only test it on CI.
|
// This test is too slow, only test it on CI.
|
||||||
if (!isCI) return
|
if (!isCI) return
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
const assert = require('assert')
|
const assert = require('assert')
|
||||||
const Module = require('module')
|
const Module = require('module')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const temp = require('temp')
|
const {remote} = require('electron')
|
||||||
|
const {BrowserWindow} = remote
|
||||||
|
const {closeWindow} = require('./window-helpers')
|
||||||
|
|
||||||
|
describe('modules support', function () {
|
||||||
|
var fixtures = path.join(__dirname, 'fixtures')
|
||||||
|
|
||||||
describe('third-party module', function () {
|
describe('third-party module', function () {
|
||||||
var fixtures = path.join(__dirname, 'fixtures')
|
|
||||||
temp.track()
|
|
||||||
|
|
||||||
if (process.platform !== 'win32' || process.execPath.toLowerCase().indexOf('\\out\\d\\') === -1) {
|
if (process.platform !== 'win32' || process.execPath.toLowerCase().indexOf('\\out\\d\\') === -1) {
|
||||||
describe('runas', function () {
|
describe('runas', function () {
|
||||||
it('can be required in renderer', function () {
|
it('can be required in renderer', function () {
|
||||||
|
@ -56,6 +58,7 @@ describe('third-party module', function () {
|
||||||
assert.strictEqual(require('./fixtures/module/test.coffee'), true)
|
assert.strictEqual(require('./fixtures/module/test.coffee'), true)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('global variables', function () {
|
describe('global variables', function () {
|
||||||
describe('process', function () {
|
describe('process', function () {
|
||||||
|
@ -76,7 +79,6 @@ describe('third-party module', function () {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
|
||||||
|
|
||||||
describe('Module._nodeModulePaths', function () {
|
describe('Module._nodeModulePaths', function () {
|
||||||
describe('when the path is inside the resources path', function () {
|
describe('when the path is inside the resources path', function () {
|
||||||
|
@ -129,3 +131,27 @@ describe('Module._nodeModulePaths', function () {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('require', () => {
|
||||||
|
describe('when loaded URL is not file: protocol', () => {
|
||||||
|
let w
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
w = new BrowserWindow({
|
||||||
|
show: false
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await closeWindow(w)
|
||||||
|
w = null
|
||||||
|
})
|
||||||
|
|
||||||
|
it('searches for module under app directory', async () => {
|
||||||
|
w.loadURL('about:blank')
|
||||||
|
const result = await w.webContents.executeJavaScript('typeof require("q").when')
|
||||||
|
assert.equal(result, 'function')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
|
@ -85,7 +85,7 @@ describe('node feature', function () {
|
||||||
child.stdout.on('data', (chunk) => {
|
child.stdout.on('data', (chunk) => {
|
||||||
data += String(chunk)
|
data += String(chunk)
|
||||||
})
|
})
|
||||||
child.on('exit', (code) => {
|
child.on('close', (code) => {
|
||||||
assert.equal(code, 0)
|
assert.equal(code, 0)
|
||||||
assert.equal(data, 'pipes stdio')
|
assert.equal(data, 'pipes stdio')
|
||||||
done()
|
done()
|
||||||
|
|
Loading…
Reference in a new issue