mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
WebBrowserComponent: Windows: Add accessibility integration
This commit is contained in:
parent
6ef45eb20c
commit
7657efd227
12 changed files with 381 additions and 29 deletions
|
|
@ -28,6 +28,63 @@ namespace juce
|
||||||
|
|
||||||
AccessibilityHandler* AccessibilityHandler::currentlyFocusedHandler = nullptr;
|
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<void*, Component*> componentForNativeChild;
|
||||||
|
std::map<Component*, void*> nativeChildForComponent;
|
||||||
|
};
|
||||||
|
|
||||||
AccessibilityHandler::AccessibilityHandler (Component& comp,
|
AccessibilityHandler::AccessibilityHandler (Component& comp,
|
||||||
AccessibilityRole accessibilityRole,
|
AccessibilityRole accessibilityRole,
|
||||||
AccessibilityActions accessibilityActions,
|
AccessibilityActions accessibilityActions,
|
||||||
|
|
@ -322,6 +379,21 @@ std::unique_ptr<AccessibilityHandler::AccessibilityNativeImpl> AccessibilityHand
|
||||||
#endif
|
#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
|
#if ! JUCE_NATIVE_ACCESSIBILITY_INCLUDED
|
||||||
void AccessibilityHandler::notifyAccessibilityEvent (AccessibilityEvent) const {}
|
void AccessibilityHandler::notifyAccessibilityEvent (AccessibilityEvent) const {}
|
||||||
void AccessibilityHandler::postAnnouncement (const String&, AnnouncementPriority) {}
|
void AccessibilityHandler::postAnnouncement (const String&, AnnouncementPriority) {}
|
||||||
|
|
|
||||||
|
|
@ -291,6 +291,26 @@ public:
|
||||||
AccessibilityNativeHandle* getNativeImplementation() const;
|
AccessibilityNativeHandle* getNativeImplementation() const;
|
||||||
/** @internal */
|
/** @internal */
|
||||||
std::type_index getTypeIndex() const { return typeIndex; }
|
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:
|
private:
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
|
|
|
||||||
|
|
@ -2182,7 +2182,7 @@ void Component::internalMouseDown (MouseInputSource source,
|
||||||
|
|
||||||
if (! flags.dontFocusOnMouseClickFlag)
|
if (! flags.dontFocusOnMouseClickFlag)
|
||||||
{
|
{
|
||||||
grabKeyboardFocusInternal (focusChangedByMouseClick, true);
|
grabKeyboardFocusInternal (focusChangedByMouseClick, true, FocusChangeDirection::unknown);
|
||||||
|
|
||||||
if (checker.shouldBailOut())
|
if (checker.shouldBailOut())
|
||||||
return;
|
return;
|
||||||
|
|
@ -2435,17 +2435,20 @@ void Component::internalBroughtToFront()
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
void Component::focusGained (FocusChangeType) {}
|
void Component::focusGained (FocusChangeType) {}
|
||||||
|
void Component::focusGainedWithDirection (FocusChangeType, FocusChangeDirection) {}
|
||||||
void Component::focusLost (FocusChangeType) {}
|
void Component::focusLost (FocusChangeType) {}
|
||||||
void Component::focusOfChildComponentChanged (FocusChangeType) {}
|
void Component::focusOfChildComponentChanged (FocusChangeType) {}
|
||||||
|
|
||||||
void Component::internalKeyboardFocusGain (FocusChangeType cause)
|
void Component::internalKeyboardFocusGain (FocusChangeType cause)
|
||||||
{
|
{
|
||||||
internalKeyboardFocusGain (cause, WeakReference<Component> (this));
|
internalKeyboardFocusGain (cause, WeakReference<Component> (this), FocusChangeDirection::unknown);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Component::internalKeyboardFocusGain (FocusChangeType cause,
|
void Component::internalKeyboardFocusGain (FocusChangeType cause,
|
||||||
const WeakReference<Component>& safePointer)
|
const WeakReference<Component>& safePointer,
|
||||||
|
FocusChangeDirection direction)
|
||||||
{
|
{
|
||||||
|
focusGainedWithDirection (cause, direction);
|
||||||
focusGained (cause);
|
focusGained (cause);
|
||||||
|
|
||||||
if (safePointer == nullptr)
|
if (safePointer == nullptr)
|
||||||
|
|
@ -2585,7 +2588,7 @@ std::unique_ptr<ComponentTraverser> Component::createKeyboardFocusTraverser()
|
||||||
return parentComponent->createKeyboardFocusTraverser();
|
return parentComponent->createKeyboardFocusTraverser();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Component::takeKeyboardFocus (FocusChangeType cause)
|
void Component::takeKeyboardFocus (FocusChangeType cause, FocusChangeDirection direction)
|
||||||
{
|
{
|
||||||
if (currentlyFocusedComponent == this)
|
if (currentlyFocusedComponent == this)
|
||||||
return;
|
return;
|
||||||
|
|
@ -2614,11 +2617,11 @@ void Component::takeKeyboardFocus (FocusChangeType cause)
|
||||||
componentLosingFocus->internalKeyboardFocusLoss (cause);
|
componentLosingFocus->internalKeyboardFocusLoss (cause);
|
||||||
|
|
||||||
if (currentlyFocusedComponent == this)
|
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())
|
if (! isShowing())
|
||||||
return;
|
return;
|
||||||
|
|
@ -2626,7 +2629,7 @@ void Component::grabKeyboardFocusInternal (FocusChangeType cause, bool canTryPar
|
||||||
if (flags.wantsKeyboardFocusFlag
|
if (flags.wantsKeyboardFocusFlag
|
||||||
&& (isEnabled() || parentComponent == nullptr))
|
&& (isEnabled() || parentComponent == nullptr))
|
||||||
{
|
{
|
||||||
takeKeyboardFocus (cause);
|
takeKeyboardFocus (cause, direction);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2637,7 +2640,7 @@ void Component::grabKeyboardFocusInternal (FocusChangeType cause, bool canTryPar
|
||||||
{
|
{
|
||||||
if (auto* defaultComp = traverser->getDefaultComponent (this))
|
if (auto* defaultComp = traverser->getDefaultComponent (this))
|
||||||
{
|
{
|
||||||
defaultComp->grabKeyboardFocusInternal (cause, false);
|
defaultComp->grabKeyboardFocusInternal (cause, false, direction);
|
||||||
return;
|
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,
|
// if no children want it and we're allowed to try our parent comp,
|
||||||
// then pass up to parent, which will try our siblings.
|
// then pass up to parent, which will try our siblings.
|
||||||
if (canTryParent && parentComponent != nullptr)
|
if (canTryParent && parentComponent != nullptr)
|
||||||
parentComponent->grabKeyboardFocusInternal (cause, true);
|
parentComponent->grabKeyboardFocusInternal (cause, true, direction);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Component::grabKeyboardFocus()
|
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.
|
// thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe.
|
||||||
JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED
|
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!
|
// 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
|
// 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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
nextComp->grabKeyboardFocusInternal (focusChangedByTabKey, true);
|
nextComp->grabKeyboardFocusInternal (focusChangedByTabKey,
|
||||||
|
true,
|
||||||
|
moveToNext ? FocusChangeDirection::forward
|
||||||
|
: FocusChangeDirection::backward);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1891,11 +1891,28 @@ public:
|
||||||
focusChangedDirectly /**< Means that the focus was changed by a call to grabKeyboardFocus(). */
|
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.
|
/** Called to indicate that this component has just acquired the keyboard focus.
|
||||||
@see focusLost, setWantsKeyboardFocus, getCurrentlyFocusedComponent, hasKeyboardFocus
|
@see focusLost, setWantsKeyboardFocus, getCurrentlyFocusedComponent, hasKeyboardFocus
|
||||||
*/
|
*/
|
||||||
virtual void focusGained (FocusChangeType cause);
|
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.
|
/** Called to indicate that this component has just lost the keyboard focus.
|
||||||
@see focusGained, setWantsKeyboardFocus, getCurrentlyFocusedComponent, hasKeyboardFocus
|
@see focusGained, setWantsKeyboardFocus, getCurrentlyFocusedComponent, hasKeyboardFocus
|
||||||
*/
|
*/
|
||||||
|
|
@ -2614,7 +2631,7 @@ private:
|
||||||
void internalMouseWheel (MouseInputSource, Point<float>, Time, const MouseWheelDetails&);
|
void internalMouseWheel (MouseInputSource, Point<float>, Time, const MouseWheelDetails&);
|
||||||
void internalMagnifyGesture (MouseInputSource, Point<float>, Time, float);
|
void internalMagnifyGesture (MouseInputSource, Point<float>, Time, float);
|
||||||
void internalBroughtToFront();
|
void internalBroughtToFront();
|
||||||
void internalKeyboardFocusGain (FocusChangeType, const WeakReference<Component>&);
|
void internalKeyboardFocusGain (FocusChangeType, const WeakReference<Component>&, FocusChangeDirection);
|
||||||
void internalKeyboardFocusGain (FocusChangeType);
|
void internalKeyboardFocusGain (FocusChangeType);
|
||||||
void internalKeyboardFocusLoss (FocusChangeType);
|
void internalKeyboardFocusLoss (FocusChangeType);
|
||||||
void internalChildKeyboardFocusChange (FocusChangeType, const WeakReference<Component>&);
|
void internalChildKeyboardFocusChange (FocusChangeType, const WeakReference<Component>&);
|
||||||
|
|
@ -2632,8 +2649,8 @@ private:
|
||||||
void sendMovedResizedMessagesIfPending();
|
void sendMovedResizedMessagesIfPending();
|
||||||
void repaintParent();
|
void repaintParent();
|
||||||
void sendFakeMouseMove() const;
|
void sendFakeMouseMove() const;
|
||||||
void takeKeyboardFocus (FocusChangeType);
|
void takeKeyboardFocus (FocusChangeType, FocusChangeDirection);
|
||||||
void grabKeyboardFocusInternal (FocusChangeType, bool canTryParent);
|
void grabKeyboardFocusInternal (FocusChangeType, bool canTryParent, FocusChangeDirection);
|
||||||
void giveAwayKeyboardFocusInternal (bool sendFocusLossEvent);
|
void giveAwayKeyboardFocusInternal (bool sendFocusLossEvent);
|
||||||
void sendEnablementChangeMessage();
|
void sendEnablementChangeMessage();
|
||||||
void sendVisibilityChangeMessage();
|
void sendVisibilityChangeMessage();
|
||||||
|
|
|
||||||
|
|
@ -166,10 +166,15 @@ JUCE_COMRESULT AccessibilityNativeHandle::get_HostRawElementProvider (IRawElemen
|
||||||
{
|
{
|
||||||
return withCheckedComArgs (pRetVal, *this, [&]
|
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);
|
return wrapper->hostProviderFromHwnd ((HWND) accessibilityHandler.getComponent().getWindowHandle(), pRetVal);
|
||||||
|
|
||||||
|
if (auto* embeddedWindow = static_cast<HWND> (AccessibilityHandler::getNativeChildForComponent (accessibilityHandler.getComponent())))
|
||||||
|
return wrapper->hostProviderFromHwnd (embeddedWindow, pRetVal);
|
||||||
|
}
|
||||||
|
|
||||||
return S_OK;
|
return S_OK;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -180,6 +185,10 @@ JUCE_COMRESULT AccessibilityNativeHandle::get_ProviderOptions (ProviderOptions*
|
||||||
return E_INVALIDARG;
|
return E_INVALIDARG;
|
||||||
|
|
||||||
*options = (ProviderOptions) (ProviderOptions_ServerSideProvider | ProviderOptions_UseComThreading);
|
*options = (ProviderOptions) (ProviderOptions_ServerSideProvider | ProviderOptions_UseComThreading);
|
||||||
|
|
||||||
|
if (AccessibilityHandler::getNativeChildForComponent (accessibilityHandler.getComponent()) != nullptr)
|
||||||
|
*options |= ProviderOptions_OverrideProvider;
|
||||||
|
|
||||||
return S_OK;
|
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
|
String AccessibilityNativeHandle::getElementName() const
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ namespace juce
|
||||||
{
|
{
|
||||||
|
|
||||||
class AccessibilityNativeHandle : public ComBaseClassHelper<IRawElementProviderSimple,
|
class AccessibilityNativeHandle : public ComBaseClassHelper<IRawElementProviderSimple,
|
||||||
|
IRawElementProviderHwndOverride,
|
||||||
ComTypes::IRawElementProviderFragment,
|
ComTypes::IRawElementProviderFragment,
|
||||||
ComTypes::IRawElementProviderFragmentRoot>
|
ComTypes::IRawElementProviderFragmentRoot>
|
||||||
{
|
{
|
||||||
|
|
@ -58,6 +59,8 @@ public:
|
||||||
JUCE_COMRESULT ElementProviderFromPoint (double x, double y, ComTypes::IRawElementProviderFragment** pRetVal) override;
|
JUCE_COMRESULT ElementProviderFromPoint (double x, double y, ComTypes::IRawElementProviderFragment** pRetVal) override;
|
||||||
JUCE_COMRESULT GetFocus (ComTypes::IRawElementProviderFragment** pRetVal) override;
|
JUCE_COMRESULT GetFocus (ComTypes::IRawElementProviderFragment** pRetVal) override;
|
||||||
|
|
||||||
|
JUCE_COMRESULT GetOverrideProviderForHwnd (HWND hwnd, IRawElementProviderSimple** pRetVal) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
String getElementName() const;
|
String getElementName() const;
|
||||||
|
|
|
||||||
|
|
@ -4147,6 +4147,21 @@ private:
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
case WM_SETFOCUS:
|
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();
|
updateKeyModifiers();
|
||||||
handleFocusGain();
|
handleFocusGain();
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -264,12 +264,17 @@ public:
|
||||||
/** @internal */
|
/** @internal */
|
||||||
void visibilityChanged() override;
|
void visibilityChanged() override;
|
||||||
/** @internal */
|
/** @internal */
|
||||||
void focusGained (FocusChangeType) override;
|
void focusGainedWithDirection (FocusChangeType, FocusChangeDirection) override;
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
class Pimpl;
|
class Pimpl;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override
|
||||||
|
{
|
||||||
|
return std::make_unique<AccessibilityHandler> (*this, AccessibilityRole::group);
|
||||||
|
}
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
std::unique_ptr<Pimpl> browser;
|
std::unique_ptr<Pimpl> browser;
|
||||||
bool blankPageShown = false, unloadPageWhenHidden;
|
bool blankPageShown = false, unloadPageWhenHidden;
|
||||||
|
|
|
||||||
|
|
@ -676,7 +676,7 @@ void WebBrowserComponent::visibilityChanged()
|
||||||
checkWindowAssociation();
|
checkWindowAssociation();
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebBrowserComponent::focusGained (FocusChangeType)
|
void WebBrowserComponent::focusGainedWithDirection (FocusChangeType, FocusChangeDirection)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1036,7 +1036,7 @@ void WebBrowserComponent::visibilityChanged()
|
||||||
checkWindowAssociation();
|
checkWindowAssociation();
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebBrowserComponent::focusGained (FocusChangeType)
|
void WebBrowserComponent::focusGainedWithDirection (FocusChangeType, FocusChangeDirection)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -724,7 +724,7 @@ void WebBrowserComponent::visibilityChanged()
|
||||||
checkWindowAssociation();
|
checkWindowAssociation();
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebBrowserComponent::focusGained (FocusChangeType)
|
void WebBrowserComponent::focusGainedWithDirection (FocusChangeType, FocusChangeDirection)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -351,11 +351,56 @@ private:
|
||||||
|
|
||||||
#if JUCE_USE_WIN_WEBVIEW2
|
#if JUCE_USE_WIN_WEBVIEW2
|
||||||
|
|
||||||
|
#include <winuser.h>
|
||||||
|
|
||||||
using namespace Microsoft::WRL;
|
using namespace Microsoft::WRL;
|
||||||
|
|
||||||
|
static std::vector<HWND> getDirectChildWindows (HWND hwnd)
|
||||||
|
{
|
||||||
|
std::vector<HWND> 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<bool (HWND)> callback)
|
||||||
|
{
|
||||||
|
// EnumChildWindows itself provides the recursion
|
||||||
|
EnumChildWindows (hwnd,
|
||||||
|
[] (HWND hwnd, LPARAM lParam)
|
||||||
|
{
|
||||||
|
auto* callbackPtr = reinterpret_cast<std::function<bool (HWND)>*> (lParam);
|
||||||
|
return (*callbackPtr) (hwnd) ? TRUE : FALSE;
|
||||||
|
},
|
||||||
|
reinterpret_cast<LPARAM> (&callback));
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool anyChildWindow (HWND hwnd, std::function<bool (HWND)> 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,
|
class WebView2 : public InternalWebViewType,
|
||||||
public Component,
|
public Component,
|
||||||
public ComponentMovementWatcher
|
public ComponentMovementWatcher,
|
||||||
|
private AsyncUpdater
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
WebView2 (WebBrowserComponent& o, const WebBrowserComponent::Options& prefs)
|
WebView2 (WebBrowserComponent& o, const WebBrowserComponent::Options& prefs)
|
||||||
|
|
@ -372,6 +417,26 @@ public:
|
||||||
owner.addAndMakeVisible (this);
|
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
|
~WebView2() override
|
||||||
{
|
{
|
||||||
removeEventHandlers();
|
removeEventHandlers();
|
||||||
|
|
@ -389,7 +454,9 @@ public:
|
||||||
|
|
||||||
bool hasBrowserBeenCreated() override
|
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
|
void goToURL (const String& url, const StringArray* headers, const MemoryBlock* postData) override
|
||||||
|
|
@ -462,6 +529,11 @@ public:
|
||||||
owner.visibilityChanged();
|
owner.visibilityChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override
|
||||||
|
{
|
||||||
|
return std::make_unique<AccessibilityHandler> (*this, AccessibilityRole::group);
|
||||||
|
}
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
struct WebViewHandle
|
struct WebViewHandle
|
||||||
{
|
{
|
||||||
|
|
@ -470,7 +542,7 @@ public:
|
||||||
ComSmartPtr<ICoreWebView2Environment> environment;
|
ComSmartPtr<ICoreWebView2Environment> environment;
|
||||||
};
|
};
|
||||||
|
|
||||||
static std::optional<WebViewHandle> createWebViewHandle(const WebBrowserComponent::Options::WinWebView2& options)
|
static std::optional<WebViewHandle> createWebViewHandle (const WebBrowserComponent::Options::WinWebView2& options)
|
||||||
{
|
{
|
||||||
using CreateWebViewEnvironmentWithOptionsFunc = HRESULT (*) (PCWSTR, PCWSTR,
|
using CreateWebViewEnvironmentWithOptionsFunc = HRESULT (*) (PCWSTR, PCWSTR,
|
||||||
ICoreWebView2EnvironmentOptions*,
|
ICoreWebView2EnvironmentOptions*,
|
||||||
|
|
@ -645,6 +717,45 @@ private:
|
||||||
return S_OK;
|
return S_OK;
|
||||||
}).Get(), &webResourceRequestedToken);
|
}).Get(), &webResourceRequestedToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (webViewController != nullptr)
|
||||||
|
{
|
||||||
|
webViewController->add_MoveFocusRequested (Callback<ICoreWebView2MoveFocusRequestedEventHandler> (
|
||||||
|
[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()
|
void removeEventHandlers()
|
||||||
|
|
@ -669,6 +780,12 @@ private:
|
||||||
webView->remove_WebResourceRequested (webResourceRequestedToken);
|
webView->remove_WebResourceRequested (webResourceRequestedToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (webViewController != nullptr)
|
||||||
|
{
|
||||||
|
if (moveFocusRequestedToken.value != 0)
|
||||||
|
webViewController->remove_MoveFocusRequested (moveFocusRequestedToken);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void setWebViewPreferences()
|
void setWebViewPreferences()
|
||||||
|
|
@ -710,7 +827,18 @@ private:
|
||||||
{
|
{
|
||||||
if (auto* peer = getPeer())
|
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<WebView2> weakThis (this);
|
WeakReference<WebView2> weakThis (this);
|
||||||
|
|
||||||
|
|
@ -720,7 +848,7 @@ private:
|
||||||
{
|
{
|
||||||
if (weakThis != nullptr)
|
if (weakThis != nullptr)
|
||||||
{
|
{
|
||||||
weakThis->isCreating = false;
|
webView2ConstructionHelper.webView2BeingCreated = nullptr;
|
||||||
|
|
||||||
if (controller != nullptr)
|
if (controller != nullptr)
|
||||||
{
|
{
|
||||||
|
|
@ -729,6 +857,40 @@ private:
|
||||||
|
|
||||||
if (weakThis->webView != nullptr)
|
if (weakThis->webView != nullptr)
|
||||||
{
|
{
|
||||||
|
if (UINT32 browserProcessId;
|
||||||
|
weakThis->webView->get_BrowserProcessId (&browserProcessId) == S_OK)
|
||||||
|
{
|
||||||
|
auto* self = weakThis.get();
|
||||||
|
auto* webView2WindowHandle = static_cast<HWND> (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->addEventHandlers();
|
||||||
weakThis->setWebViewPreferences();
|
weakThis->setWebViewPreferences();
|
||||||
weakThis->componentMovedOrResized (true, true);
|
weakThis->componentMovedOrResized (true, true);
|
||||||
|
|
@ -737,6 +899,9 @@ private:
|
||||||
weakThis->webView->Navigate (weakThis->urlRequest.url.toWideCharPointer());
|
weakThis->webView->Navigate (weakThis->urlRequest.url.toWideCharPointer());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (! weakThis->webView2ConstructionHelper.viewsWaitingForCreation.empty())
|
||||||
|
(*weakThis->webView2ConstructionHelper.viewsWaitingForCreation.begin())->triggerAsyncUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
return S_OK;
|
return S_OK;
|
||||||
|
|
@ -746,6 +911,11 @@ private:
|
||||||
|
|
||||||
void closeWebView()
|
void closeWebView()
|
||||||
{
|
{
|
||||||
|
if (auto* webViewNativeWindow = AccessibilityHandler::getNativeChildForComponent (*this))
|
||||||
|
webView2ConstructionHelper.associatedWebViewNativeWindows.erase (webViewNativeWindow);
|
||||||
|
|
||||||
|
AccessibilityHandler::setNativeChildForComponent (*this, nullptr);
|
||||||
|
|
||||||
if (webViewController != nullptr)
|
if (webViewController != nullptr)
|
||||||
{
|
{
|
||||||
webViewController->Close();
|
webViewController->Close();
|
||||||
|
|
@ -756,6 +926,12 @@ private:
|
||||||
webViewHandle.environment = nullptr;
|
webViewHandle.environment = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
void handleAsyncUpdate() override
|
||||||
|
{
|
||||||
|
createWebView();
|
||||||
|
}
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
void setControlBounds (Rectangle<int> newBounds) const
|
void setControlBounds (Rectangle<int> newBounds) const
|
||||||
{
|
{
|
||||||
|
|
@ -790,7 +966,10 @@ private:
|
||||||
newWindowRequestedToken { 0 },
|
newWindowRequestedToken { 0 },
|
||||||
windowCloseRequestedToken { 0 },
|
windowCloseRequestedToken { 0 },
|
||||||
navigationCompletedToken { 0 },
|
navigationCompletedToken { 0 },
|
||||||
webResourceRequestedToken { 0 };
|
webResourceRequestedToken { 0 },
|
||||||
|
moveFocusRequestedToken { 0 };
|
||||||
|
|
||||||
|
bool inMoveFocusRequested = false;
|
||||||
|
|
||||||
struct URLRequest
|
struct URLRequest
|
||||||
{
|
{
|
||||||
|
|
@ -801,7 +980,14 @@ private:
|
||||||
|
|
||||||
URLRequest urlRequest;
|
URLRequest urlRequest;
|
||||||
|
|
||||||
bool isCreating = false;
|
struct WebView2ConstructionHelper
|
||||||
|
{
|
||||||
|
WebView2* webView2BeingCreated;
|
||||||
|
std::set<WebView2*> viewsWaitingForCreation;
|
||||||
|
std::set<void*> associatedWebViewNativeWindows;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline static WebView2ConstructionHelper webView2ConstructionHelper;
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
JUCE_DECLARE_WEAK_REFERENCEABLE (WebView2)
|
JUCE_DECLARE_WEAK_REFERENCEABLE (WebView2)
|
||||||
|
|
@ -969,9 +1155,16 @@ void WebBrowserComponent::visibilityChanged()
|
||||||
checkWindowAssociation();
|
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<WebView2*> (&browser->getInternalWebView()))
|
||||||
|
webView2->focusGainedWithDirection (type, dir);
|
||||||
|
else
|
||||||
|
#endif
|
||||||
|
browser->getInternalWebView().focusGained();
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebBrowserComponent::clearCookies()
|
void WebBrowserComponent::clearCookies()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue