Diff
Added: branches/safari-607-branch/LayoutTests/http/wpt/webrtc/getUserMedia-processSwapping-expected.txt (0 => 241088)
--- branches/safari-607-branch/LayoutTests/http/wpt/webrtc/getUserMedia-processSwapping-expected.txt (rev 0)
+++ branches/safari-607-branch/LayoutTests/http/wpt/webrtc/getUserMedia-processSwapping-expected.txt 2019-02-06 22:18:00 UTC (rev 241088)
@@ -0,0 +1 @@
+PASS
Added: branches/safari-607-branch/LayoutTests/http/wpt/webrtc/getUserMedia-processSwapping.html (0 => 241088)
--- branches/safari-607-branch/LayoutTests/http/wpt/webrtc/getUserMedia-processSwapping.html (rev 0)
+++ branches/safari-607-branch/LayoutTests/http/wpt/webrtc/getUserMedia-processSwapping.html 2019-02-06 22:18:00 UTC (rev 241088)
@@ -0,0 +1,41 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>getUserMedia on process swapping</title>
+<body>
+<video id="local"></video>
+<script src=""
+<script>
+if (window.testRunner) {
+ testRunner.waitUntilDone();
+ testRunner.dumpAsText();
+}
+
+async function getUserMediaAndNavigate()
+{
+ local.srcObject = await navigator.mediaDevices.getUserMedia({video: true});
+ await local.play();
+ await new Promise(resolve => setTimeout(resolve, 1000));
+ if (window.testRunner && !testRunner.isDoingMediaCapture) {
+ document.body.innerHTML = "FAIL: no capture state";
+ testRunner.notifyDone();
+ return;
+ }
+ window.location = get_host_info().HTTPS_ORIGIN + "/WebKit/webrtc/getUserMedia-processSwapping.html";
+}
+
+function validateMediaCaptureStateAfterNavigation()
+{
+ if (!window.testRunner) {
+ document.body.innerHTML = "FAIL: Need testRunner API";
+ return;
+ }
+ document.body.innerHTML = testRunner.isDoingMediaCapture ? "FAIL" : "PASS";
+ testRunner.notifyDone();
+}
+
+if (window.location.protocol === "http:")
+ getUserMediaAndNavigate();
+else
+ validateMediaCaptureStateAfterNavigation();
+</script>
+</body>
Modified: branches/safari-607-branch/Source/WebCore/WebCore.xcodeproj/project.pbxproj (241087 => 241088)
--- branches/safari-607-branch/Source/WebCore/WebCore.xcodeproj/project.pbxproj 2019-02-06 22:17:54 UTC (rev 241087)
+++ branches/safari-607-branch/Source/WebCore/WebCore.xcodeproj/project.pbxproj 2019-02-06 22:18:00 UTC (rev 241088)
@@ -32077,6 +32077,7 @@
Spanish,
Dutch,
Italian,
+ en,
);
mainGroup = 0867D691FE84028FC02AAC07 /* WebCore */;
productRefGroup = 034768DFFF38A50411DB9C8B /* Products */;
Modified: branches/safari-607-branch/Source/WebKit/ChangeLog (241087 => 241088)
--- branches/safari-607-branch/Source/WebKit/ChangeLog 2019-02-06 22:17:54 UTC (rev 241087)
+++ branches/safari-607-branch/Source/WebKit/ChangeLog 2019-02-06 22:18:00 UTC (rev 241088)
@@ -1,5 +1,32 @@
2019-02-05 Alan Coon <alanc...@apple.com>
+ Apply patch. rdar://problem/47810458
+
+ fix-194122
+
+ 2019-02-04 Youenn Fablet <you...@apple.com>
+
+ Capture state should be managed consistently when doing process swapping
+ https://bugs.webkit.org/show_bug.cgi?id=194122
+ <rdar://problem/47609293>
+
+ When doing PSON, WebPageProxy::resetState is called.
+ It resets the media state, but does not call the client delegates.
+ Instead of directly updating the media state, call the routine used to update it so that client delegates are called.
+
+ Covered by new API test and layout test.
+
+ * UIProcess/API/Cocoa/WKWebView.mm:
+ (-[WKWebView _mediaCaptureState]):
+ * UIProcess/API/Cocoa/WKWebViewPrivate.h:
+ * UIProcess/WebPageProxy.cpp:
+ (WebKit::WebPageProxy::resetState):
+ (WebKit::WebPageProxy::isPlayingMediaDidChange):
+ (WebKit::WebPageProxy::updatePlayingMediaDidChange):
+ * UIProcess/WebPageProxy.h:
+
+2019-02-05 Alan Coon <alanc...@apple.com>
+
Cherry-pick r239752. rdar://problem/47776478
[WebAuthN] Support U2F HID Authenticators on macOS
Modified: branches/safari-607-branch/Source/WebKit/UIProcess/API/Cocoa/WKWebView.mm (241087 => 241088)
--- branches/safari-607-branch/Source/WebKit/UIProcess/API/Cocoa/WKWebView.mm 2019-02-06 22:17:54 UTC (rev 241087)
+++ branches/safari-607-branch/Source/WebKit/UIProcess/API/Cocoa/WKWebView.mm 2019-02-06 22:18:00 UTC (rev 241088)
@@ -5555,6 +5555,23 @@
return _page->mediaCaptureEnabled();
}
+- (_WKMediaCaptureState)_mediaCaptureState
+{
+ auto state = _page->mediaStateFlags();
+
+ _WKMediaCaptureState mediaCaptureState = _WKMediaCaptureStateNone;
+ if (state & WebCore::MediaProducer::HasActiveAudioCaptureDevice)
+ mediaCaptureState |= _WKMediaCaptureStateActiveMicrophone;
+ if (state & WebCore::MediaProducer::HasActiveVideoCaptureDevice)
+ mediaCaptureState |= _WKMediaCaptureStateActiveCamera;
+ if (state & WebCore::MediaProducer::HasMutedAudioCaptureDevice)
+ mediaCaptureState |= _WKMediaCaptureStateMutedMicrophone;
+ if (state & WebCore::MediaProducer::HasMutedVideoCaptureDevice)
+ mediaCaptureState |= _WKMediaCaptureStateMutedCamera;
+
+ return mediaCaptureState;
+}
+
- (void)_setPageMuted:(_WKMediaMutedState)mutedState
{
WebCore::MediaProducer::MutedStateFlags coreState = WebCore::MediaProducer::NoneMuted;
Modified: branches/safari-607-branch/Source/WebKit/UIProcess/API/Cocoa/WKWebViewPrivate.h (241087 => 241088)
--- branches/safari-607-branch/Source/WebKit/UIProcess/API/Cocoa/WKWebViewPrivate.h 2019-02-06 22:17:54 UTC (rev 241087)
+++ branches/safari-607-branch/Source/WebKit/UIProcess/API/Cocoa/WKWebViewPrivate.h 2019-02-06 22:18:00 UTC (rev 241088)
@@ -399,6 +399,7 @@
- (void)_muteMediaCapture WK_API_AVAILABLE(macosx(10.13), ios(11.0));
- (void)_setPageMuted:(_WKMediaMutedState)mutedState WK_API_AVAILABLE(macosx(10.13), ios(11.0));
+@property (nonatomic, readonly) _WKMediaCaptureState _mediaCaptureState WK_API_AVAILABLE(macosx(WK_MAC_TBA), ios(WK_IOS_TBA));
@property (nonatomic, setter=_setMediaCaptureEnabled:) BOOL _mediaCaptureEnabled WK_API_AVAILABLE(macosx(10.13), ios(11.0));
@property (nonatomic, readonly) BOOL _canTogglePictureInPicture;
Modified: branches/safari-607-branch/Source/WebKit/UIProcess/WebPageProxy.cpp (241087 => 241088)
--- branches/safari-607-branch/Source/WebKit/UIProcess/WebPageProxy.cpp 2019-02-06 22:17:54 UTC (rev 241087)
+++ branches/safari-607-branch/Source/WebKit/UIProcess/WebPageProxy.cpp 2019-02-06 22:18:00 UTC (rev 241088)
@@ -6630,7 +6630,7 @@
editCommand->invalidate();
m_activePopupMenu = nullptr;
- m_mediaState = MediaProducer::IsNotPlaying;
+ updatePlayingMediaDidChange(MediaProducer::IsNotPlaying);
#if ENABLE(POINTER_LOCK)
requestPointerUnlock();
@@ -7690,7 +7690,11 @@
ASSERT(focusManager);
focusManager->updatePlaybackAttributesFromMediaState(this, sourceElementID, newState);
#endif
+ updatePlayingMediaDidChange(newState);
+}
+void WebPageProxy::updatePlayingMediaDidChange(MediaProducer::MediaStateFlags newState)
+{
if (newState == m_mediaState)
return;
Modified: branches/safari-607-branch/Source/WebKit/UIProcess/WebPageProxy.h (241087 => 241088)
--- branches/safari-607-branch/Source/WebKit/UIProcess/WebPageProxy.h 2019-02-06 22:17:54 UTC (rev 241087)
+++ branches/safari-607-branch/Source/WebKit/UIProcess/WebPageProxy.h 2019-02-06 22:18:00 UTC (rev 241088)
@@ -1240,6 +1240,7 @@
bool isPlayingAudio() const { return !!(m_mediaState & WebCore::MediaProducer::IsPlayingAudio); }
void isPlayingMediaDidChange(WebCore::MediaProducer::MediaStateFlags, uint64_t);
+ void updatePlayingMediaDidChange(WebCore::MediaProducer::MediaStateFlags);
bool hasActiveAudioStream() const { return m_mediaState & WebCore::MediaProducer::HasActiveAudioCaptureDevice; }
bool hasActiveVideoStream() const { return m_mediaState & WebCore::MediaProducer::HasActiveVideoCaptureDevice; }
WebCore::MediaProducer::MediaStateFlags mediaStateFlags() const { return m_mediaState; }
Modified: branches/safari-607-branch/Source/WebKitLegacy/WebKitLegacy.xcodeproj/project.pbxproj (241087 => 241088)
--- branches/safari-607-branch/Source/WebKitLegacy/WebKitLegacy.xcodeproj/project.pbxproj 2019-02-06 22:17:54 UTC (rev 241087)
+++ branches/safari-607-branch/Source/WebKitLegacy/WebKitLegacy.xcodeproj/project.pbxproj 2019-02-06 22:18:00 UTC (rev 241088)
@@ -3240,6 +3240,7 @@
Spanish,
Dutch,
Italian,
+ en,
);
mainGroup = 0867D691FE84028FC02AAC07 /* WebKit */;
productRefGroup = 034768DFFF38A50411DB9C8B /* Products */;
Modified: branches/safari-607-branch/Tools/ChangeLog (241087 => 241088)
--- branches/safari-607-branch/Tools/ChangeLog 2019-02-06 22:17:54 UTC (rev 241087)
+++ branches/safari-607-branch/Tools/ChangeLog 2019-02-06 22:18:00 UTC (rev 241088)
@@ -1,5 +1,33 @@
2019-02-05 Alan Coon <alanc...@apple.com>
+ Apply patch. rdar://problem/47810458
+
+ fix-194122
+
+ 2019-02-04 Youenn Fablet <you...@apple.com>
+
+ Capture state should be managed consistently when doing process swapping
+ https://bugs.webkit.org/show_bug.cgi?id=194122
+ <rdar://problem/47609293>
+
+ * TestWebKitAPI/Tests/WebKitCocoa/ProcessSwapOnNavigation.mm:
+ (-[GetUserMediaUIDelegate _webView:requestUserMediaAuthorizationForDevices:url:mainFrameURL:decisionHandler:]):
+ (-[GetUserMediaUIDelegate _webView:checkUserMediaPermissionForURL:mainFrameURL:frameIdentifier:decisionHandler:]):
+ (-[GetUserMediaUIDelegate _webView:mediaCaptureStateDidChange:]):
+ * WebKitTestRunner/InjectedBundle/Bindings/TestRunner.idl:
+ * WebKitTestRunner/InjectedBundle/TestRunner.cpp:
+ (WTR::TestRunner::isDoingMediaCapture const):
+ * WebKitTestRunner/InjectedBundle/TestRunner.h:
+ * WebKitTestRunner/TestController.cpp:
+ (WTR::TestController::isDoingMediaCapture const):
+ * WebKitTestRunner/TestController.h:
+ * WebKitTestRunner/TestInvocation.cpp:
+ (WTR::TestInvocation::didReceiveSynchronousMessageFromInjectedBundle):
+ * WebKitTestRunner/cocoa/TestControllerCocoa.mm:
+ (WTR::TestController::isDoingMediaCapture const):
+
+2019-02-05 Alan Coon <alanc...@apple.com>
+
Cherry-pick r239752. rdar://problem/47776478
[WebAuthN] Support U2F HID Authenticators on macOS
Modified: branches/safari-607-branch/Tools/TestWebKitAPI/Tests/WebKitCocoa/ProcessSwapOnNavigation.mm (241087 => 241088)
--- branches/safari-607-branch/Tools/TestWebKitAPI/Tests/WebKitCocoa/ProcessSwapOnNavigation.mm 2019-02-06 22:17:54 UTC (rev 241087)
+++ branches/safari-607-branch/Tools/TestWebKitAPI/Tests/WebKitCocoa/ProcessSwapOnNavigation.mm 2019-02-06 22:18:00 UTC (rev 241088)
@@ -28,6 +28,7 @@
#import "PlatformUtilities.h"
#import "Test.h"
#import "TestNavigationDelegate.h"
+#import "TestWKWebView.h"
#import <WebKit/WKContentRuleListStore.h>
#import <WebKit/WKNavigationDelegatePrivate.h>
#import <WebKit/WKNavigationPrivate.h>
@@ -4719,4 +4720,88 @@
done = false;
}
+static bool isCapturing = false;
+@interface GetUserMediaUIDelegate : NSObject<WKUIDelegate>
+- (void)_webView:(WKWebView *)webView requestUserMediaAuthorizationForDevices:(_WKCaptureDevices)devices url:(NSURL *)url mainFrameURL:(NSURL *)mainFrameURL decisionHandler:(void (^)(BOOL authorized))decisionHandler;
+- (void)_webView:(WKWebView *)webView checkUserMediaPermissionForURL:(NSURL *)url mainFrameURL:(NSURL *)mainFrameURL frameIdentifier:(NSUInteger)frameIdentifier decisionHandler:(void (^)(NSString *salt, BOOL authorized))decisionHandler;
+- (void)_webView:(WKWebView *)webView mediaCaptureStateDidChange:(_WKMediaCaptureState)state;
+@end
+
+@implementation GetUserMediaUIDelegate
+- (void)_webView:(WKWebView *)webView requestUserMediaAuthorizationForDevices:(_WKCaptureDevices)devices url:(NSURL *)url mainFrameURL:(NSURL *)mainFrameURL decisionHandler:(void (^)(BOOL authorized))decisionHandler
+{
+ decisionHandler(YES);
+}
+
+- (void)_webView:(WKWebView *)webView checkUserMediaPermissionForURL:(NSURL *)url mainFrameURL:(NSURL *)mainFrameURL frameIdentifier:(NSUInteger)frameIdentifier decisionHandler:(void (^)(NSString *salt, BOOL authorized))decisionHandler
+{
+ decisionHandler(@"0x987654321", YES);
+}
+
+- (void)_webView:(WKWebView *)webView mediaCaptureStateDidChange:(_WKMediaCaptureState)state
+{
+ isCapturing = state == _WKMediaCaptureStateActiveCamera;
+}
+@end
+
+static const char* getUserMediaBytes = R"PSONRESOURCE(
+<head>
+<body>
+<script>
+navigator.mediaDevices.getUserMedia({video: true});
+</script>
+</body>
+</head>
+)PSONRESOURCE";
+
+TEST(ProcessSwap, GetUserMediaCaptureState)
+{
+ auto processPoolConfiguration = adoptNS([[_WKProcessPoolConfiguration alloc] init]);
+ processPoolConfiguration.get().processSwapsOnNavigation = YES;
+ processPoolConfiguration.get().prewarmsProcessesAutomatically = YES;
+
+ auto processPool = adoptNS([[WKProcessPool alloc] _initWithConfiguration:processPoolConfiguration.get()]);
+
+ auto webViewConfiguration = adoptNS([[WKWebViewConfiguration alloc] init]);
+ auto preferences = [webViewConfiguration.get() preferences];
+ preferences._mediaCaptureRequiresSecureConnection = NO;
+ preferences._mediaDevicesEnabled = YES;
+ preferences._mockCaptureDevicesEnabled = YES;
+
+ [webViewConfiguration setProcessPool:processPool.get()];
+ auto handler = adoptNS([[PSONScheme alloc] init]);
+ [handler addMappingFromURLString:@"pson://www.webkit.org/getUserMedia.html" toData:getUserMediaBytes];
+ [handler addMappingFromURLString:@"pson://www.apple.org/test.html" toData:""];
+ [webViewConfiguration setURLSchemeHandler:handler.get() forURLScheme:@"PSON"];
+
+ auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:webViewConfiguration.get()]);
+
+ auto navigationDelegate = adoptNS([[PSONNavigationDelegate alloc] init]);
+ [webView setNavigationDelegate:navigationDelegate.get()];
+
+ auto uiDelegate = adoptNS([[GetUserMediaUIDelegate alloc] init]);
+ [webView setUIDelegate: uiDelegate.get()];
+
+ auto request = adoptNS([NSURLRequest requestWithURL:[NSURL URLWithString:@"pson://www.webkit.org/getUserMedia.html"]]);
+ [webView loadRequest:request.get()];
+
+ TestWebKitAPI::Util::run(&done);
+ done = false;
+
+ TestWebKitAPI::Util::run(&isCapturing);
+
+ auto pid1 = [webView _webProcessIdentifier];
+
+ request = adoptNS([NSURLRequest requestWithURL:[NSURL URLWithString:@"pson://www.apple.org/test.html"]]);
+ [webView loadRequest:request.get()];
+
+ TestWebKitAPI::Util::run(&done);
+ done = false;
+
+ auto pid2 = [webView _webProcessIdentifier];
+
+ EXPECT_FALSE(isCapturing);
+ EXPECT_FALSE(pid1 == pid2);
+}
+
#endif // WK_API_ENABLED
Modified: branches/safari-607-branch/Tools/WebKitTestRunner/InjectedBundle/Bindings/TestRunner.idl (241087 => 241088)
--- branches/safari-607-branch/Tools/WebKitTestRunner/InjectedBundle/Bindings/TestRunner.idl 2019-02-06 22:17:54 UTC (rev 241087)
+++ branches/safari-607-branch/Tools/WebKitTestRunner/InjectedBundle/Bindings/TestRunner.idl 2019-02-06 22:18:00 UTC (rev 241088)
@@ -216,6 +216,7 @@
void setUserMediaPersistentPermissionForOrigin(boolean permission, DOMString origin, DOMString parentOrigin);
unsigned long userMediaPermissionRequestCountForOrigin(DOMString origin, DOMString parentOrigin);
void resetUserMediaPermissionRequestCountForOrigin(DOMString origin, DOMString parentOrigin);
+ readonly attribute boolean isDoingMediaCapture;
// Audio testing.
[PassContext] void setAudioResult(object data);
Modified: branches/safari-607-branch/Tools/WebKitTestRunner/InjectedBundle/TestRunner.cpp (241087 => 241088)
--- branches/safari-607-branch/Tools/WebKitTestRunner/InjectedBundle/TestRunner.cpp 2019-02-06 22:17:54 UTC (rev 241087)
+++ branches/safari-607-branch/Tools/WebKitTestRunner/InjectedBundle/TestRunner.cpp 2019-02-06 22:18:00 UTC (rev 241088)
@@ -1093,6 +1093,15 @@
InjectedBundle::singleton().resetUserMediaPermission();
}
+bool TestRunner::isDoingMediaCapture() const
+{
+ WKRetainPtr<WKStringRef> messageName(AdoptWK, WKStringCreateWithUTF8CString("IsDoingMediaCapture"));
+ WKTypeRef returnData = nullptr;
+ WKBundlePagePostSynchronousMessageForTesting(InjectedBundle::singleton().page()->page(), messageName.get(), nullptr, &returnData);
+ ASSERT(WKGetTypeID(returnData) == WKBooleanGetTypeID());
+ return WKBooleanGetValue(adoptWK(static_cast<WKBooleanRef>(returnData)).get());
+}
+
void TestRunner::setUserMediaPersistentPermissionForOrigin(bool permission, JSStringRef origin, JSStringRef parentOrigin)
{
WKRetainPtr<WKStringRef> originWK = toWK(origin);
Modified: branches/safari-607-branch/Tools/WebKitTestRunner/InjectedBundle/TestRunner.h (241087 => 241088)
--- branches/safari-607-branch/Tools/WebKitTestRunner/InjectedBundle/TestRunner.h 2019-02-06 22:17:54 UTC (rev 241087)
+++ branches/safari-607-branch/Tools/WebKitTestRunner/InjectedBundle/TestRunner.h 2019-02-06 22:18:00 UTC (rev 241088)
@@ -318,6 +318,7 @@
void setUserMediaPersistentPermissionForOrigin(bool permission, JSStringRef origin, JSStringRef parentOrigin);
unsigned userMediaPermissionRequestCountForOrigin(JSStringRef origin, JSStringRef parentOrigin) const;
void resetUserMediaPermissionRequestCountForOrigin(JSStringRef origin, JSStringRef parentOrigin);
+ bool isDoingMediaCapture() const;
void setPageVisibility(JSStringRef state);
void resetPageVisibility();
Modified: branches/safari-607-branch/Tools/WebKitTestRunner/TestController.cpp (241087 => 241088)
--- branches/safari-607-branch/Tools/WebKitTestRunner/TestController.cpp 2019-02-06 22:17:54 UTC (rev 241087)
+++ branches/safari-607-branch/Tools/WebKitTestRunner/TestController.cpp 2019-02-06 22:18:00 UTC (rev 241088)
@@ -3185,6 +3185,12 @@
{
return false;
}
+
+bool TestController::isDoingMediaCapture() const
+{
+ return false;
+}
+
#endif
void TestController::sendDisplayConfigurationChangedMessageForTesting()
Modified: branches/safari-607-branch/Tools/WebKitTestRunner/TestController.h (241087 => 241088)
--- branches/safari-607-branch/Tools/WebKitTestRunner/TestController.h 2019-02-06 22:17:54 UTC (rev 241087)
+++ branches/safari-607-branch/Tools/WebKitTestRunner/TestController.h 2019-02-06 22:18:00 UTC (rev 241088)
@@ -278,6 +278,8 @@
UIKeyboardInputMode *overriddenKeyboardInputMode() const { return m_overriddenKeyboardInputMode.get(); }
#endif
+ bool isDoingMediaCapture() const;
+
private:
WKRetainPtr<WKPageConfigurationRef> generatePageConfiguration(WKContextConfigurationRef);
WKRetainPtr<WKContextConfigurationRef> generateContextConfiguration() const;
Modified: branches/safari-607-branch/Tools/WebKitTestRunner/TestInvocation.cpp (241087 => 241088)
--- branches/safari-607-branch/Tools/WebKitTestRunner/TestInvocation.cpp 2019-02-06 22:17:54 UTC (rev 241087)
+++ branches/safari-607-branch/Tools/WebKitTestRunner/TestInvocation.cpp 2019-02-06 22:18:00 UTC (rev 241088)
@@ -1034,6 +1034,10 @@
WKRetainPtr<WKUInt64Ref> result(AdoptWK, WKUInt64Create(count));
return result;
}
+ if (WKStringIsEqualToUTF8CString(messageName, "IsDoingMediaCapture")) {
+ WKRetainPtr<WKTypeRef> result(AdoptWK, WKBooleanCreate(TestController::singleton().isDoingMediaCapture()));
+ return result;
+ }
if (WKStringIsEqualToUTF8CString(messageName, "SetStatisticsDebugMode")) {
ASSERT(WKGetTypeID(messageBody) == WKBooleanGetTypeID());
Modified: branches/safari-607-branch/Tools/WebKitTestRunner/cocoa/TestControllerCocoa.mm (241087 => 241088)
--- branches/safari-607-branch/Tools/WebKitTestRunner/cocoa/TestControllerCocoa.mm 2019-02-06 22:17:54 UTC (rev 241087)
+++ branches/safari-607-branch/Tools/WebKitTestRunner/cocoa/TestControllerCocoa.mm 2019-02-06 22:18:00 UTC (rev 241088)
@@ -382,4 +382,13 @@
return false;
}
+bool TestController::isDoingMediaCapture() const
+{
+#if WK_API_ENABLED
+ return m_mainWebView->platformView()._mediaCaptureState != _WKMediaCaptureStateNone;
+#else
+ return false;
+#endif
+}
+
} // namespace WTR