mirror of
https://github.com/electron/electron.git
synced 2026-02-19 03:14:51 -05:00
Compare commits
6 Commits
same-party
...
custom-err
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
77189f7a39 | ||
|
|
28b794216d | ||
|
|
58d9c451c6 | ||
|
|
71d8a4a466 | ||
|
|
825f3a3a7d | ||
|
|
b2625db2ba |
6
docs/api/structures/cancellable-navigation-event.md
Normal file
6
docs/api/structures/cancellable-navigation-event.md
Normal file
@@ -0,0 +1,6 @@
|
||||
# CancellableNavigationEvent Object extends `Event`
|
||||
|
||||
* `returnValue` Object - Set this to cancel the navigation event and optionally return a custom error code or error page
|
||||
* `errorCode` Number - Can be any error code from the [Net Error List](https://source.chromium.org/chromium/chromium/src/+/master:net/base/net_error_list.h). If you provide `errorPage` then this code can not be `-3`. We recommend using `-2` which is `net::Aborted`.
|
||||
* `errorPage` String (optional) - Custom HTML error page to display when the navigation is cancelled.
|
||||
|
||||
@@ -57,6 +57,25 @@ Process: [Main](../glossary.md#main-process)
|
||||
Emitted when the navigation is done, i.e. the spinner of the tab has stopped
|
||||
spinning, and the `onload` event was dispatched.
|
||||
|
||||
#### Event: 'will-fail-load' _Experimental_
|
||||
|
||||
Returns:
|
||||
|
||||
* `event` [CancellableNavigationEvent](structures/cancellable-navigation-event.md)
|
||||
* `url` String
|
||||
* `isInPlace` Boolean
|
||||
* `isMainFrame` Boolean
|
||||
* `frameProcessId` Integer
|
||||
* `frameRoutingId` Integer
|
||||
* `errorCode` Integer
|
||||
* `errorDescription` String
|
||||
|
||||
This event will be emitted after `did-start-loading` and always before the
|
||||
`did-fail-load` event for the same navigation.
|
||||
|
||||
Settings `event.returnValue` to the appropriate object will result in a custom error page being
|
||||
displayed using custom HTML.
|
||||
|
||||
#### Event: 'did-fail-load'
|
||||
|
||||
Returns:
|
||||
@@ -224,11 +243,11 @@ Not emitted if the creation of the window is canceled from
|
||||
|
||||
See [`window.open()`](window-open.md) for more details and how to use this in conjunction with `webContents.setWindowOpenHandler`.
|
||||
|
||||
#### Event: 'will-navigate'
|
||||
#### Event: 'will-navigate' _Experimental_
|
||||
|
||||
Returns:
|
||||
|
||||
* `event` Event
|
||||
* `event` [CancellableNavigationEvent](structures/cancellable-navigation-event.md)
|
||||
* `url` String
|
||||
|
||||
Emitted when a user or the page wants to start navigation. It can happen when
|
||||
@@ -243,6 +262,9 @@ this purpose.
|
||||
|
||||
Calling `event.preventDefault()` will prevent the navigation.
|
||||
|
||||
Settings `event.returnValue` to the appropriate object will result in a custom error page being
|
||||
displayed using custom HTML and the navigation being cancelled.
|
||||
|
||||
#### Event: 'did-start-navigation'
|
||||
|
||||
Returns:
|
||||
|
||||
@@ -819,7 +819,7 @@ which potential security issues are not as widely known.
|
||||
[webview-tag]: ../api/webview-tag.md
|
||||
[web-contents]: ../api/web-contents.md
|
||||
[window-open-handler]: ../api/web-contents.md#contentssetwindowopenhandler-handler
|
||||
[will-navigate]: ../api/web-contents.md#event-will-navigate
|
||||
[will-navigate]: ../api/web-contents.md#event-will-navigateexperimental
|
||||
[open-external]: ../api/shell.md#shellopenexternalurl-options
|
||||
[sandbox]: ../api/sandbox-option.md
|
||||
[responsible-disclosure]: https://en.wikipedia.org/wiki/Responsible_disclosure
|
||||
|
||||
@@ -73,6 +73,7 @@ auto_filenames = {
|
||||
"docs/api/webview-tag.md",
|
||||
"docs/api/window-open.md",
|
||||
"docs/api/structures/bluetooth-device.md",
|
||||
"docs/api/structures/cancellable-navigation-event.md",
|
||||
"docs/api/structures/certificate-principal.md",
|
||||
"docs/api/structures/certificate.md",
|
||||
"docs/api/structures/cookie.md",
|
||||
|
||||
@@ -574,6 +574,31 @@ WebContents.prototype._init = function () {
|
||||
ipcMain.emit(channel, event, message);
|
||||
});
|
||||
|
||||
const handleCustomErrorPageEvent = (eventName: string) => {
|
||||
this.on(`-${eventName}` as any, function (this: any, event: any, ...args: any[]) {
|
||||
let allowReturnValue = true;
|
||||
Object.defineProperty(event, 'returnValue', {
|
||||
set: (value) => {
|
||||
if (!allowReturnValue) return;
|
||||
|
||||
if (typeof value !== 'object' || !value) { throw new TypeError(`event.returnValue must be set to a non-null object, was set to a "${typeof value}"`); }
|
||||
if (typeof value.errorCode !== 'number') { throw new TypeError(`event.returnValue.errorCode must be set to a number, was set to a "${typeof value.errorCode}"`); }
|
||||
if (value.errorCode >= 0) { throw new TypeError(`event.returnValue.errorCode must be negative, was set to "${value.errorCode}"`); }
|
||||
if (value.errorPage && typeof value.errorPage !== 'string') { throw new TypeError(`event.returnValue.errorPage must be set to a string if provided, was set to a "${typeof value.errorPage}"`); }
|
||||
if (typeof value.errorPage === 'string' && value.errorPage.length === 0) { throw new Error('event.returnValue.errorPage must be a non-empty string, an empty string was provided'); }
|
||||
if (value.errorPage && value.errorCode === -3) { throw new Error('event.returnValue.errorCode can not be set to "-2" when an errorPage is provided'); }
|
||||
event.preventDefault();
|
||||
event.sendReply(value);
|
||||
},
|
||||
get: () => {}
|
||||
});
|
||||
this.emit(eventName, event, ...args);
|
||||
allowReturnValue = false;
|
||||
});
|
||||
};
|
||||
handleCustomErrorPageEvent('will-navigate');
|
||||
handleCustomErrorPageEvent('will-fail-load');
|
||||
|
||||
// Handle context menu action request from pepper plugin.
|
||||
this.on('pepper-context-menu' as any, function (event: any, params: {x: number, y: number, menu: Array<(MenuItemConstructorOptions) | (MenuItem)>}, callback: () => void) {
|
||||
// Access Menu via electron.Menu to prevent circular require.
|
||||
|
||||
@@ -1470,7 +1470,8 @@ void WebContents::DidStopLoading() {
|
||||
|
||||
bool WebContents::EmitNavigationEvent(
|
||||
const std::string& event,
|
||||
content::NavigationHandle* navigation_handle) {
|
||||
content::NavigationHandle* navigation_handle,
|
||||
gin_helper::Event::ValueCallback callback) {
|
||||
bool is_main_frame = navigation_handle->IsInMainFrame();
|
||||
int frame_tree_node_id = navigation_handle->GetFrameTreeNodeId();
|
||||
content::FrameTreeNode* frame_tree_node =
|
||||
@@ -1490,8 +1491,18 @@ bool WebContents::EmitNavigationEvent(
|
||||
}
|
||||
bool is_same_document = navigation_handle->IsSameDocument();
|
||||
auto url = navigation_handle->GetURL();
|
||||
return Emit(event, url, is_same_document, is_main_frame, frame_process_id,
|
||||
frame_routing_id);
|
||||
int code = navigation_handle->GetNetErrorCode();
|
||||
auto description = net::ErrorToShortString(code);
|
||||
return EmitWithSender(event, nullptr, std::move(callback), url,
|
||||
is_same_document, is_main_frame, frame_process_id,
|
||||
frame_routing_id, code, description);
|
||||
}
|
||||
|
||||
bool WebContents::EmitNavigationEvent(
|
||||
const std::string& event,
|
||||
content::NavigationHandle* navigation_handle) {
|
||||
return EmitNavigationEvent(event, navigation_handle,
|
||||
gin_helper::Event::ValueCallback());
|
||||
}
|
||||
|
||||
void WebContents::BindElectronBrowser(
|
||||
@@ -1513,8 +1524,9 @@ void WebContents::Message(bool internal,
|
||||
TRACE_EVENT1("electron", "WebContents::Message", "channel", channel);
|
||||
// webContents.emit('-ipc-message', new Event(), internal, channel,
|
||||
// arguments);
|
||||
EmitWithSender("-ipc-message", receivers_.current_context(), InvokeCallback(),
|
||||
internal, channel, std::move(arguments));
|
||||
EmitWithSender("-ipc-message", receivers_.current_context(),
|
||||
gin_helper::Event::ValueCallback(), internal, channel,
|
||||
std::move(arguments));
|
||||
}
|
||||
|
||||
void WebContents::Invoke(bool internal,
|
||||
@@ -1524,7 +1536,9 @@ void WebContents::Invoke(bool internal,
|
||||
TRACE_EVENT1("electron", "WebContents::Invoke", "channel", channel);
|
||||
// webContents.emit('-ipc-invoke', new Event(), internal, channel, arguments);
|
||||
EmitWithSender("-ipc-invoke", receivers_.current_context(),
|
||||
std::move(callback), internal, channel, std::move(arguments));
|
||||
gin_helper::Event::AdaptInvokeCallbackToValueCallback(
|
||||
std::move(callback)),
|
||||
internal, channel, std::move(arguments));
|
||||
}
|
||||
|
||||
void WebContents::OnFirstNonEmptyLayout() {
|
||||
@@ -1541,8 +1555,9 @@ void WebContents::ReceivePostMessage(const std::string& channel,
|
||||
MessagePort::EntanglePorts(isolate, std::move(message.ports));
|
||||
v8::Local<v8::Value> message_value =
|
||||
electron::DeserializeV8Value(isolate, message);
|
||||
EmitWithSender("-ipc-ports", receivers_.current_context(), InvokeCallback(),
|
||||
false, channel, message_value, std::move(wrapped_ports));
|
||||
EmitWithSender("-ipc-ports", receivers_.current_context(),
|
||||
gin_helper::Event::ValueCallback(), false, channel,
|
||||
message_value, std::move(wrapped_ports));
|
||||
}
|
||||
|
||||
void WebContents::PostMessage(const std::string& channel,
|
||||
@@ -1586,7 +1601,9 @@ void WebContents::MessageSync(bool internal,
|
||||
// webContents.emit('-ipc-message-sync', new Event(sender, message), internal,
|
||||
// channel, arguments);
|
||||
EmitWithSender("-ipc-message-sync", receivers_.current_context(),
|
||||
std::move(callback), internal, channel, std::move(arguments));
|
||||
gin_helper::Event::AdaptInvokeCallbackToValueCallback(
|
||||
std::move(callback)),
|
||||
internal, channel, std::move(arguments));
|
||||
}
|
||||
|
||||
void WebContents::MessageTo(bool internal,
|
||||
@@ -1608,7 +1625,8 @@ void WebContents::MessageHost(const std::string& channel,
|
||||
TRACE_EVENT1("electron", "WebContents::MessageHost", "channel", channel);
|
||||
// webContents.emit('ipc-message-host', new Event(), channel, args);
|
||||
EmitWithSender("ipc-message-host", receivers_.current_context(),
|
||||
InvokeCallback(), channel, std::move(arguments));
|
||||
gin_helper::Event::ValueCallback(), channel,
|
||||
std::move(arguments));
|
||||
}
|
||||
|
||||
void WebContents::UpdateDraggableRegions(
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include "mojo/public/cpp/bindings/receiver_set.h"
|
||||
#include "printing/buildflags/buildflags.h"
|
||||
#include "services/service_manager/public/cpp/binder_registry.h"
|
||||
#include "shell/browser/api/event.h"
|
||||
#include "shell/browser/api/frame_subscriber.h"
|
||||
#include "shell/browser/api/save_page_handler.h"
|
||||
#include "shell/browser/event_emitter_mixin.h"
|
||||
@@ -408,11 +409,15 @@ class WebContents : public gin::Wrappable<WebContents>,
|
||||
bool EmitNavigationEvent(const std::string& event,
|
||||
content::NavigationHandle* navigation_handle);
|
||||
|
||||
bool EmitNavigationEvent(const std::string& event,
|
||||
content::NavigationHandle* navigation_handle,
|
||||
gin_helper::Event::ValueCallback callback);
|
||||
|
||||
// this.emit(name, new Event(sender, message), args...);
|
||||
template <typename... Args>
|
||||
bool EmitWithSender(base::StringPiece name,
|
||||
content::RenderFrameHost* sender,
|
||||
electron::mojom::ElectronBrowser::InvokeCallback callback,
|
||||
gin_helper::Event::ValueCallback callback,
|
||||
Args&&... args) {
|
||||
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
|
||||
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
|
||||
|
||||
@@ -14,6 +14,22 @@
|
||||
|
||||
namespace gin_helper {
|
||||
|
||||
namespace {
|
||||
|
||||
bool InvokeCallbackAdapter(Event::InvokeCallback callback,
|
||||
v8::Local<v8::Value> result) {
|
||||
v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate();
|
||||
blink::CloneableMessage message;
|
||||
if (!gin::ConvertFromV8(isolate, result, &message)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::move(callback).Run(std::move(message));
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
gin::WrapperInfo Event::kWrapperInfo = {gin::kEmbedderNativeGin};
|
||||
|
||||
Event::Event() {}
|
||||
@@ -29,7 +45,7 @@ Event::~Event() {
|
||||
}
|
||||
}
|
||||
|
||||
void Event::SetCallback(InvokeCallback callback) {
|
||||
void Event::SetCallback(ValueCallback callback) {
|
||||
DCHECK(!callback_);
|
||||
callback_ = std::move(callback);
|
||||
}
|
||||
@@ -45,13 +61,7 @@ bool Event::SendReply(v8::Isolate* isolate, v8::Local<v8::Value> result) {
|
||||
if (!callback_)
|
||||
return false;
|
||||
|
||||
blink::CloneableMessage message;
|
||||
if (!gin::ConvertFromV8(isolate, result, &message)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::move(callback_).Run(std::move(message));
|
||||
return true;
|
||||
return std::move(callback_).Run(result);
|
||||
}
|
||||
|
||||
gin::ObjectTemplateBuilder Event::GetObjectTemplateBuilder(
|
||||
@@ -70,4 +80,12 @@ gin::Handle<Event> Event::Create(v8::Isolate* isolate) {
|
||||
return gin::CreateHandle(isolate, new Event());
|
||||
}
|
||||
|
||||
Event::ValueCallback Event::AdaptInvokeCallbackToValueCallback(
|
||||
Event::InvokeCallback callback) {
|
||||
if (!callback)
|
||||
return ValueCallback();
|
||||
|
||||
return base::BindOnce(InvokeCallbackAdapter, std::move(callback));
|
||||
}
|
||||
|
||||
} // namespace gin_helper
|
||||
|
||||
@@ -18,17 +18,21 @@ namespace gin_helper {
|
||||
class Event : public gin::Wrappable<Event> {
|
||||
public:
|
||||
using InvokeCallback = electron::mojom::ElectronBrowser::InvokeCallback;
|
||||
using ValueCallback = base::OnceCallback<bool(v8::Local<v8::Value>)>;
|
||||
|
||||
static gin::WrapperInfo kWrapperInfo;
|
||||
|
||||
static gin::Handle<Event> Create(v8::Isolate* isolate);
|
||||
|
||||
// Pass the callback to be invoked.
|
||||
void SetCallback(InvokeCallback callback);
|
||||
static ValueCallback AdaptInvokeCallbackToValueCallback(
|
||||
InvokeCallback callback);
|
||||
|
||||
// event.PreventDefault().
|
||||
void PreventDefault(v8::Isolate* isolate);
|
||||
|
||||
// Pass the callback to be invoked.
|
||||
void SetCallback(ValueCallback callback);
|
||||
|
||||
// event.sendReply(value), used for replying to synchronous messages and
|
||||
// `invoke` calls.
|
||||
bool SendReply(v8::Isolate* isolate, v8::Local<v8::Value> result);
|
||||
@@ -44,7 +48,7 @@ class Event : public gin::Wrappable<Event> {
|
||||
|
||||
private:
|
||||
// Replyer for the synchronous messages.
|
||||
InvokeCallback callback_;
|
||||
ValueCallback callback_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(Event);
|
||||
};
|
||||
|
||||
@@ -4,12 +4,32 @@
|
||||
|
||||
#include "shell/browser/electron_navigation_throttle.h"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "content/public/browser/navigation_handle.h"
|
||||
#include "shell/browser/api/electron_api_web_contents.h"
|
||||
#include "shell/browser/javascript_environment.h"
|
||||
#include "shell/common/gin_helper/dictionary.h"
|
||||
|
||||
namespace electron {
|
||||
|
||||
namespace {
|
||||
|
||||
bool HandleEventReturnValue(int* error_code,
|
||||
std::string* error_html,
|
||||
v8::Local<v8::Value> val) {
|
||||
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
|
||||
gin_helper::Dictionary dict;
|
||||
gin::ConvertFromV8(isolate, val, &dict);
|
||||
if (!dict.Get("errorCode", error_code))
|
||||
return false;
|
||||
dict.Get("errorPage", error_html);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ElectronNavigationThrottle::ElectronNavigationThrottle(
|
||||
content::NavigationHandle* navigation_handle)
|
||||
: content::NavigationThrottle(navigation_handle) {}
|
||||
@@ -21,7 +41,9 @@ const char* ElectronNavigationThrottle::GetNameForLogging() {
|
||||
}
|
||||
|
||||
content::NavigationThrottle::ThrottleCheckResult
|
||||
ElectronNavigationThrottle::WillStartRequest() {
|
||||
ElectronNavigationThrottle::DelegateEventToWebContents(
|
||||
const std::string& event_name,
|
||||
net::Error error_code) {
|
||||
auto* handle = navigation_handle();
|
||||
auto* contents = handle->GetWebContents();
|
||||
if (!contents) {
|
||||
@@ -37,13 +59,37 @@ ElectronNavigationThrottle::WillStartRequest() {
|
||||
return PROCEED;
|
||||
}
|
||||
|
||||
if (handle->IsRendererInitiated() && handle->IsInMainFrame() &&
|
||||
api_contents->EmitNavigationEvent("will-navigate", handle)) {
|
||||
return CANCEL;
|
||||
int error_code_int = error_code;
|
||||
std::string error_html;
|
||||
// JS ensures that this callback can not be called out-of-stack
|
||||
if (api_contents->EmitNavigationEvent(
|
||||
event_name, handle,
|
||||
base::BindOnce(&HandleEventReturnValue,
|
||||
base::Unretained(&error_code_int),
|
||||
base::Unretained(&error_html)))) {
|
||||
if (!error_html.empty()) {
|
||||
return content::NavigationThrottle::ThrottleCheckResult(
|
||||
CANCEL, error_code, error_html);
|
||||
}
|
||||
return content::NavigationThrottle::ThrottleCheckResult(CANCEL, error_code);
|
||||
}
|
||||
return PROCEED;
|
||||
}
|
||||
|
||||
content::NavigationThrottle::ThrottleCheckResult
|
||||
ElectronNavigationThrottle::WillFailRequest() {
|
||||
return DelegateEventToWebContents("-will-fail-load",
|
||||
navigation_handle()->GetNetErrorCode());
|
||||
}
|
||||
|
||||
content::NavigationThrottle::ThrottleCheckResult
|
||||
ElectronNavigationThrottle::WillStartRequest() {
|
||||
auto* handle = navigation_handle();
|
||||
if (handle->IsRendererInitiated() && handle->IsInMainFrame())
|
||||
return DelegateEventToWebContents("-will-navigate", net::ERR_ABORTED);
|
||||
return PROCEED;
|
||||
}
|
||||
|
||||
content::NavigationThrottle::ThrottleCheckResult
|
||||
ElectronNavigationThrottle::WillRedirectRequest() {
|
||||
auto* handle = navigation_handle();
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
#ifndef SHELL_BROWSER_ELECTRON_NAVIGATION_THROTTLE_H_
|
||||
#define SHELL_BROWSER_ELECTRON_NAVIGATION_THROTTLE_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "content/public/browser/navigation_throttle.h"
|
||||
|
||||
namespace electron {
|
||||
@@ -16,12 +18,18 @@ class ElectronNavigationThrottle : public content::NavigationThrottle {
|
||||
|
||||
ElectronNavigationThrottle::ThrottleCheckResult WillStartRequest() override;
|
||||
|
||||
ElectronNavigationThrottle::ThrottleCheckResult WillFailRequest() override;
|
||||
|
||||
ElectronNavigationThrottle::ThrottleCheckResult WillRedirectRequest()
|
||||
override;
|
||||
|
||||
const char* GetNameForLogging() override;
|
||||
|
||||
private:
|
||||
content::NavigationThrottle::ThrottleCheckResult DelegateEventToWebContents(
|
||||
const std::string& event_name,
|
||||
net::Error error_code);
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(ElectronNavigationThrottle);
|
||||
};
|
||||
|
||||
|
||||
@@ -49,13 +49,12 @@ v8::Local<v8::Object> CreateEvent(v8::Isolate* isolate,
|
||||
return event;
|
||||
}
|
||||
|
||||
v8::Local<v8::Object> CreateNativeEvent(
|
||||
v8::Isolate* isolate,
|
||||
v8::Local<v8::Object> sender,
|
||||
content::RenderFrameHost* frame,
|
||||
electron::mojom::ElectronBrowser::MessageSyncCallback callback) {
|
||||
v8::Local<v8::Object> CreateNativeEvent(v8::Isolate* isolate,
|
||||
v8::Local<v8::Object> sender,
|
||||
content::RenderFrameHost* frame,
|
||||
Event::ValueCallback callback) {
|
||||
v8::Local<v8::Object> event;
|
||||
if (frame && callback) {
|
||||
if (callback) {
|
||||
gin::Handle<Event> native_event = Event::Create(isolate);
|
||||
native_event->SetCallback(std::move(callback));
|
||||
event = v8::Local<v8::Object>::Cast(native_event.ToV8());
|
||||
|
||||
@@ -29,7 +29,7 @@ v8::Local<v8::Object> CreateNativeEvent(
|
||||
v8::Isolate* isolate,
|
||||
v8::Local<v8::Object> sender,
|
||||
content::RenderFrameHost* frame,
|
||||
electron::mojom::ElectronBrowser::MessageSyncCallback callback);
|
||||
base::OnceCallback<bool(v8::Local<v8::Value>)> callback);
|
||||
|
||||
} // namespace internal
|
||||
|
||||
|
||||
@@ -325,6 +325,8 @@
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/klaw/-/klaw-3.0.1.tgz#29f90021c0234976aa4eb97efced9cb6db9fa8b3"
|
||||
integrity sha512-acnF3n9mYOr1aFJKFyvfNX0am9EtPUsYPq22QUCGdJE+MVt6UyAN1jwo+PmOPqXD4K7ZS9MtxDEp/un0lxFccA==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/linkify-it@*":
|
||||
version "2.1.0"
|
||||
|
||||
Reference in New Issue
Block a user