Skip to content

Commit

Permalink
Add basic download events (#57) (#285)
Browse files Browse the repository at this point in the history
  • Loading branch information
basyskom-bmeier authored Jul 1, 2024
1 parent 08eee67 commit 35c92a1
Show file tree
Hide file tree
Showing 5 changed files with 213 additions and 19 deletions.
44 changes: 25 additions & 19 deletions lib/src/enums.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ enum LoadingState { none, loading, navigationCompleted }
// Order must match WebviewPointerButton (see webview.h)
enum PointerButton { none, primary, secondary, tertiary }

enum WebviewDownloadEventKind {
downloadStarted,
downloadCompleted,
downloadProgress
}

/// Pointer Event kind
// Order must match WebviewPointerEventKind (see webview.h)
enum WebviewPointerEventKind { activate, down, enter, leave, up, update }
Expand Down Expand Up @@ -44,23 +50,23 @@ enum WebviewPopupWindowPolicy { allow, deny, sameWindow }
enum WebviewHostResourceAccessKind { deny, allow, denyCors }

enum WebErrorStatus {
WebErrorStatusUnknown,
WebErrorStatusCertificateCommonNameIsIncorrect,
WebErrorStatusCertificateExpired,
WebErrorStatusClientCertificateContainsErrors,
WebErrorStatusCertificateRevoked,
WebErrorStatusCertificateIsInvalid,
WebErrorStatusServerUnreachable,
WebErrorStatusTimeout,
WebErrorStatusErrorHTTPInvalidServerResponse,
WebErrorStatusConnectionAborted,
WebErrorStatusConnectionReset,
WebErrorStatusDisconnected,
WebErrorStatusCannotConnect,
WebErrorStatusHostNameNotResolved,
WebErrorStatusOperationCanceled,
WebErrorStatusRedirectFailed,
WebErrorStatusUnexpectedError,
WebErrorStatusValidAuthenticationCredentialsRequired,
WebErrorStatusValidProxyAuthenticationRequired,
WebErrorStatusUnknown,
WebErrorStatusCertificateCommonNameIsIncorrect,
WebErrorStatusCertificateExpired,
WebErrorStatusClientCertificateContainsErrors,
WebErrorStatusCertificateRevoked,
WebErrorStatusCertificateIsInvalid,
WebErrorStatusServerUnreachable,
WebErrorStatusTimeout,
WebErrorStatusErrorHTTPInvalidServerResponse,
WebErrorStatusConnectionAborted,
WebErrorStatusConnectionReset,
WebErrorStatusDisconnected,
WebErrorStatusCannotConnect,
WebErrorStatusHostNameNotResolved,
WebErrorStatusOperationCanceled,
WebErrorStatusRedirectFailed,
WebErrorStatusUnexpectedError,
WebErrorStatusValidAuthenticationCredentialsRequired,
WebErrorStatusValidProxyAuthenticationRequired,
}
32 changes: 32 additions & 0 deletions lib/src/webview.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,21 @@ class HistoryChanged {
const HistoryChanged(this.canGoBack, this.canGoForward);
}

class WebviewDownloadEvent {
final WebviewDownloadEventKind kind;
final String url;
final String resultFilePath;
final int bytesReceived;
final int totalBytesToReceive;
const WebviewDownloadEvent(
this.kind,
this.url,
this.resultFilePath,
this.bytesReceived,
this.totalBytesToReceive,
);
}

typedef PermissionRequestedDelegate
= FutureOr<WebviewPermissionDecision> Function(
String url, WebviewPermissionKind permissionKind, bool isUserInitiated);
Expand Down Expand Up @@ -111,12 +126,19 @@ class WebviewController extends ValueNotifier<WebviewValue> {

final StreamController<LoadingState> _loadingStateStreamController =
StreamController<LoadingState>.broadcast();

final StreamController<WebviewDownloadEvent> _downloadEventStreamController =
StreamController<WebviewDownloadEvent>.broadcast();

final StreamController<WebErrorStatus> _onLoadErrorStreamController =
StreamController<WebErrorStatus>();

/// A stream reflecting the current loading state.
Stream<LoadingState> get loadingState => _loadingStateStreamController.stream;

Stream<WebviewDownloadEvent> get onDownloadEvent =>
_downloadEventStreamController.stream;

/// A stream reflecting the navigation error when navigation completed with an error.
Stream<WebErrorStatus> get onLoadError => _onLoadErrorStreamController.stream;

Expand Down Expand Up @@ -189,6 +211,16 @@ class WebviewController extends ValueNotifier<WebviewValue> {
final value = LoadingState.values[map['value']];
_loadingStateStreamController.add(value);
break;
case 'downloadEvent':
final value = WebviewDownloadEvent(
WebviewDownloadEventKind.values[map['value']['kind']],
map['value']['url'],
map['value']['resultFilePath'],
map['value']['bytesReceived'],
map['value']['totalBytesToReceive'],
);
_downloadEventStreamController.add(value);
break;
case 'historyChanged':
final value = HistoryChanged(
map['value']['canGoBack'], map['value']['canGoForward']);
Expand Down
109 changes: 109 additions & 0 deletions windows/webview.cc
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,52 @@ void Webview::RegisterEventHandlers() {
})
.Get(),
&event_registrations_.contains_fullscreen_element_changed_token_);

auto webview24 = webview_.try_query<ICoreWebView2_4>();
if (webview24) {
webview24->add_DownloadStarting(
Callback<ICoreWebView2DownloadStartingEventHandler>(
[this](ICoreWebView2* sender,
ICoreWebView2DownloadStartingEventArgs* args) -> HRESULT {
wil::com_ptr<ICoreWebView2Deferral> deferral;
args->GetDeferral(&deferral);

args->put_Handled(TRUE);

wil::com_ptr<ICoreWebView2DownloadOperation> download;
args->get_DownloadOperation(&download);

INT64 totalBytesToReceive = 0;
download->get_TotalBytesToReceive(&totalBytesToReceive);

wil::unique_cotaskmem_string uri;
download->get_Uri(&uri);

wil::unique_cotaskmem_string mimeType;
download->get_MimeType(&mimeType);

wil::unique_cotaskmem_string contentDisposition;
download->get_ContentDisposition(&contentDisposition);

wil::unique_cotaskmem_string resultFilePath;
args->get_ResultFilePath(&resultFilePath);

args->put_ResultFilePath(resultFilePath.get());
UpdateDownloadProgress(download.get());

if (download_event_callback_) {
download_event_callback_(
{WebviewDownloadEventKind::DownloadStarted,
util::Utf8FromUtf16(uri.get()),
util::Utf8FromUtf16(resultFilePath.get()), 0,
totalBytesToReceive});
}

return S_OK;
})
.Get(),
&event_registrations_.download_starting_token_);
}
}

