diff --git a/SampleApps/WebView2APISample/AppWindow.cpp b/SampleApps/WebView2APISample/AppWindow.cpp index 6d700b4..78842b2 100644 --- a/SampleApps/WebView2APISample/AppWindow.cpp +++ b/SampleApps/WebView2APISample/AppWindow.cpp @@ -54,6 +54,7 @@ #include "ScenarioSharedWorkerManager.h" #include "ScenarioSaveAs.h" #include "ScenarioScreenCapture.h" +#include "ScenarioSensitivityLabel.h" #include "ScenarioSharedBuffer.h" #include "ScenarioSharedWorkerWRR.h" #include "ScenarioThrottlingControl.h" @@ -630,6 +631,30 @@ bool AppWindow::ExecuteWebViewCommands(WPARAM wParam, LPARAM lParam) NewComponent(this); return true; } + case IDM_SCENARIO_PIRM_SET_ALLOWLIST: + { + auto sensitivityLabelComponent = GetOrCreateComponent(); + sensitivityLabelComponent->SetPageRestrictionManagerAllowlist(); + return true; + } + case IDM_SCENARIO_PIRM_CHECK_AVAILABILITY: + { + auto sensitivityLabelComponent = GetOrCreateComponent(); + sensitivityLabelComponent->CheckPageRestrictionManagerAvailability(); + return true; + } + case IDM_SCENARIO_SENSITIVITY_LABEL_START_TEST: + { + auto sensitivityLabelComponent = GetOrCreateComponent(); + sensitivityLabelComponent->LaunchLabelDemoPage(); + return true; + } + case IDM_SCENARIO_SENSITIVITY_LABEL_TOGGLE_EVENTS: + { + auto sensitivityLabelComponent = GetOrCreateComponent(); + sensitivityLabelComponent->ToggleEventListener(); + return true; + } case IDM_SCENARIO_TESTING_FOCUS: { WCHAR testingFocusPath[] = L"ScenarioTestingFocus.html"; @@ -1740,7 +1765,12 @@ void AppWindow::InitializeWebView() //! [CreateCoreWebView2EnvironmentWithOptions] std::wstring args; - args.append(L"--enable-features=ThirdPartyStoragePartitioning,PartitionedCookies"); + // Page Interaction Restriction Manager requires msPageInteractionManagerWebview2 to be + // enabled from the args, as by default it's disabled in the browser. If you want to + // test these scenarios, this flag should be enabled. + args.append( + L"--enable-features=ThirdPartyStoragePartitioning,PartitionedCookies," + L"msPageInteractionManagerWebview2"); auto options = Microsoft::WRL::Make(); options->put_AdditionalBrowserArguments(args.c_str()); CHECK_FAILURE( diff --git a/SampleApps/WebView2APISample/AppWindow.h b/SampleApps/WebView2APISample/AppWindow.h index 4fab787..0d0b35f 100644 --- a/SampleApps/WebView2APISample/AppWindow.h +++ b/SampleApps/WebView2APISample/AppWindow.h @@ -131,6 +131,8 @@ class AppWindow template ComponentType* GetComponent(); + template ComponentType* GetOrCreateComponent(); + void DeleteComponent(ComponentBase* scenario); // Runs a function by posting it to the event loop. Use this to do things @@ -329,3 +331,14 @@ template ComponentType* AppWindow::GetComponent() } return nullptr; } + +template ComponentType* AppWindow::GetOrCreateComponent() +{ + auto component = GetComponent(); + if (!component) + { + NewComponent(this); + component = GetComponent(); + } + return component; +} diff --git a/SampleApps/WebView2APISample/ScenarioSensitivityLabel.cpp b/SampleApps/WebView2APISample/ScenarioSensitivityLabel.cpp new file mode 100644 index 0000000..7965d45 --- /dev/null +++ b/SampleApps/WebView2APISample/ScenarioSensitivityLabel.cpp @@ -0,0 +1,390 @@ +// Copyright (C) Microsoft Corporation. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "stdafx.h" + +#include "ScenarioSensitivityLabel.h" + +#include "AppWindow.h" +#include "CheckFailure.h" +#include "TextInputDialog.h" +#include "Util.h" + +using namespace Microsoft::WRL; + +static constexpr WCHAR c_samplePath[] = L"ScenarioSensitivityLabelChanged.html"; + +ScenarioSensitivityLabel::ScenarioSensitivityLabel(AppWindow* appWindow) + : m_appWindow(appWindow), m_webView(appWindow->GetWebView()) +{ + m_sampleUri = m_appWindow->GetLocalUri(c_samplePath); +} + +ScenarioSensitivityLabel::~ScenarioSensitivityLabel() +{ + UnregisterSensitivityLabelChange(); +} + +void ScenarioSensitivityLabel::SetPageRestrictionManagerAllowlist() +{ + std::vector allowlist = ShowAllowlistInputDialog(); + + if (allowlist.empty()) + { + m_appWindow->AsyncMessageBox( + L"No URLs entered. Allowlist not modified.", L"Sensitivity Label - Set Allowlist"); + return; + } + + std::vector items; + items.reserve(allowlist.size()); + for (const auto& str : allowlist) + { + items.push_back(str.c_str()); + } + + HRESULT hr = SetAllowlistOnProfile(items); + if (FAILED(hr)) + { + m_appWindow->AsyncMessageBox( + L"Failed to set PageInteractionRestrictionManager allowlist.", + L"Sensitivity Label - Set Allowlist Error"); + return; + } + + std::wstring message = + L"PageInteractionRestrictionManager allowlist set successfully with " + + std::to_wstring(allowlist.size()) + L" URLs:\n\n"; + for (const auto& url : allowlist) + { + message += L"- " + url + L"\n"; + } + + m_appWindow->AsyncMessageBox(message, L"Sensitivity Label - Allowlist Set"); +} + +void ScenarioSensitivityLabel::CheckPageRestrictionManagerAvailability() +{ + // Execute script to check if navigator.pageInteractionRestrictionManager exists + std::wstring script = + L"(() => {" + L" try {" + L" return typeof navigator.pageInteractionRestrictionManager !== 'undefined' && " + L" navigator.pageInteractionRestrictionManager !== null;" + L" } catch (e) {" + L" return false;" + L" }" + L"})();"; + + CHECK_FAILURE(m_webView->ExecuteScript( + script.c_str(), + Callback( + [this](HRESULT errorCode, LPCWSTR resultObjectAsJson) -> HRESULT + { + if (FAILED(errorCode)) + { + m_appWindow->AsyncMessageBox( + L"Failed to execute script to check PageInteractionRestrictionManager " + L"availability.", + L"Sensitivity Label - Error"); + return S_OK; + } + + bool isAvailable = + (resultObjectAsJson && wcscmp(resultObjectAsJson, L"true") == 0); + + std::wstring message; + if (isAvailable) + { + message = L"PageInteractionRestrictionManager is AVAILABLE\n\n" + L"The navigator.pageInteractionRestrictionManager object " + L"exists and can be used for sensitivity labeling."; + } + else + { + wil::unique_cotaskmem_string currentUri; + message = L"PageInteractionRestrictionManager is NOT AVAILABLE\n\n"; + + if (SUCCEEDED(m_webView->get_Source(¤tUri)) && currentUri.get()) + { + message += L"Current URL: " + std::wstring(currentUri.get()) + L"\n\n"; + } + + message += + L"To enable: Go to Scenario -> Sensitivity Label -> Set PIRM Allowlist"; + } + + m_appWindow->AsyncMessageBox( + message, L"Sensitivity Label - Availability Check"); + return S_OK; + }) + .Get())); +} + +void ScenarioSensitivityLabel::LaunchLabelDemoPage() +{ + RegisterForSensitivityLabelChange(); + + CHECK_FAILURE(m_webView->Navigate(m_sampleUri.c_str())); +} + +void ScenarioSensitivityLabel::ToggleEventListener() +{ + if (m_isEventListenerRegistered) + { + UnregisterSensitivityLabelChange(); + + m_appWindow->AsyncMessageBox( + L"Sensitivity label event listener has been turned OFF.\n\n" + L"The application will no longer receive notifications when sensitivity labels " + L"change.", + L"Event Listener - OFF"); + } + else + { + RegisterForSensitivityLabelChange(); + + m_appWindow->AsyncMessageBox( + L"Sensitivity label event listener has been turned ON.\n\n" + L"The application will now receive notifications when sensitivity labels change.", + L"Event Listener - ON"); + } +} + +std::vector ScenarioSensitivityLabel::ShowAllowlistInputDialog() +{ + std::vector result; + + std::wstring prompt = L"Enter URLs for the PageInteractionRestrictionManager Allowlist " + L"separated by semicolons (;):"; + + std::wstring description = + L"Example: https://site1.com/;https://site2.com/*;https://*.site3.com/*"; + + std::wstring defaultValue = + L"https://*example.com*;https://microsoft.com*;https://github.com*"; + + TextInputDialog dialog( + m_appWindow->GetMainWindow(), L"PageInteractionRestrictionManager Allowlist", + prompt.c_str(), description.c_str(), defaultValue); + + if (dialog.confirmed && !dialog.input.empty()) + { + // Use the utility function to split the input string by semicolons and trim whitespace + result = Util::SplitString(dialog.input, L';'); + } + + return result; +} + +void ScenarioSensitivityLabel::RegisterForSensitivityLabelChange() +{ + // If event is already registered return + if (m_sensitivityInfoChangedToken.value != 0) + { + return; + } + + auto webView32 = m_webView.try_query(); + CHECK_FEATURE_RETURN_EMPTY(webView32); + //! [SensitivityInfoChanged] + CHECK_FAILURE(webView32->add_SensitivityInfoChanged( + Callback( + [this](ICoreWebView2* sender, IUnknown* args) -> HRESULT + { + auto webView32 = this->m_webView.try_query(); + Microsoft::WRL::ComPtr + sensitivityInfo; + webView32->get_SensitivityInfo(&sensitivityInfo); + + OnSensitivityChanged(sensitivityInfo.Get()); + + return S_OK; + }) + .Get(), + &m_sensitivityInfoChangedToken)); + //! [SensitivityInfoChanged] + + m_isEventListenerRegistered = true; +} + +void ScenarioSensitivityLabel::UnregisterSensitivityLabelChange() +{ + if (m_sensitivityInfoChangedToken.value != 0) + { + auto webView32 = m_webView.try_query(); + webView32->remove_SensitivityInfoChanged(m_sensitivityInfoChangedToken); + m_sensitivityInfoChangedToken = {}; + m_isEventListenerRegistered = false; + } +} + +HRESULT ScenarioSensitivityLabel::SetAllowlistOnProfile(std::vector allowlist) +{ + //! [SetAllowList] + auto webView2_13 = m_webView.try_query(); + if (!webView2_13) + { + return E_NOINTERFACE; + } + + wil::com_ptr webView2Profile; + HRESULT hr = webView2_13->get_Profile(&webView2Profile); + if (FAILED(hr)) + { + return hr; + } + + auto experimentalProfile14 = + webView2Profile.try_query(); + if (!experimentalProfile14) + { + return E_NOINTERFACE; + } + + // allowlist consists of URL patterns that can access PageInteractionRestrictionManager API + // Examples: "https://example.com/*", "https://*.trusted-domain.com/*", "*://site.com/*" + return experimentalProfile14->SetPageInteractionRestrictionManagerAllowList( + static_cast(allowlist.size()), allowlist.data()); + //! [SetAllowList] +} + +std::wstring ScenarioSensitivityLabel::GetSensitivityLabelStrings( + COREWEBVIEW2_SENSITIVITY_LABELS_STATE& sensitivityLabelsState) +{ + std::wstring labelsString; + switch (sensitivityLabelsState) + { + case COREWEBVIEW2_SENSITIVITY_LABELS_STATE_NOT_APPLICABLE: + labelsString = L"No allowlisted pages loaded"; + break; + case COREWEBVIEW2_SENSITIVITY_LABELS_STATE_PENDING: + labelsString = L"Labels undetermined - allowlisted page loaded but no " + L"labels detected yet"; + break; + case COREWEBVIEW2_SENSITIVITY_LABELS_STATE_AVAILABLE: + labelsString = L"Labels determined"; + break; + } + return labelsString; +} + +void ScenarioSensitivityLabel::OnSensitivityChanged( + ICoreWebView2ExperimentalSensitivityInfo* sensitivityInfo) +{ + if (!sensitivityInfo) + { + return; + } + + //! [SensitivityInfo] + COREWEBVIEW2_SENSITIVITY_LABELS_STATE sensitivityLabelsState; + CHECK_FAILURE(sensitivityInfo->get_SensitivityLabelsState(&sensitivityLabelsState)); + Microsoft::WRL::ComPtr + sensitivityLabelsCollection; + if (sensitivityLabelsState == COREWEBVIEW2_SENSITIVITY_LABELS_STATE_AVAILABLE) + { + CHECK_FAILURE(sensitivityInfo->get_SensitivityLabels(&sensitivityLabelsCollection)); + } + //! [SensitivityInfo] + + std::wstring labelsString = GetSensitivityLabelStrings(sensitivityLabelsState); + + if (sensitivityLabelsCollection) + { + // Get the count of labels + UINT32 labelCount = 0; + CHECK_FAILURE(sensitivityLabelsCollection->get_Count(&labelCount)); + + if (labelCount == 0) + { + labelsString += L"\n\nNo labels present"; + } + else + { + // Extract label information into a vector of pairs + std::vector> labelInfos = + ExtractSensitivityLabelInfo(sensitivityLabelsCollection.Get()); + + // Generate the formatted string from the label information + labelsString += L"\n\nDetected " + std::to_wstring(labelCount) + L" label(s):\n"; + labelsString += GenerateLabelInfoString(labelInfos); + } + } + + // Show the sensitivity labels in a popup dialog + // Using a sync message box here to ensure the user sees the label change + // in the right order if multiple changes occur quickly + MessageBoxW( + m_appWindow->GetMainWindow(), labelsString.c_str(), L"Sensitivity Label Changed", + MB_OK | MB_ICONINFORMATION); +} + +//! [SensitivityLabels] +std::vector> ScenarioSensitivityLabel:: + ExtractSensitivityLabelInfo( + ICoreWebView2ExperimentalSensitivityLabelCollectionView* labelCollection) +{ + std::vector> labelInfos; + + if (!labelCollection) + { + return labelInfos; + } + + UINT32 labelCount = 0; + CHECK_FAILURE(labelCollection->get_Count(&labelCount)); + + for (UINT32 i = 0; i < labelCount; ++i) + { + Microsoft::WRL::ComPtr sensitivityLabel; + CHECK_FAILURE(labelCollection->GetValueAtIndex(i, &sensitivityLabel)); + + COREWEBVIEW2_SENSITIVITY_LABEL_KIND labelKind; + CHECK_FAILURE(sensitivityLabel->get_LabelKind(&labelKind)); + if (labelKind == COREWEBVIEW2_SENSITIVITY_LABEL_KIND_MIP) + { + //! [MipSensitivityLabels] + Microsoft::WRL::ComPtr mipLabel; + if (SUCCEEDED(sensitivityLabel.As(&mipLabel))) + { + wil::unique_cotaskmem_string labelId; + wil::unique_cotaskmem_string organizationId; + CHECK_FAILURE(mipLabel->get_LabelId(&labelId)); + CHECK_FAILURE(mipLabel->get_OrganizationId(&organizationId)); + + // Store the label id and organization id as a pair. Can be used + // to query Purview for the allowed rights on the document + labelInfos.emplace_back( + std::wstring(labelId.get()), std::wstring(organizationId.get())); + } + //! [MipSensitivityLabels] + } + } + return labelInfos; +} +//! [SensitivityLabels] + +std::wstring ScenarioSensitivityLabel::GenerateLabelInfoString( + const std::vector>& labelInfos) +{ + std::wstring result; + + if (labelInfos.empty()) + { + result += L"\n\nNo MIP labels present"; + return result; + } + + for (size_t i = 0; i < labelInfos.size(); ++i) + { + const auto& labelInfo = labelInfos[i]; + + result += L"\n" + std::to_wstring(i + 1) + L". "; + result += + L"MIP Label\n ID: " + labelInfo.first + L"\n Organization: " + labelInfo.second; + } + + return result; +} diff --git a/SampleApps/WebView2APISample/ScenarioSensitivityLabel.h b/SampleApps/WebView2APISample/ScenarioSensitivityLabel.h new file mode 100644 index 0000000..0840080 --- /dev/null +++ b/SampleApps/WebView2APISample/ScenarioSensitivityLabel.h @@ -0,0 +1,52 @@ +// Copyright (C) Microsoft Corporation. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ComponentBase.h" +#include "stdafx.h" + +#include +#include +#include + +class AppWindow; +struct ICoreWebView2ExperimentalSensitivityInfo; + +class ScenarioSensitivityLabel : public ComponentBase +{ +public: + ScenarioSensitivityLabel(AppWindow* appWindow); + ~ScenarioSensitivityLabel() override; + + // PageInteractionRestrictionManager Allowlist functionality + void SetPageRestrictionManagerAllowlist(); + void CheckPageRestrictionManagerAvailability(); + void LaunchLabelDemoPage(); + void ToggleEventListener(); + +private: + AppWindow* m_appWindow = nullptr; + wil::com_ptr m_webView; + EventRegistrationToken m_sensitivityInfoChangedToken = {}; + std::wstring m_sampleUri; + bool m_isEventListenerRegistered = false; + + // UI method for collecting user input + std::vector ShowAllowlistInputDialog(); + + // Sensitivity label event handling + void RegisterForSensitivityLabelChange(); + void UnregisterSensitivityLabelChange(); + std::wstring GetSensitivityLabelStrings( + COREWEBVIEW2_SENSITIVITY_LABELS_STATE& sensitivityLabelsState); + void OnSensitivityChanged(ICoreWebView2ExperimentalSensitivityInfo* sensitivityInfo); + + // Label processing helpers + std::vector> ExtractSensitivityLabelInfo( + ICoreWebView2ExperimentalSensitivityLabelCollectionView* labelCollection); + std::wstring GenerateLabelInfoString( + const std::vector>& labelInfos); + + // Helper methods + HRESULT SetAllowlistOnProfile(std::vector allowlist); +}; diff --git a/SampleApps/WebView2APISample/Util.cpp b/SampleApps/WebView2APISample/Util.cpp index 00ed942..28b346e 100644 --- a/SampleApps/WebView2APISample/Util.cpp +++ b/SampleApps/WebView2APISample/Util.cpp @@ -15,5 +15,62 @@ std::wstring Util::UnixEpochToDateTime(double value) gmtime_s(&timeStruct, &rawTime); _wasctime_s(rawResult, 32, &timeStruct); std::wstring result(rawResult); + return result; +} + +std::wstring Util::TrimWhitespace(const std::wstring& text) +{ + if (text.empty()) + { + return text; + } + + std::wstring trimmedText = text; + trimmedText.erase(0, trimmedText.find_first_not_of(L" \t\r\n")); + trimmedText.erase(trimmedText.find_last_not_of(L" \t\r\n") + 1); + + return trimmedText; +} + +std::vector Util::SplitString(const std::wstring& input, wchar_t delimiter) +{ + std::vector result; + + if (input.empty()) + { + return result; + } + + std::wstring::size_type start = 0; + std::wstring::size_type end = 0; + + end = input.find(delimiter, start); + while (end != std::wstring::npos) + { + std::wstring token = input.substr(start, end - start); + token = TrimWhitespace(token); + + if (!token.empty()) + { + result.push_back(token); + } + + start = end + 1; + end = input.find(delimiter, start); + } + + // Handle the last token after the final delimiter (or the only token if there's no + // delimiter) + if (start < input.length()) + { + std::wstring token = input.substr(start); + token = TrimWhitespace(token); + + if (!token.empty()) + { + result.push_back(token); + } + } + return result; } \ No newline at end of file diff --git a/SampleApps/WebView2APISample/Util.h b/SampleApps/WebView2APISample/Util.h index 02dde31..ba8839b 100644 --- a/SampleApps/WebView2APISample/Util.h +++ b/SampleApps/WebView2APISample/Util.h @@ -5,6 +5,7 @@ #pragma once #include +#include #include "stdafx.h" @@ -12,4 +13,6 @@ class Util { public: static std::wstring UnixEpochToDateTime(double value); + static std::vector SplitString(const std::wstring& input, wchar_t delimiter); + static std::wstring TrimWhitespace(const std::wstring& text); }; diff --git a/SampleApps/WebView2APISample/WebView2APISample.rc b/SampleApps/WebView2APISample/WebView2APISample.rc index 410c3a4..fece213 100644 --- a/SampleApps/WebView2APISample/WebView2APISample.rc +++ b/SampleApps/WebView2APISample/WebView2APISample.rc @@ -337,6 +337,13 @@ BEGIN MENUITEM "Toggle Suppress Default Find Dialog", IDC_SUPPRESS_DEFAULT_FIND_DIALOG MENUITEM "Toggle Should Match Word", IDC_SHOULD_MATCH_WHOLE_WORD END + POPUP "Sensitivity Label" + BEGIN + MENUITEM "Set PIRM Allowlist", IDM_SCENARIO_PIRM_SET_ALLOWLIST + MENUITEM "Check PIRM Availability", IDM_SCENARIO_PIRM_CHECK_AVAILABILITY + MENUITEM "Start Sensitivity Label Test", IDM_SCENARIO_SENSITIVITY_LABEL_START_TEST + MENUITEM "Toggle Event Listener On/Off", IDM_SCENARIO_SENSITIVITY_LABEL_TOGGLE_EVENTS + END END POPUP "&Audio" BEGIN diff --git a/SampleApps/WebView2APISample/WebView2APISample.vcxproj b/SampleApps/WebView2APISample/WebView2APISample.vcxproj index fa86cb8..d13bd02 100644 --- a/SampleApps/WebView2APISample/WebView2APISample.vcxproj +++ b/SampleApps/WebView2APISample/WebView2APISample.vcxproj @@ -250,6 +250,7 @@ + @@ -311,6 +312,7 @@ + @@ -416,6 +418,12 @@ $(OutDir)\assets + + $(OutDir)\assets + + + $(OutDir)\assets + $(OutDir)\assets @@ -517,13 +525,13 @@ - + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + diff --git a/SampleApps/WebView2APISample/assets/ScenarioSensitivityLabelChanged.html b/SampleApps/WebView2APISample/assets/ScenarioSensitivityLabelChanged.html new file mode 100644 index 0000000..f21c5e8 --- /dev/null +++ b/SampleApps/WebView2APISample/assets/ScenarioSensitivityLabelChanged.html @@ -0,0 +1,122 @@ + + + + + Sensitivity Label Changed Scenario + + + +
+

