Skip to content

Commit b048839

Browse files
author
Eain Chen
committed
Apply new suggestiongs
1 parent 6aefe7b commit b048839

File tree

1 file changed

+109
-116
lines changed

1 file changed

+109
-116
lines changed

specs/ProgrammaticSaveAs.md

Lines changed: 109 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -3,107 +3,105 @@ Programmatic Save As API
33

44
# Background
55

6-
The context menu has the "Save as" item to manually save the html page, image,
7-
pdf, or other content through a save as dialog. We provide more flexiable ways
8-
to do the save as programmatically in WebView2. You can bring up the default
9-
save as dialog easily. And you will be able to block default dialog, save the
10-
content silently, by providing the path and save as kind programmatically or
11-
even build your own save as UI.
6+
Chromium browser's context menus have a "Save as" menu item to save the top level
7+
document (html page, image, pdf, or other content) through a save as dialog. We
8+
provide more flexible ways to programmatically perform the Save As operation in
9+
WebView2.
1210

13-
In this document we describe the API. We'd appreciate your feedback.
11+
With the new API you will be able to:
12+
- Launch the default save as dialog
13+
- Block the default save as dialog
14+
- Request save as silently by providing the path and save as kind
15+
- Build your own save as UI
16+
17+
The chromium browser's Save As operation consists of showing the Save As dialog
18+
and then starting a download of the top level document. The Save As method and
19+
event described in this document relate to the Save As dialog and not the download,
20+
which will go through the existing WebView2 download APIs.
21+
22+
We'd appreciate your feedback.
1423

1524
# Description
1625

1726
We propose the `RequestSaveAs` method WebView2, which allows you to trigger
18-
the save as programmatically. By using this method, the system default dialog,
19-
or your own ui will popup.
27+
the Save As UX programmatically. By using this method, the system default dialog,
28+
or your own UI will show and start the Save As operation.
2029

21-
We propose the `SaveAsRequested` event. You can register this event to block
22-
the default dialog and use the `SaveAsRequestedEventArgs` instead, to set
23-
your preferred save as path, save as kind, and duplicate file replacement rule.
30+
We also propose the `CoreWebView2.SaveAsRequested` event. You can register this event to block
31+
the default dialog and instead create your own Save As UI using the `SaveAsRequestedEventArgs`,
32+
to set your preferred save as path, save as kind, and duplicate file replacement rule.
2433
In your client app, you can design your own UI to input these parameters.
2534
For HTML documents, we support 3 save as kinds: HTML_ONLY, SINGLE_FILE and
26-
COMPLETE. Non-HTML documents, must use DEFAULT, which will save the content as
35+
COMPLETE. Non-HTML documents, you must use DEFAULT, which will save the content as
2736
it is. This API has default values for all parameters, to perform the common
2837
save as operation.
2938

