diff --git a/docs/api/global-shortcut.md b/docs/api/global-shortcut.md index 5967223ec23c..3d18a39f5cde 100644 --- a/docs/api/global-shortcut.md +++ b/docs/api/global-shortcut.md @@ -47,6 +47,8 @@ The `globalShortcut` module has the following methods: * `accelerator` [Accelerator](accelerator.md) * `callback` Function +Returns `Boolean` - Whether or not the shortcut was registered successfully. + Registers a global shortcut of `accelerator`. The `callback` is called when the registered shortcut is pressed by the user. diff --git a/package.json b/package.json index a37b59bd4c6d..be2f33e1b0bc 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "dotenv-safe": "^4.0.4", "dugite": "^1.45.0", "electron-docs-linter": "^2.4.0", - "electron-typescript-definitions": "^6.0.0", + "electron-typescript-definitions": "^7.0.0", "eslint": "^5.13.0", "eslint-config-standard": "^12.0.0", "eslint-plugin-mocha": "^5.2.0", @@ -57,7 +57,7 @@ "lint:docs-relative-links": "python ./script/check-relative-doc-links.py", "lint:js-in-markdown": "standard-markdown docs", "create-api-json": "electron-docs-linter docs --outfile=electron-api.json", - "create-typescript-definitions": "npm run create-api-json && electron-typescript-definitions --in=electron-api.json --out=electron.d.ts", + "create-typescript-definitions": "npm run create-api-json && electron-typescript-definitions --in=electron-api.json --out=electron.d.ts && node spec/ts-smoke/runner.js", "gn-typescript-definitions": "npm run create-typescript-definitions && cp electron.d.ts", "preinstall": "node -e 'process.exit(0)'", "precommit": "lint-staged", diff --git a/script/lint.js b/script/lint.js index 865d269a20e8..b2b540b44e4c 100755 --- a/script/lint.js +++ b/script/lint.js @@ -29,7 +29,10 @@ const BLACKLIST = new Set([ ['atom', 'common', 'common_message_generator.cc'], ['atom', 'common', 'common_message_generator.h'], ['atom', 'common', 'node_includes.h'], - ['spec', 'static', 'jquery-2.0.3.min.js'] + ['spec', 'static', 'jquery-2.0.3.min.js'], + ['spec', 'ts-smoke', 'electron', 'main.ts'], + ['spec', 'ts-smoke', 'electron', 'renderer.ts'], + ['spec', 'ts-smoke', 'runner.js'] ].map(tokens => path.join(SOURCE_ROOT, ...tokens))) function spawnAndCheckExitCode (cmd, args, opts) { diff --git a/spec/ts-smoke/electron/main.ts b/spec/ts-smoke/electron/main.ts new file mode 100644 index 000000000000..6884eea69a97 --- /dev/null +++ b/spec/ts-smoke/electron/main.ts @@ -0,0 +1,1159 @@ +// tslint:disable:ordered-imports curly no-console no-angle-bracket-type-assertion object-literal-sort-keys only-arrow-functions + +import { + app, + autoUpdater, + BrowserWindow, + contentTracing, + dialog, + globalShortcut, + ipcMain, + Menu, + MenuItem, + net, + powerMonitor, + powerSaveBlocker, + protocol, + Tray, + clipboard, + crashReporter, + nativeImage, + screen, + shell, + session, + systemPreferences, + webContents, + Event, + TouchBar +} from 'electron' + +import * as path from 'path' + +// Quick start +// https://github.com/atom/electron/blob/master/docs/tutorial/quick-start.md + +// Keep a global reference of the window object, if you don't, the window will +// be closed automatically when the javascript object is GCed. +let mainWindow: Electron.BrowserWindow = null +const mainWindow2: BrowserWindow = null + +// Quit when all windows are closed. +app.on('window-all-closed', () => { + if (process.platform !== 'darwin') { + app.quit() + } +}) + +// Check single instance app +const gotLock = app.requestSingleInstanceLock() + +if (!gotLock) { + app.quit() + process.exit(0) +} + +// This method will be called when Electron has done everything +// initialization and ready for creating browser windows. +app.on('ready', () => { + // Create the browser window. + mainWindow = new BrowserWindow({ width: 800, height: 600 }) + + // and load the index.html of the app. + mainWindow.loadURL(`file://${__dirname}/index.html`) + mainWindow.loadURL('file://foo/bar', { userAgent: 'cool-agent', httpReferrer: 'greateRefferer' }) + mainWindow.webContents.loadURL('file://foo/bar', { userAgent: 'cool-agent', httpReferrer: 'greateRefferer' }) + mainWindow.webContents.loadURL('file://foo/bar', { userAgent: 'cool-agent', httpReferrer: 'greateRefferer', postData: [{ type: 'blob', blobUUID: 'hogefuga' }] }) + + mainWindow.webContents.openDevTools() + mainWindow.webContents.toggleDevTools() + mainWindow.webContents.openDevTools({ mode: 'detach' }) + mainWindow.webContents.closeDevTools() + mainWindow.webContents.addWorkSpace('/path/to/workspace') + mainWindow.webContents.removeWorkSpace('/path/to/workspace') + const opened: boolean = mainWindow.webContents.isDevToolsOpened() + const focused = mainWindow.webContents.isDevToolsFocused() + // Emitted when the window is closed. + mainWindow.on('closed', () => { + // Dereference the window object, usually you would store windows + // in an array if your app supports multi windows, this is the time + // when you should delete the corresponding element. + mainWindow = null + }) + + mainWindow.webContents.setVisualZoomLevelLimits(50, 200) + mainWindow.webContents.setLayoutZoomLevelLimits(50, 200) + + mainWindow.webContents.print({ silent: true, printBackground: false }) + mainWindow.webContents.print() + + mainWindow.webContents.printToPDF({ + marginsType: 1, + pageSize: 'A3', + printBackground: true, + printSelectionOnly: true, + landscape: true + }, (error: Error, data: Buffer) => console.log(error)) + + mainWindow.webContents.printToPDF({}, (err, data) => console.log(err)) + + mainWindow.webContents.executeJavaScript('return true;').then((v: boolean) => console.log(v)) + mainWindow.webContents.executeJavaScript('return true;', true) + mainWindow.webContents.executeJavaScript('return true;', true, (result: boolean) => console.log(result)) + mainWindow.webContents.insertText('blah, blah, blah') + mainWindow.webContents.startDrag({ file: '/path/to/img.png', icon: nativeImage.createFromPath('/path/to/icon.png') }) + mainWindow.webContents.findInPage('blah') + mainWindow.webContents.findInPage('blah', { + forward: true, + matchCase: false + }) + mainWindow.webContents.stopFindInPage('clearSelection') + mainWindow.webContents.stopFindInPage('keepSelection') + mainWindow.webContents.stopFindInPage('activateSelection') + + mainWindow.loadURL('https://github.com') + + mainWindow.webContents.on('did-finish-load', function () { + mainWindow.webContents.savePage('/tmp/test.html', 'HTMLComplete', function (error) { + if (!error) { console.log('Save page successfully') } + }) + }) + + try { + mainWindow.webContents.debugger.attach('1.1') + } catch (err) { + console.log('Debugger attach failed : ', err) + } + + mainWindow.webContents.debugger.on('detach', function (event, reason) { + console.log('Debugger detached due to : ', reason) + }) + + mainWindow.webContents.debugger.on('message', function (event, method, params) { + if (method === 'Network.requestWillBeSent') { + if (params.request.url === 'https://www.github.com') { + mainWindow.webContents.debugger.detach() + } + } + }) + + mainWindow.webContents.debugger.sendCommand('Network.enable') + mainWindow.webContents.capturePage((image) => { + console.log(image.toDataURL()) + }) + mainWindow.webContents.capturePage({ x: 0, y: 0, width: 100, height: 200 }, (image) => { + console.log(image.toPNG()) + }) +}) + +app.commandLine.appendSwitch('enable-web-bluetooth') + +app.on('ready', () => { + mainWindow.webContents.on('select-bluetooth-device', (event, deviceList, callback) => { + event.preventDefault() + + const result = (() => { + for (const device of deviceList) { + if (device.deviceName === 'test') { + return device + } + } + return null + })() + + if (!result) { + callback('') + } else { + callback(result.deviceId) + } + }) +}) + +// Locale +app.getLocale() + +// Desktop environment integration +// https://github.com/atom/electron/blob/master/docs/tutorial/desktop-environment-integration.md + +app.addRecentDocument('/Users/USERNAME/Desktop/work.type') +app.clearRecentDocuments() +const dockMenu = Menu.buildFromTemplate([ + { + label: 'New Window', + click: () => { + console.log('New Window') + } + }, + { + label: 'New Window with Settings', + submenu: [ + { label: 'Basic' }, + { label: 'Pro' } + ] + }, + { label: 'New Command...' }, + { + label: 'Edit', + submenu: [ + { + label: 'Undo', + accelerator: 'CmdOrCtrl+Z', + role: 'undo' + }, + { + label: 'Redo', + accelerator: 'Shift+CmdOrCtrl+Z', + role: 'redo' + }, + { + type: 'separator' + }, + { + label: 'Cut', + accelerator: 'CmdOrCtrl+X', + role: 'cut' + }, + { + label: 'Copy', + accelerator: 'CmdOrCtrl+C', + role: 'copy' + }, + { + label: 'Paste', + accelerator: 'CmdOrCtrl+V', + role: 'paste' + } + ] + } +]) +app.dock.setMenu(dockMenu) +app.dock.setBadge('foo') +const dockid = app.dock.bounce('informational') +app.dock.cancelBounce(dockid) +app.dock.setIcon('/path/to/icon.png') + +app.setBadgeCount(app.getBadgeCount() + 1) + +app.setUserTasks([ + { + program: process.execPath, + arguments: '--new-window', + iconPath: process.execPath, + iconIndex: 0, + title: 'New Window', + description: 'Create a new window' + } +]) +app.setUserTasks([]) + +app.setJumpList([ + { + type: 'custom', + name: 'Recent Projects', + items: [ + { type: 'file', path: 'C:\\Projects\\project1.proj' }, + { type: 'file', path: 'C:\\Projects\\project2.proj' } + ] + }, + { // has a name so type is assumed to be "custom" + name: 'Tools', + items: [ + { + type: 'task', + title: 'Tool A', + program: process.execPath, + args: '--run-tool-a', + iconPath: process.execPath, + iconIndex: 0, + description: 'Runs Tool A' + }, + { + type: 'task', + title: 'Tool B', + program: process.execPath, + args: '--run-tool-b', + iconPath: process.execPath, + iconIndex: 0, + description: 'Runs Tool B' + }] + }, + { + type: 'frequent' + }, + { // has no name and no type so type is assumed to be "tasks" + items: [ + { + type: 'task', + title: 'New Project', + program: process.execPath, + args: '--new-project', + description: 'Create a new project.' + }, + { + type: 'separator' + }, + { + type: 'task', + title: 'Recover Project', + program: process.execPath, + args: '--recover-project', + description: 'Recover Project' + }] + } +]) + +if (app.isUnityRunning()) { + console.log('unity running') +} +if (app.isAccessibilitySupportEnabled()) { + console.log('a11y running') +} +app.setLoginItemSettings({ openAtLogin: true, openAsHidden: false }) +console.log(app.getLoginItemSettings().wasOpenedAtLogin) +app.setAboutPanelOptions({ + applicationName: 'Test', + version: '1.2.3' +}) + +let window = new BrowserWindow() +window.setProgressBar(0.5) +window.setRepresentedFilename('/etc/passwd') +window.setDocumentEdited(true) +window.previewFile('/path/to/file') +window.previewFile('/path/to/file', 'Displayed Name') +window.setVibrancy('light') +window.setVibrancy('titlebar') + +// Online/Offline Event Detection +// https://github.com/atom/electron/blob/master/docs/tutorial/online-offline-events.md + +let onlineStatusWindow: Electron.BrowserWindow + +app.on('ready', () => { + onlineStatusWindow = new BrowserWindow({ width: 0, height: 0, show: false, vibrancy: 'sidebar' }) + onlineStatusWindow.loadURL(`file://${__dirname}/online-status.html`) +}) +app.on('accessibility-support-changed', (_, enabled) => console.log('accessibility: ' + enabled)) + +ipcMain.on('online-status-changed', (event: any, status: any) => { + console.log(status) +}) + +// Synopsis +// https://github.com/atom/electron/blob/master/docs/api/synopsis.md + +app.on('ready', () => { + window = new BrowserWindow({ + width: 800, + height: 600, + titleBarStyle: 'hiddenInset' + }) + window.loadURL('https://github.com') +}) + +// Supported Chrome command line switches +// https://github.com/atom/electron/blob/master/docs/api/chrome-command-line-switches.md + +app.commandLine.appendSwitch('remote-debugging-port', '8315') +app.commandLine.appendSwitch('host-rules', 'MAP * 127.0.0.1') +app.commandLine.appendSwitch('vmodule', 'console=0') + +// systemPreferences +// https://github.com/electron/electron/blob/master/docs/api/system-preferences.md + +const browserOptions = { + width: 1000, + height: 800, + transparent: false, + frame: true +} + +// Make the window transparent only if the platform supports it. +if (process.platform !== 'win32' || systemPreferences.isAeroGlassEnabled()) { + browserOptions.transparent = true + browserOptions.frame = false +} + +if (process.platform === 'win32') { + systemPreferences.on('color-changed', () => { console.log('color changed') }) + systemPreferences.on('inverted-color-scheme-changed', (_, inverted) => console.log(inverted ? 'inverted' : 'not inverted')) + console.log('Color for menu is', systemPreferences.getColor('menu')) +} + +// Create the window. +const win1 = new BrowserWindow(browserOptions) + +// Navigate. +if (browserOptions.transparent) { + win1.loadURL('file://' + __dirname + '/index.html') +} else { + // No transparency, so we load a fallback that uses basic styles. + win1.loadURL('file://' + __dirname + '/fallback.html') +} + +// app +// https://github.com/atom/electron/blob/master/docs/api/app.md + +app.on('certificate-error', function (event, webContents, url, error, certificate, callback) { + if (url === 'https://github.com') { + // Verification logic. + event.preventDefault() + callback(true) + } else { + callback(false) + } +}) + +app.on('select-client-certificate', function (event, webContents, url, list, callback) { + event.preventDefault() + callback(list[0]) +}) + +app.on('login', function (event, webContents, request, authInfo, callback) { + event.preventDefault() + callback('username', 'secret') +}) + +const win2 = new BrowserWindow({ show: false }) +win2.once('ready-to-show', () => { + win2.show() +}) + +app.relaunch({ args: process.argv.slice(1).concat(['--relaunch']) }) +app.exit(0) + +// auto-updater +// https://github.com/atom/electron/blob/master/docs/api/auto-updater.md + +autoUpdater.setFeedURL({ + url: 'http://mycompany.com/myapp/latest?version=' + app.getVersion(), + headers: { + key: 'value' + }, + serverType: 'default' +}) +autoUpdater.checkForUpdates() +autoUpdater.quitAndInstall() + +autoUpdater.on('error', (error) => { + console.log('error', error) +}) + +autoUpdater.on('update-downloaded', (event, releaseNotes, releaseName, releaseDate, updateURL) => { + console.log('update-downloaded', releaseNotes, releaseName, releaseDate, updateURL) +}) + +// browser-window +// https://github.com/atom/electron/blob/master/docs/api/browser-window.md + +let win3 = new BrowserWindow({ width: 800, height: 600, show: false }) +win3.on('closed', () => { + win3 = null +}) + +win3.loadURL('https://github.com') +win3.show() + +const toolbarRect = document.getElementById('toolbar').getBoundingClientRect() +win3.setSheetOffset(toolbarRect.height) + +const installed = BrowserWindow.getDevToolsExtensions().hasOwnProperty('devtron') + +// content-tracing +// https://github.com/atom/electron/blob/master/docs/api/content-tracing.md + +const options = { + categoryFilter: '*', + traceOptions: 'record-until-full,enable-sampling' +} + +contentTracing.startRecording(options, function () { + console.log('Tracing started') + setTimeout(function () { + contentTracing.stopRecording('', function (path) { + console.log('Tracing data recorded to ' + path) + }) + }, 5000) +}) + +// dialog +// https://github.com/atom/electron/blob/master/docs/api/dialog.md + +// variant without browserWindow +let openDialogResult: string[] = dialog.showOpenDialog({ + title: 'Testing showOpenDialog', + defaultPath: '/var/log/syslog', + filters: [{ name: '', extensions: [''] }], + properties: ['openFile', 'openDirectory', 'multiSelections'] +}) + +// variant with browserWindow +openDialogResult = dialog.showOpenDialog(win3, { + title: 'Testing showOpenDialog', + defaultPath: '/var/log/syslog', + filters: [{ name: '', extensions: [''] }], + properties: ['openFile', 'openDirectory', 'multiSelections'] +}) + +// global-shortcut +// https://github.com/atom/electron/blob/master/docs/api/global-shortcut.md + +// Register a 'ctrl+x' shortcut listener. +const ret = globalShortcut.register('ctrl+x', () => { + console.log('ctrl+x is pressed') +}) +if (!ret) { console.log('registerion fails') } + +// Check whether a shortcut is registered. +console.log(globalShortcut.isRegistered('ctrl+x')) + +// Unregister a shortcut. +globalShortcut.unregister('ctrl+x') + +// Unregister all shortcuts. +globalShortcut.unregisterAll() + +// ipcMain +// https://github.com/atom/electron/blob/master/docs/api/ipc-main-process.md + +ipcMain.on('asynchronous-message', (event: Electron.Event, arg: any) => { + console.log(arg) // prints "ping" + event.sender.send('asynchronous-reply', 'pong') +}) + +ipcMain.on('synchronous-message', (event: Electron.Event, arg: any) => { + console.log(arg) // prints "ping" + event.returnValue = 'pong' +}) + +ipcMain.on('synchronous-message', (event: Event, arg: any) => { + console.log("event isn't namespaced and refers to the correct type.") + event.returnValue = 'pong' +}) + +const winWindows = new BrowserWindow({ + width: 800, + height: 600, + show: false, + thickFrame: false, + type: 'toolbar' +}) + +// menu-item +// https://github.com/atom/electron/blob/master/docs/api/menu-item.md + +const menuItem = new MenuItem({}) + +menuItem.label = 'Hello World!' +menuItem.click = (passedMenuItem: Electron.MenuItem, browserWindow: Electron.BrowserWindow) => { + console.log('click', passedMenuItem, browserWindow) +} + +// menu +// https://github.com/atom/electron/blob/master/docs/api/menu.md + +let menu = new Menu() +menu.append(new MenuItem({ label: 'MenuItem1', click: () => { console.log('item 1 clicked') } })) +menu.append(new MenuItem({ type: 'separator' })) +menu.append(new MenuItem({ label: 'MenuItem2', type: 'checkbox', checked: true })) +menu.insert(0, menuItem) + +console.log(menu.items) + +const pos = screen.getCursorScreenPoint() +menu.popup({ x: pos.x, y: pos.y }) + +// main.js +const template = [ + { + label: 'Electron', + submenu: [ + { + label: 'About Electron', + role: 'about' + }, + { + type: 'separator' + }, + { + label: 'Services', + role: 'services', + submenu: [] + }, + { + type: 'separator' + }, + { + label: 'Hide Electron', + accelerator: 'Command+H', + role: 'hide' + }, + { + label: 'Hide Others', + accelerator: 'Command+Shift+H', + role: 'hideothers' + }, + { + label: 'Show All', + role: 'unhide' + }, + { + type: 'separator' + }, + { + label: 'Quit', + accelerator: 'Command+Q', + click: () => { app.quit() } + } + ] + }, + { + label: 'Edit', + submenu: [ + { + label: 'Undo', + accelerator: 'Command+Z', + role: 'undo' + }, + { + label: 'Redo', + accelerator: 'Shift+Command+Z', + role: 'redo' + }, + { + type: 'separator' + }, + { + label: 'Cut', + accelerator: 'Command+X', + role: 'cut' + }, + { + label: 'Copy', + accelerator: 'Command+C', + role: 'copy' + }, + { + label: 'Paste', + accelerator: 'Command+V', + role: 'paste' + }, + { + label: 'Select All', + accelerator: 'Command+A', + role: 'selectall' + } + ] + }, + { + label: 'View', + submenu: [ + { + label: 'Reload', + accelerator: 'Command+R', + click: (item, focusedWindow) => { + if (focusedWindow) { + focusedWindow.webContents.reloadIgnoringCache() + } + } + }, + { + label: 'Toggle DevTools', + accelerator: 'Alt+Command+I', + click: (item, focusedWindow) => { + if (focusedWindow) { + focusedWindow.webContents.toggleDevTools() + } + } + }, + { + type: 'separator' + }, + { + label: 'Actual Size', + accelerator: 'CmdOrCtrl+0', + click: (item, focusedWindow) => { + if (focusedWindow) { + focusedWindow.webContents.setZoomLevel(0) + } + } + }, + { + label: 'Zoom In', + accelerator: 'CmdOrCtrl+Plus', + click: (item, focusedWindow) => { + if (focusedWindow) { + const { webContents } = focusedWindow + const zoomLevel = webContents.getZoomLevel() + webContents.setZoomLevel(zoomLevel + 0.5) + } + } + }, + { + label: 'Zoom Out', + accelerator: 'CmdOrCtrl+-', + click: (item, focusedWindow) => { + if (focusedWindow) { + const { webContents } = focusedWindow + const zoomLevel = webContents.getZoomLevel() + webContents.setZoomLevel(zoomLevel - 0.5) + } + } + } + ] + }, + { + label: 'Window', + submenu: [ + { + label: 'Minimize', + accelerator: 'Command+M', + role: 'minimize' + }, + { + label: 'Close', + accelerator: 'Command+W', + role: 'close' + }, + { + type: 'separator' + }, + { + label: 'Bring All to Front', + role: 'front' + } + ] + }, + { + label: 'Help', + submenu: [] + } +] + +menu = Menu.buildFromTemplate(template) + +Menu.setApplicationMenu(menu) // Must be called within app.on('ready', function(){ ... }); + +Menu.buildFromTemplate([ + { label: '4', id: '4' }, + { label: '5', id: '5', after: ['4'] }, + { label: '1', id: '1', before: ['4'] }, + { label: '2', id: '2' }, + { label: '3', id: '3' } +]) + +Menu.buildFromTemplate([ + { label: 'a' }, + { label: '1' }, + { label: 'b' }, + { label: '2' }, + { label: 'c' }, + { label: '3' } +]) + +// net +// https://github.com/electron/electron/blob/master/docs/api/net.md + +app.on('ready', () => { + const request = net.request('https://github.com') + request.setHeader('Some-Custom-Header-Name', 'Some-Custom-Header-Value') + const header = request.getHeader('Some-Custom-Header-Name') + request.removeHeader('Some-Custom-Header-Name') + request.on('response', (response) => { + console.log(`Status code: ${response.statusCode}`) + console.log(`Status message: ${response.statusMessage}`) + console.log(`Headers: ${JSON.stringify(response.headers)}`) + console.log(`Http version: ${response.httpVersion}`) + console.log(`Major Http version: ${response.httpVersionMajor}`) + console.log(`Minor Http version: ${response.httpVersionMinor}`) + response.on('data', (chunk) => { + console.log(`BODY: ${chunk}`) + }) + response.on('end', () => { + console.log('No more data in response.') + }) + response.on('error', () => { + console.log('"error" event emitted') + }) + response.on('aborted', () => { + console.log('"aborted" event emitted') + }) + }) + request.on('login', (authInfo, callback) => { + callback('username', 'password') + }) + request.on('finish', () => { + console.log('"finish" event emitted') + }) + request.on('abort', () => { + console.log('"abort" event emitted') + }) + request.on('error', () => { + console.log('"error" event emitted') + }) + request.write('Hello World!', 'utf-8') + request.end('Hello World!', 'utf-8') + request.abort() +}) + +// power-monitor +// https://github.com/atom/electron/blob/master/docs/api/power-monitor.md + +app.on('ready', () => { + powerMonitor.on('suspend', () => { + console.log('The system is going to sleep') + }) + powerMonitor.on('resume', () => { + console.log('The system has resumed from sleep') + }) + powerMonitor.on('on-ac', () => { + console.log('The system changed to AC power') + }) + powerMonitor.on('on-battery', () => { + console.log('The system changed to battery power') + }) +}) + +// power-save-blocker +// https://github.com/atom/electron/blob/master/docs/api/power-save-blocker.md + +const id = powerSaveBlocker.start('prevent-display-sleep') +console.log(powerSaveBlocker.isStarted(id)) + +powerSaveBlocker.stop(id) + +// protocol +// https://github.com/atom/electron/blob/master/docs/api/protocol.md + +app.on('ready', () => { + protocol.registerSchemesAsPrivileged([{ scheme: 'https', privileges: { standard: true, allowServiceWorkers: true } }]) + + protocol.registerFileProtocol('atom', (request, callback) => { + callback(`${__dirname}/${request.url}`) + }) + + protocol.registerBufferProtocol('atom', (request, callback) => { + callback({ mimeType: 'text/html', data: new Buffer('
Response
') }) + }) + + protocol.registerStringProtocol('atom', (request, callback) => { + callback('Hello World!') + }) + + protocol.registerHttpProtocol('atom', (request, callback) => { + callback({ url: request.url, method: request.method }) + }) + + protocol.unregisterProtocol('atom', (error) => { + console.log(error ? error.message : 'ok') + }) + + protocol.isProtocolHandled('atom', (handled) => { + console.log(handled) + }) +}) + +// tray +// https://github.com/atom/electron/blob/master/docs/api/tray.md + +let appIcon: Electron.Tray = null +app.on('ready', () => { + appIcon = new Tray('/path/to/my/icon') + const contextMenu = Menu.buildFromTemplate([ + { label: 'Item1', type: 'radio' }, + { label: 'Item2', type: 'radio' }, + { label: 'Item3', type: 'radio', checked: true }, + { label: 'Item4', type: 'radio' } + ]) + appIcon.setToolTip('This is my application.') + appIcon.setContextMenu(contextMenu) + appIcon.setImage('/path/to/new/icon') + appIcon.popUpContextMenu(contextMenu, { x: 100, y: 100 }) + + appIcon.on('click', (event, bounds) => { + console.log('click', event, bounds) + }) + + appIcon.on('balloon-show', () => { + console.log('balloon-show') + }) + + appIcon.displayBalloon({ + title: 'Hello World!', + content: 'This the the balloon content.' + }) +}) + +// clipboard +// https://github.com/atom/electron/blob/master/docs/api/clipboard.md + +{ + let str: string + clipboard.writeText('Example String') + clipboard.writeText('Example String', 'selection') + clipboard.writeBookmark('foo', 'http://example.com') + clipboard.writeBookmark('foo', 'http://example.com', 'selection') + clipboard.writeFindText('foo') + str = clipboard.readText('selection') + str = clipboard.readFindText() + console.log(clipboard.availableFormats()) + console.log(clipboard.readBookmark().title) + clipboard.clear() + + clipboard.write({ + html: '', + text: 'Hello World!', + image: clipboard.readImage() + }) +} + +// crash-reporter +// https://github.com/atom/electron/blob/master/docs/api/crash-reporter.md + +crashReporter.start({ + productName: 'YourName', + companyName: 'YourCompany', + submitURL: 'https://your-domain.com/url-to-submit', + uploadToServer: true, + extra: { + someKey: 'value' + } +}) + +console.log(crashReporter.getLastCrashReport()) +console.log(crashReporter.getUploadedReports()) + +// nativeImage +// https://github.com/atom/electron/blob/master/docs/api/native-image.md + +const appIcon2 = new Tray('/Users/somebody/images/icon.png') +const window2 = new BrowserWindow({ icon: '/Users/somebody/images/window.png' }) +const image = clipboard.readImage() +const appIcon3 = new Tray(image) +const appIcon4 = new Tray('/Users/somebody/images/icon.png') + +const image2 = nativeImage.createFromPath('/Users/somebody/images/icon.png') + +// process +// https://github.com/electron/electron/blob/master/docs/api/process.md + +console.log(process.versions.electron) +console.log(process.versions.chrome) +console.log(process.type) +console.log(process.resourcesPath) +console.log(process.mas) +console.log(process.windowsStore) +process.noAsar = true +process.crash() +process.hang() +process.setFdLimit(8192) + +// screen +// https://github.com/atom/electron/blob/master/docs/api/screen.md + +app.on('ready', () => { + const size = screen.getPrimaryDisplay().workAreaSize + mainWindow = new BrowserWindow({ width: size.width, height: size.height }) +}) + +app.on('ready', () => { + const displays = screen.getAllDisplays() + let externalDisplay: any = null + for (const i in displays) { + if (displays[i].bounds.x > 0 || displays[i].bounds.y > 0) { + externalDisplay = displays[i] + break + } + } + + if (externalDisplay) { + mainWindow = new BrowserWindow({ + x: externalDisplay.bounds.x + 50, + y: externalDisplay.bounds.y + 50 + }) + } + + screen.on('display-added', (event, display) => { + console.log('display-added', display) + }) + + screen.on('display-removed', (event, display) => { + console.log('display-removed', display) + }) + + screen.on('display-metrics-changed', (event, display, changes) => { + console.log('display-metrics-changed', display, changes) + }) +}) + +// shell +// https://github.com/atom/electron/blob/master/docs/api/shell.md + +shell.showItemInFolder('/home/user/Desktop/test.txt') +shell.openItem('/home/user/Desktop/test.txt') +shell.moveItemToTrash('/home/user/Desktop/test.txt') + +shell.openExternal('https://github.com', { + activate: false +}) + +shell.beep() + +shell.writeShortcutLink('/home/user/Desktop/shortcut.lnk', 'update', shell.readShortcutLink('/home/user/Desktop/shortcut.lnk')) + +// cookies +// https://github.com/atom/electron/blob/master/docs/api/cookies.md +{ + const { session } = require('electron') + + // Query all cookies. + session.defaultSession.cookies.get({}) + .then((cookies) => { + console.log(cookies) + }).catch((error) => { + console.log(error) + }) + + // Query all cookies associated with a specific url. + session.defaultSession.cookies.get({ url: 'http://www.github.com' }) + .then((cookies) => { + console.log(cookies) + }).catch((error) => { + console.log(error) + }) + + // Set a cookie with the given cookie data; + // may overwrite equivalent cookies if they exist. + const cookie = { url: 'http://www.github.com', name: 'dummy_name', value: 'dummy' } + session.defaultSession.cookies.set(cookie) + .then(() => { + // success + }, (error) => { + console.error(error) + }) +} + +// session +// https://github.com/atom/electron/blob/master/docs/api/session.md + +session.defaultSession.on('will-download', (event, item, webContents) => { + event.preventDefault() + require('request')(item.getURL(), (data: any) => { + require('fs').writeFileSync('/somewhere', data) + }) +}) + +// In the main process. +session.defaultSession.on('will-download', (event, item, webContents) => { + // Set the save path, making Electron not to prompt a save dialog. + item.setSavePath('/tmp/save.pdf') + console.log(item.getSavePath()) + console.log(item.getMimeType()) + console.log(item.getFilename()) + console.log(item.getTotalBytes()) + + item.on('updated', (_event, state) => { + if (state === 'interrupted') { + console.log('Download is interrupted but can be resumed') + } else if (state === 'progressing') { + if (item.isPaused()) { + console.log('Download is paused') + } else { + console.log(`Received bytes: ${item.getReceivedBytes()}`) + } + } + }) + + item.on('done', function (e, state) { + if (state === 'completed') { + console.log('Download successfully') + } else { + console.log(`Download failed: ${state}`) + } + }) +}) + +// To emulate a GPRS connection with 50kbps throughput and 500 ms latency. +session.defaultSession.enableNetworkEmulation({ + latency: 500, + downloadThroughput: 6400, + uploadThroughput: 6400 +}) + +// To emulate a network outage. +session.defaultSession.enableNetworkEmulation({ + offline: true +}) + +session.defaultSession.setCertificateVerifyProc((request, callback) => { + const { hostname } = request + if (hostname === 'github.com') { + callback(0) + } else { + callback(-2) + } +}) + +session.defaultSession.setPermissionRequestHandler(function (webContents, permission, callback) { + if (webContents.getURL() === 'github.com') { + if (permission === 'notifications') { + callback(false) + return + } + } + + callback(true) +}) + +// consider any url ending with `example.com`, `foobar.com`, `baz` +// for integrated authentication. +session.defaultSession.allowNTLMCredentialsForDomains('*example.com, *foobar.com, *baz') + +// consider all urls for integrated authentication. +session.defaultSession.allowNTLMCredentialsForDomains('*') + +// Modify the user agent for all requests to the following urls. +const filter = { + urls: ['https://*.github.com/*', '*://electron.github.io'] +} + +session.defaultSession.webRequest.onBeforeSendHeaders(filter, function (details: any, callback: any) { + details.requestHeaders['User-Agent'] = 'MyAgent' + callback({ cancel: false, requestHeaders: details.requestHeaders }) +}) + +app.on('ready', function () { + const protocol = session.defaultSession.protocol + protocol.registerFileProtocol('atom', function (request, callback) { + const url = request.url.substr(7) + callback(path.normalize(__dirname + '/' + url)) + }, function (error) { + if (error) { + console.error('Failed to register protocol') + } + }) +}) + +// webContents +// https://github.com/electron/electron/blob/master/docs/api/web-contents.md + +console.log(webContents.getAllWebContents()) +console.log(webContents.getFocusedWebContents()) + +const win4 = new BrowserWindow({ + webPreferences: { + offscreen: true + } +}) + +win4.webContents.on('paint', (event, dirty, _image) => { + console.log(dirty, _image.getBitmap()) +}) + +win4.loadURL('http://github.com') + +const unusedTouchBar = new TouchBar({ + items: [ + new TouchBar.TouchBarButton({ label: '' }), + new TouchBar.TouchBarLabel({ label: '' }) + ] +}) diff --git a/spec/ts-smoke/electron/renderer.ts b/spec/ts-smoke/electron/renderer.ts new file mode 100644 index 000000000000..5bd601320d7c --- /dev/null +++ b/spec/ts-smoke/electron/renderer.ts @@ -0,0 +1,266 @@ + +import { + ipcRenderer, + remote, + webFrame, + clipboard, + crashReporter, + nativeImage, + screen, + shell +} from 'electron' + +import * as fs from 'fs' + +// In renderer process (web page). +// https://github.com/atom/electron/blob/master/docs/api/ipc-renderer.md +console.log(ipcRenderer.sendSync('synchronous-message', 'ping')) // prints "pong" + +ipcRenderer.on('asynchronous-reply', (event: Electron.Event, arg: any) => { + console.log(arg) // prints "pong" + event.sender.send('another-message', 'Hello World!') +}) + +ipcRenderer.send('asynchronous-message', 'ping') + +// remote +// https://github.com/atom/electron/blob/master/docs/api/remote.md + +const BrowserWindow = remote.BrowserWindow +const win = new BrowserWindow({ width: 800, height: 600 }) +win.loadURL('https://github.com') + +remote.getCurrentWindow().on('close', () => { + // blabla... +}) + +remote.getCurrentWindow().capturePage(buf => { + fs.writeFile('/tmp/screenshot.png', buf, err => { + console.log(err) + }) +}) + +remote.getCurrentWebContents().print() + +remote.getCurrentWindow().capturePage(buf => { + remote.require('fs').writeFile('/tmp/screenshot.png', buf, (err: Error) => { + console.log(err) + }) +}) + +// web-frame +// https://github.com/atom/electron/blob/master/docs/api/web-frame.md + +webFrame.setZoomFactor(2) +console.log(webFrame.getZoomFactor()) + +webFrame.setZoomLevel(200) +console.log(webFrame.getZoomLevel()) + +webFrame.setVisualZoomLevelLimits(50, 200) +webFrame.setLayoutZoomLevelLimits(50, 200) + +webFrame.setSpellCheckProvider('en-US', { + spellCheck (words, callback) { + setTimeout(() => { + const spellchecker = require('spellchecker') + const misspelled = words.filter(x => spellchecker.isMisspelled(x)) + callback(misspelled) + }, 0) + } +}) + +webFrame.insertText('text') + +webFrame.executeJavaScript('JSON.stringify({})', false, (result) => { + console.log(result) +}).then((result: string) => console.log('OK:' + result)) + +console.log(webFrame.getResourceUsage()) +webFrame.clearCache() + +// clipboard +// https://github.com/atom/electron/blob/master/docs/api/clipboard.md + +clipboard.writeText('Example String') +clipboard.writeText('Example String', 'selection') +console.log(clipboard.readText('selection')) +console.log(clipboard.availableFormats()) +clipboard.clear() + +clipboard.write({ + html: '', + text: 'Hello World!', + bookmark: 'Bookmark name', + image: clipboard.readImage() +}) + +// crash-reporter +// https://github.com/atom/electron/blob/master/docs/api/crash-reporter.md + +crashReporter.start({ + productName: 'YourName', + companyName: 'YourCompany', + submitURL: 'https://your-domain.com/url-to-submit', + uploadToServer: true +}) + +// desktopCapturer +// https://github.com/atom/electron/blob/master/docs/api/desktop-capturer.md + +const desktopCapturer = require('electron').desktopCapturer + +desktopCapturer.getSources({ types: ['window', 'screen'] }, function (error, sources) { + if (error) throw error + for (let i = 0; i < sources.length; ++i) { + if (sources[i].name == 'Electron') { + (navigator as any).webkitGetUserMedia({ + audio: false, + video: { + mandatory: { + chromeMediaSource: 'desktop', + chromeMediaSourceId: sources[i].id, + minWidth: 1280, + maxWidth: 1280, + minHeight: 720, + maxHeight: 720 + } + } + }, gotStream, getUserMediaError) + return + } + } +}) + +function gotStream (stream: any) { + (document.querySelector('video') as HTMLVideoElement).src = URL.createObjectURL(stream) +} + +function getUserMediaError (error: Error) { + console.log('getUserMediaError', error) +} + +// File object +// https://github.com/atom/electron/blob/master/docs/api/file-object.md + +/* +
+ Drag your file here +
+*/ + +const holder = document.getElementById('holder') + +holder.ondragover = function () { + return false +} + +holder.ondragleave = holder.ondragend = function () { + return false +} + +holder.ondrop = function (e) { + e.preventDefault() + const file = e.dataTransfer.files[0] + console.log('File you dragged here is', file.path) + return false +} + +// nativeImage +// https://github.com/atom/electron/blob/master/docs/api/native-image.md + +const Tray = remote.Tray +const appIcon2 = new Tray('/Users/somebody/images/icon.png') +const window2 = new BrowserWindow({ icon: '/Users/somebody/images/window.png' }) +const image = clipboard.readImage() +const appIcon3 = new Tray(image) +const appIcon4 = new Tray('/Users/somebody/images/icon.png') + +// https://github.com/electron/electron/blob/master/docs/api/process.md + +// preload.js +const _setImmediate = setImmediate +const _clearImmediate = clearImmediate +process.once('loaded', function () { + global.setImmediate = _setImmediate + global.clearImmediate = _clearImmediate +}) + +// screen +// https://github.com/atom/electron/blob/master/docs/api/screen.md + +const app = remote.app + +let mainWindow: Electron.BrowserWindow = null + +app.on('ready', () => { + const size = screen.getPrimaryDisplay().workAreaSize + mainWindow = new BrowserWindow({ width: size.width, height: size.height }) +}) + +app.on('ready', () => { + const displays = screen.getAllDisplays() + let externalDisplay: any = null + for (const i in displays) { + if (displays[i].bounds.x > 0 || displays[i].bounds.y > 0) { + externalDisplay = displays[i] + break + } + } + + if (externalDisplay) { + mainWindow = new BrowserWindow({ + x: externalDisplay.bounds.x + 50, + y: externalDisplay.bounds.y + 50 + }) + } +}) + +// shell +// https://github.com/atom/electron/blob/master/docs/api/shell.md + +shell.openExternal('https://github.com') + +// +// https://github.com/atom/electron/blob/master/docs/api/web-view-tag.md + +const webview = document.createElement('webview') +webview.loadURL('https://github.com') + +webview.addEventListener('console-message', function (e) { + console.log('Guest page logged a message:', e.message) +}) + +webview.addEventListener('found-in-page', function (e) { + if (e.result.finalUpdate) { + webview.stopFindInPage('keepSelection') + } +}) + +const requestId = webview.findInPage('test') + +webview.addEventListener('new-window', function (e) { + require('electron').shell.openExternal(e.url) +}) + +webview.addEventListener('close', function () { + webview.src = 'about:blank' +}) + +// In embedder page. +webview.addEventListener('ipc-message', function (event) { + console.log(event.channel) // Prints "pong" +}) +webview.send('ping') +webview.capturePage((image) => { console.log(image) }) + +{ + const opened: boolean = webview.isDevToolsOpened() + const focused: boolean = webview.isDevToolsFocused() + const focused2: boolean = webview.getWebContents().isFocused() +} + +// In guest page. +ipcRenderer.on('ping', function () { + ipcRenderer.sendToHost('pong') +}) diff --git a/spec/ts-smoke/runner.js b/spec/ts-smoke/runner.js new file mode 100644 index 000000000000..32b6a1c02e88 --- /dev/null +++ b/spec/ts-smoke/runner.js @@ -0,0 +1,18 @@ +const path = require('path') +const childProcess = require('child_process') + +const typeCheck = () => { + const tscExec = path.resolve(require.resolve('typescript'), '../../bin/tsc') + const tscChild = childProcess.spawn(process.execPath, [tscExec, '--project', './ts-smoke/tsconfig.json'], { + cwd: path.resolve(__dirname, '../') + }) + tscChild.stdout.on('data', d => console.log(d.toString())) + tscChild.stderr.on('data', d => console.error(d.toString())) + tscChild.on('exit', (tscStatus) => { + if (tscStatus !== 0) { + process.exit(tscStatus) + } + }) +} + +typeCheck() diff --git a/spec/ts-smoke/tsconfig.json b/spec/ts-smoke/tsconfig.json new file mode 100644 index 000000000000..4ab8887665c8 --- /dev/null +++ b/spec/ts-smoke/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "module": "commonjs", + "lib": [ + "es6", + "dom" + ], + "noImplicitAny": true, + "noImplicitThis": true, + "strictNullChecks": false, + "noEmit": true, + "forceConsistentCasingInFileNames": true + }, + "files": [ + "electron/main.ts", + "electron/renderer.ts", + "../../electron.d.ts" + ] +} \ No newline at end of file