Sensitivity Label Changed Event Scenario

+ +
+

About This Scenario

+

This scenario demonstrates the SensitivityLabelChanged event in WebView2. This event is fired when the sensitivity label of the document changes, typically used in Microsoft Information Protection (MIP) scenarios.

+ +

The WebView2 control has registered an event handler that will show a popup dialog whenever a sensitivity label change is detected.

+
+ + + + + +
+

Add Sensitivity Label

+

Enter the Label ID GUID and Organization ID GUID to apply a sensitivity label to this page:

+ +
+ + +
+ +
+ + +
+ + + +
+ Success: +
+ +
+ Error: +
+
+ +
+

Remove Sensitivity Label

+

Select a label ID from the list below to remove an existing sensitivity label:

+ +
+ + +
+ + +
+ +

Test the Event

+

The sample app scenario can be tested with the following steps:

+
    +
  1. Enter valid Label ID GUID and Organization ID GUID in the "Add Sensitivity Label" form above
  2. +
  3. Click the "Add Sensitivity Label" button to apply a label (you can add multiple labels with different IDs)
  4. +
  5. Select a label ID from the dropdown in the "Remove Sensitivity Label" section
  6. +
  7. Click the "Remove Selected Label" button to remove the chosen label
  8. +
  9. Observe the popup dialog that appears when the SensitivityLabelChanged event fires for both add and remove operations
  10. +
  11. Alternatively, navigate to a document or website that supports Microsoft Information Protection
  12. +
