From 52febeab0071fc1cf1f7063be537c7dfcc09050c Mon Sep 17 00:00:00 2001 From: Eain C <41822980+Master-Ukulele@users.noreply.github.com> Date: Wed, 6 Sep 2023 18:02:47 -0700 Subject: [PATCH 01/13] Create ProgrammaticSaveAs.md --- specs/ProgrammaticSaveAs.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 specs/ProgrammaticSaveAs.md diff --git a/specs/ProgrammaticSaveAs.md b/specs/ProgrammaticSaveAs.md new file mode 100644 index 000000000..1f8b685f4 --- /dev/null +++ b/specs/ProgrammaticSaveAs.md @@ -0,0 +1,20 @@ +Programmatic Save As API + + +# Background + +The context menu has the "Save as" item to manually save the html page, image, pdf, or other content through a save as dialog. We provide more flexiable ways to do the save as programmatically in WebView2. You can bring up the default save as dialog easily. And you will be able to block default dialog, save the content silently, by providing the path and save as type programmatically or even build your own save as UI. + +In this document we describe the API. We'd appreciate your feedback. + +# Description + +We propose the `SaveContentAs` method WebView2, which allows you to trigger the save as programmatically. By using this method alone, the system default will popup. + +Additionally, we propose the `SaveAsRequestedEvent`. You can register this event to block the default dialog and use the `SaveAsRequestedEventArgs` instead, to set your perferred save as path, save as type, and depulicate file replacement rule. In your clinet app, you can design your own UI to input these parameters. For html page, we support 3 save as types: HTML_ONLY, SINGLE_FILE and COMPLETE. For non-html page, the type is been set as DEFAULT, which will save the content as it is. This API also provides default values for all parameters, if you don't want to input anything. + +# Examples +## Win32 C++ +# API Details +## Win32 C++ +Sync with the code and design doc soon From 06a199f9eb0087d4da7fb4ef13658161f4f0eb51 Mon Sep 17 00:00:00 2001 From: Eain C <41822980+Master-Ukulele@users.noreply.github.com> Date: Mon, 11 Sep 2023 18:09:34 -0700 Subject: [PATCH 02/13] Add win32 example and api detail --- specs/ProgrammaticSaveAs.md | 251 +++++++++++++++++++++++++++++++++++- 1 file changed, 249 insertions(+), 2 deletions(-) diff --git a/specs/ProgrammaticSaveAs.md b/specs/ProgrammaticSaveAs.md index 1f8b685f4..9150e5d27 100644 --- a/specs/ProgrammaticSaveAs.md +++ b/specs/ProgrammaticSaveAs.md @@ -1,5 +1,5 @@ Programmatic Save As API - +=== # Background @@ -15,6 +15,253 @@ Additionally, we propose the `SaveAsRequestedEvent`. You can register this event # Examples ## Win32 C++ +```c++ +//! [ToggleSilent] +// Turn on/off Silent SaveAs, which won't show the system default save as dialog. +// This example hides the default save as dialog and shows a customized dialog. +bool ScenarioSaveAs::ToggleSilent() +{ + if (!m_webView2Staging20) + return false; + if (m_silentSaveAs) + { + // Unregister the handler for the `SaveAsRequested` event. + m_webView2Staging20->remove_SaveAsRequested(m_saveAsRequestedToken); + } + else + { + // Register a handler for the `SaveAsRequested` event. + m_webView2Staging20->add_SaveAsRequested( + Callback( + [this]( + ICoreWebView2* sender, + ICoreWebView2StagingSaveAsRequestedEventArgs* args) -> HRESULT + { + // Hide the system default save as dialog. + CHECK_FAILURE(args->put_Handled(TRUE)); + + auto showCustomizedDialog = [this, args] + { + // Preview the content mime type, optional + wil::unique_cotaskmem_string mimeType; + CHECK_FAILURE(args->get_ContentMimeType(&mimeType)); + + SaveAsDialog dialog(m_appWindow->GetMainWindow(), contentSaveTypes); + if (dialog.confirmed) + { + // Set the ResultFilePath, SaveAsType, AllowReplace for the event + // args from this customized dialog inputs, optional. If nothing + // needs to input, the event args will provide default values. + CHECK_FAILURE( + args->put_ResultFilePath((LPCWSTR)dialog.path.c_str())); + CHECK_FAILURE(args->put_SaveAsType(dialog.selectedType)); + CHECK_FAILURE(args->put_AllowReplace(dialog.allowReplace)); + + // Confirm to download, required + CHECK_FAILURE(args->put_ConfirmToSave(TRUE)); + } + }; + + wil::com_ptr deferral; + CHECK_FAILURE(args->GetDeferral(&deferral)); + + m_appWindow->RunAsync( + [deferral, showCustomizedDialog]() + { + showCustomizedDialog(); + CHECK_FAILURE(deferral->Complete()); + }); + return S_OK; + }) + .Get(), + &m_saveAsRequestedToken); + } + m_silentSaveAs = !m_silentSaveAs; + MessageBox( + m_appWindow->GetMainWindow(), + (m_silentSaveAs ? L"Silent Save As Enabled" : L"Silent Save As Disabled"), L"Info", + MB_OK); + return true; +} +//! [ToggleSilent] + +//! [ProgrammaticSaveAs] +// Call SaveContentAs method to trigger the programmatic save as. +bool ScenarioSaveAs::ProgrammaticSaveAs() +{ + if (!m_webView2Staging20) + return false; + m_webView2Staging20->SaveContentAs( + Callback( + [this](HRESULT errorCode, COREWEBVIEW2_SAVE_CONTENT_AS_RESULTS result) -> HRESULT + { + // Show SaveContentAs returned result, optional + // Skip message box when the result is COREWEBVIEW2_SAVE_AS_OPEN_SYSTEM_DIALOG (0), + // to prevent it deactivates save as modal dialog focus on the window + if (result > 0) + { + MessageBox( + m_appWindow->GetMainWindow(), + (L"Save As " + saveAsResultString[result]).c_str(), L"Info", MB_OK); + } + return S_OK; + }) + .Get()); + return true; +} +//! [ProgrammaticSaveAs] +``` # API Details ## Win32 C++ -Sync with the code and design doc soon +```c++ +/// Specifies save as type selection options for `ICoreWebView2Staging20`, +/// used in `SaveAsRequestedEventArgs` +/// +/// When the source is a html page, allows to select `HTML_ONLY`, +/// `SINGLE_FILE`, `COMPLETE`; when the source is a non-html, +/// only allows to select `DEFAULT`; otherwise, will deny the download +/// and return `COREWEBVIEW2_SAVE_AS_TYPE_NOT_SUPPORTED`. +/// +/// The content type/format is a MIME type, indicated by the source +/// server side and identified by the browser. It’s not related to the +/// file’s type or extension. MIME type of `text/html`, +/// `application/xhtml+xml` are considered as html page. +[v1_enum] typedef enum COREWEBVIEW2_SAVE_AS_TYPE { + /// Default to save for a non-html content. If it is selected for a html + /// page, it’s same as HTML_ONLY option. + COREWEBVIEW2_SAVE_AS_TYPE_DEFAULT, + /// Save the page as html + COREWEBVIEW2_SAVE_AS_TYPE_HTML_ONLY, + /// Save the page as mhtml + COREWEBVIEW2_SAVE_AS_TYPE_SINGLE_FILE, + /// Save the page as html, plus, download the page related source files in + /// a folder + COREWEBVIEW2_SAVE_AS_TYPE_COMPLETE, +} COREWEBVIEW2_SAVE_AS_TYPE; + +/// Status of a programmatic save as call, indicates the result +/// for method `SaveContentAs` +[v1_enum] typedef enum COREWEBVIEW2_SAVE_CONTENT_AS_RESULTS { + /// Programmatically open a system default save as dialog + COREWEBVIEW2_SAVE_AS_OPEN_SYSTEM_DIALOG, + /// Save as downloading not start as given an invalid path + COREWEBVIEW2_SAVE_AS_INVALID_PATH, + /// Save as downloading not start as given a duplicate filename and + /// replace file not allowed + COREWEBVIEW2_SAVE_AS_FILE_ALREADY_EXISTS, + /// Save as downloading not start as the `SAVE_AS_TYPE` selection not + /// supported because of the content MIME type or system limits + COREWEBVIEW2_SAVE_AS_TYPE_NOT_SUPPORTED, + /// Cancel the save as request + COREWEBVIEW2_SAVE_AS_CANCELLED, + /// Save as request complete, the downloading started + COREWEBVIEW2_SAVE_AS_STARTED +} COREWEBVIEW2_SAVE_CONTENT_AS_RESULTS; + + +[uuid(15e1c6a3-c72a-4df3-91d7-d097fbec3bfd), object, pointer_default(unique)] +interface ICoreWebView2Staging20 : IUnknown { + /// Programmatically trigger a save as action for current content. + /// + /// Opens a system modal dialog by default. Returns COREWEBVIEW2_SAVE_AS_OPEN_SYSTEM_DIALOG. + /// If it was already opened, this method would not open another one + /// + /// If the silent save as option is enabled, won't open the system dialog, will + /// raise the `SaveAsRequested` event instead and process through its event args. The method can return + /// a detailed info to indicate the call's result. Please see COREWEBVIEW2_SAVE_CONTENT_AS_RESULTS + /// + /// \snippet ScenarioSaveAs.cpp ProgrammaticSaveAs + HRESULT SaveContentAs([in] ICoreWebView2StagingSaveContentAsCompletedHandler* handler); + + /// Add an event handler for the `SaveAsRequested` event. This event is raised + /// when save as is triggered, programmatically or manually. + /// + /// \snippet ScenarioSaveAs.cpp ToggleSilent + HRESULT add_SaveAsRequested( + [in] ICoreWebView2StagingSaveAsRequestedEventHandler* eventHanlder, + [out] EventRegistrationToken* token); + + /// Remove an event handler previously added with `add_SaveAsRequested`. + /// + /// \snippet ScenarioSaveAs.cpp ToggleSilent + HRESULT remove_SaveAsRequested( + [in] EventRegistrationToken token); +} + +/// The event handler for the `SaveAsRequested` event, when the handler +/// exists, the silent save as enables. +[uuid(55b86cd2-adfd-47f1-9cef-cdfb8c414ed3), object, pointer_default(unique)] +interface ICoreWebView2StagingSaveAsRequestedEventHandler : IUnknown { + HRESULT Invoke( + [in] ICoreWebView2* sender, + [in] ICoreWebView2StagingSaveAsRequestedEventArgs* args); +} + +/// The event args for `SaveAsRequested` event +[uuid(80101027-b8c3-49a1-a052-9ea4bd63ba47), object, pointer_default(unique)] +interface ICoreWebView2StagingSaveAsRequestedEventArgs : IUnknown { + /// Get the Mime type of content to be saved + [propget] HRESULT ContentMimeType([out, retval] LPWSTR* value); + + /// Indicates if this event is a silent save as job. `Handled` as FALSE means + /// save as handled by system default dialog; TRUE means a silent save as, + /// will skip the system dialog. + /// + /// Set the `Handled` for save as + [propput] HRESULT Handled ([in] BOOL handled); + + /// Get the `Handled` for save as + [propget] HRESULT Handled ([out, retval] BOOL* handled); + + /// Indicates if a silent save as confirm to download, TRUE means confirm. + /// when the event is invoked, the download will start. A programmatic call will + /// return COREWEBVIEW2_SAVE_AS_STARTED as well. set it FASLE to cancel save as + /// and will return COREWEBVIEW2_SAVE_AS_CANCELLED. + /// + /// Set the `ConfrimToSave` for save as + [propput] HRESULT ConfirmToSave ([in] BOOL confirmToSave); + + /// Get the `ConfrimToSave` for save as + [propget] HRESULT ConfirmToSave ([out, retval] BOOL* confirmToSave); + + /// Returns an `ICoreWebView2Deferral` object. Use this operation to complete + /// the SaveAsRequestedEvent. + HRESULT GetDeferral([out, retval] ICoreWebView2Deferral** deferral); + + /// `ResultFilePath` is absolute full path of the location. It includes the + /// file name and extension. If `ResultFilePath` is not valid, e.g. root drive + /// not exist, save as will be denied and return COREWEBVIEW2_SAVE_AS_INVALID_PATH. + /// + /// Set the `ResultFilePath` for save as + [propput] HRESULT ResultFilePath ([in] LPCWSTR resultFilePath); + + /// Get the `ResultFilePath` for save as + [propget] HRESULT ResultFilePath ([out, retval] LPWSTR* resultFilePath); + + /// `AllowReplace` gives the user an option to control when the file name + /// duplicates with an existing file. TRUE allows the old file be replaced. + /// FALSE denies when the file name duplicates, the download won’t start, + /// will return COREWEBVIEW2_SAVE_AS_FILE_ALREADY_EXISTS. + /// + /// Set if allowed to replace the old file if duplicate happens in the save as job + [propput] HRESULT AllowReplace ([in] BOOL allowReplace); + + /// Get the duplicates replace rule for save as + [propget] HRESULT AllowReplace ([out, retval] BOOL* allowReplace); + + /// `SaveAsType` is required, see the enum COREWEBVIEW2_SAVE_AS_TYPE, if the type + /// doesn’t match, return COREWEBVIEW2_SAVE_AS_TYPE_NOT_SUPPORT + /// + /// Set the content save as type for save as job + [propput] HRESULT SaveAsType ([in] COREWEBVIEW2_SAVE_AS_TYPE type); + + /// Get the content save as type for save as job + [propget] HRESULT SaveAsType ([out, retval] COREWEBVIEW2_SAVE_AS_TYPE* type); +} + +/// Receive the result for `SaveContentAs` method +[uuid(1a02e9d9-14d3-41c6-9581-8d6e1e6f50fe), object, pointer_default(unique)] +interface ICoreWebView2StagingSaveContentAsCompletedHandler : IUnknown { + HRESULT Invoke([in] HRESULT errorCode, [in] COREWEBVIEW2_SAVE_CONTENT_AS_RESULTS result); +} +``` From 72b095e20ebd5522695ef80e4a6b21aa8d6aaf5d Mon Sep 17 00:00:00 2001 From: Eain Chen Date: Tue, 19 Sep 2023 09:47:04 -0700 Subject: [PATCH 03/13] Apply format and context suggestions --- specs/ProgrammaticSaveAs.md | 51 ++++++++++++++++++++++++------------- 1 file changed, 33 insertions(+), 18 deletions(-) diff --git a/specs/ProgrammaticSaveAs.md b/specs/ProgrammaticSaveAs.md index 9150e5d27..855e4ec8f 100644 --- a/specs/ProgrammaticSaveAs.md +++ b/specs/ProgrammaticSaveAs.md @@ -3,15 +3,27 @@ Programmatic Save As API # Background -The context menu has the "Save as" item to manually save the html page, image, pdf, or other content through a save as dialog. We provide more flexiable ways to do the save as programmatically in WebView2. You can bring up the default save as dialog easily. And you will be able to block default dialog, save the content silently, by providing the path and save as type programmatically or even build your own save as UI. +The context menu has the "Save as" item to manually save the html page, image, +pdf, or other content through a save as dialog. We provide more flexiable ways +to do the save as programmatically in WebView2. You can bring up the default +save as dialog easily. And you will be able to block default dialog, save the +content silently, by providing the path and save as type programmatically or +even build your own save as UI. In this document we describe the API. We'd appreciate your feedback. # Description -We propose the `SaveContentAs` method WebView2, which allows you to trigger the save as programmatically. By using this method alone, the system default will popup. +We propose the `SaveContentAs` method WebView2, which allows you to trigger +the save as programmatically. By using this method alone, the system default +will popup. -Additionally, we propose the `SaveAsRequestedEvent`. You can register this event to block the default dialog and use the `SaveAsRequestedEventArgs` instead, to set your perferred save as path, save as type, and depulicate file replacement rule. In your clinet app, you can design your own UI to input these parameters. For html page, we support 3 save as types: HTML_ONLY, SINGLE_FILE and COMPLETE. For non-html page, the type is been set as DEFAULT, which will save the content as it is. This API also provides default values for all parameters, if you don't want to input anything. +Additionally, we propose the `SaveAsRequested` event. You can register this +event to block the default dialog and use the `SaveAsRequestedEventArgs` +instead, to set your preferred save as path, save as type, and duplicate file replacement rule. In your client app, you can design your own UI to input +these parameters. For HTML documents, we support 3 save as types: HTML_ONLY, SINGLE_FILE and COMPLETE. Non-HTML documents, must use DEFAULT, which will +save the content as it is. This API has default values for all parameters, +to perform the common save as operation. # Examples ## Win32 C++ @@ -49,9 +61,9 @@ bool ScenarioSaveAs::ToggleSilent() SaveAsDialog dialog(m_appWindow->GetMainWindow(), contentSaveTypes); if (dialog.confirmed) { - // Set the ResultFilePath, SaveAsType, AllowReplace for the event - // args from this customized dialog inputs, optional. If nothing - // needs to input, the event args will provide default values. + // Setting the ResultFilePath, SaveAsType, AllowReplace for the event + // args from this customized dialog inputs is optional. + // The event args has default values based on the document to save. CHECK_FAILURE( args->put_ResultFilePath((LPCWSTR)dialog.path.c_str())); CHECK_FAILURE(args->put_SaveAsType(dialog.selectedType)); @@ -144,15 +156,15 @@ bool ScenarioSaveAs::ProgrammaticSaveAs() [v1_enum] typedef enum COREWEBVIEW2_SAVE_CONTENT_AS_RESULTS { /// Programmatically open a system default save as dialog COREWEBVIEW2_SAVE_AS_OPEN_SYSTEM_DIALOG, - /// Save as downloading not start as given an invalid path + /// Could not perform Save As because the destination file path is an invalid path. COREWEBVIEW2_SAVE_AS_INVALID_PATH, - /// Save as downloading not start as given a duplicate filename and - /// replace file not allowed + /// Could not perform Save As because the destination file path already exists and + /// replacing files was not allowed by the AllowReplace property. COREWEBVIEW2_SAVE_AS_FILE_ALREADY_EXISTS, /// Save as downloading not start as the `SAVE_AS_TYPE` selection not /// supported because of the content MIME type or system limits COREWEBVIEW2_SAVE_AS_TYPE_NOT_SUPPORTED, - /// Cancel the save as request + /// Did not perform Save As because the client side decided to cancel. COREWEBVIEW2_SAVE_AS_CANCELLED, /// Save as request complete, the downloading started COREWEBVIEW2_SAVE_AS_STARTED @@ -224,8 +236,8 @@ interface ICoreWebView2StagingSaveAsRequestedEventArgs : IUnknown { /// Get the `ConfrimToSave` for save as [propget] HRESULT ConfirmToSave ([out, retval] BOOL* confirmToSave); - /// Returns an `ICoreWebView2Deferral` object. Use this operation to complete - /// the SaveAsRequestedEvent. + /// Returns an `ICoreWebView2Deferral` object. This will defer showing the + /// default Save As dialog and performing the Save As operation. HRESULT GetDeferral([out, retval] ICoreWebView2Deferral** deferral); /// `ResultFilePath` is absolute full path of the location. It includes the @@ -238,10 +250,11 @@ interface ICoreWebView2StagingSaveAsRequestedEventArgs : IUnknown { /// Get the `ResultFilePath` for save as [propget] HRESULT ResultFilePath ([out, retval] LPWSTR* resultFilePath); - /// `AllowReplace` gives the user an option to control when the file name - /// duplicates with an existing file. TRUE allows the old file be replaced. - /// FALSE denies when the file name duplicates, the download won’t start, - /// will return COREWEBVIEW2_SAVE_AS_FILE_ALREADY_EXISTS. + /// `AllowReplace` allows you to control what happens when a file already + /// exists in the file path to which the Save As operation is saving. + /// Setting this TRUE allows existing files to be replaced. + /// Settings this FALSE will not replace existing files and will return + /// COREWEBVIEW2_SAVE_AS_FILE_ALREADY_EXISTS. /// /// Set if allowed to replace the old file if duplicate happens in the save as job [propput] HRESULT AllowReplace ([in] BOOL allowReplace); @@ -249,8 +262,10 @@ interface ICoreWebView2StagingSaveAsRequestedEventArgs : IUnknown { /// Get the duplicates replace rule for save as [propget] HRESULT AllowReplace ([out, retval] BOOL* allowReplace); - /// `SaveAsType` is required, see the enum COREWEBVIEW2_SAVE_AS_TYPE, if the type - /// doesn’t match, return COREWEBVIEW2_SAVE_AS_TYPE_NOT_SUPPORT + /// How to save documents with different types. See the enum + /// COREWEBVIEW2_SAVE_AS_TYPE for a description of the different options. + /// If the type isn't allowed for the current document, + /// COREWEBVIEW2_SAVE_AS_TYPE_NOT_SUPPORT will be returned from SaveContentAs. /// /// Set the content save as type for save as job [propput] HRESULT SaveAsType ([in] COREWEBVIEW2_SAVE_AS_TYPE type); From 8e0d604af9f1249c90e7863eeea41bf860318920 Mon Sep 17 00:00:00 2001 From: Eain Chen Date: Tue, 26 Sep 2023 16:22:36 -0700 Subject: [PATCH 04/13] Add more details Add explanations, examples. Add property 'Cancel', 'SuppressDefaultDialog' --- specs/ProgrammaticSaveAs.md | 134 +++++++++++++++++++++++++----------- 1 file changed, 92 insertions(+), 42 deletions(-) diff --git a/specs/ProgrammaticSaveAs.md b/specs/ProgrammaticSaveAs.md index 855e4ec8f..6cda1ce95 100644 --- a/specs/ProgrammaticSaveAs.md +++ b/specs/ProgrammaticSaveAs.md @@ -20,22 +20,23 @@ will popup. Additionally, we propose the `SaveAsRequested` event. You can register this event to block the default dialog and use the `SaveAsRequestedEventArgs` -instead, to set your preferred save as path, save as type, and duplicate file replacement rule. In your client app, you can design your own UI to input -these parameters. For HTML documents, we support 3 save as types: HTML_ONLY, SINGLE_FILE and COMPLETE. Non-HTML documents, must use DEFAULT, which will +instead, to set your preferred save as path, save as type, and duplicate file +replacement rule. In your client app, you can design your own UI to input +these parameters. For HTML documents, we support 3 save as types: HTML_ONLY, +SINGLE_FILE and COMPLETE. Non-HTML documents, must use DEFAULT, which will save the content as it is. This API has default values for all parameters, to perform the common save as operation. # Examples -## Win32 C++ +## Win32 C++ +### Add or Remove the Event Handler +This example hides the default save as dialog and shows a customized dialog. ```c++ -//! [ToggleSilent] -// Turn on/off Silent SaveAs, which won't show the system default save as dialog. -// This example hides the default save as dialog and shows a customized dialog. -bool ScenarioSaveAs::ToggleSilent() +bool ScenarioSaveAs::ToggleEventHandler() { if (!m_webView2Staging20) return false; - if (m_silentSaveAs) + if (m_hasSaveAsRequestedEventHandler) { // Unregister the handler for the `SaveAsRequested` event. m_webView2Staging20->remove_SaveAsRequested(m_saveAsRequestedToken); @@ -50,14 +51,21 @@ bool ScenarioSaveAs::ToggleSilent() ICoreWebView2StagingSaveAsRequestedEventArgs* args) -> HRESULT { // Hide the system default save as dialog. - CHECK_FAILURE(args->put_Handled(TRUE)); - + CHECK_FAILURE(args->put_SuppressDefaultDialog(TRUE)); + auto showCustomizedDialog = [this, args] { // Preview the content mime type, optional wil::unique_cotaskmem_string mimeType; CHECK_FAILURE(args->get_ContentMimeType(&mimeType)); + + // As an end developer, you can design your own dialog UI, or no UI at all. + // You can ask the user information like file name, file extenstion, and etc. + // Finally, concatenate and pass them into the event args + // + // This is a customized dialog example, the constructor returns after the + // dialog interaction is completed by the end user. SaveAsDialog dialog(m_appWindow->GetMainWindow(), contentSaveTypes); if (dialog.confirmed) { @@ -68,10 +76,15 @@ bool ScenarioSaveAs::ToggleSilent() args->put_ResultFilePath((LPCWSTR)dialog.path.c_str())); CHECK_FAILURE(args->put_SaveAsType(dialog.selectedType)); CHECK_FAILURE(args->put_AllowReplace(dialog.allowReplace)); - - // Confirm to download, required - CHECK_FAILURE(args->put_ConfirmToSave(TRUE)); } + else + { + // Save As cancelled from this customized dialog + CHECK_FAILURE(args->put_Cancel(TRUE)); + } + + // Indicate out parameters have been set. + CHECK_FAILURE(args->put_Handled(TRUE)); }; wil::com_ptr deferral; @@ -88,17 +101,18 @@ bool ScenarioSaveAs::ToggleSilent() .Get(), &m_saveAsRequestedToken); } - m_silentSaveAs = !m_silentSaveAs; + m_hasSaveAsRequestedEventHandler = !m_hasSaveAsRequestedEventHandler; MessageBox( m_appWindow->GetMainWindow(), - (m_silentSaveAs ? L"Silent Save As Enabled" : L"Silent Save As Disabled"), L"Info", + (m_hasSaveAsRequestedEventHandler ? L"Event Handler Added" : L"Event Handler Rremoved"), L"Info", MB_OK); return true; } -//! [ToggleSilent] +``` +### Programmatic Save As +Call SaveContentAs method to trigger the programmatic save as. +```c++ -//! [ProgrammaticSaveAs] -// Call SaveContentAs method to trigger the programmatic save as. bool ScenarioSaveAs::ProgrammaticSaveAs() { if (!m_webView2Staging20) @@ -121,15 +135,15 @@ bool ScenarioSaveAs::ProgrammaticSaveAs() .Get()); return true; } -//! [ProgrammaticSaveAs] ``` + # API Details ## Win32 C++ ```c++ /// Specifies save as type selection options for `ICoreWebView2Staging20`, /// used in `SaveAsRequestedEventArgs` /// -/// When the source is a html page, allows to select `HTML_ONLY`, +/// When the source is a html page, supports to select `HTML_ONLY`, /// `SINGLE_FILE`, `COMPLETE`; when the source is a non-html, /// only allows to select `DEFAULT`; otherwise, will deny the download /// and return `COREWEBVIEW2_SAVE_AS_TYPE_NOT_SUPPORTED`. @@ -147,7 +161,7 @@ bool ScenarioSaveAs::ProgrammaticSaveAs() /// Save the page as mhtml COREWEBVIEW2_SAVE_AS_TYPE_SINGLE_FILE, /// Save the page as html, plus, download the page related source files in - /// a folder + /// a same name directory COREWEBVIEW2_SAVE_AS_TYPE_COMPLETE, } COREWEBVIEW2_SAVE_AS_TYPE; @@ -157,12 +171,24 @@ bool ScenarioSaveAs::ProgrammaticSaveAs() /// Programmatically open a system default save as dialog COREWEBVIEW2_SAVE_AS_OPEN_SYSTEM_DIALOG, /// Could not perform Save As because the destination file path is an invalid path. + /// + /// It is considered as invalid when: + /// the path is empty, a relativate path, the parent directory doesn't + /// exist, or the path is a driectory. + /// + /// Parent directory can be itself, if the path is root directory, or + /// root disk. When the root doesn't exist, the path is invalid. COREWEBVIEW2_SAVE_AS_INVALID_PATH, /// Could not perform Save As because the destination file path already exists and /// replacing files was not allowed by the AllowReplace property. COREWEBVIEW2_SAVE_AS_FILE_ALREADY_EXISTS, /// Save as downloading not start as the `SAVE_AS_TYPE` selection not /// supported because of the content MIME type or system limits + /// + /// MIME type limits please see the emun `COREWEBVIEW2_SAVE_AS_TYPE` + /// + /// System limits might happen when select `HTML_ONLY` for an error page, + /// select `COMPLETE` and WebView running in an App Container, etc. COREWEBVIEW2_SAVE_AS_TYPE_NOT_SUPPORTED, /// Did not perform Save As because the client side decided to cancel. COREWEBVIEW2_SAVE_AS_CANCELLED, @@ -188,14 +214,14 @@ interface ICoreWebView2Staging20 : IUnknown { /// Add an event handler for the `SaveAsRequested` event. This event is raised /// when save as is triggered, programmatically or manually. /// - /// \snippet ScenarioSaveAs.cpp ToggleSilent + /// \snippet ScenarioSaveAs.cpp ToggleEventHandler HRESULT add_SaveAsRequested( [in] ICoreWebView2StagingSaveAsRequestedEventHandler* eventHanlder, [out] EventRegistrationToken* token); /// Remove an event handler previously added with `add_SaveAsRequested`. /// - /// \snippet ScenarioSaveAs.cpp ToggleSilent + /// \snippet ScenarioSaveAs.cpp ToggleEventHandler HRESULT remove_SaveAsRequested( [in] EventRegistrationToken token); } @@ -215,26 +241,39 @@ interface ICoreWebView2StagingSaveAsRequestedEventArgs : IUnknown { /// Get the Mime type of content to be saved [propget] HRESULT ContentMimeType([out, retval] LPWSTR* value); - /// Indicates if this event is a silent save as job. `Handled` as FALSE means - /// save as handled by system default dialog; TRUE means a silent save as, - /// will skip the system dialog. + /// Indicates if pramameters in the event args has been set, TRUE means been set. + /// + /// The default value is FALSE. /// /// Set the `Handled` for save as - [propput] HRESULT Handled ([in] BOOL handled); + [propput] HRESULT Handled ([in] BOOL value); /// Get the `Handled` for save as - [propget] HRESULT Handled ([out, retval] BOOL* handled); + [propget] HRESULT Handled ([out, retval] BOOL* value); - /// Indicates if a silent save as confirm to download, TRUE means confirm. - /// when the event is invoked, the download will start. A programmatic call will - /// return COREWEBVIEW2_SAVE_AS_STARTED as well. set it FASLE to cancel save as - /// and will return COREWEBVIEW2_SAVE_AS_CANCELLED. + /// Indicates if client side cancelled the silent save as, TRUE means cancelled. + /// When the event is invoked, the download won't start. A programmatic call will + /// return COREWEBVIEW2_SAVE_AS_CANCELLED as well. /// - /// Set the `ConfrimToSave` for save as - [propput] HRESULT ConfirmToSave ([in] BOOL confirmToSave); + /// The default value is FALSE. + /// + /// Set the `Cancel` for save as + [propput] HRESULT Cancel ([in] BOOL value); - /// Get the `ConfrimToSave` for save as - [propget] HRESULT ConfirmToSave ([out, retval] BOOL* confirmToSave); + /// Get the `Cancel` for save as + [propget] HRESULT Cancel ([out, retval] BOOL* value); + + /// Indicates if the system default dialog will be supressed, FALSE means + /// save as default dialog will show; TRUE means a silent save as, will + /// skip the system dialog. + /// + /// The default value is FALSE. + /// + /// Set the `SupressDefaultDialog` + [propput] HRESULT SupressDefaultDialog([in] BOOL value); + + /// Get the `SupressDefaultDialog` + [propget] HRESULT SupressDefaultDialog([out, retval] BOOL* value); /// Returns an `ICoreWebView2Deferral` object. This will defer showing the /// default Save As dialog and performing the Save As operation. @@ -244,11 +283,18 @@ interface ICoreWebView2StagingSaveAsRequestedEventArgs : IUnknown { /// file name and extension. If `ResultFilePath` is not valid, e.g. root drive /// not exist, save as will be denied and return COREWEBVIEW2_SAVE_AS_INVALID_PATH. /// + /// When the download complete and success, a target file will be saved at this + /// location. If the SAVE_AS_TYPE is `COMPLETE`, will be an additional directory + /// with resources files. The directory has the same name as filename, at the same + /// location. + /// + /// The default value is a system suggested path, based on users' local environment. + /// /// Set the `ResultFilePath` for save as - [propput] HRESULT ResultFilePath ([in] LPCWSTR resultFilePath); + [propput] HRESULT ResultFilePath ([in] LPCWSTR value); /// Get the `ResultFilePath` for save as - [propget] HRESULT ResultFilePath ([out, retval] LPWSTR* resultFilePath); + [propget] HRESULT ResultFilePath ([out, retval] LPWSTR* value); /// `AllowReplace` allows you to control what happens when a file already /// exists in the file path to which the Save As operation is saving. @@ -256,22 +302,26 @@ interface ICoreWebView2StagingSaveAsRequestedEventArgs : IUnknown { /// Settings this FALSE will not replace existing files and will return /// COREWEBVIEW2_SAVE_AS_FILE_ALREADY_EXISTS. /// + /// The default value is FALSE + /// /// Set if allowed to replace the old file if duplicate happens in the save as job - [propput] HRESULT AllowReplace ([in] BOOL allowReplace); + [propput] HRESULT AllowReplace ([in] BOOL value); /// Get the duplicates replace rule for save as - [propget] HRESULT AllowReplace ([out, retval] BOOL* allowReplace); + [propget] HRESULT AllowReplace ([out, retval] BOOL* value); /// How to save documents with different types. See the enum /// COREWEBVIEW2_SAVE_AS_TYPE for a description of the different options. /// If the type isn't allowed for the current document, /// COREWEBVIEW2_SAVE_AS_TYPE_NOT_SUPPORT will be returned from SaveContentAs. /// + /// The default value is COREWEBVIEW2_SAVE_AS_TYPE_DEFAULT + /// /// Set the content save as type for save as job - [propput] HRESULT SaveAsType ([in] COREWEBVIEW2_SAVE_AS_TYPE type); + [propput] HRESULT SaveAsType ([in] COREWEBVIEW2_SAVE_AS_TYPE value); /// Get the content save as type for save as job - [propget] HRESULT SaveAsType ([out, retval] COREWEBVIEW2_SAVE_AS_TYPE* type); + [propget] HRESULT SaveAsType ([out, retval] COREWEBVIEW2_SAVE_AS_TYPE* value); } /// Receive the result for `SaveContentAs` method From 84bbe8e4a25b72b0d2f94600d4df7a7794b562d0 Mon Sep 17 00:00:00 2001 From: Eain Chen Date: Wed, 27 Sep 2023 13:47:45 -0700 Subject: [PATCH 05/13] Propose new naming --- specs/ProgrammaticSaveAs.md | 156 ++++++++++++++++-------------------- 1 file changed, 68 insertions(+), 88 deletions(-) diff --git a/specs/ProgrammaticSaveAs.md b/specs/ProgrammaticSaveAs.md index 6cda1ce95..ebfd1a19a 100644 --- a/specs/ProgrammaticSaveAs.md +++ b/specs/ProgrammaticSaveAs.md @@ -7,25 +7,25 @@ The context menu has the "Save as" item to manually save the html page, image, pdf, or other content through a save as dialog. We provide more flexiable ways to do the save as programmatically in WebView2. You can bring up the default save as dialog easily. And you will be able to block default dialog, save the -content silently, by providing the path and save as type programmatically or +content silently, by providing the path and save as kind programmatically or even build your own save as UI. In this document we describe the API. We'd appreciate your feedback. # Description -We propose the `SaveContentAs` method WebView2, which allows you to trigger -the save as programmatically. By using this method alone, the system default -will popup. +We propose the `RequestSaveAs` method WebView2, which allows you to trigger +the save as programmatically. By using this method, the system default dialog, +or your own ui will popup. -Additionally, we propose the `SaveAsRequested` event. You can register this -event to block the default dialog and use the `SaveAsRequestedEventArgs` -instead, to set your preferred save as path, save as type, and duplicate file -replacement rule. In your client app, you can design your own UI to input -these parameters. For HTML documents, we support 3 save as types: HTML_ONLY, -SINGLE_FILE and COMPLETE. Non-HTML documents, must use DEFAULT, which will -save the content as it is. This API has default values for all parameters, -to perform the common save as operation. +We propose the `SaveAsRequested` event. You can register this event to block +the default dialog and use the `SaveAsRequestedEventArgs` instead, to set +your preferred save as path, save as kind, and duplicate file replacement rule. +In your client app, you can design your own UI to input these parameters. +For HTML documents, we support 3 save as kinds: HTML_ONLY, SINGLE_FILE and +COMPLETE. Non-HTML documents, must use DEFAULT, which will save the content as +it is. This API has default values for all parameters, to perform the common +save as operation. # Examples ## Win32 C++ @@ -66,15 +66,15 @@ bool ScenarioSaveAs::ToggleEventHandler() // // This is a customized dialog example, the constructor returns after the // dialog interaction is completed by the end user. - SaveAsDialog dialog(m_appWindow->GetMainWindow(), contentSaveTypes); + SaveAsDialog dialog(m_appWindow->GetMainWindow(), saveKinds); if (dialog.confirmed) { - // Setting the ResultFilePath, SaveAsType, AllowReplace for the event + // Setting the ResultFilePath, Kind, AllowReplace for the event // args from this customized dialog inputs is optional. // The event args has default values based on the document to save. CHECK_FAILURE( args->put_ResultFilePath((LPCWSTR)dialog.path.c_str())); - CHECK_FAILURE(args->put_SaveAsType(dialog.selectedType)); + CHECK_FAILURE(args->put_Kind(dialog.selectedKind)); CHECK_FAILURE(args->put_AllowReplace(dialog.allowReplace)); } else @@ -82,9 +82,6 @@ bool ScenarioSaveAs::ToggleEventHandler() // Save As cancelled from this customized dialog CHECK_FAILURE(args->put_Cancel(TRUE)); } - - // Indicate out parameters have been set. - CHECK_FAILURE(args->put_Handled(TRUE)); }; wil::com_ptr deferral; @@ -110,26 +107,21 @@ bool ScenarioSaveAs::ToggleEventHandler() } ``` ### Programmatic Save As -Call SaveContentAs method to trigger the programmatic save as. +Call RequestSaveAs method to trigger the programmatic save as. ```c++ bool ScenarioSaveAs::ProgrammaticSaveAs() { if (!m_webView2Staging20) return false; - m_webView2Staging20->SaveContentAs( - Callback( - [this](HRESULT errorCode, COREWEBVIEW2_SAVE_CONTENT_AS_RESULTS result) -> HRESULT + m_webView2Staging20->RequestSaveAs( + Callback( + [this](HRESULT errorCode, COREWEBVIEW2_SAVE_AS_REQUESTED_RESULTS result) -> HRESULT { - // Show SaveContentAs returned result, optional - // Skip message box when the result is COREWEBVIEW2_SAVE_AS_OPEN_SYSTEM_DIALOG (0), - // to prevent it deactivates save as modal dialog focus on the window - if (result > 0) - { - MessageBox( - m_appWindow->GetMainWindow(), - (L"Save As " + saveAsResultString[result]).c_str(), L"Info", MB_OK); - } + // Show RequestSaveAs returned result, optional + MessageBox( + m_appWindow->GetMainWindow(), + (L"Save As " + saveAsResultString[result]).c_str(), L"Info", MB_OK); return S_OK; }) .Get()); @@ -140,36 +132,34 @@ bool ScenarioSaveAs::ProgrammaticSaveAs() # API Details ## Win32 C++ ```c++ -/// Specifies save as type selection options for `ICoreWebView2Staging20`, +/// Specifies save as requested kind selection options for `ICoreWebView2Staging20`, /// used in `SaveAsRequestedEventArgs` /// /// When the source is a html page, supports to select `HTML_ONLY`, /// `SINGLE_FILE`, `COMPLETE`; when the source is a non-html, /// only allows to select `DEFAULT`; otherwise, will deny the download -/// and return `COREWEBVIEW2_SAVE_AS_TYPE_NOT_SUPPORTED`. +/// and return `COREWEBVIEW2_SAVE_AS_REQUESTED_KIND_NOT_SUPPORTED`. /// /// The content type/format is a MIME type, indicated by the source /// server side and identified by the browser. It’s not related to the /// file’s type or extension. MIME type of `text/html`, /// `application/xhtml+xml` are considered as html page. -[v1_enum] typedef enum COREWEBVIEW2_SAVE_AS_TYPE { +[v1_enum] typedef enum COREWEBVIEW2_SAVE_AS_REQUESTED_KIND { /// Default to save for a non-html content. If it is selected for a html /// page, it’s same as HTML_ONLY option. - COREWEBVIEW2_SAVE_AS_TYPE_DEFAULT, + COREWEBVIEW2_SAVE_AS_REQUESTED_KIND_DEFAULT, /// Save the page as html - COREWEBVIEW2_SAVE_AS_TYPE_HTML_ONLY, + COREWEBVIEW2_SAVE_AS_REQUESTED_KIND_HTML_ONLY, /// Save the page as mhtml - COREWEBVIEW2_SAVE_AS_TYPE_SINGLE_FILE, + COREWEBVIEW2_SAVE_AS_REQUESTED_KIND_SINGLE_FILE, /// Save the page as html, plus, download the page related source files in /// a same name directory - COREWEBVIEW2_SAVE_AS_TYPE_COMPLETE, -} COREWEBVIEW2_SAVE_AS_TYPE; + COREWEBVIEW2_SAVE_AS_REQUESTED_KIND_COMPLETE, +} COREWEBVIEW2_SAVE_AS_REQUESTED_KIND; /// Status of a programmatic save as call, indicates the result -/// for method `SaveContentAs` -[v1_enum] typedef enum COREWEBVIEW2_SAVE_CONTENT_AS_RESULTS { - /// Programmatically open a system default save as dialog - COREWEBVIEW2_SAVE_AS_OPEN_SYSTEM_DIALOG, +/// for method `RequestSaveAs` +[v1_enum] typedef enum COREWEBVIEW2_SAVE_AS_REQUESTED_RESULTS { /// Could not perform Save As because the destination file path is an invalid path. /// /// It is considered as invalid when: @@ -178,38 +168,39 @@ bool ScenarioSaveAs::ProgrammaticSaveAs() /// /// Parent directory can be itself, if the path is root directory, or /// root disk. When the root doesn't exist, the path is invalid. - COREWEBVIEW2_SAVE_AS_INVALID_PATH, + COREWEBVIEW2_SAVE_AS_REQUESTED_INVALID_PATH, /// Could not perform Save As because the destination file path already exists and - /// replacing files was not allowed by the AllowReplace property. - COREWEBVIEW2_SAVE_AS_FILE_ALREADY_EXISTS, - /// Save as downloading not start as the `SAVE_AS_TYPE` selection not + /// replacing files was not allowed by the `AllowReplace` property. + COREWEBVIEW2_SAVE_AS_REQUESTED_FILE_ALREADY_EXISTS, + /// Could not perform Save As when the `Kind` property selection not /// supported because of the content MIME type or system limits /// - /// MIME type limits please see the emun `COREWEBVIEW2_SAVE_AS_TYPE` + /// MIME type limits please see the emun `COREWEBVIEW2_SAVE_AS_REQUESTED_KIND` /// /// System limits might happen when select `HTML_ONLY` for an error page, /// select `COMPLETE` and WebView running in an App Container, etc. - COREWEBVIEW2_SAVE_AS_TYPE_NOT_SUPPORTED, + COREWEBVIEW2_SAVE_AS_REQUESTED_KIND_NOT_SUPPORTED, /// Did not perform Save As because the client side decided to cancel. - COREWEBVIEW2_SAVE_AS_CANCELLED, - /// Save as request complete, the downloading started - COREWEBVIEW2_SAVE_AS_STARTED -} COREWEBVIEW2_SAVE_CONTENT_AS_RESULTS; + COREWEBVIEW2_SAVE_AS_REQUESTED_CANCELLED, + /// Save as requested completeed, the downloading would start + COREWEBVIEW2_SAVE_AS_REQUESTED_STARTED +} COREWEBVIEW2_SAVE_AS_REQUESTED_RESULTS; [uuid(15e1c6a3-c72a-4df3-91d7-d097fbec3bfd), object, pointer_default(unique)] interface ICoreWebView2Staging20 : IUnknown { - /// Programmatically trigger a save as action for current content. + /// Programmatically trigger a save as action for current content. `SaveAsRequested` + /// event will be raised. /// - /// Opens a system modal dialog by default. Returns COREWEBVIEW2_SAVE_AS_OPEN_SYSTEM_DIALOG. - /// If it was already opened, this method would not open another one + /// Opens a system modal dialog by default. If it was already opened, this method + /// would not open another one. If the `SuppressDefaultDialog` is TRUE, won't open + /// the system dialog. /// - /// If the silent save as option is enabled, won't open the system dialog, will - /// raise the `SaveAsRequested` event instead and process through its event args. The method can return - /// a detailed info to indicate the call's result. Please see COREWEBVIEW2_SAVE_CONTENT_AS_RESULTS + /// The method can return a detailed info to indicate the call's result. + /// Please see COREWEBVIEW2_SAVE_AS_REQUESTED_RESULTS /// /// \snippet ScenarioSaveAs.cpp ProgrammaticSaveAs - HRESULT SaveContentAs([in] ICoreWebView2StagingSaveContentAsCompletedHandler* handler); + HRESULT SaveContentAs([in] ICoreWebView2StagingRequestSaveAsCompletedHandler* handler); /// Add an event handler for the `SaveAsRequested` event. This event is raised /// when save as is triggered, programmatically or manually. @@ -226,8 +217,7 @@ interface ICoreWebView2Staging20 : IUnknown { [in] EventRegistrationToken token); } -/// The event handler for the `SaveAsRequested` event, when the handler -/// exists, the silent save as enables. +/// The event handler for the `SaveAsRequested` event. [uuid(55b86cd2-adfd-47f1-9cef-cdfb8c414ed3), object, pointer_default(unique)] interface ICoreWebView2StagingSaveAsRequestedEventHandler : IUnknown { HRESULT Invoke( @@ -241,16 +231,6 @@ interface ICoreWebView2StagingSaveAsRequestedEventArgs : IUnknown { /// Get the Mime type of content to be saved [propget] HRESULT ContentMimeType([out, retval] LPWSTR* value); - /// Indicates if pramameters in the event args has been set, TRUE means been set. - /// - /// The default value is FALSE. - /// - /// Set the `Handled` for save as - [propput] HRESULT Handled ([in] BOOL value); - - /// Get the `Handled` for save as - [propget] HRESULT Handled ([out, retval] BOOL* value); - /// Indicates if client side cancelled the silent save as, TRUE means cancelled. /// When the event is invoked, the download won't start. A programmatic call will /// return COREWEBVIEW2_SAVE_AS_CANCELLED as well. @@ -284,9 +264,9 @@ interface ICoreWebView2StagingSaveAsRequestedEventArgs : IUnknown { /// not exist, save as will be denied and return COREWEBVIEW2_SAVE_AS_INVALID_PATH. /// /// When the download complete and success, a target file will be saved at this - /// location. If the SAVE_AS_TYPE is `COMPLETE`, will be an additional directory - /// with resources files. The directory has the same name as filename, at the same - /// location. + /// location. If the select `COREWEBVIEW2_SAVE_AS_REQUESTED_KIND_COMPLETE`, will be + /// an additional directory with resources files. The directory has the same name + /// as filename, at the same location. /// /// The default value is a system suggested path, based on users' local environment. /// @@ -304,29 +284,29 @@ interface ICoreWebView2StagingSaveAsRequestedEventArgs : IUnknown { /// /// The default value is FALSE /// - /// Set if allowed to replace the old file if duplicate happens in the save as job + /// Set if allowed to replace the old file if duplicate happens in the save as [propput] HRESULT AllowReplace ([in] BOOL value); /// Get the duplicates replace rule for save as [propget] HRESULT AllowReplace ([out, retval] BOOL* value); - /// How to save documents with different types. See the enum - /// COREWEBVIEW2_SAVE_AS_TYPE for a description of the different options. - /// If the type isn't allowed for the current document, - /// COREWEBVIEW2_SAVE_AS_TYPE_NOT_SUPPORT will be returned from SaveContentAs. + /// How to save documents with different kind. See the enum + /// COREWEBVIEW2_SAVE_AS_REQUESTED_KIND for a description of the different options. + /// If the kind isn't allowed for the current document, + /// COREWEBVIEW2_SAVE_AS_REQUESTED_KIND_NOT_SUPPORT will be returned from RequestSaveAs. /// - /// The default value is COREWEBVIEW2_SAVE_AS_TYPE_DEFAULT + /// The default value is COREWEBVIEW2_SAVE_AS_REQUESTED_KIND_DEFAULT /// - /// Set the content save as type for save as job - [propput] HRESULT SaveAsType ([in] COREWEBVIEW2_SAVE_AS_TYPE value); + /// Set the kind for save as + [propput] HRESULT Kind ([in] COREWEBVIEW2_SAVE_AS_REQUESTED_KIND value); - /// Get the content save as type for save as job - [propget] HRESULT SaveAsType ([out, retval] COREWEBVIEW2_SAVE_AS_TYPE* value); + /// Get the kind for save as + [propget] HRESULT Kind ([out, retval] COREWEBVIEW2_SAVE_AS_REQUESTED_KIND* value); } -/// Receive the result for `SaveContentAs` method +/// Receive the result for `RequestSaveAs` method [uuid(1a02e9d9-14d3-41c6-9581-8d6e1e6f50fe), object, pointer_default(unique)] -interface ICoreWebView2StagingSaveContentAsCompletedHandler : IUnknown { - HRESULT Invoke([in] HRESULT errorCode, [in] COREWEBVIEW2_SAVE_CONTENT_AS_RESULTS result); +interface ICoreWebView2StagingRequestSaveAsCompletedHandler : IUnknown { + HRESULT Invoke([in] HRESULT errorCode, [in] COREWEBVIEW2_REQUEST_SAVE_RESULTS result); } ``` From 601505418d0cf6393fb7ca3d066087f34ceb4225 Mon Sep 17 00:00:00 2001 From: Eain Chen Date: Fri, 29 Sep 2023 16:03:44 -0700 Subject: [PATCH 06/13] Add .net winrt code --- specs/ProgrammaticSaveAs.md | 154 +++++++++++++++++++++++++++++++----- 1 file changed, 136 insertions(+), 18 deletions(-) diff --git a/specs/ProgrammaticSaveAs.md b/specs/ProgrammaticSaveAs.md index ebfd1a19a..0130d5004 100644 --- a/specs/ProgrammaticSaveAs.md +++ b/specs/ProgrammaticSaveAs.md @@ -34,21 +34,23 @@ This example hides the default save as dialog and shows a customized dialog. ```c++ bool ScenarioSaveAs::ToggleEventHandler() { - if (!m_webView2Staging20) + if (!m_webView2_20) return false; + // m_hasSaveAsRequestedEventHandler indicates whether the event handler + // has been subscribed. if (m_hasSaveAsRequestedEventHandler) { // Unregister the handler for the `SaveAsRequested` event. - m_webView2Staging20->remove_SaveAsRequested(m_saveAsRequestedToken); + m_webView2_20->remove_SaveAsRequested(m_saveAsRequestedToken); } else { // Register a handler for the `SaveAsRequested` event. - m_webView2Staging20->add_SaveAsRequested( - Callback( + m_webView2_20->add_SaveAsRequested( + Callback( [this]( ICoreWebView2* sender, - ICoreWebView2StagingSaveAsRequestedEventArgs* args) -> HRESULT + ICoreWebView2SaveAsRequestedEventArgs* args) -> HRESULT { // Hide the system default save as dialog. CHECK_FAILURE(args->put_SuppressDefaultDialog(TRUE)); @@ -59,7 +61,6 @@ bool ScenarioSaveAs::ToggleEventHandler() wil::unique_cotaskmem_string mimeType; CHECK_FAILURE(args->get_ContentMimeType(&mimeType)); - // As an end developer, you can design your own dialog UI, or no UI at all. // You can ask the user information like file name, file extenstion, and etc. // Finally, concatenate and pass them into the event args @@ -112,10 +113,10 @@ Call RequestSaveAs method to trigger the programmatic save as. bool ScenarioSaveAs::ProgrammaticSaveAs() { - if (!m_webView2Staging20) + if (!m_webView2_20) return false; - m_webView2Staging20->RequestSaveAs( - Callback( + m_webView2_20->RequestSaveAs( + Callback( [this](HRESULT errorCode, COREWEBVIEW2_SAVE_AS_REQUESTED_RESULTS result) -> HRESULT { // Show RequestSaveAs returned result, optional @@ -129,10 +130,77 @@ bool ScenarioSaveAs::ProgrammaticSaveAs() } ``` +## .Net/ WinRT +### Add or Remove the Event Handler +This example hides the default save as dialog and shows a customized dialog. +```c# + +void TaggleEventHandlerExecuted(object target, ExecutedRoutedEventArgs e) +{ + if (hasSaveAsRequestedEventHandler) + webView.CoreWebView2.SaveAsRequested -= WebView_SaveAsRequested; + else + webView.CoreWebView2.SaveAsRequested += WebView_SaveAsRequested; + hasSaveAsRequestedEventHandler = !hasSaveAsRequestedEventHandler; + MessageBox.Show(hasSaveAsRequestedEventHandler? "Event Handler Added":"Event Handler Rremoved" , "Info"); +} + +void WebView_SaveAsRequested(object sender, CoreWebView2SaveAsRequestedEventArgs args) + { + // Hide the system default save as dialog. + args.SuppressDefaultDialog = true; + + // Developer can obtain a deferral for the event so that the CoreWebView2 + // doesn't examine the properties we set on the event args until + // after the deferral completes asynchronously. + CoreWebView2Deferral deferral = args.GetDeferral(); + + // We avoid potential reentrancy from running a message loop in the event + // handler. Show the customized dialog later when complete the deferral + // asynchronously. + System.Threading.SynchronizationContext.Current.Post((_) => + { + using (deferral) + { + // This is a customized dialog example, the constructor returns after the + // dialog interaction is completed by the end user. + var dialog = new SaveAsDialog(_Kinds); + if (dialog.ShowDialog() == true) + { + // Preview the content mime type, optional. + args.ContentMimeType; + + // Setting parameters of event args from this dialog is optional. + // The event args has default values. + args.ResultFilePath = dialog.Directory.Text + "/" + dialog.Filename.Text; + args.Kind = (CoreWebView2SaveAsRequestedKind)dialog.Kind.SelectedItem; + args.AllowReplace = (bool)dialog.AllowReplaceOldFile.IsChecked; + + } + else + { + // Save As cancelled from this customized dialog + args.Cancel = true; + } + } + }, null); +} +``` +### Programmatic Save As +Call RequestSaveAsAsync method to trigger the programmatic save as. +```c# +async void ProgrammaticSaveAsExecuted(object target, ExecutedRoutedEventArgs e) +{ + CoreWebView2SaveAsRequestedResults result = await webView.CoreWebView2.RequestSaveAsAsync(); + // Show RequestSaveAsAsync returned result, optional + MessageBox.Show(result.ToString(), "Info"); +} +``` + # API Details ## Win32 C++ ```c++ -/// Specifies save as requested kind selection options for `ICoreWebView2Staging20`, +/// Specifies save as requested kind selection options for `ICoreWebView2_20`, /// used in `SaveAsRequestedEventArgs` /// /// When the source is a html page, supports to select `HTML_ONLY`, @@ -183,12 +251,12 @@ bool ScenarioSaveAs::ProgrammaticSaveAs() /// Did not perform Save As because the client side decided to cancel. COREWEBVIEW2_SAVE_AS_REQUESTED_CANCELLED, /// Save as requested completeed, the downloading would start - COREWEBVIEW2_SAVE_AS_REQUESTED_STARTED + COREWEBVIEW2_SAVE_AS_REQUESTED_COMPLETED, } COREWEBVIEW2_SAVE_AS_REQUESTED_RESULTS; [uuid(15e1c6a3-c72a-4df3-91d7-d097fbec3bfd), object, pointer_default(unique)] -interface ICoreWebView2Staging20 : IUnknown { +interface ICoreWebView2_20 : IUnknown { /// Programmatically trigger a save as action for current content. `SaveAsRequested` /// event will be raised. /// @@ -200,14 +268,14 @@ interface ICoreWebView2Staging20 : IUnknown { /// Please see COREWEBVIEW2_SAVE_AS_REQUESTED_RESULTS /// /// \snippet ScenarioSaveAs.cpp ProgrammaticSaveAs - HRESULT SaveContentAs([in] ICoreWebView2StagingRequestSaveAsCompletedHandler* handler); + HRESULT SaveContentAs([in] ICoreWebView2RequestSaveAsCompletedHandler* handler); /// Add an event handler for the `SaveAsRequested` event. This event is raised /// when save as is triggered, programmatically or manually. /// /// \snippet ScenarioSaveAs.cpp ToggleEventHandler HRESULT add_SaveAsRequested( - [in] ICoreWebView2StagingSaveAsRequestedEventHandler* eventHanlder, + [in] ICoreWebView2SaveAsRequestedEventHandler* eventHanlder, [out] EventRegistrationToken* token); /// Remove an event handler previously added with `add_SaveAsRequested`. @@ -219,15 +287,15 @@ interface ICoreWebView2Staging20 : IUnknown { /// The event handler for the `SaveAsRequested` event. [uuid(55b86cd2-adfd-47f1-9cef-cdfb8c414ed3), object, pointer_default(unique)] -interface ICoreWebView2StagingSaveAsRequestedEventHandler : IUnknown { +interface ICoreWebView2SaveAsRequestedEventHandler : IUnknown { HRESULT Invoke( [in] ICoreWebView2* sender, - [in] ICoreWebView2StagingSaveAsRequestedEventArgs* args); + [in] ICoreWebView2SaveAsRequestedEventArgs* args); } /// The event args for `SaveAsRequested` event [uuid(80101027-b8c3-49a1-a052-9ea4bd63ba47), object, pointer_default(unique)] -interface ICoreWebView2StagingSaveAsRequestedEventArgs : IUnknown { +interface ICoreWebView2SaveAsRequestedEventArgs : IUnknown { /// Get the Mime type of content to be saved [propget] HRESULT ContentMimeType([out, retval] LPWSTR* value); @@ -306,7 +374,57 @@ interface ICoreWebView2StagingSaveAsRequestedEventArgs : IUnknown { /// Receive the result for `RequestSaveAs` method [uuid(1a02e9d9-14d3-41c6-9581-8d6e1e6f50fe), object, pointer_default(unique)] -interface ICoreWebView2StagingRequestSaveAsCompletedHandler : IUnknown { +interface ICoreWebView2RequestSaveAsCompletedHandler : IUnknown { HRESULT Invoke([in] HRESULT errorCode, [in] COREWEBVIEW2_REQUEST_SAVE_RESULTS result); } ``` + +## .Net/ WinRT +```c# (but really MIDL3) +namespace Microsoft.Web.WebView2.Core +{ + + runtimeclass CoreWebView2SaveAsRequestedEventArgs; + runtimeclass CoreWebView2; + + enum CoreWebView2SaveAsRequestedResults + { + InvalidPath = 0, + FileAlreadyExists = 1, + KindNotSupported = 2, + Cancelled = 3, + Completed = 4, + }; + + enum CoreWebView2SaveAsRequestedKind + { + Default = 0, + HtmlOnly = 1, + SingleFile = 2, + Complete = 3, + }; + + runtimeclass CoreWebView2SaveAsRequestedEventArgs + { + String ContentMimeType { get; }; + Boolean Cancel { get; set; }; + Boolean SuppressDefaultDialog { get; set; }; + String ResultFilePath { get; set; }; + Boolean AllowReplace { get; set; }; + CoreWebView2SaveAsRequestedKind Kind { get; set; }; + Windows.Foundation.Deferral GetDeferral(); + }; + + runtimeclass CoreWebView2 + { + [interface_name("Microsoft.Web.WebView2.Core.ICoreWebView2_20")] + { + // ... + event Windows.Foundation.TypedEventHandler + SaveAsRequested; + Windows.Foundation.IAsyncOperation + RequestSaveAsAsync(); + } + }; +} +``` \ No newline at end of file From 6aefe7b59c45c2658a0343b6bb23760e111b820d Mon Sep 17 00:00:00 2001 From: Eain Chen Date: Mon, 2 Oct 2023 08:30:56 -0700 Subject: [PATCH 07/13] Adjust formats --- specs/ProgrammaticSaveAs.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/specs/ProgrammaticSaveAs.md b/specs/ProgrammaticSaveAs.md index 0130d5004..02831d74e 100644 --- a/specs/ProgrammaticSaveAs.md +++ b/specs/ProgrammaticSaveAs.md @@ -175,7 +175,6 @@ void WebView_SaveAsRequested(object sender, CoreWebView2SaveAsRequestedEventArgs args.ResultFilePath = dialog.Directory.Text + "/" + dialog.Filename.Text; args.Kind = (CoreWebView2SaveAsRequestedKind)dialog.Kind.SelectedItem; args.AllowReplace = (bool)dialog.AllowReplaceOldFile.IsChecked; - } else { @@ -254,7 +253,6 @@ async void ProgrammaticSaveAsExecuted(object target, ExecutedRoutedEventArgs e) COREWEBVIEW2_SAVE_AS_REQUESTED_COMPLETED, } COREWEBVIEW2_SAVE_AS_REQUESTED_RESULTS; - [uuid(15e1c6a3-c72a-4df3-91d7-d097fbec3bfd), object, pointer_default(unique)] interface ICoreWebView2_20 : IUnknown { /// Programmatically trigger a save as action for current content. `SaveAsRequested` @@ -417,9 +415,10 @@ namespace Microsoft.Web.WebView2.Core runtimeclass CoreWebView2 { + // ... + [interface_name("Microsoft.Web.WebView2.Core.ICoreWebView2_20")] { - // ... event Windows.Foundation.TypedEventHandler SaveAsRequested; Windows.Foundation.IAsyncOperation From b04883901b9dccb15f35b8f2ca9c95c22b479512 Mon Sep 17 00:00:00 2001 From: Eain Chen Date: Tue, 3 Oct 2023 15:55:03 -0700 Subject: [PATCH 08/13] Apply new suggestiongs --- specs/ProgrammaticSaveAs.md | 225 +++++++++++++++++------------------- 1 file changed, 109 insertions(+), 116 deletions(-) diff --git a/specs/ProgrammaticSaveAs.md b/specs/ProgrammaticSaveAs.md index 02831d74e..2d8cf60ac 100644 --- a/specs/ProgrammaticSaveAs.md +++ b/specs/ProgrammaticSaveAs.md @@ -3,107 +3,105 @@ Programmatic Save As API # Background -The context menu has the "Save as" item to manually save the html page, image, -pdf, or other content through a save as dialog. We provide more flexiable ways -to do the save as programmatically in WebView2. You can bring up the default -save as dialog easily. And you will be able to block default dialog, save the -content silently, by providing the path and save as kind programmatically or -even build your own save as UI. +Chromium browser's context menus have a "Save as" menu item to save the top level +document (html page, image, pdf, or other content) through a save as dialog. We +provide more flexible ways to programmatically perform the Save As operation in +WebView2. -In this document we describe the API. We'd appreciate your feedback. +With the new API you will be able to: +- Launch the default save as dialog +- Block the default save as dialog +- Request save as silently by providing the path and save as kind +- Build your own save as UI + +The chromium browser's Save As operation consists of showing the Save As dialog +and then starting a download of the top level document. The Save As method and +event described in this document relate to the Save As dialog and not the download, +which will go through the existing WebView2 download APIs. + +We'd appreciate your feedback. # Description We propose the `RequestSaveAs` method WebView2, which allows you to trigger -the save as programmatically. By using this method, the system default dialog, -or your own ui will popup. +the Save As UX programmatically. By using this method, the system default dialog, +or your own UI will show and start the Save As operation. -We propose the `SaveAsRequested` event. You can register this event to block -the default dialog and use the `SaveAsRequestedEventArgs` instead, to set -your preferred save as path, save as kind, and duplicate file replacement rule. +We also propose the `CoreWebView2.SaveAsRequested` event. You can register this event to block +the default dialog and instead create your own Save As UI using the `SaveAsRequestedEventArgs`, +to set your preferred save as path, save as kind, and duplicate file replacement rule. In your client app, you can design your own UI to input these parameters. For HTML documents, we support 3 save as kinds: HTML_ONLY, SINGLE_FILE and -COMPLETE. Non-HTML documents, must use DEFAULT, which will save the content as +COMPLETE. Non-HTML documents, you must use DEFAULT, which will save the content as it is. This API has default values for all parameters, to perform the common save as operation. # Examples ## Win32 C++ -### Add or Remove the Event Handler +### Add the Event Handler This example hides the default save as dialog and shows a customized dialog. ```c++ -bool ScenarioSaveAs::ToggleEventHandler() +bool ScenarioSaveAs::AddEventHandler() { if (!m_webView2_20) return false; - // m_hasSaveAsRequestedEventHandler indicates whether the event handler - // has been subscribed. - if (m_hasSaveAsRequestedEventHandler) - { - // Unregister the handler for the `SaveAsRequested` event. - m_webView2_20->remove_SaveAsRequested(m_saveAsRequestedToken); - } - else - { - // Register a handler for the `SaveAsRequested` event. - m_webView2_20->add_SaveAsRequested( - Callback( - [this]( - ICoreWebView2* sender, - ICoreWebView2SaveAsRequestedEventArgs* args) -> HRESULT + + // Register a handler for the `SaveAsRequested` event. + m_webView2_20->add_SaveAsRequested( + Callback( + [this]( + ICoreWebView2* sender, + ICoreWebView2SaveAsRequestedEventArgs* args) -> HRESULT + { + // Hide the system default save as dialog. + CHECK_FAILURE(args->put_SuppressDefaultDialog(TRUE)); + + auto showCustomizedDialog = [this, args] { - // Hide the system default save as dialog. - CHECK_FAILURE(args->put_SuppressDefaultDialog(TRUE)); - - auto showCustomizedDialog = [this, args] + // Preview the content mime type, optional + wil::unique_cotaskmem_string mimeType; + CHECK_FAILURE(args->get_ContentMimeType(&mimeType)); + + // As an end developer, you can design your own dialog UI, or no UI at all. + // You can ask the user to provide information like file name, file extension, and so on. + // Finally, and set them on the event args + // + // This is a customized dialog example, the constructor returns after the + // dialog interaction is completed by the end user. + SaveAsDialog dialog(m_appWindow->GetMainWindow(), saveKinds); + if (dialog.confirmed) + { + // Setting the ResultFilePath, Kind, AllowReplace for the event + // args from this customized dialog inputs is optional. + // The event args has default values based on the document to save. + CHECK_FAILURE( + args->put_ResultFilePath((LPCWSTR)dialog.path.c_str())); + CHECK_FAILURE(args->put_Kind(dialog.selectedKind)); + CHECK_FAILURE(args->put_AllowReplace(dialog.allowReplace)); + } + else { - // Preview the content mime type, optional - wil::unique_cotaskmem_string mimeType; - CHECK_FAILURE(args->get_ContentMimeType(&mimeType)); - - // As an end developer, you can design your own dialog UI, or no UI at all. - // You can ask the user information like file name, file extenstion, and etc. - // Finally, concatenate and pass them into the event args - // - // This is a customized dialog example, the constructor returns after the - // dialog interaction is completed by the end user. - SaveAsDialog dialog(m_appWindow->GetMainWindow(), saveKinds); - if (dialog.confirmed) - { - // Setting the ResultFilePath, Kind, AllowReplace for the event - // args from this customized dialog inputs is optional. - // The event args has default values based on the document to save. - CHECK_FAILURE( - args->put_ResultFilePath((LPCWSTR)dialog.path.c_str())); - CHECK_FAILURE(args->put_Kind(dialog.selectedKind)); - CHECK_FAILURE(args->put_AllowReplace(dialog.allowReplace)); - } - else - { - // Save As cancelled from this customized dialog - CHECK_FAILURE(args->put_Cancel(TRUE)); - } - }; - - wil::com_ptr deferral; - CHECK_FAILURE(args->GetDeferral(&deferral)); - - m_appWindow->RunAsync( - [deferral, showCustomizedDialog]() - { - showCustomizedDialog(); - CHECK_FAILURE(deferral->Complete()); - }); - return S_OK; - }) - .Get(), - &m_saveAsRequestedToken); - } - m_hasSaveAsRequestedEventHandler = !m_hasSaveAsRequestedEventHandler; + // Save As cancelled from this customized dialog + CHECK_FAILURE(args->put_Cancel(TRUE)); + } + }; + + wil::com_ptr deferral; + CHECK_FAILURE(args->GetDeferral(&deferral)); + + m_appWindow->RunAsync( + [deferral, showCustomizedDialog]() + { + showCustomizedDialog(); + CHECK_FAILURE(deferral->Complete()); + }); + return S_OK; + }) + .Get(), + &m_saveAsRequestedToken); + MessageBox( - m_appWindow->GetMainWindow(), - (m_hasSaveAsRequestedEventHandler ? L"Event Handler Added" : L"Event Handler Rremoved"), L"Info", - MB_OK); + m_appWindow->GetMainWindow(), L"Event Handler Added", L"Info",MB_OK); return true; } ``` @@ -131,18 +129,14 @@ bool ScenarioSaveAs::ProgrammaticSaveAs() ``` ## .Net/ WinRT -### Add or Remove the Event Handler +### Add the Event Handler This example hides the default save as dialog and shows a customized dialog. ```c# -void TaggleEventHandlerExecuted(object target, ExecutedRoutedEventArgs e) +void AddEventHandlerExecuted(object target, ExecutedRoutedEventArgs e) { - if (hasSaveAsRequestedEventHandler) - webView.CoreWebView2.SaveAsRequested -= WebView_SaveAsRequested; - else - webView.CoreWebView2.SaveAsRequested += WebView_SaveAsRequested; - hasSaveAsRequestedEventHandler = !hasSaveAsRequestedEventHandler; - MessageBox.Show(hasSaveAsRequestedEventHandler? "Event Handler Added":"Event Handler Rremoved" , "Info"); + webView.CoreWebView2.SaveAsRequested -= WebView_SaveAsRequested; + MessageBox.Show("Event Handler Added", "Info"); } void WebView_SaveAsRequested(object sender, CoreWebView2SaveAsRequestedEventArgs args) @@ -167,11 +161,12 @@ void WebView_SaveAsRequested(object sender, CoreWebView2SaveAsRequestedEventArgs var dialog = new SaveAsDialog(_Kinds); if (dialog.ShowDialog() == true) { - // Preview the content mime type, optional. - args.ContentMimeType; - // Setting parameters of event args from this dialog is optional. // The event args has default values. + // + // Additionally, you can use `args.ContentMimeType` to check the mime + // type of the document that will be saved to help setup your custom + // Save As dialog UI args.ResultFilePath = dialog.Directory.Text + "/" + dialog.Filename.Text; args.Kind = (CoreWebView2SaveAsRequestedKind)dialog.Kind.SelectedItem; args.AllowReplace = (bool)dialog.AllowReplaceOldFile.IsChecked; @@ -199,11 +194,11 @@ async void ProgrammaticSaveAsExecuted(object target, ExecutedRoutedEventArgs e) # API Details ## Win32 C++ ```c++ -/// Specifies save as requested kind selection options for `ICoreWebView2_20`, -/// used in `SaveAsRequestedEventArgs` +/// Specifies save as requested kind selection options for +/// `ICoreWebView2SaveAsRequestedEventArgs`. /// -/// When the source is a html page, supports to select `HTML_ONLY`, -/// `SINGLE_FILE`, `COMPLETE`; when the source is a non-html, +/// When the source is an HTML document, `DEFAULT`, `HTML_ONLY`, `SINGLE_FILE`, +/// and `COMPLETE` are valid values. When the source is a non-html, /// only allows to select `DEFAULT`; otherwise, will deny the download /// and return `COREWEBVIEW2_SAVE_AS_REQUESTED_KIND_NOT_SUPPORTED`. /// @@ -219,8 +214,9 @@ async void ProgrammaticSaveAsExecuted(object target, ExecutedRoutedEventArgs e) COREWEBVIEW2_SAVE_AS_REQUESTED_KIND_HTML_ONLY, /// Save the page as mhtml COREWEBVIEW2_SAVE_AS_REQUESTED_KIND_SINGLE_FILE, - /// Save the page as html, plus, download the page related source files in - /// a same name directory + /// Save the page as html, plus, download the page related source files + /// (for example CSS, JavaScript, images, and so on) in a directory with + /// the same filename prefix. COREWEBVIEW2_SAVE_AS_REQUESTED_KIND_COMPLETE, } COREWEBVIEW2_SAVE_AS_REQUESTED_KIND; @@ -230,8 +226,8 @@ async void ProgrammaticSaveAsExecuted(object target, ExecutedRoutedEventArgs e) /// Could not perform Save As because the destination file path is an invalid path. /// /// It is considered as invalid when: - /// the path is empty, a relativate path, the parent directory doesn't - /// exist, or the path is a driectory. + /// the path is empty or a relativate path, the parent directory doesn't + /// exist, or the path is a directory. /// /// Parent directory can be itself, if the path is root directory, or /// root disk. When the root doesn't exist, the path is invalid. @@ -247,16 +243,17 @@ async void ProgrammaticSaveAsExecuted(object target, ExecutedRoutedEventArgs e) /// System limits might happen when select `HTML_ONLY` for an error page, /// select `COMPLETE` and WebView running in an App Container, etc. COREWEBVIEW2_SAVE_AS_REQUESTED_KIND_NOT_SUPPORTED, - /// Did not perform Save As because the client side decided to cancel. + /// Did not perform Save As because the end user cancelled or the + /// CoreWebView2SaveAsRequestedEventArgs.Cancel property was set to TRUE. COREWEBVIEW2_SAVE_AS_REQUESTED_CANCELLED, - /// Save as requested completeed, the downloading would start + /// The RequestSaveAs method call completed successfully and the download has started. COREWEBVIEW2_SAVE_AS_REQUESTED_COMPLETED, } COREWEBVIEW2_SAVE_AS_REQUESTED_RESULTS; [uuid(15e1c6a3-c72a-4df3-91d7-d097fbec3bfd), object, pointer_default(unique)] interface ICoreWebView2_20 : IUnknown { - /// Programmatically trigger a save as action for current content. `SaveAsRequested` - /// event will be raised. + /// Programmatically trigger a save as action for the current top-level document. + /// The `SaveAsRequested` event will be raised. /// /// Opens a system modal dialog by default. If it was already opened, this method /// would not open another one. If the `SuppressDefaultDialog` is TRUE, won't open @@ -266,19 +263,17 @@ interface ICoreWebView2_20 : IUnknown { /// Please see COREWEBVIEW2_SAVE_AS_REQUESTED_RESULTS /// /// \snippet ScenarioSaveAs.cpp ProgrammaticSaveAs - HRESULT SaveContentAs([in] ICoreWebView2RequestSaveAsCompletedHandler* handler); + HRESULT RequestSaveAs([in] ICoreWebView2RequestSaveAsCompletedHandler* handler); /// Add an event handler for the `SaveAsRequested` event. This event is raised /// when save as is triggered, programmatically or manually. /// - /// \snippet ScenarioSaveAs.cpp ToggleEventHandler + /// \snippet ScenarioSaveAs.cpp AddEventHandler HRESULT add_SaveAsRequested( [in] ICoreWebView2SaveAsRequestedEventHandler* eventHanlder, [out] EventRegistrationToken* token); /// Remove an event handler previously added with `add_SaveAsRequested`. - /// - /// \snippet ScenarioSaveAs.cpp ToggleEventHandler HRESULT remove_SaveAsRequested( [in] EventRegistrationToken token); } @@ -297,9 +292,8 @@ interface ICoreWebView2SaveAsRequestedEventArgs : IUnknown { /// Get the Mime type of content to be saved [propget] HRESULT ContentMimeType([out, retval] LPWSTR* value); - /// Indicates if client side cancelled the silent save as, TRUE means cancelled. - /// When the event is invoked, the download won't start. A programmatic call will - /// return COREWEBVIEW2_SAVE_AS_CANCELLED as well. + /// You can set this to TRUE to cancel the Save As. Then the download won't start. + /// A programmatic call will return COREWEBVIEW2_SAVE_AS_CANCELLED as well. /// /// The default value is FALSE. /// @@ -325,14 +319,13 @@ interface ICoreWebView2SaveAsRequestedEventArgs : IUnknown { /// default Save As dialog and performing the Save As operation. HRESULT GetDeferral([out, retval] ICoreWebView2Deferral** deferral); - /// `ResultFilePath` is absolute full path of the location. It includes the - /// file name and extension. If `ResultFilePath` is not valid, e.g. root drive + /// `ResultFilePath` is absolute full path of the location. It includes the file name + /// and extension. If `ResultFilePath` is not valid, for example the root drive does /// not exist, save as will be denied and return COREWEBVIEW2_SAVE_AS_INVALID_PATH. /// - /// When the download complete and success, a target file will be saved at this - /// location. If the select `COREWEBVIEW2_SAVE_AS_REQUESTED_KIND_COMPLETE`, will be - /// an additional directory with resources files. The directory has the same name - /// as filename, at the same location. + /// If the associated download completes successfully, a target file will be saved at + /// this location. If the Kind property is `COREWEBVIEW2_SAVE_AS_REQUESTED_KIND_COMPLETE`, + /// there will be an additional directory with resources files. /// /// The default value is a system suggested path, based on users' local environment. /// From 5ea4dacc945b781db219eb8f28c85f10875b1366 Mon Sep 17 00:00:00 2001 From: Eain Chen Date: Fri, 6 Oct 2023 11:12:04 -0700 Subject: [PATCH 09/13] Rename after spec review --- specs/ProgrammaticSaveAs.md | 208 ++++++++++++++++++------------------ 1 file changed, 105 insertions(+), 103 deletions(-) diff --git a/specs/ProgrammaticSaveAs.md b/specs/ProgrammaticSaveAs.md index 2d8cf60ac..0ff49ba31 100644 --- a/specs/ProgrammaticSaveAs.md +++ b/specs/ProgrammaticSaveAs.md @@ -23,12 +23,12 @@ We'd appreciate your feedback. # Description -We propose the `RequestSaveAs` method WebView2, which allows you to trigger +We propose the `CoreWebView2.ShowSaveAsUI` method, which allows you to trigger the Save As UX programmatically. By using this method, the system default dialog, or your own UI will show and start the Save As operation. -We also propose the `CoreWebView2.SaveAsRequested` event. You can register this event to block -the default dialog and instead create your own Save As UI using the `SaveAsRequestedEventArgs`, +We also propose the `CoreWebView2.SaveAsUIShowing` event. You can register this event to block +the default dialog and instead create your own Save As UI using the `SaveAsUIShowingEventArgs`, to set your preferred save as path, save as kind, and duplicate file replacement rule. In your client app, you can design your own UI to input these parameters. For HTML documents, we support 3 save as kinds: HTML_ONLY, SINGLE_FILE and @@ -46,36 +46,36 @@ bool ScenarioSaveAs::AddEventHandler() if (!m_webView2_20) return false; - // Register a handler for the `SaveAsRequested` event. - m_webView2_20->add_SaveAsRequested( - Callback( + // Register a handler for the `SaveAsUIShowing` event. + m_webView2_20->add_SaveAsUIShowing( + Callback( [this]( ICoreWebView2* sender, - ICoreWebView2SaveAsRequestedEventArgs* args) -> HRESULT + ICoreWebView2SaveAsUIShowingEventArgs* args) -> HRESULT { // Hide the system default save as dialog. CHECK_FAILURE(args->put_SuppressDefaultDialog(TRUE)); - auto showCustomizedDialog = [this, args] + auto showCustomizedDialog = [this, args = wil:: make_com_ptr(args)] { - // Preview the content mime type, optional - wil::unique_cotaskmem_string mimeType; - CHECK_FAILURE(args->get_ContentMimeType(&mimeType)); - // As an end developer, you can design your own dialog UI, or no UI at all. - // You can ask the user to provide information like file name, file extension, and so on. - // Finally, and set them on the event args + // You can ask the user to provide information like file name, file extension, + // and so on. Finally, and set them on the event args // // This is a customized dialog example, the constructor returns after the // dialog interaction is completed by the end user. - SaveAsDialog dialog(m_appWindow->GetMainWindow(), saveKinds); + SaveAsDialog dialog; if (dialog.confirmed) { - // Setting the ResultFilePath, Kind, AllowReplace for the event - // args from this customized dialog inputs is optional. - // The event args has default values based on the document to save. + // Setting the SaveAsFilePath, Kind, AllowReplace for the event + // args from this customized dialog inputs is optional. The event + // args has default values based on the document to save. + // + // Additionally, you can use `get_ContentMimeType` to check the mime + // type of the document that will be saved to help setup your custom + // Save As dialog UI CHECK_FAILURE( - args->put_ResultFilePath((LPCWSTR)dialog.path.c_str())); + args->put_SaveAsFilePath((LPCWSTR)dialog.path.c_str())); CHECK_FAILURE(args->put_Kind(dialog.selectedKind)); CHECK_FAILURE(args->put_AllowReplace(dialog.allowReplace)); } @@ -98,7 +98,7 @@ bool ScenarioSaveAs::AddEventHandler() return S_OK; }) .Get(), - &m_saveAsRequestedToken); + &m_SaveAsUIShowingToken); MessageBox( m_appWindow->GetMainWindow(), L"Event Handler Added", L"Info",MB_OK); @@ -106,21 +106,21 @@ bool ScenarioSaveAs::AddEventHandler() } ``` ### Programmatic Save As -Call RequestSaveAs method to trigger the programmatic save as. +Call ShowSaveAsUI method to trigger the programmatic save as. ```c++ bool ScenarioSaveAs::ProgrammaticSaveAs() { if (!m_webView2_20) return false; - m_webView2_20->RequestSaveAs( - Callback( - [this](HRESULT errorCode, COREWEBVIEW2_SAVE_AS_REQUESTED_RESULTS result) -> HRESULT + m_webView2_20->ShowSaveAsUI( + Callback( + [this](HRESULT errorCode, COREWEBVIEW2_SAVE_AS_UI_RESULT result) -> HRESULT { - // Show RequestSaveAs returned result, optional + // Show ShowSaveAsUI returned result, optional MessageBox( m_appWindow->GetMainWindow(), - (L"Save As " + saveAsResultString[result]).c_str(), L"Info", MB_OK); + (L"Save As " + saveAsUIString[result]).c_str(), L"Info", MB_OK); return S_OK; }) .Get()); @@ -135,11 +135,11 @@ This example hides the default save as dialog and shows a customized dialog. void AddEventHandlerExecuted(object target, ExecutedRoutedEventArgs e) { - webView.CoreWebView2.SaveAsRequested -= WebView_SaveAsRequested; + webView.CoreWebView2.SaveAsUIShowing += WebView_SaveAsUIShowing; MessageBox.Show("Event Handler Added", "Info"); } -void WebView_SaveAsRequested(object sender, CoreWebView2SaveAsRequestedEventArgs args) +void WebView_SaveAsUIShowing(object sender, CoreWebView2SaveAsUIShowingEventArgs args) { // Hide the system default save as dialog. args.SuppressDefaultDialog = true; @@ -156,9 +156,8 @@ void WebView_SaveAsRequested(object sender, CoreWebView2SaveAsRequestedEventArgs { using (deferral) { - // This is a customized dialog example, the constructor returns after the - // dialog interaction is completed by the end user. - var dialog = new SaveAsDialog(_Kinds); + // This is a customized dialog example. + var dialog = new SaveAsDialog(); if (dialog.ShowDialog() == true) { // Setting parameters of event args from this dialog is optional. @@ -167,8 +166,9 @@ void WebView_SaveAsRequested(object sender, CoreWebView2SaveAsRequestedEventArgs // Additionally, you can use `args.ContentMimeType` to check the mime // type of the document that will be saved to help setup your custom // Save As dialog UI - args.ResultFilePath = dialog.Directory.Text + "/" + dialog.Filename.Text; - args.Kind = (CoreWebView2SaveAsRequestedKind)dialog.Kind.SelectedItem; + args.SaveAsFilePath = System.IO.Path.Combine( + dialog.Directory.Text, dialog.Filename.Text); + args.Kind = (CoreWebView2SaveAsKind)dialog.Kind.SelectedItem; args.AllowReplace = (bool)dialog.AllowReplaceOldFile.IsChecked; } else @@ -181,12 +181,12 @@ void WebView_SaveAsRequested(object sender, CoreWebView2SaveAsRequestedEventArgs } ``` ### Programmatic Save As -Call RequestSaveAsAsync method to trigger the programmatic save as. +Call ShowSaveAsUIAsync method to trigger the programmatic save as. ```c# async void ProgrammaticSaveAsExecuted(object target, ExecutedRoutedEventArgs e) { - CoreWebView2SaveAsRequestedResults result = await webView.CoreWebView2.RequestSaveAsAsync(); - // Show RequestSaveAsAsync returned result, optional + CoreWebView2SaveAsUIResults result = await webView.CoreWebView2.ShowSaveAsUIAsync(); + // Show ShowSaveAsUIAsync returned result, optional MessageBox.Show(result.ToString(), "Info"); } ``` @@ -195,100 +195,102 @@ async void ProgrammaticSaveAsExecuted(object target, ExecutedRoutedEventArgs e) ## Win32 C++ ```c++ /// Specifies save as requested kind selection options for -/// `ICoreWebView2SaveAsRequestedEventArgs`. +/// `ICoreWebView2SaveAsUIShowingEventArgs`. /// /// When the source is an HTML document, `DEFAULT`, `HTML_ONLY`, `SINGLE_FILE`, /// and `COMPLETE` are valid values. When the source is a non-html, /// only allows to select `DEFAULT`; otherwise, will deny the download -/// and return `COREWEBVIEW2_SAVE_AS_REQUESTED_KIND_NOT_SUPPORTED`. +/// and return `COREWEBVIEW2_SAVE_AS_UI_KIND_NOT_SUPPORTED`. /// /// The content type/format is a MIME type, indicated by the source -/// server side and identified by the browser. It’s not related to the +/// server side and identified by the browser. It's not related to the /// file’s type or extension. MIME type of `text/html`, /// `application/xhtml+xml` are considered as html page. -[v1_enum] typedef enum COREWEBVIEW2_SAVE_AS_REQUESTED_KIND { +[v1_enum] typedef enum COREWEBVIEW2_SAVE_AS_UI_KIND { /// Default to save for a non-html content. If it is selected for a html /// page, it’s same as HTML_ONLY option. - COREWEBVIEW2_SAVE_AS_REQUESTED_KIND_DEFAULT, + COREWEBVIEW2_SAVE_AS_UI_KIND_DEFAULT, /// Save the page as html - COREWEBVIEW2_SAVE_AS_REQUESTED_KIND_HTML_ONLY, + COREWEBVIEW2_SAVE_AS_UI_KIND_HTML_ONLY, /// Save the page as mhtml - COREWEBVIEW2_SAVE_AS_REQUESTED_KIND_SINGLE_FILE, + COREWEBVIEW2_SAVE_AS_UI_KIND_SINGLE_FILE, /// Save the page as html, plus, download the page related source files /// (for example CSS, JavaScript, images, and so on) in a directory with /// the same filename prefix. - COREWEBVIEW2_SAVE_AS_REQUESTED_KIND_COMPLETE, -} COREWEBVIEW2_SAVE_AS_REQUESTED_KIND; + COREWEBVIEW2_SAVE_AS_UI_KIND_COMPLETE, +} COREWEBVIEW2_SAVE_AS_UI_KIND; /// Status of a programmatic save as call, indicates the result -/// for method `RequestSaveAs` -[v1_enum] typedef enum COREWEBVIEW2_SAVE_AS_REQUESTED_RESULTS { +/// for method `ShowSaveAsUI` +[v1_enum] typedef enum COREWEBVIEW2_SAVE_AS_UI_RESULT { + /// The ShowSaveAsUI method call completed successfully. By defaut the the system + /// save as dialog will open. If `SuppressDefaultDialog` is set to TRUE, will skip + /// the system dialog, and start the download. + COREWEBVIEW2_SAVE_AS_UI_SUCCESS, /// Could not perform Save As because the destination file path is an invalid path. /// /// It is considered as invalid when: - /// the path is empty or a relativate path, the parent directory doesn't + /// the path is empty or a relative path, the parent directory doesn't /// exist, or the path is a directory. /// /// Parent directory can be itself, if the path is root directory, or /// root disk. When the root doesn't exist, the path is invalid. - COREWEBVIEW2_SAVE_AS_REQUESTED_INVALID_PATH, + COREWEBVIEW2_SAVE_AS_UI_INVALID_PATH, /// Could not perform Save As because the destination file path already exists and /// replacing files was not allowed by the `AllowReplace` property. - COREWEBVIEW2_SAVE_AS_REQUESTED_FILE_ALREADY_EXISTS, + COREWEBVIEW2_SAVE_AS_UI_FILE_ALREADY_EXISTS, /// Could not perform Save As when the `Kind` property selection not /// supported because of the content MIME type or system limits /// - /// MIME type limits please see the emun `COREWEBVIEW2_SAVE_AS_REQUESTED_KIND` + /// MIME type limits please see the emun `COREWEBVIEW2_SAVE_AS_UI_KIND` /// /// System limits might happen when select `HTML_ONLY` for an error page, /// select `COMPLETE` and WebView running in an App Container, etc. - COREWEBVIEW2_SAVE_AS_REQUESTED_KIND_NOT_SUPPORTED, + COREWEBVIEW2_SAVE_AS_UI_KIND_NOT_SUPPORTED, /// Did not perform Save As because the end user cancelled or the - /// CoreWebView2SaveAsRequestedEventArgs.Cancel property was set to TRUE. - COREWEBVIEW2_SAVE_AS_REQUESTED_CANCELLED, - /// The RequestSaveAs method call completed successfully and the download has started. - COREWEBVIEW2_SAVE_AS_REQUESTED_COMPLETED, -} COREWEBVIEW2_SAVE_AS_REQUESTED_RESULTS; + /// CoreWebView2SaveAsUIShowingEventArgs.Cancel property was set to TRUE. + COREWEBVIEW2_SAVE_AS_UI_CANCELLED, +} COREWEBVIEW2_SAVE_AS_UI_RESULT; [uuid(15e1c6a3-c72a-4df3-91d7-d097fbec3bfd), object, pointer_default(unique)] interface ICoreWebView2_20 : IUnknown { /// Programmatically trigger a save as action for the current top-level document. - /// The `SaveAsRequested` event will be raised. + /// The `SaveAsUIShowing` event will be raised. /// /// Opens a system modal dialog by default. If it was already opened, this method /// would not open another one. If the `SuppressDefaultDialog` is TRUE, won't open /// the system dialog. /// /// The method can return a detailed info to indicate the call's result. - /// Please see COREWEBVIEW2_SAVE_AS_REQUESTED_RESULTS + /// Please see COREWEBVIEW2_SAVE_AS_UI_RESULT /// /// \snippet ScenarioSaveAs.cpp ProgrammaticSaveAs - HRESULT RequestSaveAs([in] ICoreWebView2RequestSaveAsCompletedHandler* handler); + HRESULT ShowSaveAsUI([in] ICoreWebView2ShowSaveAsUICompletedHandler* handler); - /// Add an event handler for the `SaveAsRequested` event. This event is raised + /// Add an event handler for the `SaveAsUIShowing` event. This event is raised /// when save as is triggered, programmatically or manually. /// /// \snippet ScenarioSaveAs.cpp AddEventHandler - HRESULT add_SaveAsRequested( - [in] ICoreWebView2SaveAsRequestedEventHandler* eventHanlder, + HRESULT add_SaveAsUIShowing( + [in] ICoreWebView2SaveAsUIShowingEventHandler* eventHanlder, [out] EventRegistrationToken* token); - /// Remove an event handler previously added with `add_SaveAsRequested`. - HRESULT remove_SaveAsRequested( + /// Remove an event handler previously added with `add_SaveAsUIShowing`. + HRESULT remove_SaveAsUIShowing( [in] EventRegistrationToken token); } -/// The event handler for the `SaveAsRequested` event. +/// The event handler for the `SaveAsUIShowing` event. [uuid(55b86cd2-adfd-47f1-9cef-cdfb8c414ed3), object, pointer_default(unique)] -interface ICoreWebView2SaveAsRequestedEventHandler : IUnknown { +interface ICoreWebView2SaveAsUIShowingEventHandler : IUnknown { HRESULT Invoke( [in] ICoreWebView2* sender, - [in] ICoreWebView2SaveAsRequestedEventArgs* args); + [in] ICoreWebView2SaveAsUIShowingEventArgs* args); } -/// The event args for `SaveAsRequested` event +/// The event args for `SaveAsUIShowing` event [uuid(80101027-b8c3-49a1-a052-9ea4bd63ba47), object, pointer_default(unique)] -interface ICoreWebView2SaveAsRequestedEventArgs : IUnknown { +interface ICoreWebView2SaveAsUIShowingEventArgs : IUnknown { /// Get the Mime type of content to be saved [propget] HRESULT ContentMimeType([out, retval] LPWSTR* value); @@ -303,37 +305,37 @@ interface ICoreWebView2SaveAsRequestedEventArgs : IUnknown { /// Get the `Cancel` for save as [propget] HRESULT Cancel ([out, retval] BOOL* value); - /// Indicates if the system default dialog will be supressed, FALSE means + /// Indicates if the system default dialog will be Suppressed, FALSE means /// save as default dialog will show; TRUE means a silent save as, will /// skip the system dialog. /// /// The default value is FALSE. /// - /// Set the `SupressDefaultDialog` - [propput] HRESULT SupressDefaultDialog([in] BOOL value); + /// Set the `SuppressDefaultDialog` + [propput] HRESULT SuppressDefaultDialog([in] BOOL value); - /// Get the `SupressDefaultDialog` - [propget] HRESULT SupressDefaultDialog([out, retval] BOOL* value); + /// Get the `SuppressDefaultDialog` + [propget] HRESULT SuppressDefaultDialog([out, retval] BOOL* value); /// Returns an `ICoreWebView2Deferral` object. This will defer showing the /// default Save As dialog and performing the Save As operation. HRESULT GetDeferral([out, retval] ICoreWebView2Deferral** deferral); - /// `ResultFilePath` is absolute full path of the location. It includes the file name - /// and extension. If `ResultFilePath` is not valid, for example the root drive does + /// `SaveAsFilePath` is absolute full path of the location. It includes the file name + /// and extension. If `SaveAsFilePath` is not valid, for example the root drive does /// not exist, save as will be denied and return COREWEBVIEW2_SAVE_AS_INVALID_PATH. /// /// If the associated download completes successfully, a target file will be saved at - /// this location. If the Kind property is `COREWEBVIEW2_SAVE_AS_REQUESTED_KIND_COMPLETE`, + /// this location. If the Kind property is `COREWEBVIEW2_SAVE_AS_UI_KIND_COMPLETE`, /// there will be an additional directory with resources files. /// /// The default value is a system suggested path, based on users' local environment. /// - /// Set the `ResultFilePath` for save as - [propput] HRESULT ResultFilePath ([in] LPCWSTR value); + /// Set the `SaveAsFilePath` for save as + [propput] HRESULT SaveAsFilePath ([in] LPCWSTR value); - /// Get the `ResultFilePath` for save as - [propget] HRESULT ResultFilePath ([out, retval] LPWSTR* value); + /// Get the `SaveAsFilePath` for save as + [propget] HRESULT SaveAsFilePath ([out, retval] LPWSTR* value); /// `AllowReplace` allows you to control what happens when a file already /// exists in the file path to which the Save As operation is saving. @@ -350,22 +352,22 @@ interface ICoreWebView2SaveAsRequestedEventArgs : IUnknown { [propget] HRESULT AllowReplace ([out, retval] BOOL* value); /// How to save documents with different kind. See the enum - /// COREWEBVIEW2_SAVE_AS_REQUESTED_KIND for a description of the different options. + /// COREWEBVIEW2_SAVE_AS_UI_KIND for a description of the different options. /// If the kind isn't allowed for the current document, - /// COREWEBVIEW2_SAVE_AS_REQUESTED_KIND_NOT_SUPPORT will be returned from RequestSaveAs. + /// COREWEBVIEW2_SAVE_AS_UI_KIND_NOT_SUPPORTED will be returned from ShowSaveAsUI. /// - /// The default value is COREWEBVIEW2_SAVE_AS_REQUESTED_KIND_DEFAULT + /// The default value is COREWEBVIEW2_SAVE_AS_UI_KIND_DEFAULT /// /// Set the kind for save as - [propput] HRESULT Kind ([in] COREWEBVIEW2_SAVE_AS_REQUESTED_KIND value); + [propput] HRESULT Kind ([in] COREWEBVIEW2_SAVE_AS_UI_KIND value); /// Get the kind for save as - [propget] HRESULT Kind ([out, retval] COREWEBVIEW2_SAVE_AS_REQUESTED_KIND* value); + [propget] HRESULT Kind ([out, retval] COREWEBVIEW2_SAVE_AS_UI_KIND* value); } -/// Receive the result for `RequestSaveAs` method +/// Receive the result for `ShowSaveAsUI` method [uuid(1a02e9d9-14d3-41c6-9581-8d6e1e6f50fe), object, pointer_default(unique)] -interface ICoreWebView2RequestSaveAsCompletedHandler : IUnknown { +interface ICoreWebView2ShowSaveAsUICompletedHandler : IUnknown { HRESULT Invoke([in] HRESULT errorCode, [in] COREWEBVIEW2_REQUEST_SAVE_RESULTS result); } ``` @@ -375,19 +377,19 @@ interface ICoreWebView2RequestSaveAsCompletedHandler : IUnknown { namespace Microsoft.Web.WebView2.Core { - runtimeclass CoreWebView2SaveAsRequestedEventArgs; + runtimeclass CoreWebView2SaveAsUIShowingEventArgs; runtimeclass CoreWebView2; - enum CoreWebView2SaveAsRequestedResults + enum CoreWebView2SaveAsUIResults { - InvalidPath = 0, - FileAlreadyExists = 1, - KindNotSupported = 2, - Cancelled = 3, - Completed = 4, + Success = 0, + InvalidPath = 1, + FileAlreadyExists = 2, + KindNotSupported = 3, + Cancelled = 4, }; - enum CoreWebView2SaveAsRequestedKind + enum CoreWebView2SaveAsKind { Default = 0, HtmlOnly = 1, @@ -395,14 +397,14 @@ namespace Microsoft.Web.WebView2.Core Complete = 3, }; - runtimeclass CoreWebView2SaveAsRequestedEventArgs + runtimeclass CoreWebView2SaveAsUIShowingEventArgs { String ContentMimeType { get; }; Boolean Cancel { get; set; }; Boolean SuppressDefaultDialog { get; set; }; - String ResultFilePath { get; set; }; + String SaveAsFilePath { get; set; }; Boolean AllowReplace { get; set; }; - CoreWebView2SaveAsRequestedKind Kind { get; set; }; + CoreWebView2SaveAsKind Kind { get; set; }; Windows.Foundation.Deferral GetDeferral(); }; @@ -413,9 +415,9 @@ namespace Microsoft.Web.WebView2.Core [interface_name("Microsoft.Web.WebView2.Core.ICoreWebView2_20")] { event Windows.Foundation.TypedEventHandler - SaveAsRequested; - Windows.Foundation.IAsyncOperation - RequestSaveAsAsync(); + SaveAsUIShowing; + Windows.Foundation.IAsyncOperation + ShowSaveAsUIAsync(); } }; } From cecc290acada08759c6c934dfc9e3b3c747d20c3 Mon Sep 17 00:00:00 2001 From: Eain Chen Date: Fri, 6 Oct 2023 11:52:50 -0700 Subject: [PATCH 10/13] Update context for confusion descriptions --- specs/ProgrammaticSaveAs.md | 53 +++++++++++++++---------------------- 1 file changed, 21 insertions(+), 32 deletions(-) diff --git a/specs/ProgrammaticSaveAs.md b/specs/ProgrammaticSaveAs.md index 0ff49ba31..bfa3748ea 100644 --- a/specs/ProgrammaticSaveAs.md +++ b/specs/ProgrammaticSaveAs.md @@ -150,7 +150,7 @@ void WebView_SaveAsUIShowing(object sender, CoreWebView2SaveAsUIShowingEventArgs CoreWebView2Deferral deferral = args.GetDeferral(); // We avoid potential reentrancy from running a message loop in the event - // handler. Show the customized dialog later when complete the deferral + // handler. Show the customized dialog later then complete the deferral // asynchronously. System.Threading.SynchronizationContext.Current.Post((_) => { @@ -194,31 +194,25 @@ async void ProgrammaticSaveAsExecuted(object target, ExecutedRoutedEventArgs e) # API Details ## Win32 C++ ```c++ -/// Specifies save as requested kind selection options for +/// Specifies save as kind selection options for /// `ICoreWebView2SaveAsUIShowingEventArgs`. /// -/// When the source is an HTML document, `DEFAULT`, `HTML_ONLY`, `SINGLE_FILE`, -/// and `COMPLETE` are valid values. When the source is a non-html, -/// only allows to select `DEFAULT`; otherwise, will deny the download -/// and return `COREWEBVIEW2_SAVE_AS_UI_KIND_NOT_SUPPORTED`. -/// -/// The content type/format is a MIME type, indicated by the source -/// server side and identified by the browser. It's not related to the -/// file’s type or extension. MIME type of `text/html`, -/// `application/xhtml+xml` are considered as html page. -[v1_enum] typedef enum COREWEBVIEW2_SAVE_AS_UI_KIND { +/// For HTML documents, we support 3 save as kinds: HTML_ONLY, SINGLE_FILE and +/// COMPLETE. Non-HTML documents, you must use DEFAULT. MIME type of `text/html`, +/// `application/xhtml+xml` are considered as HTML documents. +[v1_enum] typedef enum COREWEBVIEW2_SAVE_AS_KIND { /// Default to save for a non-html content. If it is selected for a html /// page, it’s same as HTML_ONLY option. - COREWEBVIEW2_SAVE_AS_UI_KIND_DEFAULT, + COREWEBVIEW2_SAVE_AS_KIND_DEFAULT, /// Save the page as html - COREWEBVIEW2_SAVE_AS_UI_KIND_HTML_ONLY, + COREWEBVIEW2_SAVE_AS_KIND_HTML_ONLY, /// Save the page as mhtml - COREWEBVIEW2_SAVE_AS_UI_KIND_SINGLE_FILE, + COREWEBVIEW2_SAVE_AS_KIND_SINGLE_FILE, /// Save the page as html, plus, download the page related source files /// (for example CSS, JavaScript, images, and so on) in a directory with /// the same filename prefix. - COREWEBVIEW2_SAVE_AS_UI_KIND_COMPLETE, -} COREWEBVIEW2_SAVE_AS_UI_KIND; + COREWEBVIEW2_SAVE_AS_KIND_COMPLETE, +} COREWEBVIEW2_SAVE_AS_KIND; /// Status of a programmatic save as call, indicates the result /// for method `ShowSaveAsUI` @@ -229,12 +223,8 @@ async void ProgrammaticSaveAsExecuted(object target, ExecutedRoutedEventArgs e) COREWEBVIEW2_SAVE_AS_UI_SUCCESS, /// Could not perform Save As because the destination file path is an invalid path. /// - /// It is considered as invalid when: - /// the path is empty or a relative path, the parent directory doesn't - /// exist, or the path is a directory. - /// - /// Parent directory can be itself, if the path is root directory, or - /// root disk. When the root doesn't exist, the path is invalid. + /// It is considered as invalid when the path is empty, a relative path, a directory, + /// or the parent path doesn't exist COREWEBVIEW2_SAVE_AS_UI_INVALID_PATH, /// Could not perform Save As because the destination file path already exists and /// replacing files was not allowed by the `AllowReplace` property. @@ -242,7 +232,7 @@ async void ProgrammaticSaveAsExecuted(object target, ExecutedRoutedEventArgs e) /// Could not perform Save As when the `Kind` property selection not /// supported because of the content MIME type or system limits /// - /// MIME type limits please see the emun `COREWEBVIEW2_SAVE_AS_UI_KIND` + /// MIME type limits please see the emun `COREWEBVIEW2_SAVE_AS_KIND` /// /// System limits might happen when select `HTML_ONLY` for an error page, /// select `COMPLETE` and WebView running in an App Container, etc. @@ -257,9 +247,8 @@ interface ICoreWebView2_20 : IUnknown { /// Programmatically trigger a save as action for the current top-level document. /// The `SaveAsUIShowing` event will be raised. /// - /// Opens a system modal dialog by default. If it was already opened, this method - /// would not open another one. If the `SuppressDefaultDialog` is TRUE, won't open - /// the system dialog. + /// Opens a system modal dialog by default. If the `SuppressDefaultDialog` is TRUE, + /// won't open the system dialog. /// /// The method can return a detailed info to indicate the call's result. /// Please see COREWEBVIEW2_SAVE_AS_UI_RESULT @@ -326,7 +315,7 @@ interface ICoreWebView2SaveAsUIShowingEventArgs : IUnknown { /// not exist, save as will be denied and return COREWEBVIEW2_SAVE_AS_INVALID_PATH. /// /// If the associated download completes successfully, a target file will be saved at - /// this location. If the Kind property is `COREWEBVIEW2_SAVE_AS_UI_KIND_COMPLETE`, + /// this location. If the Kind property is `COREWEBVIEW2_SAVE_AS_KIND_COMPLETE`, /// there will be an additional directory with resources files. /// /// The default value is a system suggested path, based on users' local environment. @@ -352,17 +341,17 @@ interface ICoreWebView2SaveAsUIShowingEventArgs : IUnknown { [propget] HRESULT AllowReplace ([out, retval] BOOL* value); /// How to save documents with different kind. See the enum - /// COREWEBVIEW2_SAVE_AS_UI_KIND for a description of the different options. + /// COREWEBVIEW2_SAVE_AS_KIND for a description of the different options. /// If the kind isn't allowed for the current document, /// COREWEBVIEW2_SAVE_AS_UI_KIND_NOT_SUPPORTED will be returned from ShowSaveAsUI. /// - /// The default value is COREWEBVIEW2_SAVE_AS_UI_KIND_DEFAULT + /// The default value is COREWEBVIEW2_SAVE_AS_KIND_DEFAULT /// /// Set the kind for save as - [propput] HRESULT Kind ([in] COREWEBVIEW2_SAVE_AS_UI_KIND value); + [propput] HRESULT Kind ([in] COREWEBVIEW2_SAVE_AS_KIND value); /// Get the kind for save as - [propget] HRESULT Kind ([out, retval] COREWEBVIEW2_SAVE_AS_UI_KIND* value); + [propget] HRESULT Kind ([out, retval] COREWEBVIEW2_SAVE_AS_KIND* value); } /// Receive the result for `ShowSaveAsUI` method From bf2c558de10c92043ffb136ed12a6c04700da869 Mon Sep 17 00:00:00 2001 From: Eain Chen Date: Mon, 27 Nov 2023 16:16:46 -0800 Subject: [PATCH 11/13] Update with spec review comments - More accurate the description for different Kind, including top-level document, subresources, wiki link. - Rewrite the sample code. The methods of add event handler were removed. In new sample code, one method will both register handler and call save as. - Add comment to address get_ContentMimeType can help the Kind selection. - Add comment to address the CoreWebView2SaveAsUIResults will help end developer know the error if API call failed. --- specs/ProgrammaticSaveAs.md | 138 ++++++++++++++++-------------------- 1 file changed, 61 insertions(+), 77 deletions(-) diff --git a/specs/ProgrammaticSaveAs.md b/specs/ProgrammaticSaveAs.md index bfa3748ea..3bd7e0e0b 100644 --- a/specs/ProgrammaticSaveAs.md +++ b/specs/ProgrammaticSaveAs.md @@ -3,10 +3,9 @@ Programmatic Save As API # Background -Chromium browser's context menus have a "Save as" menu item to save the top level -document (html page, image, pdf, or other content) through a save as dialog. We -provide more flexible ways to programmatically perform the Save As operation in -WebView2. +Chromium browser's context menus have a "Save as" menu item to save the document +(html page, image, pdf, or other content) through a save as dialog. We provide +more flexible ways to programmatically perform the Save As operation in WebView2. With the new API you will be able to: - Launch the default save as dialog @@ -15,8 +14,8 @@ With the new API you will be able to: - Build your own save as UI The chromium browser's Save As operation consists of showing the Save As dialog -and then starting a download of the top level document. The Save As method and -event described in this document relate to the Save As dialog and not the download, +and then starting a download of the document. The Save As method and event +described in this document relate to the Save As dialog and not the download, which will go through the existing WebView2 download APIs. We'd appreciate your feedback. @@ -38,10 +37,11 @@ save as operation. # Examples ## Win32 C++ -### Add the Event Handler +### Programmatic Save As This example hides the default save as dialog and shows a customized dialog. +The sample code will register a handler and trigger programmaic save as once. ```c++ -bool ScenarioSaveAs::AddEventHandler() +bool ScenarioSaveAs::ProgrammaticSaveAs() { if (!m_webView2_20) return false; @@ -72,8 +72,7 @@ bool ScenarioSaveAs::AddEventHandler() // args has default values based on the document to save. // // Additionally, you can use `get_ContentMimeType` to check the mime - // type of the document that will be saved to help setup your custom - // Save As dialog UI + // type of the document that will be saved to help the Kind selection. CHECK_FAILURE( args->put_SaveAsFilePath((LPCWSTR)dialog.path.c_str())); CHECK_FAILURE(args->put_Kind(dialog.selectedKind)); @@ -99,25 +98,14 @@ bool ScenarioSaveAs::AddEventHandler() }) .Get(), &m_SaveAsUIShowingToken); - - MessageBox( - m_appWindow->GetMainWindow(), L"Event Handler Added", L"Info",MB_OK); - return true; -} -``` -### Programmatic Save As -Call ShowSaveAsUI method to trigger the programmatic save as. -```c++ - -bool ScenarioSaveAs::ProgrammaticSaveAs() -{ - if (!m_webView2_20) - return false; + + // Call method ShowSaveAsUI to trigger the programmatic save as once. m_webView2_20->ShowSaveAsUI( Callback( [this](HRESULT errorCode, COREWEBVIEW2_SAVE_AS_UI_RESULT result) -> HRESULT { - // Show ShowSaveAsUI returned result, optional + // Show ShowSaveAsUI returned result, optional. See + // COREWEBVIEW2_SAVE_AS_UI_RESULT for more details. MessageBox( m_appWindow->GetMainWindow(), (L"Save As " + saveAsUIString[result]).c_str(), L"Info", MB_OK); @@ -129,64 +117,58 @@ bool ScenarioSaveAs::ProgrammaticSaveAs() ``` ## .Net/ WinRT -### Add the Event Handler +### Programmatic Save As This example hides the default save as dialog and shows a customized dialog. +The sample code will register a handler and trigger programmaic save as once. ```c# -void AddEventHandlerExecuted(object target, ExecutedRoutedEventArgs e) +async void ProgrammaticSaveAsExecuted(object target, ExecutedRoutedEventArgs e) { - webView.CoreWebView2.SaveAsUIShowing += WebView_SaveAsUIShowing; - MessageBox.Show("Event Handler Added", "Info"); -} - -void WebView_SaveAsUIShowing(object sender, CoreWebView2SaveAsUIShowingEventArgs args) - { - // Hide the system default save as dialog. - args.SuppressDefaultDialog = true; - - // Developer can obtain a deferral for the event so that the CoreWebView2 - // doesn't examine the properties we set on the event args until - // after the deferral completes asynchronously. - CoreWebView2Deferral deferral = args.GetDeferral(); - - // We avoid potential reentrancy from running a message loop in the event - // handler. Show the customized dialog later then complete the deferral - // asynchronously. - System.Threading.SynchronizationContext.Current.Post((_) => + // Register a handler for the `SaveAsUIShowing` event. + webView.CoreWebView2.SaveAsUIShowing += (sender, args) => { - using (deferral) + // Hide the system default save as dialog. + args.SuppressDefaultDialog = true; + + // Developer can obtain a deferral for the event so that the CoreWebView2 + // doesn't examine the properties we set on the event args until + // after the deferral completes asynchronously. + CoreWebView2Deferral deferral = args.GetDeferral(); + + // We avoid potential reentrancy from running a message loop in the event + // handler. Show the customized dialog later then complete the deferral + // asynchronously. + System.Threading.SynchronizationContext.Current.Post((_) => { - // This is a customized dialog example. - var dialog = new SaveAsDialog(); - if (dialog.ShowDialog() == true) - { - // Setting parameters of event args from this dialog is optional. - // The event args has default values. - // - // Additionally, you can use `args.ContentMimeType` to check the mime - // type of the document that will be saved to help setup your custom - // Save As dialog UI - args.SaveAsFilePath = System.IO.Path.Combine( - dialog.Directory.Text, dialog.Filename.Text); - args.Kind = (CoreWebView2SaveAsKind)dialog.Kind.SelectedItem; - args.AllowReplace = (bool)dialog.AllowReplaceOldFile.IsChecked; - } - else + using (deferral) { - // Save As cancelled from this customized dialog - args.Cancel = true; + // This is a customized dialog example. + var dialog = new SaveAsDialog(); + if (dialog.ShowDialog() == true) + { + // Setting parameters of event args from this dialog is optional. + // The event args has default values. + // + // Additionally, you can use `args.ContentMimeType` to check the mime + // type of the document that will be saved to help the Kind selection. + args.SaveAsFilePath = System.IO.Path.Combine( + dialog.Directory.Text, dialog.Filename.Text); + args.Kind = (CoreWebView2SaveAsKind)dialog.Kind.SelectedItem; + args.AllowReplace = (bool)dialog.AllowReplaceOldFile.IsChecked; + } + else + { + // Save As cancelled from this customized dialog + args.Cancel = true; + } } - } - }, null); -} -``` -### Programmatic Save As -Call ShowSaveAsUIAsync method to trigger the programmatic save as. -```c# -async void ProgrammaticSaveAsExecuted(object target, ExecutedRoutedEventArgs e) -{ + }, null); + }; + + // Call ShowSaveAsUIAsync method to trigger the programmatic save as once. CoreWebView2SaveAsUIResults result = await webView.CoreWebView2.ShowSaveAsUIAsync(); - // Show ShowSaveAsUIAsync returned result, optional + // Show ShowSaveAsUIAsync returned result, optional. See + // CoreWebView2SaveAsUIResults for more details. MessageBox.Show(result.ToString(), "Info"); } ``` @@ -204,9 +186,11 @@ async void ProgrammaticSaveAsExecuted(object target, ExecutedRoutedEventArgs e) /// Default to save for a non-html content. If it is selected for a html /// page, it’s same as HTML_ONLY option. COREWEBVIEW2_SAVE_AS_KIND_DEFAULT, - /// Save the page as html + /// Save the page as html. It only saves top-level document, excludes + /// subresource. COREWEBVIEW2_SAVE_AS_KIND_HTML_ONLY, - /// Save the page as mhtml + /// Save the page as mhtml. + /// Read more about mhtml at (https://en.wikipedia.org/wiki/MHTML) COREWEBVIEW2_SAVE_AS_KIND_SINGLE_FILE, /// Save the page as html, plus, download the page related source files /// (for example CSS, JavaScript, images, and so on) in a directory with @@ -244,7 +228,7 @@ async void ProgrammaticSaveAsExecuted(object target, ExecutedRoutedEventArgs e) [uuid(15e1c6a3-c72a-4df3-91d7-d097fbec3bfd), object, pointer_default(unique)] interface ICoreWebView2_20 : IUnknown { - /// Programmatically trigger a save as action for the current top-level document. + /// Programmatically trigger a save as action for the currently loaded document. /// The `SaveAsUIShowing` event will be raised. /// /// Opens a system modal dialog by default. If the `SuppressDefaultDialog` is TRUE, From 0a3ac0284208a1c7ab8547c84dfdaa1ac3bbfb46 Mon Sep 17 00:00:00 2001 From: Eain Chen Date: Wed, 29 Nov 2023 13:23:46 -0800 Subject: [PATCH 12/13] minor fix --- specs/ProgrammaticSaveAs.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/specs/ProgrammaticSaveAs.md b/specs/ProgrammaticSaveAs.md index 3bd7e0e0b..ff2965643 100644 --- a/specs/ProgrammaticSaveAs.md +++ b/specs/ProgrammaticSaveAs.md @@ -278,7 +278,7 @@ interface ICoreWebView2SaveAsUIShowingEventArgs : IUnknown { /// Get the `Cancel` for save as [propget] HRESULT Cancel ([out, retval] BOOL* value); - /// Indicates if the system default dialog will be Suppressed, FALSE means + /// Indicates if the system default dialog will be suppressed, FALSE means /// save as default dialog will show; TRUE means a silent save as, will /// skip the system dialog. /// @@ -341,7 +341,7 @@ interface ICoreWebView2SaveAsUIShowingEventArgs : IUnknown { /// Receive the result for `ShowSaveAsUI` method [uuid(1a02e9d9-14d3-41c6-9581-8d6e1e6f50fe), object, pointer_default(unique)] interface ICoreWebView2ShowSaveAsUICompletedHandler : IUnknown { - HRESULT Invoke([in] HRESULT errorCode, [in] COREWEBVIEW2_REQUEST_SAVE_RESULTS result); + HRESULT Invoke([in] HRESULT errorCode, [in] COREWEBVIEW2_SAVE_AS_UI_RESULT result); } ``` From 67b4ab14f1651f64cf96d5088c19879fbad4a7ef Mon Sep 17 00:00:00 2001 From: Eain Chen Date: Fri, 1 Mar 2024 16:31:41 -0800 Subject: [PATCH 13/13] update spec with api experimental promotion --- specs/ProgrammaticSaveAs.md | 93 ++++++++++++++++++------------------- 1 file changed, 46 insertions(+), 47 deletions(-) diff --git a/specs/ProgrammaticSaveAs.md b/specs/ProgrammaticSaveAs.md index ff2965643..891a38a2e 100644 --- a/specs/ProgrammaticSaveAs.md +++ b/specs/ProgrammaticSaveAs.md @@ -43,11 +43,11 @@ The sample code will register a handler and trigger programmaic save as once. ```c++ bool ScenarioSaveAs::ProgrammaticSaveAs() { - if (!m_webView2_20) + if (!m_webView2_24) return false; // Register a handler for the `SaveAsUIShowing` event. - m_webView2_20->add_SaveAsUIShowing( + m_webView2_24->add_SaveAsUIShowing( Callback( [this]( ICoreWebView2* sender, @@ -60,7 +60,7 @@ bool ScenarioSaveAs::ProgrammaticSaveAs() { // As an end developer, you can design your own dialog UI, or no UI at all. // You can ask the user to provide information like file name, file extension, - // and so on. Finally, and set them on the event args + // and so on. Finally, and set them on the event args. // // This is a customized dialog example, the constructor returns after the // dialog interaction is completed by the end user. @@ -80,7 +80,7 @@ bool ScenarioSaveAs::ProgrammaticSaveAs() } else { - // Save As cancelled from this customized dialog + // Save As cancelled from this customized dialog. CHECK_FAILURE(args->put_Cancel(TRUE)); } }; @@ -100,7 +100,7 @@ bool ScenarioSaveAs::ProgrammaticSaveAs() &m_SaveAsUIShowingToken); // Call method ShowSaveAsUI to trigger the programmatic save as once. - m_webView2_20->ShowSaveAsUI( + m_webView2_24->ShowSaveAsUI( Callback( [this](HRESULT errorCode, COREWEBVIEW2_SAVE_AS_UI_RESULT result) -> HRESULT { @@ -158,7 +158,7 @@ async void ProgrammaticSaveAsExecuted(object target, ExecutedRoutedEventArgs e) } else { - // Save As cancelled from this customized dialog + // Save As cancelled from this customized dialog. args.Cancel = true; } } @@ -166,9 +166,9 @@ async void ProgrammaticSaveAsExecuted(object target, ExecutedRoutedEventArgs e) }; // Call ShowSaveAsUIAsync method to trigger the programmatic save as once. - CoreWebView2SaveAsUIResults result = await webView.CoreWebView2.ShowSaveAsUIAsync(); + CoreWebView2SaveAsUIResult result = await webView.CoreWebView2.ShowSaveAsUIAsync(); // Show ShowSaveAsUIAsync returned result, optional. See - // CoreWebView2SaveAsUIResults for more details. + // CoreWebView2SaveAsUIResult for more details. MessageBox.Show(result.ToString(), "Info"); } ``` @@ -184,7 +184,7 @@ async void ProgrammaticSaveAsExecuted(object target, ExecutedRoutedEventArgs e) /// `application/xhtml+xml` are considered as HTML documents. [v1_enum] typedef enum COREWEBVIEW2_SAVE_AS_KIND { /// Default to save for a non-html content. If it is selected for a html - /// page, it’s same as HTML_ONLY option. + /// page, it's same as HTML_ONLY option. COREWEBVIEW2_SAVE_AS_KIND_DEFAULT, /// Save the page as html. It only saves top-level document, excludes /// subresource. @@ -199,35 +199,35 @@ async void ProgrammaticSaveAsExecuted(object target, ExecutedRoutedEventArgs e) } COREWEBVIEW2_SAVE_AS_KIND; /// Status of a programmatic save as call, indicates the result -/// for method `ShowSaveAsUI` +/// for method `ShowSaveAsUI`. [v1_enum] typedef enum COREWEBVIEW2_SAVE_AS_UI_RESULT { /// The ShowSaveAsUI method call completed successfully. By defaut the the system /// save as dialog will open. If `SuppressDefaultDialog` is set to TRUE, will skip /// the system dialog, and start the download. - COREWEBVIEW2_SAVE_AS_UI_SUCCESS, + COREWEBVIEW2_SAVE_AS_UI_RESULT_SUCCESS, /// Could not perform Save As because the destination file path is an invalid path. /// /// It is considered as invalid when the path is empty, a relative path, a directory, - /// or the parent path doesn't exist - COREWEBVIEW2_SAVE_AS_UI_INVALID_PATH, + /// or the parent path doesn't exist. + COREWEBVIEW2_SAVE_AS_UI_RESULT_INVALID_PATH, /// Could not perform Save As because the destination file path already exists and /// replacing files was not allowed by the `AllowReplace` property. - COREWEBVIEW2_SAVE_AS_UI_FILE_ALREADY_EXISTS, + COREWEBVIEW2_SAVE_AS_UI_RESULT_FILE_ALREADY_EXISTS, /// Could not perform Save As when the `Kind` property selection not - /// supported because of the content MIME type or system limits + /// supported because of the content MIME type or system limits. /// - /// MIME type limits please see the emun `COREWEBVIEW2_SAVE_AS_KIND` + /// MIME type limits please see the emun `COREWEBVIEW2_SAVE_AS_KIND`. /// - /// System limits might happen when select `HTML_ONLY` for an error page, - /// select `COMPLETE` and WebView running in an App Container, etc. - COREWEBVIEW2_SAVE_AS_UI_KIND_NOT_SUPPORTED, + /// System limits might happen when select `HTML_ONLY` for an error page at child + /// mode, select `COMPLETE` and WebView running in an App Container, etc. + COREWEBVIEW2_SAVE_AS_UI_RESULT_KIND_NOT_SUPPORTED, /// Did not perform Save As because the end user cancelled or the /// CoreWebView2SaveAsUIShowingEventArgs.Cancel property was set to TRUE. - COREWEBVIEW2_SAVE_AS_UI_CANCELLED, + COREWEBVIEW2_SAVE_AS_UI_RESULT_CANCELLED, } COREWEBVIEW2_SAVE_AS_UI_RESULT; [uuid(15e1c6a3-c72a-4df3-91d7-d097fbec3bfd), object, pointer_default(unique)] -interface ICoreWebView2_20 : IUnknown { +interface ICoreWebView2_24 : IUnknown { /// Programmatically trigger a save as action for the currently loaded document. /// The `SaveAsUIShowing` event will be raised. /// @@ -235,15 +235,13 @@ interface ICoreWebView2_20 : IUnknown { /// won't open the system dialog. /// /// The method can return a detailed info to indicate the call's result. - /// Please see COREWEBVIEW2_SAVE_AS_UI_RESULT + /// Please see COREWEBVIEW2_SAVE_AS_UI_RESULT. /// /// \snippet ScenarioSaveAs.cpp ProgrammaticSaveAs HRESULT ShowSaveAsUI([in] ICoreWebView2ShowSaveAsUICompletedHandler* handler); /// Add an event handler for the `SaveAsUIShowing` event. This event is raised /// when save as is triggered, programmatically or manually. - /// - /// \snippet ScenarioSaveAs.cpp AddEventHandler HRESULT add_SaveAsUIShowing( [in] ICoreWebView2SaveAsUIShowingEventHandler* eventHanlder, [out] EventRegistrationToken* token); @@ -261,21 +259,21 @@ interface ICoreWebView2SaveAsUIShowingEventHandler : IUnknown { [in] ICoreWebView2SaveAsUIShowingEventArgs* args); } -/// The event args for `SaveAsUIShowing` event +/// The event args for `SaveAsUIShowing` event. [uuid(80101027-b8c3-49a1-a052-9ea4bd63ba47), object, pointer_default(unique)] interface ICoreWebView2SaveAsUIShowingEventArgs : IUnknown { - /// Get the Mime type of content to be saved + /// Get the Mime type of content to be saved. [propget] HRESULT ContentMimeType([out, retval] LPWSTR* value); /// You can set this to TRUE to cancel the Save As. Then the download won't start. - /// A programmatic call will return COREWEBVIEW2_SAVE_AS_CANCELLED as well. + /// A programmatic call will return COREWEBVIEW2_SAVE_AS_UI_RESULT_CANCELLED as well. /// /// The default value is FALSE. /// - /// Set the `Cancel` for save as + /// Set the `Cancel` for save as. [propput] HRESULT Cancel ([in] BOOL value); - /// Get the `Cancel` for save as + /// Get the `Cancel` for save as. [propget] HRESULT Cancel ([out, retval] BOOL* value); /// Indicates if the system default dialog will be suppressed, FALSE means @@ -284,10 +282,10 @@ interface ICoreWebView2SaveAsUIShowingEventArgs : IUnknown { /// /// The default value is FALSE. /// - /// Set the `SuppressDefaultDialog` + /// Set the `SuppressDefaultDialog`. [propput] HRESULT SuppressDefaultDialog([in] BOOL value); - /// Get the `SuppressDefaultDialog` + /// Get the `SuppressDefaultDialog`. [propget] HRESULT SuppressDefaultDialog([out, retval] BOOL* value); /// Returns an `ICoreWebView2Deferral` object. This will defer showing the @@ -296,7 +294,7 @@ interface ICoreWebView2SaveAsUIShowingEventArgs : IUnknown { /// `SaveAsFilePath` is absolute full path of the location. It includes the file name /// and extension. If `SaveAsFilePath` is not valid, for example the root drive does - /// not exist, save as will be denied and return COREWEBVIEW2_SAVE_AS_INVALID_PATH. + /// not exist, save as will be denied and return COREWEBVIEW2_SAVE_AS_UI_RESULT_INVALID_PATH. /// /// If the associated download completes successfully, a target file will be saved at /// this location. If the Kind property is `COREWEBVIEW2_SAVE_AS_KIND_COMPLETE`, @@ -304,41 +302,42 @@ interface ICoreWebView2SaveAsUIShowingEventArgs : IUnknown { /// /// The default value is a system suggested path, based on users' local environment. /// - /// Set the `SaveAsFilePath` for save as + /// Set the `SaveAsFilePath` for save as. [propput] HRESULT SaveAsFilePath ([in] LPCWSTR value); - /// Get the `SaveAsFilePath` for save as + /// Get the `SaveAsFilePath` for save as. [propget] HRESULT SaveAsFilePath ([out, retval] LPWSTR* value); /// `AllowReplace` allows you to control what happens when a file already /// exists in the file path to which the Save As operation is saving. /// Setting this TRUE allows existing files to be replaced. - /// Settings this FALSE will not replace existing files and will return - /// COREWEBVIEW2_SAVE_AS_FILE_ALREADY_EXISTS. + /// Setting this FALSE will not replace existing files and will return + /// COREWEBVIEW2_SAVE_AS_UI_RESULT_FILE_ALREADY_EXISTS. /// - /// The default value is FALSE + /// The default value is FALSE. /// - /// Set if allowed to replace the old file if duplicate happens in the save as + /// Set if allowed to replace the old file if duplicate happens in the save as. [propput] HRESULT AllowReplace ([in] BOOL value); - /// Get the duplicates replace rule for save as + /// Get the duplicates replace rule for save as. [propget] HRESULT AllowReplace ([out, retval] BOOL* value); /// How to save documents with different kind. See the enum /// COREWEBVIEW2_SAVE_AS_KIND for a description of the different options. /// If the kind isn't allowed for the current document, - /// COREWEBVIEW2_SAVE_AS_UI_KIND_NOT_SUPPORTED will be returned from ShowSaveAsUI. + /// COREWEBVIEW2_SAVE_AS_UI_RESULT_KIND_NOT_SUPPORTED will be returned from + /// ShowSaveAsUI. /// - /// The default value is COREWEBVIEW2_SAVE_AS_KIND_DEFAULT + /// The default value is COREWEBVIEW2_SAVE_AS_KIND_DEFAULT. /// - /// Set the kind for save as + /// Set the kind for save as. [propput] HRESULT Kind ([in] COREWEBVIEW2_SAVE_AS_KIND value); - /// Get the kind for save as + /// Get the kind for save as. [propget] HRESULT Kind ([out, retval] COREWEBVIEW2_SAVE_AS_KIND* value); } -/// Receive the result for `ShowSaveAsUI` method +/// Receive the result for `ShowSaveAsUI` method. [uuid(1a02e9d9-14d3-41c6-9581-8d6e1e6f50fe), object, pointer_default(unique)] interface ICoreWebView2ShowSaveAsUICompletedHandler : IUnknown { HRESULT Invoke([in] HRESULT errorCode, [in] COREWEBVIEW2_SAVE_AS_UI_RESULT result); @@ -353,7 +352,7 @@ namespace Microsoft.Web.WebView2.Core runtimeclass CoreWebView2SaveAsUIShowingEventArgs; runtimeclass CoreWebView2; - enum CoreWebView2SaveAsUIResults + enum CoreWebView2SaveAsUIResult { Success = 0, InvalidPath = 1, @@ -385,11 +384,11 @@ namespace Microsoft.Web.WebView2.Core { // ... - [interface_name("Microsoft.Web.WebView2.Core.ICoreWebView2_20")] + [interface_name("Microsoft.Web.WebView2.Core.ICoreWebView2_24")] { event Windows.Foundation.TypedEventHandler SaveAsUIShowing; - Windows.Foundation.IAsyncOperation + Windows.Foundation.IAsyncOperation ShowSaveAsUIAsync(); } };