3039
# Examples
3140
## Win32 C++
32-
### Add or Remove the Event Handler
41+
### Add the Event Handler
3342
This example hides the default save as dialog and shows a customized dialog.
3443
```c++
35-
bool ScenarioSaveAs::ToggleEventHandler()
44+
bool ScenarioSaveAs::AddEventHandler()
3645
{
3746
if (!m_webView2_20)
3847
return false;
39-
// m_hasSaveAsRequestedEventHandler indicates whether the event handler
40-
// has been subscribed.
41-
if (m_hasSaveAsRequestedEventHandler)
42-
{
43-
// Unregister the handler for the `SaveAsRequested` event.
44-
m_webView2_20->remove_SaveAsRequested(m_saveAsRequestedToken);
45-
}
46-
else
47-
{
48-
// Register a handler for the `SaveAsRequested` event.
49-
m_webView2_20->add_SaveAsRequested(
50-
Callback<ICoreWebView2SaveAsRequestedEventHandler>(
51-
[this](
52-
ICoreWebView2* sender,
53-
ICoreWebView2SaveAsRequestedEventArgs* args) -> HRESULT
48+
49+
// Register a handler for the `SaveAsRequested` event.
50+
m_webView2_20->add_SaveAsRequested(
51+
Callback<ICoreWebView2SaveAsRequestedEventHandler>(
52+
[this](
53+
ICoreWebView2* sender,
54+
ICoreWebView2SaveAsRequestedEventArgs* args) -> HRESULT
55+
{
56+
// Hide the system default save as dialog.
57+
CHECK_FAILURE(args->put_SuppressDefaultDialog(TRUE));
58+
59+
auto showCustomizedDialog = [this, args]
5460
{
55-
// Hide the system default save as dialog.
56-
CHECK_FAILURE(args->put_SuppressDefaultDialog(TRUE));
57-
58-
auto showCustomizedDialog = [this, args]
61+
// Preview the content mime type, optional
62+
wil::unique_cotaskmem_string mimeType;
63+
CHECK_FAILURE(args->get_ContentMimeType(&mimeType));
64+
65+
// As an end developer, you can design your own dialog UI, or no UI at all.
66+
// You can ask the user to provide information like file name, file extension, and so on.
67+
// Finally, and set them on the event args
68+
//
69+
// This is a customized dialog example, the constructor returns after the
70+
// dialog interaction is completed by the end user.
71+
SaveAsDialog dialog(m_appWindow->GetMainWindow(), saveKinds);
72+
if (dialog.confirmed)
73+
{
74+
// Setting the ResultFilePath, Kind, AllowReplace for the event
75+
// args from this customized dialog inputs is optional.
76+
// The event args has default values based on the document to save.
77+
CHECK_FAILURE(
78+
args->put_ResultFilePath((LPCWSTR)dialog.path.c_str()));
79+
CHECK_FAILURE(args->put_Kind(dialog.selectedKind));
80+
CHECK_FAILURE(args->put_AllowReplace(dialog.allowReplace));
81+
}
82+
else
5983
{
60-
// Preview the content mime type, optional
61-
wil::unique_cotaskmem_string mimeType;
62-
CHECK_FAILURE(args->get_ContentMimeType(&mimeType));
63-
64-
// As an end developer, you can design your own dialog UI, or no UI at all.
65-
// You can ask the user information like file name, file extenstion, and etc.
66-
// Finally, concatenate and pass them into the event args
67-
//
68-
// This is a customized dialog example, the constructor returns after the
69-
// dialog interaction is completed by the end user.
70-
SaveAsDialog dialog(m_appWindow->GetMainWindow(), saveKinds);
71-
if (dialog.confirmed)
72-
{
73-
// Setting the ResultFilePath, Kind, AllowReplace for the event
74-
// args from this customized dialog inputs is optional.
75-
// The event args has default values based on the document to save.
76-
CHECK_FAILURE(
77-
args->put_ResultFilePath((LPCWSTR)dialog.path.c_str()));
78-
CHECK_FAILURE(args->put_Kind(dialog.selectedKind));
79-
CHECK_FAILURE(args->put_AllowReplace(dialog.allowReplace));
80-
}
81-
else
82-
{
83-
// Save As cancelled from this customized dialog
84-
CHECK_FAILURE(args->put_Cancel(TRUE));
85-
}
86-
};
87-
88-
wil::com_ptr<ICoreWebView2Deferral> deferral;
89-
CHECK_FAILURE(args->GetDeferral(&deferral));
90-
91-
m_appWindow->RunAsync(
92-
[deferral, showCustomizedDialog]()
93-
{
94-
showCustomizedDialog();
95-
CHECK_FAILURE(deferral->Complete());
96-
});
97-
return S_OK;
98-
})
99-
.Get(),
100-
&m_saveAsRequestedToken);
101-
}
102-
m_hasSaveAsRequestedEventHandler = !m_hasSaveAsRequestedEventHandler;
84+
// Save As cancelled from this customized dialog
85+
CHECK_FAILURE(args->put_Cancel(TRUE));
86+
}
87+
};
88+
89+
wil::com_ptr<ICoreWebView2Deferral> deferral;
90+
CHECK_FAILURE(args->GetDeferral(&deferral));
91+
92+
m_appWindow->RunAsync(
93+
[deferral, showCustomizedDialog]()
94+
{
95+
showCustomizedDialog();
96+
CHECK_FAILURE(deferral->Complete());
97+
});
98+
return S_OK;
99+
})
100+
.Get(),
101+
&m_saveAsRequestedToken);
102+
103103
MessageBox(
104-
m_appWindow->GetMainWindow(),
105-
(m_hasSaveAsRequestedEventHandler ? L"Event Handler Added" : L"Event Handler Rremoved"), L"Info",
106-
MB_OK);
104+
m_appWindow->GetMainWindow(), L"Event Handler Added", L"Info",MB_OK);
107105
return true;
108106
}
109107
```
@@ -131,18 +129,14 @@ bool ScenarioSaveAs::ProgrammaticSaveAs()
131129
```
132130

133131
## .Net/ WinRT
134-
### Add or Remove the Event Handler
132+
### Add the Event Handler
135133
This example hides the default save as dialog and shows a customized dialog.
136134
```c#
137135

