mirror of
https://github.com/electron/electron.git
synced 2026-02-26 03:01:17 -05:00
Compare commits
8 Commits
v21.0.0-be
...
v21.0.0-be
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
62b64e6e60 | ||
|
|
a5db8a3d53 | ||
|
|
85f93bc5bf | ||
|
|
605ee9e28a | ||
|
|
e48878ea63 | ||
|
|
e78aa6af16 | ||
|
|
fb0bbf0100 | ||
|
|
5e8b4bd86f |
@@ -1 +1 @@
|
||||
21.0.0-beta.1
|
||||
21.0.0-beta.4
|
||||
53
docs/tutorial/asar-integrity.md
Normal file
53
docs/tutorial/asar-integrity.md
Normal file
@@ -0,0 +1,53 @@
|
||||
---
|
||||
title: 'ASAR Integrity'
|
||||
description: 'An experimental feature that ensures the validity of ASAR contents at runtime.'
|
||||
slug: asar-integrity
|
||||
hide_title: false
|
||||
---
|
||||
|
||||
## Platform Support
|
||||
|
||||
Currently ASAR integrity checking is only supported on macOS.
|
||||
|
||||
## Requirements
|
||||
|
||||
### Electron Forge / Electron Packager
|
||||
|
||||
If you are using `>= electron-packager@15.4.0` or `>= @electron-forge/core@6.0.0-beta.61` then all these requirements are met for you automatically and you can skip to [Toggling the Fuse](#toggling-the-fuse).
|
||||
|
||||
### Other build systems
|
||||
|
||||
In order to enable ASAR integrity checking you need to ensure that your `app.asar` file was generated by a version of the `asar` npm package that supports asar integrity. Support was introduced in version `3.1.0`.
|
||||
|
||||
Your must then populate a valid `ElectronAsarIntegrity` dictionary block in your packaged apps `Info.plist`. An example is included below.
|
||||
|
||||
```plist
|
||||
<key>ElectronAsarIntegrity</key>
|
||||
<dict>
|
||||
<key>Resources/app.asar</key>
|
||||
<dict>
|
||||
<key>algorithm</key>
|
||||
<string>SHA256</string>
|
||||
<key>hash</key>
|
||||
<string>9d1f61ea03c4bb62b4416387a521101b81151da0cfbe18c9f8c8b818c5cebfac</string>
|
||||
</dict>
|
||||
</dict>
|
||||
```
|
||||
|
||||
Valid `algorithm` values are currently `SHA256` only. The `hash` is a hash of the ASAR header using the given algorithm. The `asar` package exposes a `getRawHeader` method whose result can then be hashed to generate this value.
|
||||
|
||||
## Toggling the Fuse
|
||||
|
||||
ASAR integrity checking is currently disabled by default and can be enabled by toggling a fuse. See [Electron Fuses](fuses.md) for more information on what Electron Fuses are and how they work. When enabling this fuse you typically also want to enable the `onlyLoadAppFromAsar` fuse otherwise the validity checking can be bypassed via the Electron app code search path.
|
||||
|
||||
```js
|
||||
require('@electron/fuses').flipFuses(
|
||||
// E.g. /a/b/Foo.app
|
||||
pathToPackagedApp,
|
||||
{
|
||||
version: FuseVersion.V1,
|
||||
[FuseV1Options.EnableEmbeddedAsarIntegrityValidation]: true,
|
||||
[FuseV1Options.OnlyLoadAppFromAsar]: true
|
||||
}
|
||||
)
|
||||
```
|
||||
@@ -8,6 +8,52 @@ For a subset of Electron functionality it makes sense to disable certain feature
|
||||
|
||||
Fuses are the solution to this problem, at a high level they are "magic bits" in the Electron binary that can be flipped when packaging your Electron app to enable / disable certain features / restrictions. Because they are flipped at package time before you code sign your app the OS becomes responsible for ensuring those bits aren't flipped back via OS level code signing validation (Gatekeeper / App Locker).
|
||||
|
||||
## Current Fuses
|
||||
|
||||
### `runAsNode`
|
||||
|
||||
**Default:** Enabled
|
||||
**@electron/fuses:** `FuseV1Options.RunAsNode`
|
||||
|
||||
The runAsNode fuse toggles whether the `ELECTRON_RUN_AS_NODE` environment variable is respected or not. Please note that if this fuse is disabled then `process.fork` in the main process will not function as expected as it depends on this environment variable to function.
|
||||
|
||||
### `cookieEncryption`
|
||||
|
||||
**Default:** Disabled
|
||||
**@electron/fuses:** `FuseV1Options.EnableCookieEncryption`
|
||||
|
||||
The cookieEncryption fuse toggles whether the cookie store on disk is encrypted using OS level cryptography keys. By default the sqlite database that Chromium uses to store cookies stores the values in plaintext. If you wish to ensure your apps cookies are encrypted in the same way Chrome does then you should enable this fuse. Please note it is a one-way transition, if you enable this fuse existing unencrypted cookies will be encrypted-on-write but if you then disable the fuse again your cookie store will effectively be corrupt and useless. Most apps can safely enable this fuse.
|
||||
|
||||
### `nodeOptions`
|
||||
|
||||
**Default:** Enabled
|
||||
**@electron/fuses:** `FuseV1Options.EnableNodeOptionsEnvironmentVariable`
|
||||
|
||||
The nodeOptions fuse toggles whether the [`NODE_OPTIONS`](https://nodejs.org/api/cli.html#node_optionsoptions) environment variable is respected or not. This environment variable can be used to pass all kinds of custom options to the Node.js runtime and isn't typically used by apps in production. Most apps can safely disable this fuse.
|
||||
|
||||
### `nodeCliInspect`
|
||||
|
||||
**Default:** Enabled
|
||||
**@electron/fuses:** `FuseV1Options.EnableNodeCliInspectArguments`
|
||||
|
||||
The nodeCliInspect fuse toggles whether the `--inspect`, `--inspect-brk`, etc. flags are respected or not. When disabled it also ensures that `SIGUSR1` signal does not initialize the main process inspector. Most apps can safely disable this fuse.
|
||||
|
||||
### `embeddedAsarIntegrityValidation`
|
||||
|
||||
**Default:** Disabled
|
||||
**@electron/fuses:** `FuseV1Options.EnableEmbeddedAsarIntegrityValidation`
|
||||
|
||||
The embeddedAsarIntegrityValidation fuse toggles an experimental feature on macOS that validates the content of the `app.asar` file when it is loaded. This feature is designed to have a minimal performance impact but may marginally slow down file reads from inside the `app.asar` archive.
|
||||
|
||||
For more information on how to use asar integrity validation please read the [Asar Integrity](asar-integrity.md) documentation.
|
||||
|
||||
### `onlyLoadAppFromAsar`
|
||||
|
||||
**Default:** Disabled
|
||||
**@electron/fuses:** `FuseV1Options.OnlyLoadAppFromAsar`
|
||||
|
||||
The onlyLoadAppFromAsar fuse changes the search system that Electron uses to locate your app code. By default Electron will search in the following order `app.asar` -> `app` -> `default_app.asasr`. When this fuse is enabled the search order becomes a single entry `app.asar` thus ensuring that when combined with the `embeddedAsarIntegrityValidation` fuse it is impossible to load non-validated code.
|
||||
|
||||
## How do I flip the fuses?
|
||||
|
||||
### The easy way
|
||||
@@ -20,11 +66,18 @@ require('@electron/fuses').flipFuses(
|
||||
require('electron'),
|
||||
// Fuses to flip
|
||||
{
|
||||
runAsNode: false
|
||||
version: FuseVersion.V1,
|
||||
[FuseV1Options.RunAsNode]: false
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
You can validate the fuses have been flipped or check the fuse status of an arbitrary Electron app using the fuses CLI.
|
||||
|
||||
```bash
|
||||
npx @electron/fuses read --app /Applications/Foo.app
|
||||
```
|
||||
|
||||
### The hard way
|
||||
|
||||
#### Quick Glossary
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "electron",
|
||||
"version": "21.0.0-beta.1",
|
||||
"version": "21.0.0-beta.4",
|
||||
"repository": "https://github.com/electron/electron",
|
||||
"description": "Build cross platform desktop apps with JavaScript, HTML, and CSS",
|
||||
"devDependencies": {
|
||||
|
||||
@@ -117,3 +117,5 @@ add_maximized_parameter_to_linuxui_getwindowframeprovider.patch
|
||||
revert_spellcheck_fully_launch_spell_check_delayed_initialization.patch
|
||||
add_electron_deps_to_license_credits_file.patch
|
||||
feat_add_set_can_resize_mutator.patch
|
||||
fix_revert_emulationhandler_update_functions_to_early_return.patch
|
||||
cherry-pick-9b5207569882.patch
|
||||
|
||||
179
patches/chromium/cherry-pick-9b5207569882.patch
Normal file
179
patches/chromium/cherry-pick-9b5207569882.patch
Normal file
@@ -0,0 +1,179 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Ken Rockot <rockot@google.com>
|
||||
Date: Wed, 31 Aug 2022 15:39:45 +0000
|
||||
Subject: Mojo: Validate response message type
|
||||
|
||||
Ensures that a response message is actually the type expected by the
|
||||
original request.
|
||||
|
||||
Fixed: 1358134
|
||||
Change-Id: I8f8f58168764477fbf7a6d2e8aeb040f07793d45
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3864274
|
||||
Reviewed-by: Robert Sesek <rsesek@chromium.org>
|
||||
Commit-Queue: Ken Rockot <rockot@google.com>
|
||||
Cr-Commit-Position: refs/heads/main@{#1041553}
|
||||
|
||||
diff --git a/mojo/public/cpp/bindings/interface_endpoint_client.h b/mojo/public/cpp/bindings/interface_endpoint_client.h
|
||||
index 0ebbc94ad51cca74fcebc357b5262229ae5c9d0e..cd79a5edb3f939623b874db36542ee651113c164 100644
|
||||
--- a/mojo/public/cpp/bindings/interface_endpoint_client.h
|
||||
+++ b/mojo/public/cpp/bindings/interface_endpoint_client.h
|
||||
@@ -221,20 +221,32 @@ class COMPONENT_EXPORT(MOJO_CPP_BINDINGS) InterfaceEndpointClient
|
||||
void ForgetAsyncRequest(uint64_t request_id);
|
||||
|
||||
private:
|
||||
- // Maps from the id of a response to the MessageReceiver that handles the
|
||||
- // response.
|
||||
- using AsyncResponderMap =
|
||||
- std::map<uint64_t, std::unique_ptr<MessageReceiver>>;
|
||||
+ struct PendingAsyncResponse {
|
||||
+ public:
|
||||
+ PendingAsyncResponse(uint32_t request_message_name,
|
||||
+ std::unique_ptr<MessageReceiver> responder);
|
||||
+ PendingAsyncResponse(PendingAsyncResponse&&);
|
||||
+ PendingAsyncResponse(const PendingAsyncResponse&) = delete;
|
||||
+ PendingAsyncResponse& operator=(PendingAsyncResponse&&);
|
||||
+ PendingAsyncResponse& operator=(const PendingAsyncResponse&) = delete;
|
||||
+ ~PendingAsyncResponse();
|
||||
+
|
||||
+ uint32_t request_message_name;
|
||||
+ std::unique_ptr<MessageReceiver> responder;
|
||||
+ };
|
||||
+
|
||||
+ using AsyncResponderMap = std::map<uint64_t, PendingAsyncResponse>;
|
||||
|
||||
struct SyncResponseInfo {
|
||||
public:
|
||||
- explicit SyncResponseInfo(bool* in_response_received);
|
||||
+ SyncResponseInfo(uint32_t request_message_name, bool* in_response_received);
|
||||
|
||||
SyncResponseInfo(const SyncResponseInfo&) = delete;
|
||||
SyncResponseInfo& operator=(const SyncResponseInfo&) = delete;
|
||||
|
||||
~SyncResponseInfo();
|
||||
|
||||
+ uint32_t request_message_name;
|
||||
Message response;
|
||||
|
||||
// Points to a stack-allocated variable.
|
||||
diff --git a/mojo/public/cpp/bindings/lib/interface_endpoint_client.cc b/mojo/public/cpp/bindings/lib/interface_endpoint_client.cc
|
||||
index ded0d20ab193cfdaf36bd44c25719d7073c989fb..a6f41414b8918989662eb0a1dea773932631c8cc 100644
|
||||
--- a/mojo/public/cpp/bindings/lib/interface_endpoint_client.cc
|
||||
+++ b/mojo/public/cpp/bindings/lib/interface_endpoint_client.cc
|
||||
@@ -30,6 +30,7 @@
|
||||
#include "mojo/public/cpp/bindings/sync_call_restrictions.h"
|
||||
#include "mojo/public/cpp/bindings/sync_event_watcher.h"
|
||||
#include "mojo/public/cpp/bindings/thread_safe_proxy.h"
|
||||
+#include "third_party/abseil-cpp/absl/types/optional.h"
|
||||
#include "third_party/perfetto/protos/perfetto/trace/track_event/chrome_mojo_event_info.pbzero.h"
|
||||
|
||||
namespace mojo {
|
||||
@@ -316,9 +317,27 @@ class ResponderThunk : public MessageReceiverWithStatus {
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
+InterfaceEndpointClient::PendingAsyncResponse::PendingAsyncResponse(
|
||||
+ uint32_t request_message_name,
|
||||
+ std::unique_ptr<MessageReceiver> responder)
|
||||
+ : request_message_name(request_message_name),
|
||||
+ responder(std::move(responder)) {}
|
||||
+
|
||||
+InterfaceEndpointClient::PendingAsyncResponse::PendingAsyncResponse(
|
||||
+ PendingAsyncResponse&&) = default;
|
||||
+
|
||||
+InterfaceEndpointClient::PendingAsyncResponse&
|
||||
+InterfaceEndpointClient::PendingAsyncResponse::operator=(
|
||||
+ PendingAsyncResponse&&) = default;
|
||||
+
|
||||
+InterfaceEndpointClient::PendingAsyncResponse::~PendingAsyncResponse() =
|
||||
+ default;
|
||||
+
|
||||
InterfaceEndpointClient::SyncResponseInfo::SyncResponseInfo(
|
||||
+ uint32_t request_message_name,
|
||||
bool* in_response_received)
|
||||
- : response_received(in_response_received) {}
|
||||
+ : request_message_name(request_message_name),
|
||||
+ response_received(in_response_received) {}
|
||||
|
||||
InterfaceEndpointClient::SyncResponseInfo::~SyncResponseInfo() {}
|
||||
|
||||
@@ -606,6 +625,7 @@ bool InterfaceEndpointClient::SendMessageWithResponder(
|
||||
// message before calling |SendMessage()| below.
|
||||
#endif
|
||||
|
||||
+ const uint32_t message_name = message->name();
|
||||
const bool is_sync = message->has_flag(Message::kFlagIsSync);
|
||||
const bool exclusive_wait = message->has_flag(Message::kFlagNoInterrupt);
|
||||
if (!controller_->SendMessage(message))
|
||||
@@ -622,7 +642,8 @@ bool InterfaceEndpointClient::SendMessageWithResponder(
|
||||
controller_->RegisterExternalSyncWaiter(request_id);
|
||||
}
|
||||
base::AutoLock lock(async_responders_lock_);
|
||||
- async_responders_[request_id] = std::move(responder);
|
||||
+ async_responders_.emplace(
|
||||
+ request_id, PendingAsyncResponse{message_name, std::move(responder)});
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -630,7 +651,8 @@ bool InterfaceEndpointClient::SendMessageWithResponder(
|
||||
|
||||
bool response_received = false;
|
||||
sync_responses_.insert(std::make_pair(
|
||||
- request_id, std::make_unique<SyncResponseInfo>(&response_received)));
|
||||
+ request_id,
|
||||
+ std::make_unique<SyncResponseInfo>(message_name, &response_received)));
|
||||
|
||||
base::WeakPtr<InterfaceEndpointClient> weak_self =
|
||||
weak_ptr_factory_.GetWeakPtr();
|
||||
@@ -808,13 +830,13 @@ void InterfaceEndpointClient::ResetFromAnotherSequenceUnsafe() {
|
||||
}
|
||||
|
||||
void InterfaceEndpointClient::ForgetAsyncRequest(uint64_t request_id) {
|
||||
- std::unique_ptr<MessageReceiver> responder;
|
||||
+ absl::optional<PendingAsyncResponse> response;
|
||||
{
|
||||
base::AutoLock lock(async_responders_lock_);
|
||||
auto it = async_responders_.find(request_id);
|
||||
if (it == async_responders_.end())
|
||||
return;
|
||||
- responder = std::move(it->second);
|
||||
+ response = std::move(it->second);
|
||||
async_responders_.erase(it);
|
||||
}
|
||||
}
|
||||
@@ -906,6 +928,10 @@ bool InterfaceEndpointClient::HandleValidatedMessage(Message* message) {
|
||||
return false;
|
||||
|
||||
if (it->second) {
|
||||
+ if (message->name() != it->second->request_message_name) {
|
||||
+ return false;
|
||||
+ }
|
||||
+
|
||||
it->second->response = std::move(*message);
|
||||
*it->second->response_received = true;
|
||||
return true;
|
||||
@@ -916,18 +942,22 @@ bool InterfaceEndpointClient::HandleValidatedMessage(Message* message) {
|
||||
sync_responses_.erase(it);
|
||||
}
|
||||
|
||||
- std::unique_ptr<MessageReceiver> responder;
|
||||
+ absl::optional<PendingAsyncResponse> pending_response;
|
||||
{
|
||||
base::AutoLock lock(async_responders_lock_);
|
||||
auto it = async_responders_.find(request_id);
|
||||
if (it == async_responders_.end())
|
||||
return false;
|
||||
- responder = std::move(it->second);
|
||||
+ pending_response = std::move(it->second);
|
||||
async_responders_.erase(it);
|
||||
}
|
||||
|
||||
+ if (message->name() != pending_response->request_message_name) {
|
||||
+ return false;
|
||||
+ }
|
||||
+
|
||||
internal::MessageDispatchContext dispatch_context(message);
|
||||
- return responder->Accept(message);
|
||||
+ return pending_response->responder->Accept(message);
|
||||
} else {
|
||||
if (mojo::internal::ControlMessageHandler::IsControlMessage(message))
|
||||
return control_message_handler_.Accept(message);
|
||||
@@ -0,0 +1,45 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Shelley Vohr <shelley.vohr@gmail.com>
|
||||
Date: Fri, 26 Aug 2022 11:24:27 +0200
|
||||
Subject: fix: revert EmulationHandler update functions to early return
|
||||
|
||||
Our debugger class (a subclass of ontent::DevToolsAgentHostClient) isn't
|
||||
aware of those changes unless we hook RenderFrameHostChanged. If we
|
||||
don't do this, some navigation lifecycle events
|
||||
{loadingFinished, dataReceived} emitted by the renderer are lost. We
|
||||
disconnect and reconnect the webcontents to the DevToolsAgentHost to
|
||||
prevent this from happening.
|
||||
|
||||
As of https://chromium-review.googlesource.com/c/chromium/src/+/3758294
|
||||
this results in a DCHECK, since DevToolsAgentHost::DisconnectWebContents
|
||||
results in a call to UpdateDeviceEmulationState and host_ is nullptr. To
|
||||
fix this, we revert those state update calls to early returns.
|
||||
|
||||
Upstreamed at https://chromium-review.googlesource.com/c/chromium/src/+/3856525
|
||||
|
||||
diff --git a/content/browser/devtools/protocol/emulation_handler.cc b/content/browser/devtools/protocol/emulation_handler.cc
|
||||
index 84736afeb21d1deec0f4032ef6f3304075d8fffb..d023bb7b5a34c5c055d3d7cc5dc3e04ef43bcc3b 100644
|
||||
--- a/content/browser/devtools/protocol/emulation_handler.cc
|
||||
+++ b/content/browser/devtools/protocol/emulation_handler.cc
|
||||
@@ -565,7 +565,9 @@ WebContentsImpl* EmulationHandler::GetWebContents() {
|
||||
}
|
||||
|
||||
void EmulationHandler::UpdateTouchEventEmulationState() {
|
||||
- DCHECK(host_);
|
||||
+ if (!host_)
|
||||
+ return;
|
||||
+
|
||||
// We only have a single TouchEmulator for all frames, so let the main frame's
|
||||
// EmulationHandler enable/disable it.
|
||||
DCHECK(!host_->GetParentOrOuterDocument());
|
||||
@@ -585,7 +587,9 @@ void EmulationHandler::UpdateTouchEventEmulationState() {
|
||||
}
|
||||
|
||||
void EmulationHandler::UpdateDeviceEmulationState() {
|
||||
- DCHECK(host_);
|
||||
+ if (!host_)
|
||||
+ return;
|
||||
+
|
||||
// Device emulation only happens on the outermost main frame.
|
||||
DCHECK(!host_->GetParentOrOuterDocument());
|
||||
|
||||
@@ -86,8 +86,11 @@ class DataPipeReader {
|
||||
if (result == MOJO_RESULT_OK) { // success
|
||||
remaining_size_ -= length;
|
||||
head_ += length;
|
||||
if (remaining_size_ == 0)
|
||||
if (remaining_size_ == 0) {
|
||||
OnSuccess();
|
||||
} else {
|
||||
handle_watcher_.ArmOrNotify();
|
||||
}
|
||||
} else if (result == MOJO_RESULT_SHOULD_WAIT) { // IO pending
|
||||
handle_watcher_.ArmOrNotify();
|
||||
} else { // error
|
||||
|
||||
@@ -24,6 +24,10 @@
|
||||
#include "ui/display/win/screen_win.h"
|
||||
#endif
|
||||
|
||||
#if defined(USE_OZONE)
|
||||
#include "ui/ozone/public/ozone_platform.h"
|
||||
#endif
|
||||
|
||||
namespace electron::api {
|
||||
|
||||
gin::WrapperInfo Screen::kWrapperInfo = {gin::kEmbedderNativeGin};
|
||||
@@ -68,7 +72,18 @@ Screen::~Screen() {
|
||||
screen_->RemoveObserver(this);
|
||||
}
|
||||
|
||||
gfx::Point Screen::GetCursorScreenPoint() {
|
||||
gfx::Point Screen::GetCursorScreenPoint(v8::Isolate* isolate) {
|
||||
#if defined(USE_OZONE)
|
||||
// Wayland will crash unless a window is created prior to calling
|
||||
// GetCursorScreenPoint.
|
||||
if (!ui::OzonePlatform::IsInitialized()) {
|
||||
gin_helper::ErrorThrower thrower(isolate);
|
||||
thrower.ThrowError(
|
||||
"screen.getCursorScreenPoint() cannot be called before a window has "
|
||||
"been created.");
|
||||
return gfx::Point();
|
||||
}
|
||||
#endif
|
||||
return screen_->GetCursorScreenPoint();
|
||||
}
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ class Screen : public gin::Wrappable<Screen>,
|
||||
Screen(v8::Isolate* isolate, display::Screen* screen);
|
||||
~Screen() override;
|
||||
|
||||
gfx::Point GetCursorScreenPoint();
|
||||
gfx::Point GetCursorScreenPoint(v8::Isolate* isolate);
|
||||
display::Display GetPrimaryDisplay();
|
||||
std::vector<display::Display> GetAllDisplays();
|
||||
display::Display GetDisplayNearestPoint(const gfx::Point& point);
|
||||
|
||||
@@ -50,8 +50,8 @@ END
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 21,0,0,1
|
||||
PRODUCTVERSION 21,0,0,1
|
||||
FILEVERSION 21,0,0,4
|
||||
PRODUCTVERSION 21,0,0,4
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
|
||||
@@ -529,6 +529,50 @@ describe('session module', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('ses.getBlobData2()', () => {
|
||||
const scheme = 'cors-blob';
|
||||
const protocol = session.defaultSession.protocol;
|
||||
const url = `${scheme}://host`;
|
||||
after(async () => {
|
||||
await protocol.unregisterProtocol(scheme);
|
||||
});
|
||||
afterEach(closeAllWindows);
|
||||
|
||||
it('returns blob data for uuid', (done) => {
|
||||
const content = `<html>
|
||||
<script>
|
||||
let fd = new FormData();
|
||||
fd.append("data", new Blob(new Array(65_537).fill('a')));
|
||||
fetch('${url}', {method:'POST', body: fd });
|
||||
</script>
|
||||
</html>`;
|
||||
|
||||
protocol.registerStringProtocol(scheme, (request, callback) => {
|
||||
try {
|
||||
if (request.method === 'GET') {
|
||||
callback({ data: content, mimeType: 'text/html' });
|
||||
} else if (request.method === 'POST') {
|
||||
const uuid = request.uploadData![1].blobUUID;
|
||||
expect(uuid).to.be.a('string');
|
||||
session.defaultSession.getBlobData(uuid!).then(result => {
|
||||
try {
|
||||
const data = new Array(65_537).fill('a');
|
||||
expect(result.toString()).to.equal(data.join(''));
|
||||
done();
|
||||
} catch (e) {
|
||||
done(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
done(e);
|
||||
}
|
||||
});
|
||||
const w = new BrowserWindow({ show: false });
|
||||
w.loadURL(url);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ses.setCertificateVerifyProc(callback)', () => {
|
||||
let server: http.Server;
|
||||
|
||||
|
||||
@@ -326,6 +326,7 @@ describe('webContents module', () => {
|
||||
|
||||
describe('loadURL() promise API', () => {
|
||||
let w: BrowserWindow;
|
||||
|
||||
beforeEach(async () => {
|
||||
w = new BrowserWindow({ show: false });
|
||||
});
|
||||
@@ -357,6 +358,35 @@ describe('webContents module', () => {
|
||||
.and.have.property('code', 'ERR_FAILED');
|
||||
});
|
||||
|
||||
it('does not crash when loading a new URL with emulation settings set', async () => {
|
||||
const setEmulation = async () => {
|
||||
if (w.webContents) {
|
||||
w.webContents.debugger.attach('1.3');
|
||||
|
||||
const deviceMetrics = {
|
||||
width: 700,
|
||||
height: 600,
|
||||
deviceScaleFactor: 2,
|
||||
mobile: true,
|
||||
dontSetVisibleSize: true
|
||||
};
|
||||
await w.webContents.debugger.sendCommand(
|
||||
'Emulation.setDeviceMetricsOverride',
|
||||
deviceMetrics
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
await w.loadURL(`file://${fixturesPath}/pages/blank.html`);
|
||||
await setEmulation();
|
||||
await w.loadURL('data:text/html,<h1>HELLO</h1>');
|
||||
await setEmulation();
|
||||
} catch (e) {
|
||||
expect((e as Error).message).to.match(/Debugger is already attached to the target/);
|
||||
}
|
||||
});
|
||||
|
||||
it('sets appropriate error information on rejection', async () => {
|
||||
let err: any;
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user