Compare commits

...

9 Commits

Author SHA1 Message Date
Electron Bot
5be2183dd7 Bump v14.0.0-nightly.20210326 2021-03-26 07:32:17 -07:00
Saúl Ibarra Corretgé
2632564ccf feat: initialize field trials from command line arguments (#28305)
Fixes: #27877
2021-03-26 09:49:00 +09:00
Alexander Prinzhorn
521146f71e docs: add missing line in web-contents.md (#28376)
* Update web-contents.md

The text block was rendered as part of the `features` property, not the `handler`

* fix linting
2021-03-26 09:46:59 +09:00
Cheng Zhao
fb4e99e729 test: load minimal dict for spellchecker (#28386) 2021-03-25 10:41:11 -04:00
Electron Bot
77365e701f Bump v14.0.0-nightly.20210325 2021-03-25 07:34:28 -07:00
Shelley Vohr
1453a8e743 fix: disappearing thumbar after win.hide() (#28366)
* fix: disappearing thumbar after win.hide()

* Add descriptive comment
2021-03-25 04:02:47 -07:00
Samuel Maddock
b9b734c9c4 fix: export patches not retaining CRLF line endings (#28360)
When a patch targets a file using CRLF line endings, they need to be
retained in the patch file. Otherwise the patch will fail to apply
due to being unable to find surrounding lines with matching whitespace.
2021-03-25 14:49:53 +09:00
Samuel Attard
7918ddb026 perf: do not double-proxy methods being return over the contextBridge (#28285) 2021-03-24 11:43:02 -07:00
Calvin
89df6b98da fix: isolate Pepper plugins (#28332) 2021-03-24 11:11:26 -07:00
17 changed files with 146 additions and 46 deletions

View File

@@ -1 +1 @@
14.0.0-nightly.20210324
14.0.0-nightly.20210326

View File

@@ -75,6 +75,12 @@ This switch can not be used in `app.commandLine.appendSwitch` since it is parsed
earlier than user's app is loaded, but you can set the `ELECTRON_ENABLE_LOGGING`
environment variable to achieve the same effect.
## --force-fieldtrials=`trials`
Field trials to be forcefully enabled or disabled.
For example: `WebRTC-Audio-Red-For-Opus/Enabled/`
### --host-rules=`rules`
A comma-separated list of `rules` that control how hostnames are mapped.

View File

@@ -1131,6 +1131,7 @@ Ignore application menu shortcuts while this web contents is focused.
* `url` String - The _resolved_ version of the URL passed to `window.open()`. e.g. opening a window with `window.open('foo')` will yield something like `https://the-origin/the/current/path/foo`.
* `frameName` String - Name of the window provided in `window.open()`
* `features` String - Comma separated list of window features provided to `window.open()`.
Returns `{action: 'deny'} | {action: 'allow', overrideBrowserWindowOptions?: BrowserWindowConstructorOptions}` - `deny` cancels the creation of the new
window. `allow` will allow the new window to be created. Specifying `overrideBrowserWindowOptions` allows customization of the created window.
Returning an unrecognized value such as a null, undefined, or an object

View File

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

View File

@@ -47,7 +47,7 @@ def get_repo_root(path):
def am(repo, patch_data, threeway=False, directory=None, exclude=None,
committer_name=None, committer_email=None):
committer_name=None, committer_email=None, keep_cr=True):
args = []
if threeway:
args += ['--3way']
@@ -56,6 +56,10 @@ def am(repo, patch_data, threeway=False, directory=None, exclude=None,
if exclude is not None:
for path_pattern in exclude:
args += ['--exclude', path_pattern]
if keep_cr is True:
# Keep the CR of CRLF in case any patches target files with Windows line
# endings.
args += ['--keep-cr']
root_args = ['-C', repo]
if committer_name is not None:
@@ -230,7 +234,9 @@ def split_patches(patch_data):
"""Split a concatenated series of patches into N separate patches"""
patches = []
patch_start = re.compile('^From [0-9a-f]+ ')
for line in patch_data.splitlines():
# Keep line endings in case any patches target files with CRLF.
keep_line_endings = True
for line in patch_data.splitlines(keep_line_endings):
if patch_start.match(line):
patches.append([])
patches[-1].append(line)
@@ -246,13 +252,23 @@ def munge_subject_to_filename(subject):
def get_file_name(patch):
"""Return the name of the file to which the patch should be written"""
file_name = None
for line in patch:
if line.startswith('Patch-Filename: '):
return line[len('Patch-Filename: '):]
file_name = line[len('Patch-Filename: '):]
break
# If no patch-filename header, munge the subject.
for line in patch:
if line.startswith('Subject: '):
return munge_subject_to_filename(line[len('Subject: '):])
if not file_name:
for line in patch:
if line.startswith('Subject: '):
file_name = munge_subject_to_filename(line[len('Subject: '):])
break
return file_name.rstrip('\n')
def join_patch(patch):
"""Joins and formats patch contents"""
return ''.join(remove_patch_filename(patch)).rstrip('\n') + '\n'
def remove_patch_filename(patch):
@@ -294,10 +310,8 @@ def export_patches(repo, out_dir, patch_range=None, dry_run=False):
for patch in patches:
filename = get_file_name(patch)
filepath = posixpath.join(out_dir, filename)
existing_patch = io.open(filepath, 'r', encoding='utf-8').read()
formatted_patch = (
'\n'.join(remove_patch_filename(patch)).rstrip('\n') + '\n'
)
existing_patch = io.open(filepath, 'rb').read()
formatted_patch = join_patch(patch)
if formatted_patch != existing_patch:
patch_count += 1
if patch_count > 0:
@@ -322,12 +336,11 @@ def export_patches(repo, out_dir, patch_range=None, dry_run=False):
for patch in patches:
filename = get_file_name(patch)
file_path = posixpath.join(out_dir, filename)
formatted_patch = (
'\n'.join(remove_patch_filename(patch)).rstrip('\n') + '\n'
)
formatted_patch = join_patch(patch)
# Write in binary mode to retain mixed line endings on write.
with io.open(
file_path, 'w', newline='\n', encoding='utf-8'
file_path, 'wb'
) as f:
f.write(formatted_patch)
f.write(formatted_patch.encode('utf-8'))
pl.write(filename + '\n')

View File

@@ -207,7 +207,7 @@ const LINTERS = [{
console.warn(`Patch file '${f}' has no description. Every patch must contain a justification for why the patch exists and the plan for its removal.`);
return false;
}
const trailingWhitespace = patchText.split('\n').filter(line => line.startsWith('+')).some(line => /\s+$/.test(line));
const trailingWhitespace = patchText.split(/\r?\n/).some(line => line.startsWith('+') && /\s+$/.test(line));
if (trailingWhitespace) {
console.warn(`Patch file '${f}' has trailing whitespace on some lines.`);
return false;

View File

@@ -279,6 +279,9 @@ void ElectronBrowserMainParts::PostEarlyInitialization() {
base::FeatureList::ClearInstanceForTesting();
InitializeFeatureList();
// Initialize field trials.
InitializeFieldTrials();
// Initialize after user script environment creation.
fake_browser_process_->PostEarlyInitialization();
}

View File

@@ -9,6 +9,7 @@
#include "base/base_switches.h"
#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/metrics/field_trial.h"
#include "content/public/common/content_features.h"
#include "electron/buildflags/buildflags.h"
#include "media/base/media_switches.h"
@@ -49,4 +50,12 @@ void InitializeFeatureList() {
base::FeatureList::InitializeInstance(enable_features, disable_features);
}
void InitializeFieldTrials() {
auto* cmd_line = base::CommandLine::ForCurrentProcess();
auto force_fieldtrials =
cmd_line->GetSwitchValueASCII(::switches::kForceFieldTrials);
base::FieldTrialList::CreateTrialsFromString(force_fieldtrials);
}
} // namespace electron

View File

@@ -7,6 +7,7 @@
namespace electron {
void InitializeFeatureList();
}
void InitializeFieldTrials();
} // namespace electron
#endif // SHELL_BROWSER_FEATURE_LIST_H_

View File

@@ -442,6 +442,14 @@ void NativeWindowViews::Hide() {
if (!features::IsUsingOzonePlatform() && global_menu_bar_)
global_menu_bar_->OnWindowUnmapped();
#endif
#if defined(OS_WIN)
// When the window is removed from the taskbar via win.hide(),
// the thumbnail buttons need to be set up again.
// Ensure that when the window is hidden,
// the taskbar host is notified that it should re-add them.
taskbar_host_.SetThumbarButtonsAdded(false);
#endif
}
bool NativeWindowViews::IsVisible() {

View File

@@ -50,8 +50,8 @@ END
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 14,0,0,20210324
PRODUCTVERSION 14,0,0,20210324
FILEVERSION 14,0,0,20210326
PRODUCTVERSION 14,0,0,20210326
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L

View File

@@ -114,11 +114,12 @@ bool TaskbarHost::SetThumbarButtons(HWND window,
// Finally add them to taskbar.
HRESULT r;
if (thumbar_buttons_added_)
if (thumbar_buttons_added_) {
r = taskbar_->ThumbBarUpdateButtons(window, kMaxButtonsCount,
thumb_buttons);
else
} else {
r = taskbar_->ThumbBarAddButtons(window, kMaxButtonsCount, thumb_buttons);
}
thumbar_buttons_added_ = true;
last_buttons_ = buttons;

View File

@@ -60,6 +60,8 @@ class TaskbarHost {
// Called by the window that there is a button in thumbar clicked.
bool HandleThumbarButtonEvent(int button_id);
void SetThumbarButtonsAdded(bool added) { thumbar_buttons_added_ = added; }
private:
// Initialize the taskbar object.
bool InitializeTaskbar();

View File

@@ -41,6 +41,8 @@ namespace context_bridge {
const char* const kProxyFunctionPrivateKey = "electron_contextBridge_proxy_fn";
const char* const kSupportsDynamicPropertiesPrivateKey =
"electron_contextBridge_supportsDynamicProperties";
const char* const kOriginalFunctionPrivateKey =
"electron_contextBridge_original_fn";
} // namespace context_bridge
@@ -179,9 +181,26 @@ v8::MaybeLocal<v8::Value> PassValueToOtherContext(
// the global handle at the right time.
if (value->IsFunction()) {
auto func = v8::Local<v8::Function>::Cast(value);
v8::MaybeLocal<v8::Value> maybe_original_fn = GetPrivate(
source_context, func, context_bridge::kOriginalFunctionPrivateKey);
{
v8::Context::Scope destination_scope(destination_context);
v8::Local<v8::Value> proxy_func;
// If this function has already been sent over the bridge,
// then it is being sent _back_ over the bridge and we can
// simply return the original method here for performance reasons
// For safety reasons we check if the destination context is the
// creation context of the original method. If it's not we proceed
// with the proxy logic
if (maybe_original_fn.ToLocal(&proxy_func) && proxy_func->IsFunction() &&
proxy_func.As<v8::Object>()->CreationContext() ==
destination_context) {
return v8::MaybeLocal<v8::Value>(proxy_func);
}
v8::Local<v8::Object> state =
v8::Object::New(destination_context->GetIsolate());
SetPrivate(destination_context, state,
@@ -190,10 +209,12 @@ v8::MaybeLocal<v8::Value> PassValueToOtherContext(
context_bridge::kSupportsDynamicPropertiesPrivateKey,
gin::ConvertToV8(destination_context->GetIsolate(),
support_dynamic_properties));
v8::Local<v8::Value> proxy_func;
if (!v8::Function::New(destination_context, ProxyFunctionWrapper, state)
.ToLocal(&proxy_func))
return v8::MaybeLocal<v8::Value>();
SetPrivate(destination_context, proxy_func.As<v8::Object>(),
context_bridge::kOriginalFunctionPrivateKey, func);
object_cache->CacheProxiedObject(value, proxy_func);
return v8::MaybeLocal<v8::Value>(proxy_func);
}

View File

@@ -361,11 +361,8 @@ bool RendererClientBase::IsPluginHandledExternally(
bool RendererClientBase::IsOriginIsolatedPepperPlugin(
const base::FilePath& plugin_path) {
#if BUILDFLAG(ENABLE_PDF_VIEWER)
return plugin_path.value() == kPdfPluginPath;
#else
return false;
#endif
// Isolate all Pepper plugins, including the PDF plugin.
return true;
}
std::unique_ptr<blink::WebPrescientNetworking>

View File

@@ -353,6 +353,17 @@ describe('contextBridge', () => {
expect(result).equal('return-value');
});
it('should not double-proxy functions when they are returned to their origin side of the bridge', async () => {
await makeBindingWindow(() => {
contextBridge.exposeInMainWorld('example', (fn: any) => fn);
});
const result = await callWithBindings(async (root: any) => {
const fn = () => null;
return root.example(fn) === fn;
});
expect(result).equal(true);
});
it('should proxy methods that are callable multiple times', async () => {
await makeBindingWindow(() => {
contextBridge.exposeInMainWorld('example', {

View File

@@ -2,6 +2,9 @@ import { BrowserWindow, Session, session } from 'electron/main';
import { expect } from 'chai';
import * as path from 'path';
import * as fs from 'fs';
import * as http from 'http';
import { AddressInfo } from 'net';
import { closeWindow } from './window-helpers';
import { emittedOnce } from './events-helpers';
import { ifit, ifdescribe, delay } from './spec-helpers';
@@ -10,9 +13,7 @@ const features = process._linkedBinding('electron_common_features');
const v8Util = process._linkedBinding('electron_common_v8_util');
ifdescribe(features.isBuiltinSpellCheckerEnabled())('spellchecker', function () {
// TODO(zcbenz): Spellchecker loads really slow on ASan, we should provide
// a small testing dictionary to make the tests load faster.
this.timeout((process.env.IS_ASAN ? 700 : 20) * 1000);
this.timeout((process.env.IS_ASAN ? 100 : 20) * 1000);
let w: BrowserWindow;
@@ -32,7 +33,7 @@ ifdescribe(features.isBuiltinSpellCheckerEnabled())('spellchecker', function ()
// to detect spellchecker is to keep checking with a busy loop.
async function rightClickUntil (fn: (params: Electron.ContextMenuParams) => boolean) {
const now = Date.now();
const timeout = (process.env.IS_ASAN ? 600 : 10) * 1000;
const timeout = 10 * 1000;
let contextMenuParams = await rightClick();
while (!fn(contextMenuParams) && (Date.now() - now < timeout)) {
await delay(100);
@@ -41,6 +42,26 @@ ifdescribe(features.isBuiltinSpellCheckerEnabled())('spellchecker', function ()
return contextMenuParams;
}
// Setup a server to download hunspell dictionary.
const server = http.createServer((req, res) => {
// The provided is minimal dict for testing only, full list of words can
// be found at src/third_party/hunspell_dictionaries/xx_XX.dic.
fs.readFile(path.join(__dirname, '/../../third_party/hunspell_dictionaries/xx-XX-3-0.bdic'), function (err, data) {
if (err) {
console.error('Failed to read dictionary file');
res.writeHead(404);
res.end(JSON.stringify(err));
return;
}
res.writeHead(200);
res.end(data);
});
});
before((done) => {
server.listen(0, '127.0.0.1', () => done());
});
after(() => server.close());
beforeEach(async () => {
w = new BrowserWindow({
show: false,
@@ -50,6 +71,7 @@ ifdescribe(features.isBuiltinSpellCheckerEnabled())('spellchecker', function ()
contextIsolation: false
}
});
w.webContents.session.setSpellCheckerDictionaryDownloadURL(`http://127.0.0.1:${(server.address() as AddressInfo).port}/`);
w.webContents.session.setSpellCheckerLanguages(['en-US']);
await w.loadFile(path.resolve(__dirname, './fixtures/chromium/spellchecker.html'));
});
@@ -62,7 +84,7 @@ ifdescribe(features.isBuiltinSpellCheckerEnabled())('spellchecker', function ()
const shouldRun = process.platform !== 'win32';
ifit(shouldRun)('should detect correctly spelled words as correct', async () => {
await w.webContents.executeJavaScript('document.body.querySelector("textarea").value = "Beautiful and lovely"');
await w.webContents.executeJavaScript('document.body.querySelector("textarea").value = "typography"');
await w.webContents.executeJavaScript('document.body.querySelector("textarea").focus()');
const contextMenuParams = await rightClickUntil((contextMenuParams) => contextMenuParams.selectionText.length > 0);
expect(contextMenuParams.misspelledWord).to.eq('');
@@ -70,10 +92,10 @@ ifdescribe(features.isBuiltinSpellCheckerEnabled())('spellchecker', function ()
});
ifit(shouldRun)('should detect incorrectly spelled words as incorrect', async () => {
await w.webContents.executeJavaScript('document.body.querySelector("textarea").value = "Beautifulllll asd asd"');
await w.webContents.executeJavaScript('document.body.querySelector("textarea").value = "typograpy"');
await w.webContents.executeJavaScript('document.body.querySelector("textarea").focus()');
const contextMenuParams = await rightClickUntil((contextMenuParams) => contextMenuParams.misspelledWord.length > 0);
expect(contextMenuParams.misspelledWord).to.eq('Beautifulllll');
expect(contextMenuParams.misspelledWord).to.eq('typograpy');
expect(contextMenuParams.dictionarySuggestions).to.have.length.of.at.least(1);
});
@@ -81,24 +103,24 @@ ifdescribe(features.isBuiltinSpellCheckerEnabled())('spellchecker', function ()
w.webContents.session.setSpellCheckerLanguages([]);
await delay(500);
w.webContents.session.setSpellCheckerLanguages(['en-US']);
await w.webContents.executeJavaScript('document.body.querySelector("textarea").value = "Beautifulllll asd asd"');
await w.webContents.executeJavaScript('document.body.querySelector("textarea").value = "typograpy"');
await w.webContents.executeJavaScript('document.body.querySelector("textarea").focus()');
const contextMenuParams = await rightClickUntil((contextMenuParams) => contextMenuParams.misspelledWord.length > 0);
expect(contextMenuParams.misspelledWord).to.eq('Beautifulllll');
expect(contextMenuParams.misspelledWord).to.eq('typograpy');
expect(contextMenuParams.dictionarySuggestions).to.have.length.of.at.least(1);
});
ifit(shouldRun)('should expose webFrame spellchecker correctly', async () => {
await w.webContents.executeJavaScript('document.body.querySelector("textarea").value = "Beautifulllll asd asd"');
await w.webContents.executeJavaScript('document.body.querySelector("textarea").value = "typograpy"');
await w.webContents.executeJavaScript('document.body.querySelector("textarea").focus()');
await rightClickUntil((contextMenuParams) => contextMenuParams.misspelledWord.length > 0);
const callWebFrameFn = (expr: string) => w.webContents.executeJavaScript('require("electron").webFrame.' + expr);
expect(await callWebFrameFn('isWordMisspelled("test")')).to.equal(false);
expect(await callWebFrameFn('isWordMisspelled("testt")')).to.equal(true);
expect(await callWebFrameFn('getWordSuggestions("test")')).to.be.empty();
expect(await callWebFrameFn('getWordSuggestions("testt")')).to.not.be.empty();
expect(await callWebFrameFn('isWordMisspelled("typography")')).to.equal(false);
expect(await callWebFrameFn('isWordMisspelled("typograpy")')).to.equal(true);
expect(await callWebFrameFn('getWordSuggestions("typography")')).to.be.empty();
expect(await callWebFrameFn('getWordSuggestions("typograpy")')).to.not.be.empty();
});
describe('spellCheckerEnabled', () => {
@@ -107,7 +129,7 @@ ifdescribe(features.isBuiltinSpellCheckerEnabled())('spellchecker', function ()
});
ifit(shouldRun)('can be dynamically changed', async () => {
await w.webContents.executeJavaScript('document.body.querySelector("textarea").value = "Beautifulllll asd asd"');
await w.webContents.executeJavaScript('document.body.querySelector("textarea").value = "typograpy"');
await w.webContents.executeJavaScript('document.body.querySelector("textarea").focus()');
await rightClickUntil((contextMenuParams) => contextMenuParams.misspelledWord.length > 0);
@@ -116,12 +138,17 @@ ifdescribe(features.isBuiltinSpellCheckerEnabled())('spellchecker', function ()
w.webContents.session.spellCheckerEnabled = false;
v8Util.runUntilIdle();
expect(w.webContents.session.spellCheckerEnabled).to.be.false();
expect(await callWebFrameFn('isWordMisspelled("testt")')).to.equal(false);
// spellCheckerEnabled is sent to renderer asynchronously and there is
// no event notifying when it is finished, so wait a little while to
// ensure the setting has been changed in renderer.
await delay(500);
expect(await callWebFrameFn('isWordMisspelled("typograpy")')).to.equal(false);
w.webContents.session.spellCheckerEnabled = true;
v8Util.runUntilIdle();
expect(w.webContents.session.spellCheckerEnabled).to.be.true();
expect(await callWebFrameFn('isWordMisspelled("testt")')).to.equal(true);
await delay(500);
expect(await callWebFrameFn('isWordMisspelled("typograpy")')).to.equal(true);
});
});