138-
void TaggleEventHandlerExecuted(object target, ExecutedRoutedEventArgs e)
136+
void AddEventHandlerExecuted(object target, ExecutedRoutedEventArgs e)
139137
{
140-
if (hasSaveAsRequestedEventHandler)
141-
webView.CoreWebView2.SaveAsRequested -= WebView_SaveAsRequested;
142-
else
143-
webView.CoreWebView2.SaveAsRequested += WebView_SaveAsRequested;
144-
hasSaveAsRequestedEventHandler = !hasSaveAsRequestedEventHandler;
145-
MessageBox.Show(hasSaveAsRequestedEventHandler? "Event Handler Added":"Event Handler Rremoved" , "Info");
138+
webView.CoreWebView2.SaveAsRequested -= WebView_SaveAsRequested;
139+
MessageBox.Show("Event Handler Added", "Info");
146140
}
147141

148142
void WebView_SaveAsRequested(object sender, CoreWebView2SaveAsRequestedEventArgs args)
@@ -167,11 +161,12 @@ void WebView_SaveAsRequested(object sender, CoreWebView2SaveAsRequestedEventArgs
167161
var dialog = new SaveAsDialog(_Kinds);
168162
if (dialog.ShowDialog() == true)
169163
{
170-
// Preview the content mime type, optional.
171-
args.ContentMimeType;
172-
173164
// Setting parameters of event args from this dialog is optional.
174165
// The event args has default values.
166+
//
167+
// Additionally, you can use `args.ContentMimeType` to check the mime
168+
// type of the document that will be saved to help setup your custom
169+
// Save As dialog UI
175170
args.ResultFilePath = dialog.Directory.Text + "/" + dialog.Filename.Text;
176171
args.Kind = (CoreWebView2SaveAsRequestedKind)dialog.Kind.SelectedItem;
177172
args.AllowReplace = (bool)dialog.AllowReplaceOldFile.IsChecked;
@@ -199,11 +194,11 @@ async void ProgrammaticSaveAsExecuted(object target, ExecutedRoutedEventArgs e)
199194
# API Details
200195
## Win32 C++
201196
```c++
202-
/// Specifies save as requested kind selection options for `ICoreWebView2_20`,
203-
/// used in `SaveAsRequestedEventArgs`
197+
/// Specifies save as requested kind selection options for
198+
/// `ICoreWebView2SaveAsRequestedEventArgs`.
204199
///
205-
/// When the source is a html page, supports to select `HTML_ONLY`,
206-
/// `SINGLE_FILE`, `COMPLETE`; when the source is a non-html,
200+
/// When the source is an HTML document, `DEFAULT`, `HTML_ONLY`, `SINGLE_FILE`,
201+
/// and `COMPLETE` are valid values. When the source is a non-html,
207202
/// only allows to select `DEFAULT`; otherwise, will deny the download
208203
/// and return `COREWEBVIEW2_SAVE_AS_REQUESTED_KIND_NOT_SUPPORTED`.
209204
///
@@ -219,8 +214,9 @@ async void ProgrammaticSaveAsExecuted(object target, ExecutedRoutedEventArgs e)
219214
COREWEBVIEW2_SAVE_AS_REQUESTED_KIND_HTML_ONLY,
220215
/// Save the page as mhtml
221216
COREWEBVIEW2_SAVE_AS_REQUESTED_KIND_SINGLE_FILE,
222-
/// Save the page as html, plus, download the page related source files in
223-
/// a same name directory
217+
/// Save the page as html, plus, download the page related source files
218+
/// (for example CSS, JavaScript, images, and so on) in a directory with
219+
/// the same filename prefix.
224220
COREWEBVIEW2_SAVE_AS_REQUESTED_KIND_COMPLETE,
225221
} COREWEBVIEW2_SAVE_AS_REQUESTED_KIND;
226222

@@ -230,8 +226,8 @@ async void ProgrammaticSaveAsExecuted(object target, ExecutedRoutedEventArgs e)
230226
/// Could not perform Save As because the destination file path is an invalid path.
231227
///
232228
/// It is considered as invalid when:
233-
/// the path is empty, a relativate path, the parent directory doesn't
234-
/// exist, or the path is a driectory.
229+
/// the path is empty or a relativate path, the parent directory doesn't
230+
/// exist, or the path is a directory.
235231
///
236232
/// Parent directory can be itself, if the path is root directory, or
237233
/// root disk. When the root doesn't exist, the path is invalid.
@@ -247,16 +243,17 @@ async void ProgrammaticSaveAsExecuted(object target, ExecutedRoutedEventArgs e)
247243
/// System limits might happen when select `HTML_ONLY` for an error page,
248244
/// select `COMPLETE` and WebView running in an App Container, etc.
249245
COREWEBVIEW2_SAVE_AS_REQUESTED_KIND_NOT_SUPPORTED,
250-
/// Did not perform Save As because the client side decided to cancel.
246+
/// Did not perform Save As because the end user cancelled or the
247+
/// CoreWebView2SaveAsRequestedEventArgs.Cancel property was set to TRUE.
251248
COREWEBVIEW2_SAVE_AS_REQUESTED_CANCELLED,
252-
/// Save as requested completeed, the downloading would start
249+
/// The RequestSaveAs method call completed successfully and the download has started.
253250
COREWEBVIEW2_SAVE_AS_REQUESTED_COMPLETED,
254251
} COREWEBVIEW2_SAVE_AS_REQUESTED_RESULTS;
255252

256253
[uuid(15e1c6a3-c72a-4df3-91d7-d097fbec3bfd), object, pointer_default(unique)]
257254
interface ICoreWebView2_20 : IUnknown {
258-
/// Programmatically trigger a save as action for current content. `SaveAsRequested`
259-
/// event will be raised.
255+
/// Programmatically trigger a save as action for the current top-level document.
256+
/// The `SaveAsRequested` event will be raised.
260257
///
261258
/// Opens a system modal dialog by default. If it was already opened, this method
262259
/// would not open another one. If the `SuppressDefaultDialog` is TRUE, won't open
@@ -266,19 +263,17 @@ interface ICoreWebView2_20 : IUnknown {
266263
/// Please see COREWEBVIEW2_SAVE_AS_REQUESTED_RESULTS
267264
///
268265
/// \snippet ScenarioSaveAs.cpp ProgrammaticSaveAs
269-
HRESULT SaveContentAs([in] ICoreWebView2RequestSaveAsCompletedHandler* handler);
266+
HRESULT RequestSaveAs([in] ICoreWebView2RequestSaveAsCompletedHandler* handler);
270267

271268
/// Add an event handler for the `SaveAsRequested` event. This event is raised
272269
/// when save as is triggered, programmatically or manually.
273270
///
274-
/// \snippet ScenarioSaveAs.cpp ToggleEventHandler
271+
/// \snippet ScenarioSaveAs.cpp AddEventHandler
275272
HRESULT add_SaveAsRequested(
276273
[in] ICoreWebView2SaveAsRequestedEventHandler* eventHanlder,
277274
[out] EventRegistrationToken* token);
278275

279276
/// Remove an event handler previously added with `add_SaveAsRequested`.
280-
///
281-
/// \snippet ScenarioSaveAs.cpp ToggleEventHandler
282277
HRESULT remove_SaveAsRequested(
283278
[in] EventRegistrationToken token);
284279
}
@@ -297,9 +292,8 @@ interface ICoreWebView2SaveAsRequestedEventArgs : IUnknown {
297292
/// Get the Mime type of content to be saved
298293
[propget] HRESULT ContentMimeType([out, retval] LPWSTR* value);
299294

300-
/// Indicates if client side cancelled the silent save as, TRUE means cancelled.
301-
/// When the event is invoked, the download won't start. A programmatic call will
302-
/// return COREWEBVIEW2_SAVE_AS_CANCELLED as well.
295+
/// You can set this to TRUE to cancel the Save As. Then the download won't start.
296+
/// A programmatic call will return COREWEBVIEW2_SAVE_AS_CANCELLED as well.
303297
///
304298
/// The default value is FALSE.
305299
///
@@ -325,14 +319,13 @@ interface ICoreWebView2SaveAsRequestedEventArgs : IUnknown {
325319
/// default Save As dialog and performing the Save As operation.
326320
HRESULT GetDeferral([out, retval] ICoreWebView2Deferral** deferral);
327321

328-
/// `ResultFilePath` is absolute full path of the location. It includes the
329-
/// file name and extension. If `ResultFilePath` is not valid, e.g. root drive
322+
/// `ResultFilePath` is absolute full path of the location. It includes the file name
323+
/// and extension. If `ResultFilePath` is not valid, for example the root drive does
330324
/// not exist, save as will be denied and return COREWEBVIEW2_SAVE_AS_INVALID_PATH.
331325
///
332-
/// When the download complete and success, a target file will be saved at this
333-
/// location. If the select `COREWEBVIEW2_SAVE_AS_REQUESTED_KIND_COMPLETE`, will be
334-
/// an additional directory with resources files. The directory has the same name
335-
/// as filename, at the same location.
326+
/// If the associated download completes successfully, a target file will be saved at
327+
/// this location. If the Kind property is `COREWEBVIEW2_SAVE_AS_REQUESTED_KIND_COMPLETE`,
328+
/// there will be an additional directory with resources files.
336329
///
337330
/// The default value is a system suggested path, based on users' local environment.
338331
///

0 commit comments

Comments
 (0)