void Webview::SetSurfaceSize(size_t width, size_t height, float scale_factor) {
Expand Down Expand Up @@ -795,3 +841,66 @@ bool Webview::ClearVirtualHostNameMapping(const std::string& hostName) {
return webview->ClearVirtualHostNameToFolderMapping(
util::Utf16FromUtf8(hostName).c_str());
}

void Webview::UpdateDownloadProgress(ICoreWebView2DownloadOperation* download) {
download->add_BytesReceivedChanged(
Callback<ICoreWebView2BytesReceivedChangedEventHandler>(
[this](ICoreWebView2DownloadOperation* download,
IUnknown* args) -> HRESULT {
if (download_event_callback_) {
INT64 recvd = 0;
download->get_BytesReceived(&recvd);
INT64 total = 0;
download->get_TotalBytesToReceive(&total);

wil::unique_cotaskmem_string uri;
download->get_Uri(&uri);
wil::unique_cotaskmem_string resultFilePath;
download->get_ResultFilePath(&resultFilePath);
download_event_callback_(
{WebviewDownloadEventKind::DownloadProgress,
util::Utf8FromUtf16(uri.get()),
util::Utf8FromUtf16(resultFilePath.get()), recvd, total});
}
return S_OK;
})
.Get(),
&event_registrations_.download_bytes_received_token_);

download->add_StateChanged(
Callback<ICoreWebView2StateChangedEventHandler>(
[this](ICoreWebView2DownloadOperation* download,
IUnknown* args) -> HRESULT {
COREWEBVIEW2_DOWNLOAD_STATE state;
download->get_State(&state);
switch (state) {
case COREWEBVIEW2_DOWNLOAD_STATE_IN_PROGRESS:
break;
case COREWEBVIEW2_DOWNLOAD_STATE_INTERRUPTED:
// Here developer can take different actions based on
// `download->InterruptReason`. For example, show an error
// message to the end user.
break;
case COREWEBVIEW2_DOWNLOAD_STATE_COMPLETED:
if (download_event_callback_) {
wil::unique_cotaskmem_string uri;
download->get_Uri(&uri);
wil::unique_cotaskmem_string resultFilePath;
download->get_ResultFilePath(&resultFilePath);
INT64 recvd = 0;
download->get_BytesReceived(&recvd);
INT64 total = 0;
download->get_TotalBytesToReceive(&total);
download_event_callback_(
{WebviewDownloadEventKind::DownloadCompleted,
util::Utf8FromUtf16(uri.get()),
util::Utf8FromUtf16(resultFilePath.get()), recvd,
total});
}
break;
}
return S_OK;
})
.Get(),
&event_registrations_.download_state_changed_token_);
}
25 changes: 25 additions & 0 deletions windows/webview.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ enum class WebviewPointerButton { None, Primary, Secondary, Tertiary };

enum class WebviewPointerEventKind { Activate, Down, Enter, Leave, Up, Update };

enum class WebviewDownloadEventKind {
DownloadStarted,
DownloadCompleted,
DownloadProgress
};

enum class WebviewPermissionKind {
Unknown,
Microphone,
Expand All @@ -37,6 +43,14 @@ struct WebviewHistoryChanged {
BOOL can_go_forward;
};

struct WebviewDownloadEvent {
WebviewDownloadEventKind kind;
std::string url;
std::string resultFilePath;
INT64 bytesReceived;
INT64 totalBytesToReceive;
};

struct VirtualKeyState {
public:
inline void set_isLeftButtonDown(bool is_down) {
Expand Down Expand Up @@ -87,6 +101,9 @@ struct EventRegistrations {
EventRegistrationToken devtools_protocol_event_token_{};
EventRegistrationToken new_windows_requested_token_{};
EventRegistrationToken contains_fullscreen_element_changed_token_{};
EventRegistrationToken download_starting_token_{};
EventRegistrationToken download_bytes_received_token_{};
EventRegistrationToken download_state_changed_token_{};
};

class Webview {
Expand Down Expand Up @@ -116,6 +133,7 @@ class Webview {
PermissionRequestedCallback;
typedef std::function<void(bool contains_fullscreen_element)>
ContainsFullScreenElementChangedCallback;
typedef std::function<void(WebviewDownloadEvent)> DownloadEventCallback;

~Webview();

Expand Down Expand Up @@ -160,6 +178,8 @@ class Webview {
WebviewHostResourceAccessKind accessKind);
bool ClearVirtualHostNameMapping(const std::string& hostName);

void UpdateDownloadProgress(ICoreWebView2DownloadOperation* download);

void OnUrlChanged(UrlChangedCallback callback) {
url_changed_callback_ = std::move(callback);
}
Expand All @@ -172,6 +192,10 @@ class Webview {
loading_state_changed_callback_ = std::move(callback);
}

void OnDownloadEvent(DownloadEventCallback callback) {
download_event_callback_ = std::move(callback);
}

void OnHistoryChanged(HistoryChangedCallback callback) {
history_changed_callback_ = std::move(callback);
}
Expand Down Expand Up @@ -234,6 +258,7 @@ class Webview {

UrlChangedCallback url_changed_callback_;
LoadingStateChangedCallback loading_state_changed_callback_;
DownloadEventCallback download_event_callback_;
OnLoadErrorCallback on_load_error_callback_;
HistoryChangedCallback history_changed_callback_;
DocumentTitleChangedCallback document_title_changed_callback_;
Expand Down
22 changes: 22 additions & 0 deletions windows/webview_bridge.cc
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,28 @@ void WebviewBridge::RegisterEventHandlers() {
EmitEvent(event);
});

webview_->OnDownloadEvent([this](WebviewDownloadEvent webviewDownloadEvent) {
const auto event = flutter::EncodableValue(flutter::EncodableMap{
{flutter::EncodableValue(kEventType),
flutter::EncodableValue("downloadEvent")},
{flutter::EncodableValue(kEventValue),
flutter::EncodableValue(flutter::EncodableMap{
{flutter::EncodableValue("kind"),
flutter::EncodableValue(
static_cast<int>(webviewDownloadEvent.kind))},
{flutter::EncodableValue("url"),
flutter::EncodableValue(webviewDownloadEvent.url)},
{flutter::EncodableValue("resultFilePath"),
flutter::EncodableValue(webviewDownloadEvent.resultFilePath)},
{flutter::EncodableValue("bytesReceived"),
flutter::EncodableValue(webviewDownloadEvent.bytesReceived)},
{flutter::EncodableValue("totalBytesToReceive"),
flutter::EncodableValue(
webviewDownloadEvent.totalBytesToReceive)},
})}});
EmitEvent(event);
});

webview_->OnHistoryChanged([this](WebviewHistoryChanged historyChanged) {
const auto event = flutter::EncodableValue(flutter::EncodableMap{
{flutter::EncodableValue(kEventType),
Expand Down

0 comments on commit 35c92a1

Please sign in to comment.