From c365aa85c2887f5dfd7d1f1c74bfab9f5a207bc6 Mon Sep 17 00:00:00 2001 From: Luke Page Date: Mon, 25 Jul 2016 13:57:38 +0200 Subject: [PATCH 01/74] Clarify the default arguments for popup The null / -1 defaults weren't very helpful, so changed to follow other documentation and write the behaviour if that param is missing. --- docs/api/menu.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/docs/api/menu.md b/docs/api/menu.md index 5a405ea12ba2..c60cbbac7ffe 100644 --- a/docs/api/menu.md +++ b/docs/api/menu.md @@ -227,16 +227,14 @@ The `menu` object has the following instance methods: #### `menu.popup([browserWindow, x, y, positioningItem])` -* `browserWindow` BrowserWindow (optional) - Default is `null`. -* `x` Number (optional) - Default is -1. -* `y` Number (**required** if `x` is used) - Default is -1. +* `browserWindow` BrowserWindow (optional) - Default is `BrowserWindow.getFocusedWindow()`. +* `x` Number (optional) - Default is the current mouse cursor position. +* `y` Number (**required** if `x` is used) - Default is the current mouse cursor position. * `positioningItem` Number (optional) _macOS_ - The index of the menu item to be positioned under the mouse cursor at the specified coordinates. Default is -1. -Pops up this menu as a context menu in the `browserWindow`. You can optionally -provide a `x, y` coordinate to place the menu at, otherwise it will be placed -at the current mouse cursor position. +Pops up this menu as a context menu in the `browserWindow`. #### `menu.append(menuItem)` From 2cc01eea30f1694758942d72ccd17ddac5765258 Mon Sep 17 00:00:00 2001 From: liusi Date: Mon, 25 Jul 2016 22:51:27 +0800 Subject: [PATCH 02/74] issue 6574 - Window position doesn't restore correctly --- atom/browser/native_window_views_win.cc | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/atom/browser/native_window_views_win.cc b/atom/browser/native_window_views_win.cc index 9af003d55db0..2da0293f341a 100644 --- a/atom/browser/native_window_views_win.cc +++ b/atom/browser/native_window_views_win.cc @@ -121,7 +121,10 @@ bool NativeWindowViews::PreHandleMSG( ::GetWindowRect(GetAcceleratedWidget(), (LPRECT)l_param); return false; } - + case WM_MOVE: { + last_normal_bounds_ = GetBounds(); + return false; + } default: return false; } From a518c47f4c8de23eb8ca35c86542c8cf1f50b0cc Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 25 Jul 2016 10:07:39 -0700 Subject: [PATCH 03/74] Use let/const instead of var --- lib/common/asar.js | 280 ++++++++++++++++++++++----------------------- 1 file changed, 137 insertions(+), 143 deletions(-) diff --git a/lib/common/asar.js b/lib/common/asar.js index 6adc24e8ded0..40b7981da91e 100644 --- a/lib/common/asar.js +++ b/lib/common/asar.js @@ -4,14 +4,13 @@ const path = require('path') const util = require('util') - var hasProp = {}.hasOwnProperty + const hasProp = {}.hasOwnProperty // Cache asar archive objects. - var cachedArchives = {} + const cachedArchives = {} - var getOrCreateArchive = function (p) { - var archive - archive = cachedArchives[p] + const getOrCreateArchive = function (p) { + let archive = cachedArchives[p] if (archive != null) { return archive } @@ -25,18 +24,14 @@ // Clean cache on quit. process.on('exit', function () { - var archive, p - for (p in cachedArchives) { + for (let p in cachedArchives) { if (!hasProp.call(cachedArchives, p)) continue - archive = cachedArchives[p] - archive.destroy() + cachedArchives[p].destroy() } }) // Separate asar package's path from full path. - var splitPath = function (p) { - var index - + const splitPath = function (p) { // shortcut to disable asar. if (process.noAsar) { return [false] @@ -45,11 +40,13 @@ if (typeof p !== 'string') { return [false] } + if (p.substr(-5) === '.asar') { return [true, p, ''] } + p = path.normalize(p) - index = p.lastIndexOf('.asar' + path.sep) + const index = p.lastIndexOf('.asar' + path.sep) if (index === -1) { return [false] } @@ -57,15 +54,15 @@ } // Convert asar archive's Stats object to fs's Stats object. - var nextInode = 0 + let nextInode = 0 - var uid = process.getuid != null ? process.getuid() : 0 + const uid = process.getuid != null ? process.getuid() : 0 - var gid = process.getgid != null ? process.getgid() : 0 + const gid = process.getgid != null ? process.getgid() : 0 - var fakeTime = new Date() + const fakeTime = new Date() - var asarStatsToFsStats = function (stats) { + const asarStatsToFsStats = function (stats) { return { dev: 1, ino: ++nextInode, @@ -104,97 +101,97 @@ } // Create a ENOENT error. - var notFoundError = function (asarPath, filePath, callback) { - var error - error = new Error(`ENOENT, ${filePath} not found in ${asarPath}`) + const notFoundError = function (asarPath, filePath, callback) { + const error = new Error(`ENOENT, ${filePath} not found in ${asarPath}`) error.code = 'ENOENT' error.errno = -2 if (typeof callback !== 'function') { throw error } - return process.nextTick(function () { - return callback(error) + process.nextTick(function () { + callback(error) }) } // Create a ENOTDIR error. - var notDirError = function (callback) { - var error - error = new Error('ENOTDIR, not a directory') + const notDirError = function (callback) { + const error = new Error('ENOTDIR, not a directory') error.code = 'ENOTDIR' error.errno = -20 if (typeof callback !== 'function') { throw error } - return process.nextTick(function () { - return callback(error) + process.nextTick(function () { + callback(error) }) } // Create invalid archive error. - var invalidArchiveError = function (asarPath, callback) { - var error - error = new Error(`Invalid package ${asarPath}`) + const invalidArchiveError = function (asarPath, callback) { + const error = new Error(`Invalid package ${asarPath}`) if (typeof callback !== 'function') { throw error } - return process.nextTick(function () { - return callback(error) + process.nextTick(function () { + callback(error) }) } // Override APIs that rely on passing file path instead of content to C++. - var overrideAPISync = function (module, name, arg) { - var old + const overrideAPISync = function (module, name, arg) { if (arg == null) { arg = 0 } - old = module[name] + const old = module[name] module[name] = function () { - var archive, newPath, p - p = arguments[arg] + const p = arguments[arg] const [isAsar, asarPath, filePath] = splitPath(p) if (!isAsar) { return old.apply(this, arguments) } - archive = getOrCreateArchive(asarPath) + + const archive = getOrCreateArchive(asarPath) if (!archive) { invalidArchiveError(asarPath) } - newPath = archive.copyFileOut(filePath) + + const newPath = archive.copyFileOut(filePath) if (!newPath) { notFoundError(asarPath, filePath) } + arguments[arg] = newPath return old.apply(this, arguments) } } - var overrideAPI = function (module, name, arg) { - var old + const overrideAPI = function (module, name, arg) { if (arg == null) { arg = 0 } - old = module[name] + const old = module[name] module[name] = function () { - var archive, callback, newPath, p - p = arguments[arg] + const p = arguments[arg] const [isAsar, asarPath, filePath] = splitPath(p) if (!isAsar) { return old.apply(this, arguments) } - callback = arguments[arguments.length - 1] + + const callback = arguments[arguments.length - 1] if (typeof callback !== 'function') { return overrideAPISync(module, name, arg) } - archive = getOrCreateArchive(asarPath) + + const archive = getOrCreateArchive(asarPath) if (!archive) { return invalidArchiveError(asarPath, callback) } - newPath = archive.copyFileOut(filePath) + + const newPath = archive.copyFileOut(filePath) if (!newPath) { return notFoundError(asarPath, filePath, callback) } + arguments[arg] = newPath return old.apply(this, arguments) } @@ -202,61 +199,58 @@ // Override fs APIs. exports.wrapFsWithAsar = function (fs) { - var exists, existsSync, internalModuleReadFile, internalModuleStat, lstat, lstatSync, mkdir, mkdirSync, readFile, readFileSync, readdir, readdirSync, realpath, realpathSync, stat, statSync, statSyncNoException, logFDs, logASARAccess - - logFDs = {} - logASARAccess = function (asarPath, filePath, offset) { + const logFDs = {} + const logASARAccess = function (asarPath, filePath, offset) { if (!process.env.ELECTRON_LOG_ASAR_READS) { return } if (!logFDs[asarPath]) { - var logFilename, logPath const path = require('path') - logFilename = path.basename(asarPath, '.asar') + '-access-log.txt' - logPath = path.join(require('os').tmpdir(), logFilename) + const logFilename = path.basename(asarPath, '.asar') + '-access-log.txt' + const logPath = path.join(require('os').tmpdir(), logFilename) logFDs[asarPath] = fs.openSync(logPath, 'a') console.log('Logging ' + asarPath + ' access to ' + logPath) } fs.writeSync(logFDs[asarPath], offset + ': ' + filePath + '\n') } - lstatSync = fs.lstatSync + const {lstatSync} = fs fs.lstatSync = function (p) { - var archive, stats const [isAsar, asarPath, filePath] = splitPath(p) if (!isAsar) { return lstatSync(p) } - archive = getOrCreateArchive(asarPath) + const archive = getOrCreateArchive(asarPath) if (!archive) { invalidArchiveError(asarPath) } - stats = archive.stat(filePath) + const stats = archive.stat(filePath) if (!stats) { notFoundError(asarPath, filePath) } return asarStatsToFsStats(stats) } - lstat = fs.lstat + + const {lstat} = fs fs.lstat = function (p, callback) { - var archive, stats const [isAsar, asarPath, filePath] = splitPath(p) if (!isAsar) { return lstat(p, callback) } - archive = getOrCreateArchive(asarPath) + const archive = getOrCreateArchive(asarPath) if (!archive) { return invalidArchiveError(asarPath, callback) } - stats = getOrCreateArchive(asarPath).stat(filePath) + const stats = getOrCreateArchive(asarPath).stat(filePath) if (!stats) { return notFoundError(asarPath, filePath, callback) } - return process.nextTick(function () { - return callback(null, asarStatsToFsStats(stats)) + process.nextTick(function () { + callback(null, asarStatsToFsStats(stats)) }) } - statSync = fs.statSync + + const {statSync} = fs fs.statSync = function (p) { const [isAsar] = splitPath(p) if (!isAsar) { @@ -266,7 +260,8 @@ // Do not distinguish links for now. return fs.lstatSync(p) } - stat = fs.stat + + const {stat} = fs fs.stat = function (p, callback) { const [isAsar] = splitPath(p) if (!isAsar) { @@ -274,47 +269,47 @@ } // Do not distinguish links for now. - return process.nextTick(function () { - return fs.lstat(p, callback) + process.nextTick(function () { + fs.lstat(p, callback) }) } - statSyncNoException = fs.statSyncNoException + + const {statSyncNoException} = fs fs.statSyncNoException = function (p) { - var archive, stats const [isAsar, asarPath, filePath] = splitPath(p) if (!isAsar) { return statSyncNoException(p) } - archive = getOrCreateArchive(asarPath) + const archive = getOrCreateArchive(asarPath) if (!archive) { return false } - stats = archive.stat(filePath) + const stats = archive.stat(filePath) if (!stats) { return false } return asarStatsToFsStats(stats) } - realpathSync = fs.realpathSync + + const {realpathSync} = fs fs.realpathSync = function (p) { - var archive, real const [isAsar, asarPath, filePath] = splitPath(p) if (!isAsar) { return realpathSync.apply(this, arguments) } - archive = getOrCreateArchive(asarPath) + const archive = getOrCreateArchive(asarPath) if (!archive) { invalidArchiveError(asarPath) } - real = archive.realpath(filePath) + const real = archive.realpath(filePath) if (real === false) { notFoundError(asarPath, filePath) } return path.join(realpathSync(asarPath), real) } - realpath = fs.realpath + + const {realpath} = fs fs.realpath = function (p, cache, callback) { - var archive, real const [isAsar, asarPath, filePath] = splitPath(p) if (!isAsar) { return realpath.apply(this, arguments) @@ -323,11 +318,11 @@ callback = cache cache = void 0 } - archive = getOrCreateArchive(asarPath) + const archive = getOrCreateArchive(asarPath) if (!archive) { return invalidArchiveError(asarPath, callback) } - real = archive.realpath(filePath) + const real = archive.realpath(filePath) if (real === false) { return notFoundError(asarPath, filePath, callback) } @@ -338,37 +333,37 @@ return callback(null, path.join(p, real)) }) } - exists = fs.exists + + const {exists} = fs fs.exists = function (p, callback) { - var archive const [isAsar, asarPath, filePath] = splitPath(p) if (!isAsar) { return exists(p, callback) } - archive = getOrCreateArchive(asarPath) + const archive = getOrCreateArchive(asarPath) if (!archive) { return invalidArchiveError(asarPath, callback) } - return process.nextTick(function () { - return callback(archive.stat(filePath) !== false) + process.nextTick(function () { + callback(archive.stat(filePath) !== false) }) } - existsSync = fs.existsSync + + const {existsSync} = fs fs.existsSync = function (p) { - var archive const [isAsar, asarPath, filePath] = splitPath(p) if (!isAsar) { return existsSync(p) } - archive = getOrCreateArchive(asarPath) + const archive = getOrCreateArchive(asarPath) if (!archive) { return false } return archive.stat(filePath) !== false } - readFile = fs.readFile + + const {readFile} = fs fs.readFile = function (p, options, callback) { - var archive, buffer, encoding, fd, info, realPath const [isAsar, asarPath, filePath] = splitPath(p) if (!isAsar) { return readFile.apply(this, arguments) @@ -377,21 +372,21 @@ callback = options options = void 0 } - archive = getOrCreateArchive(asarPath) + const archive = getOrCreateArchive(asarPath) if (!archive) { return invalidArchiveError(asarPath, callback) } - info = archive.getFileInfo(filePath) + const info = archive.getFileInfo(filePath) if (!info) { return notFoundError(asarPath, filePath, callback) } if (info.size === 0) { return process.nextTick(function () { - return callback(null, new Buffer(0)) + callback(null, new Buffer(0)) }) } if (info.unpacked) { - realPath = archive.copyFileOut(filePath) + const realPath = archive.copyFileOut(filePath) return fs.readFile(realPath, options, callback) } if (!options) { @@ -405,31 +400,29 @@ } else if (!util.isObject(options)) { throw new TypeError('Bad arguments') } - encoding = options.encoding - buffer = new Buffer(info.size) - fd = archive.getFd() + const {encoding} = options + const buffer = new Buffer(info.size) + const fd = archive.getFd() if (!(fd >= 0)) { return notFoundError(asarPath, filePath, callback) } logASARAccess(asarPath, filePath, info.offset) - return fs.read(fd, buffer, 0, info.size, info.offset, function (error) { - return callback(error, encoding ? buffer.toString(encoding) : buffer) + fs.read(fd, buffer, 0, info.size, info.offset, function (error) { + callback(error, encoding ? buffer.toString(encoding) : buffer) }) } - readFileSync = fs.readFileSync - fs.readFileSync = function (p, opts) { - // this allows v8 to optimize this function - var archive, buffer, encoding, fd, info, options, realPath - options = opts + + const {readFileSync} = fs + fs.readFileSync = function (p, options) { const [isAsar, asarPath, filePath] = splitPath(p) if (!isAsar) { return readFileSync.apply(this, arguments) } - archive = getOrCreateArchive(asarPath) + const archive = getOrCreateArchive(asarPath) if (!archive) { invalidArchiveError(asarPath) } - info = archive.getFileInfo(filePath) + const info = archive.getFileInfo(filePath) if (!info) { notFoundError(asarPath, filePath) } @@ -441,7 +434,7 @@ } } if (info.unpacked) { - realPath = archive.copyFileOut(filePath) + const realPath = archive.copyFileOut(filePath) return fs.readFileSync(realPath, options) } if (!options) { @@ -455,9 +448,9 @@ } else if (!util.isObject(options)) { throw new TypeError('Bad arguments') } - encoding = options.encoding - buffer = new Buffer(info.size) - fd = archive.getFd() + const {encoding} = options + const buffer = new Buffer(info.size) + const fd = archive.getFd() if (!(fd >= 0)) { notFoundError(asarPath, filePath) } @@ -469,89 +462,89 @@ return buffer } } - readdir = fs.readdir + + const {readdir} = fs fs.readdir = function (p, callback) { - var archive, files const [isAsar, asarPath, filePath] = splitPath(p) if (!isAsar) { return readdir.apply(this, arguments) } - archive = getOrCreateArchive(asarPath) + const archive = getOrCreateArchive(asarPath) if (!archive) { return invalidArchiveError(asarPath, callback) } - files = archive.readdir(filePath) + const files = archive.readdir(filePath) if (!files) { return notFoundError(asarPath, filePath, callback) } - return process.nextTick(function () { - return callback(null, files) + process.nextTick(function () { + callback(null, files) }) } - readdirSync = fs.readdirSync + + const {readdirSync} = fs fs.readdirSync = function (p) { - var archive, files const [isAsar, asarPath, filePath] = splitPath(p) if (!isAsar) { return readdirSync.apply(this, arguments) } - archive = getOrCreateArchive(asarPath) + const archive = getOrCreateArchive(asarPath) if (!archive) { invalidArchiveError(asarPath) } - files = archive.readdir(filePath) + const files = archive.readdir(filePath) if (!files) { notFoundError(asarPath, filePath) } return files } - internalModuleReadFile = process.binding('fs').internalModuleReadFile + + const {internalModuleReadFile} = process.binding('fs') process.binding('fs').internalModuleReadFile = function (p) { - var archive, buffer, fd, info, realPath const [isAsar, asarPath, filePath] = splitPath(p) if (!isAsar) { return internalModuleReadFile(p) } - archive = getOrCreateArchive(asarPath) + const archive = getOrCreateArchive(asarPath) if (!archive) { - return void 0 + return } - info = archive.getFileInfo(filePath) + const info = archive.getFileInfo(filePath) if (!info) { - return void 0 + return } if (info.size === 0) { return '' } if (info.unpacked) { - realPath = archive.copyFileOut(filePath) + const realPath = archive.copyFileOut(filePath) return fs.readFileSync(realPath, { encoding: 'utf8' }) } - buffer = new Buffer(info.size) - fd = archive.getFd() + const buffer = new Buffer(info.size) + const fd = archive.getFd() if (!(fd >= 0)) { - return void 0 + return } logASARAccess(asarPath, filePath, info.offset) fs.readSync(fd, buffer, 0, info.size, info.offset) return buffer.toString('utf8') } - internalModuleStat = process.binding('fs').internalModuleStat + + const {internalModuleStat} = process.binding('fs') process.binding('fs').internalModuleStat = function (p) { - var archive, stats const [isAsar, asarPath, filePath] = splitPath(p) if (!isAsar) { return internalModuleStat(p) } - archive = getOrCreateArchive(asarPath) + const archive = getOrCreateArchive(asarPath) // -ENOENT if (!archive) { return -34 } - stats = archive.stat(filePath) + const stats = archive.stat(filePath) // -ENOENT if (!stats) { @@ -569,7 +562,7 @@ // This is to work around the recursive looping bug of mkdirp since it is // widely used. if (process.platform === 'win32') { - mkdir = fs.mkdir + const {mkdir} = fs fs.mkdir = function (p, mode, callback) { if (typeof mode === 'function') { callback = mode @@ -578,9 +571,10 @@ if (isAsar && filePath.length) { return notDirError(callback) } - return mkdir(p, mode, callback) + mkdir(p, mode, callback) } - mkdirSync = fs.mkdirSync + + const {mkdirSync} = fs fs.mkdirSync = function (p, mode) { const [isAsar, , filePath] = splitPath(p) if (isAsar && filePath.length) { @@ -595,12 +589,12 @@ // called by `childProcess.{exec,execSync}`, causing // Electron to consider the full command as a single path // to an archive. - [ 'exec', 'execSync' ].forEach(function (functionName) { - var old = childProcess[functionName] + ['exec', 'execSync'].forEach(function (functionName) { + const old = childProcess[functionName] childProcess[functionName] = function () { - var processNoAsarOriginalValue = process.noAsar + const processNoAsarOriginalValue = process.noAsar process.noAsar = true - var result = old.apply(this, arguments) + const result = old.apply(this, arguments) process.noAsar = processNoAsarOriginalValue return result } @@ -611,6 +605,6 @@ overrideAPISync(process, 'dlopen', 1) overrideAPISync(require('module')._extensions, '.node', 1) overrideAPISync(fs, 'openSync') - return overrideAPISync(childProcess, 'execFileSync') + overrideAPISync(childProcess, 'execFileSync') } })() From 3ad55041945c4a669df1601a168e830f1fee931c Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 25 Jul 2016 10:49:20 -0700 Subject: [PATCH 04/74] Add asar-supported fs.access implementation --- lib/common/asar.js | 47 ++++++++++++++++++++++++++++++++++++++++++++++ spec/asar-spec.js | 26 +++++++++++++++++++++++++ 2 files changed, 73 insertions(+) diff --git a/lib/common/asar.js b/lib/common/asar.js index 40b7981da91e..3b7860a044f9 100644 --- a/lib/common/asar.js +++ b/lib/common/asar.js @@ -126,6 +126,19 @@ }) } + // Create a EACCES error. + const accessError = function (asarPath, filePath, callback) { + const error = new Error(`EACCES: permission denied, access '${filePath}'`) + error.code = 'EACCES' + error.errno = -13 + if (typeof callback !== 'function') { + throw error + } + process.nextTick(function () { + callback(error) + }) + } + // Create invalid archive error. const invalidArchiveError = function (asarPath, callback) { const error = new Error(`Invalid package ${asarPath}`) @@ -362,6 +375,40 @@ return archive.stat(filePath) !== false } + const {access} = fs + fs.access = function (p, mode, callback) { + const [isAsar, asarPath, filePath] = splitPath(p) + if (!isAsar) { + return access.apply(this, arguments) + } + if (typeof mode === 'function') { + callback = mode + mode = fs.constants.F_OK + } + const archive = getOrCreateArchive(asarPath) + if (!archive) { + return invalidArchiveError(asarPath, callback) + } + const info = archive.getFileInfo(filePath) + if (!info) { + return notFoundError(asarPath, filePath, callback) + } + if (info.unpacked) { + const realPath = archive.copyFileOut(filePath) + return fs.access(realPath, mode, callback) + } + const stats = getOrCreateArchive(asarPath).stat(filePath) + if (!stats) { + return notFoundError(asarPath, filePath, callback) + } + if (mode & fs.constants.W_OK) { + return accessError(asarPath, filePath, callback) + } + process.nextTick(function () { + callback() + }) + } + const {readFile} = fs fs.readFile = function (p, options, callback) { const [isAsar, asarPath, filePath] = splitPath(p) diff --git a/spec/asar-spec.js b/spec/asar-spec.js index 376e5ef0805f..b88c3ec199ae 100644 --- a/spec/asar-spec.js +++ b/spec/asar-spec.js @@ -534,6 +534,32 @@ describe('asar package', function () { }) }) + describe('fs.access', function () { + it('throws an error when called with write mode', function (done) { + var p = path.join(fixtures, 'asar', 'a.asar', 'file1') + fs.access(p, fs.constants.R_OK | fs.constants.W_OK, function (err) { + assert.equal(err.code, 'EACCES') + done() + }) + }) + + it('throws an error when called on non-existent file', function (done) { + var p = path.join(fixtures, 'asar', 'a.asar', 'not-exist') + fs.access(p, function (err) { + assert.equal(err.code, 'ENOENT') + done() + }) + }) + + it('allows write mode for unpacked files', function (done) { + var p = path.join(fixtures, 'asar', 'unpack.asar', 'a.txt') + fs.access(p, fs.constants.R_OK | fs.constants.W_OK, function (err) { + assert(err == null) + done() + }) + }) + }) + describe('child_process.fork', function () { it('opens a normal js file', function (done) { var child = ChildProcess.fork(path.join(fixtures, 'asar', 'a.asar', 'ping.js')) From 30fbe92970a75a4ba472ae30f040cfc6a7958391 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 25 Jul 2016 11:05:18 -0700 Subject: [PATCH 05/74] Add asar-supported fs.accessSync implementation --- lib/common/asar.js | 30 ++++++++++++++++++++++++++++++ spec/asar-spec.js | 23 +++++++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/lib/common/asar.js b/lib/common/asar.js index 3b7860a044f9..ceb58df14d87 100644 --- a/lib/common/asar.js +++ b/lib/common/asar.js @@ -409,6 +409,36 @@ }) } + const {accessSync} = fs + fs.accessSync = function (p, mode) { + const [isAsar, asarPath, filePath] = splitPath(p) + if (!isAsar) { + return accessSync.apply(this, arguments) + } + if (mode == null) { + mode = fs.constants.F_OK + } + const archive = getOrCreateArchive(asarPath) + if (!archive) { + invalidArchiveError(asarPath) + } + const info = archive.getFileInfo(filePath) + if (!info) { + notFoundError(asarPath, filePath) + } + if (info.unpacked) { + const realPath = archive.copyFileOut(filePath) + return fs.accessSync(realPath, mode) + } + const stats = getOrCreateArchive(asarPath).stat(filePath) + if (!stats) { + notFoundError(asarPath, filePath) + } + if (mode & fs.constants.W_OK) { + accessError(asarPath, filePath) + } + } + const {readFile} = fs fs.readFile = function (p, options, callback) { const [isAsar, asarPath, filePath] = splitPath(p) diff --git a/spec/asar-spec.js b/spec/asar-spec.js index b88c3ec199ae..a8fc6a9ade4d 100644 --- a/spec/asar-spec.js +++ b/spec/asar-spec.js @@ -560,6 +560,29 @@ describe('asar package', function () { }) }) + describe('fs.accessSync', function () { + it('throws an error when called with write mode', function () { + var p = path.join(fixtures, 'asar', 'a.asar', 'file1') + assert.throws(function () { + fs.accessSync(p, fs.constants.R_OK | fs.constants.W_OK) + }, /EACCES/) + }) + + it('throws an error when called on non-existent file', function () { + var p = path.join(fixtures, 'asar', 'a.asar', 'not-exist') + assert.throws(function () { + fs.accessSync(p) + }, /ENOENT/) + }) + + it('allows write mode for unpacked files', function () { + var p = path.join(fixtures, 'asar', 'unpack.asar', 'a.txt') + assert.doesNotThrow(function () { + fs.accessSync(p, fs.constants.R_OK | fs.constants.W_OK) + }) + }) + }) + describe('child_process.fork', function () { it('opens a normal js file', function (done) { var child = ChildProcess.fork(path.join(fixtures, 'asar', 'a.asar', 'ping.js')) From 8eca0191573d2386ca6eb786c25423a2d7bf5907 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 25 Jul 2016 11:10:36 -0700 Subject: [PATCH 06/74] Support paths as Buffers --- lib/common/asar.js | 4 ++++ spec/asar-spec.js | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/lib/common/asar.js b/lib/common/asar.js index ceb58df14d87..fcb26ba97b71 100644 --- a/lib/common/asar.js +++ b/lib/common/asar.js @@ -37,6 +37,10 @@ return [false] } + if (Buffer.isBuffer(p)) { + p = p.toString() + } + if (typeof p !== 'string') { return [false] } diff --git a/spec/asar-spec.js b/spec/asar-spec.js index a8fc6a9ade4d..45278c302495 100644 --- a/spec/asar-spec.js +++ b/spec/asar-spec.js @@ -13,6 +13,11 @@ describe('asar package', function () { var fixtures = path.join(__dirname, 'fixtures') describe('node api', function () { + it('supports paths specified as a Buffer', function () { + var file = new Buffer(path.join(fixtures, 'asar', 'a.asar', 'file1')) + assert.equal(fs.existsSync(file), true) + }) + describe('fs.readFileSync', function () { it('does not leak fd', function () { var readCalls = 1 From b627b8711a3f570c5b349d64f184f795e84369f5 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 25 Jul 2016 11:17:40 -0700 Subject: [PATCH 07/74] Add spec for accessing normal files --- spec/asar-spec.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/spec/asar-spec.js b/spec/asar-spec.js index 45278c302495..81f6ebe2161d 100644 --- a/spec/asar-spec.js +++ b/spec/asar-spec.js @@ -540,6 +540,14 @@ describe('asar package', function () { }) describe('fs.access', function () { + it('accesses a normal file', function (done) { + var p = path.join(fixtures, 'asar', 'a.asar', 'file1') + fs.access(p, function (err) { + assert(err == null) + done() + }) + }) + it('throws an error when called with write mode', function (done) { var p = path.join(fixtures, 'asar', 'a.asar', 'file1') fs.access(p, fs.constants.R_OK | fs.constants.W_OK, function (err) { @@ -566,6 +574,13 @@ describe('asar package', function () { }) describe('fs.accessSync', function () { + it('accesses a normal file', function () { + var p = path.join(fixtures, 'asar', 'a.asar', 'file1') + assert.doesNotThrow(function () { + fs.accessSync(p) + }) + }) + it('throws an error when called with write mode', function () { var p = path.join(fixtures, 'asar', 'a.asar', 'file1') assert.throws(function () { From d6a7ced32cd28429d5ea9507f1e0077089ebd686 Mon Sep 17 00:00:00 2001 From: Feross Aboukhadijeh Date: Mon, 25 Jul 2016 15:13:17 -0700 Subject: [PATCH 08/74] MenuItem: Use 'Close Window' for 'close' role label On OS X, the standard label that's used for the 'close' role is 'Close Window'. You can see this in the default macOS apps from Apple. --- lib/browser/api/menu-item-roles.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/browser/api/menu-item-roles.js b/lib/browser/api/menu-item-roles.js index 6487911764e1..17447ffc66a2 100644 --- a/lib/browser/api/menu-item-roles.js +++ b/lib/browser/api/menu-item-roles.js @@ -7,7 +7,7 @@ const roles = { } }, close: { - label: 'Close', + label: process.platform === 'darwin' ? 'Close Window' : 'Close', accelerator: 'CommandOrControl+W', windowMethod: 'close' }, From 9bc144c8801804e79f30328def8fdae35f5b0772 Mon Sep 17 00:00:00 2001 From: Feross Aboukhadijeh Date: Mon, 25 Jul 2016 17:27:45 -0700 Subject: [PATCH 09/74] Fix test for PR #6600 Just realized that the tests don't pass after my PR. This fixes that :) --- spec/api-menu-spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/api-menu-spec.js b/spec/api-menu-spec.js index b5c707bfb377..4ea6d71cdb5a 100644 --- a/spec/api-menu-spec.js +++ b/spec/api-menu-spec.js @@ -399,7 +399,7 @@ describe('menu module', function () { describe('MenuItem role', function () { it('includes a default label and accelerator', function () { var item = new MenuItem({role: 'close'}) - assert.equal(item.label, 'Close') + assert.equal(item.label, process.platform === 'darwin' ? 'Close Window' : 'Close') assert.equal(item.accelerator, undefined) assert.equal(item.getDefaultRoleAccelerator(), 'CommandOrControl+W') From 20e26a9639705bebef5d3ca3debb3f72e89e5dbb Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Tue, 26 Jul 2016 11:40:55 +1000 Subject: [PATCH 10/74] Move auto-updater-win to ES6 --- .../api/auto-updater/auto-updater-win.js | 105 +++++++++--------- 1 file changed, 50 insertions(+), 55 deletions(-) diff --git a/lib/browser/api/auto-updater/auto-updater-win.js b/lib/browser/api/auto-updater/auto-updater-win.js index 564e2ff762a2..a010f76dd743 100644 --- a/lib/browser/api/auto-updater/auto-updater-win.js +++ b/lib/browser/api/auto-updater/auto-updater-win.js @@ -1,71 +1,66 @@ 'use strict' -const app = require('electron').app -const EventEmitter = require('events').EventEmitter +const {app} = require('electron') +const {EventEmitter} = require('events') const squirrelUpdate = require('./squirrel-update-win') -const util = require('util') -function AutoUpdater () { - EventEmitter.call(this) -} - -util.inherits(AutoUpdater, EventEmitter) - -AutoUpdater.prototype.quitAndInstall = function () { - if (!this.updateAvailable) { - return this.emitError('No update available, can\'t quit and install') - } - squirrelUpdate.processStart() - return app.quit() -} - -AutoUpdater.prototype.getFeedURL = function () { - return this.updateURL -} - -AutoUpdater.prototype.setFeedURL = function (updateURL, headers) { - this.updateURL = updateURL -} - -AutoUpdater.prototype.checkForUpdates = function () { - if (!this.updateURL) { - return this.emitError('Update URL is not set') - } - if (!squirrelUpdate.supported()) { - return this.emitError('Can not find Squirrel') - } - this.emit('checking-for-update') - squirrelUpdate.download(this.updateURL, (error, update) => { - if (error != null) { - return this.emitError(error) +class AutoUpdater extends EventEmitter { + quitAndInstall () { + if (!this.updateAvailable) { + return this.emitError('No update available, can\'t quit and install') } - if (update == null) { - this.updateAvailable = false - return this.emit('update-not-available') + squirrelUpdate.processStart() + return app.quit() + } + + getFeedURL () { + return this.updateURL + } + + setFeedURL (updateURL, headers) { + this.updateURL = updateURL + } + + checkForUpdates () { + if (!this.updateURL) { + return this.emitError('Update URL is not set') } - this.updateAvailable = true - this.emit('update-available') - squirrelUpdate.update(this.updateURL, (error) => { - var date, releaseNotes, version + if (!squirrelUpdate.supported()) { + return this.emitError('Can not find Squirrel') + } + this.emit('checking-for-update') + squirrelUpdate.download(this.updateURL, (error, update) => { if (error != null) { return this.emitError(error) } - releaseNotes = update.releaseNotes - version = update.version + if (update == null) { + this.updateAvailable = false + return this.emit('update-not-available') + } + this.updateAvailable = true + this.emit('update-available') + squirrelUpdate.update(this.updateURL, (error) => { + var date, releaseNotes, version + if (error != null) { + return this.emitError(error) + } + releaseNotes = update.releaseNotes + version = update.version - // Following information is not available on Windows, so fake them. - date = new Date() - this.emit('update-downloaded', {}, releaseNotes, version, date, this.updateURL, () => { - this.quitAndInstall() + // Following information is not available on Windows, so fake them. + date = new Date() + this.emit('update-downloaded', {}, releaseNotes, version, date, this.updateURL, () => { + this.quitAndInstall() + }) }) }) - }) -} + } -// Private: Emit both error object and message, this is to keep compatibility -// with Old APIs. -AutoUpdater.prototype.emitError = function (message) { - return this.emit('error', new Error(message), message) + // Private: Emit both error object and message, this is to keep compatibility + // with Old APIs. + emitError (message) { + return this.emit('error', new Error(message), message) + } } module.exports = new AutoUpdater() From 12dba2cb671ddf39d549951550fc1d486964a745 Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Tue, 26 Jul 2016 11:44:01 +1000 Subject: [PATCH 11/74] Remove all spaces from potential appUserModelID as it isn't allowed by the spec --- lib/browser/init.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/browser/init.js b/lib/browser/init.js index 68a555ad5858..37d1f4bb6360 100644 --- a/lib/browser/init.js +++ b/lib/browser/init.js @@ -93,8 +93,8 @@ if (process.platform === 'win32') { if (fs.statSyncNoException(updateDotExe)) { var packageDir = path.dirname(path.resolve(updateDotExe)) - var packageName = path.basename(packageDir) - var exeName = path.basename(process.execPath).replace(/\.exe$/i, '') + var packageName = path.basename(packageDir).replace(/ /g, '') + var exeName = path.basename(process.execPath).replace(/\.exe$/i, '').replace(/ /g, '') app.setAppUserModelId(`com.squirrel.${packageName}.${exeName}`) } From 89309244b5160870ec5450a7125026063e02fd2c Mon Sep 17 00:00:00 2001 From: Vadim Macagon Date: Mon, 25 Jul 2016 17:49:25 +0700 Subject: [PATCH 12/74] Improve the desktopCapturer docs Made minor improvements to readability, and added a link to the `navigator.getUserMedia` docs on MDN for convenience. --- docs/api/desktop-capturer.md | 64 ++++++++++++++++++++---------------- 1 file changed, 35 insertions(+), 29 deletions(-) diff --git a/docs/api/desktop-capturer.md b/docs/api/desktop-capturer.md index a72539a7a283..56b525a51989 100644 --- a/docs/api/desktop-capturer.md +++ b/docs/api/desktop-capturer.md @@ -1,7 +1,10 @@ # desktopCapturer -> List `getUserMedia` sources for capturing audio, video, and images from a -microphone, camera, or screen. +> Access information about media sources that can be used to capture audio and +> video from the desktop using the [`navigator.webkitGetUserMedia`] API. + +The following example shows how to capture video from a desktop window whose +title is `Electron`: ```javascript // In the renderer process. @@ -23,29 +26,28 @@ desktopCapturer.getSources({types: ['window', 'screen']}, (error, sources) => { maxHeight: 720 } } - }, gotStream, getUserMediaError); + }, handleStream, handleError); return; } } }); -function gotStream(stream) { +function handleStream(stream) { document.querySelector('video').src = URL.createObjectURL(stream); } -function getUserMediaError(e) { - console.log('getUserMediaError'); +function handleError(e) { + console.log(e); } ``` -When creating a constraints object for the `navigator.webkitGetUserMedia` call, -if you are using a source from `desktopCapturer` your `chromeMediaSource` must -be set to `"desktop"` and your `audio` must be set to `false`. +To capture video from a source provided by `desktopCapturer` the constraints +passed to [`navigator.webkitGetUserMedia`] must include +`chromeMediaSource: 'desktop'`, and `audio: false`. -If you wish to -capture the audio and video from the entire desktop you can set -`chromeMediaSource` to `"screen"` and `audio` to `true`. When using this method -you cannot specify a `chromeMediaSourceId`. +To capture both audio and video from the entire desktop the constraints passed +to [`navigator.webkitGetUserMedia`] must include `chromeMediaSource: "screen"`, +and `audio: true`, but should not include a `chromeMediaSourceId` constraint. ## Methods @@ -56,24 +58,28 @@ The `desktopCapturer` module has the following methods: * `options` Object * `types` Array - An array of String that lists the types of desktop sources to be captured, available types are `screen` and `window`. - * `thumbnailSize` Object (optional) - The suggested size that thumbnail should - be scaled, it is `{width: 150, height: 150}` by default. + * `thumbnailSize` Object (optional) - The suggested size that the media source + thumbnail should be scaled to, defaults to `{width: 150, height: 150}`. * `callback` Function -Starts a request to get all desktop sources, `callback` will be called with -`callback(error, sources)` when the request is completed. +Starts gathering information about all available desktop media sources, +and calls `callback(error, sources)` when finished. -The `sources` is an array of `Source` objects, each `Source` represents a -captured screen or individual window, and has following properties: +`sources` is an array of `Source` objects, each `Source` represents a +screen or an individual window that can be captured, and has the following +properties: -* `id` String - The id of the captured window or screen used in - `navigator.webkitGetUserMedia`. The format looks like `window:XX` or - `screen:XX` where `XX` is a random generated number. -* `name` String - The described name of the capturing screen or window. If the - source is a screen, the name will be `Entire Screen` or `Screen `; if - it is a window, the name will be the window's title. -* `thumbnail` [NativeImage](native-image.md) - A thumbnail native image. +* `id` String - The identifier of a window or screen that can be used as a + `chromeMediaSourceId` constraint when calling + [`navigator.webkitGetUserMedia`]. The format of the identifier will be + `window:XX` or `screen:XX`, where `XX` is a random generated number. +* `name` String - A screen source will be named either `Entire Screen` or + `Screen `, while the name of a window source will match the window + title. +* `thumbnail` [NativeImage](native-image.md) - A thumbnail image. **Note:** + There is no guarantee that the size of the thumbnail is the same as the + `thumnbailSize` specified in the `options` passed to + `desktopCapturer.getSources`. The actual size depends on the scale of the + screen or window. -**Note:** There is no guarantee that the size of `source.thumbnail` is always -the same as the `thumnbailSize` in `options`. It also depends on the scale of -the screen or window. +[`navigator.webkitGetUserMedia`]: https://developer.mozilla.org/en/docs/Web/API/Navigator/getUserMedia From 0da1a772be9f60b3b164d5c2e4433fae6f296149 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 26 Jul 2016 13:29:27 +0900 Subject: [PATCH 13/74] Set page's font settings with system settings --- atom/browser/native_window.cc | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/atom/browser/native_window.cc b/atom/browser/native_window.cc index 662835ab921a..0320261960b2 100644 --- a/atom/browser/native_window.cc +++ b/atom/browser/native_window.cc @@ -40,6 +40,11 @@ #include "ui/gfx/geometry/size_conversions.h" #include "ui/gl/gpu_switching_manager.h" +#if defined(OS_LINUX) || defined(OS_WIN) +#include "content/public/common/renderer_preferences.h" +#include "ui/gfx/font_render_params.h" +#endif + DEFINE_WEB_CONTENTS_USER_DATA_KEY(atom::NativeWindowRelay); namespace atom { @@ -67,6 +72,21 @@ NativeWindow::NativeWindow( if (parent) options.Get("modal", &is_modal_); +#if defined(OS_LINUX) || defined(OS_WIN) + content::RendererPreferences* prefs = + web_contents()->GetMutableRendererPrefs(); + + // Update font settings. + CR_DEFINE_STATIC_LOCAL(const gfx::FontRenderParams, params, + (gfx::GetFontRenderParams(gfx::FontRenderParamsQuery(), NULL))); + prefs->should_antialias_text = params.antialiasing; + prefs->use_subpixel_positioning = params.subpixel_positioning; + prefs->hinting = params.hinting; + prefs->use_autohinter = params.autohinter; + prefs->use_bitmaps = params.use_bitmaps; + prefs->subpixel_rendering = params.subpixel_rendering; +#endif + // Tell the content module to initialize renderer widget with transparent // mode. ui::GpuSwitchingManager::SetTransparent(transparent_); From d2ce50e3dd2cf476d4a7319c01b9dab8b75fd53e Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 26 Jul 2016 13:32:17 +0900 Subject: [PATCH 14/74] c++11 styling --- atom/browser/native_window.cc | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/atom/browser/native_window.cc b/atom/browser/native_window.cc index 0320261960b2..d6f8dcf3cb45 100644 --- a/atom/browser/native_window.cc +++ b/atom/browser/native_window.cc @@ -73,12 +73,11 @@ NativeWindow::NativeWindow( options.Get("modal", &is_modal_); #if defined(OS_LINUX) || defined(OS_WIN) - content::RendererPreferences* prefs = - web_contents()->GetMutableRendererPrefs(); + auto* prefs = web_contents()->GetMutableRendererPrefs(); // Update font settings. CR_DEFINE_STATIC_LOCAL(const gfx::FontRenderParams, params, - (gfx::GetFontRenderParams(gfx::FontRenderParamsQuery(), NULL))); + (gfx::GetFontRenderParams(gfx::FontRenderParamsQuery(), nullptr))); prefs->should_antialias_text = params.antialiasing; prefs->use_subpixel_positioning = params.subpixel_positioning; prefs->hinting = params.hinting; From 6cc68638e76d23d499cc1a07a2f343da8e22cc8f Mon Sep 17 00:00:00 2001 From: Vadim Macagon Date: Tue, 26 Jul 2016 11:57:39 +0700 Subject: [PATCH 15/74] Normalize string quotes in desktopCapturer docs Missed one string in my previous PR :( --- docs/api/desktop-capturer.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/desktop-capturer.md b/docs/api/desktop-capturer.md index 56b525a51989..0e092b9e983b 100644 --- a/docs/api/desktop-capturer.md +++ b/docs/api/desktop-capturer.md @@ -46,7 +46,7 @@ passed to [`navigator.webkitGetUserMedia`] must include `chromeMediaSource: 'desktop'`, and `audio: false`. To capture both audio and video from the entire desktop the constraints passed -to [`navigator.webkitGetUserMedia`] must include `chromeMediaSource: "screen"`, +to [`navigator.webkitGetUserMedia`] must include `chromeMediaSource: 'screen'`, and `audio: true`, but should not include a `chromeMediaSourceId` constraint. ## Methods From 9bf31502370b53787ba5810e5682746f86d8ed6f Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 26 Jul 2016 17:38:31 +0900 Subject: [PATCH 16/74] Add --msvs parameter to bootstrap.py --- script/bootstrap.py | 15 +++++++++++---- script/update.py | 8 +++++++- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/script/bootstrap.py b/script/bootstrap.py index 198d7d0a7dec..445f27805310 100755 --- a/script/bootstrap.py +++ b/script/bootstrap.py @@ -65,7 +65,7 @@ def main(): create_chrome_version_h() touch_config_gypi() - run_update(defines, args.disable_clang, args.clang_dir) + run_update(defines, args.msvs, args.disable_clang, args.clang_dir) update_electron_modules('spec', args.target_arch) @@ -86,6 +86,8 @@ def parse_args(): action='store_true', help='Run non-interactively by assuming "yes" to all ' \ 'prompts.') + parser.add_argument('--msvs', action='store_true', + help='Generate Visual Studio project') parser.add_argument('--target_arch', default=get_target_arch(), help='Manually specify the arch to build for') parser.add_argument('--clang_dir', default='', help='Path to clang binaries') @@ -249,14 +251,19 @@ def touch_config_gypi(): f.write(content) -def run_update(defines, disable_clang, clang_dir): +def run_update(defines, msvs, disable_clang, clang_dir): env = os.environ.copy() if not disable_clang and clang_dir == '': # Build with prebuilt clang. set_clang_env(env) - update = os.path.join(SOURCE_ROOT, 'script', 'update.py') - execute_stdout([sys.executable, update, '--defines', defines], env) + args = [sys.executable, os.path.join(SOURCE_ROOT, 'script', 'update.py')] + if defines: + args += ['--defines', defines] + if msvs: + args += ['--msvs'] + + execute_stdout(args, env) if __name__ == '__main__': diff --git a/script/update.py b/script/update.py index a67a49e7ab51..2e551f7ad6cf 100755 --- a/script/update.py +++ b/script/update.py @@ -28,6 +28,8 @@ def parse_args(): parser = argparse.ArgumentParser(description='Update build configurations') parser.add_argument('--defines', default='', help='The definetions passed to gyp') + parser.add_argument('--msvs', action='store_true', + help='Generate Visual Studio project') return parser.parse_args() @@ -86,7 +88,11 @@ def run_gyp(target_arch, component): if define: defines += ['-D' + define] - return subprocess.call([python, gyp, '-f', 'ninja', '--depth', '.', + generator = 'ninja' + if args.msvs: + generator = 'msvs-ninja' + + return subprocess.call([python, gyp, '-f', generator, '--depth', '.', 'electron.gyp', '-Icommon.gypi'] + defines, env=env) From b34deb1d2f0ff701f0c58f6747faf7245e50d6ff Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 26 Jul 2016 17:40:58 +0900 Subject: [PATCH 17/74] docs: x64 is the default build --- docs/development/build-instructions-windows.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/development/build-instructions-windows.md b/docs/development/build-instructions-windows.md index 7a7e1ee6c6e9..2c85dbcf11bb 100644 --- a/docs/development/build-instructions-windows.md +++ b/docs/development/build-instructions-windows.md @@ -58,13 +58,13 @@ $ python script\build.py -c D After building is done, you can find `electron.exe` under `out\D` (debug target) or under `out\R` (release target). -## 64bit Build +## 32bit Build -To build for the 64bit target, you need to pass `--target_arch=x64` when running -the bootstrap script: +To build for the 32bit target, you need to pass `--target_arch=ia32` when +running the bootstrap script: ```powershell -$ python script\bootstrap.py -v --target_arch=x64 +$ python script\bootstrap.py -v --target_arch=ia32 ``` The other building steps are exactly the same. From 89de791e9d6b50566d7370e151497968c2f17f69 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 26 Jul 2016 17:42:12 +0900 Subject: [PATCH 18/74] docs: Mention the --msvs --- docs/development/build-instructions-windows.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/development/build-instructions-windows.md b/docs/development/build-instructions-windows.md index 2c85dbcf11bb..400fa370e8c5 100644 --- a/docs/development/build-instructions-windows.md +++ b/docs/development/build-instructions-windows.md @@ -69,6 +69,14 @@ $ python script\bootstrap.py -v --target_arch=ia32 The other building steps are exactly the same. +## Visual Studio project + +To generate a Visual Studio project, you can pass the `--msvs` parameter: + +```powershell +$ python script\bootstrap.py --msvs +``` + ## Tests Test your changes conform to the project coding style using: From 49181403efb826534bc31c54ca95fda743a6f407 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 26 Jul 2016 19:24:28 +0900 Subject: [PATCH 19/74] Ignore CC and CXX in env --- script/cibuild | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/script/cibuild b/script/cibuild index 7d025b4e82cd..7f33762c614c 100755 --- a/script/cibuild +++ b/script/cibuild @@ -35,6 +35,13 @@ def main(): if os.environ.has_key('JANKY_SHA1'): setup_nodenv() + # Ignore the CXX and CC env in CI. + try: + del os.environ['CC'] + del os.environ['CXX'] + except KeyError: + pass + target_arch = 'x64' if os.environ.has_key('TARGET_ARCH'): target_arch = os.environ['TARGET_ARCH'] From 7d11912a03a564749ab49a541511e2e465bdc0a5 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 26 Jul 2016 19:24:58 +0900 Subject: [PATCH 20/74] No more need to overwrite env when running update.py --- script/bootstrap.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/script/bootstrap.py b/script/bootstrap.py index 445f27805310..dc70ba6110eb 100755 --- a/script/bootstrap.py +++ b/script/bootstrap.py @@ -65,7 +65,7 @@ def main(): create_chrome_version_h() touch_config_gypi() - run_update(defines, args.msvs, args.disable_clang, args.clang_dir) + run_update(defines, args.msvs) update_electron_modules('spec', args.target_arch) @@ -251,19 +251,14 @@ def touch_config_gypi(): f.write(content) -def run_update(defines, msvs, disable_clang, clang_dir): - env = os.environ.copy() - if not disable_clang and clang_dir == '': - # Build with prebuilt clang. - set_clang_env(env) - +def run_update(defines, msvs): args = [sys.executable, os.path.join(SOURCE_ROOT, 'script', 'update.py')] if defines: args += ['--defines', defines] if msvs: args += ['--msvs'] - execute_stdout(args, env) + execute_stdout(args) if __name__ == '__main__': From e65bc481a8dce02f9ef48dd4a26c74db0b926329 Mon Sep 17 00:00:00 2001 From: deepak1556 Date: Tue, 26 Jul 2016 16:34:04 +0530 Subject: [PATCH 21/74] browser: initialize pref registry in brightray --- atom/browser/atom_browser_context.cc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/atom/browser/atom_browser_context.cc b/atom/browser/atom_browser_context.cc index d87047ce283c..7ac5dde4ef21 100644 --- a/atom/browser/atom_browser_context.cc +++ b/atom/browser/atom_browser_context.cc @@ -87,6 +87,9 @@ AtomBrowserContext::AtomBrowserContext( // Read options. use_cache_ = true; options.GetBoolean("cache", &use_cache_); + + // Initialize Pref Registry in brightray. + InitPrefs(); } AtomBrowserContext::~AtomBrowserContext() { From dd9935a9d72f3bfa8a70dcdb15b674d126bbba68 Mon Sep 17 00:00:00 2001 From: Zeke Sikelianos Date: Mon, 25 Jul 2016 18:37:58 -0700 Subject: [PATCH 22/74] add npm script to lint all javascript blocks in the docs using standard --- package.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 6dab962b19da..73229386bc6b 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,8 @@ "devDependencies": { "asar": "^0.11.0", "request": "*", - "standard": "^7.1.2" + "standard": "^7.1.2", + "standard-markdown": "^1.0.3" }, "optionalDependencies": { "runas": "^3.0.0" @@ -26,6 +27,7 @@ "lint": "npm run lint-js && npm run lint-cpp", "lint-js": "standard && cd spec && standard", "lint-cpp": "python ./script/cpplint.py", + "lint-docs": "standard-markdown docs", "preinstall": "node -e 'process.exit(0)'", "repl": "python ./script/start.py --interactive", "start": "python ./script/start.py", From 06a354a2eb5334840894f2865c8237956e38e9c9 Mon Sep 17 00:00:00 2001 From: Zeke Sikelianos Date: Mon, 25 Jul 2016 18:39:25 -0700 Subject: [PATCH 23/74] standardize all javascript blocks in English docs --- docs/api/app.md | 48 ++++--- docs/api/browser-window.md | 43 +++++-- docs/api/chrome-command-line-switches.md | 9 +- docs/api/clipboard.md | 15 ++- docs/api/content-tracing.md | 14 +-- docs/api/crash-reporter.md | 4 +- docs/api/desktop-capturer.md | 14 +-- docs/api/dialog.md | 11 +- docs/api/download-item.md | 2 + docs/api/file-object.md | 12 +- docs/api/frameless-window.md | 7 ++ docs/api/global-shortcut.md | 18 +-- docs/api/ipc-main.md | 24 ++-- docs/api/menu.md | 57 ++++----- docs/api/native-image.md | 11 +- docs/api/power-monitor.md | 8 +- docs/api/power-save-blocker.md | 8 +- docs/api/process.md | 10 +- docs/api/protocol.md | 33 ++--- docs/api/remote.md | 35 +++--- docs/api/screen.md | 4 +- docs/api/session.md | 77 ++++++------ docs/api/shell.md | 4 +- docs/api/synopsis.md | 47 ++++--- docs/api/system-preferences.md | 17 +-- docs/api/tray.md | 16 ++- docs/api/web-contents.md | 117 ++++++++++-------- docs/api/web-frame.md | 18 +-- docs/api/web-view-tag.md | 66 +++++----- docs/faq.md | 28 +++-- docs/tutorial/application-packaging.md | 31 ++--- .../desktop-environment-integration.md | 70 ++++++----- docs/tutorial/online-offline-events.md | 52 ++++---- docs/tutorial/quick-start.md | 30 ++--- docs/tutorial/using-pepper-flash-plugin.md | 7 +- docs/tutorial/using-selenium-and-webdriver.md | 32 ++--- docs/tutorial/using-widevine-cdm-plugin.md | 13 +- 37 files changed, 567 insertions(+), 445 deletions(-) diff --git a/docs/api/app.md b/docs/api/app.md index 114ac8c5e09b..1220f36c9f8c 100644 --- a/docs/api/app.md +++ b/docs/api/app.md @@ -6,10 +6,10 @@ The following example shows how to quit the application when the last window is closed: ```javascript -const {app} = require('electron'); +const {app} = require('electron') app.on('window-all-closed', () => { - app.quit(); -}); + app.quit() +}) ``` ## Events @@ -192,15 +192,17 @@ certificate you should prevent the default behavior with `event.preventDefault()` and call `callback(true)`. ```javascript +const {app} = require('electron') + app.on('certificate-error', (event, webContents, url, error, certificate, callback) => { if (url === 'https://github.com') { // Verification logic. - event.preventDefault(); - callback(true); + event.preventDefault() + callback(true) } else { - callback(false); + callback(false) } -}); +}) ``` ### Event: 'select-client-certificate' @@ -228,10 +230,12 @@ and `callback` needs to be called with an entry filtered from the list. Using certificate from the store. ```javascript +const {app} = require('electron') + app.on('select-client-certificate', (event, webContents, url, list, callback) => { - event.preventDefault(); - callback(list[0]); -}); + event.preventDefault() + callback(list[0]) +}) ``` ### Event: 'login' @@ -259,10 +263,12 @@ should prevent the default behavior with `event.preventDefault()` and call `callback(username, password)` with the credentials. ```javascript +const {app} = require('electron') + app.on('login', (event, webContents, request, authInfo, callback) => { - event.preventDefault(); - callback('username', 'secret'); -}); + event.preventDefault() + callback('username', 'secret') +}) ``` ### Event: 'gpu-process-crashed' @@ -331,6 +337,8 @@ An example of restarting current instance immediately and adding a new command line argument to the new instance: ```javascript +const {app} = require('electron') + app.relaunch({args: process.argv.slice(1) + ['--relaunch']}) app.exit(0) ``` @@ -537,24 +545,24 @@ An example of activating the window of primary instance when a second instance starts: ```javascript -let myWindow = null; +const {app} = require('electron') +let myWindow = null const shouldQuit = app.makeSingleInstance((commandLine, workingDirectory) => { // Someone tried to run a second instance, we should focus our window. if (myWindow) { - if (myWindow.isMinimized()) myWindow.restore(); - myWindow.focus(); + if (myWindow.isMinimized()) myWindow.restore() + myWindow.focus() } -}); +}) if (shouldQuit) { - app.quit(); - return; + app.quit() } // Create myWindow, load the rest of the app, etc... app.on('ready', () => { -}); +}) ``` ### `app.releaseSingleInstance()` diff --git a/docs/api/browser-window.md b/docs/api/browser-window.md index 73d53ed875c3..e53ec013de6f 100644 --- a/docs/api/browser-window.md +++ b/docs/api/browser-window.md @@ -6,8 +6,8 @@ // In the main process. const {BrowserWindow} = require('electron') -// Or in the renderer process. -const {BrowserWindow} = require('electron').remote +// Or use `remote` from the renderer process. +// const {BrowserWindow} = require('electron').remote let win = new BrowserWindow({width: 800, height: 600}) win.on('closed', () => { @@ -35,6 +35,7 @@ process has done drawing for the first time, showing window after this event will have no visual flash: ```javascript +const {BrowserWindow} = require('electron') let win = new BrowserWindow({show: false}) win.once('ready-to-show', () => { win.show() @@ -52,6 +53,8 @@ the app feel slow. In this case, it is recommended to show the window immediately, and use a `backgroundColor` close to your app's background: ```javascript +const {BrowserWindow} = require('electron') + let win = new BrowserWindow({backgroundColor: '#2e2c29'}) win.loadURL('https://github.com') ``` @@ -64,8 +67,12 @@ to set `backgroundColor` to make app feel more native. By using `parent` option, you can create child windows: ```javascript +const {BrowserWindow} = require('electron') + let top = new BrowserWindow() let child = new BrowserWindow({parent: top}) +child.show() +top.show() ``` The `child` window will always show on top of the `top` window. @@ -76,6 +83,8 @@ A modal window is a child window that disables parent window, to create a modal window, you have to set both `parent` and `modal` options: ```javascript +const {BrowserWindow} = require('electron') + let child = new BrowserWindow({parent: top, modal: true, show: false}) child.loadURL('https://github.com') child.once('ready-to-show', () => { @@ -308,14 +317,14 @@ close. For example: ```javascript window.onbeforeunload = (e) => { - console.log('I do not want to be closed'); + console.log('I do not want to be closed') // Unlike usual browsers that a message box will be prompted to users, returning // a non-void value will silently cancel the close. // It is recommended to use the dialog API to let the user confirm closing the // application. - e.returnValue = false; -}; + e.returnValue = false +} ``` #### Event: 'closed' @@ -414,12 +423,14 @@ Commands are lowercased, underscores are replaced with hyphens, and the e.g. `APPCOMMAND_BROWSER_BACKWARD` is emitted as `browser-backward`. ```javascript -someWindow.on('app-command', (e, cmd) => { +const {BrowserWindow} = require('electron') +let win = new BrowserWindow() +win.on('app-command', (e, cmd) => { // Navigate the window back when the user hits their mouse back button - if (cmd === 'browser-backward' && someWindow.webContents.canGoBack()) { - someWindow.webContents.goBack(); + if (cmd === 'browser-backward' && win.webContents.canGoBack()) { + win.webContents.goBack() } -}); +}) ``` #### Event: 'scroll-touch-begin' _macOS_ @@ -496,7 +507,10 @@ an Object containing `name` and `version` properties. To check if a DevTools extension is installed you can run the following: ```javascript +const {BrowserWindow} = require('electron') + let installed = BrowserWindow.getDevToolsExtensions().hasOwnProperty('devtron') +console.log(installed) ``` **Note:** This API cannot be called before the `ready` event of the `app` module @@ -507,8 +521,10 @@ is emitted. Objects created with `new BrowserWindow` have the following properties: ```javascript +const {BrowserWindow} = require('electron') // In this example `win` is our instance -let win = new BrowserWindow({width: 800, height: 600}); +let win = new BrowserWindow({width: 800, height: 600}) +win.loadURL('https://github.com') ``` #### `win.webContents` @@ -809,8 +825,11 @@ attached just below the window frame, but you may want to display them beneath a HTML-rendered toolbar. For example: ```javascript -let toolbarRect = document.getElementById('toolbar').getBoundingClientRect(); -win.setSheetOffset(toolbarRect.height); +const {BrowserWindow} = require('electron') +let win = new BrowserWindow() + +let toolbarRect = document.getElementById('toolbar').getBoundingClientRect() +win.setSheetOffset(toolbarRect.height) ``` #### `win.flashFrame(flag)` diff --git a/docs/api/chrome-command-line-switches.md b/docs/api/chrome-command-line-switches.md index 4fe6d136da2b..7d840f1a3e23 100644 --- a/docs/api/chrome-command-line-switches.md +++ b/docs/api/chrome-command-line-switches.md @@ -7,13 +7,13 @@ your app's main script before the [ready][ready] event of the [app][app] module is emitted: ```javascript -const {app} = require('electron'); -app.commandLine.appendSwitch('remote-debugging-port', '8315'); -app.commandLine.appendSwitch('host-rules', 'MAP * 127.0.0.1'); +const {app} = require('electron') +app.commandLine.appendSwitch('remote-debugging-port', '8315') +app.commandLine.appendSwitch('host-rules', 'MAP * 127.0.0.1') app.on('ready', () => { // Your code here -}); +}) ``` ## --ignore-connections-limit=`domains` @@ -57,6 +57,7 @@ list of hosts. This flag has an effect only if used in tandem with For example: ```javascript +const {app} = require('electron') app.commandLine.appendSwitch('proxy-bypass-list', ';*.google.com;*foo.com;1.2.3.4:5678') ``` diff --git a/docs/api/clipboard.md b/docs/api/clipboard.md index 85712bc651d9..bc7e15ee09b0 100644 --- a/docs/api/clipboard.md +++ b/docs/api/clipboard.md @@ -5,16 +5,17 @@ The following example shows how to write a string to the clipboard: ```javascript -const {clipboard} = require('electron'); -clipboard.writeText('Example String'); +const {clipboard} = require('electron') +clipboard.writeText('Example String') ``` On X Window systems, there is also a selection clipboard. To manipulate it you need to pass `selection` to each method: ```javascript -clipboard.writeText('Example String', 'selection'); -console.log(clipboard.readText('selection')); +const {clipboard} = require('electron') +clipboard.writeText('Example String', 'selection') +console.log(clipboard.readText('selection')) ``` ## Methods @@ -109,7 +110,8 @@ Returns an array of supported formats for the clipboard `type`. Returns whether the clipboard supports the format of specified `data`. ```javascript -console.log(clipboard.has('

selection

')); +const {clipboard} = require('electron') +console.log(clipboard.has('

selection

')) ``` ### `clipboard.read(data[, type])` _Experimental_ @@ -130,6 +132,7 @@ Reads `data` from the clipboard. * `type` String (optional) ```javascript -clipboard.write({text: 'test', html: "test"}); +const {clipboard} = require('electron') +clipboard.write({text: 'test', html: 'test'}) ``` Writes `data` to the clipboard. diff --git a/docs/api/content-tracing.md b/docs/api/content-tracing.md index d269d602ef59..8cfc7670c6a6 100644 --- a/docs/api/content-tracing.md +++ b/docs/api/content-tracing.md @@ -8,22 +8,22 @@ This module does not include a web interface so you need to open result. ```javascript -const {contentTracing} = require('electron'); +const {contentTracing} = require('electron') const options = { categoryFilter: '*', traceOptions: 'record-until-full,enable-sampling' -}; +} contentTracing.startRecording(options, () => { - console.log('Tracing started'); + console.log('Tracing started') setTimeout(() => { contentTracing.stopRecording('', (path) => { - console.log('Tracing data recorded to ' + path); - }); - }, 5000); -}); + console.log('Tracing data recorded to ' + path) + }) + }, 5000) +}) ``` ## Methods diff --git a/docs/api/crash-reporter.md b/docs/api/crash-reporter.md index bd323de88394..5c84cb4cb113 100644 --- a/docs/api/crash-reporter.md +++ b/docs/api/crash-reporter.md @@ -6,14 +6,14 @@ The following is an example of automatically submitting a crash report to a remote server: ```javascript -const {crashReporter} = require('electron'); +const {crashReporter} = require('electron') crashReporter.start({ productName: 'YourName', companyName: 'YourCompany', submitURL: 'https://your-domain.com/url-to-submit', autoSubmit: true -}); +}) ``` For setting up a server to accept and process crash reports, you can use diff --git a/docs/api/desktop-capturer.md b/docs/api/desktop-capturer.md index 0e092b9e983b..a636c7be03d3 100644 --- a/docs/api/desktop-capturer.md +++ b/docs/api/desktop-capturer.md @@ -8,10 +8,10 @@ title is `Electron`: ```javascript // In the renderer process. -const {desktopCapturer} = require('electron'); +const {desktopCapturer} = require('electron') desktopCapturer.getSources({types: ['window', 'screen']}, (error, sources) => { - if (error) throw error; + if (error) throw error for (let i = 0; i < sources.length; ++i) { if (sources[i].name === 'Electron') { navigator.webkitGetUserMedia({ @@ -26,18 +26,18 @@ desktopCapturer.getSources({types: ['window', 'screen']}, (error, sources) => { maxHeight: 720 } } - }, handleStream, handleError); - return; + }, handleStream, handleError) + return } } -}); +}) function handleStream(stream) { - document.querySelector('video').src = URL.createObjectURL(stream); + document.querySelector('video').src = URL.createObjectURL(stream) } function handleError(e) { - console.log(e); + console.log(e) } ``` diff --git a/docs/api/dialog.md b/docs/api/dialog.md index d71109e9dfdc..f6bf0fdc19fd 100644 --- a/docs/api/dialog.md +++ b/docs/api/dialog.md @@ -5,17 +5,16 @@ An example of showing a dialog to select multiple files and directories: ```javascript -let win = ...; // BrowserWindow in which to show the dialog -const {dialog} = require('electron'); - -console.log(dialog.showOpenDialog({properties: ['openFile', 'openDirectory', 'multiSelections']})); +const {dialog} = require('electron') +console.log(dialog.showOpenDialog({properties: ['openFile', 'openDirectory', 'multiSelections']})) ``` The Dialog is opened from Electron's main thread. If you want to use the dialog object from a renderer process, remember to access it using the remote: ```javascript -const {dialog} = require('electron').remote; +const {dialog} = require('electron').remote +console.log(dialog) ``` ## Methods @@ -42,7 +41,7 @@ otherwise it returns `undefined`. The `filters` specifies an array of file types that can be displayed or selected when you want to limit the user to a specific type. For example: -```javascript +``` { filters: [ {name: 'Images', extensions: ['jpg', 'png', 'gif']}, diff --git a/docs/api/download-item.md b/docs/api/download-item.md index 778f68b7bc36..e0416d8ef265 100644 --- a/docs/api/download-item.md +++ b/docs/api/download-item.md @@ -8,6 +8,8 @@ control the download item. ```javascript // In the main process. +const {BrowserWindow} = require('electron') +let win = new BrowserWindow() win.webContents.session.on('will-download', (event, item, webContents) => { // Set the save path, making Electron not to prompt a save dialog. item.setSavePath('/tmp/save.pdf') diff --git a/docs/api/file-object.md b/docs/api/file-object.md index b1e643c74080..c39a0cf715e6 100644 --- a/docs/api/file-object.md +++ b/docs/api/file-object.md @@ -15,19 +15,19 @@ Example on getting a real path from a dragged-onto-the-app file: ``` diff --git a/docs/api/frameless-window.md b/docs/api/frameless-window.md index b6b5926beef1..1ca64a456f28 100644 --- a/docs/api/frameless-window.md +++ b/docs/api/frameless-window.md @@ -16,6 +16,7 @@ To create a frameless window, you need to set `frame` to `false` in ```javascript const {BrowserWindow} = require('electron') let win = new BrowserWindow({width: 800, height: 600, frame: false}) +win.show() ``` ### Alternatives on macOS @@ -28,7 +29,9 @@ the window controls ("traffic lights") for standard window actions. You can do so by specifying the new `titleBarStyle` option: ```javascript +const {BrowserWindow} = require('electron') let win = new BrowserWindow({titleBarStyle: 'hidden'}) +win.show() ``` ## Transparent window @@ -37,7 +40,9 @@ By setting the `transparent` option to `true`, you can also make the frameless window transparent: ```javascript +const {BrowserWindow} = require('electron') let win = new BrowserWindow({transparent: true, frame: false}) +win.show() ``` ### Limitations @@ -66,6 +71,8 @@ events, you can call the [win.setIgnoreMouseEvents(ignore)][ignore-mouse-events] API: ```javascript +const {BrowserWindow} = require('electron') +let win = new BrowserWindow() win.setIgnoreMouseEvents(true) ``` diff --git a/docs/api/global-shortcut.md b/docs/api/global-shortcut.md index d9488a7b7fd9..5a2f819ac368 100644 --- a/docs/api/global-shortcut.md +++ b/docs/api/global-shortcut.md @@ -11,29 +11,29 @@ not have the keyboard focus. You should not use this module until the `ready` event of the app module is emitted. ```javascript -const {app, globalShortcut} = require('electron'); +const {app, globalShortcut} = require('electron') app.on('ready', () => { // Register a 'CommandOrControl+X' shortcut listener. const ret = globalShortcut.register('CommandOrControl+X', () => { - console.log('CommandOrControl+X is pressed'); - }); + console.log('CommandOrControl+X is pressed') + }) if (!ret) { - console.log('registration failed'); + console.log('registration failed') } // Check whether a shortcut is registered. - console.log(globalShortcut.isRegistered('CommandOrControl+X')); -}); + console.log(globalShortcut.isRegistered('CommandOrControl+X')) +}) app.on('will-quit', () => { // Unregister a shortcut. - globalShortcut.unregister('CommandOrControl+X'); + globalShortcut.unregister('CommandOrControl+X') // Unregister all shortcuts. - globalShortcut.unregisterAll(); -}); + globalShortcut.unregisterAll() +}) ``` ## Methods diff --git a/docs/api/ipc-main.md b/docs/api/ipc-main.md index 0b7acbfefa0c..6ba9c2656403 100644 --- a/docs/api/ipc-main.md +++ b/docs/api/ipc-main.md @@ -23,27 +23,27 @@ processes: ```javascript // In main process. -const {ipcMain} = require('electron'); +const {ipcMain} = require('electron') ipcMain.on('asynchronous-message', (event, arg) => { - console.log(arg); // prints "ping" - event.sender.send('asynchronous-reply', 'pong'); -}); + console.log(arg) // prints "ping" + event.sender.send('asynchronous-reply', 'pong') +}) ipcMain.on('synchronous-message', (event, arg) => { - console.log(arg); // prints "ping" - event.returnValue = 'pong'; -}); + console.log(arg) // prints "ping" + event.returnValue = 'pong' +}) ``` ```javascript // In renderer process (web page). -const {ipcRenderer} = require('electron'); -console.log(ipcRenderer.sendSync('synchronous-message', 'ping')); // prints "pong" +const {ipcRenderer} = require('electron') +console.log(ipcRenderer.sendSync('synchronous-message', 'ping')) // prints "pong" ipcRenderer.on('asynchronous-reply', (event, arg) => { - console.log(arg); // prints "pong" -}); -ipcRenderer.send('asynchronous-message', 'ping'); + console.log(arg) // prints "pong" +}) +ipcRenderer.send('asynchronous-message', 'ping') ``` ## Listening for Messages diff --git a/docs/api/menu.md b/docs/api/menu.md index c60cbbac7ffe..760942df2491 100644 --- a/docs/api/menu.md +++ b/docs/api/menu.md @@ -15,18 +15,18 @@ the user right clicks the page: ```html ``` @@ -34,6 +34,8 @@ An example of creating the application menu in the render process with the simple template API: ```javascript +const {Menu} = require('electron') + const template = [ { label: 'Edit', @@ -64,7 +66,7 @@ const template = [ }, { role: 'selectall' - }, + } ] }, { @@ -73,8 +75,8 @@ const template = [ { label: 'Reload', accelerator: 'CmdOrCtrl+R', - click(item, focusedWindow) { - if (focusedWindow) focusedWindow.reload(); + click (item, focusedWindow) { + if (focusedWindow) focusedWindow.reload() } }, { @@ -83,11 +85,10 @@ const template = [ { label: 'Toggle Developer Tools', accelerator: process.platform === 'darwin' ? 'Alt+Command+I' : 'Ctrl+Shift+I', - click(item, focusedWindow) { - if (focusedWindow) - focusedWindow.webContents.toggleDevTools(); + click (item, focusedWindow) { + if (focusedWindow) focusedWindow.webContents.toggleDevTools() } - }, + } ] }, { @@ -98,7 +99,7 @@ const template = [ }, { role: 'close' - }, + } ] }, { @@ -106,14 +107,14 @@ const template = [ submenu: [ { label: 'Learn More', - click() { require('electron').shell.openExternal('http://electron.atom.io'); } - }, + click () { require('electron').shell.openExternal('http://electron.atom.io') } + } ] - }, -]; + } +] if (process.platform === 'darwin') { - const name = require('electron').remote.app.getName(); + const name = require('electron').remote.app.getName() template.unshift({ label: name, submenu: [ @@ -144,9 +145,9 @@ if (process.platform === 'darwin') { }, { role: 'quit' - }, + } ] - }); + }) // Window menu. template[3].submenu = [ { @@ -170,11 +171,11 @@ if (process.platform === 'darwin') { label: 'Bring All to Front', role: 'front' } - ]; + ] } -const menu = Menu.buildFromTemplate(template); -Menu.setApplicationMenu(menu); +const menu = Menu.buildFromTemplate(template) +Menu.setApplicationMenu(menu) ``` ## Class: Menu @@ -320,7 +321,7 @@ the first item. Template: -```javascript +``` [ {label: '4', id: '4'}, {label: '5', id: '5'}, @@ -342,7 +343,7 @@ Menu: Template: -```javascript +``` [ {label: 'a', position: 'endof=letters'}, {label: '1', position: 'endof=numbers'}, diff --git a/docs/api/native-image.md b/docs/api/native-image.md index 4bf12413f21e..6db5822a2e86 100644 --- a/docs/api/native-image.md +++ b/docs/api/native-image.md @@ -9,15 +9,20 @@ For example, when creating a tray or setting a window's icon, you can pass an image file path as a `String`: ```javascript +const {BrowserWindow, Tray} = require('electron') + const appIcon = new Tray('/Users/somebody/images/icon.png') let win = new BrowserWindow({icon: '/Users/somebody/images/window.png'}) +console.log(appIcon, win) ``` Or read the image from the clipboard which returns a `nativeImage`: ```javascript +const {clipboard, Tray} = require('electron') const image = clipboard.readImage() const appIcon = new Tray(image) +console.log(appIcon) ``` ## Supported Formats @@ -55,7 +60,9 @@ images/ ```javascript +const {Tray} = require('electron') let appIcon = new Tray('/Users/somebody/images/icon.png') +console.log(appIcon) ``` Following suffixes for DPI are also supported: @@ -105,8 +112,10 @@ Creates an empty `NativeImage` instance. Creates a new `NativeImage` instance from a file located at `path`. ```javascript -const {nativeImage} = require('electron') +const nativeImage = require('electron').nativeImage + let image = nativeImage.createFromPath('/Users/somebody/images/icon.png') +console.log(image) ``` ### `nativeImage.createFromBuffer(buffer[, scaleFactor])` diff --git a/docs/api/power-monitor.md b/docs/api/power-monitor.md index 12c27578e67e..57e9d030be92 100644 --- a/docs/api/power-monitor.md +++ b/docs/api/power-monitor.md @@ -8,11 +8,13 @@ event of the `app` module is emitted. For example: ```javascript +const {app} = require('electron') + app.on('ready', () => { require('electron').powerMonitor.on('suspend', () => { - console.log('The system is going to sleep'); - }); -}); + console.log('The system is going to sleep') + }) +}) ``` ## Events diff --git a/docs/api/power-save-blocker.md b/docs/api/power-save-blocker.md index f9e350867a3e..4ad17df53bd2 100644 --- a/docs/api/power-save-blocker.md +++ b/docs/api/power-save-blocker.md @@ -5,12 +5,12 @@ For example: ```javascript -const {powerSaveBlocker} = require('electron'); +const {powerSaveBlocker} = require('electron') -const id = powerSaveBlocker.start('prevent-display-sleep'); -console.log(powerSaveBlocker.isStarted(id)); +const id = powerSaveBlocker.start('prevent-display-sleep') +console.log(powerSaveBlocker.isStarted(id)) -powerSaveBlocker.stop(id); +powerSaveBlocker.stop(id) ``` ## Methods diff --git a/docs/api/process.md b/docs/api/process.md index 2f618f240da3..e467f2fc2cdb 100644 --- a/docs/api/process.md +++ b/docs/api/process.md @@ -16,12 +16,12 @@ the global scope when node integration is turned off: ```javascript // preload.js -const _setImmediate = setImmediate; -const _clearImmediate = clearImmediate; +const _setImmediate = setImmediate +const _clearImmediate = clearImmediate process.once('loaded', () => { - global.setImmediate = _setImmediate; - global.clearImmediate = _clearImmediate; -}); + global.setImmediate = _setImmediate + global.clearImmediate = _clearImmediate +}) ``` ## Properties diff --git a/docs/api/protocol.md b/docs/api/protocol.md index bcea37bd224c..97e37d3c4ba2 100644 --- a/docs/api/protocol.md +++ b/docs/api/protocol.md @@ -6,19 +6,19 @@ An example of implementing a protocol that has the same effect as the `file://` protocol: ```javascript -const {app, protocol} = require('electron'); -const path = require('path'); +const {app, protocol} = require('electron') +const path = require('path') app.on('ready', () => { protocol.registerFileProtocol('atom', (request, callback) => { - const url = request.url.substr(7); - callback({path: path.normalize(__dirname + '/' + url)}); + const url = request.url.substr(7) + callback({path: path.normalize(`${__dirname}/${url}`)}) }, (error) => { - if (error) - console.error('Failed to register protocol'); - }); -}); + if (error) console.error('Failed to register protocol') + }) +}) ``` + **Note:** All methods unless specified can only be used after the `ready` event of the `app` module gets emitted. @@ -52,10 +52,12 @@ So if you want to register a custom protocol to replace the `http` protocol, you have to register it as standard scheme: ```javascript -protocol.registerStandardSchemes(['atom']); +const {app, protocol} = require('electron') + +protocol.registerStandardSchemes(['atom']) app.on('ready', () => { - protocol.registerHttpProtocol('atom', ...); -}); + protocol.registerHttpProtocol('atom', '...') +}) ``` **Note:** This method can only be used before the `ready` event of the `app` @@ -119,12 +121,13 @@ should be called with either a `Buffer` object or an object that has the `data`, Example: ```javascript +const {protocol} = require('electron') + protocol.registerBufferProtocol('atom', (request, callback) => { - callback({mimeType: 'text/html', data: new Buffer('
Response
')}); + callback({mimeType: 'text/html', data: new Buffer('
Response
')}) }, (error) => { - if (error) - console.error('Failed to register protocol'); -}); + if (error) console.error('Failed to register protocol') +}) ``` ### `protocol.registerStringProtocol(scheme, handler[, completion])` diff --git a/docs/api/remote.md b/docs/api/remote.md index aa1b8082bb08..455e7f58a6a5 100644 --- a/docs/api/remote.md +++ b/docs/api/remote.md @@ -14,10 +14,9 @@ similar to Java's [RMI][rmi]. An example of creating a browser window from a renderer process: ```javascript -const {BrowserWindow} = require('electron').remote; - -let win = new BrowserWindow({width: 800, height: 600}); -win.loadURL('https://github.com'); +const {BrowserWindow} = require('electron').remote +let win = new BrowserWindow({width: 800, height: 600}) +win.loadURL('https://github.com') ``` **Note:** for the reverse (access the renderer process from the main process), @@ -70,23 +69,22 @@ For instance you can't use a function from the renderer process in an ```javascript // main process mapNumbers.js exports.withRendererCallback = (mapper) => { - return [1,2,3].map(mapper); -}; + return [1, 2, 3].map(mapper) +} exports.withLocalCallback = () => { - return [1,2,3].map(x => x + 1); -}; + return [1, 2, 3].map(x => x + 1) +} ``` ```javascript // renderer process -const mapNumbers = require('electron').remote.require('./mapNumbers'); +const mapNumbers = require('electron').remote.require('./mapNumbers') +const withRendererCb = mapNumbers.withRendererCallback(x => x + 1) +const withLocalCb = mapNumbers.withLocalCallback() -const withRendererCb = mapNumbers.withRendererCallback(x => x + 1); - -const withLocalCb = mapNumbers.withLocalCallback(); - -console.log(withRendererCb, withLocalCb); // [undefined, undefined, undefined], [2, 3, 4] +console.log(withRendererCb, withLocalCb) +// [undefined, undefined, undefined], [2, 3, 4] ``` As you can see, the renderer callback's synchronous return value was not as @@ -100,9 +98,9 @@ For example, the following code seems innocent at first glance. It installs a callback for the `close` event on a remote object: ```javascript -remote.getCurrentWindow().on('close', () => { - // blabla... -}); +require('electron').remote.getCurrentWindow().on('close', () => { + // window was closed... +}) ``` But remember the callback is referenced by the main process until you @@ -124,7 +122,8 @@ The built-in modules in the main process are added as getters in the `remote` module, so you can use them directly like the `electron` module. ```javascript -const app = remote.app; +const app = require('electron').remote.app +console.log(app) ``` ## Methods diff --git a/docs/api/screen.md b/docs/api/screen.md index d5318d8fc02c..191688afd5db 100644 --- a/docs/api/screen.md +++ b/docs/api/screen.md @@ -21,7 +21,8 @@ let win app.on('ready', () => { const {width, height} = electron.screen.getPrimaryDisplay().workAreaSize win = new BrowserWindow({width, height}) -}); + win.loadURL('https://github.com') +}) ``` Another example of creating a window in the external display: @@ -43,6 +44,7 @@ app.on('ready', () => { x: externalDisplay.bounds.x + 50, y: externalDisplay.bounds.y + 50 }) + win.loadURL('https://github.com') } }) ``` diff --git a/docs/api/session.md b/docs/api/session.md index 527effc4d0b6..3ec443f884fd 100644 --- a/docs/api/session.md +++ b/docs/api/session.md @@ -1,4 +1,4 @@ -# session + # session > Manage browser sessions, cookies, cache, proxy settings, etc. @@ -8,12 +8,13 @@ You can also access the `session` of existing pages by using the `session` property of [`WebContents`](web-contents.md), or from the `session` module. ```javascript -const {session, BrowserWindow} = require('electron') +const {BrowserWindow} = require('electron') let win = new BrowserWindow({width: 800, height: 600}) win.loadURL('http://github.com') const ses = win.webContents.session +console.log(ses.getUserAgent()) ``` ## Methods @@ -52,9 +53,9 @@ Returns the default session object of the app. You can create a `Session` object in the `session` module: ```javascript -const session = require('electron').session; - -const ses = session.fromPartition('persist:name'); +const {session} = require('electron') +const ses = session.fromPartition('persist:name') +console.log(ses.getUserAgent()) ``` ### Instance Events @@ -73,12 +74,13 @@ Calling `event.preventDefault()` will cancel the download and `item` will not be available from next tick of the process. ```javascript +const {session} = require('electron') session.defaultSession.on('will-download', (event, item, webContents) => { - event.preventDefault(); + event.preventDefault() require('request')(item.getURL(), (data) => { - require('fs').writeFileSync('/somewhere', data); - }); -}); + require('fs').writeFileSync('/somewhere', data) + }) +}) ``` ### Instance Methods @@ -220,13 +222,13 @@ Emulates network with the given configuration for the `session`. ```javascript // To emulate a GPRS connection with 50kbps throughput and 500 ms latency. window.webContents.session.enableNetworkEmulation({ - latency: 500, - downloadThroughput: 6400, - uploadThroughput: 6400 -}); + latency: 500, + downloadThroughput: 6400, + uploadThroughput: 6400 +}) // To emulate a network outage. -window.webContents.session.enableNetworkEmulation({offline: true}); +window.webContents.session.enableNetworkEmulation({offline: true}) ``` #### `ses.disableNetworkEmulation()` @@ -247,12 +249,12 @@ Calling `setCertificateVerifyProc(null)` will revert back to default certificate verify proc. ```javascript -myWindow.webContents.session.setCertificateVerifyProc((hostname, cert, callback) => { - if (hostname === 'github.com') - callback(true); - else - callback(false); -}); +const {BrowserWindow} = require('electron') +let win = new BrowserWindow() + +win.webContents.session.setCertificateVerifyProc((hostname, cert, callback) => { + callback(hostname === 'github.com') +}) ``` #### `ses.setPermissionRequestHandler(handler)` @@ -267,16 +269,14 @@ Sets the handler which can be used to respond to permission requests for the `se Calling `callback(true)` will allow the permission and `callback(false)` will reject it. ```javascript -session.fromPartition(partition).setPermissionRequestHandler((webContents, permission, callback) => { - if (webContents.getURL() === host) { - if (permission === 'notifications') { - callback(false); // denied. - return; - } +const {session} = require('electron') +session.fromPartition('some-partition').setPermissionRequestHandler((webContents, permission, callback) => { + if (webContents.getURL() === 'some-host' && permission === 'notifications') { + return callback(false) // denied. } - callback(true); -}); + callback(true) +}) ``` #### `ses.clearHostResolverCache([callback])` @@ -294,6 +294,7 @@ Dynamically sets whether to always send credentials for HTTP NTLM or Negotiate authentication. ```javascript +const {session} = require('electron') // consider any url ending with `example.com`, `foobar.com`, `baz` // for integrated authentication. session.defaultSession.allowNTLMCredentialsForDomains('*example.com, *foobar.com, *baz') @@ -340,13 +341,12 @@ const {app, session} = require('electron') const path = require('path') app.on('ready', function () { - const protocol = session.fromPartition(partitionName).protocol + const protocol = session.fromPartition('some-partition').protocol protocol.registerFileProtocol('atom', function (request, callback) { var url = request.url.substr(7) - callback({path: path.normalize(__dirname + '/' + url)}) + callback({path: path.normalize(`${__dirname}/${url}`)}) }, function (error) { - if (error) - console.error('Failed to register protocol') + if (error) console.error('Failed to register protocol') }) }) ``` @@ -359,22 +359,23 @@ The `Cookies` class gives you ability to query and modify cookies. Instances of For example: ```javascript +const {session} = require('electron') + // Query all cookies. session.defaultSession.cookies.get({}, (error, cookies) => { - console.log(cookies) + console.log(error, cookies) }) // Query all cookies associated with a specific url. session.defaultSession.cookies.get({url: 'http://www.github.com'}, (error, cookies) => { - console.log(cookies) + console.log(error, cookies) }) // 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, (error) => { - if (error) - console.error(error) + if (error) console.error(error) }) ``` @@ -464,13 +465,15 @@ called with an `response` object when `listener` has done its work. An example of adding `User-Agent` header for requests: ```javascript +const {session} = require('electron') + // 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, (details, callback) => { - details.requestHeaders['User-Agent'] = "MyAgent" + details.requestHeaders['User-Agent'] = 'MyAgent' callback({cancel: false, requestHeaders: details.requestHeaders}) }) ``` diff --git a/docs/api/shell.md b/docs/api/shell.md index a1c01f0fbc77..5963246caf2c 100644 --- a/docs/api/shell.md +++ b/docs/api/shell.md @@ -7,9 +7,9 @@ The `shell` module provides functions related to desktop integration. An example of opening a URL in the user's default browser: ```javascript -const {shell} = require('electron'); +const {shell} = require('electron') -shell.openExternal('https://github.com'); +shell.openExternal('https://github.com') ``` ## Methods diff --git a/docs/api/synopsis.md b/docs/api/synopsis.md index a83ffc41f7b9..8a7fca71b77d 100644 --- a/docs/api/synopsis.md +++ b/docs/api/synopsis.md @@ -19,14 +19,13 @@ scripts to be able to use those modules. The main process script is just like a normal Node.js script: ```javascript -const {app, BrowserWindow} = require('electron'); - -let win = null; +const {app, BrowserWindow} = require('electron') +let win = null app.on('ready', () => { - win = new BrowserWindow({width: 800, height: 600}); - win.loadURL('https://github.com'); -}); + win = new BrowserWindow({width: 800, height: 600}) + win.loadURL('https://github.com') +}) ``` The renderer process is no different than a normal web page, except for the @@ -37,8 +36,8 @@ extra ability to use node modules: @@ -53,23 +52,43 @@ As of 0.37, you can use built-in modules. ```javascript -const {app, BrowserWindow} = require('electron'); +const {app, BrowserWindow} = require('electron') + +let win + +app.on('ready', () => { + win = new BrowserWindow() + win.loadURL('https://github.com') +}) ``` If you need the entire `electron` module, you can require it and then using destructuring to access the individual modules from `electron`. ```javascript -const electron = require('electron'); -const {app, BrowserWindow} = electron; +const electron = require('electron') +const {app, BrowserWindow} = electron + +let win + +app.on('ready', () => { + win = new BrowserWindow() + win.loadURL('https://github.com') +}) ``` This is equivalent to the following code: ```javascript -const electron = require('electron'); -const app = electron.app; -const BrowserWindow = electron.BrowserWindow; +const electron = require('electron') +const app = electron.app +const BrowserWindow = electron.BrowserWindow +let win + +app.on('ready', () => { + win = new BrowserWindow() + win.loadURL('https://github.com') +}) ``` [gui]: https://en.wikipedia.org/wiki/Graphical_user_interface diff --git a/docs/api/system-preferences.md b/docs/api/system-preferences.md index 586422e56654..8a2af28a295d 100644 --- a/docs/api/system-preferences.md +++ b/docs/api/system-preferences.md @@ -3,8 +3,8 @@ > Get system preferences. ```javascript -const {systemPreferences} = require('electron'); -console.log(systemPreferences.isDarkMode()); +const {systemPreferences} = require('electron') +console.log(systemPreferences.isDarkMode()) ``` ## Methods @@ -79,23 +79,24 @@ An example of using it to determine if you should create a transparent window or not (transparent windows won't work correctly when DWM composition is disabled): ```javascript -let browserOptions = {width: 1000, height: 800}; +const {BrowserWindow, systemPreferences} = require('electron') +let browserOptions = {width: 1000, height: 800} // Make the window transparent only if the platform supports it. if (process.platform !== 'win32' || systemPreferences.isAeroGlassEnabled()) { - browserOptions.transparent = true; - browserOptions.frame = false; + browserOptions.transparent = true + browserOptions.frame = false } // Create the window. -let win = new BrowserWindow(browserOptions); +let win = new BrowserWindow(browserOptions) // Navigate. if (browserOptions.transparent) { - win.loadURL('file://' + __dirname + '/index.html'); + win.loadURL(`file://${__dirname}/index.html`) } else { // No transparency, so we load a fallback that uses basic styles. - win.loadURL('file://' + __dirname + '/fallback.html'); + win.loadURL(`file://${__dirname}/fallback.html`) } ``` diff --git a/docs/api/tray.md b/docs/api/tray.md index 5ab68c5e9445..5e2536e95eac 100644 --- a/docs/api/tray.md +++ b/docs/api/tray.md @@ -13,7 +13,7 @@ app.on('ready', () => { {label: 'Item2', type: 'radio'}, {label: 'Item3', type: 'radio', checked: true}, {label: 'Item4', type: 'radio'} - ]); + ]) tray.setToolTip('This is my application.') tray.setContextMenu(contextMenu) }) @@ -31,8 +31,18 @@ __Platform limitations:__ you have to call `setContextMenu` again. For example: ```javascript -contextMenu.items[2].checked = false; -appIcon.setContextMenu(contextMenu); +const {Menu, Tray} = require('electron') +const appIcon = new Tray('/path/to/my/icon') +const contextMenu = Menu.buildFromTemplate([ + {label: 'Item1', type: 'radio'}, + {label: 'Item2', type: 'radio'} +]) + +// Make a change to the context menu +contextMenu.items[2].checked = false + +// Call this again for Linux because we modified the context menu +appIcon.setContextMenu(contextMenu) ``` * On Windows it is recommended to use `ICO` icons to get best visual effects. diff --git a/docs/api/web-contents.md b/docs/api/web-contents.md index 714d61eced95..5cff48af9738 100644 --- a/docs/api/web-contents.md +++ b/docs/api/web-contents.md @@ -9,12 +9,13 @@ the [`BrowserWindow`](browser-window.md) object. An example of accessing the `webContents` object: ```javascript -const {BrowserWindow} = require('electron'); +const {BrowserWindow} = require('electron') -let win = new BrowserWindow({width: 800, height: 1500}); -win.loadURL('http://github.com'); +let win = new BrowserWindow({width: 800, height: 1500}) +win.loadURL('http://github.com') -let contents = win.webContents; +let contents = win.webContents +console.log(contents) ``` ## Methods @@ -22,7 +23,8 @@ let contents = win.webContents; These methods can be accessed from the `webContents` module: ```js -const {webContents} = require('electron'); +const {webContents} = require('electron') +console.log(webContents) ``` ### `webContents.getAllWebContents()` @@ -431,6 +433,7 @@ first available device will be selected. `callback` should be called with cancel the request. ```javascript +const {app, webContents} = require('electron') app.commandLine.appendSwitch('enable-web-bluetooth') app.on('ready', () => { @@ -467,8 +470,9 @@ e.g. the `http://` or `file://`. If the load should bypass http cache then use the `pragma` header to achieve it. ```javascript -const options = {extraHeaders: 'pragma: no-cache\n'}; -webContents.loadURL(url, options); +const {webContents} = require('electron') +const options = {extraHeaders: 'pragma: no-cache\n'} +webContents.loadURL('https://github.com', options) ``` #### `contents.downloadURL(url)` @@ -483,10 +487,12 @@ Initiates a download of the resource at `url` without navigating. The Returns URL of the current web page. ```javascript -let win = new BrowserWindow({width: 800, height: 600}); -win.loadURL('http://github.com'); +const {BrowserWindow} = require('electron') +let win = new BrowserWindow({width: 800, height: 600}) +win.loadURL('http://github.com') -let currentURL = win.webContents.getURL(); +let currentURL = win.webContents.getURL() +console.log(currentURL) ``` #### `contents.getTitle()` @@ -690,12 +696,13 @@ the request can be obtained by subscribing to Stops any `findInPage` request for the `webContents` with the provided `action`. ```javascript +const {webContents} = require('electron') webContents.on('found-in-page', (event, result) => { - if (result.finalUpdate) - webContents.stopFindInPage('clearSelection'); -}); + if (result.finalUpdate) webContents.stopFindInPage('clearSelection') +}) -const requestId = webContents.findInPage('api'); +const requestId = webContents.findInPage('api') +console.log(requestId) ``` #### `contents.capturePage([rect, ]callback)` @@ -761,7 +768,7 @@ The `callback` will be called with `callback(error, data)` on completion. The By default, an empty `options` will be regarded as: -```javascript +``` { marginsType: 0, printBackground: false, @@ -773,23 +780,22 @@ By default, an empty `options` will be regarded as: An example of `webContents.printToPDF`: ```javascript -const {BrowserWindow} = require('electron'); -const fs = require('fs'); +const {BrowserWindow} = require('electron') +const fs = require('fs') -let win = new BrowserWindow({width: 800, height: 600}); -win.loadURL('http://github.com'); +let win = new BrowserWindow({width: 800, height: 600}) +win.loadURL('http://github.com') win.webContents.on('did-finish-load', () => { // Use default printing options win.webContents.printToPDF({}, (error, data) => { - if (error) throw error; + if (error) throw error fs.writeFile('/tmp/print.pdf', data, (error) => { - if (error) - throw error; - console.log('Write PDF successfully.'); - }); - }); -}); + if (error) throw error + console.log('Write PDF successfully.') + }) + }) +}) ``` #### `contents.addWorkSpace(path)` @@ -800,9 +806,11 @@ Adds the specified path to DevTools workspace. Must be used after DevTools creation: ```javascript +const {BrowserWindow} = require('electron') +let win = new BrowserWindow() win.webContents.on('devtools-opened', () => { - win.webContents.addWorkSpace(__dirname); -}); + win.webContents.addWorkSpace(__dirname) +}) ``` #### `contents.removeWorkSpace(path)` @@ -863,15 +871,16 @@ An example of sending messages from the main process to the renderer process: ```javascript // In the main process. -let win = null; +const {app, BrowserWindow} = require('electron') +let win = null app.on('ready', () => { - win = new BrowserWindow({width: 800, height: 600}); - win.loadURL(`file://${__dirname}/index.html`); + win = new BrowserWindow({width: 800, height: 600}) + win.loadURL(`file://${__dirname}/index.html`) win.webContents.on('did-finish-load', () => { - win.webContents.send('ping', 'whoooooooh!'); - }); -}); + win.webContents.send('ping', 'whoooooooh!') + }) +}) ``` ```html @@ -880,8 +889,8 @@ app.on('ready', () => { @@ -1010,14 +1019,16 @@ the cursor when dragging. Returns true if the process of saving page has been initiated successfully. ```javascript -win.loadURL('https://github.com'); +const {BrowserWindow} = require('electron') +let win = new BrowserWindow() + +win.loadURL('https://github.com') win.webContents.on('did-finish-load', () => { win.webContents.savePage('/tmp/test.html', 'HTMLComplete', (error) => { - if (!error) - console.log('Save page successfully'); - }); -}); + if (!error) console.log('Save page successfully') + }) +}) ``` #### `contents.showDefinitionForSelection()` _macOS_ @@ -1054,24 +1065,28 @@ Get the debugger instance for this webContents. Debugger API serves as an alternate transport for [remote debugging protocol][rdp]. ```javascript +const {BrowserWindow} = require('electron') +let win = new BrowserWindow() + try { - win.webContents.debugger.attach('1.1'); -} catch(err) { - console.log('Debugger attach failed : ', err); -}; + win.webContents.debugger.attach('1.1') +} catch (err) { + console.log('Debugger attach failed : ', err) +} win.webContents.debugger.on('detach', (event, reason) => { - console.log('Debugger detached due to : ', reason); -}); + console.log('Debugger detached due to : ', reason) +}) win.webContents.debugger.on('message', (event, method, params) => { if (method === 'Network.requestWillBeSent') { - if (params.request.url === 'https://www.github.com') - win.webContents.debugger.detach(); + if (params.request.url === 'https://www.github.com') { + win.webContents.debugger.detach() + } } -}); +}) -win.webContents.debugger.sendCommand('Network.enable'); +win.webContents.debugger.sendCommand('Network.enable') ``` ### Instance Methods diff --git a/docs/api/web-frame.md b/docs/api/web-frame.md index 1ad5589bfb4e..31ec91f93f9d 100644 --- a/docs/api/web-frame.md +++ b/docs/api/web-frame.md @@ -5,9 +5,9 @@ An example of zooming current page to 200%. ```javascript -const {webFrame} = require('electron'); +const {webFrame} = require('electron') -webFrame.setZoomFactor(2); +webFrame.setZoomFactor(2) ``` ## Methods @@ -58,11 +58,12 @@ whether the word passed is correctly spelled. An example of using [node-spellchecker][spellchecker] as provider: ```javascript +const {webFrame} = require('electron') webFrame.setSpellCheckProvider('en-US', true, { - spellCheck(text) { - return !(require('spellchecker').isMisspelled(text)); + spellCheck (text) { + return !(require('spellchecker').isMisspelled(text)) } -}); +}) ``` ### `webFrame.registerURLSchemeAsSecure(scheme)` @@ -112,12 +113,13 @@ Returns an object describing usage information of Blink's internal memory caches. ```javascript +const {webFrame} = require('electron') console.log(webFrame.getResourceUsage()) ``` This will generate: -```javascript +``` { images: { count: 22, @@ -130,8 +132,8 @@ This will generate: cssStyleSheets: { /* same with "images" */ }, xslStyleSheets: { /* same with "images" */ }, fonts: { /* same with "images" */ }, - other: { /* same with "images" */ }, -} + other: { /* same with "images" */ } +}) ``` ### `webFrame.clearCache()` diff --git a/docs/api/web-view-tag.md b/docs/api/web-view-tag.md index e5f6df811d47..8c93bf9fbb8e 100644 --- a/docs/api/web-view-tag.md +++ b/docs/api/web-view-tag.md @@ -12,7 +12,7 @@ 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 safe from the embedded content. -For security purpose, `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. ## Example @@ -35,20 +35,20 @@ and displays a "loading..." message during the load time: ```html ``` @@ -223,9 +223,10 @@ The `webview` tag has the following methods: **Example** ```javascript +const webview = document.getElementById('foo') webview.addEventListener('dom-ready', () => { - webview.openDevTools(); -}); + webview.openDevTools() +}) ``` ### `.loadURL(url[, options])` @@ -618,9 +619,10 @@ The following example code forwards all log messages to the embedder's console without regard for log level or other properties. ```javascript +const webview = document.getElementById('foo') webview.addEventListener('console-message', (e) => { - console.log('Guest page logged a message:', e.message); -}); + console.log('Guest page logged a message:', e.message) +}) ``` ### Event: 'found-in-page' @@ -638,12 +640,13 @@ Fired when a result is available for [`webview.findInPage`](web-view-tag.md#webviewtagfindinpage) request. ```javascript +const webview = document.getElementById('foo') webview.addEventListener('found-in-page', (e) => { - if (e.result.finalUpdate) - webview.stopFindInPage('keepSelection'); -}); + if (e.result.finalUpdate) webview.stopFindInPage('keepSelection') +}) -const requestId = webview.findInPage('test'); +const requestId = webview.findInPage('test') +console.log(requestId) ``` ### Event: 'new-window' @@ -662,14 +665,15 @@ Fired when the guest page attempts to open a new browser window. The following example code opens the new url in system's default browser. ```javascript -const {shell} = require('electron'); +const {shell} = require('electron') +const webview = document.getElementById('foo') webview.addEventListener('new-window', (e) => { - const protocol = require('url').parse(e.url).protocol; + const protocol = require('url').parse(e.url).protocol if (protocol === 'http:' || protocol === 'https:') { - shell.openExternal(e.url); + shell.openExternal(e.url) } -}); +}) ``` ### Event: 'will-navigate' @@ -722,9 +726,10 @@ The following example code navigates the `webview` to `about:blank` when the guest attempts to close itself. ```javascript +const webview = document.getElementById('foo') webview.addEventListener('close', () => { - webview.src = 'about:blank'; -}); + webview.src = 'about:blank' +}) ``` ### Event: 'ipc-message' @@ -741,19 +746,20 @@ between guest page and embedder page: ```javascript // In embedder page. +const webview = document.getElementById('foo') webview.addEventListener('ipc-message', (event) => { - console.log(event.channel); + console.log(event.channel) // Prints "pong" -}); -webview.send('ping'); +}) +webview.send('ping') ``` ```javascript // In guest page. -const {ipcRenderer} = require('electron'); +const {ipcRenderer} = require('electron') ipcRenderer.on('ping', () => { - ipcRenderer.sendToHost('pong'); -}); + ipcRenderer.sendToHost('pong') +}) ``` ### Event: 'crashed' diff --git a/docs/faq.md b/docs/faq.md index 51ebf687a41c..0079d43f4e26 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -36,17 +36,17 @@ renderers through the `remote` property of `electron` module: // In the main process. global.sharedObject = { someProperty: 'default value' -}; +} ``` ```javascript // In page 1. -require('electron').remote.getGlobal('sharedObject').someProperty = 'new value'; +require('electron').remote.getGlobal('sharedObject').someProperty = 'new value' ``` ```javascript // In page 2. -console.log(require('electron').remote.getGlobal('sharedObject').someProperty); +console.log(require('electron').remote.getGlobal('sharedObject').someProperty) ``` ## My app's window/tray disappeared after a few minutes. @@ -63,18 +63,22 @@ If you want a quick fix, you can make the variables global by changing your code from this: ```javascript +const {app, Tray} = require('electron') app.on('ready', () => { - const tray = new Tray('/path/to/icon.png'); -}); + const tray = new Tray('/path/to/icon.png') + tray.setTitle('hello world') +}) ``` to this: ```javascript -let tray = null; +const {app, Tray} = require('electron') +let tray = null app.on('ready', () => { - tray = new Tray('/path/to/icon.png'); -}); + tray = new Tray('/path/to/icon.png') + tray.setTitle('hello world') +}) ``` ## I can not use jQuery/RequireJS/Meteor/AngularJS in Electron. @@ -87,11 +91,13 @@ To solve this, you can turn off node integration in Electron: ```javascript // In the main process. +const {BrowserWindow} = require('electron') let win = new BrowserWindow({ webPreferences: { nodeIntegration: false } -}); +}) +win.show() ``` But if you want to keep the abilities of using Node.js and Electron APIs, you @@ -114,7 +120,7 @@ delete window.module; When using Electron's built-in module you might encounter an error like this: ``` -> require('electron').webFrame.setZoomFactor(1.0); +> require('electron').webFrame.setZoomFactor(1.0) Uncaught TypeError: Cannot read property 'setZoomLevel' of undefined ``` @@ -125,7 +131,7 @@ To verify whether you are using the correct built-in module, you can print the path of the `electron` module: ```javascript -console.log(require.resolve('electron')); +console.log(require.resolve('electron')) ``` and then check if it is in the following form: diff --git a/docs/tutorial/application-packaging.md b/docs/tutorial/application-packaging.md index 07caa681102a..819720122502 100644 --- a/docs/tutorial/application-packaging.md +++ b/docs/tutorial/application-packaging.md @@ -51,29 +51,29 @@ $ asar list /path/to/example.asar Read a file in the `asar` archive: ```javascript -const fs = require('fs'); -fs.readFileSync('/path/to/example.asar/file.txt'); +const fs = require('fs') +fs.readFileSync('/path/to/example.asar/file.txt') ``` List all files under the root of the archive: ```javascript -const fs = require('fs'); -fs.readdirSync('/path/to/example.asar'); +const fs = require('fs') +fs.readdirSync('/path/to/example.asar') ``` Use a module from the archive: ```javascript -require('/path/to/example.asar/dir/module.js'); +require('/path/to/example.asar/dir/module.js') ``` You can also display a web page in an `asar` archive with `BrowserWindow`: ```javascript -const {BrowserWindow} = require('electron'); -let win = new BrowserWindow({width: 800, height: 600}); -win.loadURL('file:///path/to/example.asar/static/index.html'); +const {BrowserWindow} = require('electron') +let win = new BrowserWindow({width: 800, height: 600}) +win.loadURL('file:///path/to/example.asar/static/index.html') ``` ### Web API @@ -85,10 +85,10 @@ For example, to get a file with `$.get`: ```html ``` @@ -99,16 +99,17 @@ content of an `asar` archive as a file. For this purpose you can use the built-i `original-fs` module which provides original `fs` APIs without `asar` support: ```javascript -const originalFs = require('original-fs'); -originalFs.readFileSync('/path/to/example.asar'); +const originalFs = require('original-fs') +originalFs.readFileSync('/path/to/example.asar') ``` You can also set `process.noAsar` to `true` to disable the support for `asar` in the `fs` module: ```javascript -process.noAsar = true; -fs.readFileSync('/path/to/example.asar'); +const fs = require('fs') +process.noAsar = true +fs.readFileSync('/path/to/example.asar') ``` ## Limitations of the Node API diff --git a/docs/tutorial/desktop-environment-integration.md b/docs/tutorial/desktop-environment-integration.md index 413106bb6357..343657092660 100644 --- a/docs/tutorial/desktop-environment-integration.md +++ b/docs/tutorial/desktop-environment-integration.md @@ -20,11 +20,11 @@ the currently running operating system's native notification APIs to display it. ```javascript let myNotification = new Notification('Title', { body: 'Lorem Ipsum Dolor Sit Amet' -}); +}) myNotification.onclick = () => { - console.log('Notification clicked'); -}; + console.log('Notification clicked') +} ``` While code and user experience across operating systems are similar, there @@ -75,14 +75,16 @@ To add a file to recent documents, you can use the [app.addRecentDocument][addrecentdocument] API: ```javascript -app.addRecentDocument('/Users/USERNAME/Desktop/work.type'); +const {app} = require('electron') +app.addRecentDocument('/Users/USERNAME/Desktop/work.type') ``` And you can use [app.clearRecentDocuments][clearrecentdocuments] API to empty the recent documents list: ```javascript -app.clearRecentDocuments(); +const {app} = require('electron') +app.clearRecentDocuments() ``` ### Windows Notes @@ -113,19 +115,17 @@ To set your custom dock menu, you can use the `app.dock.setMenu` API, which is only available on macOS: ```javascript -const electron = require('electron'); -const app = electron.app; -const Menu = electron.Menu; +const {app, Menu} = require('electron') const dockMenu = Menu.buildFromTemplate([ - { label: 'New Window', click() { console.log('New Window'); } }, - { label: 'New Window with Settings', submenu: [ - { label: 'Basic' }, - { label: 'Pro'} + {label: 'New Window', click () { console.log('New Window') }}, + {label: 'New Window with Settings', submenu: [ + {label: 'Basic'}, + {label: 'Pro'} ]}, - { label: 'New Command...'} -]); -app.dock.setMenu(dockMenu); + {label: 'New Command...'} +]) +app.dock.setMenu(dockMenu) ``` ## User Tasks (Windows) @@ -162,6 +162,7 @@ To set user tasks for your application, you can use [app.setUserTasks][setusertaskstasks] API: ```javascript +const {app} = require('electron') app.setUserTasks([ { program: process.execPath, @@ -171,13 +172,14 @@ app.setUserTasks([ title: 'New Window', description: 'Create a new window' } -]); +]) ``` To clean your tasks list, just call `app.setUserTasks` with an empty array: ```javascript -app.setUserTasks([]); +const {app} = require('electron') +app.setUserTasks([]) ``` The user tasks will still show even after your application closes, so the icon @@ -209,34 +211,36 @@ You can use [BrowserWindow.setThumbarButtons][setthumbarbuttons] to set thumbnail toolbar in your application: ```javascript -const {BrowserWindow} = require('electron'); -const path = require('path'); +const {BrowserWindow} = require('electron') +const path = require('path') let win = new BrowserWindow({ width: 800, height: 600 -}); +}) win.setThumbarButtons([ { tooltip: 'button1', icon: path.join(__dirname, 'button1.png'), - click() { console.log('button1 clicked'); } + click () { console.log('button1 clicked') } }, { tooltip: 'button2', icon: path.join(__dirname, 'button2.png'), flags: ['enabled', 'dismissonclick'], - click() { console.log('button2 clicked.'); } + click () { console.log('button2 clicked.') } } -]); +]) ``` To clean thumbnail toolbar buttons, just call `BrowserWindow.setThumbarButtons` with an empty array: ```javascript -win.setThumbarButtons([]); +const {BrowserWindow} = require('electron') +let win = new BrowserWindow() +win.setThumbarButtons([]) ``` ## Unity Launcher Shortcuts (Linux) @@ -267,8 +271,9 @@ To set the progress bar for a Window, you can use the [BrowserWindow.setProgressBar][setprogressbar] API: ```javascript -let win = new BrowserWindow({...}); -win.setProgressBar(0.5); +const {BrowserWindow} = require('electron') +let win = new BrowserWindow() +win.setProgressBar(0.5) ``` ## Icon Overlays in Taskbar (Windows) @@ -294,8 +299,9 @@ To set the overlay icon for a window, you can use the [BrowserWindow.setOverlayIcon][setoverlayicon] API: ```javascript -let win = new BrowserWindow({...}); -win.setOverlayIcon('path/to/overlay.png', 'Description for overlay'); +const {BrowserWindow} = require('electron') +let win = new BrowserWindow() +win.setOverlayIcon('path/to/overlay.png', 'Description for overlay') ``` ## Represented File of Window (macOS) @@ -316,9 +322,10 @@ To set the represented file of window, you can use the [BrowserWindow.setDocumentEdited][setdocumentedited] APIs: ```javascript -let win = new BrowserWindow({...}); -win.setRepresentedFilename('/etc/passwd'); -win.setDocumentEdited(true); +const {BrowserWindow} = require('electron') +let win = new BrowserWindow() +win.setRepresentedFilename('/etc/passwd') +win.setDocumentEdited(true) ``` ## Dragging files out of the window @@ -342,6 +349,7 @@ In web page: In the main process: ```javascript +const {ipcMain} = require('electron') ipcMain.on('ondragstart', (event, filePath) => { event.sender.startDrag({ file: filePath, diff --git a/docs/tutorial/online-offline-events.md b/docs/tutorial/online-offline-events.md index 2b7a6d7edc6a..f89e7fad78e4 100644 --- a/docs/tutorial/online-offline-events.md +++ b/docs/tutorial/online-offline-events.md @@ -6,16 +6,14 @@ using standard HTML5 APIs, as shown in the following example. _main.js_ ```javascript -const electron = require('electron'); -const app = electron.app; -const BrowserWindow = electron.BrowserWindow; +const {app, BrowserWindow} = require('electron') -let onlineStatusWindow; +let onlineStatusWindow app.on('ready', () => { - onlineStatusWindow = new BrowserWindow({ width: 0, height: 0, show: false }); - onlineStatusWindow.loadURL(`file://${__dirname}/online-status.html`); -}); + onlineStatusWindow = new BrowserWindow({ width: 0, height: 0, show: false }) + onlineStatusWindow.loadURL(`file://${__dirname}/online-status.html`) +}) ``` _online-status.html_ @@ -26,13 +24,13 @@ _online-status.html_ @@ -47,21 +45,17 @@ to the main process and handled as needed, as shown in the following example. _main.js_ ```javascript -const electron = require('electron'); -const app = electron.app; -const ipcMain = electron.ipcMain; -const BrowserWindow = electron.BrowserWindow; - -let onlineStatusWindow; +const {app, BrowserWindow, ipcMain} = require('electron') +let onlineStatusWindow app.on('ready', () => { - onlineStatusWindow = new BrowserWindow({ width: 0, height: 0, show: false }); - onlineStatusWindow.loadURL(`file://${__dirname}/online-status.html`); -}); + onlineStatusWindow = new BrowserWindow({ width: 0, height: 0, show: false }) + onlineStatusWindow.loadURL(`file://${__dirname}/online-status.html`) +}) ipcMain.on('online-status-changed', (event, status) => { - console.log(status); -}); + console.log(status) +}) ``` _online-status.html_ @@ -71,15 +65,15 @@ _online-status.html_ diff --git a/docs/tutorial/quick-start.md b/docs/tutorial/quick-start.md index 6abfd9133e8c..5ae2e81c8b49 100644 --- a/docs/tutorial/quick-start.md +++ b/docs/tutorial/quick-start.md @@ -80,56 +80,52 @@ The `main.js` should create windows and handle system events, a typical example being: ```javascript -const electron = require('electron'); -// Module to control application life. -const {app} = electron; -// Module to create native browser window. -const {BrowserWindow} = electron; +const {app, BrowserWindow} = require('electron') // Keep a global reference of the window object, if you don't, the window will // be closed automatically when the JavaScript object is garbage collected. -let win; +let win -function createWindow() { +function createWindow () { // Create the browser window. - win = new BrowserWindow({width: 800, height: 600}); + win = new BrowserWindow({width: 800, height: 600}) // and load the index.html of the app. - win.loadURL(`file://${__dirname}/index.html`); + win.loadURL(`file://${__dirname}/index.html`) // Open the DevTools. - win.webContents.openDevTools(); + win.webContents.openDevTools() // Emitted when the window is closed. win.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. - win = null; - }); + win = null + }) } // This method will be called when Electron has finished // initialization and is ready to create browser windows. // Some APIs can only be used after this event occurs. -app.on('ready', createWindow); +app.on('ready', createWindow) // Quit when all windows are closed. app.on('window-all-closed', () => { // On macOS it is common for applications and their menu bar // to stay active until the user quits explicitly with Cmd + Q if (process.platform !== 'darwin') { - app.quit(); + app.quit() } -}); +}) app.on('activate', () => { // On macOS it's common to re-create a window in the app when the // dock icon is clicked and there are no other windows open. if (win === null) { - createWindow(); + createWindow() } -}); +}) // In this file you can include the rest of your app's specific main process // code. You can also put them in separate files and require them here. diff --git a/docs/tutorial/using-pepper-flash-plugin.md b/docs/tutorial/using-pepper-flash-plugin.md index 349d7c0e9f63..4b4fab5867a1 100644 --- a/docs/tutorial/using-pepper-flash-plugin.md +++ b/docs/tutorial/using-pepper-flash-plugin.md @@ -20,6 +20,9 @@ before the app ready event. Also, turn on `plugins` option of `BrowserWindow`. For example: ```javascript +const {app, BrowserWindow} = require('electron') +const path = require('path') + // Specify flash path, supposing it is placed in the same directory with main.js. let pluginName switch (process.platform) { @@ -39,7 +42,7 @@ app.commandLine.appendSwitch('ppapi-flash-path', path.join(__dirname, pluginName app.commandLine.appendSwitch('ppapi-flash-version', '17.0.0.169') app.on('ready', () => { - win = new BrowserWindow({ + let win = new BrowserWindow({ width: 800, height: 600, webPreferences: { @@ -48,7 +51,7 @@ app.on('ready', () => { }) win.loadURL(`file://${__dirname}/index.html`) // Something else -}); +}) ``` You can also try loading the system wide Pepper Flash plugin instead of shipping diff --git a/docs/tutorial/using-selenium-and-webdriver.md b/docs/tutorial/using-selenium-and-webdriver.md index 50727c7169f4..8e6961697764 100644 --- a/docs/tutorial/using-selenium-and-webdriver.md +++ b/docs/tutorial/using-selenium-and-webdriver.md @@ -79,7 +79,7 @@ upstream, except that you have to manually specify how to connect chrome driver and where to find Electron's binary: ```javascript -const webdriver = require('selenium-webdriver'); +const webdriver = require('selenium-webdriver') const driver = new webdriver.Builder() // The "9515" is the port opened by chrome driver. @@ -87,22 +87,22 @@ const driver = new webdriver.Builder() .withCapabilities({ chromeOptions: { // Here is the path to your Electron binary. - binary: '/Path-to-Your-App.app/Contents/MacOS/Electron', + binary: '/Path-to-Your-App.app/Contents/MacOS/Electron' } }) .forBrowser('electron') - .build(); + .build() -driver.get('http://www.google.com'); -driver.findElement(webdriver.By.name('q')).sendKeys('webdriver'); -driver.findElement(webdriver.By.name('btnG')).click(); +driver.get('http://www.google.com') +driver.findElement(webdriver.By.name('q')).sendKeys('webdriver') +driver.findElement(webdriver.By.name('btnG')).click() driver.wait(() => { - return driver.getTitle().then((title) => { - return title === 'webdriver - Google Search'; - }); -}, 1000); + return driver.getTitle().then((title) => { + return title === 'webdriver - Google Search' + }) +}, 1000) -driver.quit(); +driver.quit() ``` ## Setting up with WebdriverIO @@ -132,7 +132,7 @@ $ npm install webdriverio ### 3. Connect to chrome driver ```javascript -const webdriverio = require('webdriverio'); +const webdriverio = require('webdriverio') const options = { host: 'localhost', // Use localhost as chrome driver server port: 9515, // "9515" is the port opened by chrome driver. @@ -143,9 +143,9 @@ const options = { args: [/* cli arguments */] // Optional, perhaps 'app=' + /path/to/your/app/ } } -}; +} -let client = webdriverio.remote(options); +let client = webdriverio.remote(options) client .init() @@ -153,9 +153,9 @@ client .setValue('#q', 'webdriverio') .click('#btnG') .getTitle().then((title) => { - console.log('Title was: ' + title); + console.log('Title was: ' + title) }) - .end(); + .end() ``` ## Workflow diff --git a/docs/tutorial/using-widevine-cdm-plugin.md b/docs/tutorial/using-widevine-cdm-plugin.md index b7b37c58a991..512da7a041a1 100644 --- a/docs/tutorial/using-widevine-cdm-plugin.md +++ b/docs/tutorial/using-widevine-cdm-plugin.md @@ -51,23 +51,26 @@ enabled. Example code: ```javascript +const {app, BrowserWindow} = require('electron') + // You have to pass the filename of `widevinecdmadapter` here, it is // * `widevinecdmadapter.plugin` on macOS, // * `libwidevinecdmadapter.so` on Linux, // * `widevinecdmadapter.dll` on Windows. -app.commandLine.appendSwitch('widevine-cdm-path', '/path/to/widevinecdmadapter.plugin'); +app.commandLine.appendSwitch('widevine-cdm-path', '/path/to/widevinecdmadapter.plugin') // The version of plugin can be got from `chrome://plugins` page in Chrome. -app.commandLine.appendSwitch('widevine-cdm-version', '1.4.8.866'); +app.commandLine.appendSwitch('widevine-cdm-version', '1.4.8.866') -let win = null; +let win = null app.on('ready', () => { win = new BrowserWindow({ webPreferences: { // The `plugins` have to be enabled. plugins: true } - }); -}); + }) + win.show() +}) ``` ## Verifying the plugin From 784bee8faaae527ab022da1f2c442b0352a5fb66 Mon Sep 17 00:00:00 2001 From: Zeke Sikelianos Date: Mon, 25 Jul 2016 18:48:48 -0700 Subject: [PATCH 24/74] update `npm run lint` to include docs --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 73229386bc6b..ea5a249597fe 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "scripts": { "bootstrap": "python ./script/bootstrap.py", "build": "python ./script/build.py -c D", - "lint": "npm run lint-js && npm run lint-cpp", + "lint": "npm run lint-js && npm run lint-cpp && npm run lint-docs", "lint-js": "standard && cd spec && standard", "lint-cpp": "python ./script/cpplint.py", "lint-docs": "standard-markdown docs", From 3a16a9b0bf73d4bd96ca908111726b07888eb5c9 Mon Sep 17 00:00:00 2001 From: Zeke Sikelianos Date: Tue, 26 Jul 2016 09:17:27 -0700 Subject: [PATCH 25/74] update to standard-markdown with node 0.10 support --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ea5a249597fe..295dafc11046 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "asar": "^0.11.0", "request": "*", "standard": "^7.1.2", - "standard-markdown": "^1.0.3" + "standard-markdown": "^1.1.0" }, "optionalDependencies": { "runas": "^3.0.0" From c38f505001872a8103ae857a918c7cd6596258fd Mon Sep 17 00:00:00 2001 From: Zeke Sikelianos Date: Tue, 26 Jul 2016 09:43:22 -0700 Subject: [PATCH 26/74] update standard-markdown to use linux-friendly local require statements --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 295dafc11046..20a3a71c0948 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "asar": "^0.11.0", "request": "*", "standard": "^7.1.2", - "standard-markdown": "^1.1.0" + "standard-markdown": "^1.1.1" }, "optionalDependencies": { "runas": "^3.0.0" From c3f26df57793dd26150733c23e452b7742cd76d1 Mon Sep 17 00:00:00 2001 From: Zeke Sikelianos Date: Tue, 26 Jul 2016 09:49:02 -0700 Subject: [PATCH 27/74] standardize electron-capturer doc snippets --- docs/api/desktop-capturer.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/api/desktop-capturer.md b/docs/api/desktop-capturer.md index a636c7be03d3..af35b11fdeeb 100644 --- a/docs/api/desktop-capturer.md +++ b/docs/api/desktop-capturer.md @@ -32,11 +32,11 @@ desktopCapturer.getSources({types: ['window', 'screen']}, (error, sources) => { } }) -function handleStream(stream) { +function handleStream (stream) { document.querySelector('video').src = URL.createObjectURL(stream) } -function handleError(e) { +function handleError (e) { console.log(e) } ``` From 895638880482e55db07cf3aa234247e9fdd828d3 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 26 Jul 2016 10:40:11 -0700 Subject: [PATCH 28/74] Only register code range once --- atom/common/crash_reporter/crash_reporter_win.cc | 16 +++++++++------- atom/common/crash_reporter/crash_reporter_win.h | 1 + 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/atom/common/crash_reporter/crash_reporter_win.cc b/atom/common/crash_reporter/crash_reporter_win.cc index 7ce61d69719c..9d066aa517a4 100644 --- a/atom/common/crash_reporter/crash_reporter_win.cc +++ b/atom/common/crash_reporter/crash_reporter_win.cc @@ -138,7 +138,8 @@ void UnregisterNonABICompliantCodeRange(void* start) { } // namespace CrashReporterWin::CrashReporterWin() - : skip_system_crash_handler_(false) { + : skip_system_crash_handler_(false), + code_range_registered_(false) { } CrashReporterWin::~CrashReporterWin() { @@ -189,19 +190,20 @@ void CrashReporterWin::InitBreakpad(const std::string& product_name, LOG(ERROR) << "Cannot initialize out-of-process crash handler"; #ifdef _WIN64 - bool registered = false; // Hook up V8 to breakpad. - { + if (!code_range_registered_) { // gin::Debug::SetCodeRangeCreatedCallback only runs the callback when // Isolate is just created, so we have to manually run following code here. void* code_range = nullptr; size_t size = 0; v8::Isolate::GetCurrent()->GetCodeRange(&code_range, &size); - if (code_range && size) - registered = RegisterNonABICompliantCodeRange(code_range, size); + if (code_range && size && + RegisterNonABICompliantCodeRange(code_range, size)) { + code_range_registered_ = true; + gin::Debug::SetCodeRangeDeletedCallback( + UnregisterNonABICompliantCodeRange); + } } - if (registered) - gin::Debug::SetCodeRangeDeletedCallback(UnregisterNonABICompliantCodeRange); #endif } diff --git a/atom/common/crash_reporter/crash_reporter_win.h b/atom/common/crash_reporter/crash_reporter_win.h index 93be8af32cdb..806c3de317b2 100644 --- a/atom/common/crash_reporter/crash_reporter_win.h +++ b/atom/common/crash_reporter/crash_reporter_win.h @@ -62,6 +62,7 @@ class CrashReporterWin : public CrashReporter { google_breakpad::CustomClientInfo custom_info_; bool skip_system_crash_handler_; + bool code_range_registered_; std::unique_ptr breakpad_; DISALLOW_COPY_AND_ASSIGN(CrashReporterWin); From 3279f5c80ae2d3f4beca0edccf503ea7537dabdb Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 26 Jul 2016 10:43:57 -0700 Subject: [PATCH 29/74] Add spec for starting crash reporter twice --- spec/api-crash-reporter-spec.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/spec/api-crash-reporter-spec.js b/spec/api-crash-reporter-spec.js index b4f63fe1ef27..16806c7102c4 100644 --- a/spec/api-crash-reporter-spec.js +++ b/spec/api-crash-reporter-spec.js @@ -88,5 +88,19 @@ describe('crash-reporter module', function () { }) }, /companyName is a required option to crashReporter\.start/) }) + + it('can be called multiple times', function () { + assert.doesNotThrow(function () { + crashReporter.start({ + companyName: 'Umbrella Corporation', + submitURL: 'http://127.0.0.1/crashes' + }) + + crashReporter.start({ + companyName: 'Umbrella Corporation 2', + submitURL: 'http://127.0.0.1/more-crashes' + }) + }) + }) }) }) From 7a1b796dd05d62f2eec8620a263b874de51a66c8 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 26 Jul 2016 10:50:12 -0700 Subject: [PATCH 30/74] Change registered flag from InitBreakpad --- atom/common/crash_reporter/crash_reporter_win.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atom/common/crash_reporter/crash_reporter_win.cc b/atom/common/crash_reporter/crash_reporter_win.cc index 9d066aa517a4..3261f9c9c6a3 100644 --- a/atom/common/crash_reporter/crash_reporter_win.cc +++ b/atom/common/crash_reporter/crash_reporter_win.cc @@ -192,6 +192,7 @@ void CrashReporterWin::InitBreakpad(const std::string& product_name, #ifdef _WIN64 // Hook up V8 to breakpad. if (!code_range_registered_) { + code_range_registered_ = true; // gin::Debug::SetCodeRangeCreatedCallback only runs the callback when // Isolate is just created, so we have to manually run following code here. void* code_range = nullptr; @@ -199,7 +200,6 @@ void CrashReporterWin::InitBreakpad(const std::string& product_name, v8::Isolate::GetCurrent()->GetCodeRange(&code_range, &size); if (code_range && size && RegisterNonABICompliantCodeRange(code_range, size)) { - code_range_registered_ = true; gin::Debug::SetCodeRangeDeletedCallback( UnregisterNonABICompliantCodeRange); } From bde432b64de86c2a232b8054a565872e43a7f8b8 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 26 Jul 2016 12:06:11 -0700 Subject: [PATCH 31/74] Add webContents.copyImageAt --- atom/browser/api/atom_api_web_contents.cc | 7 +++++++ atom/browser/api/atom_api_web_contents.h | 1 + 2 files changed, 8 insertions(+) diff --git a/atom/browser/api/atom_api_web_contents.cc b/atom/browser/api/atom_api_web_contents.cc index 1042e4080d33..0083ac71eb96 100644 --- a/atom/browser/api/atom_api_web_contents.cc +++ b/atom/browser/api/atom_api_web_contents.cc @@ -1146,6 +1146,12 @@ void WebContents::ShowDefinitionForSelection() { #endif } +void WebContents::CopyImageAt(int x, int y) { + const auto host = web_contents()->GetRenderViewHost(); + if (host) + host->CopyImageAt(x, y); +} + void WebContents::Focus() { web_contents()->Focus(); } @@ -1425,6 +1431,7 @@ void WebContents::BuildPrototype(v8::Isolate* isolate, .SetMethod("removeWorkSpace", &WebContents::RemoveWorkSpace) .SetMethod("showDefinitionForSelection", &WebContents::ShowDefinitionForSelection) + .SetMethod("copyImageAt", &WebContents::CopyImageAt) .SetMethod("capturePage", &WebContents::CapturePage) .SetMethod("isFocused", &WebContents::IsFocused) .SetProperty("id", &WebContents::ID) diff --git a/atom/browser/api/atom_api_web_contents.h b/atom/browser/api/atom_api_web_contents.h index f55ab9ca4410..381f3770d398 100644 --- a/atom/browser/api/atom_api_web_contents.h +++ b/atom/browser/api/atom_api_web_contents.h @@ -126,6 +126,7 @@ class WebContents : public mate::TrackableObject, uint32_t FindInPage(mate::Arguments* args); void StopFindInPage(content::StopFindAction action); void ShowDefinitionForSelection(); + void CopyImageAt(int x, int y); // Focus. void Focus(); From 31564f079f1979e728b7c5b48b0b66267beda7a5 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 26 Jul 2016 12:08:05 -0700 Subject: [PATCH 32/74] Document webContents.copyImageAt(x,y) --- docs/api/web-contents.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/api/web-contents.md b/docs/api/web-contents.md index 714d61eced95..709886e283ed 100644 --- a/docs/api/web-contents.md +++ b/docs/api/web-contents.md @@ -620,6 +620,13 @@ Executes the editing command `cut` in web page. Executes the editing command `copy` in web page. +### `contents.copyImageAt(x, y)` + +* `x` Integer +* `y` Integer + +Copy the image at the given position to the clipboard. + #### `contents.paste()` Executes the editing command `paste` in web page. From b2f9cce2973bc396519d1203efae1fc27ae850ce Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 26 Jul 2016 13:51:43 -0700 Subject: [PATCH 33/74] Add option to always highlight the tray icon --- atom/browser/api/atom_api_tray.cc | 43 +++++++++++++++++++++++++++--- atom/browser/api/atom_api_tray.h | 3 ++- atom/browser/ui/tray_icon.cc | 2 +- atom/browser/ui/tray_icon.h | 10 ++++--- atom/browser/ui/tray_icon_cocoa.h | 2 +- atom/browser/ui/tray_icon_cocoa.mm | 26 +++++++++++------- 6 files changed, 67 insertions(+), 19 deletions(-) diff --git a/atom/browser/api/atom_api_tray.cc b/atom/browser/api/atom_api_tray.cc index e576601449d6..3b804972cbf7 100644 --- a/atom/browser/api/atom_api_tray.cc +++ b/atom/browser/api/atom_api_tray.cc @@ -8,7 +8,6 @@ #include "atom/browser/api/atom_api_menu.h" #include "atom/browser/browser.h" -#include "atom/browser/ui/tray_icon.h" #include "atom/common/api/atom_api_native_image.h" #include "atom/common/native_mate_converters/gfx_converter.h" #include "atom/common/native_mate_converters/image_converter.h" @@ -18,6 +17,44 @@ #include "native_mate/dictionary.h" #include "ui/gfx/image/image.h" +namespace mate { + +template<> +struct Converter { + static bool FromV8(v8::Isolate* isolate, v8::Local val, + atom::TrayIcon::HighlightMode* out) { + std::string mode; + if (ConvertFromV8(isolate, val, &mode)) { + if (mode == "always") { + *out = atom::TrayIcon::HighlightMode::ALWAYS; + return true; + } + if (mode == "selection") { + *out = atom::TrayIcon::HighlightMode::SELECTION; + return true; + } + if (mode == "never") { + *out = atom::TrayIcon::HighlightMode::NEVER; + return true; + } + } + + // Support old boolean parameter + bool hightlight; + if (ConvertFromV8(isolate, val, &hightlight)) { + if (hightlight) + *out = atom::TrayIcon::HighlightMode::SELECTION; + else + *out = atom::TrayIcon::HighlightMode::NEVER; + return true; + } + + return false; + } +}; +} // namespace mate + + namespace atom { namespace api { @@ -117,8 +154,8 @@ void Tray::SetTitle(const std::string& title) { tray_icon_->SetTitle(title); } -void Tray::SetHighlightMode(bool highlight) { - tray_icon_->SetHighlightMode(highlight); +void Tray::SetHighlightMode(TrayIcon::HighlightMode mode) { + tray_icon_->SetHighlightMode(mode); } void Tray::DisplayBalloon(mate::Arguments* args, diff --git a/atom/browser/api/atom_api_tray.h b/atom/browser/api/atom_api_tray.h index 56d851d44e76..1e1bc1307543 100644 --- a/atom/browser/api/atom_api_tray.h +++ b/atom/browser/api/atom_api_tray.h @@ -10,6 +10,7 @@ #include #include "atom/browser/api/trackable_object.h" +#include "atom/browser/ui/tray_icon.h" #include "atom/browser/ui/tray_icon_observer.h" #include "native_mate/handle.h" @@ -62,7 +63,7 @@ class Tray : public mate::TrackableObject, void SetPressedImage(v8::Isolate* isolate, mate::Handle image); void SetToolTip(const std::string& tool_tip); void SetTitle(const std::string& title); - void SetHighlightMode(bool highlight); + void SetHighlightMode(TrayIcon::HighlightMode mode); void DisplayBalloon(mate::Arguments* args, const mate::Dictionary& options); void PopUpContextMenu(mate::Arguments* args); void SetContextMenu(v8::Isolate* isolate, mate::Handle menu); diff --git a/atom/browser/ui/tray_icon.cc b/atom/browser/ui/tray_icon.cc index 6e54af92757d..273f5c084391 100644 --- a/atom/browser/ui/tray_icon.cc +++ b/atom/browser/ui/tray_icon.cc @@ -18,7 +18,7 @@ void TrayIcon::SetPressedImage(ImageType image) { void TrayIcon::SetTitle(const std::string& title) { } -void TrayIcon::SetHighlightMode(bool highlight) { +void TrayIcon::SetHighlightMode(TrayIcon::HighlightMode mode) { } void TrayIcon::DisplayBalloon(ImageType icon, diff --git a/atom/browser/ui/tray_icon.h b/atom/browser/ui/tray_icon.h index 78981d076c77..90e81657aae2 100644 --- a/atom/browser/ui/tray_icon.h +++ b/atom/browser/ui/tray_icon.h @@ -43,9 +43,13 @@ class TrayIcon { // only works on macOS. virtual void SetTitle(const std::string& title); - // Sets whether the status icon is highlighted when it is clicked. This only - // works on macOS. - virtual void SetHighlightMode(bool highlight); + // Sets the status icon highlight mode. This only works on macOS. + enum HighlightMode { + ALWAYS, // Always highlight the tray icon + NEVER, // Never highlight the tray icon + SELECTION // Highlight the tray icon when clicked or the menu is opened + }; + virtual void SetHighlightMode(HighlightMode mode); // Displays a notification balloon with the specified contents. // Depending on the platform it might not appear by the icon tray. diff --git a/atom/browser/ui/tray_icon_cocoa.h b/atom/browser/ui/tray_icon_cocoa.h index e9abaa5590e3..fb66a6b3f147 100644 --- a/atom/browser/ui/tray_icon_cocoa.h +++ b/atom/browser/ui/tray_icon_cocoa.h @@ -27,7 +27,7 @@ class TrayIconCocoa : public TrayIcon, void SetPressedImage(const gfx::Image& image) override; void SetToolTip(const std::string& tool_tip) override; void SetTitle(const std::string& title) override; - void SetHighlightMode(bool highlight) override; + void SetHighlightMode(TrayIcon::HighlightMode mode) override; void PopUpContextMenu(const gfx::Point& pos, AtomMenuModel* menu_model) override; void SetContextMenu(AtomMenuModel* menu_model) override; diff --git a/atom/browser/ui/tray_icon_cocoa.mm b/atom/browser/ui/tray_icon_cocoa.mm index 231f1bfb1282..5ac9050b622c 100644 --- a/atom/browser/ui/tray_icon_cocoa.mm +++ b/atom/browser/ui/tray_icon_cocoa.mm @@ -23,7 +23,7 @@ const CGFloat kVerticalTitleMargin = 2; @interface StatusItemView : NSView { atom::TrayIconCocoa* trayIcon_; // weak AtomMenuController* menuController_; // weak - BOOL isHighlightEnable_; + atom::TrayIcon::HighlightMode highlight_mode_; BOOL forceHighlight_; BOOL inMouseEventSequence_; base::scoped_nsobject image_; @@ -39,7 +39,7 @@ const CGFloat kVerticalTitleMargin = 2; - (id)initWithImage:(NSImage*)image icon:(atom::TrayIconCocoa*)icon { image_.reset([image copy]); trayIcon_ = icon; - isHighlightEnable_ = YES; + highlight_mode_ = atom::TrayIcon::HighlightMode::SELECTION; forceHighlight_ = NO; inMouseEventSequence_ = NO; @@ -192,8 +192,9 @@ const CGFloat kVerticalTitleMargin = 2; alternateImage_.reset([image copy]); } -- (void)setHighlight:(BOOL)highlight { - isHighlightEnable_ = highlight; +- (void)setHighlight:(atom::TrayIcon::HighlightMode)mode { + highlight_mode_ = mode; + [self setNeedsDisplay:YES]; } - (void)setTitle:(NSString*)title { @@ -328,10 +329,15 @@ const CGFloat kVerticalTitleMargin = 2; } - (BOOL)shouldHighlight { - if (isHighlightEnable_ && forceHighlight_) - return true; - BOOL isMenuOpen = menuController_ && [menuController_ isMenuOpen]; - return isHighlightEnable_ && (inMouseEventSequence_ || isMenuOpen); + switch (highlight_mode_) { + case atom::TrayIcon::HighlightMode::ALWAYS: + return true; + case atom::TrayIcon::HighlightMode::NEVER: + return false; + case atom::TrayIcon::HighlightMode::SELECTION: + BOOL isMenuOpen = menuController_ && [menuController_ isMenuOpen]; + return forceHighlight_ || inMouseEventSequence_ || isMenuOpen; + } } @end @@ -369,8 +375,8 @@ void TrayIconCocoa::SetTitle(const std::string& title) { [status_item_view_ setTitle:base::SysUTF8ToNSString(title)]; } -void TrayIconCocoa::SetHighlightMode(bool highlight) { - [status_item_view_ setHighlight:highlight]; +void TrayIconCocoa::SetHighlightMode(TrayIcon::HighlightMode mode) { + [status_item_view_ setHighlight:mode]; } void TrayIconCocoa::PopUpContextMenu(const gfx::Point& pos, From 0ff6b87f8cbe6e6da76a112915b6eea810a0bef1 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 26 Jul 2016 13:59:19 -0700 Subject: [PATCH 34/74] Update tray.setHighlightMode docs --- docs/api/tray.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/api/tray.md b/docs/api/tray.md index 5ab68c5e9445..f8b5989b36e5 100644 --- a/docs/api/tray.md +++ b/docs/api/tray.md @@ -173,12 +173,15 @@ Sets the hover text for this tray icon. Sets the title displayed aside of the tray icon in the status bar. -#### `tray.setHighlightMode(highlight)` _macOS_ +#### `tray.setHighlightMode(mode)` _macOS_ -* `highlight` Boolean +* `mode` String highlight mode with one of the following values: + * `'selection'` - Highlight the tray icon when it is clicked and also when + its context menu is open. This is the default. + * `'always'` - Always highlight the tray icon. + * `'never'` - Never highlight the tray icon. -Sets whether the tray icon's background becomes highlighted (in blue) -when the tray icon is clicked. Defaults to true. +Sets when the tray's icon background becomes highlighted (in blue). #### `tray.displayBalloon(options)` _Windows_ From 8e1de8851290ec6f9857a8a79917a4d03e2f1863 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 26 Jul 2016 14:17:41 -0700 Subject: [PATCH 35/74] Correct typo in variable name --- atom/browser/api/atom_api_tray.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/atom/browser/api/atom_api_tray.cc b/atom/browser/api/atom_api_tray.cc index 3b804972cbf7..aefe340a3b4e 100644 --- a/atom/browser/api/atom_api_tray.cc +++ b/atom/browser/api/atom_api_tray.cc @@ -40,9 +40,9 @@ struct Converter { } // Support old boolean parameter - bool hightlight; - if (ConvertFromV8(isolate, val, &hightlight)) { - if (hightlight) + bool highlight; + if (ConvertFromV8(isolate, val, &highlight)) { + if (highlight) *out = atom::TrayIcon::HighlightMode::SELECTION; else *out = atom::TrayIcon::HighlightMode::NEVER; From c4e743d20799a74eb7e475582ef0bde9ff3ccc9b Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 26 Jul 2016 14:18:15 -0700 Subject: [PATCH 36/74] Add TODO to deprecate boolean param --- atom/browser/api/atom_api_tray.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/atom/browser/api/atom_api_tray.cc b/atom/browser/api/atom_api_tray.cc index aefe340a3b4e..213ddbfd89a0 100644 --- a/atom/browser/api/atom_api_tray.cc +++ b/atom/browser/api/atom_api_tray.cc @@ -40,6 +40,7 @@ struct Converter { } // Support old boolean parameter + // TODO(kevinsawicki): Remove in 2.0, deprecate before then with warnings bool highlight; if (ConvertFromV8(isolate, val, &highlight)) { if (highlight) From 9f0299cc313dbe35d7bac693dad296176078fcc2 Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Wed, 27 Jul 2016 09:38:49 +1000 Subject: [PATCH 37/74] Use better regex to match spaces --- lib/browser/init.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/browser/init.js b/lib/browser/init.js index 37d1f4bb6360..88377a6978bb 100644 --- a/lib/browser/init.js +++ b/lib/browser/init.js @@ -93,8 +93,8 @@ if (process.platform === 'win32') { if (fs.statSyncNoException(updateDotExe)) { var packageDir = path.dirname(path.resolve(updateDotExe)) - var packageName = path.basename(packageDir).replace(/ /g, '') - var exeName = path.basename(process.execPath).replace(/\.exe$/i, '').replace(/ /g, '') + var packageName = path.basename(packageDir).replace(/\s/g, '') + var exeName = path.basename(process.execPath).replace(/\.exe$/i, '').replace(/\s/g, '') app.setAppUserModelId(`com.squirrel.${packageName}.${exeName}`) } From 0bfd31e7cbc760171d7f95b13ea5e4974af3aee1 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Wed, 27 Jul 2016 08:53:55 +0900 Subject: [PATCH 38/74] Update brightray for #6613 --- vendor/brightray | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/brightray b/vendor/brightray index fa17fc6b68c4..689e41c3318a 160000 --- a/vendor/brightray +++ b/vendor/brightray @@ -1 +1 @@ -Subproject commit fa17fc6b68c43188ba4f85ba8fb87d66914a0be4 +Subproject commit 689e41c3318a592d1ffd6fe3b4255e981a668f49 From 367f94aa76f878bf736ad4a9f5f503e076fcf90a Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Wed, 27 Jul 2016 13:21:17 +0900 Subject: [PATCH 39/74] Bump v1.3.1 --- atom/browser/resources/mac/Info.plist | 4 ++-- atom/browser/resources/win/atom.rc | 8 ++++---- atom/common/atom_version.h | 2 +- electron.gyp | 2 +- package.json | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/atom/browser/resources/mac/Info.plist b/atom/browser/resources/mac/Info.plist index 5eb6079f6c98..b3f3bc69a4fb 100644 --- a/atom/browser/resources/mac/Info.plist +++ b/atom/browser/resources/mac/Info.plist @@ -17,9 +17,9 @@ CFBundleIconFile electron.icns CFBundleVersion - 1.3.0 + 1.3.1 CFBundleShortVersionString - 1.3.0 + 1.3.1 LSApplicationCategoryType public.app-category.developer-tools LSMinimumSystemVersion diff --git a/atom/browser/resources/win/atom.rc b/atom/browser/resources/win/atom.rc index 0527a33b32e9..d95e04c07b80 100644 --- a/atom/browser/resources/win/atom.rc +++ b/atom/browser/resources/win/atom.rc @@ -56,8 +56,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 1,3,0,0 - PRODUCTVERSION 1,3,0,0 + FILEVERSION 1,3,1,0 + PRODUCTVERSION 1,3,1,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -74,12 +74,12 @@ BEGIN BEGIN VALUE "CompanyName", "GitHub, Inc." VALUE "FileDescription", "Electron" - VALUE "FileVersion", "1.3.0" + VALUE "FileVersion", "1.3.1" VALUE "InternalName", "electron.exe" VALUE "LegalCopyright", "Copyright (C) 2015 GitHub, Inc. All rights reserved." VALUE "OriginalFilename", "electron.exe" VALUE "ProductName", "Electron" - VALUE "ProductVersion", "1.3.0" + VALUE "ProductVersion", "1.3.1" VALUE "SquirrelAwareVersion", "1" END END diff --git a/atom/common/atom_version.h b/atom/common/atom_version.h index 437562f9ca82..ddf222faf13c 100644 --- a/atom/common/atom_version.h +++ b/atom/common/atom_version.h @@ -7,7 +7,7 @@ #define ATOM_MAJOR_VERSION 1 #define ATOM_MINOR_VERSION 3 -#define ATOM_PATCH_VERSION 0 +#define ATOM_PATCH_VERSION 1 #define ATOM_VERSION_IS_RELEASE 1 diff --git a/electron.gyp b/electron.gyp index 4f8a1056c572..83872a416f89 100644 --- a/electron.gyp +++ b/electron.gyp @@ -4,7 +4,7 @@ 'product_name%': 'Electron', 'company_name%': 'GitHub, Inc', 'company_abbr%': 'github', - 'version%': '1.3.0', + 'version%': '1.3.1', }, 'includes': [ 'filenames.gypi', diff --git a/package.json b/package.json index 20a3a71c0948..3c9f28c1e0f0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "electron", - "version": "1.3.0", + "version": "1.3.1", "devDependencies": { "asar": "^0.11.0", "request": "*", From 7f3443b1382bce0737d06d091a982f4dee3b1e63 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Wed, 27 Jul 2016 16:16:21 +0900 Subject: [PATCH 40/74] Add shell.writeShortcutLink/readShortcutLink --- atom/common/api/atom_api_shell.cc | 104 ++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) diff --git a/atom/common/api/atom_api_shell.cc b/atom/common/api/atom_api_shell.cc index 860787f17c8f..4ac31d8064da 100644 --- a/atom/common/api/atom_api_shell.cc +++ b/atom/common/api/atom_api_shell.cc @@ -11,6 +11,35 @@ #include "atom/common/node_includes.h" #include "native_mate/dictionary.h" +#if defined(OS_WIN) +#include "base/win/shortcut.h" +#endif + +namespace mate { + + +template<> +struct Converter { + static bool FromV8(v8::Isolate* isolate, v8::Handle val, + base::win::ShortcutOperation* out) { + std::string operation; + if (!ConvertFromV8(isolate, val, & operation)) + return false; + if (operation.empty() || operation == "create") + *out = base::win::SHORTCUT_CREATE_ALWAYS; + else if (operation == "update") + *out = base::win::SHORTCUT_UPDATE_EXISTING; + else if (operation == "replace") + *out = base::win::SHORTCUT_REPLACE_EXISTING; + else + return false; + return true; + } +}; + + +} // namespace mate + namespace { bool OpenExternal( @@ -30,6 +59,77 @@ bool OpenExternal( return platform_util::OpenExternal(url, activate); } +#if defined(OS_WIN) +bool WriteShortcutLink(const base::FilePath& shortcut_path, + mate::Arguments* args) { + base::win::ShortcutOperation operation = base::win::SHORTCUT_CREATE_ALWAYS; + args->GetNext(&operation); + mate::Dictionary options = mate::Dictionary::CreateEmpty(args->isolate()); + if (!args->GetNext(&options)) { + args->ThrowError(); + return false; + } + + base::win::ShortcutProperties properties; + base::FilePath path; + base::string16 str; + int index; + if (options.Get("target", &path)) + properties.set_target(path); + if (options.Get("cwd", &path)) + properties.set_working_dir(path); + if (options.Get("args", &str)) + properties.set_arguments(str); + if (options.Get("description", &str)) + properties.set_description(str); + if (options.Get("icon", &path) && options.Get("iconIndex", &index)) + properties.set_icon(path, index); + if (options.Get("appUserModelId", &str)) + properties.set_app_id(str); + + return base::win::CreateOrUpdateShortcutLink( + shortcut_path, properties, operation); +} + +mate::Dictionary ReadShortcutLink(v8::Isolate* isolate, + const base::FilePath& path) { + using base::win::ShortcutProperties; + mate::Dictionary options = mate::Dictionary::CreateEmpty(isolate); + // We have to call ResolveShortcutProperties one by one for each property + // because the API doesn't allow us to only get existing properties. + base::win::ShortcutProperties properties; + if (base::win::ResolveShortcutProperties( + path, ShortcutProperties::PROPERTIES_TARGET, &properties)) { + options.Set("target", properties.target); + } else { + // No need to continue if it doesn't even have a target. + return options; + } + if (base::win::ResolveShortcutProperties( + path, ShortcutProperties::PROPERTIES_WORKING_DIR, &properties)) { + options.Set("cwd", properties.working_dir); + } + if (base::win::ResolveShortcutProperties( + path, ShortcutProperties::PROPERTIES_ARGUMENTS, &properties)) { + options.Set("args", properties.arguments); + } + if (base::win::ResolveShortcutProperties( + path, ShortcutProperties::PROPERTIES_DESCRIPTION, &properties)) { + options.Set("description", properties.description); + } + if (base::win::ResolveShortcutProperties( + path, ShortcutProperties::PROPERTIES_ICON, &properties)) { + options.Set("icon", properties.icon); + options.Set("iconIndex", properties.icon_index); + } + if (base::win::ResolveShortcutProperties( + path, ShortcutProperties::PROPERTIES_ICON, &properties)) { + options.Set("appUserModelId", properties.app_id); + } + return options; +} +#endif + void Initialize(v8::Local exports, v8::Local unused, v8::Local context, void* priv) { mate::Dictionary dict(context->GetIsolate(), exports); @@ -38,6 +138,10 @@ void Initialize(v8::Local exports, v8::Local unused, dict.SetMethod("openExternal", &OpenExternal); dict.SetMethod("moveItemToTrash", &platform_util::MoveItemToTrash); dict.SetMethod("beep", &platform_util::Beep); +#if defined(OS_WIN) + dict.SetMethod("writeShortcutLink", &WriteShortcutLink); + dict.SetMethod("readShortcutLink", &ReadShortcutLink); +#endif } } // namespace From 60ba2d624e7b6b5d3e956688a0f4e2ec99248ba5 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Wed, 27 Jul 2016 16:20:53 +0900 Subject: [PATCH 41/74] Initialize COM before using the API --- atom/common/api/atom_api_shell.cc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/atom/common/api/atom_api_shell.cc b/atom/common/api/atom_api_shell.cc index 4ac31d8064da..fe1e5da1bfdf 100644 --- a/atom/common/api/atom_api_shell.cc +++ b/atom/common/api/atom_api_shell.cc @@ -12,6 +12,7 @@ #include "native_mate/dictionary.h" #if defined(OS_WIN) +#include "base/win/scoped_com_initializer.h" #include "base/win/shortcut.h" #endif @@ -87,6 +88,7 @@ bool WriteShortcutLink(const base::FilePath& shortcut_path, if (options.Get("appUserModelId", &str)) properties.set_app_id(str); + base::win::ScopedCOMInitializer com_initializer; return base::win::CreateOrUpdateShortcutLink( shortcut_path, properties, operation); } @@ -95,6 +97,7 @@ mate::Dictionary ReadShortcutLink(v8::Isolate* isolate, const base::FilePath& path) { using base::win::ShortcutProperties; mate::Dictionary options = mate::Dictionary::CreateEmpty(isolate); + base::win::ScopedCOMInitializer com_initializer; // We have to call ResolveShortcutProperties one by one for each property // because the API doesn't allow us to only get existing properties. base::win::ShortcutProperties properties; From 25538fe7fb8dc4f7c800661a36fb08160e446ccf Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Wed, 27 Jul 2016 16:23:35 +0900 Subject: [PATCH 42/74] All fields are always returned --- atom/common/api/atom_api_shell.cc | 37 ++++++++----------------------- 1 file changed, 9 insertions(+), 28 deletions(-) diff --git a/atom/common/api/atom_api_shell.cc b/atom/common/api/atom_api_shell.cc index fe1e5da1bfdf..be407b72a083 100644 --- a/atom/common/api/atom_api_shell.cc +++ b/atom/common/api/atom_api_shell.cc @@ -98,37 +98,18 @@ mate::Dictionary ReadShortcutLink(v8::Isolate* isolate, using base::win::ShortcutProperties; mate::Dictionary options = mate::Dictionary::CreateEmpty(isolate); base::win::ScopedCOMInitializer com_initializer; - // We have to call ResolveShortcutProperties one by one for each property - // because the API doesn't allow us to only get existing properties. base::win::ShortcutProperties properties; - if (base::win::ResolveShortcutProperties( - path, ShortcutProperties::PROPERTIES_TARGET, &properties)) { - options.Set("target", properties.target); - } else { - // No need to continue if it doesn't even have a target. + if (!base::win::ResolveShortcutProperties( + path, ShortcutProperties::PROPERTIES_ALL, &properties)) { return options; } - if (base::win::ResolveShortcutProperties( - path, ShortcutProperties::PROPERTIES_WORKING_DIR, &properties)) { - options.Set("cwd", properties.working_dir); - } - if (base::win::ResolveShortcutProperties( - path, ShortcutProperties::PROPERTIES_ARGUMENTS, &properties)) { - options.Set("args", properties.arguments); - } - if (base::win::ResolveShortcutProperties( - path, ShortcutProperties::PROPERTIES_DESCRIPTION, &properties)) { - options.Set("description", properties.description); - } - if (base::win::ResolveShortcutProperties( - path, ShortcutProperties::PROPERTIES_ICON, &properties)) { - options.Set("icon", properties.icon); - options.Set("iconIndex", properties.icon_index); - } - if (base::win::ResolveShortcutProperties( - path, ShortcutProperties::PROPERTIES_ICON, &properties)) { - options.Set("appUserModelId", properties.app_id); - } + options.Set("target", properties.target); + options.Set("cwd", properties.working_dir); + options.Set("args", properties.arguments); + options.Set("description", properties.description); + options.Set("icon", properties.icon); + options.Set("iconIndex", properties.icon_index); + options.Set("appUserModelId", properties.app_id); return options; } #endif From ff5b8047a1fe3bff25a08fb869d0174ed218a89f Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Wed, 27 Jul 2016 16:32:32 +0900 Subject: [PATCH 43/74] Throw exception when ReadShortcutLink failed --- atom/common/api/atom_api_shell.cc | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/atom/common/api/atom_api_shell.cc b/atom/common/api/atom_api_shell.cc index be407b72a083..d8113daf56e0 100644 --- a/atom/common/api/atom_api_shell.cc +++ b/atom/common/api/atom_api_shell.cc @@ -93,15 +93,16 @@ bool WriteShortcutLink(const base::FilePath& shortcut_path, shortcut_path, properties, operation); } -mate::Dictionary ReadShortcutLink(v8::Isolate* isolate, - const base::FilePath& path) { +v8::Local ReadShortcutLink(mate::Arguments* args, + const base::FilePath& path) { using base::win::ShortcutProperties; - mate::Dictionary options = mate::Dictionary::CreateEmpty(isolate); + mate::Dictionary options = mate::Dictionary::CreateEmpty(args->isolate()); base::win::ScopedCOMInitializer com_initializer; base::win::ShortcutProperties properties; if (!base::win::ResolveShortcutProperties( path, ShortcutProperties::PROPERTIES_ALL, &properties)) { - return options; + args->ThrowError("Failed to read shortcut link"); + return v8::Null(args->isolate()); } options.Set("target", properties.target); options.Set("cwd", properties.working_dir); @@ -110,7 +111,7 @@ mate::Dictionary ReadShortcutLink(v8::Isolate* isolate, options.Set("icon", properties.icon); options.Set("iconIndex", properties.icon_index); options.Set("appUserModelId", properties.app_id); - return options; + return options.GetHandle(); } #endif From d6234e11dfc61d210eddae45b3df4e3d0d2e8ea1 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Wed, 27 Jul 2016 16:47:24 +0900 Subject: [PATCH 44/74] docs: shell.writeShortcutLink/readShortcutLink --- docs/api/shell.md | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/docs/api/shell.md b/docs/api/shell.md index 5963246caf2c..25475bc1f1f5 100644 --- a/docs/api/shell.md +++ b/docs/api/shell.md @@ -48,3 +48,36 @@ Move the given file to trash and returns a boolean status for the operation. ### `shell.beep()` Play the beep sound. + +### `shell.writeShortcutLink(shortcutPath[, operation], options)` _Windows_ + +* `shortcutPath` String +* `operation` String (optional) - Default is `create`, can be one of followings: + * `create` - Creates a new shortcut, overwriting if necessary. + * `update` - Updates specified properties only on an existing shortcut. + * `replace` - Overwrites an existing shortcut, fails if the shortcut doesn't + exist. +* `options` Object + * `target` String - The target to launch from this shortcut. + * `cwd` String (optional) - The target to launch from this shortcut. Default + is empty. + * `args` String (optional) - The arguments to be applied to `target` when + launching from this shortcut. Default is empty. + * `description` String (optional) - The description of the shortcut. Default + is empty. + * `icon` String (optional) - The path to the icon, can be a DLL or EXE. + Default is empty, which uses the target's icon. + * `iconIndex` Integer (optional) - The resource ID of icon when `icon` is a + DLL or EXE. Default is 0. + * `appUserModelId` String (optional) - The Application User Model ID. Default + is empty. + +Creates or updates a shortcut link at `shortcutPath`. On success `true` is +returned, otherwise `false` is returned. + +### `shell.readShortcutLink(shortcutPath)` _Windows_ + +Resolves the shortcut link at `shortcutPath`, an object is returned with the +fields described in the `options` of `shell.writeShortcutLink`. + +An exception will be thrown when any error happens. From 57dbf284c176d6a66167fc2fbe9b4714fe65fb32 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Wed, 27 Jul 2016 19:43:02 +0900 Subject: [PATCH 45/74] spec: shell.writeShortcutLink/readShortcutLink --- spec/api-shell-spec.js | 77 ++++++++++++++++++++++++++++++ spec/fixtures/assets/shortcut.lnk | Bin 0 -> 402 bytes 2 files changed, 77 insertions(+) create mode 100644 spec/api-shell-spec.js create mode 100755 spec/fixtures/assets/shortcut.lnk diff --git a/spec/api-shell-spec.js b/spec/api-shell-spec.js new file mode 100644 index 000000000000..0dc86cee9131 --- /dev/null +++ b/spec/api-shell-spec.js @@ -0,0 +1,77 @@ +const assert = require('assert') +const fs = require('fs') +const path = require('path') +const os = require('os') +const {shell} = require('electron') + +describe('shell module', function() { + if (process.platform !== 'win32') return + + const fixtures = path.resolve(__dirname, 'fixtures') + const shortcutOptions = { + target: 'C:\\target', + description: 'description', + cwd: 'cwd', + args: 'args', + appUserModelId: 'appUserModelId', + icon: 'icon', + iconIndex: 1 + } + + describe('shell.readShortcutLink(shortcutPath)', function () { + it('throws when failed', function () { + assert.throws(function () { + shell.readShortcutLink('not-exist') + }, /Failed to read shortcut link/) + }) + + it('reads all properties of a shortcut', function () { + const shortcut = shell.readShortcutLink(path.join(fixtures, 'assets', 'shortcut.lnk')) + assert.deepEqual(shortcut, shortcutOptions) + }) + }) + + describe('shell.writeShortcutLink(shortcutPath[, operation], options)', function () { + const tmpShortcut = path.join(os.tmpdir(), `${Date.now()}.lnk`) + + afterEach(function() { + fs.unlinkSync(tmpShortcut) + }) + + it('writes the shortcut', function () { + assert.equal(shell.writeShortcutLink(tmpShortcut, {target: 'C:\\'}), true) + assert.equal(fs.existsSync(tmpShortcut), true) + }) + + it('correctly sets the fields', function () { + assert.equal(shell.writeShortcutLink(tmpShortcut, shortcutOptions), true) + assert.deepEqual(shell.readShortcutLink(tmpShortcut), shortcutOptions) + }) + + it('updates the shortcut', function () { + assert.equal(shell.writeShortcutLink(tmpShortcut, 'update', shortcutOptions), false) + assert.equal(shell.writeShortcutLink(tmpShortcut, 'create', shortcutOptions), true) + assert.deepEqual(shell.readShortcutLink(tmpShortcut), shortcutOptions) + const change = {target: 'D:\\'} + assert.equal(shell.writeShortcutLink(tmpShortcut, 'update', change), true) + assert.deepEqual(shell.readShortcutLink(tmpShortcut), Object.assign(shortcutOptions, change)) + }) + + it('replaces the shortcut', function () { + assert.equal(shell.writeShortcutLink(tmpShortcut, 'replace', shortcutOptions), false) + assert.equal(shell.writeShortcutLink(tmpShortcut, 'create', shortcutOptions), true) + assert.deepEqual(shell.readShortcutLink(tmpShortcut), shortcutOptions) + const change = { + target: 'D:\\', + description: 'description2', + cwd: 'cwd2', + args: 'args2', + appUserModelId: 'appUserModelId2', + icon: 'icon2', + iconIndex: 2 + } + assert.equal(shell.writeShortcutLink(tmpShortcut, 'replace', change), true) + assert.deepEqual(shell.readShortcutLink(tmpShortcut), change) + }) + }) +}) diff --git a/spec/fixtures/assets/shortcut.lnk b/spec/fixtures/assets/shortcut.lnk new file mode 100755 index 0000000000000000000000000000000000000000..5f325ca733ea337dd986b4544cda6570430e083d GIT binary patch literal 402 zcmeZaU|?VrVFHp23dBqILK=Dk50-$&%Lq09Rwhskq`nkr7AFJ4 z0vRA}7#tA%s4K#4kB^1(?mxnl)n??L0}3xQD i3gm&9ABYoy4k=&=1v)Dg=qz8L1t3S}Fn9uKbN~S9)j9nD literal 0 HcmV?d00001 From eadffc6db843c9246ce1f7de685a31406c69d871 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Wed, 27 Jul 2016 19:49:17 +0900 Subject: [PATCH 46/74] docs: icon and iconIndex have to be set together --- docs/api/shell.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/api/shell.md b/docs/api/shell.md index 25475bc1f1f5..bad864f28679 100644 --- a/docs/api/shell.md +++ b/docs/api/shell.md @@ -65,8 +65,9 @@ Play the beep sound. launching from this shortcut. Default is empty. * `description` String (optional) - The description of the shortcut. Default is empty. - * `icon` String (optional) - The path to the icon, can be a DLL or EXE. - Default is empty, which uses the target's icon. + * `icon` String (optional) - The path to the icon, can be a DLL or EXE. `icon` + and `iconIndex` have to be set together. Default is empty, which uses the + target's icon. * `iconIndex` Integer (optional) - The resource ID of icon when `icon` is a DLL or EXE. Default is 0. * `appUserModelId` String (optional) - The Application User Model ID. Default From ac8a490d5670cc5af25d3581b38eadd46a421d6c Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Wed, 27 Jul 2016 19:51:16 +0900 Subject: [PATCH 47/74] Fix lint warnings --- spec/api-shell-spec.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/api-shell-spec.js b/spec/api-shell-spec.js index 0dc86cee9131..8c9f9833848a 100644 --- a/spec/api-shell-spec.js +++ b/spec/api-shell-spec.js @@ -4,7 +4,7 @@ const path = require('path') const os = require('os') const {shell} = require('electron') -describe('shell module', function() { +describe('shell module', function () { if (process.platform !== 'win32') return const fixtures = path.resolve(__dirname, 'fixtures') @@ -34,7 +34,7 @@ describe('shell module', function() { describe('shell.writeShortcutLink(shortcutPath[, operation], options)', function () { const tmpShortcut = path.join(os.tmpdir(), `${Date.now()}.lnk`) - afterEach(function() { + afterEach(function () { fs.unlinkSync(tmpShortcut) }) From 242ce4f81887a32e9cfdcbeec2f45a3c62e864f9 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Wed, 27 Jul 2016 19:55:05 +0900 Subject: [PATCH 48/74] Fix building on non-Windows --- atom/common/api/atom_api_shell.cc | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/atom/common/api/atom_api_shell.cc b/atom/common/api/atom_api_shell.cc index d8113daf56e0..2d9d6e02697a 100644 --- a/atom/common/api/atom_api_shell.cc +++ b/atom/common/api/atom_api_shell.cc @@ -14,11 +14,9 @@ #if defined(OS_WIN) #include "base/win/scoped_com_initializer.h" #include "base/win/shortcut.h" -#endif namespace mate { - template<> struct Converter { static bool FromV8(v8::Isolate* isolate, v8::Handle val, @@ -38,8 +36,8 @@ struct Converter { } }; - } // namespace mate +#endif namespace { From ba2b8c8d3ef88bf8280d5c08054966fbd8ffe439 Mon Sep 17 00:00:00 2001 From: Hans Kristian Flaatten Date: Wed, 27 Jul 2016 22:01:01 +0200 Subject: [PATCH 49/74] docs: mention app ready event for DevTools Extension tutorial --- docs/tutorial/devtools-extension.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/tutorial/devtools-extension.md b/docs/tutorial/devtools-extension.md index 00911a2d2e92..84c8447172ae 100644 --- a/docs/tutorial/devtools-extension.md +++ b/docs/tutorial/devtools-extension.md @@ -29,7 +29,10 @@ Using the [React Developer Tools][react-devtools] as example: * on macOS it is `~/Library/Application Support/Google/Chrome/Default/Extensions`. 1. Pass the location of the extension to `BrowserWindow.addDevToolsExtension` API, for the React Developer Tools, it is something like: - `~/Library/Application Support/Google/Chrome/Default/Extensions/fmkadmapgofadopljbjfkapdkoienihi/0.14.10_0` + `~/Library/Application Support/Google/Chrome/Default/Extensions/fmkadmapgofadopljbjfkapdkoienihi/0.15.0_0` + +**Note:** The `BrowserWindow.addDevToolsExtension` API cannot be called before the +ready event of the app module is emitted. The name of the extension is returned by `BrowserWindow.addDevToolsExtension`, and you can pass the name of the extension to the `BrowserWindow.removeDevToolsExtension` From 4823b5582feac182c6a5b1d6206d4e6943ccef99 Mon Sep 17 00:00:00 2001 From: Dave Jeffery Date: Wed, 27 Jul 2016 21:35:13 +0100 Subject: [PATCH 50/74] docs: Explain how to use highlightMode with BrowserWindow As discussed on https://github.com/electron/electron/pull/6620 --- docs/api/tray.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/api/tray.md b/docs/api/tray.md index af9243b3c6f5..95d25ba4ada8 100644 --- a/docs/api/tray.md +++ b/docs/api/tray.md @@ -193,6 +193,19 @@ Sets the title displayed aside of the tray icon in the status bar. Sets when the tray's icon background becomes highlighted (in blue). +**Note:** You can use `highlightMode` with a [`BrowserWindow`](browser-window.md) +by toggling between `'never'` and `'always'` modes when the window visibility +changes. + +```js +win.on('show', () => { + tray.setHighlightMode('always') +}) +win.on('hide', () => { + tray.setHighlightMode('never') +}) +``` + #### `tray.displayBalloon(options)` _Windows_ * `options` Object From 322fe36dea4a5b949ce63abc9b2d4cf21f20a5b7 Mon Sep 17 00:00:00 2001 From: DaveJ Date: Wed, 27 Jul 2016 23:23:26 +0100 Subject: [PATCH 51/74] docs: Fix undefined variables --- docs/api/tray.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/api/tray.md b/docs/api/tray.md index 95d25ba4ada8..5692880d2bda 100644 --- a/docs/api/tray.md +++ b/docs/api/tray.md @@ -198,6 +198,10 @@ by toggling between `'never'` and `'always'` modes when the window visibility changes. ```js +const {BrowserWindow, Tray} = require('electron') + +const win = new BrowserWindow({width: 800, height: 600}) +const tray = new Tray('/path/to/my/icon') win.on('show', () => { tray.setHighlightMode('always') }) From 42640b6170e6b01dd7c41e3091530e8d2b1c55cd Mon Sep 17 00:00:00 2001 From: DaveJ Date: Wed, 27 Jul 2016 23:56:00 +0100 Subject: [PATCH 52/74] docs: Add click handler to tray window example --- docs/api/tray.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/api/tray.md b/docs/api/tray.md index 5692880d2bda..b0333402c047 100644 --- a/docs/api/tray.md +++ b/docs/api/tray.md @@ -202,6 +202,10 @@ const {BrowserWindow, Tray} = require('electron') const win = new BrowserWindow({width: 800, height: 600}) const tray = new Tray('/path/to/my/icon') + +tray.on('click', () => { + win.isVisible() ? win.hide() : win.show() +}) win.on('show', () => { tray.setHighlightMode('always') }) From 0ad8c66b65784cd9798ed29ef252c02158a8ed19 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 27 Jul 2016 17:31:26 -0700 Subject: [PATCH 53/74] Use selected dialog path as item save path --- atom/browser/atom_download_manager_delegate.cc | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/atom/browser/atom_download_manager_delegate.cc b/atom/browser/atom_download_manager_delegate.cc index 6d7e3c7172d8..63ca5f661f98 100644 --- a/atom/browser/atom_download_manager_delegate.cc +++ b/atom/browser/atom_download_manager_delegate.cc @@ -85,6 +85,14 @@ void AtomDownloadManagerDelegate::OnDownloadPathGenerated( download_manager_->GetBrowserContext()); browser_context->prefs()->SetFilePath(prefs::kDownloadDefaultDirectory, path.DirName()); + + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + v8::Locker locker(isolate); + v8::HandleScope handle_scope(isolate); + api::DownloadItem* download_item = api::DownloadItem::FromWrappedClass( + isolate, item); + if (download_item) + download_item->SetSavePath(path); } // Running the DownloadTargetCallback with an empty FilePath signals that the From 4e9b19074f65f3f0f37aee2db60b5b7983609eff Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 27 Jul 2016 17:33:36 -0700 Subject: [PATCH 54/74] Make downloadItem.getSavePath() public --- docs/api/download-item.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/api/download-item.md b/docs/api/download-item.md index e0416d8ef265..7ec8fd1896fe 100644 --- a/docs/api/download-item.md +++ b/docs/api/download-item.md @@ -80,6 +80,12 @@ The API is only available in session's `will-download` callback function. If user doesn't set the save path via the API, Electron will use the original routine to determine the save path(Usually prompts a save dialog). +### `downloadItem.getSavePath()` + +Returns the save path of the download item. This will be either the path +set via `downloadItem.setSavePath(path)` or the path selected from the shown +save dialog. + ### `downloadItem.pause()` Pauses the download. From 55d6e0de1a3c3cce4c0f05b7d4be101aa5fe74aa Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 27 Jul 2016 17:52:36 -0700 Subject: [PATCH 55/74] Assert save path of download item --- spec/api-session-spec.js | 11 ++++++----- spec/static/main.js | 3 ++- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/spec/api-session-spec.js b/spec/api-session-spec.js index 640cc44551d5..e6f5737b33ba 100644 --- a/spec/api-session-spec.js +++ b/spec/api-session-spec.js @@ -239,9 +239,10 @@ describe('session module', function () { res.end(mockPDF) downloadServer.close() }) - var assertDownload = function (event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename, port) { + var assertDownload = function (event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename, port, savePath) { assert.equal(state, 'completed') assert.equal(filename, 'mock.pdf') + assert.equal(savePath, path.join(__dirname, 'fixtures', 'mock.pdf')) assert.equal(url, 'http://127.0.0.1:' + port + '/') assert.equal(mimeType, 'application/pdf') assert.equal(receivedBytes, mockPDF.length) @@ -256,8 +257,8 @@ describe('session module', function () { var port = downloadServer.address().port ipcRenderer.sendSync('set-download-option', false, false) w.loadURL(url + ':' + port) - ipcRenderer.once('download-done', function (event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename) { - assertDownload(event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename, port) + ipcRenderer.once('download-done', function (event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename, savePath) { + assertDownload(event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename, port, savePath) done() }) }) @@ -272,8 +273,8 @@ describe('session module', function () { webview.addEventListener('did-finish-load', function () { webview.downloadURL(url + ':' + port + '/') }) - ipcRenderer.once('download-done', function (event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename) { - assertDownload(event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename, port) + ipcRenderer.once('download-done', function (event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename, savePath) { + assertDownload(event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename, port, savePath) document.body.removeChild(webview) done() }) diff --git a/spec/static/main.js b/spec/static/main.js index 5104a0f2525a..8a432532f8e7 100644 --- a/spec/static/main.js +++ b/spec/static/main.js @@ -142,7 +142,8 @@ app.on('ready', function () { item.getReceivedBytes(), item.getTotalBytes(), item.getContentDisposition(), - item.getFilename()) + item.getFilename(), + item.getSavePath()) }) if (needCancel) item.cancel() } From 46e707fd6d862d7c2de3da0d7713cd279e2779c3 Mon Sep 17 00:00:00 2001 From: amor Date: Thu, 28 Jul 2016 15:01:36 +0800 Subject: [PATCH 56/74] update Prerequisites --- docs-translations/zh-CN/development/build-instructions-osx.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs-translations/zh-CN/development/build-instructions-osx.md b/docs-translations/zh-CN/development/build-instructions-osx.md index d2052c27a54d..f9992c929958 100644 --- a/docs-translations/zh-CN/development/build-instructions-osx.md +++ b/docs-translations/zh-CN/development/build-instructions-osx.md @@ -8,7 +8,7 @@ * [Xcode](https://developer.apple.com/technologies/tools/) >= 5.1 * [node.js](http://nodejs.org) (外部) -如果你通过 Homebrew 使用 Python 下载,需要安装下面的 Python 模块: +如果你目前使用的Python是通过 Homebrew 安装的,则你还需要安装如下Python模块: * pyobjc @@ -59,4 +59,4 @@ $ ./script/cpplint.py ```bash $ ./script/test.py -``` \ No newline at end of file +``` From 29d66eb0d093d862be393bf0ab77328fbdd7811e Mon Sep 17 00:00:00 2001 From: Yuya Ochiai Date: Thu, 28 Jul 2016 23:15:25 +0900 Subject: [PATCH 57/74] :memo: Add recommended sizes for Windows ICO icons For #6396 Please refer to https://msdn.microsoft.com/en-us/library/windows/desktop/dn742485(v=vs.85).aspx [ci skip] --- docs/api/native-image.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/api/native-image.md b/docs/api/native-image.md index 6db5822a2e86..000c6d303cd2 100644 --- a/docs/api/native-image.md +++ b/docs/api/native-image.md @@ -35,6 +35,8 @@ quality it is recommended to include at least the following sizes in the icon: * 16x16 * 32x32 +* 40x40 +* 48x48 * 64x64 * 256x256 From 5eaae8136ed00d77988d5e5d17f13946688deb91 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 28 Jul 2016 08:48:56 -0700 Subject: [PATCH 58/74] Check that window is an EventDispatchingWindow --- atom/browser/common_web_contents_delegate_mac.mm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/atom/browser/common_web_contents_delegate_mac.mm b/atom/browser/common_web_contents_delegate_mac.mm index 34df15bb0c2c..c76606137939 100644 --- a/atom/browser/common_web_contents_delegate_mac.mm +++ b/atom/browser/common_web_contents_delegate_mac.mm @@ -32,7 +32,8 @@ void CommonWebContentsDelegate::HandleKeyboardEvent( [[NSApp mainMenu] performKeyEquivalent:event.os_event]) return; - if (event.os_event.window) + if (event.os_event.window && + [event.os_event.window isKindOfClass:[EventDispatchingWindow class]]) [event.os_event.window redispatchKeyEvent:event.os_event]; } From bd2ce5327c4c3d295832c2cdd8a8a6ee9b1ab0ff Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 28 Jul 2016 15:47:52 -0700 Subject: [PATCH 59/74] Return early when render widget host view is null --- atom/browser/api/atom_api_web_contents_mac.mm | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/atom/browser/api/atom_api_web_contents_mac.mm b/atom/browser/api/atom_api_web_contents_mac.mm index 19246e82ac7f..913951737fe8 100644 --- a/atom/browser/api/atom_api_web_contents_mac.mm +++ b/atom/browser/api/atom_api_web_contents_mac.mm @@ -13,6 +13,9 @@ namespace atom { namespace api { bool WebContents::IsFocused() const { + auto view = web_contents()->GetRenderWidgetHostView(); + if (!view) return false; + if (GetType() != BACKGROUND_PAGE) { auto window = web_contents()->GetTopLevelNativeWindow(); // On Mac the render widget host view does not lose focus when the window @@ -21,8 +24,7 @@ bool WebContents::IsFocused() const { return false; } - auto view = web_contents()->GetRenderWidgetHostView(); - return view && view->HasFocus(); + return view->HasFocus(); } } // namespace api From 5982e3a75ebc1578e60729d8ced873c65637aa11 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 28 Jul 2016 15:48:01 -0700 Subject: [PATCH 60/74] Fix typo in comment --- atom/browser/api/atom_api_web_contents.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atom/browser/api/atom_api_web_contents.cc b/atom/browser/api/atom_api_web_contents.cc index 0083ac71eb96..43ef9d35d5af 100644 --- a/atom/browser/api/atom_api_web_contents.cc +++ b/atom/browser/api/atom_api_web_contents.cc @@ -743,7 +743,7 @@ bool WebContents::OnMessageReceived(const IPC::Message& message) { } // There are three ways of destroying a webContents: -// 1. call webContents.destory(); +// 1. call webContents.destroy(); // 2. garbage collection; // 3. user closes the window of webContents; // For webview only #1 will happen, for BrowserWindow both #1 and #3 may From 5e7ee675f7b059ace7b0b88d8959a1f741be8ea7 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Fri, 29 Jul 2016 10:24:56 +0900 Subject: [PATCH 61/74] Update ico to include sizes for 125% DPI --- atom/browser/resources/win/atom.ico | Bin 77776 -> 376846 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/atom/browser/resources/win/atom.ico b/atom/browser/resources/win/atom.ico index aa09177554651fb38078881c4dd73f7d1d17ae7f..004176004f740387d180ef573ec55ba639795a80 100644 GIT binary patch literal 376846 zcmeFa3ACM6l`fjnQp+mKdvCqYs^`5G#j;SVta^2=do3*+5F0>5MC?#RL2OGDL3#)f z2%!xD5}Nc4X$cSlBq0e+=!Eorcaog+eE{o?>lj+^f8RIf`qn@9fBt>;Nk~F~bH-R> z&b^ws=9+VUbFIJG`}FAXM?K!wgMWY2qi2sJd;W2cUxV+}D}DZxKk6|Ab)Wup`h0Yc z9?u;4mLC83kN$b=-}LCQ=O=IJapaNy`A2`%qsM@LZ|TvqXKMe?J$n3qK zkN&tvk9!_Pf6Rf39zEXV_59ca^K}1pTcFzl-4^J!K(__DEzoU&ZVPl zw*|T_&~1Tk3%u?uz`H=Nq`dA7(!HQ=3%pJ&P={;W@#S6LPRpCoURPR;V|(44sP6vj zwm`Q94w40`n2R{A;+PM4s^u-H+n-XE!_U?EqApcE50W{$#dcfZb!&l$p@^-BJK?g5 z>9@ivDtzdy@+aW;r)WH>h=|0xu_71l%LzkuJDqH&~ACg;$6nm^_T=7wrs(0a5!tyk-p zJutNUuiFBzOA9E*74yV^@F09dylX$#K1{4dj0>N{^k1O-Z>jw6pe+9f%Kup@{}(=Y zrBugJmgH(4&dIr@2XlmS19g?&%vZ_@IwxqoT0di+Atz-!t9ie<&NFt7M; zhW9$GN1R7|6VLBTEUsu#l zs&S-}*Au+v;hfAV)PXr7=7wrssOAKoV{}GPKBzkzyczx8l#1+%kKKRW7C7uIp!j6G zGH!LY{LwGy-#atjeDi_Gk=+^z$ZGl(S0*XcL zQNn#I%xjP5d0E%#JX7oJ&V7|Q>WM;ZMVt$R!Z)!>9DgX4zXN6Y`%pd%UgaOacUIJo zG|G@%&BHmBC#Z|`s^*2BXj4wmIe}}{bpvgn`v~s@&%D@KE>Iqz&1HYE-GAK{IAj)3 z+%X0jj}iCWyLkVb`xxVx=Um1$G0%NZnCE^N_xFhRBhW@15mR+I7ruqne?q^Hp&X5J zJj$0*PC+>p<+~_np!^W!$0$EV`5DUDsr({Q*0GI!*q38C_FE{FMVZH;a1PGJIW<4? z1aLR-?Nv@LTh&jhqNOLrcC5xV~l zi3O^-XB^h;^^99#o_kvz=DEl7yi82I7bW8T1Nf{xowy>th_(NLLi~LRg}D4a%1{5| zFaF}(qmDZ2inGo->z3ZVdylyF)?4o#K79Dp@#DuoHD$__`7>tBSo6daPi&n%d-l$! zpMHApv(G;J+u!`=H@|z~g%|$t{PWMV{O-Bup8GA^o_gx3olidbnU{Q}ySHm>FY`l9S4T zhROr<33GC*Jb*6Uf87@7V1eqM{}%M%neuHYyw+rFRdFBB_&w2Hx7YKW`zaJ+{wH7i z+SmFH9Xj+8Ts!Zo;s*Pqo#{y&n zwO?01M;YX+g>q;k+KM)#?UD^?%XlWxeS)5-rw`~0`XuH7T^A@1FrV|jZFe4k5xW1( z0^AE);hy)JxVJF2>Tu8VDRIwziu={kD5t*r-S0lHPoF*`9)JAtwbeM+XU02u;YYmM zt8`!cSI4*de6ZzL=WjJ1ZATl@mb7WIt_1-}DSpY_PgZ;JI^L35S=l6V$ z$5?8Hd)})$7UhS0&h_Y{k1iut4itVO1|wgG$9Z#~Ef&suqF6kCTCr@wgT;!4_Z2G_ zjxSa(8dI!UG@@9$WN5K&$>3t`;=4ibD%LN#17%<;ccA`GZ(BULXj?o4V@4LM7mk6P z@g{5O{K>_V1=DSg`SYHu%@0w>$k*xHmAWWHJZ(%{)8@(c^g(BNfIg%zdHqJ;{uxSV z*92(p{(Bu-pw*rqukjf(d>2jk^WO`e*PPsI7;FEC@4F@hPf2 zAj6b<-N6?pjZk#y*i(zgQP}w$tXcJ$&#Z`Xc#6d4Rs6 z&-A>2zNAlekC49ZJ`2DIZ;-#v_I#cTxi9FfuWNihbAAtsaQ{K@{}0LuZ+qL@esa-8 z7u|t(YTFr$jLm9%DuyGCeIg(GM1J1f=Zi)2X94Szv5$|!zI|s$JSU8|8}5O9Vw!xy zb}KylxHKlnQr?I;#dCn?mpq^49n~SXEWHc5jy9dCJJ+B!#WhD>?fXOVu5vql0iXPs zzDYi!ub8XpJNi)X574)|CZNyh`|h&xr@8s>3`i?%N zFMFc!I*!+K@tS}!z*zVb6vhT)B;FUm$L_z^paqC~VV}=^=-0QQFyGVP+VkUmeV!M2 zZsfha51{-V$}w+w%UgbQ_0?Aoedd{GcI-<`cBSvUxxXovE_lSQ(|Jwq_)R!Vdw9m8 z;XPn5VpDx%UF4g!3)iA?j>tD_mt4^}XXH~m*SLD|SX(pK-kFW&&6{WKq>=Az41Gjj z!DqZba7^+ceMz6{njk(eqRa`|mYk0pXr`T-axR|5KD^d;WXS#(jeO z#fMP-C(3tDKKbO{cz1O*V=l&E_c8<{`|SPmo~$2kTEa(RB6Oez;~!?I2Sa*-#(Am+@z6D@>|M_VkZycgF9Pv$%+yLFXDo0F&S=tKIFK1HE#brzt{>3f|8;(dYc>j4<%HTYM> zzRvr+&-0&9c>h=T_L={+=ks0>_lKk2{N^{G*{fHtyYY_ti`95y9ICD>G0DCQ=Ra%r z>iPVQcx{QFW;kh5zpwII^>5YQD(AEs->RMRX{!~BrW6b2J(JH|vb$)V{nbVqdHRw* zrElTmqm!@cbDsBf7KqOX7%SC#gWcx<809eiBkqNLp4oJ*&$Ar;$Y;O2$H(`0nD2Q% zI9})fGd_Rw*kg~qV&=@5OY355U+WexcoOIPF~x>ueoaprdEuyv2Y!wv=8VXzPppf4 zvv&6BtZh^_~^?+L&dhtXdZ`@Dx+z1G)xpU?AqqVVje>-=N?@-P4L>=7eIOhL^2 zfpLR#qZRRWu@u{4|0N3^FV-v`oiSX+gK!~Q#Z$F^wcYXvb41D~hCT8rZ@kj+lGBV< z8T}&c^ZHHK`nOpKR(m@ z=BcNi+6TXdwL@_uT8D4NnP}{@WWh|FTSqw#10ET_4MlBD@Y~QgU`cqWQsG16*seO` zmnA%cZU}r;jy`K_Q+Yur>$8vRX_db;uV{5WZA(VtJTS8sD>g_>Gbs^gUxB z#R6l3vGE=h#tLJGu@j#Ua9>dl=)N9+F+in}f z{h%5rUH!~>TDousa6i21nL3{N8)84;l(8=SRcXX5dBt0WZ0lOZaNg zDauOvb!cxPU-O6_to5&3G6K&KAI;~qi0x+ni+#wW{DCo$?hAYzaZZ^W2l zY%#{Va{!F+D*i?6^SX@wpkH*KFJ9~G{(e61zyJO3KWFOHsdJmfiR!EI!g|#$T{si( z;|*=x+liTo(a2{!1q`#@@FeQ@2)r)wCbK->XWTZC=Xfvk$v)#KE${!1~uM9f}-0GhegGcl$ya3ycZG&#@@+dO*(ybWb4O7i7#a_PWmjFv21Ht74z` zW`zCfeZHP(<2_!U?fwDfo2Q+2+O@bJymMdTBlcai;90!S#`}5+>#gu>?E%-sfp8T0 z+)ue+Kjk-ID#~h-PoztpKj<3U_7}fZxpB>r&;6p#k+0~L`+I%clCcGS6MeNWJYxc} zq3Z$03S)+`(-VdFjFkfzW8KdNV1!rUuNC&I_xXhVkMWFm#~pW!MNH)L{(=PyEM7<> zuegbI3m42QRxX}Wv@g4}*l4kr@M$p&KH~%NsJPDk7^A6wLtW^b#+z*Lq>(S>r@180 z=8E%I+FQ(*bCLDyfK=r<0 zcMdqT37`)9yf3Tw`6Bk;gL>Y}`2fnXM;>|PIr!e_Jo;Zgk38!-D@zwWUaVU>ES=Sf z<<1Im&UVrgD_zEO9$P<$W6yJgWJb!l)u+^>)Tgmd^`bTUc92)Em&=tL(HeOvw>8aY z2Vni{;j1MJAIrX$Z==s6uXa(!3S)+`lVV8s1* z@H+0KZ++`qSL1BAOTOI~x@h6^#hNAK8umNKmdCYlCTbY4{TT628e*M&)K8TC#h2|R zW|Tu3sjx*FdCxbEb#?N~y2idu@>R%TwfQWp!Q>rA>tC~Ed_f=Xi_g`UF~itF41FcV z5@U+{ik=N{zlqNWyK?{xauEJnVLv|K(>pzX2R*)a_St9OggpN{`Lq>XxNxDx3h9c) zkKms9T`lpsak*oV)C%!HKHx$acf1L=_0;7h8ImKKWJ-1&jTrU35p#s=MLp`qN>5Ss z6ZQJW{N(zg?l4_HeMn!n@;%$5|H(6U@O$MqGL}#nQ_KO3G35ZpUUv?F5f0Md{$T&Z z(B)Ko2kZ{`?+@~6oquDU;$z{07mBte<^8=F^W<9=VKw2wumhen^1_SZ)!MtZ|GFIZ zP4WUY-8#(g*z{abVz=2ks`-QcRM%`wLw$$&J(p#DlPwPQ`rDS=gR{V#oZl4}qVj*0 zGKLsSj42exR(B475e~uM{$QWK&vDv-0Ru)?{m9SqHEHD4ciH0ExW6}~VgGK3ZD7IT zRy5(3bxrYA!GpKsyrduuvU(XV~DHbkURM`#S%}I>m@6V~Vkb!Wiq$0WiWr{3G^R;(LAZ86U5`biJ=L z|3`4&_Ivog*MsuezR;CR9zrbR-acYR`*fG$HO5#N+ibsUzyxB_=1NrairW0kU8jvk z{n>-@P2yc+Nh9yFQofPS>eJc$oG0|}kgwE6iW29H{C>}Wz}J6gu?oKKN^HbEgFIu4 zF^1Ut2ud{v=zf5n3%nJ5_)aQgSn&+D`>zWYpuPBxHSI?`w!;2L|NPJY{H%u_dgyWb zbXJaYn}<$E*;}Bo?&kc2zF_IB!oTFhZ<+jcb zovzK~IgXq5(^{lcRi9SvRXjJW;n1z0KBw1qyOEHkFN z|5{t1754ev4BmgyzW?!ez3W{+#&^5sw!%B}g8GnOx+tCZ6|*sx72g%QDPfG%c#IJ> z%t%&cT;4b19FbT5SVz8!7n7fG9_O;YHTX&TcJ=(wkh7#yps%O(^Lk*};-{MCfNH#G zUtz2<<`9E?XE^47_`RX(IiNcSz*sH*s@UiEE%ZJ=-|3}&`3&|wD75>>jyU3opX2}9 zuZX@<-i~#VXZy+}4+C4czu$nd63b2TEu4u)3>qKy;0EmazN%c&W%-(?tF(hYAt#s& zo)eT;BEO&db(rt9wtid>EPbR)=fxO9j5)?0V=%=cV^Z%2Fjn<_0md-Tusq9l=KvU? z$)B*#>m|NRQN=#rzl_-bpUlC+K7G^GvS#W1<$X6|K`{)z6+Wx&!m%i^r9P2w*6vtz z>~*ETWM_HM-Rii8z~}agK6$OFzs*mcb_ji&jkmc{KlO<;%80!B#5(fTQk7rr-=RIM z`IW4nG0`f1bavGK!q`jafd7fucYXn1KBj~(JGV4@nU2jX+9`8FktkXl*6 z>kjawD29(j>-4G0*SbY*d_Jd?xvN=w-5jr^uBu;0eUz6D{`%K0n@})DV$4Loihs7@ zUBS8Oe!xGa*kp|A`vQzz#&G-%k?tG-gH-;Af9?CiKHsh6cdz+-UBW)!=l=)XgFT)1 z`l4^@_{EDCn=eV1EM8EoTQ;^~kE@H@*72Ay2h12o43CaQ!=$h&yjnlU)&BIa;-RYZ zzUq6R<@uUlKkH|VEM7D}#|`6(@e^aI8f%P2#w24Cg)!O_g)z(6WenH7C(M}d{%ic* zE?y_ad;NUBir>NJ_b_Q!zJqn@xN+kq*ZCvXMSjWRxyAZrBO9=9SR^)E;n&*B{nq-k zE)~NQ_$n^-nf=tq=Ss3tey}$1>R+W*d}qCz<)5HioLh1`$YX!m!T3h~8g#D6u9L5M zL}Q-@S%>lZ+n0~RelfQJ^NgozQEng|J9g}3#%799#wuf$v8#6kyYC0W7?nTfKwc01 zF^aDFX(zr@sb~EB?T#Z*K8W&ld_QYc)erg{{ULto;unDZVIJd(bI}<42~Uh$VZ?a! z5%u^DIm5CSPF-94y zD2&iXok2*%a%lcO2Mj4Us zvb@*f`Zw7*V2wbN%pRvUlYliu1J!6+KjM(OLtLnV~#yn%67*Gx%HeSzv+WR{@_rC`Q;C0hi1`QfC zdg;=oU9$JmC5wyoE5;f2_60xHc;@F=TE~Bh@hTSMSjnv7#pid-JKrcT)=5TZ^gzpx zYlysbh;`(vrK$^{x{ zv0UjN>m)N$%2J;!H^{H*XnjKt(%iY9#*#+9277r7+W3DqXFYgfG3u`G7q?t{a&gTi zUnnmB_0h!@=O0t_?e)dt*1lgw+Zn}^k6co;ExI+^vV%UDGp;k@|7&sEb*B_hO}i{(opPh>yawvWH6=Z* z5BR4258?WwF3t3lo}%ohagopM$qtM;#-w5}@~n#m_X2h@rWxBPjPdwP@QrXCpuNA< zx&MQ>Uivovcl!&nt7x5lS+{1{v>bb#`KwvH=Gd*oWtaO`;=hBuYCAu7R)nRbkHun& zImM-DogB42Grd=F&85fI;=2y>syi>`1X32B6J|}hu++JtpXs2wxYne9hxX3aUNe4w z*UxcrY?u8hy+xx=kw^QQWmEInl(E=ZdHnImUtnwl`+Se>jdmRn_x|er{`h|Hmj@3X zJfbr@#5)nCy(ly?~Lx{+ChWb%5RttUeDQW?m0}aqq9b=M&%YJNnEs z&+G;J{k}6Bs%_cQ1$f3kBw()@J}UmVm`#0a;&)4`BaM95@z`p(&!>i=uf$Rm$jglmXdlP?T91l^=}2Ysnu zCq--1_$uunulf2L&Ki=_VSH7WMtRZak#FYzjmz;HB8WxBWoH!k0)EdJ2dIxiiO&P7 z?*;Lk{3evwdJYiwb=@Do+Y`Un_u+TH``tglZ>(*n{a~k-rG3SC$0z)3eoFopPwM_F zX?=Xa{~X)J{kEn23O-#2*D;Q@Ic?s6p^DCR{bOC^Cyn|k@cplb@sgIy}T%Gm}ZhvUwwlXJ+*;#t!>Mam{64%y}Z`1dNURxFKhm&20Xl zv*Wx4o+|!o@JWzG*>QccSFDSC({{-LZI~;?i~0Q(`k%4Lm{sgH%K_|z`&!!(>t_Mm zA4chkqUQm8|3%*s((}OAdJZ7|g?-)g<8LCw@AmL4^yLW?Cfpxw7+e&ZqcE z`+2O3eX~DX?bjg=VysbCU5R}nU)Q%zpI8_9Qs46Y%HP)=fa`L@XxgLW8UM-)KUX|G zqhH4L?oHE*9qaEcwzu6=Y+W;=*s==OPpbxjA6{&08)a>~H$9U3&zU{2xcZXgZ130g zL!`XVz;~GA+TxlcANxc;_KCdu#5(fT!hY)G{Hx`E-V1oPQ~WE28S9LB;No9U-h)Cc z#NP^dJ-izn_kP{;)84Open;Y-=(nDJ`spRXHcf4~V)-K6(;e0jZ(CL+9Fj&p;8roB zI1yEhMXEMZ;~`VDUVdY|`8b_{8}|_DA*wkVsmA&>iTCw6qMqc34ft-_kK_LA{D?b$ zTx@BZY*^m4VKVSN8tb^bVNa{d^SSOSwyhai?8F$J2e!38RE)a&XMQgrode=|U{L>4 zJH39AOwDDsOtx)Yx3Jyy^_$G-^T@vn{^xg2mM>e_G=5`@Gv*ol!2OXZdMAMAVx0lv zI{~k?GeEPwpZ7WVTV8s{_p>+LaKmk~%kt&R&32@bS9|-435LZOHyz`v5s%&;@DpPs z^7|PtitA>y8t+wo8*&QusT3QJy0~WY_l^Fy;{3DI^}cAr=Y!)eHtg=+G&SLV|JJ`1 zc|hlZMa6B&(S1*R_CLx zssnHHB^r6tHLR=IXU3@FwH3YYy6bKO7LGyDy+C4v7~yx}Udv~Iy1k$GH}sxgPw*dp z|NGy67T)LI+YIk92drH=qx4hoSN0w2R4*ER+pN9nQ;kjitMTWt<@>tDK&8Lwpt)*j zOL;By;hc}-dAzQ}{XFhCBtBw#HQF7UZ()*NkeAl9GPM4c=8Hu$6~?OL~JMP5sNEB&cY+lohP zb3lw|@{E0AfLK5wCgSe}y;h$GR`>q+JDz&p$9JGknml>(!?Fo;rFha?%K_Zy}9*YG3g5wZ+B1{8Yxj_GHqh zXWVS}^meTCXCn<**kApVj@#Fbv%K=;v>OcnoG+G3e)Xwh(KCHp`zWqC@?Bhir@oeq zNaGxlk9|7hf|3n@l7Ep)@y%!jt2lCz-@1ea`o&|Ka_w!jt zPn3W8_P4)%(aM!8|ATf}v0{Z4@l{)GSUJhDNBegu#0l#YUNYYVuWYC9Bu9Mm=hlQd zQsdE2G>_5PI$Dv}fZ-ZEcZg@ol#Gh}O)KswuD|k&4g0U-|Kr7j6D|QhXB6AljY+mR z@ax~UcC_Vy=?`3)vCsWqI$V4CvH1UA1KqaCo(FRM_4YqJ{m+=K#&Pv?iu?aK?X=S_ z0wz9|utALQoXqp`YwZlsmA(J{Z++`qzlr~^zLM(~<|8jVv2OK>XBzjEDn5jj9B+;I z5q6v6xyAU5#U$V4f$zFJ#HYD}LyJvkaHK$7dL^`5gZp zL0xu(X)N+{X1b^^rmaRTI^_l1kdg! zV2*$_uKggbe+RAwIOmO5pIlt{3%?E^_40{g!Bc&@yxyu^nyp{`I<_Ca{%7p2Kn&-2 zR$M09*xoy<+XSQsP6r|f64opyq3{*|KGmzo$vfM{*S`;xb|lDS+{bU z#gTlj80d_uFRAgsfGF`GKDSr!-hi1J{8Z;Qc}a$3M#}!`6M6NCIz>LWV?5u9xvBSw z9rpf<@viUc#rNP@-TfJFnk$bFYi=fAV{C14&5?&5aqYanxMs=N;*wu|x`SRfTzP`6 zS#m}9cl~jE)S-@7UsB@_()u-bbw152${1FB$C!^i+wq%W+ldWerFsTXut>xn1_?jILU-z~4>bp7sjxB|nHtR`%cOBp-Au3*$yK%$@s(I`*x~Q$Nwz z$93?0u+`QS`4q2dt#SO+dw-So)^e}q{y$~HrNypI4?*X2pFfQCxzc*Lrc|o>a;;Xk z&+8wAXM+zHkKE_a1uy)?C!nK08z5cso9i2FqxpT^)Q4;D+WMt)DWjA11fO)N|9-FE zb&zhP^<{9uSne#=)z+_HzuSTTqfmOHRG$SL?(YQh{v_Y^jqm!!=l$=0``h3CO}yj3 zN$bV^KeG#I{>Gvw2$4 zug}+Vexhy{pYzFL&BFd!FUg2wtMlq}>_a~1;@7bMH>{jci~kt&ch3hx8d zy$?W)5o?FjUp)J3?~m{L^@OsY=-apN&BA(IyYhi(+sdb!?^hLk?rVHh`!~cGgM z`N#Gwt4sTr_!OV}EKlP^W6Xn}GWKjcUq}33gdDJJ?r^*2udc_i23cLj(?09VTti(g z=iF87Tt6QFS8Y6gC*atepB|ccndPMpwv21jdaE?{0dMO`H1b)t>5}pU_(Q$^YzwWU zqQCT9yXtA%<66Z(F+!}M5Hmaj#CHOTt;6wIAo0(<#P2NVJAVAXXUt!GxA}c|*YWH4 z?$f%s{$~89RijEAR{YyCUTObWI*f0O->Uy!Y5c?(jeLVnLBH51^6bB1$-v@@3qE7# z>Ue)`^x&V_*_-G7W^!}=Az1(J%@5%o#MENZZwBH2tBx=B;yL8j)p%bcol8WsPF-BT z_3xm6!g`QVCpXu182{?{fAgx*9q?Zl|HKHfLd+!W5JSXL^}B(@+~M$7-TV2zkM@54 z?<2n3{I9)w_3B%<-fCUk`@7`-ee|#4onurON0Jfw1Jy1$&1jsn64UqJ^Rx0?o$jym zp5OZAX6xRV$4flJu&cK;qLTO+2>8xlj z$*MJwyW`8_nBBBW)gOYA)Nt;t;6jM5cht5XFu1 znKK7V_3Ql3=;l@UZW?U1EfvNhd5g&yE3#|lJnDV%9Ttrb)csV64~sX{Deg2^mfx5k zq)T)8E&8`ukIBc}8vNg0zvl6F=|XCJpbcYFJjMQu?apFfZNv&OgF@_7e;?p* z`(6<5Mev$L_x<>LhWagkez*Cf7hG_`m8({*vbBq9t&y%>^=wOjN1I1JvyEFkgFkAqg@TeWNbMx$?=%^UlG zU%UFbF6Dsp&p-bvVCP>@h#~#|Kw_)8+b?pu}3tH*_y z?3#Z&_zO0+I@&$(tc!hfys#gA*QEc_=Wi&UeE7oR;fcR2rr&?A?QQLdeT|PYBj2oD zdWf>0#zp?dt4?s-*!~avfQ}#jOYHslUXR6E#HR2}d?vi6{oG_HJ7^wJtvS+dvO^jp zoz`P7CI%-A`(bfQpOf$%gikuAc`iw~rH-$(SguHGjy&aFasFrUUF7c<^Pjq=gZweBB9JDPNgS~%9-h;JnqqrB?xE8EG#kO?d`+?#!&dqMgX1t$h zwj6A9Db~mR{q`HaT3mwr4jJbW@8W}fazE&vd9_z*>=V}NX9CZATEFF%G~eyleYJSu zv8#%O_|DN#{JuHQ3bEbhMcNh#=75@*;Nf;3FrNMOJwKlPb>Hu}88c=)s`Ye6H?5wC*iJSUt?`Ha zBAVkl`O|$?`lH(JbHLx6!{$kR8oy@2Ek*xpPx81;v6f?)u^I|vj`+|Vac++(#Za9- zY0owtz^{=WgNhM%e79j9&N&dj$F*z2q#Vb2{bB#&oDt)7`k1afBh;-Q^^2dqu9!6P z#|6L3lh-XfgtbRI97_J;qyuT>Tj_B3E#I_zDh)ob@qe^g)l&|I^u@&;0bvUuS>bv-sQhzW2S~LtguxaKC!> zYFjsHENfTKO*xo(xLO+Xv$v~Gm`Sw8pN)HBnak-5`9rjALI0x9rC-Q##h8jQ7Wo)& zx`x*}ySn~piz_bpEZztDYB6NM4~lz7|GJnl<(lG!*#nA| z3r81g7LUa{!()oJCF6>d4Xqg>4^I2_xXLl zi_f84FTHV(d}sRuP1apAM>^*-pXB_P;*r-joR8lne&XSaiUBvAl5x`6y44of&)*ju zbnEHG!;>yA7Cn#ktb4HF^FJ&2j$r0lhj#+3Y@M0vNl{pz>o|t>PWjto&p*+>xM#@E zi>vV5fcBssqQ)nG)#2Uy`gMW)H3ZjfTz}R7O=C*FN%pgTs;kD3`L&FzTA67qV?JUa zVk2UP7$TMurid-RQ>*U;yqdoe+Uo586}-!UrTCuTY<+F37dOVWY}>@%87pdUMd=gC zjJ(FBI^H*`ix<}2cKtVt@gsg&48QY-_@6q*FlkrGKQh*QdN9-sbRmiT#yxZZ0MaJ{z)qu^n{B+Ixy!c$aY3#`}srn~+DgOef~_zbpMIC-r0O zN37TJcqe!@@ctE)!|58J)!F~U-}~P8&aGQdwXS{jjM5i%_LJSaN}~+$+xa=s$eRyB z-@d($vwfUc_Pyd0`0d+c@jLlEGPc-1?Mc(_J;(7+U4?DUk??3| zgrrl}A$)%RvA(vyDPDWwo_9OW$d)4{6V&$cK-r#UbN(Qc{QTKhW3T=e?&GEXkUE>* zILC_pztXX60B^AGA&FIp`uusHj|tfUd}aC?P+?Y zT*v=8%=ee0{KpoxvXS^JXPv;P$baV-u0kxtZ6JgIa{w80-*PnCx8TUL+C zI3QMpiHHyK_?^6o#1u+=4nWMkTJHjNdiKZr%U{L+I@s21J<1E~*34_LVXZxbjT>xV zwQKqueb6O;V%%d7U10kn_ePb0H+|dApgYzz<|oD}bHU05Bg>p&X9hfNZTZ>k*Gd0Y?NyoV|ImbUGM0sh z`*81?SoAr|{Ymoq4({dW9ff!DQqCYn*|>b5#j56s@n~a-d&>d!>)XaNFRq#QY{4}& z&K*0}LkD3h;hXp)&K+x|O&s?ZlJlNxlQqJ6zxL?oLMY*?(X76IJ+}7k_>UJEc0(6?6M_$Um2z_i6m!#ydP`P)@Q1sqvZ*)NNSW ze%TAYm&-?7LtmT+cH^EwX>aN2xnu{<48sQgpacG&oO-E^kv~c6N?dc~)z@-4bxk?S z^iU27YsvjX9s6zq1|lY!@x&Cdl`uxE5p#UsjuR!KZWP-cX}Vjbz0BX6x)sQ?PqvlTYISXPt%ucUYp`Ft{-)_ z2TMNaZ*WiRX1pt_eNHsuU6|*)_xF!I-`*wGS%djz2li0jqjS4xtw~SJkDSwLbzj_${eBzH_qeCAv;7{McSo34e5B&3qwn*s5A%ZS-KhT#+~*%N z&g>ql?dt=ql#1yeb zjG+)~uhw(G>b1YV>&thP{|@&m&yns`{KuTIdCdc6YuT=;J!|YGyX~*NtK&B;A6i_7 zy*uM3;po0mI0G>DVoV@T+wm^^wU>RNL;rh*{4DQ3zE`HWHjF!;V<2H&vdjJ4ucv)& z9K$#fE%|kL-+k1brCyX=FW`A9o=Jv%UgNSq z%odn4Q0qh8(pt?g;z>haUw>nrjZ6K+vmdcg-5={d6I;X>O8i}rL-{UX-L-#w=kMd= z#*G^$-6P&3zqV}!e2Ft#@@=LS+qj+GhK_^4t8bJMd9_z*?9=Awy`4Cxj=bx~7FUW@ zQkD?|zgsNFbtkXyHsSgHGx%=nwU-0?m6+z|`Oo0pGRU_W(*Dl*ZGQu`xDIPDU4Rwo zWcxdC7Io%W)+bD`ZRX@(7gyoCvvoSex?8XNda>ZyyNZ`^O;6nKg?8 zj`?>Ob!_K0%xU$|A-A(%DAbKQc`g8N*DHxa?uM23H0sIw5U|yHJR8V<*ZN7VaJ`Ag z+}WPc=ZQ!90?&>4gz|32yM1$UPmllqi{DdRg7=v3!v83)`jdSpj{GI?&-W4B|5*>( zopu(DJnOnrs%tS>VQg*tkfLqP@($@Yo)_%igfk6wG7NYuX^-4N3?+ScAop2J13SzOzIUR(`k8V#`bj5JE6|Vc$h`2xt-$>($332f?c#n1yZSy(-JBP-GXU)l-5u+W z=|?f6;uE=88Td-wS@+xC;LR8Rt63bIJjV2im}5&TW)=#SQhacGrh` zo32K~*wo+r2;S}GV^tSr6BEKmGfHd`V<^Pht8ot?;=g+Bk5`qCI1Rf0zM1~2-@NAj zWcy^hXixFUer8{*bAOoaVC!tp#Je9l+P`u9-8PPSzj6E!yAGaqf3J)!#i=lr@gc;oMOKQQ_yc;3)4 zKPeZGUVp_2#nX@8Xy<$G`Fpnj_wA#xR?G{T%r84RALWCRx15tH`>5Ufb3ga7q$pfd zq9&KJsj~|7E%O6-oZ0WY=hqqc!sD31XTmm^Bj73Hf@ee6$!r#E$a!~f9A7M*I}E># z;?G{<`m@gx_G268Wtoll0d{Y~wGZ{>+|-|QEc=hXTGmphYeVwAk+ z2+lR6nD^9eel}swBsM{B?faFyew)MQhA(U``XtR0DP%Ke?8W+?$N7fuMp{0BJmI^8 zzrvhT@qWXL_-?_DwRrD1`KLSwP&Utkq^WRj@}kD8jrDFbQC~+{=W}o!Ne1NPwPs%Z zhZbws`aN^vq^=NC_rM@cEN%|8b_*{>7j6r;Bf8C7V{T_YS_beAVW{8_~einspw>$L&*a zZ*LZ28}H|B80VNLb`vJcJs(%Q^C@4ZcIH*yuVJoUK5s;MZ9%^T-(-Jbu4^wl z&hiC)yL0_G=uDh54%}Cyd49um885yb`laj>dGod8nEx3AWwG({Kl_<{r`6aeHiVTr zO8iWpK7BI4{V5dP1E_ls;9%gNd4>3|e&1Km{lAF+XS_z(Z$>w@P0u!nc5KFXX0O<$ z@s0kVpHV9Q;B#K@?|$Z4P`=>1BpX-XSH`QbMvB6{mhT44dwO887xBb<>D$+hN^7D# z#z~5o6xR{&J9+(#K1R{5eUsF&HU!V~uD5shJO^=J# zZNaCt^uj@4b%s5euxKA}!{i{^6-2UZU>Q`-7KT=|& z8E#Zhi~)lupzvKAVvqOV^&Zf{{628J_K)}e_?^V}yzhPQJ9XWrhLMfV$k|oUBaNn4&t2qO7MNd{LIg(oqgJu4NY-W#_A1MehK%UrrG(LvBZ08 zyuRLP_mN$;<`%Y$GOmFG#Wv$vc+>T`uEE)F+N59M{?$==e&73n`Lu8H9pkC?U6)6T zm+?Gr@8$;}Zz%KtW>}a<)^SeqY3@WVKcsruOIS84UD2O?8`@~opu5Y~9HJOwb0-R( z^Xyzd9KV-@vkcb|_UPHuE=hC9Cg{s~tt8&`C7*0e-KYycBk#Vrb*lM<=N~@b+=cH6 z?%429s$+ge**YEHEgVz&Kjixu6DCvhB+58ZpUBtswL0lW8u>2B+uDZTL}+sMSN%F* zj94S)P>4PKCeXot4=CcldhhS=FT3orOQD@_gk=Ww5jp1nJDcip451Z z6_sB^{k|Uh*?Eh8LopvD8s+6PL6C>oA{N&#y%TZj`>pogYw*mNF}4@6v}ZHF^L7tp zr#bRGic`dMo5wNdcPug<0heFd&J`0ftAAG;Ip2HSZf z$sy0VJg*QVmGuLUv>h?p!Jd#oJ+f`1ttba$IF7a!b^oL|NB`#eVMs9NYl>%lXIy_@l!VVuS6{s;5(cK+VLLwMdf!TXt9 zw~g!VagwjsOnw<-_AX$upV`FL$9ZukNb}~i0s3{YXVTa7?1Y!SHJ%s|ULuAfPpkp= ze~)q~?*R(`@!p?)>+53=Jn+D%sAJ@{o(*f~dVIltTCcF%%1*i6Y@d8DpBPoFdz)Q5 zR_)F`o_)2~OBUnWVjpeTL-~95yuWk{miVJ?aV>!@7{2)kIyoxKjd*8NjJv~ zKRd2P9lPwG3fjr5zuH~bG~VQr#~fA?AI8}lO(xe)dC4|*CK-h9;`a4?Mq4tsfeya) zJJy%6;PY#&>*RXLPH3Z@DcjaW-rIcb&eKQojh%O-Gj$`+wU9T8&uXv6gk-awJgN18 zPc^2Ns+|E564AK&}? z9G*?SKz%zao7c^9+sU4^pLo*9XWLfnY4x$Lyp}h6v)%T8P{|kWB{R>%SV;34m7j*< z`uoHV`|qlGH(CD@;~^$7-s6!nV*ESLyh2P_{EA}CL*svrT=VG;a|!zcZv1Ak9{&rG zxaTtlUT-+Ik}T@ohIOR1Irh}X>jD(&Lux!}Xg9rw+V}?D*e8$m{%%k5X)Q^P<4roy zH{^-wWLxOAYQfzZ_rm{mc&_2sEba&CMW0X?`9^X)r&1ivwA z+9j9vO1$5*!DpTc&5kL)C{q|_OtG-eDEZireN+ETQ-8@YdFUf*I;3%|L)pA`R&hR@Py>MUSGOd*qg}=ua z?IJm(CdcM?8|oUw^o7l7d#*)P_djU+?048d<2%HO^)Wrsmvf|f%wO!s68DYdXMZM9 z%C~q#-<=`fSzYRHa>z?&3%YIXq^9^;m+}JMt=j|apNOJo0P!B6-UCGQ0sG^5Lihf8 zpMm!qIVbNwzW1Xa{pjh^EmGwIQWm}olI>-_bGxXn!B#EpSINDpzuVsT9^>J|2Kn%_ z#RK~%?2|GVFiw>lCXf9!-na2{s_1>Aewt&In75+X0p(|lU+{`w&TVy|!XdwtG627+ z9kDL_bFM!4?cIg=?U9$ZKW6v0cCH_3V|`yu*lbIb^Fwdp&3R%YwG$tv8$Kr;ohDqe zpX!qw%V&u!xGcxHEN-&1VOX3unggWugT{nC02i?kJd zqO*a`VfwfY87FzoW>@zG>wMjw8$37D4(?lyGe2Xl(tl{9{;A~HAogqUpU0U@pV#Yr zujd)X?MEHaZn`>+JlpEp`EG*nLM#bG^Xo~<^Ql^eEEK%_zrl;F^c&uekl+8WWodIS~>4#Tw8oG@At|rq%4DOJH6Pl z_QB%i9kcLGBHmM@P8hFZ^P`Sbxc25sh{JVmszNlkg#u18*x4P6njpcd3WU!CU`JME)xze1H zS*65E#Evi(>n^?Y(o2BF51|~2X8;lZ)qDRR89#pfozgMVsPBfg&zn7c{iQvtYp2cC z$9>>Fq>ZhO{s{K-{%OBQoB1or$bPoiaeta@!+(ke`U>sL%`6^2yazZI@2KFqJmFID zx_aI%=X-ZTGi_DI5N&8ap$aKTk5AFqg$psS9I+_|?8a{0+G2TN$g`pQld3|KMUwCfUw= zOy>H8Q`a4Hpzjk8U1Zn4cCU{)1-Zovc)qLh+~c?4c|P9Z<2(EK-5rm0tS7I*<>YxY zz8$l~E9c*}-t&NcCXeyC&iG;FXB)#lX9g$%`pY(Rfi^Mm=y{OYo^?IBBg(EiHj@~czE8hy{=v5;el zpRLZR=2d^?a^(Tma~|C{lFz-)_a(1Oay4h1Ki7F5VxEe-aqHyJI8wV_t`~@7A>jMH`Z;%nbvJAJIKBzm7SdG8Xi7M`1X5A?4N9& zeC0j~axDfJ2f@e5-i%55)ck|-jqy>+*s*R{@i^Wc6GnyCoA8@@7JI}_!WZM;VwG_K z*@|1|hvNU)o(nm|yr1zi1rGRsc9zc{j%$6M_a~Xo)Un)u9dEmcKjGEz#y(})4SqMq zk#c_VyNOx*JQQ`Fx2!Jov-UJzYhoRF%Ap-ip4Cwf+aR}{x`8LPIv-=YCmqcPiGOPP z)s|1=`o|1DE8B~GDaUDJ9x!~wBZ_rvE8U&QZu?rx>;_O)Z(Ua}jh@v>#Tee$zjoFPk`)Ap%m5G!%-tmBC}VDESoJp(+L-viRKzv{F9 z|N5?Xz3Z#YP1XEV)tURLY?G<2mv-U0WuM5SPPx{ut4jM?f7y*R7Rs~Fj(N-9Hiy^y zIiGvK&uwP_^taE5U+O~~?AbIC|G)l>fPLS;hu(gw%V(^!KgSxM;+8yQ&&2<+_uS#w zR}LT@jCc9Aw?Ay}@b2C)+Q&0S4Ku_ZFa=(iB6Zva%o4+zn>6yK4`bYQvprjVI-?vX zIf2jPW1Zy?>X^-V!1Z3;cYT&7c!V|YiK z*ktq9ed@3Fb?tkCU=yw$PI>(&L z+}%DL&*3`8EMs>mzN2Gt$T7LNEY8E<=;4`xeMQBOAAYudcjcvR)9^0eNQ+g&Ou`Cz z?OW7Md$jTREV_q0=JtJ>xxn{*QOg;YBhZI3!P{OAeouSKF&MMQ`lRvV+1Kc|Uk#2zu2ut-euxjZp?px*(g;$QFneBhdEuDL||G@~2a7dK## zK33d_cZ??6Ci_}lv=RAaXXW6$w?iiTH=NN@o#lC(6aI&6&Yg3v)pIQ425pQJ#SQou z@ecW|*PYT~-{Kh{#-X2k8NWmEzW~oK`=6ioeo~Z)BY#o6yz?>qF3%&t;PAA^Ck%6L zlo>Trv)ovw}RvmhTBZhh&$zBgG!|PVts? zNpfrqcw(j*o>Wil0qY+?;a;qF01h<%dF@|^f4=+kkE2JAzD4?M*s#HLCXM{&_0PL6 zf~~YpV01xM#zd;@QV;M!a$_N3qy-x+J~3!m)~tC-A>SVr=_3#`~v=2_w!fe#?9OTOY)H zxW`ZIB+TV_&g&rFxKB$r@a#_IKA zTE%-+->vOWru9a9xeaJXJI{lfO)J!FpKwpi)5eB<_)@t;X92fq@)vE+LO-VQ%;$#H z6e}@yGBz-W;qPYl>vNLhT6-7hZP$M#j^`}kzOo6pA|M6UaqH{o| z7AvTypE1UY*#zq~`}q2mo7rC4+t(KB$YRc}Cs7WFT<5!9LyJYv_9@S{igENAKm6ym@4vL=L0gB<#Wisb z-~u$`#jwX%c1#HqPPIq-o^Ja!`**Q$4&r?NQ`Z!u?mE4=>FN{kTcIc59_M$8XQp4~ z`+t&EVir1boY}tQHD^b?TsP$jwwHZ|75)2uwalX_N3NdVKiix0usQW$>%(fd>YRTRsY*Ko0sDmKZqOn-*PQSL`=`Gf7LP==oal04M&?3>$DKVn8$YKE^_vu523?0+=jzxq1>JhLB+{a^U! zdx!t}{PWLGmwwIY_V%fHeOhPr`p5V1ln-S$P{%W30Dj>)pX1|spY>Lk>{iP6v4;8J zQ;Qq+g)AQnx%S!P#`6Qm(zmEHIt=gSO)m!ZKMmMF%JA>>sG`rMpTqy>z1{k5Svjca zi|6@q-yb(DVc*XDzAr{>h$kiH75l=B;{duO>}szz{82yRl{QGYjg(`BV^Usc54z?l9$+1E0sE$QQevixr|SMn>=A<~b?*Ql3jBX^ z;lhQ_RrRcX-o76D9$+?pwZ-bYLDm%Fatmp1Y~xj3C=M z<(RK$y@`M7JMhM@;#!cu1D0%nxpe=~a;e3E+a~Lo{GQ`X`WsDgK{<#WlYx50o}VXC z&v;0&rMMN1ayiaooP8PRjDPUf*Km+>gX)M2_Dz(Ssp6>mnb-sN`CEVo8~=QtNZ0>y z|37-giWSQ@ZroVZ73$0S?d$LL^(Pz1PL>;0M@qYdTxs@8?b(*FKl85p#ck^Hl3!>~ z`qOahel-7EEP6cHK1JW6FWWTU`lYz>vpL$>p2|zx9w{CedtQ!3VTkmJDHmAG@ONi^ zyW_Fqx46guGU&_O{CYp*hxn&1km-7X;_P3(|RpH1T|P*{D;AedZaLi#p~p&M%sDuy*QfpOfC!XBht9==YNS z9RJj1;nUZfOdFT-qjDk7M3cw-!gY(fV%)^xKXkjmCg!8GcH6gAhwCSUs;P$GHvsUO-xZ!aZ?o z_L6Tzll|yhYq$6+{Yab1Z{}0`Oy8;=^I)vuDAA0mfEgd_cvDPSKkliU?m?Me+|>KT z9FJEf++1_XXN$H4x8QsFV{o5u8n8dTcyaT6wy!a+70;A!yyB5$h*$W^Voc_-`pp!H5}3|d-04wlsw1mY`-7>-^cG8X5EwBpP7D% z>q;NONAgwnE%8r(b3Y)?X&-zhWo}VF>yz6WV-OK5$I9JX{apcZ!?gNq7VHHUOJ^2e@wkKNR@?9Db{4oAhZ$`F}BVuIXSevnPD4 zYa;rI_Jcp&Z?vO)=r)oq&Hm;G<>Z7djCZ`y7x>>E^mXzTW6}KCjxmfG`b%}1+x+MA z=a^z&#Mg`Xz0D=~UwXnyq_f_Lv+i+(Q^AYA4S=sc*)A z!n$3HvW@XXn)=!O5se{54#;B}|C-m%0chv_0APlA+O%m?hjV~%M(hFm{2icI3;&_2v`L9~t*H#^X})tb16DXZcNW=olN0{r%D6{xRofObIijqj6uI`j`$D zkHW6w!El0j)ZVB)yeZxTz8qtaN359)@S4Nsq+GAFJ(&3ipI6VnIqO=*f6QOTCwy@X zv1mCg$@ICnwwn2&kL3u_*avkwAB-FNlLq|LX3x#+jWxNQTt?*S*WtLPnt|Uyyvgz| z$3(sRBk%H_h5H>k=eenq=K_lu-63@={KrY>RuIaYr`Az3@it0sEhSmGS>_74OndbQjiQYYywP zbu!etsU{=d2TX%7BlQGYnt+%(+sG zm<(W#_X1zsIs^BhzL9X{d#dulEWG!hbw*rUjFE~@^JosuYc%0NxZ@bda>AUAEpc!C z5>AEry&FcPwQ%f+V#}%_#g!KwZTqER2N;*G&FI$Ec6L| z^p)i)=5g>k4`ja(v+P5Br(EE%;TSZZnGBc1HY?gkEGIxN`gpv0oC&{_?J=ElgZ1+` z^XCfS75@nX9P4ojir+Smd#ErLDe(U?u>XZu5&y>mBGQ|K0o%bExdTj&@50 zei!!5ez3XO5a%wljrj?EA;yH8^Slo%NI5``l&CkC-R)&8DDnJhfmG#Mm-J{ zFC|v-J`iN_OxnI`Leb~4&xIV2;_||y@c%cjG+B-Z*H3$(;S{m0&lzKk{j|5+IoNb| zd`VyHODx*>5suNo+ZyrN<2PY+d>YR$zEix&dmWgkgRW^V zokc9yFeloa(yO!`^>n*?+wg(7|Ci_WYm!CJ-c;Hm$y#vb69WWx77>!nO}YF#4Y`a`9Z0#baorFzKjFQ1;PJ5*8SGNhdzwe@~o#k;q%%( zLCYB-KVYn%J=44?@93PsdCK@7-U0u_nJ|_x_rHMsV-ErT`7E*S{{L~CH*fxfa#B@4 z>1tSnEo^OAv+|s;*KH!a(kD6#xKC&+*;e^MbrG-0rd9tKhJ4RwT)2HnIWNYr;JTjF z;>LXB^6h+Q^*#^V_dx%xtL{P$7=i16>BS4P`v)8yh5yU#G2Q2qV~S;Sa9`Wv4&$rw zm+(@~>+vXj3r~&s!@dqVw(nNtslT7ux&K3+&4E1}-!WP?Z)m`b>*#yGuwUkbCC}q5 z-HvNE!!G1dRw{-k%q_iKmoiW2bH+V&NQGvUvY%y0R;g*DTbW((TH?87?e-PZn@{fI@s-XiaImk)HUZ~jd=*T&P==3|b< zxMXvSVepIti%s!{8IK9aS!%a}7XGnSQWl1#{8ju7UxFKUl!h9}j(%J}Cy$9DdQ z9t8{a5|xgmyAe0Eq1hniHm+HA&G%U08_$)gjr(%yw}*CO&Y&-tf0O^jo6YDG)U(eX zjI}e+i zM|tm$`09uEYuoXAzk443|$s4vD?;hvGl=rc=#2W^Qb?Z+oCYa8xx+Q`1( zjXHM8@7*-6`1f5;;CIIQAuk=Du_Ik??R#=DXZEeQ#(o0l?D0AGm@J#8%uSYO()^C` zq?6?b&d<+IZOvK>)_wc+C!3u(ujXF2=QDhB4ElSVQa;ZBE|+z7*04R%>zG%CE72;xaNgGWpIAIp@BhZ{|9lqjB)%;D zn$g|(?Ptqv#J<*;Y?y6epRsnc6K%k}$m=V&hwb0Av3%Nq?_@Lc5q;vt>@T0d=Q(cW zyN&b@_5{eR;(jmZVcc>Y_$lN54Y77~ie0CCUzRcZ^416N9ii#C9~iGo{fy1H(-gWXAfKAQbuwRM^j`jTS{r{_^)-IZ3j`&9#W74SS1D_|G(Gu*3w*SgWZ?u1Y7yS6{uIz5iET~Zxw zV7Zqz$@WHH@XEjPllg=F=||e1K2ARHwj_h?9B;T!b(C!~Jw_OF9y7$K^BeKMxOqJv zV~qU6_nwdY(uhZt-)?^x?>RnL^uP9`N4WBwO6CG10v@x&$ii6>=wY4g3n`7GP#x!3={#69T0@5KMo#r-uK zBON&=@uZQL{;cErb35mSuD&Mf%ynr!Bk&!b<6$4=QO7O+ce<^MYotAiSyKAT)-CFC zlyClh2cE;?eN4*J^$Y#NbDH0ypt`0afmp0VDwKirg@*4G1m9;F97e8AX~AD>r%Y) zw{i8P;>O-z%>84|xb3_7+N0EM_T%n@J0B6!2KwRyew z3w-MD_)7I0hciC+`j=5&+&nJHM|+3i#3l$8^h0tTh8TiiqDz%*w;M)-|rdMevNF` z0{H#uW_VKlx^?UL{tw^(J($1$0r4I1uiyVV8t))&+_Gg$!9u-6r6cLC_VHzIgN<#y z#If=I*|169njPI%<|o>aeleT}`x^F9uQpQ3Lx1^>)bmDaOR>dyGoFa~?AtWI$#kCA zlGj`s_kPE}{Z>ED{o0qQ_uh@8@ZSFnyFbnEswyTUy#?0-?MrY!fO85Ppf3_zzzNS2 ztoQxYv5@g)I1-)`H5nWud5PDWSZ{NAz0HF$FKrrWg>#V?o^#(c-t+-aTI!bOvbmv$ z%@Os1{yL9HuSB_5J5FiGql>FA{tU*CvbAS>Bsa-m*EHK6EFPM8QO+qY z`>5hNd`Hmk16Vw9oX5b6DHqu~O`iMTa{%{z^>xaA7Dx1NiYMg>?nj1gi@Ow?X^--_ z#GHyp+wT;cjQukHc^o9{2z+n|Em7}=O^(yq|2mFGrDWT z{jfjRmF$(M<-c49TbUiw^BdS&=PTOMa{&8th8-KG*#A$KeMR~EtL&HU%sCJv7BBF*&U%hL#02BQ;y~C}JUR9AJovOH zc}$|O#Vu(WKRG5D_tYW9ZI*#CjDL=G%9!-LKpl2%xG(Re&GuJf57_7T{|@%=fATtl z*Z;yle`}a?er)N|rO!&gW^~s^d^CdoSjFaby*`iM5WOy+?S*PLiU6}`{pI-yPKW1O9Rkr4R%?IFqJ^d)LN?4F3^R z)$;(c2Mm5ZVUhcP{Qchpb?pza_B$f{J`wRB|NqB7&z(DWTGX+Q-?3qu*?~6GvliOf za2)JH8IQ&HY1AvpS?GMzVxcfytN%*;V1JTGGstnY^Tq*ZKPdG`fCUc*kYm}DnjUP>DE@e z?O>}7N`go*WFnGELM9@a$vjl%p)!)BlB!f@$V`%&=Q&k@`#ryB@BO~#-v7N-6+$4x z{jYWY=e%cn&)NHV_TJ~c)6G}?!wfkb|0P~SWxlX;9*Z6{f7mGWlk;UBePhkhruWL~ zxl75l&F@=qu;*TdPQgoYzQaIaf%|}y;Co<5_hn4hg`H5lu?8MvOy%ZuhuOqjVz2B# z*pJp%iRkH4bCZ}H%hpU&U^fd9M)kbC^N68HS2>#x85 zv|87qMT@*<^0vJ6n^%~dwwblZE{F3G^IN{y0L!)a`{@BPYG24|n}$1^Z>qF(1;yZGGXDj3zc!h46)tm7=8IN;%X zPSfjN@0&4K;q=n8_A=gsOU12W8?f4EbT~e2C&Fgnk0S;5;6}{jag=ks>nXdvpR3{y z^RC8@%t@bUukn4}ofk9~J5Vy|ZNl+`Ir+H>XSp7NI=OOJ_hagWkR$RT(ca*ddP zvbdObgBal0L-(99{{^T0PxtHgD=gT47-Mjq`xNHto`DzIA|BvAg{c~&yzGE&3Ug#1 zVKZV>uIUe!vp?qnVul9)5eGCF)_icL*q}kMh0NS8HDO$aNimgO|iU$Urhf4xFdBPC5BmKDxDT$dPU8$$Snyc`jsM zb>!AljEQ?`tm@E>?Z5g|9$X*cndg8P|E%Bp{Y~L1&g_x@e}e^I)AM`9_q2l}4esgB4l@>xuX#*9m2xIH;iT&>Z_Cx*ad&iI z!~+`ZF|o$73B(bOC9;is!?QSR>V`J1|S4Jy|?Vr+ca{;1+86T4I%H$+p~^7uW*CDr@7==_T`X$-={Hwk%}=K3ve%|#Ee->aqY{#-kuj_p2D4&XNa1RbD z!Nu%$aANdElh=)jF3xHqRXHY@)f2%tA)w5nBaUEgNL$cFE0p2>D?v zZD%z{=%dw7oD;1d#xqXbx9ykitXu0a%d#Cl10)XU7x((g;J#ZA*0X7zPxpz}l~+D` zp64^a;k{FEGPoagwgKQ3j6N1;ffbKlI5>9Jo}RPpNsHHi`rP{l-_Y-ad%f_A)NwYr z=pt$A=MUePb#`mj`kM7S z2GCq59rZ`%KQgWT&%5@Z?8}~{4|866(|B_q`MIpN+;{0c^5n7ffNuCaR?Hu-Gf=*7 z!6C*<+3POv^oPz86JSyGftBF3acJD=9^}R&wjlYQw!mi#w}Ip6*J0Us4a{~JHtxlJ z&l5JF;tV)b{F#))J#q18lt$nC_jLIG_A@~B9>6ypa>yZnHEDfSzhvRv=5p{hB`{`b8mYwy{$l5lc=f3qOS^If$>c%=M95q~N_2zTF`Ul>@9yk*Yz?;ffocn(U zpUu~lD?j0=|29~=Q16ieAD(xVli*i%zIWg^&bIh-UKhAbXNSx6eZYT~ekyDWHlXYb z&jhZ%@DrLp`81M8JkZ1jY7hU~b2+#f^}I$w3jf~cRp+tUUgfnzW0;3MF~8p9KwsxM zzskk$$V(=%{;DHC^5748-z`7yzwO9eo925g_nbN?-|usW#*^2*dRq{>KqqS!o;f(~ z@Q-%)uuLV}uU+=3h$XC-uopF+y#$7sC$MKbqK8A3sM~ADY3w2Uwm(NM*uj*g^3XxZ4_SBim~km5WC^D&M{?wg4Qb?D zI%K{zO5I$J3u6AV)y&H|mi+YNKGyk>!t2!+|D!Qz9fJ=z0WLMBaNfnP#*gNATlRhI z#KC&KM~L^_@%feFedT2X$WJ}?zxcZlnb(|i9^48p>)yIYS5D5xn&7)($X-@j~6*p;vqvBtH`;We?gD0>|#D?SfZdnG3=c_88z*5m!y257zHl3lwt zY$EcdujOogCr?$+=K?O$>j14BxySjZ{!gz944^yDNq_K=m?&O?kFW6ai%60M%OL9aIj&;Rr+ngBlLSX@7CP!=rNo3BIcO2-|jkx91`O- zKl2pdm?!K)&&QUpwT9KjBXT9LiQH?VF2=iG_G!y~fBFUI>psK~v7TtFa^=2<#~kn9 zzYFfaBW=Jt{GEhr7oMtl;d#F%Kl@-^tVj9_zM|jg7u=8j=pgPx-{S|jUjN0^8!`>K zetFg(dj1$!ysvqc3k%^V;vRvOz?1Lq`^L3nOZ9i|I55h#5pdktEc|K=m@i!VJ~b~K zi1z-yCt)YR(lBAYpreFibsf> zi_ekA{_A{Su>fuQp1qV+^ojn#{jkZ%H2i&^cY1{LH9m9umaL~7b|Wwlec^S~qr=aA zYPaNMaq}Je9!kAuRyZxJ7XP`96yw3I`+-ekxZ0`bp2rW?={1CnvH{=x`ssQN{C7J3 z@0*w&q}{9}&b;Sis>C-s-<1$cg6~tzV{m>k{4|#Df^DXY*-GjX$JLh3%d%GJp znX!xRKY7M|_TT5-nm6sXbKygI*ptXRd`S0{@lD2_@Kb&yY(bB=!VUcjuib0V8K=_6 zxK|FYY$p;PW zSa|W^lw<$YeE0p*2DE%R_KUwac<7EVdj5_R4R6b57VqNv#N;&GK2d85j>GRNSKhX_ zb$wrrPa8qI=T{zfMBIdz*aGvjw+Eg}{jR(C?l&mTbeK2R- z&-I=keUoC0?QqI??ZVTB_+L6CwhkN6>$~({nCLOmJ{Rh_UHyu~;IDj8hehf}a2xmO z=Lvhlb>JU7cjsbpWBFqjcDyP6v|OSN&WV-(PU-1>_8;u;-vb2y+t&bodH($Qv&Zqj zty})sHL`WJPq^80AK8VB!aiEQimluqTOnSDOru{LpRE$_V|>{0Ru9&f?%C^AeTK8x%+q%z*#$0e7>l<3C4YI1L7Q@--Uhjnu&hDI0fg#!(UcQSJao@j^*sW=+u^skU-kezA9lbVgL~#4sCXo7${OV@ z*E>$Kua4hYZpSob`kA{6*@qJO!Wt8M>*vZo-Q0}r;|urG-k8tvh}LoVLGSmTijU-X z+;mvS^O8l$le{g5equeY*ZS%#5Pd_p=;&bmu3;-L%1iZ|yv7Lj0<4f*FNp=?h&gnv z_PAfh0j?__6?TI;j6;p97{K#_Q)4i4^W0eOu^qM|u-*GT*#Y$7We^&R-?B|K-yvKl{W#@oy)0_x630 zo7fhvJC2W(JKLlB>N~D3xCnZg*bStaVqu^x@OGdw23l zS;ciPzfpMxcT4VYxpaKI-naF>8TVSO-#@edgp0l}IN%=Oj}#aSoT#lZu`j>5FZryXeV?D>5!-6z-U_PTfaxFw4oup04|HXI7x5_CqWQXlQXXvlv zFJzbCLF+;@J`{xLDw+}G`O4Np{d zoy)}?##PEW#Xp`gY|Qh({kNYuIO5>n>+Jxs$lu2Eh{*qgu>PM0w_NoZ%`eWuKPCHO zb1}+m7oMaw1kc?MPS-X4R9>e(9_Rh3F4tQ1-8geQ?i02p%k*zj@LzSB|L&U(==`zQ zEtwvE;0N^lIJyYHsA3wzsvrA>ZTueR^SEkW-zZ{)1ji<@~R)PH;A` zu{P%-CsE(|2IDLGRSa49;od$ssE>1vt9T%=2|knO%A-wvgylL@guSUY`h)uuI^SPh zFFv(7oD&yctJFUGgPU;l>H3@W4Di?W9>5jt`o@)4K6uK?__qYFyx<@Fo44TlbG8AIx8-9G z{r$n+`W>8@qrb0t;q}y-iti_i^JO2%YfM|#n6wY%wj*AD%)!0bQ{{ffk^OBOD69V* zeJ3YtZP$C&NqcFWY!3UE94(8If#wZ)Xl}RZ-q|ngLc?QoHsnc6fA9P+^tL-?^~L>u zTlZkCF|Pau*KIl4ga5`3HUfJfjPzsSG~;^S7M}x;J)S)WzKeP2&v~}+O8#h`0kRH! zu6*pgA)h~vM{rF9|8=GB)w92{CoTWmXMn%*jcsCB=t>w_kLNY1YkoO!9 zdC|{y*Rqlhh_#fgH*|npout=JN4!U^>KRiuJu#xTstX+l{=g~tu`Lk(4$Cumi`fO& z?W;Z`@je_-9m(9&b&utdIxtMX$~`v5m+y{qx9SvItX+IsJR6i9c z;8VraCms2b!GpK`lg8^lO6GW%=i1Cm?pvHD=bmX3lye>SA?K()($=jz%Gd?!Mq`Dr z87B@-{qo1smiDpB4Obq}&od+s^n{!u@9co@8S*Q6x4G??#?77cY4g9eH~i+1_m}*) zpD*8_d)o#WCt-)+cldJ^O3#i)!M4(}MYrdk^%|=Vi}7YW7j(fcN zbmLXVE`*&*KFYqto@*XmdryozaqvZbheFFo$`9Qo*||^3#4^+PlxOIRdEsKN(S1Tc zZufnWId!D3=rzUv+R4HClU%Mif1g;N#`mw?7H4}*FbBNta6&ojn7jN6Wn)g*Gl#)q z@;9CbfX__s10DmL{kWWi_*QLo4qmqCj)@$?m%^`WuDOQK{rs#_`|OX{A8tOK@xP|` zJ=$jgeg=5xp@;4@%Kchz#Q@8eJT}B}+rZj8d2tz8NuK53(Qo;A+f~W7u5DxCF*vba zdMvd1bnKArF-P_*yI?&B-gstx>kYFy92Kt)`s{D$vsuzta+o}DiXZH`9o*6UT6d4< z=))NEr{(~|P1$2rM^3`0(tq8eQxywb<}<9>q3i z#=gB*>;<-?k>Q9Ph;@X#`U`~8mVy*|uq>4^4--x@uZZijG1-_UXC(etbC@^7B0Gxtm^U8ncrwVaPK zxJ_~vQd+m<9KUyXvgv2ZA;h=#{<{^_L_2w7zLiEJ?-uvwjk=G{o*tKBKC~!%l#j! zv;W$~XUF)|wPTe|@58(ed_|pYfMds*9FG{F$Bz9v`0ad5VK=Z`eyVUjihJ7xabuJ( zqt|c@u8EO+=jYS;ncpGOHy17c+h+i8)Atd7UF(~;2ijIFyxKhPZDGXkAZvNPVkJ*Hlt~G9d@JNgZINaJs0&c z?zqD~G-F+tpa1FDmu*kZQ*)(UDu%EesgsVv#`d2?2!dg9>vQ2Wo@L9%f`nkY8evd?)8?i3) z!43EhPl`vCS3MjP7vHAzy*~Sw34s5j&j5%+enHlH(9 zTyceM0N>$%-Q3}7W374-n-VO`vn(+|MUwx z5o?L{WE=9(x?HDxiGGut_bhV1e?*2#tmXbY^!pvmpZ9PX8I>)S97}eV4|7t-{<#Nb z%bT)d1-)1Fm_t6;?Ys85|KGoRuyWxA?(g^enCFW5!APV3HdgF>2e=`XZOAc!IgKy8 zCC>|&b^f;vU`*NtV-L=E^D;NM*RwF^`mUiRcXixvxzlnAj=?phaj*Ns&F{ru@xT2n z(5}ZHfBYwk>yz^(;^Rp}cF(#=rk^H@E#X9B$gSH(f==1s)4$e5?e+gIdY^``iKKrYGo_LM^;hz$t#&U5X>)<=} z!s~kDKCj)k{AT9Hc=Rp4mk;jvXZsQTH!S6SLs#bOg;xx&zIb1~2kt)w=d@OHk39EL zIr}*1AKvTV%i;Toy*{s#dEsZjE|;5ou2-4FddR~j=sM#C_IZQ8+pxH38z4Jyh`!@w z!_xCTF6_$4K1+7Uz-8pCwUBIIRiCyNJ@GvbryTRq&hM9vA;09DPs#V6r z&cT80zUK=J4Dns}Ggf#l#`XtYf}OGpfxogJ_UW24uu2=_3Zt1jW{q-t7FZ*>EnT{J zD0XgbK$YQ`2)?V*=)_02I{mmm^5-@J8->@AJh>ixN*t>WewZubkIP_Pb>@_E@Ls&aUbx)yEz3R|Fvr4^ z{cK?4$~m?Js}@~4xcvOT*LmePhxp&wg7n9}&hill{r2E;eecOP?>eIAv*r$-a*x`Z z>PGHg_QCea_TWVE@;~?Y&U^5>%DN?r$GvanjCsv(@41(Q;{(eNd4!Fz9P|9a?_`{O z)Smr4&=L1he&iQ^U-!9O-wn(&3F$U%dgwTHZ5$*f><H5r{rXKrOXMoYq z0{mP3Cdn<$dc7yDdqAR|eC6Vsh4qqkFAK{~a#_2Oy55e4Jw+Gk#gMCi zz-(|i_}ufj@C8BKYZ+Uof-b?(K4Zsd;SUOua;A-1}wacy?%)R;$ z^P#xWq*XVnQ7T>GiIL>vj&MJp3$TfMdX}jpFb@Kb@2Ppow%}h>G zzGH65D)!m!Ut_Ry%-h>Q-OqNi zB{u$BrS^5dr~5tsV-0{R$DIM*GH1@5!zQi2t6!?u0c~$32m5`_{K!Wfmz*5)C%;24 z$S8SQ{Zfvd9YMd0SG|3>+tHbHOr6IIKQ&M4kn8dZ=$*28oc+N2#uZl%uDx`>&bN&6 z=kxpQ=JNpWF*onwY2r3GQ@9QOxQ#wqo6FAO1Ga;)O3KZRMQ}Q~V*X)IqK^75xqtSh zycqn?9&BC}c3{)0YX|qtKW=dHF@HR4$Hwrdc;tPGOJCM|3;EpP-8Ua7eMt|@PrQ-q zsJv_f`GV^{r_VCRnZI(*8fzAxuW`BecK_7nzCBN83v?gf4|$w8;Pu3_^jZe5&D6Y= zv)=vm`XArX%4aHhR$%?3Q_okr4jhE816PG3&V?P1!BFBPaF{s4KByj_6MmfQ;IW?% zsIB~Z*#X-Mu&ZROJJ&NWVqeX(eA&XzKNe3~pGg^RiFa>N`hLd#We?gjK)nw5`cHoH zlY6q(cJ1T#vu4r7L(fkgGc}yA>(E8RfA^8Rr5m5u(GPYxZFbLfbP{?B-BvkbyWn`* z1L3gs56opQf=?x$ALCE2xp)}Ug}cfR_{_Tp7oGkW{%s$8Sj6nX>ELql7+lgGe685t z+*f_&#rw#EZ!u2aC+hj(M(|_(Vtv1$QrstVII1(uhNb$h5j`Kk29O@R@2tW3r~S44 zbFH0xTnbM0&+iWS^nV;2f8>V;S6%SegZuezB5|ztQR^f>?aO~;JJZg8?kRiey-a7t z8k_xh_CR~Gyvda!{#X1iPTq0j=LcsT|1o{GSI-ka^^R_>L+g`Gn19_Y$35#7nd%e>4<8{B{edS}%06+Z0Km2R@tj}Wh(b|Br36-y0a=UGz?V)9a zoJiQ%j0KQi$<+D@TNySv_)OV)N_mF82zwA5X1?UP=inN>gndAl)|0Uj{=#R!ldFQzQ~Ti`}D`Q2j+cnKkNf#-CwV1Zq$2-YzOq7Vtxnw z=4%ca@Ou0tf9e<@{dkl!-p>6WvhQ!`yFLEaYgjK^!+n-MaID^Q@UGyf-h*VhDF4Q- zJoisInBTpv>+GfN--&t#arj{F75fa%KIxD3`{}R z7^2f5GMB#LGkVWF_!-y69es?Az)X!XzFO?q4upM(*CjO0`0M#@dm`*|4}4k8OMF{r z*N6l3`C)OPcrhv0d*~L6TYTTgGa>db8!+w+@U!#g%{y_@KKlBl;x@KD?4fve-U=l|8Vuj7kP>tqL7o<}*j z)tAEttix9La|IU-Hm{2x67@Vfx7-*?}AKi2a+ z&H`MoSh2$UBqyy}JjYxQ?jn~yMIAEi>u5*L)CETy`B$Cu&{ybhv`pMqdIula@!{)X z%tZTo^>JE!mVC@r!t#|m5B%fcVEMm}C*%0RIo0*#ac1rE7TYzpt!vBG-sDIB(+9-4 zI1BXrhxft5zAm|st;iJT1)dQQ*IXVkfzJom4HoIQO6KS@hev+l_hRj}4(?rL@}_+C zMW2Yd#qT4^HhQhGf8O&${K4n&zH;~<_BN)Svn`Lz-`NAt8McVuQ|CPo8})t4o6w1L zgD%$-Lty`%O1I=u#s)e5>hvxgb=(ik^|pX(Fc9^XyWQB~K4BA#Rs5Oi&2M3G7}NN# zzAx*81I3k5ezctV>}NmwM{r9Cj&a`pemwWrZHNBq48Z3B>N`PR`XfK`BfqM6WnsJi zwp{#QvGg0AoPzHe8`+iy&m%^QI4aKkA@5GVi&8(zBc^WkojSA4kAC6Hr33Vd{>u-L zw`I5IoM5WrRm$?88&}LRXI4ITvA%Qq6Z)OcclVes?Cwb3+Jv@TV_QzNWwn3l!1v3S z2dCFBksYvaC(fsQO62gp`V8@3?QX+<(3kHW(CY?zKT$m=*tF_8{SMXa!My7Z9h`IW zUV~Zs4m6&v!+Y|!{Pp>Vn*a6O&$c1=@Ogcfdg;UGIX0Mg^*;>Dsois1-p2mnJ@0W? zyOg!+J&?;U_a53amGxyiAb&62b^1=u_s<+!yr^%(_0G?zZoTkDT9-qM?AMWr6S7R+A~VhaT+1fZ^ST^Q4tF*#Bj&{ST%+gOmzpQ1e)&)Io!;;2_)$Em z96x5>G=8l0ZJ*D2^0r)kYApE;SMINMCI6i#r&Tt$vo87s&o{_6a8Bph0mo=+HKii){JpC(w?$7nj+J6tN;~>3GI@@bG?yx^_%uxF!r!U`U zz&dzOQ0<}i)aLD;+cK{uo^ite^k*K3zs!5BZ^P2_G%tF^r|{|Ha~csyr&Kvby!NDuW{k~>pGt6uV2h_H+@fs_{4hzHm|O; z^fh|V>BWAoHe1iwk2~xm`n;QBgNE0Xi{HVM#-F+^V&)p-e4pL?9eO7n{oe)`oc=e1 zd-N=A(<)-#@bw!;_-u|B&nu@7wxRO2oa4(bRK8*HSL5}A<@&sVc~|y8x$HsNhmFh5 z@^^fDt@!TTFZsQ<9j|+y=6f%D=oGz2`F-2nKhN_{)bEAOx$rYFUhiYr?DL7_4DS`I z5Z7#w4%VRyaliBV)?wHL*Xeb|vJbuf6EEu9*MTwWBiUAns~wi$Eipj)slp%Q1DD0^ zx{khKCzuCZRp)9Nh9@Tp2Ej;jdF7_Wypp=Vy|U89f8=J>$GL{1MBeY-8C}%UJxa*JJy*EIDkO5cZZaH7AMOgCua_;*>urh+;LXDO&erSFM+|Pb;=sXG7w$W_ zy?q8(U$n3N>^<|2^Ld=#3@WZgew}!E zzA&Hud??5N7uO>ei1U7xv(5Z(>cpq859H&<%hoME!*S2X<(F!2ytm-0T*t|S6ZF}l z*6&mP@~n?YCYMTv#3|Uy^K=%tU~t9kPY-6F{@3~)q`k%Q&knwJ#r}i&`W(}Pcj-Ns z3pIDg^=rlZkSCvc+Ms6$<}&iHRNQy2`*gZV{plJmNB_`g7w@@V7;v7pAo(A-Nh}#- zfhEoX9p~-KxeiVnW7Mg?eHrg_aT{1J8$kQ<1F+w6VYIw*<;npZf=kIMxYd5oAKZH; z{IxLv@dcuSf3;JVLSDm#w1O{N|l zM|+&t&?j}FS90T^;za4Ze17x`oP@vcxnI}h_-C+$ykEZa^KiEOHZ})8?KK?|kFq%4l&hWGW3T}^u5!0Eb9oJ(KgPry?C+$(G5_?t;rsWA=YqUX=gYG` zEPHjOWU6)Y+`%>jzVfV5-*31)_sVx2YOlOcY5fxYCQ9DZF@xpV%hj=$?q|-ETfS;n z-s>Rv9(rUfx{P&FSGu$QjScV;3Cx5q0C$CtDw7A+jITBhpx$lK$NU#g!74Fq*@*CG z`pz(6ew6>C*Kp@oe&tu*373>czvK6r;`_@G@Eovy9}GLidz60uo_p>&cXVIluGcQs zc@ABq3`>?FFLaV}ZEP)E_cK}RF2|}L_S(8jorc{g-{0rx-}c?}P!Em@FL6zs{Yv_n zz*u0-WpHP|Q#=g52j}4=+>aDk1^+DF=mf^$7rX^ZxrLw%p(V4nvc zCviLKt53~ScA)C(dQzUeOinMo?^u1-_`TipyyE^zM}5q`zt+#5x;;1+7~X&PnkBMG z&WC)5Wow<4yi*6%g^b}f{qs8e1s2eM;DGj?=hTygE9Ko8fO5Z-?ug{P=3l=5nHc+*9cs@2*r|#Eep%mHa?7~= zwRM!&EIK!Az);K|GP8cLpVEtEO};p}sxta08y&G6{KsxLJf~kJ>$B%}yhaW#A_a!B z-8LX_6}}0d9m#lu3mVtjfUp(8-JZ{IjB#))+5+Rn#mZ@`_Ns$F^2K_c-t*!2J&)$9 z+&-Q5;%>$MQAdp71Q)w9^8^=Te%-I`;d!`!&DY$Edn%oF+#hwmzhVGBBkAtRs1Jv$%a`y#S zj7hAd4G2t)v=#1zL;8me2zwB=17DNZYWHha!g%p}6#raTf-~X}zwcG=^>1JEd#1(y ztqmYP!A|iy1$OI=#~gFapVz+HeAM2pUUp~30AY)R>&0>L+j1sHKgczCZ`n)!L%NDN zz>Mcf{dT&pF@*(P+g=ooz*mQ##GLwu-w#X$9)&^G7akqAg7x%a!F_NTcn!?<_#d?m z_(AF%*NcbdxaQla6nyWw+Vheza8zx|&F9J)r}9S46)CQni#aPrpXe8J#J$5dP>#7{ zz1-{6!Q3l9J%jt$fQ!%kgnYk#(@=e4O|{>UL(EZicK#AInH)z8xk8W-opPUPVqg=nfUZPsw*2n---jk zcyVA9|791*;SAhK4n5Oe^V71%{*A@}yk5ckfqr?>qD6PK`x{qAE^8O+d6(sl%#bfU zCq-<9oPA9nCEH!=S)bKUJ(cbDSs?p)PWpR%sGX&;CQ;7!kIU7Pd8{g(f2 z->?boxUdH^$A?XbwNmCgLcZ|X_n7|&eEMCT9pHP#*64dTHt-yewX-hu->_7B$~8{j zKYJ}%kyp8qciMnxliYP}nOSb33%8|SgO`myQ`enNty?ga{7<}uKL=ldjkp#*)DN5) zWAL7K=TR52X@gtlga0Ib7tuJ}R~`7Ltl#&zL!9|H$sIWKZ1DYBrvKXK02Kr5eDcXB z|7GoSTlwl`w-52#KH0nt&X%mp7TX4-{KLK@`_zxep}(@v!S(VHw(-JP+@tp)fi3j~ zf6{O88*EVyjJAHrekr+YyU^h_FdV)!v6*-czGwT=!Z7^GnB80nt`={qEw~=`ruFIN z<9l1s^E+%rai4nb755AN)7H0F9dq{cMgLX$&aI;ket&TO)A8JIe{!X7Ecl9F%fLp# z`>Zu5z8MxP0|mp!gtz1peDweQM#PjDAImH+R!+;baww>s!;F*=TZ_V+oK zGX29R1onvcY!ASk+cL+$CuRbVU@|!8Yxf6R^bb6Solx0r#ZNdB{=V!)@QJ#_I6O38 z7|S^7BSjzPsFa*md&~FHa@*f!`Coi+rKJxZH+b-lS^6E0!v-t$`qYM{n%{{&l;Qnw z;vRfY9p52*fVfVA^Oaw$_b&0Cpy9E#?l^0(7O&Ow*ZyPQWh=^FB1gwdjXZl_A2RKA z7uTT=bTZ8Q(pltT1B{K-dEg}K+t(1P9QFcC1?PzYBHknhz&_B1p9x#x+}KUL>a$7U zzA)dG3-{!Dum8PpMNAs^-TrXv+3+_m2H54?bI<+6w)S5#S)((O{k!GLdq-#5Uvt&% z!mit+&xW+`!(0!}$C$7Ioj%dO{Xp~qA2Bv~?6&UtQqT4NEIRXC>ev8(q}0dz!2{#Z zd{3^cY)p6j?)eh@3wsc}naTO$lR1tJNnUwu@twIwA3m8XR%Z9_DRuSFyV| z%xCuaP7i*om%bZE#xg(eZQyr>=-)ryGzt!ge9;*G0=B-TDEWSvx4}03nx0m~HUFxafzI7+N5IPK9qFdX5nS+q`*0K_+92IzW4oNT=->|i>H+{ zA7$lmxDwwr@P&QfD_{Mt^xfobIs7<%=3VpIZd|R8@8{EATH->bJ zj?)f={by|Aqj1#vg{-R>zia@$XBdmblZiX~CB1&N`0F#1}+E|*<)*{93?Z7W~1>QVa}%Ug0Bm2*9VvF@_(1t3FT8n)}1ti1~^q>Q`LP zynJ?<^8mcO=QT)9*5U|H#Ir`u$PO#aV&R?2oe-2mQl)1`p^x z`@A=K!;^ADzNb$dyx9Udqp=&=ENZVjNrw(n$ zt>cIR0t0~ud_Oh=tW>ODe!;#&ScvP$d%Oi+dK~t4Aox#QL9B4f04#3{>(vG~M4o56 z?{E1(`q|)@yyi8pd8_Q)!rJq;@-<7Z54{ivB*%;A_T$u{pX7Y_g<*YFUFY{3-bY*b z`tSu&Hdnz>@z!P80%0-E@X6=SE`)#T>_Wr!ZVvme;&5?+JowOZr2J>pgtq{nn!aOapY-?P--kKe`tdB6Z&%J|_3yZ0|G}aAy|0_&K)3HUAU{Z- zYrISE@87)UTJ7WXA-goRc5Gl5%aja;?ErgW3}*$(eookwkvlOJ_F*IEi;_L{?$_v~ zVm9j4Z|E#wpPxF%CJe;@V28HgcAE!&WGieh0&~~`xK3M#$HZyH0Be_hZHKrIC*THg zveL8N_qR5nVt{&2;I5Zne)(s{@qW#kHI@~5lvhZ1dLPM>-nO=Opvvu;-#Sa19r{V# zxDHOEbA6}p64g_N+l2$l!c4^c)PtXBH^vfkQI2~0fX9mWgYU&}wec)1vEAmBeQlqo z&%~I&s^gk5BR;=!>| zzfPZ)+dR`dP`{@&YA>QsaQ{xYug@oKT&B-G)_U1%mC4h-4ej0hRX^LPuy5>{HTU}u zS&YiF?5X2~)JNzjxNbXP-uL>kp35enYsPV%y4@fw*sr@S_}_CHn=q0_Y=Hew=+W!+ZUT-`mUUIY8L}?AnW8^r9F2Ydz<@y?92Zfcwi1WZ#=XV-g0QWbU z$M_=r8`v%$2Oomd!TI8FaXQ8X4|=}9h2dmgnKQkg-?WU+70G@q>+3dBRxEz$ng83I zALajf`rPrFC07g{U;VYg<7@Q&3yZJtXOYVWY%9ke9P+u}7~G}b9({bx^@GjJ&)5A= z)BZ`}+r1ua5!|Ov<=RWsNe-*?_ih`1|62XP0w~uf_k)MpxN7h1->Q zHZzO?V+mV8pUyw%+I7^YuNVJqFEZ{l-`ywUQT3zm8ezBfbA|1~d*yHePQVQ%I8yKP zC4PRko%^-qp}(>LJjWmg;Qb7*Kj44^KBWDBt7Ou0zvNQ+`lXkJ4QOL4>i|0-zLz~7 zvD3B#sn4?KW%D~6j6ClPzffEkKI}{IG@K9 zd#>~`JpJHywFTGW8omYBi-Ya;5eM!OK0SRqyqgCI5O%sf`P(Z`Ju#S#iZ+&UTR-f7#^2{ z`L%1;ervz|_WO`H!S{PSQ$OFYr9GuTY(T{T^`4-gz4zXGPeCq{fm4-d$ZFL>$K+vu z!^U=TfOKSipcALi8S%Yi{L*2zN1gf_3-|!=!ZrA+obqt0=TKFTf9ZXG*aF)FaOPN` zco8-rxPZL~j0f(63!VQMv z-qOu0^&KV4&T!0NzpXNLw9%&>!&!lA@-y}QTfDaqzi;k$YlyYPy~|#)rq)*Jn&*SH zM{GcoDju+$lJk~t%EIqSK(^Kk_Oj$3<*G+NmVZ7s4Bdha^c*_w{J#3uS-}3FY=H5= zvtRH4j>LQXNOB(RZ3I_N856iJ<5KlsBh1dgah_|d9^8NZ>t8=b-1u3gXY=cQ+cOSr z3{am9dc`~6`Obf@*FaWG;y?9e6V@%ixs!LC)iTz?UZ*ac*LCq99lAZ9<%dqe0`-N9 zEE`)rPKxWs6m9l5J=ZtcZ*g6IKHG#@`?AKLl`q6bgg?g)q%ANf;78buE-&7j1Hq?? z?JM5zVgqr@WpR@Hf!n?)E(dojKkl#(>pMvF{UMwg8r#7cVw3F0W_`B^eGdA}e;l#_ zUpnah8Sism(D#z)yzeoLV;*cz*#hDbY*g;4UtjWlbg$l9^mpHsfnx^Cw~w9rI(VEh z0XmT!TYd-6qrSHV>cjKfvJ1uqzFoS{GI+p0bUasm!~lV@A)L`SaRF&ydL;>C6`uaZMoVvEWIc>Tef$m??(^j zxb=bE4jq;rgVQbd!BVCU8%^&29ruh!O;b3V_F4T$7^xu4oUdp3VlJ|PR_nf9=9pAqt$;4*e1 z`&l27vvc}HeQ=+$bZCEHdGg%44qwpO4E%s~*gq4fGk+Tc5N8@+^iR%5UNL~`mtNfY z{T9QNN7JR3Ui!Bp%PvaKrDuNI;y=$b+H=5<|LBkY=r8Df!8eb}X596%_%2@0 z;5pa__xMTi0v<&C4z|;W)86vFIMs2j=T>pwbJ#|}<?)!>)T)v!~&+Dw!>(OvN z{I|Z#4?E!N)BN6E`VcSBztWP2&J*wToKLpHYh*oMU)mqt&oPD9M;&{sKG+J%u|LLh z-YH)n@`<{NdxoAJbT$GxMe=I|sW-<{UAc6E{U$z0J5V}Ko|nD%dCgqLMqmTrH+jSW z;WNNO`4oJ9T-WPoqOK0%Bu#6w0Os>zv-K_Wd{-XH6=X=}t_&*n(`EAbz z@GP*-0k8ecXFl`(b?eq`Ejeu~U$^@HwC%;`HjlE;_{4M_F4ErXx{dX$bA0}&-_JV7 zm71q)1bNSWIGn!UxP;f?tLil@#?wC%+=c@^56mUFlYD>^oxi7EJnH>yaTYE`3O}CQ zGv~6bv71+%=lAsXT!z!{8eDwFUzm^PKlfXn^D4jitiNy{Vucv@uEF64?4ds5f6Z@u zQTJ@tp*02X)yAIbpX(1>k=NWaQycFf3y-tB=!2X>epO$2wZ(WijxMAd>pM8lb!P)c z^-G)T@a@)dazE;@kI@!b5vGg*Y(SK;2RVlRvTCc^Wwm3m7eQy|EMh( zcMkYzeb>=($Vam2WLRbLHmzTNlWlV8!MSx5wmV|BC|ggC3&4Ytb(VQICfgDZ!TazZ zy$t~qB=A+3Q*Q1Hr`?&Cx{3v|pK%=cHdaTl9zGR*q-`+Ql9%)qw>HBsanzhj&IV^x z=lSR#&-B8dpXKiu9_2qio%aLsU4GaA=H)$Vm!9>%JAYq(pZt=uKdIOFczr+DqyE01 z^WcB@`YfCCl*4w#-WWrQJ%-)Nz1sFs?o-*xu!m_sC7X`-Wg9wOH2UatR60eE$!Y1q zoMxPD0=7L;-~!(s+&6}77kd8pab@towE^pd$x-|k-$(Pi?z-zZk>aP7o)2;VIR1~G z177;Jx4rG(>GRJETUm|rzm@MgeYY;LnDwM*elDlJ9KY9Dz-PdS1quh~c$4%`3R_=T zDV&h^xZ!LGzc(#YZY&9_&V%b=0}7w?3oLhjuETb6LYUtyevmr+}M`jQBea@E8We*Pd+p;vCWBR2b-2Rb_SayUi<#4x2;P1)UK&)L>m{>{>jaM=V2o{dr-Dk zI_T3&FZb!EgCIU*i5rHlRHRyx~h<`qG~3*RS8&%4<@2{o3Wy z$r;jDZ;QjW_kO?AZR#5Rq4&ax;{kM@DR`{w;y&YqAM@WhObi-tX%jkb!}-Et*n;w7 zfm`9ajq~jb3+v=c_Tyy}l2_(e?|Z|?(?(tLQC#QwJ=dG5lUzuT>sdd)=~=OSalg-# z?>_7IF@N>{n#S^)K=|RDySXQJhIMR)_l@0(a@qvq1nsT21-V!D>xA4?b_|dFvMjkG zKide^Q4YHry!W+bpLW3G)hG27I%dAmFS^;ZEI1Da$}Y?}``L%kPApIo_fSX8kJ$pxxs_|LEL$2M6>4D@rXE0wd}73tOEHi28NH;0#<& z@c&?2B>0y~^**0^pC4!9=i4899z6%}dce=?`;V?drm~loEqPmR<-LCS+;}Ey{p536 zm5bkw@6m6oXZKC+3m0)NXmjutI0-(3rNCC;EPPJ+Am_s7X3hwSRb$uq4P4Wwd>I@F z-xs)c%$%GtN8ks_hx>=@`yR&(jM=QQ#r23OVqK0gw5H&H*@ECX{cF9p16nU@_qubBtj{q5*OFIf z1JX8jveI>H1EOzo9@!@U!wwYRhxJr;+%cVPL}4NH-JJ=`H`ph@?KmHXO~9{UA5@>` z0DR5HRdctC&%!F02HV2?^XYm2_H0_&0L}sRouRMXefQn}y_a_rf+rU-GaKtc!Blt>U}uiuc3-B+Cn1 znDzi!tE$I)`Qpee8@Hd?t?emgdR`yE5BnK01i7`Chrr^ zeS7$M;~R|Aj!!JSJDyg3!=2zYeS=fQFZ0hffVOI%$$4Ujp1(W`xWv514m`g4`oZD{ z&mG)%`-y}5?>NzA-qY(g+SPY-eApW1#OBo8$$!@2`nGMv=ev*kdtK~ZbJYIJ1|;9R z^ASG2WGNXB)w`|NMHeU3S-|mH+5?~0lJjN5vG>+_;y@h7$HVjT`C%urPMC@qQtuHE z)(VqtK8nw5djc$r34D+L^Zm>}$sk|@MxO<|dH($QUufk!S{|178h!tiUK8kSb?Uq9 zIDA)I&wXLTb?_XV!13@6{d1z6cbxk}mi;r8va>1oPA4&@ z*CqNa9g-IY(0TAbVgR){2Ed+FePJcXmA}xl{`KqEj^+P2d@fk9;9z0<%}UcV|0MRu zXB^vS0Xz@QT8)yk56jIzC@I%=16`PX1R6XRg=tJ98I37fwRw^eG!q zZN{GZIVN=u_M%_-s67AMw-@&dw{0F+5ALgMzJU2CSFGLfkM{Jrz#ZGG4V*Eco+Ib&Zd4shIs4oe@z4_R(`A9f>oY+Fyc*Lh$f{CwfWz5(pm9?(Wv zzsD$yO~hXltI|jB@w)~LgJmT!UC;ZbaleEy@>l18iUT+&z2XBO_`utBepBog}upj;| z{C{EI{xbQ{jq!EzZAJQ6c#>uEvJa21;Q74nk#*s_TfW2TNSoEyV;Q45&o`3OC)y$p zyTCni9huktJ!h`1))kzG_py%0S5S`B*)aCl$w=icE=oC?_t*#8#Bs|6JBr;!hLKu5 zxJ+OAbo}NTI~`*~=T%oe9zP-d3N!AD->BCL;XmUQ7lExE#UYplyI@$U&ir6|%6$o8 z!e7~ddS3u?d)<*o9{C|X3)qD0l~2?`wQX3pSUS_^RAj$5qdW0AFpxUt+I8q3UIz~7 z6W7s>j{r;51>OQz<%hsn#HZ=|)Bp6?1ap}j<0hV|OPkQ~A$_22h1!D8a1J|?w!nM} zUiJQ#Hv09PxBpIFV;|rsxy!1LJnDm^Q7+zZUY>2TDKS3gEX_Lvet|)22jQ+Q+bwz5TSTu@fan<>mK>brQO<&N}X+_tIbKGWguveqDosjPDBz zbxl05QL^2zez9>^7;CXe-ALMW^wCHE0oYXn!#vlTKJSwdcHnQ+2Jp;eXMHd7|Jb;3 z#dCjjt>oVGD}mWfxj54wd1%xnCUSy63*j*nq6FU4ZYl z3vf4WMUBC3s9f;^{e#P8Z7 zIX*~Rnzo>my<-CSEj^8tE0#0IvE$3J2jC!VHtjwCt6Y8qOz5*o!qSf74vd0TFsn3u z-X|e^2Y=&iz^+$cef1}HR1fYay=~_GE9f}mfbiwv6XFbreQ*4J`U~?LJb7&T{f^s# zwcZvaE{&<4`?QWK1t>>~YwQ6$Ax<{-u@B&0913nndtHYuz=i||+n60LMobQm z&(`?hf8}tw%8@e%9M8Gjo^>7{WzShLM8yT&81-=s!n$Lv!?p`9$6nZDw_nD)f7pbw z73OulCLq~aj+P;1I@4^q9 zBHZEslh>5x7s989@2@yA>PGnAvRGeN`zF1s$)SMf>ZVWVpA zX&)Tpz++@XUC0QTRqAXc^2=mds_b|`T#opkv5n158*Lk)vi-koc-Z>TdDwJ(ed=HB zfd$S15d#=AI!6}P3TKtK*sDHZ6O1Z>)r$GSaN$|S>E8}Ypkjb}9_ZHuH1;j`-FM%Y z$H~9+FeBfvB0j$^8xS#n#DAUs(-zo2q`xoRnB#@1!WrlM@1Y8oY)dJI$Cj z#%Z(v3f_kwR5|^A^eOJcGdLA?A>!$%gHsi^J0Iici?7br9z2EXVLM`6URQhi_i7uO z!!bhHfIe^M>upr|`dEL(FtxupKU5rmOk&T$@30492gLO}J9RMt{BLD%omAbhokn-T zb#k~~ww*kAuKMDBcMe!Sd$2+8=^yne#r?upi$O36HkGE&`gf4vJJ4;M4d6Y(KlA9L zkDfECkJ3x!t$sGFUoF3(_p=%U=ss;fI8YoA`~^RehOZm{Lf^!kZ39>|ucHsR4XlRW z;yl~=H1`Ad;zh>2`2Bir9x-z89qt#mdR`^(8=j7Gw5~lidCfWC@s&+#ueVX&+>H;u z*W8uY`r=;NlkJt_f_xTPF+k!cAEbxIPM5tX zeTL0Q-KLJi1_b}Rcu*MGxM9tZ-!D8(${%~|v2(zr@XULAr?2zv0I{D$mvJ`WC9i+| z>wjVC(xq2T(#iPx_4=I}y{|R>ec1wH|8aZ|o`ae6?dCl^kC-!_`QiK1|G@7`>QmhB z>w27<10&oI&IkWH&f5+Iw;o@au|B!D+4C^U@UZ87&-dW5`Y@*W?y`7IJm7J|?Oi|Y z24g1JoO+(%c47qPn{Xb8{bfwh#Wpi-K*`HCK=O3HLb6qI?4SA=X9GH$>^{YJ^cUsf zy!Qi+3x$nMo8nA49zSvJY}>$~u*m!R`Q45w_n!o@pG4PjHsED%ed}9)S>G8jzx1(b z(3jm`Kc4{; z6T$n4=exMk=fuKT_78~9hqv87lvubm^Ku*8lf7puPLLVQ)k)H3t@8HcA@rqQO zFB?$&H$UyC;dk%T-QM}|hzYi=)cb(V?Q_J{NM#G6K5fK^J-{Yx@wkWyursk9U3bq1 zi~F(G;6MBB_ZE8$S-3szX4!zq(|-DlkT#YWAmv+jwqk(bxH#XvPKZv?Q`qyzSM=r5 zb+v607RGUYTlfKUU{6ry{XJ8^|DAFA$&MOl173~|*6*jyL5HQYwp^TV>s(&L;`7QO|njrIK%hpN5s=$vyaxUG1=xJ~ZEZ|Xaq(+@w|^P_wwdHD6I7jxx0oX+ITzS={zxV4R?jso^N9xlKl*~DY;rruRLGe6vHyqnl+gH_>vfd-M zRv6e$ya-lW>?~Wh?5gzpFQ0P%$x`?!_aA2iUiyYNyy53~4pX|^;e5kN*#LdsEp0&f ziNHz3lPbHdu?2;-^hNepg~`G#muhx)CaYn*_UdL9_%z)zKl zH@jHV@#V1Ze_VE;<2`Xf8}k>I!EPqFg70^H-<+G_3tTa8nsYwf3$6wKi+`?%)9K%f z)75US!dKeu`|?`x~ z_htVq7qt;Xxr}@zFX97itnCIl^>xk)9oeU26C9UC9M^H*&v=RNWGkfWA>Hqw4S4Xu z2ag3~U`+|kO+U{gA$*_yS{qRB5#qJipSttTJ3qg9^X9>5Lg&<#?yFq=HtIa^_;P(N z&b$sxneX@ypDP1%jyKh(d%oY{GB9f222O|S!~cW*j@ROf{XYIbY+}govF`TnpS`fT(1 zhbHQOTlfG|U`q*%weRhjex64H`9A%PvjM#KYS$ZYyz#Ha>#fD{;(VJ|Tk+re-?&Pg zxK!LOpMsC+ zpGVjWwl8czZ|CC7V_S#}El2u=ydvK3IUjS-mNK?&4ILEUJKd!2?ECHaWxu7<(RhEX z{zv?S`fLSDU`lE9eSK5zf1ilo+!h;9cA(+_e`e^y3orcd(%+`y_mj%k+?xId?uRW1 z9}~W&&X*O3Mq6TcoZq)yfal_axldl_{+{plmFXjU8&E!!Tq(KV$LZ9G_xAPTWb(7+ zq}w#svA%7@xtdE!Ho&>cU7PI!eY1br3v9qD)}S$3gKb5wCHPGpiT!A7tuyx%^|XaO zsQp(A6t8I@BibYNF+j@Jb|QQ|vPYKvb3dNbhOUU$ct4Q!f-c9zgWIc#M%Uv$1Tv3fsy3;6z~E-0%2PHX(QfuX;{ZxsTI_{WiRgw$6VyJTJbF^4~t6 ze%Uw12H(4RumN6U*o}$+I6Fxh@ecO!K&(MCwai0<3ddB|6{o&U~(iT|G za6e_6v4C}x@ql9h%AE68UNP7#9gfxQcI*L|0XtwQu>_{@FXi*6+?Oz(VSnfZ{j{GM zc+E#X@{zZ#Sh3=&CyRr}AAdZsv+jOj`F!CU&PNIirXK>29VW$h&Z5F<`7rYSY%3l( z{x9DTw!sl`{H@(KC(I8J+KRKKlpF%=UQE^*uUd`!~lI9z}|xE$idG8 z{X8_~mTmP~O3A&81ssz}zN;=%tS{Y+wcX?F`EVVW02^Q=u~P9qv1G;kDo+2NB@(~} zlpQD=KrHu~4}IuEzpD3cU-_M4g>jqLe_fcHEuR&<4<95P3%h~Q#^>7x1mEMli~p-s z@j%N9*#YqrTn(;=zi#9C;5A&A9axR8kG!sHEOvpumAjnwfEb{& z1C7mbov!WwU7zbrzwej;-*1}$*OlB>`;WYy0k$@vvzfL5A!pl4WKIkq-;WNgi|>&4 z=n^b^_`@H5E7(w~cz=4T{D)oenID@_ezLgl*uP?H@r*Nn!?)sH@H4qzzFK+5eeu`4$LGU; z#{`*+_jTRJ3^BH10OADO3EjKwh3$^7b4|Xl%ESZ7f5$Xc?)KN(I_!b=jcjZaGX@an zOO9a^`ZIuekIdJ}1~p5@y z>Q}$|LB)QLfsrxza9v`h!`3)FJ-&fwg!-*5`}*Dn^f7+=x#V~2|IK}Iq_}VHB>&6r zM;+V>KODY4IoWZOYq+f3oNn41ju+P>KR?HiD|P+y+@N@2XfA9=oh$6`i9_OC;MV}C z!`H)k$1bcnu3hK-2mkHM9S>jwA`Xf=Y+~>~>UF(RzMiz|;=$$(_YLV{l7320mACrU zOM8!@doZ8`7N+sOgzy~xYtI91Ebyw`cH8a$edLixjwTi;EVWoE{EX&ppBnf0hKKY! z8hZZ_F+iqzCKzYlzM;rpswF@NwReX95r{xq&T-cz5c^ZnwzpYJK(A|7vnyZHOY z*M}bue@~fMpz?_K&v#vCBa;8_Lw}{>dlwsM9kH%h6VD_@V}RZUXwR$IcWVQZ`!j4n z%18B^b-v#s*_NDJ*_U40y0%|iM#tzHoeS%)QX2P+53Ef8o|_UF7YqEDw)#f>HuG=j zd)sen@$wy)H$9@?*}7af4_{V(t$ps*#{k8L#&4$2jQE~%bnc9>x0+6BO?w{_kP{?}f8X#R1x{ z{l2dAxj@7~!S|30a#I|I3?)nS0M9FL%R8zgbbHiMN4;0N<~4nEKK1z$!t?NNQY`SY z7ryX?yXtqx{!;i_E>~f^k&jE@uKG;G;O6CnE$eO(Pi8A|1_0N?X92&4-*BX!`IXP~ zGe7y^Evv)72LF5uJ;;Um{h#aFnky5hQH{Fd*t)t~E#|AXf_rsF(w<=klx>P%oe zWd5gJa=$wNvtImsoCSja<x?fcb@%QeV|Kpif)Bil<@Duv&mS0}Hc=7S#_qSRsZL3_iplrnqylzxXz;ExZz;E)Nn&gK4 zV)#dMf2bZVrSBKddcU7OH{$TAt4X;$`@FxLDjzK6jk0`j*$d-|xm@3%XQe zsH0rAg}m-XnLUtlJuUaKUFJI6kN6+%$Fokgt(jw4Y~8wfY>b7>CMD!sGHuJ$>GydS z9Dn@ryGzSF-cp9R3%j?XQ(DHHow9H8s2thzPX z*Y8JYh=CQ>%#`v&MEf6?y=`BkFTA`-;#6Xqx_yESG~vYvDRoo(<)?4E?KVDF{JulnmV*zoG+!-f*7&*nlpcz>Y*3wgKFed*S~{*o0U!*RjTny{E04zN!6h_TIQNDgY&it<|cfDmvA>Zn$PkgW-m^|Wu-OAdpJ+x zy2{S8zv^u}G;ci<9I^%7y>)-=6E?v1No&+vx30U{Yky+vCfmG;d)!8RKRTwBX_dwM zd(Z_sK{ra1ct4HxcL@5^PK^Y%Wd~l#^W*woyeUt3NbxK#s$6u~= ziONcAE|k5vK&kS!thzO_C-hx&nbK8yz2aK&d#=*0djHKg#PfxNty|V~=a^atYp62$ zxYYKkJ+=Ah_|g8f>sjoLmtA(*p2$39U$5m6qqT89I-CAYCGg#pz_#oF&zg2Q`|Pvd zug{O4{;h9)Yh#P;??!oh{P=F%&Pxc>zvrw3CfR~IBk=k)&$ozu z-}qa<^;_G!y)pSIlgp*))CTYcKH z;`-%!E%a*E&K}qcds5c6@~JnAYfD?sZmV2eEgrX=9XD=!{kb=5U@fePwXsIlnrmk5 z>|q?=r?I^RGyQwsOQ384IsUJn8I0Nk;^VRjy#7s`fL(awul?Gu{q)gCAN`K&ufP5y z_uhN&C**ez)bF7mqwl*oQ?bL9;wbM=d{BITRB4&GzIreiY*hY)@+XvURQ+n&)Q{IF z9$*Y(nS;5QlezVN$33|h_td?2$~CYS-Urokz2f=u@8kH+o~M6P2}~u>OJJNmXl=qc zyTE(k_zpYx$L~_ZMcxY!FJG&~S)2G9zLIv%v`bT_uKJKOhOx}ST+GSb+=F}7`|if^ zyybcu(@*&>!A<|B5_pmlXl(-JiW5fdLTelFyOrSRcamy+n{$sgwQCt&r@Bu6rV^M+ z;AxV;DBnhHL(9#!-14_Aw|yp+$KCU3+TL{9sRX7Hc#0Aj$JZxQ_Y`kG9X^%7R02~8 zOeHXtz*GWL2}~t0mB3U2QwdBZFqObm0#gZ0B`}r1R02~8OeHXtz*GWL2}~t0mB3U2 zQwdBZFqObm0#gZ0B`}r1R02~8OeHXtz*GWL2}~t0mB3U2QwdBZFqObm0#gZ0CGb=v zpznWpCjI@u4{U2S-)5(KPwue~?LRoNeKpPc{;5eFqIu#HHdp&j<0qI??LRcO-HVSd zdyngotE0!*70!vV{2zCNX8N%*>Q>JLJI!3cL)6#b3udB8V&KbS`$O88nZ6A@~X!8gNpP1SH z_I8IemOrwA8OtBpz(X_VAA-O>GuwwYFtd4R12d#g|GoPSjM?4~{6Mq8G20v3;F#?V zZSbLS>uWYRXIy)u!F|THHv$;f+z4Ro_8S3=-F_p0vDfc6$ zn(Y%6Z1oNX6BU#XNw?QNQ9(O-`;!&$_8n`VsDQm={6qz0^P~_s#XnI&UHb$Dq8M@o*=-TYW>@5pMdbU*Z2tn+`HN*2(W7&FThSb6A;*mXM%t_@k|gj4rZ|nw}?Ty{wPVN22Z1C-E`~fA9x?@YO%`LqGHrKm5Z#{MsM=(I5RurPnFF z{sk|1ffMDd`$_uHmobdZIhcz%!3X!?Ufh#=Pq82&d=LIwotM7hCpvkl(kuSOzxWrg zmi~Scy}tCNFMZ=r{KQY}{JPh@Zr7K+>}9+D$dCNUF6u+tc{^zr`Z9*G%)wmD$=uvS z62f=!4}V*FhilkjULHe zX;-B;DZN?g7nOcZx_ie*UqZnMfK?E=fvH2zU3`%`Mo{&-1D#Y-h1!E4nFwcbB;UixU0`R^UMXaXV1Rx z(n~L0e8m-4terDw&PM%)^%fFk{l@KD+Ri`!{QJ&0?tyYSJ?K~zWBv2e&efN^{QQT zy^CZGCx1r1`W^3l=R5!VKKtx*_%X*EbHxQ0T=2lvS6{v5h8u1e%$qlFuwcQ0!L7I6 zI+#CyzSAwY+%lMy=wFF3B(86~@x}pjGB@|&Uc2qK+kfZYtRdI33u|MItd%t@v3B-Q zzJa~fK2@ClH4;EB=)L$(y!A?K>?>dS%AH^Iq8Fj}ot56A^1DC!(U1P$!wx%awsi8~ z_19nj?b0cYRrvK%$+;;+c)2Qv*&E@ z!9BS*YhW#`iM6pt*2`N}WWRdh@Zo_Nzt?O?|-%s3Zea+SNP1FeoV1f2|!o&O~-Kuh>zJ9Ly z%)NP@`ZMNcm2cMgn+7*~zMH-cF1Q!>^*p6>Gf|$cwu=V0sSkbUYs|b`q<781 zTruZ7-y`nDJ?F}v%zc9E!UAh#t$+4sf3`1cXAkV9u?O{g+U%LVBZKGLAGj}DPt43& z>NPrN?eyb6{^QoWZ0I{a{_&6RPdog| z+QD55W)JSY{geUvdiajRrO(naIXXWq^shGB9|jLjbqzKOBkC*tJ82x_!NSN6J9>@uP3yh%+Sgb6M-n?6!0<*?} zvB2KgBYS1fdd9RfGSD^luj0e!Vq8$V=N!cI(^rTKuhX*>+xj=Z`OSMAb<|N8;7g@< za|pf9w;UDE&s(5$t9>s@qw1|DEFeMP=*7lW$4qk`J5_cjhmU zkG(>AKVk6AyJig@!QOT{m%Q)#|JnNvIIXH{dnuZtQS*}b(qr0GL zj4k#q7Nkn=9qD~2!vHgsp@V?*CemgYruW`^VcPz$wa+>CyWcPbP>jiY&X2=w=bp3o zT6^zP?)~n8&uZenOYWTCpUt&&V49jFVFvFoGk8#b|hqwaI&%-KXNhzYSFM%d?k0sV#8{kx72==U7o@LAcF zm@E6n*S_{O+V~IdxZ{pzH*VaRL_dH(fcK+K$JFQ1b{>tEsAKyj>BKVNNZ!%^B1bVZ zgL(FI;t#I-ZEy1A9Lq$DltkAQr@g{X=3z ztcV%0BZd~se}}mu?dw^2ucZ9w+k6)~beK7FW+d$#{KMyuq3r|r$KeOZqa`kSlk$=U zpU;H9x5PZ?*z!m&dEW7H8gDJ~X&J4ZBj5h>w%njP{Wjsxd!F@Y#Lk2bOjEhzp)++q zb}UM5fS7=PjRiAi%!mYL*AP2v16Nv1|CRfM%01*}zvW7dZ@&rI`CRnI#fulefAZu> z5l)=*44$W%jeGN zv8$U~UmhpZ8S6LsY3tOT7!V8k1F@lh5UbG8&~3zy7!pfjYO(!Sv;jZ&S3u@(U`+cR z%x&DbeEIVIadC0ftq}?HI>+UBRE(U6I*2xLKDdX^t6$JJOd_6HE;-EoJOttG$3C#{ z%Hme}IjtO=SB@MXtn;5xzsk-!Sr_IePcD!X(FcJ~46sofP^@BOVu{9oaZa&W$a^A=x1zvb(g)A$bduMP_f+vd1u+ZGM(qoPjA$>@EE+jF5mVxi^7 zVej#!{0G}az%Bwj1`!MAG=Xg(IC2bhI>zrP6bd+)*cVZVf zpCwsw><&pUhY ztE=UfcPj-Mr7io<+^?H{yS zCd>Mm^;7OMmaVcv9$>>17lSwe%nWwXoG)%Brv3Z(9|erRYq93Ikl24(&k9`JzZ`87 z`zGId;DHC8#51od>-&%!+@B!#`=R@6)y zhj=>!&-nSQpZ(7nQjijYG*3A)c>{d;%lfGG`^#{0pMK8RKo;bT1!ly~*#O%CU|UIy zi8ba2h&kVt`ZU@Bjr*wkQarEcJJ{dC^NY@SuQnk*K3;u424npbkUuu&Ltr}3a!>tQ z_utme>pAPES=cIWL4F(Hh}VM3V19d#@(bQ`5@yMPZG$9q*3&X>$RA}$pF3n|-@9b| z&_7A&%%^1EdjljVezxQ%&sCeaAoWw;7P7fxX9s@S$vtC0CNPVQIV2}yVkIW#gti03 zcH6dX3B;P16MJ%i^v`Yw+A;Q^FY;yBA;+daMqhu8$30?IObq(@F;S8cw-A`3uLr%H zzFyo_E`xc?j>oY}#7*-6la@HPc3$tZd2!v zYA1R9`D^9%7p{^1Z{I32-+fZT7j&29i+alJ$L1nT@XQ9-n?rCr@6J$6@2wpV+0vu23uuro=U-iC7bJVsAO%n2?;1n@{qOx!fBoxU-;HM%`Sg9r)_n3LWIP!!$+272@3Rq4J4m-oxJtZwbJK}n10VHvbtS66HG`0IE1{O8;~{%e8#GwW~4n5J09oJ@d! zDgVu!Q^9+t`NWt{YE7j^%*`m?NfU`oHT zHqb=Oi9IbeIEsX5znaE@A%qFFTM04$9c3z#w3$s(C=C-xd}6X zm+}1|+$~2hq5|L%)v1_cH|iHv@-nidgVFU zE`9C!u|{8u*kjBU4KA)GC*+14A(5*K`A6U9bEhx<_kaJl`u^zAqerkW zs@%uH_u~>IA?_%!L3^oqP-c@_Kc3}X56&OdI=WmDkmEdi{a2Tscnm){oCu zf66yUF(%eF-ZqhgQKLqU05@N=+%N`Q=x2mF*8>~l^Cx~w;o3cW_Mi!(EyDK^|KlK2 zM*K=(G#zP<@)L|z0H0u7&djsR7tEW#9A^iZTY}#er_Gn0>-)&tFC*^1*g?VJzA?JkX`-~@F{Qmd9Pv8Hq zkt0VAqTQj--y9zg?&FgrDSjX1o2mE^BTdA@?&l;r{{q(TI=4Pv6nhsxeVq6)@wjSH zuI9nLoY#5tg$^=uz&(@=42R7KmabJ9V0<%++J+#`4vMRR>ppz)u3Q`OZ0A6X!q&JSPIy#GKfZ19Cx5 z$PGC{47iZZ2R!l6nr`cx<9R0Er>y(58;6Di|5Cg~!IU-l& z4ErCqAE^C4*wdw$b0+^kL7)FnN=gcCPx~J62}zQbxE#97K%#vbj-0&a9M7Xq(3(vE z2e#Wsb`O(xN8G3V_P&^>>HqdEGW*@XsZSNAnz?)4@5*;u;>i1nckefDM4$g!*|4H7 zVl#Xn{;c(*KjN>S_b|~POA)_`{johGC35jAGI{g^;IS*_3p*;moD=N!+;746HPZ9d z8!(4-lk|JLEAreb1ACgp@xj}#T#vcK-)iD~Lof6_XHER8%$@v*^zVL)P^W&#HwEQK z?Hj4>2;-a6!(r2!j{a-8z%idO0GKBvBqC`XXd)-%h8&SAa)$kn+Yi*(kLd-DE57{X zlTSW|{$4ZteF+KiNXe3t_yKf)9`HXe`FVmr(Wf-&dB?>Z<$z=Lqu{>pn>VAMeVvZE zd%Su*_A&OxyxqwDcT4i|N$N}VS%;U~5<|Ahp|hUUdj|Hp9r+Q%Q`iR$wim`3-p96r z_b|q+U)IgqU1y9L=-2S+7p{gscap()J~VCoW3n==r)*s}Tn_A*jNg8SNlx-=%xy21 zbf);_a`MQ0*}wf=*%Ucg=1zS^?ZU+hjvd~5sRQiE=tLc9+tK?+=^T}d1Aaeox^bHe zTwMyBCx){W`-H?4NlZ*Y(!O}}Q%^nh7`d{XecXN^y87+D@YA3El)nG&{GCgE><8KE_?jzvI3&d+?vL`}x?uJMum}m$^>k=i2ah7OD`WV-Aj1+P@uy2euASo8+?r+8XolnO#1x$Vr%o@y8#K?;2S=`(>#v+=Q{@a@B3c z;_m1#(AK-!whjFr=-_ZUjeEZx1no06%vTP`1vw!%_>EyYxguwlyN~)zKx6+8e((d0 z{eAlMd5JNGeNEIQiAiaap0r!-kaI2`zdCQFHcwkjI5I&7qA$pPz}D4$r93NCbt=u64;vhUw%`Wz3A)Jf{i9Vr zV$l+xmd8#GcizPSazHM~3ArIhA-lz7ab=YH!(P$UB@Im$w_#O|(=WJtVzxy!yZ0xsRkJJIrfqp0Jm-oYS z)o{d^O^7uSianoKuuY{71t^;_#BbLupYy_VC;wgjdeW%-jVwOBC>!tNyAC4}vsmYh z@qbo%%Cke!{w$CM(;m~lz~J7u!=9!i#yFjfO*?*DZXI?;f8sh9bs_E~tpFz}lAKJx zOll-Y6{O~SHgLOEAg($ukgF|c(yO7qbWH#RZ^3qgYfa>RbGsI zlu?s%cDjDVv>5GZ$keCNCSHRv*3B{x{pAf2Z^+a!58C#>gRG0_C8hA8T=;aWWm>L? zO)%Nw~B9I zpR$%SCau66gT{}p+J9txVVmUHIc%sjYq6{heG_p4Iw7{v1~x8#L*`&S!!ZDL4W0c0 zY~74e4R#hQ)uk2uJX~Aih`0nU$VoD|NlQ!PHwNO#8M(6@ew4YO_V_L)-z&J{(MKPB zh~quBFSN7d_T=kA4hR*vDw z8F89D3D4kDmuY|A)#Lb~EfgZDZz7hI!oS!qj~o04ov&pXY-d+4?jf0YrdL(40rrNN zt(lSGSI`YeOF?%UWdlH~KOb(x-Eim&(+! zk4pC!uSOhTUjy?5*mi&EYV-{s!1Kt(I_E&y6`LU3$p4v=#g(|zR>1+eNCGG1h8%HD zh@6o-%OS^s=krJZzobKl4xI150>4ooNxMo(Nmd(3NlDW&P$A+E+hoUwf9vFTvD94i z*#R*{V&A+hYpEn2Unu?2X7)!Pd@$x)#t*v>zBWJ#Ggbr7`OuZ~awfj|>mhz+nRuR$ zepuL?r#061>~@3nMw&h8?~;IT#8ek;g1;|@ofuyy&upj7*2lZg2HbUL6SNVwS6T6M zWkkQb)kY)3UPeD9RQtTNLzN5p7!Qim(C18xlr`afWgObCp%_b!8GJ9EC%i4$Nh@K8 zv>UWH_P$`e0(!dhW^1{3bpd;F!0~7@I3f2b$;lc6$Qil2_S$R7;rYHB)E2)r`5f90 z_Wizw?;Nhp%*-VJjVURq*d{>kg-9kI`}y$Vvy)LVr9LiMoP738f8lSw`95a%mpjO& zRsF%)cD#?V2Hc0Je{denB=i2RtEW7BkwhODjd{0wG2ZHk_;G{sv+KPvQl7_t_cGv2 z-Z@SPaKt`>;TCdnKS*AGKJ2@?!9T~E)5kpw{yU)EdR*g|mV^5}8NmzdE5f`~RsMP@ z!Wc3)b&V8dVr+r2WD#PJc3&24>=62X0FNNQ4iR_LT-vxd9 zj_ULCXFLc0LK|Ozu>kUE+|c{Uw@IeF7RSo1u`Oc{_PRg5K^FEemW8~8INm|$df6wT zEXJ0Mex9tfariiGxCrr}2yx8t4m{jFJ{**H4<8?9n#bXaZ4kBwV{$@n$Pu|BXXK6? zS}xCr|8{tvgnhruI(6!FE83Gf_A66UQ<_uLvL!opx5gdMha9gyxfFvI=e-!|bawf5 zI}iP&hk@(0@T*6q1pNZX6Y+J4cCX_)V7hGHGdfS#2i(&JI7e4mfLM>YU-s?1`z`-L z$G$T4Jdc^rorj-$$M$~KT>-m%FS3`m<$NxX5;aNXAt&Cwh-Zvxu0CV)uvz-LwLy*6`4C z$d9eV}>dbYGYa&VnA9lbPWZM{&tzNSh>Kx?mHYDCW=QuX6mUp z*z?3aFL{ynm3zK+9p+j7rdT`N%d!^gZ?)LZ>x=oEzHfAtCG%dD(wy}=-Y$SWl3#M| z63f(m$EV$rbI%4S2alb7@E&*FE6dst@^Y+^bbKoM^S7bDd4s+WP?57(4@*R-Z|=9lq2d1U{)gwHHCM0CS*p)j38D7598 z`i)*r7QfDcIDr z+l=qyd*=tpe|maaQ(AhKWThQfzNnAmPGg+PXU52$&8l3K&DjZk%i_YV<%98;_i%nZ zJ$63kXz$i`oozg0KHnXiG8*p&DEBvD9*^8_#QmmR=kI1~c=Pk^Kd*Iych|PC55qA$ zZS3@NY+2S8c%Av!7L6NvkM;pqhrJ@zxtPmAIV_uTiv1f`4|V5#YDdI@`K<2Dqec~o^6Ax@jD~xwGq_WXd>F7zsmhDMJ5_!0T)$H>EDB=#ryERg$~R=gn-hu@1a+&y@g>ow&_-`j#b zY6n^dW%ZYZcme*|N9VgLJzl+5R)xNVvFc)Dzp&M$V-w|Fl)GU1V`}$|7drm4@krYi zwlPj`S09g=2YNC2@zXvC@lu`5*2CUAhv!!5hb28DNBJjb_@-10IV6{s({u6vw)oBj zpBa1+bBDjed*+!eDgzjtCje>Q%*GY{`yOv3yLey@c*%8{QN%dz9S)h+6wuSYI^3Geb; zi@8R=XLPg9HLqRzit@;EwOs&jd=_aEd8hqgYx$fy<{`arIeueQiLndYzbcFk_iY(~ zegWSH>nJN0KCf}c;Na|@_GILD_U}K|bJhlk7kM_g89cpXcV4%&8@$s5Zpab2B4^|d z9R8YIT29Yp4xlZ*F+u*n@ROhXqyy&tOBhQsGc%eqGV>%ieGlX@zp-(gyV$3^TRU|+ zo4%B>cVfwLJ@xf;w_MWCc#g5NDsQPA*)tjZ;9V>9u|uXjEVYHJrM76T@ekx9AC6xyT=UC^G>&Sq$PL!N`mp)J@%{weFs z@qbktS{Cvg`u}_{81+K;(#&NNGVK}IZb!V=aI@^**a!U@*av+;<#2XjaBx_7crYI^ z(pbPejFU`(`}Ekh1umwN|J;l{l9`ncuF{*y8MrIPx>X&>rRDUT_{Y0L%0K!I*Wq2C z3YL`x{#kZj`fjx~<%_mQIVc-=bp4uO`K_MRoqDyJaLsbWE!%+_@EsZUw!HJk4Qc~x zmvF9qq1IIlPRWDvjCk&1x;bz74%h(S<2C2lm*jULhxNTp2KBl{2K&;`K3(zM*6G^c zR9P5XAWNWZ|M3F6+xM{HAHNS|OkrN)KwHR7j?g)YesA3zt>sz zZ0;*H`C(cPdC{cYI(u;ESf1JE`<+a*4a!uWy+jtyct-mY&i~o3m_PMN^xgP7vB14v z9X=c5{1fq>iuZy!PF=Bx?LY2A+00KLsLqd&6NhGLTw$Me{`5z|KV(p@p)YNh@x;R+ z5Eo5*V{pQ#kUc^L11 zGbXQD@(SL;3zsp2{-A9E`8u*|C~RPfu>s4mgime9a-Y6PpEUdzfU69T zfBLYG|8wd8wfXk9zs>$X-~H);_dP1ee|C0eb5=I^&)f@H%zt8dgP6xbkEXjH+bC*d){$@!y9*Q*^7UR8u_wfGaQoY7Jfn@m97@~H-Yi6!* zBE}89x?QKg1F0!6vKV`#O~X#I7D!=w1m-FqP#c&ET^Os&^!GyK)!4p^1@2g}BWAP@ z#l>vZXVgxdeVCY_9MD$5Nq*)YAxGKP|H&Pm|FQl5yyf(~_{Y5Q)p(z**zljF{O4ut zLtWRef(Y5Wb(HEe0OO3ko8QqGKyJ&Zb1TWTKR#U4 z7JT^GW5YXbf!D}Cp2cVGGyG>~HIp-P2M&LGzWgKpbFPRs^K$QkP|KiB!ci@-bQiobxlgCF9XE+_Ky^Cc%c zyD2B9K=QJVf?G3|@yltrHo6#JvinX4VBxoS#zEq#@f&%n@)k?X2V)TDyJFnbNp@@) z3|!$?v?K6aoQ^iNc%2;GH(m441{e>vt?91qZw1a!uP|x5SD*jr&{0)nSS)>mWwtBp=+6BXUK~$Q?N(mzGoPp8XH&4_u7I_dmXc zIi&ps1qG6ulhc%&i}pYJBg%{CW-N4q4mQBKpwR>G0{*6*2oHHi`$5&@0c8hs?&H|mgX0ui z_6M229QIMJc4G2#-+#aTM)%b(**gsHPQ@t237GX|% zo{WM{oDUp+KY)hii5^3>kAGWoKu^y*gAX-ZorY)dI{|SFeEqR2z~!1a)!Cv z{p65blGC#@Z*t(B(e}6AdaJ(syKmpVHHC$REVD5;7vuk&6vGMSa*4dE+}!fKvt6G~ zR!8H9;D|gaS0-lCZx>8^1n+=eFY~AUUB_vr9X5EDk~3gSJ3ocCAT4$&eyjJO40yYf z_6eqrxgYP6Pn0^u42|vNll&@=p@wIcLB2JSdz0wnW}o@&ex|uc@8>zKdx5s~JMkM} zaybe%m>rKfYqSZf7xHQB;nuSQ_63*7+$oQu4Y(fPlzKwP8MGy5Ck6*#z&vviJGqvs-`&ED9=);x# za9mkaxDxNvt&>Hwo>v4F^m)$HuwJw8>cBD1 zmubQ|z0cSIZHDEsOy~3K%hO`GcYw^(Z|rN4oH{&of2MfgS=$}`-PCx2pF&pyLayeGOvwyYT>Lwk2YOuk9` z1dC@qj_>MERbM4H#tsbUoU1e3pT0fq8smh^ul0xOGlI~WPiP-?{xtY9-t~93rCVzQ zPHyUOs(3m2DLr*AbL`-|F}F2a`;p4I_Ejt z@*x%(dv-IaI6)W&nmgK4*UE>!Bv zwrHPVGJcaYum{_No79$etm~;<)BiL-?{$3QnN48-bXy2q3*@7na?bPJ5%(zn>%!lJ zjV)Ilg6-4TY8m7v4s}J#bezCG;8@r=-+AXaf<9nu2RLvadDS<7r?w3^F2y{8!I@aK z?px%d7@Xwg7fC++Q~T@SY}~kUex|4st|2%HU@9_RtN7=TzJA5WW^)@kj zkaXCGHh8K8zu(K*h~IYgk>P#$Oy@?-?RA#9lm4o|F|EdYQ4Pjv#%@^N zVw1orf7j#6<-0t*#&dyW;ydqWA7eznTebgp@cm)n7X1zIWXki&r*=x&7AgN5mv_@X z!^GkD=(A$#%=-d%WAO28fcV)|imi*IyvM}^&M_I=!21R{_#HmD$2;Da?yg%pK<{8~KH7YPUYwnaAUvdFadJ z4DFw12jH?4{VhII=?DIo&V5S9blRq}twwVAs$c4{<)3x{&WJ@--ZI33ZMyFFoJo($ zARQmvr2X`;+4wEUk?|T=YB68Ka@eLD?gO@<^;nxIx3<7@l!bA8KYm}t_`>JXX(wh$ zHQxQ9KA!zh9&Hn>e7MHAv1sPw`W%C82Fp`lP@I4T@|hphVzV(Xwx`OUKjqB48Yib?BKg}j-?v?IESr^$BHv~ zQH~sc#s-*|c2HNc2JeAy#T>$LJcs+ewh4p4*@*tP$?^ry>F=)DcVHib?{f#+K^XX9 zGIh8u`vLU-RiQ6nKCdHwNB#hCM?RIs)UWY~{M$r%I8V)Y#77OlGiTW2=9N9QU&1)a z>qcfj|30jIIFfU=1?o@M&&YeZ^1nn1@^ipTp>jm7$QikN=%I(cKrTIE&wZ!ubI(0T z{@Y?5`+oF)v;nZK0x2vkMy%ZoS=a|OysAA~j!oQZp%ZyW%wxZoItMT^?O-j&#EHl8 zY`))ZG6ZpZ8Dc;k@TTv${Cv;C;BB_N#&g^T&Vx_LRGG6x8p^gxLHb(RxbhvfDc3g` zi8?~zk4N_mQ|{^4oWnD9Ih!(N(>|CgF`nRazbT{ss5ZJ{(aX3eLgPrFZf7%$?Z)qG zU}s^opFqF#X6@f(#D?JcpotkKt`Q4|6|jNrxEL9*5BitMqj8g%(*|fCY=^-`0XQKy zD;+9`vC2G^yqPi8&jZ;=7Pd9Db70t8Ri)qwl)xmY1G57 zm))nH?!4-3bTxe;!~i@qi(K*+;x^~Xx?&vosy-7SHx3tvwQ}$0owniG0{s_d10Oy= zYAAmnzg1tQ&*R3S|IP9TqMtdm&n;+k9@06)l&HzdaeeU$#G-I*e^{4m9~di692kY? z`FLjp_IVKRs-G%Zp}eU5Y5lCz*fwS6T;ZCq7op27TIafmp3rfX@ZN@=&ZpF%j5^fGG8iK^M7*YU!!{)+5#vwD zp!R`nCHeu3^~}e)%%o%Q%EIZ7818#^!tdH2NBmn2nZUPl&o;xf6$Uf^zOf_$H&WI^KEwdE$$)Pg`!)KQeS6L6ZQDm@1H?mPFUqM#OfA6gX6H@8_j!7B zR9_o2=uTNa|5^EP$6)=PFWYPKOuXr<^fhM}W#~upH`sjVh8$KQ5ADF%N~qdFW99ph zeX|_g`7Y*!)SMu9I)5n_e<+6|$q@fh= zqtVCFN1^QOho@uB?}P6TME~_k8I3X+Cs^+MX-`T~#u~`T@dy0@aRoZlt_%j2Z{nlp zTVTn1m@Lk2Tb_mIh~S{Gh};*F|3-3z?}2iys7uHlIs8cEo15DF^{;=`_lB`n`@^h* z@ujE;+v0L5$v+Bx%sZ8y-}_{A_Dy+ppZYnSfq{3<>aDoI-s=ii;J3RQ^*e532Hj!e zHDdPAK3yK?%|!jr;x~v-!OtI+X=DC`d+*fiY+nX}clKkqtQm;+3%0;kIR^n>R=xfG6(1(R zrjkiSS~??WJ!Yao^yAbKGYfKn!oJ*pBbr zhQki};5XL~%1Dg6)la*Vn-)p=;<@XAJ^dJdhi`oGy$<&E$-mih-^B#hw*=3WR->-> zrKWhh_7gtbJsH0-=&9fBSTKXW8-{fx@V*fG@BAKQRemIV{0aR2<2P!XMwaVUHv0XH z34a0aSW75*C2VY)#wyx6+Y84(F`%xB1@|5A$Va?f;(e9$AN3*nk%$DA5?r8^V1I7-j^qA*p3*=>y+D9Gw zppBJV&}ZBs<@sBsp<)-ti<4k`?DyTI&vKh9*5UoWDfouVv$7Oz!OF$2<2S+m<-qpw z`rY_S_Jm_2C>bGkV&69)oYi5&Z;j-`a!2*K85{^R|Hl^u@si zIUzUX2wYwA_S9m6-@@8bA^F`%TlxVfaHLduE~Ag_5ZLT%N_ zYPU`YKlf_)%DJ%xhl@K;e2fi{7vjKp9jB07z{fkhvBSU)MtnZ~2riG(i#*DRE zUv0@YIaT_;G*#|E!hW%7=V?#W+1MQPLI2S6d9`nA;~oyw6Lw;4z~E(Vz+q|qi}Dfk zQeaOG3>PIj_QSW}?*ehk*+=d7EAhVnzWd$pw!^!iY)k$F^GdsDXXs}dJ18mBwz?vl z&+Co-s_e91wP7E}svC}-&e(eXZgp4gft9zl&+^nBhy(qJTiOWvcG~Y#KFu>f6NeZl znTKNm$8F24X%`IdhWo&NIPS;%LFr2EbB@56Zy5Rn+t&}mIm|8TID?!rmN3Cil^ZkW z*X#DY>S68K^%a$8Jr|5ct9U6^R%i0A7~nlt^p8qPN|DeGgNyR=a?bhfBu9AX^Af!G z^HJt}e5?m{v93fLJVRs~a1nk}`mFkYNeTSFSV~GOV25##Yk{?I!#jC(JnO#Y-r!-b zIc!|)=X@WrBE)$35?5 z|DYDnb@K7M&?%$t#k*!V%S^QC9KUgF$GAb;(=BDT`!2Q^d7WHFe$`d=Fn({ap{@a( z9A3sgaGqS#FI-z-kBM~|FfRr6%7JoG!r%KoOOD7DIph9?`n&3?tJ-4?a>jtKX;aB~9#el$4g~+DVnP0dPk>9OvA+eeXOucFFn%`8MYq--Z{*y@}b3-PRVATjT%E z#~tsjZEKL9iJbAh%*Hab^ENeM%l^6JkK=yMM=zQ4cl=i97H#`a9GanHK8-0TNAoiN zSX*Em3drd8oen(aen7{-e!vDitSn}Vo3~HDqD`Q!&BJfrfjO}!2jl|3`TdaG@OQrC z>O%4E%E5O8+r0Yfs~Q6^xA+R%03`5VBBf=OQeG6RvN;=ex;X9w`r>e~(rZs{w; zfm_GF!OU@@=ZSG^$;Ei(#rQt?bcuZj+61{jh50>l$lnocUirFgUiF5~16QJ6&h;>F zeaT8$x9k=0ev1t6bCYaZ*%$qZP0C-uwoI9>{qf{N9l>_s^f6nbtKFxscy=Ga!|?3d zMi(n>oB;pj#qj;ID&SB4OPk0AIU!f%h+H||F60*D3AQO0BMy8E-$r@cy3jBgeb9J&kw6bAS)}cYt&HaJ4V79jF8M z{4H_Fl)u8SyJ&x&^Nho>A2Rh}JcFL6aewWSm+&spt$2=nvn-zToYWL;*7yFj?Vycm za{RkvwlzNcq#fXxb5wzt#dgEQ1ZV5ok2%dB=nIDbYP=V#`|2O?ePB$i$vLqn2jqgB zkQ>X|s$m1HT_2J6tXj{*|T~qlQIf|H1zaIm>x2)-papMlDF5W3yu|H-&SDe2M+#}}qy-C0E z#yN-jl1Q~X+K{s+jVo$<#{N7z)OsnK;oSH>_JeGg^VUXJ!@qA|&zm?&+zj{dA@zNX zuZlj9GGOeuud1qIyT6}YVC}Dq@mrsdGuP`VKI<0!OMRDk(V|87AO>HTnaX!UyP96DtByemnFxYsUTigtO#MUI|XUez7E$zYZOuzT@tz4o1W;lxgAK~DC zHQv2`9rLp{qA!m*yuwYGPgsTi0KXNuN#g+RV$z5|$ga(JwpX?T*P#dc`!&9@v`klD z%Pnj(V580k0`}nS!fbuK+c;owu>1#M2P}apu{9W%HQTngksOdP$H`gAy8szDuY`UE z#{w5)4a&K+L+}qfD3^+gYN;qqQ@)*#t1XjzYu}!qtG^qZ{MZq zo^hZ2HU18Xd>3c{F!H-`7t@T@x~i3_;~LeY@yA-fdd%>E?U2S{fq1eAc23~ zLf&er!Z&V)4;xucgsEFGI{qWa_^2$;aXMx z8ho4MG4ug$!n3(Mur|Phnz+w%^Kp%N_)c#voKh1S}w7PdfJ_iO;|fW`*20Vs>u zRaK!atxlq*vn&&J*MrztXZ>e!g!#xx*D;+s;XJ#uBwyD z@(iOFo|~ziQ(x8DhXrhaKI7VX^?mvflIQEd)Wl|ky|?eU$JX!je2;`28harBD^Wi3 z6i15G?3Cu}y_&esbH<;U&-p&~eK921u8iFUWU!oD?gKJI2aW#$-CP_nZG*FQV#0l5 zguVhWtE#S3?ujKa#dDX^nKNhJM6Mj>ABVRKcyF7bLx-vjU_J5|DJv5AH#SgJeG2*{ z0|(mm65tCx;rjvoZ44m)mUG8{07n<|>F2;+{hmCT{Zq=b;mh&ea$AcSP>1&~>EEm; z(4XUbez;c5>&8x;4VZfZc0ilbc!PSiZrYqDk2y!*r`$&Nmi#+AXWxbix;rcw8-Wk8 zB4+d-%f0eXY{{9$`crB1&tx-v-h?%XX#;JK962(C?Eq|`(bz&2_F)5M3D6h(Ycl$q z*l#e=Hk16*Uz{EI{K?sZ;Xc?7oNqciP#%?U%PYqDbuOt5&<Y94sMaeR&0wg! z2l<)9f4LaG&G9MtFO&MR<=*4d`n_^aj(KeP4$c#}rq`VfaqHrP*QTIejRygF7#m!w z#XI9p2eY-dt@drW*Yo6^^9RI+7#ZFbJ7P#IiD_6^*u}*7e0lk2=Eu5|?N+Z|&GG0L z@jgNrQH+TSViul*4hkAh=>Yqx0YVo+0+@8`am zSP?T~M+}K2G5x4~d>Z%t@sEFO9~l|h7T=w?65k;Uqdv8@wQS3pYinr(^-^0?F14jQ z5eHxc_#LhBYwIiIo_INbxBRmWRy%NhZ#h@)>9aPb)934Te&6|efOGQ8=Q{Nzt947? z=W%ch@ZT3GS7Qw9K<$j}0Qq-ufU>Dv0shIgCgtAfrZ!NB*hk%ofnuS44{T~1i4`%! zH@mNd-?t@}p9Y5?w@$1(&|XB?UVR?x(M+LUXb&2pW0TrI9c-cwc2E&*`R8vk9d_iO z`;4{lE6ca?@B9$l*E=qhcXAep>B=d%R(}T9$}`^eHeB;Kkc=&uWA3{cP|y2m8`iFZ zx3B}(Ca9d0)!3)%f!_iJ^UppjbwrGV&dNJ6P=BaxA~wVb^E*?B8L=aVAD4$u^Zxtq zzrP*YFvhNS_?FaQ_6MLN?Ho2x3wuC1)g*OQIq;)mZ&or61Pws>H zH-4(Vt-SNt`8jzH@J;UP@jKyDNT;7SdFx>-#&&Fcuw~Z67EEf{-?aRztdyDUfwN)Q zGx|Gq#p|W6IvaXJXX=hbEQkrQAx6ZCm=U{Ale-I5_n-g#=eB$H?4cdE!S*?fY0DZK z8lZb!BNFtc4e+>H>MD*It_%5&wc%W2fZaFkyYahvjs4&lao+ek_kCO`$HAOC-pPGX z!n|)ilfB1nPgxsKn_}7Ip6v?W5u+?>e~bY(4mlfjGHZKLWZpZgD~E2>@l>PoZgj6} zY-p(GIKPb8Sd7}^ciaEc{rvN_4a`-xU9)D5zBe#^`tOIq3Ex?U#OJhuv6sW$U{C)h2i#ij>F@N@7CbsW>r2)u&!;@TtsN-$Yox*2 zMc{tK2(}p-M_4ZFaeWID2fQ}GvjN%$;}>M74%CG@Q8(%cU5(DCz~7gyL9@bOQX zyf%oofn(E`g}kL(p+lj-W_I}&v%Dv(jk96bb`;ZS`V)DP4^Om~x>eZ|5&Ye3|=ht3)?fV$(&IISDoE;FSdg5j=tVcYkug#KE z<@0|K}MAoyc^k72j!i*QAg_f`s=TMkC^P(v7>$O-o5`<#`~6> zw>*B}fd|@zhlev3Fy^&cwQAL^c*eDlI3YC~8>nw2jt#Vf24LFI1Yatb`sxIH&tWI{ zVGM}Cb_EjKX7J_n@6+}f@0D}IzuJMd1(TOHVd4T~h4u;A9%!7PUD4-Jm)41RfZvI# zERd-I@@Z0=rSGfkP1J=xkKbwS!#DYErLNRDA|j$aF=&a)|FvVRQ|HA3tT}KQ)-8Jh z@3=)1v!ZT0%XJmqG#^)((L$j5g z@9RZTC%ik&HT5oH+@sF_*LXggzQCj%zFDrg;JuP7KltE-C-M7&4;c@DC1V0`ZlE2Y z4K@A7hDP|D!@s^(8ftTuoBFB`@f+^#;CYjBZQA`v<&&JYo=hLY^cNTha4-D3q4F@w zOaR{~y8(63{(RLjoHcE4Iv&stIKco)Sg}NssB(%jd-2WA0`m=HQQLj&& zII#`BGvWCMzFU78`bmF7yRaR;RZncKEi^a65|99SJ@id>qOm@p^?6>yi-QV z`aWgGnp^7c+qZ9TOP#3ON5$p;+I{$TX&ZdUs6D=6?Be7l%a$$ch~GL*z+6cJ<11~! zC2+uYmRw+~+<>1Z*b41M&X{N`Ovt9mJJx$>Pe|a`mdUcU4)V>!KVHYP`UG%1Vd>JP zTyIH#BZc>3+hQKSJ-(s)8Sfv%fDuRAVUE<dgK)*V24BJ$;u^HAui>5e5O8t?_*a5#PzI+y!=IneGi1@z%*V1= zCd+0WtP6ejYw#`x*H!oaa6>!v@jW~BE_}9gK@q_39ovAHc1Z2ve?hVJs;jQLe87MK zKiIc#-yKOwNv~oZ)G?S}U5t0@R-Nt)x?`W2R zw*1ci{rmp_-_+om=-wZ=rX1}l3)-n5IlN4t?R=sLV7|N!=F&Kx<~%}BY-myOC3qL% zOSj*C`&S=-{PAx-|NQgc!tZ6j%Y<*Fev9Yuo%F9V5AM@SLEz`MdCf~UToX4OqPAV<$RX^UjqTe@-}gCac$mv?>$ZE zv$erDvfCUyc(4uk-FfC=-hU0LKa=7!1U^IHGXy?E;4=h1L*O$6K11L$1THKB7~7o7 z-wqCj0)J%}U35j@NQ5%^j_Y`QRp7)99tTQj=HW#b4R9G6&2eXUyu%!OH?%x9m%SQo zabAI1JM&y+3WTuc&x*Xm9oq^9x?>I+@#hY01zz#C0xK-12yb|2h(~{4K9v=;I*xLBPM;@rA~tWqD`gqviFp z9iPp9T3$b!eV%r`rM*w|8~7Ja_m>0y>t8wD-?IGIeI5mI@hmNnU*nxmU=6@NhOgrNJ^?%P*eB2c z9{Uj1!$3r_@!CTW=<&}(3~2GstMDp4^bnx-{PQXtpogA@svS?+0&Q_3niw;JEJfi!QqSs=%>av7=p??@-D*H`>O^o^8?o`tyFgeSScy z3(`QOHWz&l|KqQ#*?ji@r-K02ZES<*nr*p$$IpNM^Y*{^#V;;q;{K0+{Nwh##yp>n zbQiu>e7m+Sbi4SLTWzKjU9Vss=H)%Smt|ad$v)8%+koGT zu{Qgq_>#et5pFVBcoQQ~JJ2tFem#}Ty);zo)U9oG| z&N?RU^Bk{5gfH7MefqRHyodL)3|Ah@W*s>9uVfQ#;JcSV)+=DE)cL<)x1EO%A3ki= zs+9+K@7h^?=+K99v28tJ$kK735mhjy#yjz-H7CS2z|yLauXW*IDtWwLD6 z!Ma!{jz3j8Fa2Za^$ESJr>=3m^vRm)O7Ul zuEyvio0?7@i)@NJ9^RY~71o>>6{;!Wcvv&f@f!0SLtfs)dszm{+P!;MWk|@prL2QE zeEsWRyOMQN1|0v>|L|Sj&*6Jr)ceYx{`6;^LPA2q_wV0VckFl+Icz?1_`}Aiqgxx} zqr#h0Pt3&{a?>OuW{PCSOvaXJN{ckvUYnwMc~3%AL^I1cjIvnfag@zESQow-(TR0a z2FmhJ1c3JbMSQ1>x_^7nph5lN!$nb1;Ogj!=A%arHpU)b1D)q#?f0qBiF!_jPScTQ zw3;$bJ3rmrdy;rf5thX=S@sds!Ma!{zLD6UGEf%Eg#Az2AKLIecGPplx4-k9-z;6a zWYh8E$K-fa3~cmpV_ekQ=Jc3Zl6eyRo+RHhOgibd)XSHENrqmtTV8YL+&wGBMZQCE`Mvkvd-v9@TT^3BoPd9xXgqOz zM{`F=l8M=vQQ?O2<@h%1pZD(FQydd>61qn=yL1oP}^`TDIeeBqAU~m#^L+&Z2Y?RT;dciQb;f5RNTbDie-1C1vcN270N~#DJw9b z%pdy?_Vjraav6TNeFtK1(aDo>5*2l%DJ?csvg4*oR@^MrJ1frUPr0dwImW(s-d(3} zOjoY6<7Z*rQ^pJW32a+2Jp277AIro2kRknKs6 zMX4cjVA~)WKkQ!V`$lK!`(|gXWq+?6*g6o`<|~%1-XG60Td7aWpDj7@Qzb1fT%u3J zG{nWn$?DaSYbZN>_fqO|z5w9&Yqa~%Pnj~M*U6JHa^hq{V@m7>$wfWc@zCAcE@jHG z`)U z*^(h+3}mIul$|w>eT7ChYo#^7Z;x-39);dbK_?~E@Ex;JJ&7s zDNbL2HEmy!ZqHpS2i_ltb=4xIIBkI@?jPJfPP#pRokWJcETvfAm_B~4<@@vH#!r`o zlY5&f^Pvw8=D~)4OI@r^=Y+wNPd-WRFU32U3-P@YiH(hE%t}}!xe3re0s1FE|CWiE z1f4Va$PaD)$gW}X`tu!R&wC@Vu4ov1eVXSxnP@x9vlhb^M##%g|62CF*AMYw9_(O- zmxn&V^0S#vtIO358-R?Il`8$$WH_PPy;zu5Nv~yI{@tXLA6iJQW z+?UKu`vCbajWP7|z z{A=_nN)nUOB|YK2=KRFziaX2B^QC;qPkE+|c~E-3)(LA$ca;y`9|WHUUY4Jd^!fNE z{qJSYzbtUvys^y|*Hpo@&?e~-+V_=Kzo?Jmm~y(MAuUXy9# zAD4kW?~q~d+<`TYZkHafcGNYD=@XQ}FO$)Kj!nE+@$$f zuOR~Kx-FN?xP{X1tu7L=pqs=Wog@8w+$w99yrMEt7Qbv}A2tBlsRM0b(V~UDp?@3b zrr#-P%?R*4K3=H?L$N=B4xtxKS}7Ur&I>YLYeD*;AoI3Ur}P z)XnO6n)-ZS7AM=@dFP$Kj)^%@#(T1p);1R;&rl!Fhu?aM+(NJ7^bm$FS8zEZ@1{fGgBd)*>~``jfvHV#8v2nVlA(KY}l#1`hIKT~$D zH#=w2V_4_=QPq$9mSil#+D)%WpVvEKjjuc5FJ0y2p-Jj%p6-;9IOvx8qb*2U*Gyfg z6Lq7GR@Wd5FsGpNNRuZ`dLlU)F+U}#DKB|3;uT^$Y-YBu2f04tO^qAm zpSTpH%#ot>#ppAQL^(Yq;pl8&VsNHxE(K(y%%}r8P#5Yn3A#~fr>h?T_&fXh7p+^j zc0zi3mZYadHyT~7Y?Q&u;m#{pSi5xHvTpM3i2Lx3>u{BW9MCtCj=c*zzYXd45_bex zVNI1n=$Q|_X&3&)c&-U`lq0Sj+CEeU_3Em90NNR`DFg-;SfiI~yBJ)cH*s^axowMc z)P*`xH|mH!jq1vM_XnHcxl1s9TAz`bE19YL8w*ors!WvG)K3`#`^35owtjr?Xz7pm zoe~9m1Ygws_}&rH=Z%gsZ|XBr3|ps8syF&V><<;CQeRUR_j!(eU!E(?Tp;m>Ct|Jp zu2`ddg8EZ{SK7F-n*e?Rxm=l)(_BaFfG*T&|GvHJsUzNxKTUspf1Y#dUp{v1NOWda zo@A%K-&6$sIW{w8+43N3Artvix#y{GjvDv}#N(Isn<z270Lmv+ZDAy?vF%1hnm2NLYr^o#n*-p%i*zO-?Z2iMRiV!Ju)o!hlP z!EebQe1D*v{9ugQ7O{8rD|Rhp)N3j;WuGaz&?OW6V@#Au9jU9;Spi5*P1QcZZMWU_ z{}K}uO0%-_BtLCUb5ZInmEEh$>aH9*zLc|h;Ae?U8U1IO^X^|ICov3MErCAsR44i> z{ep>Qas4>m;h7gKwYR)LPC5gb)?cxXYMB_C#(MWF5^!T2dlD?w;(ODxj1z;%7V<)(drvq zr_~A9xTbUR@tGP2wyu6lzXwX*sXO)21YSAD=ekf^SND=0uXNC~HL|C!qr*B(e;E2> ze>BDn`?qi`5QcHXGMPO3A(=brFKTZ_9@>=g8{Mi68Ci|YxDFi((j%L*bMhq_x=}~J z&fL$<%~ky|PW~ydsLDa!g7nqR#c6EgsiUbwb)oH2Cp~Ylp*{z;_D7rjupHhqUVVSh z=Dzw(Wa8)PuQ)7}GTnyB=E(lCWp#fk$qZ8;aQWCrq+gAIA05WHk^WVYjecXyY_$6~ zW9{}qiiu)iYtO7qx*N?efJNce@Sj` zffQz}Yc5VV`Wp_7&QAA|4C;mcD(WEjlSbStOXoe0zS&yYy?L1Q?smQG-q;86Fv99T zU*i{J811(#YawjJtUJJcp4V$=)63C*ux~KD?`;yjf0Wt_;}5?%yCU>e^f&L-Iz724 z3*=ClxV6b}2mPT-4s=RRPAZ{}ex14Rei!wso|mt2QFR8pd+%V5-%aAZ94u}sL~^Dixv@+l^i5q%BlLY=4^b@c1Z zeg5HX8`{97adEN7@(W6&AZu4sDeh(2EXyS9LvN-stA8E714)0MH1a;kw?w%u$q2!> zt5!(Np=sKN&7Sxm;`to41=UgcU!;A0_WRksxkTIG`XN#KN6N_lcc>p!KyS4fTx0*| zU5t(2U;DQD7xkqqPOgBwlpD4N9jFU+iid90(dtUIy+54O{%>^j@zn)|rBaZ6q_Hex zj@E0+bb9-3b-sQZFLLpVGH>$Vz3d6dbk6?Yy&X@;yNAplWZO^GU zY~7!z=P7(^@BMW>fa#wU(;#fHzQ&w)|AM~8OZZk7`hnoa_zpRtZ&7cD2V~7a9}Bur zC+bFpovxnpzH|Hb?ipEF2>x>u8_O~cw!~NKpr0a@+r)bKy|7Iy-jr!$@7MLNn2(&Y z4@g~f4Zxz+vUB55^^uw5@5fxpIJE_SFPvi?Vqq{a`}{6d*zCW{(z(y*cL!aWERXBR zOd0(r*&6w_mO*_LA6(No;>8kUqtt=AP^W$S_Ky4q`s+ItD^@IjC_leI3JUU?O0&YD zcSxXI)tj=KM7g-GS>mzDu=CE6cyyvPmaeo|ArEk1EGK^ao-6wWOXt0eyjYie%3tKf z0mina_y!l(i~ts}?Fz(+a-<2velNQ>^@EP&7`gzXCX~%sGq`sb#09MFg?7ha!@68@ zIN%tvQ-?z6LY=6a)&I2jNAL{_Z3A9-;e{V&Wo4EW6_-nC&erDgER092zN~}sjmKt7 z-(85fyd3i&uVJmhTO{G|c(vaK^jXaJN$3xEi{#_f{qh#?N*HY=1Vii`Yea-NbI4pXcM}~YRrwWPS#C3qbx3YI8e^a zxl#fhs7p3_B&nu_V339{iMw$r4>?~AJbT#6=H2OP=~40*myZ&6XP=b`Ky<_ z1sm@S|9l8*vUWqCpr5wY+-{6`RoCeyc5KHQ@lCee?k|`hy#woY-!8}Y z4wsF~yJ7vthtZ#!gf_Gnj$f5o6YfXZT`)h|L+fHpq^g3n4 zrSA5c#z1@*;$?gTLW+wD8_O{_Q<07J*@?5&)ojh!r2=gbe+w~DW&=-0P ze)^!c3sc8Dgmq{ikqs+)qAlb2pZyuE5dr^iDqo9lOs|vd#Kp32+h{qye}aq~d?)%n zqcE<11Ag!)tZVpp*@*RP(_B>oaOj7VPRu&aS5LfzwFns zHT~U~0N<_rZfx(x3;Z`C<3&i!&dx0*t%(_F~!d#=|q z`Tbqi!TtgJ1Sa|9@NmF6$Xu3xpt-cHTBr-YOUiN2MP+4WUcaw33|!lQvC_L`Wff9d zlGj*~16gwyAsL{$X39<-Vo#oYm%2LLTGQK| z!*{q8pINhJ|D>=mzoM+XM#>8hHdp0@SY1&E)-Kky%ORWD8r^lhQ@iG=u7OEik<2>Z zW=&qM$y|?kpNP4kkg0#s#J(QqSV#501-{cAcCr?A8LZ5`c1=~5t@gt;m2D#altX!- z{@h2*g3RTRy`Uh!V)pFWKcRk3H>c~FwhpKGxVXs`6_r?*y`-TkAM1(329#r~x7oKG zqmLWV-{@@CIyP(Ab8d{fuwPh!IhApP??9W;O;#;_T_y~@OSpFNs>R*#9m;j;3!LLJ z$>GPXt%Fv z&JC>l?Cjd`OnJE89oPTkT8CKk$|bIU#eMVzsZUe+8j0FBQpOLtOB!mnYU2Ku)qO>( z-m~{{?PadPjQiaE=rfw~OT9ggfDnKeWW4hC;mKlKOxkgcltAl4PG(>2?%=ERfK1><|w zh5P682bkpdsD0Phq=7$ZKL>z6>I$gcQojw9tiZ)_R;R>w5Qzr%a`Nt_U z^>8{koz8cA;=~C%E?D`Kl9J-ATC8hRT~!7QHUfiCE0^KM$*Ai)LH58pKU_b>t?6af zZF4+RSI(2_T5et*v)+osLCaAr91gzvCCc@ z|KOd+>&nZ@itA2sEx)S9+LHIdd#H|c9ZzOmAh$LQ*EtESAre^UfZw(@iEERY{lFRs z94i^T3=U>}G?r=o!C`^ADFbDx!+J}UjWSYxCv!_(KI$=+|5XgOZ{N|OtgJNq)G4fK zS6kOmTXq=wbFI5&kk9ev^fzm%P#>ea)75VA4QGDWTi0RI?|r-L4vSzMyz;r$66&li zJAyTK>aeC-J=(j{?CsmP^EVV1Iaxodu4lThu&_`uSiO49|AU=Ha~*)XQ_T%^Rq0K- z{*SJ41l?6%=nQ+N?)v?5%d>wi1O2u z^Xc`p`RK^V)jF^3WIU4&ANM@$O5;JVUcE2R&B-_)iU3qksMV1eXV9%Us zT6?pdoeK zVGEFu5C|klLUxjuyliA&U-sm^n&0o%S1*r{$fk~eOwOs)_r6zk@2~28>%G5w3%YTi z4xRJCT*=>OuwCyMFV`Q>kt5B01?!(p#(KV$$R5kw(azp(M_-TQoU;e{#Lc=Eyyr{) zdahaF&SB4dq1-cD(N}@4CAn z|GxQN)_Z9=sHAUac|T=QCjU_f-sgPQuiqeRs}KXRe3@gr8d+HPa1`cW^n)jQdu?sa zN?@!=`ioU{tJv5()e4chki^)NlH z$e^sQu8w-j#{K+u;-gOLz8b7wRa^Q}kSlYf@u_UtvgP|Y7pJtfwZ!#wcQ^5O!(1TO zE((B4ZGCp{H{aoV?qi>xp01|0w$`|^GW(P*;o;vWe)?*dm(%F_s>XCJa*uV1W0!X zu(Cs#JTCWl>+i>#8ru11@}`Wl%SW%VgX<(O`5Q&YCjS1`)JgKuYm}+I_2&_xy1wCZ zb&_s(iCiP!)HS-@4+q!ndjHQ&ZKd;g$<$8$eWJ^o8zUB)T20_*+(trKBQb|4g3DGE z{17%W?*F+`J*-1;qx431{0{0iuzOLdWneh5MskX3*4^FX$Fo*r?NX`k@w%4(KZTTE zpuCJ?@ENiZSKp$ zcWiSSZIhEz+u8Rm$e=9Bq-^TAN?2Fa9C_16PntC8Ml>EbW5(+*#K*^femecEVqAN6 zRx0AK_c)8sh4q(a2RaeI=PJwbb0MBxN?EQSKKr})9ow*NHpaugltEdL`66Xg2iD5_ zQhy8Nd*^X7_Rc%+xGOR;Vpm4SS;W6&tK6(3&f@H_{_Ul` zj$aqXE!%RUoz#_a=1d>b7`#jj7M3d>gO867ugN24&6+t0`9eCdzPu{U+Y0PfTrxP+ zFW^v1g1wh>Lkkty%sg;>4bm zI0VMWF&2((Jo^mZrVhsOmOy7gK@s9(ju+o`*FWD0;G->UUpNNbgLv&48y~kfvY^nW zYc^Kd)K#xbj{0tV&H-rcNAQhH*RxZr|hbeKp3x*mB)Z z%QD*oHRACwU&YW}P+((xLnJX-*oMUq8?n5XFJHd&PuVz+iVD)57bLdRifaWv#*~^l zllW4PedJ;W_&lRR{HKFw5F^J8`N!laGv2{(`QyT0*EkU$Zyh^sd6q2BgDx8c1F$NjgDr=+A771>lpVPt=e;OB3z<02`S$1^#nMi23jOO~>Ko?5Z^ zQE*DPV4POI0$^l}LOb|5QJZEUc16b)F}8%fJI&1}pEy+iJ~+mBoXK$r-NZmF#6)b7 zkz0p+Ucxi3%)PJgs;Bbui&W|PR96jR_4s=;X2`@>lH<>qsnmlDgac~!GyY-aM-Qu$ zhnD~g;|zm@`^NTJCq7Z}(QhIyY`WUu^PHR$YnDF&F4A*|sr^vmFw+h%lvoCd7j^5S z?)AWO!bwcT#(6hbxo!TeeWR0;kL|J9O2J!->aQ)@fc})B=bv+YVEb&f^5Z8Er}m`U z8#PmSy2O}w>N8+_UN{issxh}oT=Y!Dk3EK%_$O3Y;A`s0-i0b5b|GS-!G}8Gs}Aq= zRv*oM5a-9!YUPs0)uCPQ2**?6-Y^D<{{n`}qUe5N!nzOSdGc-OBKs3Q%QMqsk3IY| z9Ba+?l5;pt16}NI3g?-)cy6XAEd?L#Q<-lb1N?m$``(1u2FBy;-ugOtYk$CbFio9F zK}>2XxvX1Z)7}Ao>P+zT-hh8l^CI{=WT&mfZ?Fiz%M+>%+(`Zg7su=cx`+w)w&ury zk#lY^TiY_8E#aZB6x+&FrR}h@&bD68HR53*Msi})j(!Xd;e+7RJp`RgWd8Xb`9T?o zvt6-B<1We!0FMNGT#Sz~WSmp-T2CEa0#56n-0=^|2NtR2-hV*9hcPe9-10;(=1*eO z5Gz;#OvFZvo|vs|IVX~m5(Dh^a`6AQ_1D`1p%<}GjNQVqZDP3^5F1bK8~JdI4d=N@ zjN3w|1IHukbIiBIfnluV%Ig?8!~~-qdGGe@b;6U@@#l;|3+=7y-iUR1{=oG!cPW(nc=Yz*dV*GrG=^)3L@txbkW`b|9U2w2HuRpxL z3g;B#K`dG_0KU@3U<1Yzz5*slRD~cDfo0!c>zyIxc z#I-kEs5pn%t{}uHX?>Jqt+gfT?{vyMrUdjpPh&fS)PmvGH| zN#=m(4*QdHw-MJ(#-EXw7ZLcfaFDfZ9SlV^n3KzV9(_8u`5vlLTExuZ)AbfvL7COA#Zn5;L)b7a{l` ze{AB95f5BncOe&TgIzpk`I}3jJZw`I`3!q-ecuuOx>6l+;HS+8XYCJizmk|9v}JsS z=D>r810E8$O(73A2yqGp85|clBH*s>iF`|BQZ{wSSdDJh#9tQds=JVAA58n z^F?~W_Aj| zADuj8i4j42o}WB+$g?M29%sB>@!5^C7=xCNv2aY}zcPmF;+PX1cR@_VMvQ zmONOAA+qq33s;)G7M$y+n0O+_6##ta1w{lNkeM2Mc z9_QA0V)oolK6dnJ#=|sKr#cxUU}BJX&T{T4kH_ohDa$3U&7QMXFfq1+u?(DZ)rnj{ z$gi*qu?YUiU$K4jG~in**LPx#-ZTZz5Q|}c-iLE3MwKGI!S|Dgah|V$Y{vgEc0tD! z$T+B*@e061Y{!7n6EnBXHE7|7bAF5%#fGMaQefZgB4&|=<5YP1IDWa0e3{cp%hfvI z+Y&!&!ul6mUKFD@w_ISNhjj<>PiuhdSI~%N8S-0^BKpMsLlS*OYG^Mh`-?e z@S;2mr5{@%zme8w=@Ok*%mJ_v6JqTeK3p(I&Rv6<`1yz9NuJ?sHd|yKG~weX2OIOh)ByIA-f!MP`wo9};{{U_u3;6+t%W~JJ&@^KtL%LO;D zuhO2pWDhxR4nK8?JY27Vp%q+nVj@n1(O~whOG--Q7)nb^{S9MV+FHt7?ZQnbW`kRE zf;CUr;^%tssSOu9GWbQDBg^DDd&vjCR+%S!fPJsv9pGOZ4*CG!m}Ahopbxr06EkN4=o;~-}m=N@!}->u5*B^!v*VDrQ~cpJH~+A!>WiSkf@pAhejy4&?sk9S!BgQJHx7Ve5 zI&1p7n-coF$=$^Bgm7UkKB(q6lIIBitKoxkTey$huQ{MHR`jKe?&gGk%B1Yp)|MRV zA}*u*au_f7y}4ibt@Hi6y4o85?w)?(TK0C@!RI^zTHbaS-$f*@lYB?$|8;)8vc|a4c{yxX`aP}#_aXdWwc@a?A)A8`cviEfV!>} z*VVM6&m8e4ay+qLVPWpC!1Ij2+%K-izMD3jY?=O6gB9LFVkfAe2I9TgtBcHE%@yMDkD7P4mCukZWm zCGFT3>uO)J|LYtBb&*2|t_jC-iEY>u3+I__*!G*uJLLMtSy@@K{xNvyVx!?c&#`=2 z+aMaY_bT8Jr_09XojD2=kH(_q|WeZkA?&#ZgAM})9;&G|=} zSM^Dm;%C)8p7l>5mgP|t!_%5@D?WTRe>{d*cztIvpRxIW@<;u=evoItoXlJs-!%ShBt>oIIZX7VG=`)4Fx*-c3kI$Vfk()|vHrvdYVh!;s!_`r5T?-=!?dM4{~K`5QHE+_;;t_RK_Zl@6q*pX$uX zJgSPaqs5lO^(zKALe^Rh!WSCYW*gdF*e5455!{GVos>nHlnrp#dLJBh%PqHjAGXw2 zQ&Lijv(BDy73J)}nh-(P%nK^Z3sRJEF7t#dH8rJ}vZ>=5Fpm7ukACz$@c3uL*HN37 zeN5T&!Y#hb03XKiVy>Re_BRHdQCx2qAJhV zImQhy!JSWExFrL;7lZS1?byFOKU7gRbx;>|0^^mOGh@N~c>rT8D$Gqmp4(72@51mU z2G`^n(YCrH{0%AG=Dxx027jh(>Yy&_1U}qhzwmG5AOHBr+i*>Y&(BSFl@}oIKY1Zm zF?q+$XV#pvgFD|<;eoG;o$^G&QrYG{e&^YC@HXX92X#>=bpzcO{my@b_>nhrbIx_x z3uDC=XSg22)y%UW5%em2Q~p-_AlZ%x{xfopeuRC7lQMiwUDQe4z&G4F6Cv@0r{s_3V)*H2<={#-<_NQqk}rBn;3}YB5*jb zkw^JwcsD4luq6riKzI=F%Nst#v3(1a|H`K%k2S9!2YYU)X^9wXLK>O^uOyk}+2mKXV={@-6O#H3m;k#Rcdl z24Vp|&->o!ef##kW-lr1t1U%-F6bX9S|5Mo=#W>%f0}h-nf%_Fsh^=8{ry81j$=pI zYw%O9baNTpJOkj>T!R5!)LjY;!~%SyJ$~acxW{^z+w)!Y4Y_5&Z!Ug*_-^ULK6!YN zaD3voy#t-T@afWbOdl>em7BzeXZTj!&p!0aP z1F-;`=Q%Rw^y$-|Rh8$1OTzrQzy%q`mv3qwciT3<2L6w?_BR8U_Lpl;Tp;|zESziY zdq(ama3z>?GUwDv^!+oAh4q)n6}-^LJSLUp1&UY(;k$wRE>spu&T9JVwO*rN&k=Lq zCdSQ!p10{&EIJ3`|v_#k*l#hO0b#yj`aW2A5BgXbJUX`ZacRg+pkLwUXERS&Eh+z&vl@1 zjN}ZEFG7F19qZdr24xw(gO+b$gl=LW769`+{zih2I{iYGy|1Zar@Jrx)Fv09J;x6| z%-7|3qyM=A=WJZe+sGyNw%k*=&oi$gw|TAE8U7c!e)D^BPneJ14o-#UEm%H%%ePP6 z#6T>-=XoC<0ZzhyU${`&(o}W8jfp;8og0z7zV(pJ^<2OOf^Ojy*u%i-TM7(%E_toh z-y!E8{Fr6n<&!f?M84j z=Yfm(S2e#+{(w2RAZq-7D z_V4Q7fb%MI0G-qgKXe$e7+fCp$c&7PryA;O+godpS4Vu)mVcW5Rw>Na7>VN>?`zos zstEVlvOMzZm>2<2->di&jsK7Hy%qDgN(ztFy-_nH zx2)U$=;^PeUs#HkPhHeW-NfJqbm_)O@Z(=>YN~H{)F+E?c_{y^)=B^5fR8ip^k-^s z^eo_d3tWn6xIeFkKUC)gyTp%)F&dvT}RQulCkXP`zzMBG$eT^-ZfTv zvkvn`PLt%da{ExV@3j*?z3j9VIDg)jau&~3I$xia@5;1iE-=fF>y`^0t<6;~>ZERi z;|uB$xIW&4oRvB4EhUl@P;gl}rzG!^_!dpBB-T4{Y{kH*y-EtVb-p3vSM|)L1dNhf z3}Z#U!S>b?%w1Csbpq2x*OcKfV!gFd1qB6KY2e<@T{3@+TGpJ-rHTT`Z@{}@{Xv!)WCgV4I7iBT9EaEoz)z;SjC)=>?6$}3A8ggDd%U7rA8zfn&MRb?Y zFEw6jySSz3kD@zD{86mwk6MUAf7Jab^hb?HX+hz^ZfZNWX(Tgs61I8a9b9{3A1C{I z>mTYJ#iR(rZ)J^c+B`XRw+s1mZP7JZaOd3?{&Y_Dn-wi!XNh2$8;OY1Qg?E z^8OA!T;re0jTuv(ANZg>XZ;k1YY3|RkUv`-n$ng-nHGtxAOVYtntci!C`9R zLL98#=Uy+loiZrv3e`$8;zr!#N!pG?H)|Byr%mm-E$yU@6`L#V2DT~dO6Q`sAn5MO z!oUjJ97LB@dv5c)A-{Ly)G@Z&9X31Xc`6F;nYR(6@8@;Bk@L1oHGE1`7 zVlA%+kiR?1zp;+b1lR@^LaxSZYQ$;!a}Jb>8B-qQk1Gle%k5H$BMxQfPPPHtML5H?gkb1hseD2V&QtJ+>rko!H`;kN)fl zKh<2lAGWb**jKoIRWR%tE0MDwF_~yv4j+-v$B!cq`2(;m_=wDl?at5x-LQYGqYdPu zg8pYxd{m(C3#t*eBiftNu^uMZ@j0}60rc)u4dszy!=-Ms?sH|qCiU^W{}G!h_qA3Z zR4vsBXd7kOu62x^S>uknVH-*m`!r$)x!#`G(SheeVJKonA3$vP2Jq$QsZSS8RE1fv z6;(E~G(0*(RbfaG(+jth*T^YByH|Bl# z7G6Yb_oJ$;AR6|et*Qw+SaiIUw*NK2UTEyXE0;VXex4(-?@8>6vFD1t8RHTgH0K** z#oQG_j-wGVVPbCv4#oy*{UNZeL}Ts1ht#=K8}K`9(K_96Mw=zJVKdewq@9g+Nyc;g zA%4CHzNAFhy%;w{n=NIF+%m|QvC&Q~w&phUacDa;FhI7nr4HET7*EIb3RWUsine=g z&(*O}w2OuZOcOts#2(=@ajabO5Uz2E@w~7TaceKBld#K)&6qMR+pdfiIH1qbQXYC< zY__xmie9Y!g0V2?#4@Z8SzoqYozDtGp7lwx28D^sYK6VlQF}m&+g68tpDg$t;(1{I z!*<@80GR_~Wk0b$Qx9-(y(dd|vbO1BAHHL0MO&_OOy5_F*zsbNJwFODh>VYURbo@Q zpEgE!4Lc#P6FwL}@zA!|ToEPfVHo{dzP5Q%XKO`hvZFFQprz7Umj-&JFY0XAJOX{6 z)%H@c^}*Iva|mls{8i#_xGoptB5D8PIx8$^Qa)DEp|4@h1RZn2V~nwG9J|aB=ENOm zgmJc2h6S`$ZhnY1SoyxnGnOY>*26uKut03MI+g|YDcF5VF&8nRuL_O>J7>e+<%2d6 z;zLZsdQr4fQa9sm;^0FRJ0<3$4eJ?*T^V{1?*sjtAL2F8QN5*2Y^5^Sv~L1GVlJv| zJFw>Fi-@0nMB;uTgI_@`;nVOXz=qb4fSC22@N>;m8xhMEwqY{xJcKxcS-3_%DS6rB zW8P&f3}g-1OtpPh*4b#IZr%f6YusewjyO)P1wy-MdkyS(jYm{%*&bP|mTO;@7t$WO z6ZS~jAdv?hIjg&ykHKEM7(SQ3B7XpU>&392H73ChwO`^!Bz^>PX?vwzQ*=Qmb@Q*Y zA?EKpY9cyl^E0t4?llQu6YP>TH1q^p3wszUWI9GrR zaR{Ath_iv+llHZC+5xRt5{^mhi0GhBtzG{|+s}+{U3=PGwY`;gP1-nFAKv}0taZTk z4%RJy1lMN7d@%1jZH6ZHLu{`&W(0#}+l2jKd${pF10O{J5(&x~2oMwd}*b zIv>212OjYf#{SrHArAQsys=iS4{S!rBM z4UzxeRUcj3340;!dW?ObeU7=qX|JRGtc$S#4X~fJ97Q>VZJmo<+t#?o5ZluBNZgj~ z5i)xlV(vA1zNl7RG5-jDe74q28AhhID@x1-x$&^mxow{wHapSJcoCTs;m&7M<`?N4 z{GFoXm45DO zj=Zg>dB^s?rd^#9%K*I+8^HX}Sl^`As{3HWZYB7W{KNSjGbbFrw0}Lhx#ka4ZI8Q+dkmw<>H-N z>=3e@v%#kt-woA=`*_VqUL-%f$2s}FPJ6M?&jhZ10nf*KRr&dC$n!A~ zypief0luef$Xg@te?>ul)ZbVXdKcayoI$If!F$fL@=mqU? zSa&TB&)V|Ln+tA(gU{&YSo>l&6tHrK29PhII$9>WRh@(TC1^c7W%L+j{)SM3E{n@s|jnjG$v#I zj^f$lu-yBq@vgvl+pTiy%bhe-9akiiA;Wqa{?k_#ta=Qh_+$0!Oy>TUCx!j*=$3E=) EziMnn*8l(j delta 67116 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 z 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 Q2ErUl@73Q`zBLW}KZNEjKmY&$ From 5643feabc3eed93178837a0f23d627aba47d19b8 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Fri, 29 Jul 2016 10:51:31 +0900 Subject: [PATCH 62/74] Load 200 DPI resources on Linux --- electron.gyp | 4 ++++ script/create-dist.py | 4 ++++ vendor/brightray | 2 +- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/electron.gyp b/electron.gyp index 83872a416f89..45a0bebc99de 100644 --- a/electron.gyp +++ b/electron.gyp @@ -191,7 +191,11 @@ '<@(copied_libraries)', '<(libchromiumcontent_dir)/locales', '<(libchromiumcontent_dir)/icudtl.dat', + '<(libchromiumcontent_dir)/blink_image_resources_200_percent.pak', + '<(libchromiumcontent_dir)/content_resources_200_percent.pak', '<(libchromiumcontent_dir)/content_shell.pak', + '<(libchromiumcontent_dir)/ui_resources_200_percent.pak', + '<(libchromiumcontent_dir)/views_resources_200_percent.pak', '<(libchromiumcontent_dir)/natives_blob.bin', '<(libchromiumcontent_dir)/snapshot_blob.bin', ], diff --git a/script/create-dist.py b/script/create-dist.py index ff2f22f4090e..9d37c03108e9 100755 --- a/script/create-dist.py +++ b/script/create-dist.py @@ -52,6 +52,10 @@ TARGET_BINARIES = { 'icudtl.dat', 'libffmpeg.so', 'libnode.so', + 'blink_image_resources_200_percent.pak', + 'content_resources_200_percent.pak', + 'ui_resources_200_percent.pak', + 'views_resources_200_percent.pak', 'natives_blob.bin', 'snapshot_blob.bin', ], diff --git a/vendor/brightray b/vendor/brightray index 689e41c3318a..ba150e636790 160000 --- a/vendor/brightray +++ b/vendor/brightray @@ -1 +1 @@ -Subproject commit 689e41c3318a592d1ffd6fe3b4255e981a668f49 +Subproject commit ba150e6367904e14d102f60aa7e66601e4e2a8ab From e428d5db4f994901a4afe6b809fc2624eacd9ec5 Mon Sep 17 00:00:00 2001 From: Milan Burda Date: Fri, 29 Jul 2016 11:53:01 +0200 Subject: [PATCH 63/74] docs: fix argument types --- docs/api/browser-window.md | 5 ++++- docs/api/web-contents.md | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/api/browser-window.md b/docs/api/browser-window.md index e53ec013de6f..d0dc45a39680 100644 --- a/docs/api/browser-window.md +++ b/docs/api/browser-window.md @@ -627,7 +627,7 @@ Returns a boolean, whether the window is in fullscreen mode. #### `win.setAspectRatio(aspectRatio[, extraSize])` _macOS_ -* `aspectRatio` The aspect ratio to maintain for some portion of the +* `aspectRatio` Float - The aspect ratio to maintain for some portion of the content view. * `extraSize` Object (optional) - The extra size not to be included while maintaining the aspect ratio. @@ -820,6 +820,9 @@ window. #### `win.setSheetOffset(offsetY[, offsetX])` _macOS_ +* `offsetY` Float +* `offsetX` Float (optional) + Changes the attachment point for sheets on macOS. By default, sheets are attached just below the window frame, but you may want to display them beneath a HTML-rendered toolbar. For example: diff --git a/docs/api/web-contents.md b/docs/api/web-contents.md index 39556f5bbc95..c1ad9912c4c3 100644 --- a/docs/api/web-contents.md +++ b/docs/api/web-contents.md @@ -918,7 +918,7 @@ app.on('ready', () => { (screenPosition == mobile) (default: `{x: 0, y: 0}`) * `x` Integer - Set the x axis offset from top left corner * `y` Integer - Set the y axis offset from top left corner -* `deviceScaleFactor` Integer - Set the device scale factor (if zero defaults to +* `deviceScaleFactor` Float - Set the device scale factor (if zero defaults to original device scale factor) (default: `0`) * `viewSize` Object - Set the emulated view size (empty means no override) * `width` Integer - Set the emulated view width From ab69ae07b70180547e0b9ef38e66254facaf118e Mon Sep 17 00:00:00 2001 From: Felix Rieseberg Date: Thu, 28 Jul 2016 12:54:03 -0700 Subject: [PATCH 64/74] :memo: Add debugging instructions for macOS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I already wrote up debugging instructions for Windows, but never got around to writing them for macOS - until now! This adds a very basic introduction to LLDB from the command line, which should empower people to figure out what’s happening inside Electron when they call an Electron method from JavaScript. --- docs/README.md | 3 +- .../debugging-instructions-macos.md | 125 ++++++++++++++++++ 2 files changed, 127 insertions(+), 1 deletion(-) create mode 100644 docs/development/debugging-instructions-macos.md diff --git a/docs/README.md b/docs/README.md index 9f4973759453..aec23bf47ec1 100644 --- a/docs/README.md +++ b/docs/README.md @@ -93,5 +93,6 @@ an issue: * [Build Instructions (macOS)](development/build-instructions-osx.md) * [Build Instructions (Windows)](development/build-instructions-windows.md) * [Build Instructions (Linux)](development/build-instructions-linux.md) -* [Debug Instructions (Windows)](development/debug-instructions-windows.md) +* [Debug Instructions (macOS)](development/debug-instructions-windows.md) +* [Debug Instructions (Windows)](development/debug-instructions-macos.md) * [Setting Up Symbol Server in debugger](development/setting-up-symbol-server.md) diff --git a/docs/development/debugging-instructions-macos.md b/docs/development/debugging-instructions-macos.md new file mode 100644 index 000000000000..e119db466a19 --- /dev/null +++ b/docs/development/debugging-instructions-macos.md @@ -0,0 +1,125 @@ +# Debugging on macOS + +If you experience crashes or issues in Electron that you believe are not caused +by your JavaScript application, but instead by Electron itself, debugging can +be a little bit tricky, especially for developers not used to native/C++ +debugging. However, using lldb, and the Electron source code, it is fairly easy +to enable step-through debugging with breakpoints inside Electron's source code. + +## Requirements + +* **A debug build of Electron**: The easiest way is usually building it + yourself, using the tools and prerequisites listed in the + [build instructions for macOS](build-instructions-osx.md). While you can + easily attach to and debug Electron as you can download it directly, you will + find that it is heavily optimized, making debugging substantially more + difficult: The debugger will not be able to show you the content of all + variables and the execution path can seem strange because of inlining, + tail calls, and other compiler optimizations. + +* **Xcode**: In addition to Xcode, also install the Xcode command line tools. + They include LLDB, the default debugger in Xcode on Mac OS X. It supports + debugging C, Objective-C and C++ on the desktop and iOS devices and simulator. + +## Attaching to and Debugging Electron + +To start a debugging session, open up Terminal and start `lldb`, passing a debug +build of Electron as a parameter. + +```bash +$ lldb ./out/D/Electron.app +(lldb) target create "./out/D/Electron.app" +Current executable set to './out/D/Electron.app' (x86_64). +``` + +### Setting Breakpoints + +LLDB is a powerful tool and supports multiple strategies for code inspection. For +this basic introduction, let's assume that you're calling a command from JavaScript +that isn't behaving correctly - so you'd like to break on that command's C++ +counterpart inside the Electron source. + +Relevant code files can be found in `./atom/` as well as in Brightray, found in +`./vendor/brightray/browser` and `./vendor/brightray/common`. If you're hardcore, +you can also debug Chromium directly, which is obviously found in `chromium_src`. + +Let's assume that you want to debug `app.setName()`, which is defined in `browser.cc` +as `Browser::SetName()`. Set the breakpoint using the `breakpoint` command, specifying +file and line to break on: + +```bash +(lldb) breakpoint set --file browser.cc --line 117 +Breakpoint 1: where = Electron Framework`atom::Browser::SetName(std::__1::basic_string, std::__1::allocator > const&) + 20 at browser.cc:118, address = 0x000000000015fdb4 +``` + +Then, start Electron: + +```bash +(lldb) run +``` + +The app will immediately be paused, since Electron sets the app's name on launch: + +```bash +(lldb) run +Process 25244 launched: '/Users/fr/Code/electron/out/D/Electron.app/Contents/MacOS/Electron' (x86_64) +Process 25244 stopped +* thread #1: tid = 0x839a4c, 0x0000000100162db4 Electron Framework`atom::Browser::SetName(this=0x0000000108b14f20, name="Electron") + 20 at browser.cc:118, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1 + frame #0: 0x0000000100162db4 Electron Framework`atom::Browser::SetName(this=0x0000000108b14f20, name="Electron") + 20 at browser.cc:118 + 115 } + 116 + 117 void Browser::SetName(const std::string& name) { +-> 118 name_override_ = name; + 119 } + 120 + 121 int Browser::GetBadgeCount() { +(lldb) +``` + +To show the arguments and local variables for the current frame, run `frame variable` (or `fr v`), +which will show you that the app is currently setting the name to "Electron". + +```bash +(lldb) frame variable +(atom::Browser *) this = 0x0000000108b14f20 +(const string &) name = "Electron": { + [...] +} +``` + +To do a source level single step in the currently selected thread, execute `step` (or `s`). +This would take you into into `name_override_.empty()`. To proceed and do a step over, +run `next` (or `n`). + +```bash +(lldb) step +Process 25244 stopped +* thread #1: tid = 0x839a4c, 0x0000000100162dcc Electron Framework`atom::Browser::SetName(this=0x0000000108b14f20, name="Electron") + 44 at browser.cc:119, queue = 'com.apple.main-thread', stop reason = step in + frame #0: 0x0000000100162dcc Electron Framework`atom::Browser::SetName(this=0x0000000108b14f20, name="Electron") + 44 at browser.cc:119 + 116 + 117 void Browser::SetName(const std::string& name) { + 118 name_override_ = name; +-> 119 } + 120 + 121 int Browser::GetBadgeCount() { + 122 return badge_count_; +``` + +To finish debugging at this point, run `process continue`. You can also continue until a certain +line is hit in this thread (`thread until 100`). This command will run the thread in the current +frame till it reaches line 100 in this frame or stops if it leaves the current frame. + +Now, if you open up Electron's developer tools and call `setName`, you will once again hit the +breakpoint. + +### Further Reading +LLDB is a powerful tool with a great documentation. To learn more about it, consider +Apple's debugging documentation, for instance the [LLDB Command Structure Reference][lldb-command-structure] +or the introduction to [Using LLDB as a Standalone Debugger][lldb-standalone]. + +You can also check out LLDB's fantastic [manual and tutorial][lldb-tutorial], which +will explain more complex debugging scenarios. + +[lldb-command-structure]: https://developer.apple.com/library/mac/documentation/IDEs/Conceptual/gdb_to_lldb_transition_guide/document/lldb-basics.html#//apple_ref/doc/uid/TP40012917-CH2-SW2 +[lldb-standalone]: https://developer.apple.com/library/mac/documentation/IDEs/Conceptual/gdb_to_lldb_transition_guide/document/lldb-terminal-workflow-tutorial.html +[lldb-tutorial]: http://lldb.llvm.org/tutorial.html From af80b9a7dfc146604b0a3144d274b44af2974540 Mon Sep 17 00:00:00 2001 From: Heilig Benedek Date: Sun, 31 Jul 2016 05:10:14 +0200 Subject: [PATCH 65/74] Fixes not being able to send UTF8 characters anymore --- atom/common/keyboard_util.cc | 3 ++- atom/common/native_mate_converters/blink_converter.cc | 7 ++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/atom/common/keyboard_util.cc b/atom/common/keyboard_util.cc index d860bc0c46c7..c8e9628f3bcc 100644 --- a/atom/common/keyboard_util.cc +++ b/atom/common/keyboard_util.cc @@ -159,7 +159,8 @@ ui::KeyboardCode KeyboardCodeFromKeyIdentifier(const std::string& s, return ui::VKEY_UNKNOWN; } } else { - LOG(WARNING) << "Invalid accelerator token: " << str; + if (str.size() > 2) + LOG(WARNING) << "Invalid accelerator token: " << str; return ui::VKEY_UNKNOWN; } } diff --git a/atom/common/native_mate_converters/blink_converter.cc b/atom/common/native_mate_converters/blink_converter.cc index 4e37836c5a55..c74715be86f1 100644 --- a/atom/common/native_mate_converters/blink_converter.cc +++ b/atom/common/native_mate_converters/blink_converter.cc @@ -176,9 +176,10 @@ bool Converter::FromV8( out->setKeyIdentifierFromWindowsKeyCode(); if ((out->type == blink::WebInputEvent::Char || out->type == blink::WebInputEvent::RawKeyDown) && - str.size() == 1) { - out->text[0] = str[0]; - out->unmodifiedText[0] = str[0]; + str.size() <= 2) { + base::string16 code = base::UTF8ToUTF16(str); + out->text[0] = code[0]; + out->unmodifiedText[0] = code[0]; } return true; } From 21962be60e181ca1a49eba5e8a09ef56046988f3 Mon Sep 17 00:00:00 2001 From: Heilig Benedek Date: Sun, 31 Jul 2016 05:11:18 +0200 Subject: [PATCH 66/74] Adds option to get raw data from NativeImage --- atom/common/api/atom_api_native_image.cc | 10 ++++++++++ atom/common/api/atom_api_native_image.h | 1 + 2 files changed, 11 insertions(+) diff --git a/atom/common/api/atom_api_native_image.cc b/atom/common/api/atom_api_native_image.cc index 10a1c181b381..63501aba2abb 100644 --- a/atom/common/api/atom_api_native_image.cc +++ b/atom/common/api/atom_api_native_image.cc @@ -25,6 +25,7 @@ #include "ui/gfx/geometry/size.h" #include "ui/gfx/image/image_skia.h" #include "ui/gfx/image/image_util.h" +#include "third_party/skia/include/core/SkPixelRef.h" #if defined(OS_WIN) #include "atom/common/asar/archive.h" @@ -219,6 +220,14 @@ v8::Local NativeImage::ToPNG(v8::Isolate* isolate) { static_cast(png->size())).ToLocalChecked(); } +v8::Local NativeImage::ToRawBuffer(v8::Isolate* isolate) { + const SkBitmap* bitmap = image_.ToSkBitmap(); + SkPixelRef* ref = bitmap->pixelRef(); + return node::Buffer::Copy(isolate, + reinterpret_cast(ref->pixels()), + bitmap->getSafeSize()).ToLocalChecked(); +} + v8::Local NativeImage::ToJPEG(v8::Isolate* isolate, int quality) { std::vector output; gfx::JPEG1xEncodedDataFromImage(image_, quality, &output); @@ -350,6 +359,7 @@ void NativeImage::BuildPrototype( mate::ObjectTemplateBuilder(isolate, prototype) .SetMethod("toPNG", &NativeImage::ToPNG) .SetMethod("toJPEG", &NativeImage::ToJPEG) + .SetMethod("toBUFFER", &NativeImage::ToRawBuffer) .SetMethod("getNativeHandle", &NativeImage::GetNativeHandle) .SetMethod("toDataURL", &NativeImage::ToDataURL) .SetMethod("isEmpty", &NativeImage::IsEmpty) diff --git a/atom/common/api/atom_api_native_image.h b/atom/common/api/atom_api_native_image.h index de4db4c0f382..d3ddad17db12 100644 --- a/atom/common/api/atom_api_native_image.h +++ b/atom/common/api/atom_api_native_image.h @@ -70,6 +70,7 @@ class NativeImage : public mate::Wrappable { private: v8::Local ToPNG(v8::Isolate* isolate); v8::Local ToJPEG(v8::Isolate* isolate, int quality); + v8::Local ToRawBuffer(v8::Isolate* isolate); v8::Local GetNativeHandle( v8::Isolate* isolate, mate::Arguments* args); From f3b723c9faec28abc3b14653ce1b662456295134 Mon Sep 17 00:00:00 2001 From: Heilig Benedek Date: Sun, 31 Jul 2016 05:11:56 +0200 Subject: [PATCH 67/74] Send some more data with the cursor-changed event --- atom/browser/api/atom_api_web_contents.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/atom/browser/api/atom_api_web_contents.cc b/atom/browser/api/atom_api_web_contents.cc index 43ef9d35d5af..5596cacaf1bd 100644 --- a/atom/browser/api/atom_api_web_contents.cc +++ b/atom/browser/api/atom_api_web_contents.cc @@ -1299,6 +1299,8 @@ void WebContents::OnCursorChange(const content::WebCursor& cursor) { if (cursor.IsCustom()) { Emit("cursor-changed", CursorTypeToString(info), gfx::Image::CreateFrom1xBitmap(info.custom_image), + gfx::Rect(info.custom_image.width(), info.custom_image.height()), + info.hotspot, info.image_scale_factor); } else { Emit("cursor-changed", CursorTypeToString(info)); From a77a83761f1d0480760444d884324fb5efadbab4 Mon Sep 17 00:00:00 2001 From: sairoutine Date: Sun, 31 Jul 2016 17:05:13 +0900 Subject: [PATCH 68/74] :memo: Japanese: add testing-on-headless-ci.md [ci skip] --- docs-translations/jp/README.md | 2 +- .../jp/tutorial/testing-on-headless-ci.md | 43 +++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 docs-translations/jp/tutorial/testing-on-headless-ci.md diff --git a/docs-translations/jp/README.md b/docs-translations/jp/README.md index 734bc8274c52..36d99f518849 100644 --- a/docs-translations/jp/README.md +++ b/docs-translations/jp/README.md @@ -25,7 +25,7 @@ _リンクになっていないリストは未翻訳のものです。_ * [DevTools エクステンション](tutorial/devtools-extension.md) * [Pepper Flashプラグインを使用する](tutorial/using-pepper-flash-plugin.md) * [Widevine CDMプラグインを使用する](tutorial/using-widevine-cdm-plugin.md) -* Testing on Headless CI Systems (Travis, Jenkins) (tutorial/testing-on-headless-ci.md) +* [継続的インテグレーションシステムによるテスト(Travis, Jenkins)](tutorial/testing-on-headless-ci.md) # チュートリアル diff --git a/docs-translations/jp/tutorial/testing-on-headless-ci.md b/docs-translations/jp/tutorial/testing-on-headless-ci.md new file mode 100644 index 000000000000..7fba4fef7d18 --- /dev/null +++ b/docs-translations/jp/tutorial/testing-on-headless-ci.md @@ -0,0 +1,43 @@ +# 継続的インテグレーションシステムによるテスト(Travis CI, Jenkins) + +Electron は Chromium を元にしているので、実行にはディスプレイドライバーが必要です。Chromium がディスプレイドライバーを見つけられないと、Electron は起動に失敗してテストの実行ができません。Travis や Circle、 Jenkins などの継続的インテグレーションシステムで Electron アプリをテストするにはちょっとした設定が必要です。端的に言うと仮想ディスプレイドライバーを使用します。 + +## 仮想ディスプレイサーバーの設定 + +まず [Xvfb](https://en.wikipedia.org/wiki/Xvfb) をインストールします(リンク先は英語)。Xvfb は X Window System のプロトコルを実装した仮想フレームバッファです。描画系の操作をスクリーン表示無しにメモリ内で行ってくれます。 + +その後 xvfb の仮想スクリーンを作成して、環境変数 `$DISPLAY` に作成した仮想スクリーンを指定します。Electron の Chromium は自動で`$DISPLAY`を見るので、アプリ側の設定は必要ありません。この手順は Paul Betts 氏が公開しているツール [xvfb-maybe](https://github.com/paulcbetts/xvfb-maybe) によって自動化されています。テスト実行コマンドの前に `xvfb-maybe` を追加するだけで、現在のシステムが必要とする xvfb の設定を自動で行ってくれます。xvfb の設定が必要ない Windows や macOS では何もしません。 + +``` +## Windows や macOS では以下のコマンドは electron-mocha をただ起動するだけです。 +## Linux でかつ GUI 環境でない場合、以下のコマンドは +## xvfb-run electron-mocha ./test/*.js と同等になります。 +xvfb-maybe electron-mocha ./test/*.js +``` + +### Travis CI + +Travis では `.travis.yml` を以下のようにするといいでしょう。 + +```yml +addons: + apt: + packages: + - xvfb + +install: + - export DISPLAY=':99.0' + - Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 & +``` + +### Jenkins + +Jenkins では [Xvfb plugin](https://wiki.jenkins-ci.org/display/JENKINS/Xvfb+Plugin) という便利なプラグインが利用可能です(リンク先は英語)。 + +### Circle CI + +Circle CI では、なんと既に xvfb 及び `$DISPLAY` が設定されているので、[何の設定も必要ありません](https://circleci.com/docs/environment#browsers)(リンク先は英語)。 + +### AppVeyor + +AppVeyor は Selenium や Chromium、Electron などをサポートしている Windows 上で動くので、特に設定は必要ありません。 From 1d5a12613550bfc734e78a1fdccd695f129dcb4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AD=99=E8=8C=82=E8=83=A4?= Date: Sun, 31 Jul 2016 16:11:03 +0800 Subject: [PATCH 69/74] Typo fix --- docs-translations/zh-CN/api/browser-window.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs-translations/zh-CN/api/browser-window.md b/docs-translations/zh-CN/api/browser-window.md index f2fe697c729c..d28ffc9f45ea 100644 --- a/docs-translations/zh-CN/api/browser-window.md +++ b/docs-translations/zh-CN/api/browser-window.md @@ -47,7 +47,7 @@ win.show(); * `maximizable` Boolean - 窗口是否可以最大化. 在 Linux 上无效. 默认为 `true`. * `closable` Boolean - 窗口是否可以关闭. 在 Linux 上无效. 默认为 `true`. * `alwaysOnTop` Boolean - 窗口是否总是显示在其他窗口之前. 在 Linux 上无效. 默认为 `false`. - * `fullscreen` Boolean - 窗口是否可以全屏幕. 当明确设置值为When `false` ,全屏化按钮将会隐藏,在 macOS 将禁用. 默认 `false`. + * `fullscreen` Boolean - 窗口是否可以全屏幕. 当明确设置值为 `false` ,全屏化按钮将会隐藏,在 macOS 将禁用. 默认 `false`. * `fullscreenable` Boolean - 在 macOS 上,全屏化按钮是否可用,默认为 `true`. * `skipTaskbar` Boolean - 是否在任务栏中显示窗口. 默认是`false`. * `kiosk` Boolean - kiosk 方式. 默认为 `false`. From db671702df352bfc68c3b0bc2fcb9999c9bdfcea Mon Sep 17 00:00:00 2001 From: liusi Date: Sun, 31 Jul 2016 21:49:54 +0800 Subject: [PATCH 70/74] fix maximize restore issue caused by restore window size change --- atom/browser/native_window_views.h | 10 ++++++++++ atom/browser/native_window_views_win.cc | 12 +++++++++--- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/atom/browser/native_window_views.h b/atom/browser/native_window_views.h index 7ad5e8ec292b..85aa3a15f46f 100644 --- a/atom/browser/native_window_views.h +++ b/atom/browser/native_window_views.h @@ -214,6 +214,16 @@ class NativeWindowViews : public NativeWindow, // fullscreen), so we restore it correctly. gfx::Rect last_normal_bounds_; + // last_normal_bounds_ may or may not require update on WM_MOVE. When a window + // is maximized, it is moved (WM_MOVE) to maximum size first and then sized + // (WM_SIZE). In this case, last_normal_bounds_ should not update. We keep + // last_normal_bounds_candidate_ as a candidate which will become valid + // last_normal_bounds_ if the moves are consecutive with no WM_SIZE event in + // between. + gfx::Rect last_normal_bounds_candidate_; + + bool consecutive_moves_; + // In charge of running taskbar related APIs. TaskbarHost taskbar_host_; diff --git a/atom/browser/native_window_views_win.cc b/atom/browser/native_window_views_win.cc index 2da0293f341a..92697b5642bf 100644 --- a/atom/browser/native_window_views_win.cc +++ b/atom/browser/native_window_views_win.cc @@ -111,18 +111,24 @@ bool NativeWindowViews::PreHandleMSG( return taskbar_host_.HandleThumbarButtonEvent(LOWORD(w_param)); return false; - case WM_SIZE: + case WM_SIZE: { + consecutive_moves_ = false; // Handle window state change. HandleSizeEvent(w_param, l_param); return false; - + } case WM_MOVING: { if (!movable_) ::GetWindowRect(GetAcceleratedWidget(), (LPRECT)l_param); return false; } case WM_MOVE: { - last_normal_bounds_ = GetBounds(); + if (last_window_state_ == ui::SHOW_STATE_NORMAL) { + if(consecutive_moves_) + last_normal_bounds_ = last_normal_bounds_candidate_; + last_normal_bounds_candidate_ = GetBounds(); + consecutive_moves_ = true; + } return false; } default: From bc7c5c567c37d23fdeb4481e6c4959532b106df9 Mon Sep 17 00:00:00 2001 From: Heilig Benedek Date: Mon, 1 Aug 2016 02:13:31 +0200 Subject: [PATCH 71/74] fix toBUFFER naming issue and cursor-changed parameter order --- atom/browser/api/atom_api_web_contents.cc | 6 +++--- atom/common/api/atom_api_native_image.cc | 4 ++-- atom/common/api/atom_api_native_image.h | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/atom/browser/api/atom_api_web_contents.cc b/atom/browser/api/atom_api_web_contents.cc index 5596cacaf1bd..967dc04a0b60 100644 --- a/atom/browser/api/atom_api_web_contents.cc +++ b/atom/browser/api/atom_api_web_contents.cc @@ -1299,9 +1299,9 @@ void WebContents::OnCursorChange(const content::WebCursor& cursor) { if (cursor.IsCustom()) { Emit("cursor-changed", CursorTypeToString(info), gfx::Image::CreateFrom1xBitmap(info.custom_image), - gfx::Rect(info.custom_image.width(), info.custom_image.height()), - info.hotspot, - info.image_scale_factor); + info.image_scale_factor, + gfx::Size(info.custom_image.width(), info.custom_image.height()), + info.hotspot); } else { Emit("cursor-changed", CursorTypeToString(info)); } diff --git a/atom/common/api/atom_api_native_image.cc b/atom/common/api/atom_api_native_image.cc index 63501aba2abb..187ec7f48e06 100644 --- a/atom/common/api/atom_api_native_image.cc +++ b/atom/common/api/atom_api_native_image.cc @@ -220,7 +220,7 @@ v8::Local NativeImage::ToPNG(v8::Isolate* isolate) { static_cast(png->size())).ToLocalChecked(); } -v8::Local NativeImage::ToRawBuffer(v8::Isolate* isolate) { +v8::Local NativeImage::ToBitmap(v8::Isolate* isolate) { const SkBitmap* bitmap = image_.ToSkBitmap(); SkPixelRef* ref = bitmap->pixelRef(); return node::Buffer::Copy(isolate, @@ -359,7 +359,7 @@ void NativeImage::BuildPrototype( mate::ObjectTemplateBuilder(isolate, prototype) .SetMethod("toPNG", &NativeImage::ToPNG) .SetMethod("toJPEG", &NativeImage::ToJPEG) - .SetMethod("toBUFFER", &NativeImage::ToRawBuffer) + .SetMethod("toBitmap", &NativeImage::ToBitmap) .SetMethod("getNativeHandle", &NativeImage::GetNativeHandle) .SetMethod("toDataURL", &NativeImage::ToDataURL) .SetMethod("isEmpty", &NativeImage::IsEmpty) diff --git a/atom/common/api/atom_api_native_image.h b/atom/common/api/atom_api_native_image.h index d3ddad17db12..6702b33574fc 100644 --- a/atom/common/api/atom_api_native_image.h +++ b/atom/common/api/atom_api_native_image.h @@ -70,7 +70,7 @@ class NativeImage : public mate::Wrappable { private: v8::Local ToPNG(v8::Isolate* isolate); v8::Local ToJPEG(v8::Isolate* isolate, int quality); - v8::Local ToRawBuffer(v8::Isolate* isolate); + v8::Local ToBitmap(v8::Isolate* isolate); v8::Local GetNativeHandle( v8::Isolate* isolate, mate::Arguments* args); From 7a471e1ab7ef89be01d7cb6ec7d897c4f1b2ed34 Mon Sep 17 00:00:00 2001 From: Heilig Benedek Date: Mon, 1 Aug 2016 02:14:11 +0200 Subject: [PATCH 72/74] fix UTF character sending as suggested by @zcbenz --- .../native_mate_converters/blink_converter.cc | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/atom/common/native_mate_converters/blink_converter.cc b/atom/common/native_mate_converters/blink_converter.cc index c74715be86f1..7a72219416e0 100644 --- a/atom/common/native_mate_converters/blink_converter.cc +++ b/atom/common/native_mate_converters/blink_converter.cc @@ -4,6 +4,7 @@ #include "atom/common/native_mate_converters/blink_converter.h" +#include #include #include @@ -175,11 +176,18 @@ bool Converter::FromV8( out->modifiers |= blink::WebInputEvent::ShiftKey; out->setKeyIdentifierFromWindowsKeyCode(); if ((out->type == blink::WebInputEvent::Char || - out->type == blink::WebInputEvent::RawKeyDown) && - str.size() <= 2) { - base::string16 code = base::UTF8ToUTF16(str); - out->text[0] = code[0]; - out->unmodifiedText[0] = code[0]; + out->type == blink::WebInputEvent::RawKeyDown)) { + // Make sure to not read beyond the buffer in case some bad code doesn't + // NULL-terminate it (this is called from plugins). + size_t text_length_cap = blink::WebKeyboardEvent::textLengthCap; + base::string16 text16 = base::UTF8ToUTF16(str); + + memset(out->text, 0, text_length_cap); + memset(out->unmodifiedText, 0, text_length_cap); + for (size_t i = 0; i < std::min(text_length_cap, text16.size()); ++i) { + out->text[i] = text16[i]; + out->unmodifiedText[i] = text16[i]; + } } return true; } From 2b05be64b48d0fcd3aba140ddb1ac936b56de7a5 Mon Sep 17 00:00:00 2001 From: Heilig Benedek Date: Mon, 1 Aug 2016 02:14:45 +0200 Subject: [PATCH 73/74] add documentation for changes --- docs/api/native-image.md | 4 ++++ docs/api/web-contents.md | 12 +++++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/docs/api/native-image.md b/docs/api/native-image.md index 000c6d303cd2..eed0b5cbaa18 100644 --- a/docs/api/native-image.md +++ b/docs/api/native-image.md @@ -152,6 +152,10 @@ Returns a [Buffer][buffer] that contains the image's `PNG` encoded data. Returns a [Buffer][buffer] that contains the image's `JPEG` encoded data. +#### `image.toBitmap()` + +Returns a [Buffer][buffer] that contains the image's raw pixel data. + #### `image.toDataURL()` Returns the data URL of the image. diff --git a/docs/api/web-contents.md b/docs/api/web-contents.md index c1ad9912c4c3..be94b1b7553f 100644 --- a/docs/api/web-contents.md +++ b/docs/api/web-contents.md @@ -334,7 +334,13 @@ Returns: * `event` Event * `type` String * `image` NativeImage (optional) -* `scale` Float (optional) +* `scale` Float (optional) - scaling factor for the custom cursor +* `size` Object (optional) - the size of the `image` + * `width` Integer + * `height` Integer +* `hotspot` Object (optional) - coordinates of the custom cursor's hotspot + * `x` Integer - x coordinate + * `y` Integer - y coordinate Emitted when the cursor's type changes. The `type` parameter can be `default`, `crosshair`, `pointer`, `text`, `wait`, `help`, `e-resize`, `n-resize`, @@ -346,8 +352,8 @@ Emitted when the cursor's type changes. The `type` parameter can be `default`, `not-allowed`, `zoom-in`, `zoom-out`, `grab`, `grabbing`, `custom`. If the `type` parameter is `custom`, the `image` parameter will hold the custom -cursor image in a `NativeImage`, and the `scale` will hold scaling information -for the image. +cursor image in a `NativeImage`, and `scale`, `size` and `hotspot` will hold +additional information about the custom cursor. #### Event: 'context-menu' From 01ebc772280f352dbbd7ecdd686c150528d023bc Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Mon, 1 Aug 2016 09:58:33 +0900 Subject: [PATCH 74/74] Fix styling issues --- atom/browser/native_window_views.h | 8 ++++---- atom/browser/native_window_views_win.cc | 3 +-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/atom/browser/native_window_views.h b/atom/browser/native_window_views.h index 85aa3a15f46f..71504ceac355 100644 --- a/atom/browser/native_window_views.h +++ b/atom/browser/native_window_views.h @@ -214,10 +214,10 @@ class NativeWindowViews : public NativeWindow, // fullscreen), so we restore it correctly. gfx::Rect last_normal_bounds_; - // last_normal_bounds_ may or may not require update on WM_MOVE. When a window - // is maximized, it is moved (WM_MOVE) to maximum size first and then sized - // (WM_SIZE). In this case, last_normal_bounds_ should not update. We keep - // last_normal_bounds_candidate_ as a candidate which will become valid + // last_normal_bounds_ may or may not require update on WM_MOVE. When a + // window is maximized, it is moved (WM_MOVE) to maximum size first and then + // sized (WM_SIZE). In this case, last_normal_bounds_ should not update. We + // keep last_normal_bounds_candidate_ as a candidate which will become valid // last_normal_bounds_ if the moves are consecutive with no WM_SIZE event in // between. gfx::Rect last_normal_bounds_candidate_; diff --git a/atom/browser/native_window_views_win.cc b/atom/browser/native_window_views_win.cc index 92697b5642bf..bb6d9858f045 100644 --- a/atom/browser/native_window_views_win.cc +++ b/atom/browser/native_window_views_win.cc @@ -110,7 +110,6 @@ bool NativeWindowViews::PreHandleMSG( if (HIWORD(w_param) == THBN_CLICKED) return taskbar_host_.HandleThumbarButtonEvent(LOWORD(w_param)); return false; - case WM_SIZE: { consecutive_moves_ = false; // Handle window state change. @@ -124,7 +123,7 @@ bool NativeWindowViews::PreHandleMSG( } case WM_MOVE: { if (last_window_state_ == ui::SHOW_STATE_NORMAL) { - if(consecutive_moves_) + if (consecutive_moves_) last_normal_bounds_ = last_normal_bounds_candidate_; last_normal_bounds_candidate_ = GetBounds(); consecutive_moves_ = true;