+ +
+

JavaScript API Calls:

+
+// API call for adding a label (executed when you click "Add Sensitivity Label")
+let lm = await navigator.pageInteractionRestrictionManager.requestLabelManager();
+let label_ = await lm.addLabel('MicrosoftSensitivityLabel', {
+    label: 'your-label-guid',
+    organization: 'your-org-guid'
+});
+
+// API call for removing a label (executed when you click "Remove Sensitivity Label")
+// Note: remove() is called on the label object returned by addLabel
+let result = await label_.remove();
+
+// Both operations will trigger the SensitivityLabelChanged event in the WebView2 control
+            
+
+ +

Event Handler Details

+

The C++ event handler registered for this scenario:

+
    +
  • Captures the new sensitivity label value
  • +
  • Shows a popup dialog with the label information
  • +
  • Includes timestamp for when the event occurred
  • +
  • Provides context about Microsoft Information Protection
  • +
+ +

Navigate to a page with Microsoft Information Protection to see the event in action!

+ +
+ + + + diff --git a/SampleApps/WebView2APISample/assets/ScenarioSensitivityLabelChanged.js b/SampleApps/WebView2APISample/assets/ScenarioSensitivityLabelChanged.js new file mode 100644 index 0000000..81af676 --- /dev/null +++ b/SampleApps/WebView2APISample/assets/ScenarioSensitivityLabelChanged.js @@ -0,0 +1,339 @@ +console.log('Sensitivity Label Changed scenario page loaded'); +console.log('Event handler is now active and waiting for sensitivity label changes'); + +// Element ID constants +const ELEMENT_IDS = { + PIRM_WARNING_BOX: 'pirmWarningBox', + PIRM_AVAILABLE_BOX: 'pirmAvailableBox', + CURRENT_URL: 'currentUrl', + ADD_LABEL_BTN: 'addLabelBtn', + REMOVE_LABEL_BTN: 'removeLabelBtn', + REMOVE_LABEL_SELECT: 'removeLabelSelect', + LABEL_ID_INPUT: 'labelIdInput', + ORG_ID_INPUT: 'orgIdInput', + RESULT_BOX: 'resultBox', + ERROR_BOX: 'errorBox', + RESULT_MESSAGE: 'resultMessage', + ERROR_MESSAGE: 'errorMessage' +}; + +let isPirmAvailable = false; +let labelMap = new Map(); // Map of label ID to label object + +// Hide previous result and error messages +function hideResultMessages() { + const resultBox = document.getElementById(ELEMENT_IDS.RESULT_BOX); + const errorBox = document.getElementById(ELEMENT_IDS.ERROR_BOX); + + if (resultBox) resultBox.style.display = 'none'; + if (errorBox) errorBox.style.display = 'none'; +} + +// Show error message to user +function showErrorMessage(message) { + const errorBox = document.getElementById(ELEMENT_IDS.ERROR_BOX); + const errorMessage = document.getElementById(ELEMENT_IDS.ERROR_MESSAGE); + + if (errorMessage) errorMessage.textContent = message; + if (errorBox) errorBox.style.display = 'block'; +} + +// Show success message to user +function showSuccessMessage(message) { + const resultBox = document.getElementById(ELEMENT_IDS.RESULT_BOX); + const resultMessage = document.getElementById(ELEMENT_IDS.RESULT_MESSAGE); + + if (resultMessage) resultMessage.textContent = message; + if (resultBox) resultBox.style.display = 'block'; +} + +// Get input values for sensitivity label +function getLabelInputs() { + const labelId = document.getElementById(ELEMENT_IDS.LABEL_ID_INPUT)?.value.trim() || ''; + const orgId = document.getElementById(ELEMENT_IDS.ORG_ID_INPUT)?.value.trim() || ''; + return { labelId, orgId }; +} + +// Clear input fields +function clearInputFields() { + const labelIdInput = document.getElementById(ELEMENT_IDS.LABEL_ID_INPUT); + const orgIdInput = document.getElementById(ELEMENT_IDS.ORG_ID_INPUT); + + if (labelIdInput) labelIdInput.value = ''; + if (orgIdInput) orgIdInput.value = ''; +} + +// Set button state (enabled/disabled with loading text) +function setButtonState(elementId, disabled, loadingText = null) { + const button = document.getElementById(elementId); + if (!button) return; + + button.disabled = disabled; + if (loadingText && disabled) { + button.textContent = loadingText; + } else if (elementId === ELEMENT_IDS.ADD_LABEL_BTN) { + button.textContent = 'Add Sensitivity Label'; + } else if (elementId === ELEMENT_IDS.REMOVE_LABEL_BTN) { + button.textContent = 'Remove Selected Label'; + } +} + +// Update button visual states based on availability and label count +function updateButtonStates() { + const addLabelBtn = document.getElementById(ELEMENT_IDS.ADD_LABEL_BTN); + const removeLabelBtn = document.getElementById(ELEMENT_IDS.REMOVE_LABEL_BTN); + + if (addLabelBtn) { + addLabelBtn.disabled = !isPirmAvailable; + addLabelBtn.style.opacity = isPirmAvailable ? '1' : '0.5'; + } + if (removeLabelBtn) { + removeLabelBtn.disabled = !isPirmAvailable || labelMap.size === 0; + removeLabelBtn.style.opacity = (!isPirmAvailable || labelMap.size === 0) ? '0.5' : '1'; + } +} + +// Check PIRM availability on page load +function checkPirmAvailability() { + console.log('Checking PageInteractionRestrictionManager availability...'); + + try { + isPirmAvailable = typeof navigator.pageInteractionRestrictionManager !== 'undefined' && + navigator.pageInteractionRestrictionManager !== null; + + console.log('PIRM Available:', isPirmAvailable); + updatePirmBoxVisibility(); + } catch (error) { + console.error('Error checking PIRM availability:', error); + isPirmAvailable = false; + updatePirmBoxVisibility(); + } +} + +// Update visibility of PIRM status boxes +function updatePirmBoxes() { + const pirmWarningBox = document.getElementById(ELEMENT_IDS.PIRM_WARNING_BOX); + const pirmAvailableBox = document.getElementById(ELEMENT_IDS.PIRM_AVAILABLE_BOX); + const currentUrlElement = document.getElementById(ELEMENT_IDS.CURRENT_URL); + + if (isPirmAvailable) { + console.log('Setting pirmAvailableBox to visible'); + if (pirmWarningBox) pirmWarningBox.style.display = 'none'; + if (pirmAvailableBox) pirmAvailableBox.style.display = 'block'; + } else { + console.log('Setting pirmWarningBox to visible'); + if (pirmWarningBox) pirmWarningBox.style.display = 'block'; + if (pirmAvailableBox) pirmAvailableBox.style.display = 'none'; + + // Show current URL when not available + if (currentUrlElement) { + currentUrlElement.textContent = window.location.href; + } + } +} + +// Update PIRM box visibility based on availability +function updatePirmBoxVisibility() { + updatePirmBoxes(); + updateButtonStates(); + updateRemoveLabelDropdown(); +} + +// Clear all options from a select element +function clearSelectOptions(selectElement) { + while (selectElement.firstChild) { + selectElement.removeChild(selectElement.firstChild); + } +} + +// Create and return an option element +function createOption(value, text) { + const option = document.createElement('option'); + option.value = value; + option.textContent = text; + return option; +} + +// Populate dropdown with label options +function populateLabelDropdown(selectElement) { + if (labelMap.size === 0) { + selectElement.appendChild(createOption('', 'No labels available')); + selectElement.disabled = true; + } else { + selectElement.appendChild(createOption('', 'Select a label to remove')); + + for (const [labelId] of labelMap) { + selectElement.appendChild(createOption(labelId, labelId)); + } + + selectElement.disabled = false; + } +} + +// Update the remove label dropdown with current labels +function updateRemoveLabelDropdown() { + const removeLabelSelect = document.getElementById(ELEMENT_IDS.REMOVE_LABEL_SELECT); + if (!removeLabelSelect) return; + + clearSelectOptions(removeLabelSelect); + populateLabelDropdown(removeLabelSelect); +} + +// Validate inputs for adding sensitivity label +function validateAddLabelInputs(labelId, orgId) { + if (!labelId || !orgId) { + return 'Please enter both Label ID GUID and Organization ID GUID.'; + } + + if (labelMap.has(labelId)) { + return `Label ID ${labelId} already exists. Please use a different Label ID or remove the existing one first.`; + } + + return null; // No validation errors +} + +// Add sensitivity label via API +async function addSensitivityLabelAPI(labelId, orgId) { + if (!navigator.pageInteractionRestrictionManager) { + throw new Error('pageInteractionRestrictionManager API not available. This may require adding the page to the allowlist.'); + } + + const lm = await navigator.pageInteractionRestrictionManager.requestLabelManager(); + const label = await lm.addLabel('MicrosoftSensitivityLabel', { + label: labelId, + organization: orgId + }); + + return label; +} + +async function applySensitivityLabel() { + const { labelId, orgId } = getLabelInputs(); + + hideResultMessages(); + + // Validate inputs + const validationError = validateAddLabelInputs(labelId, orgId); + if (validationError) { + showErrorMessage(validationError); + return; + } + + setButtonState(ELEMENT_IDS.ADD_LABEL_BTN, true, 'Adding Label...'); + + try { + console.log('Attempting to apply sensitivity label...'); + console.log('Label ID:', labelId); + console.log('Organization ID:', orgId); + + const label = await addSensitivityLabelAPI(labelId, orgId); + + // Store the label object for later removal + labelMap.set(labelId, label); + + console.log('Sensitivity label applied successfully:', label); + console.log('Current label map:', labelMap); + + showSuccessMessage(`Sensitivity label added successfully! Label ID: ${labelId}, Organization ID: ${orgId}`); + updateRemoveLabelDropdown(); + clearInputFields(); + + } catch (error) { + console.error('Error applying sensitivity label:', error); + showErrorMessage(`Failed to add sensitivity label: ${error.message}`); + } finally { + setButtonState(ELEMENT_IDS.ADD_LABEL_BTN, !isPirmAvailable); + setButtonState(ELEMENT_IDS.REMOVE_LABEL_BTN, + !isPirmAvailable || labelMap.size === 0); + } +} + +// Get selected label ID from dropdown +function getSelectedLabelId() { + const removeLabelSelect = document.getElementById(ELEMENT_IDS.REMOVE_LABEL_SELECT); + return removeLabelSelect?.value || ''; +} + +async function removeSensitivityLabel() { + hideResultMessages(); + + const selectedLabelId = getSelectedLabelId(); + + // Validate inputs + if (!selectedLabelId) { + showErrorMessage('Please select a label ID to remove.'); + return; + } + + const labelObject = labelMap.get(selectedLabelId); + + setButtonState(ELEMENT_IDS.REMOVE_LABEL_BTN, true, 'Removing Label...'); + + try { + console.log('Attempting to remove sensitivity label...'); + console.log('Selected Label ID:', selectedLabelId); + console.log('Label object:', labelObject); + + const result = await labelObject.remove(); + + // Remove the label from the map since it's been removed + labelMap.delete(selectedLabelId); + + console.log('Sensitivity label removed successfully:', result); + console.log('Updated label map:', labelMap); + + showSuccessMessage( + `Sensitivity label removed successfully! Label ID: ${selectedLabelId}` + ); + updateRemoveLabelDropdown(); + } catch (error) { + console.error('Error removing sensitivity label:', error); + showErrorMessage( + `Failed to remove sensitivity label: ${error.message}` + ); + } finally { + setButtonState( + ELEMENT_IDS.REMOVE_LABEL_BTN, + !isPirmAvailable || labelMap.size === 0 + ); + } +} + +// Pre-fill input fields with sample GUIDs for testing +function preFillSampleInputs() { + const labelIdInput = document.getElementById(ELEMENT_IDS.LABEL_ID_INPUT); + const orgIdInput = document.getElementById(ELEMENT_IDS.ORG_ID_INPUT); + + if (labelIdInput && orgIdInput) { + labelIdInput.value = '12345678-1234-1234-1234-123456789abc'; + orgIdInput.value = '87654321-4321-4321-4321-cba987654321'; + console.log('Pre-filled input values'); + } else { + console.error('Could not find input elements'); + } +} + +// Initialize the application +function initializeApp() { + console.log('Initializing sensitivity label app...'); + preFillSampleInputs(); + checkPirmAvailability(); +} + +// Setup event listeners and initialization +function setupApplication() { + console.log('Setting up application...'); + + if (document.readyState === 'loading') { + console.log('DOM still loading, waiting for DOMContentLoaded'); + document.addEventListener('DOMContentLoaded', initializeApp); + } else { + console.log('DOM already ready, initializing immediately'); + initializeApp(); + } +} + +// Start the application +console.log('Sensitivity Label Changed scenario page loaded'); +console.log('Event handler is now active and waiting for sensitivity label changes'); +setupApplication(); diff --git a/SampleApps/WebView2APISample/packages.config b/SampleApps/WebView2APISample/packages.config index a20c170..a203d03 100644 --- a/SampleApps/WebView2APISample/packages.config +++ b/SampleApps/WebView2APISample/packages.config @@ -1,5 +1,5 @@  - + \ No newline at end of file diff --git a/SampleApps/WebView2APISample/resource.h b/SampleApps/WebView2APISample/resource.h index 61a08d9..6c486f8 100644 --- a/SampleApps/WebView2APISample/resource.h +++ b/SampleApps/WebView2APISample/resource.h @@ -205,6 +205,10 @@ #define IDC_SHOULD_MATCH_WHOLE_WORD 2061 #define IDC_SHOULD_HIGHLIGHT_ALL_MATCHES 2062 #define IDC_SUPPRESS_DEFAULT_FIND_DIALOG 2063 +#define IDM_SCENARIO_SENSITIVITY_LABEL_START_TEST 2064 +#define IDM_SCENARIO_SENSITIVITY_LABEL_TOGGLE_EVENTS 2065 +#define IDM_SCENARIO_PIRM_SET_ALLOWLIST 2066 +#define IDM_SCENARIO_PIRM_CHECK_AVAILABILITY 2067 #define IDM_CREATION_MODE_WINDOWED 3000 #define IDM_CREATION_MODE_VISUAL_DCOMP 3001 #define IDM_CREATION_MODE_TARGET_DCOMP 3002 diff --git a/SampleApps/WebView2WindowsFormsBrowser/WebView2WindowsFormsBrowser.csproj b/SampleApps/WebView2WindowsFormsBrowser/WebView2WindowsFormsBrowser.csproj index 8dac909..603811f 100644 --- a/SampleApps/WebView2WindowsFormsBrowser/WebView2WindowsFormsBrowser.csproj +++ b/SampleApps/WebView2WindowsFormsBrowser/WebView2WindowsFormsBrowser.csproj @@ -25,7 +25,7 @@ AnyCPU - + diff --git a/SampleApps/WebView2WpfBrowser/WebView2WpfBrowser.csproj b/SampleApps/WebView2WpfBrowser/WebView2WpfBrowser.csproj index 606e93c..bf0124b 100644 --- a/SampleApps/WebView2WpfBrowser/WebView2WpfBrowser.csproj +++ b/SampleApps/WebView2WpfBrowser/WebView2WpfBrowser.csproj @@ -61,7 +61,7 @@ - +