From 7657efd227a796b62b99abf2c917e70f5e33ff2d Mon Sep 17 00:00:00 2001 From: attila Date: Mon, 17 Apr 2023 10:21:48 +0200 Subject: [PATCH] WebBrowserComponent: Windows: Add accessibility integration --- .../juce_AccessibilityHandler.cpp | 72 ++++++ .../accessibility/juce_AccessibilityHandler.h | 20 ++ .../components/juce_Component.cpp | 28 ++- .../components/juce_Component.h | 23 +- .../juce_AccessibilityElement_windows.cpp | 25 ++- .../juce_AccessibilityElement_windows.h | 3 + .../native/juce_Windowing_windows.cpp | 15 ++ .../misc/juce_WebBrowserComponent.h | 7 +- .../juce_WebBrowserComponent_android.cpp | 2 +- .../native/juce_WebBrowserComponent_linux.cpp | 2 +- .../native/juce_WebBrowserComponent_mac.mm | 2 +- .../juce_WebBrowserComponent_windows.cpp | 211 +++++++++++++++++- 12 files changed, 381 insertions(+), 29 deletions(-) diff --git a/modules/juce_gui_basics/accessibility/juce_AccessibilityHandler.cpp b/modules/juce_gui_basics/accessibility/juce_AccessibilityHandler.cpp index a0c5abb9b6..54a17bc3e7 100644 --- a/modules/juce_gui_basics/accessibility/juce_AccessibilityHandler.cpp +++ b/modules/juce_gui_basics/accessibility/juce_AccessibilityHandler.cpp @@ -28,6 +28,63 @@ namespace juce AccessibilityHandler* AccessibilityHandler::currentlyFocusedHandler = nullptr; +class NativeChildHandler +{ +public: + static NativeChildHandler& getInstance() + { + static NativeChildHandler instance; + return instance; + } + + void* getNativeChild (Component& component) const + { + if (auto it = nativeChildForComponent.find (&component); + it != nativeChildForComponent.end()) + { + return it->second; + } + + return nullptr; + } + + Component* getComponent (void* nativeChild) const + { + if (auto it = componentForNativeChild.find (nativeChild); + it != componentForNativeChild.end()) + { + return it->second; + } + + return nullptr; + } + + void setNativeChild (Component& component, void* nativeChild) + { + clearComponent (component); + + if (nativeChild != nullptr) + { + nativeChildForComponent[&component] = nativeChild; + componentForNativeChild[nativeChild] = &component; + } + } + +private: + NativeChildHandler() = default; + + void clearComponent (Component& component) + { + if (auto* nativeChild = getNativeChild (component)) + componentForNativeChild.erase (nativeChild); + + nativeChildForComponent.erase (&component); + } + + std::map componentForNativeChild; + std::map nativeChildForComponent; +}; + AccessibilityHandler::AccessibilityHandler (Component& comp, AccessibilityRole accessibilityRole, AccessibilityActions accessibilityActions, @@ -322,6 +379,21 @@ std::unique_ptr AccessibilityHand #endif } +void* AccessibilityHandler::getNativeChildForComponent (Component& component) +{ + return NativeChildHandler::getInstance().getNativeChild (component); +} + +Component* AccessibilityHandler::getComponentForNativeChild (void* nativeChild) +{ + return NativeChildHandler::getInstance().getComponent (nativeChild); +} + +void AccessibilityHandler::setNativeChildForComponent (Component& component, void* nativeChild) +{ + NativeChildHandler::getInstance().setNativeChild (component, nativeChild); +} + #if ! JUCE_NATIVE_ACCESSIBILITY_INCLUDED void AccessibilityHandler::notifyAccessibilityEvent (AccessibilityEvent) const {} void AccessibilityHandler::postAnnouncement (const String&, AnnouncementPriority) {} diff --git a/modules/juce_gui_basics/accessibility/juce_AccessibilityHandler.h b/modules/juce_gui_basics/accessibility/juce_AccessibilityHandler.h index 5cab7a705e..a0a9d41a44 100644 --- a/modules/juce_gui_basics/accessibility/juce_AccessibilityHandler.h +++ b/modules/juce_gui_basics/accessibility/juce_AccessibilityHandler.h @@ -291,6 +291,26 @@ public: AccessibilityNativeHandle* getNativeImplementation() const; /** @internal */ std::type_index getTypeIndex() const { return typeIndex; } + /** @internal */ + static void clearCurrentlyFocusedHandler() { currentlyFocusedHandler = nullptr; } + + /** @internal + + The following functions provide the means to associate JUCE Components with OS specific + types that provide their own accessibility mechanisms. This way accessibility navigation + can move from a JUCE Component to a native, embedded window and back. + + These functions assume that the concrete types behind the void* are + - Windows: HWND + - MacOS: NSView* + - iOS: UIView* + - Android: GlobalRef that points to an android.view.View + */ + static void* getNativeChildForComponent (Component& component); + /** @internal */ + static void setNativeChildForComponent (Component& component, void* nativeChild); + /** @internal */ + static Component* getComponentForNativeChild (void* nativeChild); private: //============================================================================== diff --git a/modules/juce_gui_basics/components/juce_Component.cpp b/modules/juce_gui_basics/components/juce_Component.cpp index 99ccdfeb52..9f720c794b 100644 --- a/modules/juce_gui_basics/components/juce_Component.cpp +++ b/modules/juce_gui_basics/components/juce_Component.cpp @@ -2182,7 +2182,7 @@ void Component::internalMouseDown (MouseInputSource source, if (! flags.dontFocusOnMouseClickFlag) { - grabKeyboardFocusInternal (focusChangedByMouseClick, true); + grabKeyboardFocusInternal (focusChangedByMouseClick, true, FocusChangeDirection::unknown); if (checker.shouldBailOut()) return; @@ -2435,17 +2435,20 @@ void Component::internalBroughtToFront() //============================================================================== void Component::focusGained (FocusChangeType) {} +void Component::focusGainedWithDirection (FocusChangeType, FocusChangeDirection) {} void Component::focusLost (FocusChangeType) {} void Component::focusOfChildComponentChanged (FocusChangeType) {} void Component::internalKeyboardFocusGain (FocusChangeType cause) { - internalKeyboardFocusGain (cause, WeakReference (this)); + internalKeyboardFocusGain (cause, WeakReference (this), FocusChangeDirection::unknown); } void Component::internalKeyboardFocusGain (FocusChangeType cause, - const WeakReference& safePointer) + const WeakReference& safePointer, + FocusChangeDirection direction) { + focusGainedWithDirection (cause, direction); focusGained (cause); if (safePointer == nullptr) @@ -2585,7 +2588,7 @@ std::unique_ptr Component::createKeyboardFocusTraverser() return parentComponent->createKeyboardFocusTraverser(); } -void Component::takeKeyboardFocus (FocusChangeType cause) +void Component::takeKeyboardFocus (FocusChangeType cause, FocusChangeDirection direction) { if (currentlyFocusedComponent == this) return; @@ -2614,11 +2617,11 @@ void Component::takeKeyboardFocus (FocusChangeType cause) componentLosingFocus->internalKeyboardFocusLoss (cause); if (currentlyFocusedComponent == this) - internalKeyboardFocusGain (cause, safePointer); + internalKeyboardFocusGain (cause, safePointer, direction); } } -void Component::grabKeyboardFocusInternal (FocusChangeType cause, bool canTryParent) +void Component::grabKeyboardFocusInternal (FocusChangeType cause, bool canTryParent, FocusChangeDirection direction) { if (! isShowing()) return; @@ -2626,7 +2629,7 @@ void Component::grabKeyboardFocusInternal (FocusChangeType cause, bool canTryPar if (flags.wantsKeyboardFocusFlag && (isEnabled() || parentComponent == nullptr)) { - takeKeyboardFocus (cause); + takeKeyboardFocus (cause, direction); return; } @@ -2637,7 +2640,7 @@ void Component::grabKeyboardFocusInternal (FocusChangeType cause, bool canTryPar { if (auto* defaultComp = traverser->getDefaultComponent (this)) { - defaultComp->grabKeyboardFocusInternal (cause, false); + defaultComp->grabKeyboardFocusInternal (cause, false, direction); return; } } @@ -2645,7 +2648,7 @@ void Component::grabKeyboardFocusInternal (FocusChangeType cause, bool canTryPar // if no children want it and we're allowed to try our parent comp, // then pass up to parent, which will try our siblings. if (canTryParent && parentComponent != nullptr) - parentComponent->grabKeyboardFocusInternal (cause, true); + parentComponent->grabKeyboardFocusInternal (cause, true, direction); } void Component::grabKeyboardFocus() @@ -2654,7 +2657,7 @@ void Component::grabKeyboardFocus() // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED - grabKeyboardFocusInternal (focusChangedDirectly, true); + grabKeyboardFocusInternal (focusChangedDirectly, true, FocusChangeDirection::unknown); // A component can only be focused when it's actually on the screen! // If this fails then you're probably trying to grab the focus before you've @@ -2730,7 +2733,10 @@ void Component::moveKeyboardFocusToSibling (bool moveToNext) return; } - nextComp->grabKeyboardFocusInternal (focusChangedByTabKey, true); + nextComp->grabKeyboardFocusInternal (focusChangedByTabKey, + true, + moveToNext ? FocusChangeDirection::forward + : FocusChangeDirection::backward); return; } } diff --git a/modules/juce_gui_basics/components/juce_Component.h b/modules/juce_gui_basics/components/juce_Component.h index d321a7de49..5236cfcf0b 100644 --- a/modules/juce_gui_basics/components/juce_Component.h +++ b/modules/juce_gui_basics/components/juce_Component.h @@ -1891,11 +1891,28 @@ public: focusChangedDirectly /**< Means that the focus was changed by a call to grabKeyboardFocus(). */ }; + /** Enumeration used by the focusGainedWithDirection() method. */ + enum class FocusChangeDirection + { + unknown, + forward, + backward + }; + /** Called to indicate that this component has just acquired the keyboard focus. @see focusLost, setWantsKeyboardFocus, getCurrentlyFocusedComponent, hasKeyboardFocus */ virtual void focusGained (FocusChangeType cause); + /** Called to indicate that this component has just acquired the keyboard focus. + + This function is called every time focusGained() is called but it has an additional change + direction parameter. + + @see focusLost, setWantsKeyboardFocus, getCurrentlyFocusedComponent, hasKeyboardFocus + */ + virtual void focusGainedWithDirection (FocusChangeType cause, FocusChangeDirection direction); + /** Called to indicate that this component has just lost the keyboard focus. @see focusGained, setWantsKeyboardFocus, getCurrentlyFocusedComponent, hasKeyboardFocus */ @@ -2614,7 +2631,7 @@ private: void internalMouseWheel (MouseInputSource, Point, Time, const MouseWheelDetails&); void internalMagnifyGesture (MouseInputSource, Point, Time, float); void internalBroughtToFront(); - void internalKeyboardFocusGain (FocusChangeType, const WeakReference&); + void internalKeyboardFocusGain (FocusChangeType, const WeakReference&, FocusChangeDirection); void internalKeyboardFocusGain (FocusChangeType); void internalKeyboardFocusLoss (FocusChangeType); void internalChildKeyboardFocusChange (FocusChangeType, const WeakReference&); @@ -2632,8 +2649,8 @@ private: void sendMovedResizedMessagesIfPending(); void repaintParent(); void sendFakeMouseMove() const; - void takeKeyboardFocus (FocusChangeType); - void grabKeyboardFocusInternal (FocusChangeType, bool canTryParent); + void takeKeyboardFocus (FocusChangeType, FocusChangeDirection); + void grabKeyboardFocusInternal (FocusChangeType, bool canTryParent, FocusChangeDirection); void giveAwayKeyboardFocusInternal (bool sendFocusLossEvent); void sendEnablementChangeMessage(); void sendVisibilityChangeMessage(); diff --git a/modules/juce_gui_basics/native/accessibility/juce_AccessibilityElement_windows.cpp b/modules/juce_gui_basics/native/accessibility/juce_AccessibilityElement_windows.cpp index 014afa4c54..4db52c3ddf 100644 --- a/modules/juce_gui_basics/native/accessibility/juce_AccessibilityElement_windows.cpp +++ b/modules/juce_gui_basics/native/accessibility/juce_AccessibilityElement_windows.cpp @@ -166,10 +166,15 @@ JUCE_COMRESULT AccessibilityNativeHandle::get_HostRawElementProvider (IRawElemen { return withCheckedComArgs (pRetVal, *this, [&] { - if (isFragmentRoot()) - if (auto* wrapper = WindowsUIAWrapper::getInstanceWithoutCreating()) + if (auto* wrapper = WindowsUIAWrapper::getInstanceWithoutCreating()) + { + if (isFragmentRoot()) return wrapper->hostProviderFromHwnd ((HWND) accessibilityHandler.getComponent().getWindowHandle(), pRetVal); + if (auto* embeddedWindow = static_cast (AccessibilityHandler::getNativeChildForComponent (accessibilityHandler.getComponent()))) + return wrapper->hostProviderFromHwnd (embeddedWindow, pRetVal); + } + return S_OK; }); } @@ -180,6 +185,10 @@ JUCE_COMRESULT AccessibilityNativeHandle::get_ProviderOptions (ProviderOptions* return E_INVALIDARG; *options = (ProviderOptions) (ProviderOptions_ServerSideProvider | ProviderOptions_UseComThreading); + + if (AccessibilityHandler::getNativeChildForComponent (accessibilityHandler.getComponent()) != nullptr) + *options |= ProviderOptions_OverrideProvider; + return S_OK; } @@ -618,6 +627,18 @@ JUCE_COMRESULT AccessibilityNativeHandle::GetFocus (ComTypes::IRawElementProvide }); } +JUCE_COMRESULT AccessibilityNativeHandle::GetOverrideProviderForHwnd (HWND hwnd, IRawElementProviderSimple** pRetVal) +{ + return withCheckedComArgs (pRetVal, *this, [&] + { + if (auto* component = AccessibilityHandler::getComponentForNativeChild (hwnd)) + if (auto* handler = component->getAccessibilityHandler()) + handler->getNativeImplementation()->QueryInterface (IID_PPV_ARGS (pRetVal)); + + return S_OK; + }); +} + //============================================================================== String AccessibilityNativeHandle::getElementName() const { diff --git a/modules/juce_gui_basics/native/accessibility/juce_AccessibilityElement_windows.h b/modules/juce_gui_basics/native/accessibility/juce_AccessibilityElement_windows.h index 0109d9d16b..168da31084 100644 --- a/modules/juce_gui_basics/native/accessibility/juce_AccessibilityElement_windows.h +++ b/modules/juce_gui_basics/native/accessibility/juce_AccessibilityElement_windows.h @@ -27,6 +27,7 @@ namespace juce { class AccessibilityNativeHandle : public ComBaseClassHelper { @@ -58,6 +59,8 @@ public: JUCE_COMRESULT ElementProviderFromPoint (double x, double y, ComTypes::IRawElementProviderFragment** pRetVal) override; JUCE_COMRESULT GetFocus (ComTypes::IRawElementProviderFragment** pRetVal) override; + JUCE_COMRESULT GetOverrideProviderForHwnd (HWND hwnd, IRawElementProviderSimple** pRetVal) override; + private: //============================================================================== String getElementName() const; diff --git a/modules/juce_gui_basics/native/juce_Windowing_windows.cpp b/modules/juce_gui_basics/native/juce_Windowing_windows.cpp index 2f37083c3a..9740c9f9ee 100644 --- a/modules/juce_gui_basics/native/juce_Windowing_windows.cpp +++ b/modules/juce_gui_basics/native/juce_Windowing_windows.cpp @@ -4147,6 +4147,21 @@ private: //============================================================================== case WM_SETFOCUS: + /* When the HWND receives Focus from the system it sends a + UIA_AutomationFocusChangedEventId notification redirecting the focus to the HWND + itself. This is a built-in behaviour of the HWND. + + This means that whichever JUCE managed provider was active before the entire + window lost and then regained the focus, loses its focused state, and the + window's root element will become focused under which all JUCE managed providers + can be found. + + This needs to be reflected on currentlyFocusedHandler so that the JUCE + accessibility mechanisms can detect that the root window got the focus, and send + another FocusChanged event to the system to redirect focus to a JUCE managed + provider if necessary. + */ + AccessibilityHandler::clearCurrentlyFocusedHandler(); updateKeyModifiers(); handleFocusGain(); break; diff --git a/modules/juce_gui_extra/misc/juce_WebBrowserComponent.h b/modules/juce_gui_extra/misc/juce_WebBrowserComponent.h index 758d2587ea..8720f6825d 100644 --- a/modules/juce_gui_extra/misc/juce_WebBrowserComponent.h +++ b/modules/juce_gui_extra/misc/juce_WebBrowserComponent.h @@ -264,12 +264,17 @@ public: /** @internal */ void visibilityChanged() override; /** @internal */ - void focusGained (FocusChangeType) override; + void focusGainedWithDirection (FocusChangeType, FocusChangeDirection) override; /** @internal */ class Pimpl; private: + std::unique_ptr createAccessibilityHandler() override + { + return std::make_unique (*this, AccessibilityRole::group); + } + //============================================================================== std::unique_ptr browser; bool blankPageShown = false, unloadPageWhenHidden; diff --git a/modules/juce_gui_extra/native/juce_WebBrowserComponent_android.cpp b/modules/juce_gui_extra/native/juce_WebBrowserComponent_android.cpp index 29513cdba7..93f70836d3 100644 --- a/modules/juce_gui_extra/native/juce_WebBrowserComponent_android.cpp +++ b/modules/juce_gui_extra/native/juce_WebBrowserComponent_android.cpp @@ -676,7 +676,7 @@ void WebBrowserComponent::visibilityChanged() checkWindowAssociation(); } -void WebBrowserComponent::focusGained (FocusChangeType) +void WebBrowserComponent::focusGainedWithDirection (FocusChangeType, FocusChangeDirection) { } diff --git a/modules/juce_gui_extra/native/juce_WebBrowserComponent_linux.cpp b/modules/juce_gui_extra/native/juce_WebBrowserComponent_linux.cpp index 273be7b5cf..b5e819b81c 100644 --- a/modules/juce_gui_extra/native/juce_WebBrowserComponent_linux.cpp +++ b/modules/juce_gui_extra/native/juce_WebBrowserComponent_linux.cpp @@ -1036,7 +1036,7 @@ void WebBrowserComponent::visibilityChanged() checkWindowAssociation(); } -void WebBrowserComponent::focusGained (FocusChangeType) +void WebBrowserComponent::focusGainedWithDirection (FocusChangeType, FocusChangeDirection) { } diff --git a/modules/juce_gui_extra/native/juce_WebBrowserComponent_mac.mm b/modules/juce_gui_extra/native/juce_WebBrowserComponent_mac.mm index 1d0fef01d9..fddcb6e90b 100644 --- a/modules/juce_gui_extra/native/juce_WebBrowserComponent_mac.mm +++ b/modules/juce_gui_extra/native/juce_WebBrowserComponent_mac.mm @@ -724,7 +724,7 @@ void WebBrowserComponent::visibilityChanged() checkWindowAssociation(); } -void WebBrowserComponent::focusGained (FocusChangeType) +void WebBrowserComponent::focusGainedWithDirection (FocusChangeType, FocusChangeDirection) { } diff --git a/modules/juce_gui_extra/native/juce_WebBrowserComponent_windows.cpp b/modules/juce_gui_extra/native/juce_WebBrowserComponent_windows.cpp index f4b0a32a47..61bec5a71d 100644 --- a/modules/juce_gui_extra/native/juce_WebBrowserComponent_windows.cpp +++ b/modules/juce_gui_extra/native/juce_WebBrowserComponent_windows.cpp @@ -351,11 +351,56 @@ private: #if JUCE_USE_WIN_WEBVIEW2 +#include + using namespace Microsoft::WRL; +static std::vector getDirectChildWindows (HWND hwnd) +{ + std::vector result; + + const auto getNextChildWindow = [hwnd, &result] + { + return FindWindowExA (hwnd, result.empty() ? nullptr : result.back(), nullptr, nullptr); + }; + + for (auto* next = getNextChildWindow(); next != nullptr; next = getNextChildWindow()) + result.push_back (next); + + return result; +} + +static void forEachChildWindowRecursive (HWND hwnd, std::function callback) +{ + // EnumChildWindows itself provides the recursion + EnumChildWindows (hwnd, + [] (HWND hwnd, LPARAM lParam) + { + auto* callbackPtr = reinterpret_cast*> (lParam); + return (*callbackPtr) (hwnd) ? TRUE : FALSE; + }, + reinterpret_cast (&callback)); +} + +static bool anyChildWindow (HWND hwnd, std::function predicate) +{ + auto result = false; + + forEachChildWindowRecursive (hwnd, + [&predicate, &result] (auto* child) + { + result = predicate (child); + const auto keepGoing = ! result; + return keepGoing; + }); + + return result; +} + class WebView2 : public InternalWebViewType, public Component, - public ComponentMovementWatcher + public ComponentMovementWatcher, + private AsyncUpdater { public: WebView2 (WebBrowserComponent& o, const WebBrowserComponent::Options& prefs) @@ -372,6 +417,26 @@ public: owner.addAndMakeVisible (this); } + void focusGainedWithDirection (FocusChangeType, FocusChangeDirection direction) override + { + if (inMoveFocusRequested) + return; + + const auto moveFocusReason = [&] + { + if (direction == FocusChangeDirection::backward) + return COREWEBVIEW2_MOVE_FOCUS_REASON_PREVIOUS; + + if (direction == FocusChangeDirection::forward) + return COREWEBVIEW2_MOVE_FOCUS_REASON_NEXT; + + return COREWEBVIEW2_MOVE_FOCUS_REASON_PROGRAMMATIC; + }(); + + if (webViewController != nullptr) + webViewController->MoveFocus (moveFocusReason); + } + ~WebView2() override { removeEventHandlers(); @@ -389,7 +454,9 @@ public: bool hasBrowserBeenCreated() override { - return webView != nullptr || isCreating; + return webView != nullptr + || webView2ConstructionHelper.webView2BeingCreated == this + || webView2ConstructionHelper.viewsWaitingForCreation.contains (this); } void goToURL (const String& url, const StringArray* headers, const MemoryBlock* postData) override @@ -462,6 +529,11 @@ public: owner.visibilityChanged(); } + std::unique_ptr createAccessibilityHandler() override + { + return std::make_unique (*this, AccessibilityRole::group); + } + //============================================================================== struct WebViewHandle { @@ -470,7 +542,7 @@ public: ComSmartPtr environment; }; - static std::optional createWebViewHandle(const WebBrowserComponent::Options::WinWebView2& options) + static std::optional createWebViewHandle (const WebBrowserComponent::Options::WinWebView2& options) { using CreateWebViewEnvironmentWithOptionsFunc = HRESULT (*) (PCWSTR, PCWSTR, ICoreWebView2EnvironmentOptions*, @@ -645,6 +717,45 @@ private: return S_OK; }).Get(), &webResourceRequestedToken); } + + if (webViewController != nullptr) + { + webViewController->add_MoveFocusRequested (Callback ( + [this] (ICoreWebView2Controller*, ICoreWebView2MoveFocusRequestedEventArgs* args) -> HRESULT + { + ScopedValueSetter scope { inMoveFocusRequested, true }; + + auto* comp = [&]() -> Component* + { + auto* c = owner.getParentComponent(); + + if (c == nullptr) + return nullptr; + + const auto traverser = c->createFocusTraverser(); + + if (COREWEBVIEW2_MOVE_FOCUS_REASON reason; + args->get_Reason (&reason) == S_OK && reason == COREWEBVIEW2_MOVE_FOCUS_REASON_PREVIOUS) + { + // The previous Component to the embedded WebView2 Component is the + // WebBrowserComponent. Here we want to skip that and jump to the + // Component that comes before it. + return traverser->getPreviousComponent (&owner); + } + + // The Component that comes immediately after the WebBrowserComponent is the + // embedded WebView2. We want to jump to the Component that comes after that. + return traverser->getNextComponent (this); + }(); + + if (comp != nullptr) + comp->getAccessibilityHandler()->grabFocus(); + else + giveAwayKeyboardFocus(); + + return S_OK; + }).Get(), &moveFocusRequestedToken); + } } void removeEventHandlers() @@ -669,6 +780,12 @@ private: webView->remove_WebResourceRequested (webResourceRequestedToken); } } + + if (webViewController != nullptr) + { + if (moveFocusRequestedToken.value != 0) + webViewController->remove_MoveFocusRequested (moveFocusRequestedToken); + } } void setWebViewPreferences() @@ -710,7 +827,18 @@ private: { if (auto* peer = getPeer()) { - isCreating = true; + // We enforce the serial creation of WebView2 instances so that our HWND association + // logic can work. Multiple HWNDs can belong to the same browser process, so the only + // way to identify which belongs to which WebView2 is to associate them with each other + // in the order of creation. + if (webView2ConstructionHelper.webView2BeingCreated != nullptr) + { + webView2ConstructionHelper.viewsWaitingForCreation.insert (this); + return; + } + + webView2ConstructionHelper.viewsWaitingForCreation.erase (this); + webView2ConstructionHelper.webView2BeingCreated = this; WeakReference weakThis (this); @@ -720,7 +848,7 @@ private: { if (weakThis != nullptr) { - weakThis->isCreating = false; + webView2ConstructionHelper.webView2BeingCreated = nullptr; if (controller != nullptr) { @@ -729,6 +857,40 @@ private: if (weakThis->webView != nullptr) { + if (UINT32 browserProcessId; + weakThis->webView->get_BrowserProcessId (&browserProcessId) == S_OK) + { + auto* self = weakThis.get(); + auto* webView2WindowHandle = static_cast (self->getWindowHandle()); + + // There is no WebView2 API for getting the HWND hosting + // the WebView2 content. So we iterate over all child + // windows of the JUCE peer HWND, and try to figure out + // which one belongs to a WebView2. What we are looking for + // is a window that has a child window that belongs to the + // browserProcessId. + const auto directChildWindows = getDirectChildWindows (webView2WindowHandle); + + for (auto* childWindow : directChildWindows) + { + if (! self->webView2ConstructionHelper.associatedWebViewNativeWindows.contains (childWindow)) + { + if (anyChildWindow (childWindow, + [browserProcessId] (auto* childOfChild) + { + if (DWORD procId; GetWindowThreadProcessId (childOfChild, &procId) != 0) + return (UINT32) procId == browserProcessId; + + return false; + })) + { + webView2ConstructionHelper.associatedWebViewNativeWindows.insert (childWindow); + AccessibilityHandler::setNativeChildForComponent (*self, childWindow); + } + } + } + } + weakThis->addEventHandlers(); weakThis->setWebViewPreferences(); weakThis->componentMovedOrResized (true, true); @@ -737,6 +899,9 @@ private: weakThis->webView->Navigate (weakThis->urlRequest.url.toWideCharPointer()); } } + + if (! weakThis->webView2ConstructionHelper.viewsWaitingForCreation.empty()) + (*weakThis->webView2ConstructionHelper.viewsWaitingForCreation.begin())->triggerAsyncUpdate(); } return S_OK; @@ -746,6 +911,11 @@ private: void closeWebView() { + if (auto* webViewNativeWindow = AccessibilityHandler::getNativeChildForComponent (*this)) + webView2ConstructionHelper.associatedWebViewNativeWindows.erase (webViewNativeWindow); + + AccessibilityHandler::setNativeChildForComponent (*this, nullptr); + if (webViewController != nullptr) { webViewController->Close(); @@ -756,6 +926,12 @@ private: webViewHandle.environment = nullptr; } + //============================================================================== + void handleAsyncUpdate() override + { + createWebView(); + } + //============================================================================== void setControlBounds (Rectangle newBounds) const { @@ -790,7 +966,10 @@ private: newWindowRequestedToken { 0 }, windowCloseRequestedToken { 0 }, navigationCompletedToken { 0 }, - webResourceRequestedToken { 0 }; + webResourceRequestedToken { 0 }, + moveFocusRequestedToken { 0 }; + + bool inMoveFocusRequested = false; struct URLRequest { @@ -801,7 +980,14 @@ private: URLRequest urlRequest; - bool isCreating = false; + struct WebView2ConstructionHelper + { + WebView2* webView2BeingCreated; + std::set viewsWaitingForCreation; + std::set associatedWebViewNativeWindows; + }; + + inline static WebView2ConstructionHelper webView2ConstructionHelper; //============================================================================== JUCE_DECLARE_WEAK_REFERENCEABLE (WebView2) @@ -969,9 +1155,16 @@ void WebBrowserComponent::visibilityChanged() checkWindowAssociation(); } -void WebBrowserComponent::focusGained (FocusChangeType) +void WebBrowserComponent::focusGainedWithDirection (FocusChangeType type, FocusChangeDirection dir) { - browser->getInternalWebView().focusGained(); + ignoreUnused (type, dir); + + #if JUCE_USE_WIN_WEBVIEW2 + if (auto* webView2 = dynamic_cast (&browser->getInternalWebView())) + webView2->focusGainedWithDirection (type, dir); + else + #endif + browser->getInternalWebView().focusGained(); } void WebBrowserComponent::clearCookies()