Skip to content

Commit

Permalink
feat: add will-frame-navigate event (#34418)
Browse files Browse the repository at this point in the history
  • Loading branch information
Milan Burda committed Apr 12, 2023
1 parent 5161e96 commit 1219649
Show file tree
Hide file tree
Showing 17 changed files with 683 additions and 35 deletions.
3 changes: 0 additions & 3 deletions docs/api/structures/event.md

This file was deleted.

67 changes: 64 additions & 3 deletions docs/api/web-contents.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,36 @@ const contents = win.webContents
console.log(contents)
```

## Navigation Events

Several events can be used to monitor navigations as they occur within a `webContents`.

### Document Navigations

When a `webContents` navigates to another page (as opposed to an [in-page navigation](web-contents.md#in-page-navigation)), the following events will be fired.

* [`did-start-navigation`](web-contents.md#event-did-start-navigation)
* [`will-frame-navigate`](web-contents.md#event-will-frame-navigate)
* [`will-navigate`](web-contents.md#event-will-navigate) (only fired when main frame navigates)
* [`will-redirect`](web-contents.md#event-will-redirect) (only fired when a redirect happens during navigation)
* [`did-redirect-navigation`](web-contents.md#event-did-redirect-navigation) (only fired when a redirect happens during navigation)
* [`did-frame-navigate`](web-contents.md#event-did-frame-navigate)
* [`did-navigate`](web-contents.md#event-did-navigate) (only fired when main frame navigates)

Subsequent events will not fire if `event.preventDefault()` is called on any of the cancellable events.

### In-page Navigation

In-page navigations don't cause the page to reload, but instead navigate to a location within the current page. These events are not cancellable. For an in-page navigations, the following events will fire in this order:

* [`did-start-navigation`](web-contents.md#event-did-start-navigation)
* [`did-navigate-in-page`](web-contents.md#event-did-navigate-in-page)

### Frame Navigation

The [`will-navigate`](web-contents.md#event-will-navigate) and [`did-navigate`](web-contents.md#event-did-navigate) events only fire when the [mainFrame](web-contents.md#contentsmainframe-readonly) navigates.
If you want to also observe navigations in `<iframe>`s, use [`will-frame-navigate`](web-contents.md#event-will-frame-navigate) and [`did-frame-navigate`](web-contents.md#event-did-frame-navigate) events.

## Methods

These methods can be accessed from the `webContents` module:
Expand Down Expand Up @@ -210,9 +240,40 @@ Returns:
* `event` Event
* `url` string

Emitted when a user or the page wants to start navigation. It can happen when
Emitted when a user or the page wants to start navigation on the main frame. It can happen when
the `window.location` object is changed or a user clicks a link in the page.

This event will not emit when the navigation is started programmatically with
APIs like `webContents.loadURL` and `webContents.back`.

It is also not emitted for in-page navigations, such as clicking anchor links
or updating the `window.location.hash`. Use `did-navigate-in-page` event for
this purpose.

Calling `event.preventDefault()` will prevent the navigation.

#### Event: 'will-frame-navigate'

Returns:

* `details` Event<>
* `url` string - The URL the frame is navigating to.
* `isSameDocument` boolean - Whether the navigation happened without changing
document. Examples of same document navigations are reference fragment
navigations, pushState/replaceState, and same page history navigation.
* `isMainFrame` boolean - True if the navigation is taking place in a main frame.
* `frame` WebFrameMain - The frame to be navigated.
* `initiator` WebFrameMain (optional) - The frame which initiated the
navigation, which can be a parent frame (e.g. via `window.open` with a
frame's name), or null if the navigation was not initiated by a frame. This
can also be null if the initiating frame was deleted before the event was
emitted.

Emitted when a user or the page wants to start navigation in any frame. It can happen when
the `window.location` object is changed or a user clicks a link in the page.

Unlike `will-navigate`, `will-frame-navigate` is fired when the main frame or any of its subframes attempts to navigate. When the navigation event comes from the main frame, `isMainFrame` will be `true`.

This event will not emit when the navigation is started programmatically with
APIs like `webContents.loadURL` and `webContents.back`.

Expand Down Expand Up @@ -830,7 +891,7 @@ Emitted when the preload script `preloadPath` throws an unhandled exception `err

Returns:

* `event` Event
* `event` [IpcMainEvent](structures/ipc-main-event.md)
* `channel` string
* `...args` any[]

Expand All @@ -842,7 +903,7 @@ See also [`webContents.ipc`](#contentsipc-readonly), which provides an [`IpcMain

Returns:

* `event` Event
* `event` [IpcMainEvent](structures/ipc-main-event.md)
* `channel` string
* `...args` any[]

Expand Down
22 changes: 22 additions & 0 deletions docs/api/webview-tag.md
Original file line number Diff line number Diff line change
Expand Up @@ -823,6 +823,28 @@ this purpose.

Calling `event.preventDefault()` does __NOT__ have any effect.

### Event: 'will-frame-navigate'

Returns:

* `url` string
* `isMainFrame` boolean
* `frameProcessId` Integer
* `frameRoutingId` Integer

Emitted when a user or the page wants to start navigation anywhere in the `<webview>`
or any frames embedded within. It can happen when the `window.location` object is
changed or a user clicks a link in the page.

This event will not emit when the navigation is started programmatically with
APIs like `<webview>.loadURL` and `<webview>.back`.

It is also not emitted during in-page navigation, such as clicking anchor links
or updating the `window.location.hash`. Use `did-navigate-in-page` event for
this purpose.

Calling `event.preventDefault()` does __NOT__ have any effect.

### Event: 'did-start-navigation'

Returns:
Expand Down
1 change: 0 additions & 1 deletion filenames.auto.gni
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,6 @@ auto_filenames = {
"docs/api/structures/custom-scheme.md",
"docs/api/structures/desktop-capturer-source.md",
"docs/api/structures/display.md",
"docs/api/structures/event.md",
"docs/api/structures/extension-info.md",
"docs/api/structures/extension.md",
"docs/api/structures/file-filter.md",
Expand Down
2 changes: 2 additions & 0 deletions filenames.gni
Original file line number Diff line number Diff line change
Expand Up @@ -608,6 +608,8 @@ filenames = {
"shell/common/gin_helper/event_emitter.h",
"shell/common/gin_helper/event_emitter_caller.cc",
"shell/common/gin_helper/event_emitter_caller.h",
"shell/common/gin_helper/event_new.cc",
"shell/common/gin_helper/event_new.h",
"shell/common/gin_helper/function_template.cc",
"shell/common/gin_helper/function_template.h",
"shell/common/gin_helper/function_template_extensions.h",
Expand Down
10 changes: 10 additions & 0 deletions lib/browser/guest-view-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,16 @@ const createGuest = function (embedder: Electron.WebContents, embedderFrameId: n
});
});

// Dispatch guest's frame navigation event to embedder.
guest.on('will-frame-navigate', function (event: Electron.WebContentsWillFrameNavigateEventParams) {
sendToEmbedder(IPC_MESSAGES.GUEST_VIEW_INTERNAL_DISPATCH_EVENT, 'will-frame-navigate', {
url: event.url,
isMainFrame: event.isMainFrame,
frameProcessId: event.frame.processId,
frameRoutingId: event.frame.routingId
});
});

// Notify guest of embedder window visibility when it is ready
// FIXME Remove once https://github.com/electron/electron/issues/6828 is fixed
guest.on('dom-ready', function () {
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
"devDependencies": {
"@azure/storage-blob": "^12.9.0",
"@electron/asar": "^3.2.1",
"@electron/docs-parser": "^1.0.0",
"@electron/docs-parser": "^1.1.0",
"@electron/fiddle-core": "^1.0.4",
"@electron/github-app-auth": "^1.5.0",
"@electron/typescript-definitions": "^8.10.0",
"@electron/typescript-definitions": "^8.14.0",
"@octokit/rest": "^19.0.7",
"@primer/octicons": "^10.0.0",
"@types/basic-auth": "^1.1.3",
Expand Down
68 changes: 57 additions & 11 deletions shell/browser/api/electron_api_web_contents.cc
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@
#include "shell/common/gin_converters/net_converter.h"
#include "shell/common/gin_converters/value_converter.h"
#include "shell/common/gin_helper/dictionary.h"
#include "shell/common/gin_helper/event_new.h"
#include "shell/common/gin_helper/object_template_builder.h"
#include "shell/common/language_util.h"
#include "shell/common/mouse_util.h"
Expand Down Expand Up @@ -624,6 +625,23 @@ void SetBackgroundColor(content::RenderWidgetHostView* rwhv, SkColor color) {
->SetContentBackgroundColor(color);
}

content::RenderFrameHost* GetRenderFrameHost(
content::NavigationHandle* navigation_handle) {
int frame_tree_node_id = navigation_handle->GetFrameTreeNodeId();
content::FrameTreeNode* frame_tree_node =
content::FrameTreeNode::GloballyFindByID(frame_tree_node_id);
content::RenderFrameHostManager* render_manager =
frame_tree_node->render_manager();
content::RenderFrameHost* frame_host = nullptr;
if (render_manager) {
frame_host = render_manager->speculative_frame_host();
if (!frame_host)
frame_host = render_manager->current_frame_host();
}

return frame_host;
}

} // namespace

#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
Expand Down Expand Up @@ -1765,18 +1783,8 @@ bool WebContents::EmitNavigationEvent(
const std::string& event,
content::NavigationHandle* navigation_handle) {
bool is_main_frame = navigation_handle->IsInMainFrame();
int frame_tree_node_id = navigation_handle->GetFrameTreeNodeId();
content::FrameTreeNode* frame_tree_node =
content::FrameTreeNode::GloballyFindByID(frame_tree_node_id);
content::RenderFrameHostManager* render_manager =
frame_tree_node->render_manager();
content::RenderFrameHost* frame_host = nullptr;
if (render_manager) {
frame_host = render_manager->speculative_frame_host();
if (!frame_host)
frame_host = render_manager->current_frame_host();
}
int frame_process_id = -1, frame_routing_id = -1;
content::RenderFrameHost* frame_host = GetRenderFrameHost(navigation_handle);
if (frame_host) {
frame_process_id = frame_host->GetProcess()->GetID();
frame_routing_id = frame_host->GetRoutingID();
Expand All @@ -1787,6 +1795,44 @@ bool WebContents::EmitNavigationEvent(
frame_routing_id);
}

bool WebContents::EmitNavigationEventNew(
const std::string& event_name,
content::NavigationHandle* navigation_handle) {
bool is_main_frame = navigation_handle->IsInMainFrame();
int frame_process_id = -1, frame_routing_id = -1;
content::RenderFrameHost* frame_host = GetRenderFrameHost(navigation_handle);
if (frame_host) {
frame_process_id = frame_host->GetProcess()->GetID();
frame_routing_id = frame_host->GetRoutingID();
}
bool is_same_document = navigation_handle->IsSameDocument();
auto url = navigation_handle->GetURL();
content::RenderFrameHost* initiator_frame_host =
navigation_handle->GetInitiatorFrameToken().has_value()
? content::RenderFrameHost::FromFrameToken(
navigation_handle->GetInitiatorProcessID(),
navigation_handle->GetInitiatorFrameToken().value())
: nullptr;

v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
v8::HandleScope handle_scope(isolate);

gin::Handle<gin_helper::internal::Event> event =
gin_helper::internal::Event::New(isolate);
v8::Local<v8::Object> event_object = event.ToV8().As<v8::Object>();

gin_helper::Dictionary dict(isolate, event_object);
dict.Set("url", url);
dict.Set("isSameDocument", is_same_document);
dict.Set("isMainFrame", is_main_frame);
dict.Set("frame", frame_host);
dict.SetGetter("initiator", initiator_frame_host);

EmitWithoutCustomEvent(event_name, event, url, is_same_document,
is_main_frame, frame_process_id, frame_routing_id);
return event->GetDefaultPrevented();
}

void WebContents::Message(bool internal,
const std::string& channel,
blink::CloneableMessage arguments,
Expand Down
2 changes: 2 additions & 0 deletions shell/browser/api/electron_api_web_contents.h
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,8 @@ class WebContents : public ExclusiveAccessContext,

bool EmitNavigationEvent(const std::string& event,
content::NavigationHandle* navigation_handle);
bool EmitNavigationEventNew(const std::string& event,
content::NavigationHandle* navigation_handle);

// this.emit(name, new Event(sender, message), args...);
template <typename... Args>
Expand Down
4 changes: 4 additions & 0 deletions shell/browser/electron_navigation_throttle.cc
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ ElectronNavigationThrottle::WillStartRequest() {
return PROCEED;
}

if (handle->IsRendererInitiated() &&
api_contents->EmitNavigationEventNew("will-frame-navigate", handle)) {
return CANCEL;
}
if (handle->IsRendererInitiated() && handle->IsInMainFrame() &&
api_contents->EmitNavigationEvent("will-navigate", handle)) {
return CANCEL;
Expand Down
31 changes: 31 additions & 0 deletions shell/common/gin_helper/event_new.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright (c) 2023 Salesforce, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.

#include "shell/common/gin_helper/event_new.h"
#include "gin/dictionary.h"
#include "gin/object_template_builder.h"

namespace gin_helper::internal {

// static
gin::Handle<Event> Event::New(v8::Isolate* isolate) {
return gin::CreateHandle(isolate, new Event());
}
// static
v8::Local<v8::ObjectTemplate> Event::FillObjectTemplate(
v8::Isolate* isolate,
v8::Local<v8::ObjectTemplate> templ) {
return gin::ObjectTemplateBuilder(isolate, "Event", templ)
.SetMethod("preventDefault", &Event::PreventDefault)
.SetProperty("defaultPrevented", &Event::GetDefaultPrevented)
.Build();
}

Event::Event() = default;

Event::~Event() = default;

gin::WrapperInfo Event::kWrapperInfo = {gin::kEmbedderNativeGin};

} // namespace gin_helper::internal
48 changes: 48 additions & 0 deletions shell/common/gin_helper/event_new.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright (c) 2023 Salesforce, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.

#ifndef ELECTRON_SHELL_COMMON_GIN_HELPER_EVENT_NEW_H_
#define ELECTRON_SHELL_COMMON_GIN_HELPER_EVENT_NEW_H_

#include "gin/handle.h"
#include "gin/wrappable.h"
#include "shell/common/gin_helper/constructible.h"

namespace v8 {
class Isolate;
template <typename T>
class Local;
class Object;
class ObjectTemplate;
} // namespace v8

namespace gin_helper::internal {

class Event : public gin::Wrappable<Event>,
public gin_helper::Constructible<Event> {
public:
// gin_helper::Constructible
static gin::Handle<Event> New(v8::Isolate* isolate);
static v8::Local<v8::ObjectTemplate> FillObjectTemplate(
v8::Isolate* isolate,
v8::Local<v8::ObjectTemplate> prototype);

// gin::Wrappable
static gin::WrapperInfo kWrapperInfo;

~Event() override;

void PreventDefault() { default_prevented_ = true; }

bool GetDefaultPrevented() { return default_prevented_; }

private:
Event();

bool default_prevented_ = false;
};

} // namespace gin_helper::internal

#endif // ELECTRON_SHELL_COMMON_GIN_HELPER_EVENT_NEW_H_

0 comments on commit 1219649

Please sign in to comment.