Compare commits

...

7 Commits

Author SHA1 Message Date
Electron Bot
a7599a0838 Bump v11.0.0-nightly.20200612 2020-06-12 08:32:36 -07:00
Cheng Zhao
d5f642938d fix: handle non client pointer events from pen on Win10 (#24042) 2020-06-12 09:26:16 -05:00
Jeremy Rose
7274467f73 refactor: tsify remote (#24034) 2020-06-11 11:36:03 -07:00
Jeremy Rose
7c7ea141f0 test: extract defer helper (#24019) 2020-06-11 11:35:50 -07:00
Samuel Attard
c66282a460 Revert "fix: do not use CONTEXT_MENU flag for tray menu (#23843)" (#24076)
This reverts commit b809d104d0.
2020-06-11 11:27:57 -07:00
Jeremy Rose
78fe545d18 refactor: remove renderer-side refcount in remote (#24054) 2020-06-11 10:22:28 -07:00
Paul Frazee
81d09bea44 fix: correctly handle nexttick scheduling in stream reads (#24022) 2020-06-11 09:55:59 -07:00
24 changed files with 326 additions and 253 deletions

View File

@@ -1 +1 @@
11.0.0-nightly.20200611
11.0.0-nightly.20200612

View File

@@ -149,7 +149,7 @@ auto_filenames = {
"lib/renderer/api/crash-reporter.ts",
"lib/renderer/api/desktop-capturer.ts",
"lib/renderer/api/ipc-renderer.ts",
"lib/renderer/api/remote.js",
"lib/renderer/api/remote.ts",
"lib/renderer/api/web-frame.ts",
"lib/renderer/inspector.ts",
"lib/renderer/ipc-renderer-internal-utils.ts",
@@ -283,7 +283,7 @@ auto_filenames = {
"lib/renderer/api/exports/electron.ts",
"lib/renderer/api/ipc-renderer.ts",
"lib/renderer/api/module-list.ts",
"lib/renderer/api/remote.js",
"lib/renderer/api/remote.ts",
"lib/renderer/api/web-frame.ts",
"lib/renderer/init.ts",
"lib/renderer/inspector.ts",
@@ -326,7 +326,7 @@ auto_filenames = {
"lib/renderer/api/exports/electron.ts",
"lib/renderer/api/ipc-renderer.ts",
"lib/renderer/api/module-list.ts",
"lib/renderer/api/remote.js",
"lib/renderer/api/remote.ts",
"lib/renderer/api/web-frame.ts",
"lib/renderer/ipc-renderer-internal-utils.ts",
"lib/renderer/ipc-renderer-internal.ts",

View File

@@ -51,19 +51,11 @@ class ObjectsRegistry {
// Dereference an object according to its ID.
// Note that an object may be double-freed (cleared when page is reloaded, and
// then garbage collected in old page).
// rendererSideRefCount is the ref count that the renderer process reported
// at time of GC if this is different to the number of references we sent to
// the given owner then a GC occurred between a ref being sent and the value
// being pulled out of the weak map.
// In this case we decrement out ref count and do not delete the stored
// object
// For more details on why we do renderer side ref counting see
// https://github.com/electron/electron/pull/17464
remove (webContents: WebContents, contextId: string, id: number, rendererSideRefCount: number) {
remove (webContents: WebContents, contextId: string, id: number) {
const ownerKey = getOwnerKey(webContents, contextId);
const owner = this.owners[ownerKey];
if (owner && owner.has(id)) {
const newRefCount = owner.get(id)! - rendererSideRefCount;
const newRefCount = owner.get(id)! - 1;
// Only completely remove if the number of references GCed in the
// renderer is the same as the number of references we sent them

View File

@@ -1,11 +1,9 @@
'use strict';
import * as electron from 'electron';
import { EventEmitter } from 'events';
import objectsRegistry from './objects-registry';
import { ipcMainInternal } from '../ipc-main-internal';
import { isPromise, isSerializableObject, deserialize, serialize } from '@electron/internal/common/type-utils';
import { Size } from 'electron/main';
import { isPromise, isSerializableObject, deserialize, serialize } from '../../common/type-utils';
import type { MetaTypeFromRenderer, ObjectMember, MetaType, ObjProtoDescriptor } from '../../common/remote/types';
const v8Util = process.electronBinding('v8_util');
const eventBinding = process.electronBinding('event');
@@ -27,14 +25,6 @@ const FUNCTION_PROPERTIES = [
// id => Function
const rendererFunctions = v8Util.createDoubleIDWeakMap<(...args: any[]) => void>();
type ObjectMember = {
name: string,
value?: any,
enumerable?: boolean,
writable?: boolean,
type?: 'method' | 'get'
}
// Return the description of object's members:
const getObjectMembers = function (object: any): ObjectMember[] {
let names = Object.getOwnPropertyNames(object);
@@ -60,11 +50,6 @@ const getObjectMembers = function (object: any): ObjectMember[] {
});
};
type ObjProtoDescriptor = {
members: ObjectMember[],
proto: ObjProtoDescriptor
} | null
// Return the description of object's prototype.
const getObjectPrototype = function (object: any): ObjProtoDescriptor {
const proto = Object.getPrototypeOf(object);
@@ -75,76 +60,42 @@ const getObjectPrototype = function (object: any): ObjProtoDescriptor {
};
};
type MetaType = {
type: 'number',
value: number
} | {
type: 'boolean',
value: boolean
} | {
type: 'string',
value: string
} | {
type: 'bigint',
value: bigint
} | {
type: 'symbol',
value: symbol
} | {
type: 'undefined',
value: undefined
} | {
type: 'object' | 'function',
name: string,
members: ObjectMember[],
proto: ObjProtoDescriptor,
id: number,
} | {
type: 'value',
value: any,
} | {
type: 'buffer',
value: Uint8Array,
} | {
type: 'array',
members: MetaType[]
} | {
type: 'error',
value: Error,
members: ObjectMember[]
} | {
type: 'promise',
then: MetaType
} | {
type: 'nativeimage'
value: electron.NativeImage
}
// Convert a real value into meta data.
const valueToMeta = function (sender: electron.WebContents, contextId: string, value: any, optimizeSimpleObject = false): MetaType {
// Determine the type of value.
let type: MetaType['type'] = typeof value;
if (type === 'object') {
// Recognize certain types of objects.
if (value instanceof Buffer) {
type = 'buffer';
} else if (value instanceof NativeImage) {
type = 'nativeimage';
} else if (Array.isArray(value)) {
type = 'array';
} else if (value instanceof Error) {
type = 'error';
} else if (isSerializableObject(value)) {
let type: MetaType['type'];
switch (typeof value) {
case 'object':
// Recognize certain types of objects.
if (value instanceof Buffer) {
type = 'buffer';
} else if (value instanceof NativeImage) {
type = 'nativeimage';
} else if (Array.isArray(value)) {
type = 'array';
} else if (value instanceof Error) {
type = 'error';
} else if (isSerializableObject(value)) {
type = 'value';
} else if (isPromise(value)) {
type = 'promise';
} else if (hasProp.call(value, 'callee') && value.length != null) {
// Treat the arguments object as array.
type = 'array';
} else if (optimizeSimpleObject && v8Util.getHiddenValue(value, 'simple')) {
// Treat simple objects as value.
type = 'value';
} else {
type = 'object';
}
break;
case 'function':
type = 'function';
break;
default:
type = 'value';
} else if (isPromise(value)) {
type = 'promise';
} else if (hasProp.call(value, 'callee') && value.length != null) {
// Treat the arguments object as array.
type = 'array';
} else if (optimizeSimpleObject && v8Util.getHiddenValue(value, 'simple')) {
// Treat simple objects as value.
type = 'value';
}
break;
}
// Fill the meta object according to value's type.
@@ -224,46 +175,6 @@ const removeRemoteListenersAndLogWarning = (sender: any, callIntoRenderer: (...a
console.warn(message);
};
type MetaTypeFromRenderer = {
type: 'value',
value: any
} | {
type: 'remote-object',
id: number
} | {
type: 'array',
value: MetaTypeFromRenderer[]
} | {
type: 'buffer',
value: Uint8Array
} | {
type: 'promise',
then: MetaTypeFromRenderer
} | {
type: 'object',
name: string,
members: {
name: string,
value: MetaTypeFromRenderer
}[]
} | {
type: 'function-with-return-value',
value: MetaTypeFromRenderer
} | {
type: 'function',
id: number,
location: string,
length: number
} | {
type: 'nativeimage',
value: {
size: Size,
buffer: Buffer,
scaleFactor: number,
dataURL: string
}[]
}
const fakeConstructor = (constructor: Function, name: string) =>
new Proxy(Object, {
get (target, prop, receiver) {
@@ -349,7 +260,7 @@ const isRemoteModuleEnabledImpl = function (contents: electron.WebContents) {
const isRemoteModuleEnabledCache = new WeakMap();
const isRemoteModuleEnabled = function (contents: electron.WebContents) {
export const isRemoteModuleEnabled = function (contents: electron.WebContents) {
if (!isRemoteModuleEnabledCache.has(contents)) {
isRemoteModuleEnabledCache.set(contents, isRemoteModuleEnabledImpl(contents));
}
@@ -557,14 +468,10 @@ handleRemoteCommand('ELECTRON_BROWSER_MEMBER_GET', function (event, contextId, i
return valueToMeta(event.sender, contextId, obj[name]);
});
handleRemoteCommand('ELECTRON_BROWSER_DEREFERENCE', function (event, contextId, id, rendererSideRefCount) {
objectsRegistry.remove(event.sender, contextId, id, rendererSideRefCount);
handleRemoteCommand('ELECTRON_BROWSER_DEREFERENCE', function (event, contextId, id) {
objectsRegistry.remove(event.sender, contextId, id);
});
handleRemoteCommand('ELECTRON_BROWSER_CONTEXT_RELEASE', (event, contextId) => {
objectsRegistry.clear(event.sender, contextId);
});
module.exports = {
isRemoteModuleEnabled
};

View File

@@ -0,0 +1,85 @@
import type { Size } from 'electron/main';
import type { NativeImage } from 'electron/common';
export type ObjectMember = {
name: string,
value?: any,
enumerable?: boolean,
writable?: boolean,
type?: 'method' | 'get'
}
export type ObjProtoDescriptor = {
members: ObjectMember[],
proto: ObjProtoDescriptor
} | null
export type MetaType = {
type: 'object' | 'function',
name: string,
members: ObjectMember[],
proto: ObjProtoDescriptor,
id: number,
} | {
type: 'value',
value: any,
} | {
type: 'buffer',
value: Uint8Array,
} | {
type: 'array',
members: MetaType[]
} | {
type: 'error',
value: Error,
members: ObjectMember[]
} | {
type: 'exception',
value: MetaType,
} | {
type: 'promise',
then: MetaType
} | {
type: 'nativeimage'
value: NativeImage
}
export type MetaTypeFromRenderer = {
type: 'value',
value: any
} | {
type: 'remote-object',
id: number
} | {
type: 'array',
value: MetaTypeFromRenderer[]
} | {
type: 'buffer',
value: Uint8Array
} | {
type: 'promise',
then: MetaTypeFromRenderer
} | {
type: 'object',
name: string,
members: {
name: string,
value: MetaTypeFromRenderer
}[]
} | {
type: 'function-with-return-value',
value: MetaTypeFromRenderer
} | {
type: 'function',
id: number,
location: string,
length: number
} | {
type: 'nativeimage',
value: {
size: Size,
buffer: Buffer,
scaleFactor: number,
dataURL: string
}[]
}

View File

@@ -1,18 +1,20 @@
'use strict';
import { CallbacksRegistry } from '../remote/callbacks-registry';
import { isPromise, isSerializableObject, serialize, deserialize } from '../../common/type-utils';
import { MetaTypeFromRenderer, ObjectMember, ObjProtoDescriptor, MetaType } from '../../common/remote/types';
import { ipcRendererInternal } from '../ipc-renderer-internal';
import type { BrowserWindow, WebContents } from 'electron/main';
import { browserModuleNames } from '@electron/internal/browser/api/module-names';
import { commonModuleList } from '@electron/internal/common/api/module-list';
const v8Util = process.electronBinding('v8_util');
const { hasSwitch } = process.electronBinding('command_line');
const { NativeImage } = process.electronBinding('native_image');
const { CallbacksRegistry } = require('@electron/internal/renderer/remote/callbacks-registry');
const { isPromise, isSerializableObject, serialize, deserialize } = require('@electron/internal/common/type-utils');
const { ipcRendererInternal } = require('@electron/internal/renderer/ipc-renderer-internal');
const callbacksRegistry = new CallbacksRegistry();
const remoteObjectCache = v8Util.createIDWeakMap();
// An unique ID that can represent current context.
const contextId = v8Util.getHiddenValue(global, 'contextId');
const contextId = v8Util.getHiddenValue<string>(global, 'contextId');
// Notify the main process when current context is going to be released.
// Note that when the renderer process is destroyed, the message may not be
@@ -26,8 +28,8 @@ process.on('exit', () => {
const IS_REMOTE_PROXY = Symbol('is-remote-proxy');
// Convert the arguments object into an array of meta data.
function wrapArgs (args, visited = new Set()) {
const valueToMeta = (value) => {
function wrapArgs (args: any[], visited = new Set()): any {
const valueToMeta = (value: any): any => {
// Check for circular reference.
if (visited.has(value)) {
return {
@@ -60,7 +62,7 @@ function wrapArgs (args, visited = new Set()) {
if (isPromise(value)) {
return {
type: 'promise',
then: valueToMeta(function (onFulfilled, onRejected) {
then: valueToMeta(function (onFulfilled: Function, onRejected: Function) {
value.then(onFulfilled, onRejected);
})
};
@@ -71,7 +73,7 @@ function wrapArgs (args, visited = new Set()) {
};
}
const meta = {
const meta: MetaTypeFromRenderer = {
type: 'object',
name: value.constructor ? value.constructor.name : '',
members: []
@@ -110,15 +112,15 @@ function wrapArgs (args, visited = new Set()) {
// Populate object's members from descriptors.
// The |ref| will be kept referenced by |members|.
// This matches |getObjectMemebers| in rpc-server.
function setObjectMembers (ref, object, metaId, members) {
function setObjectMembers (ref: any, object: any, metaId: number, members: ObjectMember[]) {
if (!Array.isArray(members)) return;
for (const member of members) {
if (Object.prototype.hasOwnProperty.call(object, member.name)) continue;
const descriptor = { enumerable: member.enumerable };
const descriptor: PropertyDescriptor = { enumerable: member.enumerable };
if (member.type === 'method') {
const remoteMemberFunction = function (...args) {
const remoteMemberFunction = function (this: any, ...args: any[]) {
let command;
if (this && this.constructor === remoteMemberFunction) {
command = 'ELECTRON_BROWSER_MEMBER_CONSTRUCTOR';
@@ -165,7 +167,7 @@ function setObjectMembers (ref, object, metaId, members) {
// Populate object's prototype from descriptor.
// This matches |getObjectPrototype| in rpc-server.
function setObjectPrototype (ref, object, metaId, descriptor) {
function setObjectPrototype (ref: any, object: any, metaId: number, descriptor: ObjProtoDescriptor) {
if (descriptor === null) return;
const proto = {};
setObjectMembers(ref, proto, metaId, descriptor.members);
@@ -174,7 +176,7 @@ function setObjectPrototype (ref, object, metaId, descriptor) {
}
// Wrap function in Proxy for accessing remote properties
function proxyFunctionProperties (remoteMemberFunction, metaId, name) {
function proxyFunctionProperties (remoteMemberFunction: Function, metaId: number, name: string) {
let loaded = false;
// Lazily load function properties
@@ -186,13 +188,13 @@ function proxyFunctionProperties (remoteMemberFunction, metaId, name) {
setObjectMembers(remoteMemberFunction, remoteMemberFunction, meta.id, meta.members);
};
return new Proxy(remoteMemberFunction, {
set: (target, property, value, receiver) => {
return new Proxy(remoteMemberFunction as any, {
set: (target, property, value) => {
if (property !== 'ref') loadRemoteProperties();
target[property] = value;
return true;
},
get: (target, property, receiver) => {
get: (target, property) => {
if (property === IS_REMOTE_PROXY) return true;
if (!Object.prototype.hasOwnProperty.call(target, property)) loadRemoteProperties();
const value = target[property];
@@ -215,29 +217,30 @@ function proxyFunctionProperties (remoteMemberFunction, metaId, name) {
}
// Convert meta data from browser into real value.
function metaToValue (meta) {
const types = {
value: () => meta.value,
array: () => meta.members.map((member) => metaToValue(member)),
nativeimage: () => deserialize(meta.value),
buffer: () => Buffer.from(meta.value.buffer, meta.value.byteOffset, meta.value.byteLength),
promise: () => Promise.resolve({ then: metaToValue(meta.then) }),
error: () => metaToError(meta),
exception: () => { throw metaToError(meta.value); }
};
if (Object.prototype.hasOwnProperty.call(types, meta.type)) {
return types[meta.type]();
function metaToValue (meta: MetaType): any {
if (meta.type === 'value') {
return meta.value;
} else if (meta.type === 'array') {
return meta.members.map((member) => metaToValue(member));
} else if (meta.type === 'nativeimage') {
return deserialize(meta.value);
} else if (meta.type === 'buffer') {
return Buffer.from(meta.value.buffer, meta.value.byteOffset, meta.value.byteLength);
} else if (meta.type === 'promise') {
return Promise.resolve({ then: metaToValue(meta.then) });
} else if (meta.type === 'error') {
return metaToError(meta);
} else if (meta.type === 'exception') {
if (meta.value.type === 'error') { throw metaToError(meta.value); } else { throw new Error(`Unexpected value type in exception: ${meta.value.type}`); }
} else {
let ret;
if (remoteObjectCache.has(meta.id)) {
v8Util.addRemoteObjectRef(contextId, meta.id);
if ('id' in meta && remoteObjectCache.has(meta.id)) {
return remoteObjectCache.get(meta.id);
}
// A shadow class to represent the remote function object.
if (meta.type === 'function') {
const remoteFunction = function (...args) {
const remoteFunction = function (this: any, ...args: any[]) {
let command;
if (this && this.constructor === remoteFunction) {
command = 'ELECTRON_BROWSER_CONSTRUCTOR';
@@ -254,20 +257,19 @@ function metaToValue (meta) {
setObjectMembers(ret, ret, meta.id, meta.members);
setObjectPrototype(ret, ret, meta.id, meta.proto);
if (ret.constructor && ret.constructor[IS_REMOTE_PROXY]) {
if (ret.constructor && (ret.constructor as any)[IS_REMOTE_PROXY]) {
Object.defineProperty(ret.constructor, 'name', { value: meta.name });
}
// Track delegate obj's lifetime & tell browser to clean up when object is GCed.
v8Util.setRemoteObjectFreer(ret, contextId, meta.id);
v8Util.setHiddenValue(ret, 'electronId', meta.id);
v8Util.addRemoteObjectRef(contextId, meta.id);
remoteObjectCache.set(meta.id, ret);
return ret;
}
}
function metaToError (meta) {
function metaToError (meta: { type: 'error', value: any, members: ObjectMember[] }) {
const obj = meta.value;
for (const { name, value } of meta.members) {
obj[name] = metaToValue(value);
@@ -275,7 +277,7 @@ function metaToError (meta) {
return obj;
}
function handleMessage (channel, handler) {
function handleMessage (channel: string, handler: Function) {
ipcRendererInternal.on(channel, (event, passedContextId, id, ...args) => {
if (passedContextId === contextId) {
handler(id, ...args);
@@ -288,8 +290,8 @@ function handleMessage (channel, handler) {
const enableStacks = hasSwitch('enable-api-filtering-logging');
function getCurrentStack () {
const target = {};
function getCurrentStack (): string | undefined {
const target = { stack: undefined as string | undefined };
if (enableStacks) {
Error.captureStackTrace(target, getCurrentStack);
}
@@ -297,47 +299,47 @@ function getCurrentStack () {
}
// Browser calls a callback in renderer.
handleMessage('ELECTRON_RENDERER_CALLBACK', (id, args) => {
handleMessage('ELECTRON_RENDERER_CALLBACK', (id: number, args: any) => {
callbacksRegistry.apply(id, metaToValue(args));
});
// A callback in browser is released.
handleMessage('ELECTRON_RENDERER_RELEASE_CALLBACK', (id) => {
handleMessage('ELECTRON_RENDERER_RELEASE_CALLBACK', (id: number) => {
callbacksRegistry.remove(id);
});
exports.require = (module) => {
exports.require = (module: string) => {
const command = 'ELECTRON_BROWSER_REQUIRE';
const meta = ipcRendererInternal.sendSync(command, contextId, module, getCurrentStack());
return metaToValue(meta);
};
// Alias to remote.require('electron').xxx.
exports.getBuiltin = (module) => {
export function getBuiltin (module: string) {
const command = 'ELECTRON_BROWSER_GET_BUILTIN';
const meta = ipcRendererInternal.sendSync(command, contextId, module, getCurrentStack());
return metaToValue(meta);
};
}
exports.getCurrentWindow = () => {
export function getCurrentWindow (): BrowserWindow {
const command = 'ELECTRON_BROWSER_CURRENT_WINDOW';
const meta = ipcRendererInternal.sendSync(command, contextId, getCurrentStack());
return metaToValue(meta);
};
}
// Get current WebContents object.
exports.getCurrentWebContents = () => {
export function getCurrentWebContents (): WebContents {
const command = 'ELECTRON_BROWSER_CURRENT_WEB_CONTENTS';
const meta = ipcRendererInternal.sendSync(command, contextId, getCurrentStack());
return metaToValue(meta);
};
}
// Get a global object in browser.
exports.getGlobal = (name) => {
export function getGlobal<T = any> (name: string): T {
const command = 'ELECTRON_BROWSER_GLOBAL';
const meta = ipcRendererInternal.sendSync(command, contextId, name, getCurrentStack());
return metaToValue(meta);
};
}
// Get the process object in browser.
Object.defineProperty(exports, 'process', {
@@ -345,22 +347,19 @@ Object.defineProperty(exports, 'process', {
});
// Create a function that will return the specified value when called in browser.
exports.createFunctionWithReturnValue = (returnValue) => {
export function createFunctionWithReturnValue<T> (returnValue: T): () => T {
const func = () => returnValue;
v8Util.setHiddenValue(func, 'returnValue', true);
return func;
};
}
const addBuiltinProperty = (name) => {
const addBuiltinProperty = (name: string) => {
Object.defineProperty(exports, name, {
get: () => exports.getBuiltin(name)
});
};
const { commonModuleList } = require('@electron/internal/common/api/module-list');
const { browserModuleNames } = require('@electron/internal/browser/api/module-names');
const browserModules = commonModuleList.concat(browserModuleNames.map(name => ({ name })));
const browserModules = commonModuleList.concat(browserModuleNames.map(name => ({ name, loader: () => {} })));
// And add a helper receiver for each one.
browserModules

View File

@@ -1,6 +1,6 @@
{
"name": "electron",
"version": "11.0.0-nightly.20200611",
"version": "11.0.0-nightly.20200612",
"repository": "https://github.com/electron/electron",
"description": "Build cross platform desktop apps with JavaScript, HTML, and CSS",
"devDependencies": {

View File

@@ -100,3 +100,4 @@ disable_dcheck_that_fails_with_software_compositing.patch
fix_swap_global_proxies_before_initializing_the_windows_proxies.patch
fix_default_to_ntlm_v2_in_network_service.patch
feat_add_streaming-protocol_registry_to_multibuffer_data_source.patch
fix_handling_non_client_pointer_events_from_pen_on_windows_10.patch

View File

@@ -0,0 +1,28 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Cheng Zhao <zcbenz@gmail.com>
Date: Thu, 4 Oct 2018 14:57:02 -0700
Subject: fix: handle non client pointer events from pen on Windows 10
Refs: https://github.com/electron/electron/issues/21440
Refs: https://chromium-review.googlesource.com/c/chromium/src/+/2237535
diff --git a/ui/views/win/hwnd_message_handler.cc b/ui/views/win/hwnd_message_handler.cc
index faa47e9ef7f388dc0bfa5cd3efb961e6339ce390..274b9c247b8f970a6cf2be7b149d1885163551d9 100644
--- a/ui/views/win/hwnd_message_handler.cc
+++ b/ui/views/win/hwnd_message_handler.cc
@@ -1988,6 +1988,15 @@ LRESULT HWNDMessageHandler::OnPointerEvent(UINT message,
return -1;
}
+ // Pen may also send non client pointer messages, treat them as normal
+ // touch events so they can be properly handled.
+ if (pointer_type == PT_PEN &&
+ (message == WM_NCPOINTERDOWN ||
+ message == WM_NCPOINTERUP ||
+ message == WM_NCPOINTERUPDATE)) {
+ pointer_type = PT_TOUCH;
+ }
+
switch (pointer_type) {
case PT_PEN:
return HandlePointerEventTypePen(message, w_param, l_param);

View File

@@ -1228,12 +1228,10 @@ void WebContents::MessageHost(const std::string& channel,
#if BUILDFLAG(ENABLE_REMOTE_MODULE)
void WebContents::DereferenceRemoteJSObject(const std::string& context_id,
int object_id,
int ref_count) {
int object_id) {
base::ListValue args;
args.Append(context_id);
args.Append(object_id);
args.Append(ref_count);
EmitWithSender("-ipc-message", bindings_.dispatch_context(), InvokeCallback(),
/* internal */ true, "ELECTRON_BROWSER_DEREFERENCE",
std::move(args));

View File

@@ -599,8 +599,7 @@ class WebContents : public gin_helper::TrackableObject<WebContents>,
blink::CloneableMessage arguments) override;
#if BUILDFLAG(ENABLE_REMOTE_MODULE)
void DereferenceRemoteJSObject(const std::string& context_id,
int object_id,
int ref_count) override;
int object_id) override;
#endif
void UpdateDraggableRegions(
std::vector<mojom::DraggableRegionPtr> regions) override;

View File

@@ -68,6 +68,8 @@ void NodeStreamLoader::Start(network::mojom::URLResponseHeadPtr head) {
void NodeStreamLoader::NotifyReadable() {
if (!readable_)
ReadMore();
else if (is_reading_)
has_read_waiting_ = true;
readable_ = true;
}
@@ -101,8 +103,16 @@ void NodeStreamLoader::ReadMore() {
// If there is no buffer read, wait until |readable| is emitted again.
v8::Local<v8::Value> buffer;
if (!ret.ToLocal(&buffer) || !node::Buffer::HasInstance(buffer)) {
readable_ = false;
is_reading_ = false;
// If 'readable' was called after 'read()', try again
if (has_read_waiting_) {
has_read_waiting_ = false;
ReadMore();
return;
}
readable_ = false;
if (ended_) {
NotifyComplete(result_);
}

View File

@@ -87,6 +87,11 @@ class NodeStreamLoader : public network::mojom::URLLoader {
// flag.
bool readable_ = false;
// It's possible for reads to be queued using nextTick() during read()
// which will cause 'readable' to emit during ReadMore, so we track if
// that occurred in a flag.
bool has_read_waiting_ = false;
// Store the V8 callbacks to unsubscribe them later.
std::map<std::string, v8::Global<v8::Value>> handlers_;

View File

@@ -50,8 +50,8 @@ END
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 11,0,0,20200611
PRODUCTVERSION 11,0,0,20200611
FILEVERSION 11,0,0,20200612
PRODUCTVERSION 11,0,0,20200612
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L

View File

@@ -18,6 +18,7 @@
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/image/image.h"
#include "ui/views/controls/menu/menu_runner.h"
#include "ui/views/widget/widget.h"
namespace {
@@ -48,7 +49,11 @@ NotifyIcon::NotifyIcon(NotifyIconHost* host,
HWND window,
UINT message,
GUID guid)
: host_(host), icon_id_(id), window_(window), message_id_(message) {
: host_(host),
icon_id_(id),
window_(window),
message_id_(message),
weak_factory_(this) {
guid_ = guid;
is_using_guid_ = guid != GUID_DEFAULT;
NOTIFYICONDATA icon_data;
@@ -208,10 +213,26 @@ void NotifyIcon::PopUpContextMenu(const gfx::Point& pos,
if (pos.IsOrigin())
rect.set_origin(display::Screen::GetScreen()->GetCursorScreenPoint());
menu_runner_.reset(
new views::MenuRunner(menu_model != nullptr ? menu_model : menu_model_,
views::MenuRunner::HAS_MNEMONICS));
menu_runner_->RunMenuAt(nullptr, nullptr, rect,
// Create a widget for the menu, otherwise we get no keyboard events, which
// is required for accessibility.
widget_.reset(new views::Widget());
views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
params.ownership =
views::Widget::InitParams::Ownership::WIDGET_OWNS_NATIVE_WIDGET;
params.bounds = gfx::Rect(0, 0, 0, 0);
params.force_software_compositing = true;
params.z_order = ui::ZOrderLevel::kFloatingUIElement;
widget_->Init(std::move(params));
widget_->Show();
widget_->Activate();
menu_runner_.reset(new views::MenuRunner(
menu_model != nullptr ? menu_model : menu_model_,
views::MenuRunner::CONTEXT_MENU | views::MenuRunner::HAS_MNEMONICS,
base::BindRepeating(&NotifyIcon::OnContextMenuClosed,
weak_factory_.GetWeakPtr())));
menu_runner_->RunMenuAt(widget_.get(), NULL, rect,
views::MenuAnchorPosition::kTopLeft,
ui::MENU_SOURCE_MOUSE);
}
@@ -252,4 +273,8 @@ void NotifyIcon::InitIconData(NOTIFYICONDATA* icon_data) {
}
}
void NotifyIcon::OnContextMenuClosed() {
widget_->Close();
}
} // namespace electron

View File

@@ -14,6 +14,7 @@
#include "base/compiler_specific.h"
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "base/win/scoped_gdi_object.h"
#include "shell/browser/ui/tray_icon.h"
#include "shell/browser/ui/win/notify_icon_host.h"
@@ -24,7 +25,8 @@ class Point;
namespace views {
class MenuRunner;
}
class Widget;
} // namespace views
namespace electron {
@@ -73,6 +75,7 @@ class NotifyIcon : public TrayIcon {
private:
void InitIconData(NOTIFYICONDATA* icon_data);
void OnContextMenuClosed();
// The tray that owns us. Weak.
NotifyIconHost* host_;
@@ -101,6 +104,12 @@ class NotifyIcon : public TrayIcon {
// Context menu associated with this icon (if any).
std::unique_ptr<views::MenuRunner> menu_runner_;
// Temporary widget for the context menu, needed for keyboard event capture.
std::unique_ptr<views::Widget> widget_;
// WeakPtrFactory for CloseClosure safety.
base::WeakPtrFactory<NotifyIcon> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(NotifyIcon);
};

View File

@@ -84,8 +84,7 @@ interface ElectronBrowser {
[EnableIf=enable_remote_module]
DereferenceRemoteJSObject(
string context_id,
int32 object_id,
int32 ref_count);
int32 object_id);
UpdateDraggableRegions(
array<DraggableRegion> regions);

View File

@@ -149,7 +149,6 @@ void Initialize(v8::Local<v8::Object> exports,
dict.SetMethod("setRemoteCallbackFreer",
&electron::RemoteCallbackFreer::BindTo);
dict.SetMethod("setRemoteObjectFreer", &electron::RemoteObjectFreer::BindTo);
dict.SetMethod("addRemoteObjectRef", &electron::RemoteObjectFreer::AddRef);
dict.SetMethod(
"createDoubleIDWeakMap",
&electron::api::KeyWeakMap<std::pair<std::string, int32_t>>::Create);

View File

@@ -35,14 +35,6 @@ void RemoteObjectFreer::BindTo(v8::Isolate* isolate,
new RemoteObjectFreer(isolate, target, context_id, object_id);
}
// static
void RemoteObjectFreer::AddRef(const std::string& context_id, int object_id) {
ref_mapper_[context_id][object_id]++;
}
// static
std::map<std::string, std::map<int, int>> RemoteObjectFreer::ref_mapper_;
RemoteObjectFreer::RemoteObjectFreer(v8::Isolate* isolate,
v8::Local<v8::Object> target,
const std::string& context_id,
@@ -65,25 +57,10 @@ void RemoteObjectFreer::RunDestructor() {
if (!render_frame)
return;
// Reset our local ref count in case we are in a GC race condition
// and will get more references in an inbound IPC message
int ref_count = 0;
const auto objects_it = ref_mapper_.find(context_id_);
if (objects_it != std::end(ref_mapper_)) {
auto& objects = objects_it->second;
const auto ref_it = objects.find(object_id_);
if (ref_it != std::end(objects)) {
ref_count = ref_it->second;
objects.erase(ref_it);
}
if (objects.empty())
ref_mapper_.erase(objects_it);
}
mojom::ElectronBrowserPtr electron_ptr;
render_frame->GetRemoteInterfaces()->GetInterface(
mojo::MakeRequest(&electron_ptr));
electron_ptr->DereferenceRemoteJSObject(context_id_, object_id_, ref_count);
electron_ptr->DereferenceRemoteJSObject(context_id_, object_id_);
}
} // namespace electron

View File

@@ -18,7 +18,6 @@ class RemoteObjectFreer : public ObjectLifeMonitor {
v8::Local<v8::Object> target,
const std::string& context_id,
int object_id);
static void AddRef(const std::string& context_id, int object_id);
protected:
RemoteObjectFreer(v8::Isolate* isolate,
@@ -29,9 +28,6 @@ class RemoteObjectFreer : public ObjectLifeMonitor {
void RunDestructor() override;
// { context_id => { object_id => ref_count }}
static std::map<std::string, std::map<int, int>> ref_mapper_;
private:
std::string context_id_;
int object_id_;

View File

@@ -7,6 +7,7 @@ import * as http from 'http';
import * as fs from 'fs';
import * as qs from 'querystring';
import * as stream from 'stream';
import { EventEmitter } from 'events';
import { closeWindow } from './window-helpers';
import { emittedOnce } from './events-helpers';
import { WebmGenerator } from './video-helpers';
@@ -410,6 +411,36 @@ describe('protocol module', () => {
const r = await ajax(protocolName + '://fake-host');
expect(r.data).to.have.lengthOf(1024 * 1024 * 2);
});
it('can handle next-tick scheduling during read calls', async () => {
const events = new EventEmitter();
function createStream () {
const buffers = [
Buffer.alloc(65536),
Buffer.alloc(65537),
Buffer.alloc(39156)
];
const e = new stream.Readable({ highWaterMark: 0 });
e.push(buffers.shift());
e._read = function () {
process.nextTick(() => this.push(buffers.shift() || null));
};
e.on('end', function () {
events.emit('end');
});
return e;
}
registerStreamProtocol(protocolName, (request, callback) => {
callback({
statusCode: 200,
headers: { 'Content-Type': 'text/plain' },
data: createStream()
});
});
const hasEndedPromise = emittedOnce(events, 'end');
ajax(protocolName + '://fake-host');
await hasEndedPromise;
});
});
describe('protocol.isProtocolRegistered', () => {

View File

@@ -60,6 +60,20 @@ app.whenReady().then(async () => {
}
const mocha = new Mocha(mochaOptions);
// The cleanup method is registered this way rather than through an
// `afterEach` at the top level so that it can run before other `afterEach`
// methods.
//
// The order of events is:
// 1. test completes,
// 2. `defer()`-ed methods run, in reverse order,
// 3. regular `afterEach` hooks run.
const { runCleanupFunctions } = require('./spec-helpers');
mocha.suite.on('suite', function attach (suite) {
suite.afterEach('cleanup', runCleanupFunctions);
suite.on('suite', attach);
});
if (!process.env.MOCHA_REPORTER) {
mocha.ui('bdd').reporter('tap');
}
@@ -110,8 +124,4 @@ app.whenReady().then(async () => {
chai.use(require('dirty-chai'));
const runner = mocha.run(cb);
const { runCleanupFunctions } = require('./spec-helpers');
runner.on('test end', () => {
runCleanupFunctions();
});
});

View File

@@ -19,7 +19,7 @@ export async function runCleanupFunctions () {
}
export function defer (f: CleanupFunction) {
cleanupFunctions.push(f);
cleanupFunctions.unshift(f);
}
class RemoteControlApp {

View File

@@ -44,6 +44,9 @@ declare namespace NodeJS {
weaklyTrackValue(value: any): void;
clearWeaklyTrackedValues(): void;
getWeaklyTrackedValues(): any[];
addRemoteObjectRef(contextId: string, id: number): void;
setRemoteCallbackFreer(fn: Function, contextId: string, id: number, sender: any): void
setRemoteObjectFreer(object: any, contextId: string, id: number): void
}
type DataPipe = {