Compare commits

...

6 Commits

Author SHA1 Message Date
Samuel Attard
77189f7a39 chore: fix lint 2020-11-10 14:07:04 -08:00
Samuel Attard
28b794216d fixup tests 2020-11-10 14:03:02 -08:00
Samuel Attard
58d9c451c6 chore: fixup linting 2020-11-10 14:03:00 -08:00
Samuel Attard
71d8a4a466 chore: fixup docs 2020-11-10 14:02:22 -08:00
Samuel Attard
825f3a3a7d chore: fixup for review 2020-11-10 14:02:22 -08:00
Samuel Attard
b2625db2ba feat: add support for will-navigate and will-fail-load custom error pages 2020-11-10 14:02:20 -08:00
14 changed files with 190 additions and 36 deletions

View 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.

View File

@@ -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:

View File

@@ -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

View File

@@ -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",

View File

@@ -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.

View File

@@ -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(

View File

@@ -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();

View File

@@ -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

View File

@@ -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);
};

View File

@@ -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();

View File

@@ -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);
};

View File

@@ -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());

View File

@@ -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

View File

@@ -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"