This is an automated email from the ASF dual-hosted git repository.
hiedra pushed a commit to branch examples/TDJ_localresources
in repository https://gitbox.apache.org/repos/asf/royale-asjs.git
The following commit(s) were added to refs/heads/examples/TDJ_localresources by
this push:
new 64782b4a00 Fixed FileBrowser bead (JS): Improved compatibility with
Chrome's user activation policies when triggering file selection dialogs. Now
dynamically checks if the active element is eligible to activate a file input,
and gracefully falls back to a hidden proxy button if not. This ensures
consistent behavior across all browsers when invoking `browse()` from different
UI contexts.
64782b4a00 is described below
commit 64782b4a003a9ac22a056833abd6e378813517a7
Author: mjesteve <[email protected]>
AuthorDate: Thu Apr 24 02:48:38 2025 +0200
Fixed FileBrowser bead (JS): Improved compatibility with Chrome's user
activation policies when triggering file selection dialogs. Now dynamically
checks if the active element is eligible to activate a file input, and
gracefully falls back to a hidden proxy button if not. This ensures consistent
behavior across all browsers when invoking `browse()` from different UI
contexts.
---
.../org/apache/royale/file/beads/FileBrowser.as | 101 +++++++++++++++++++--
1 file changed, 95 insertions(+), 6 deletions(-)
diff --git
a/frameworks/projects/Network/src/main/royale/org/apache/royale/file/beads/FileBrowser.as
b/frameworks/projects/Network/src/main/royale/org/apache/royale/file/beads/FileBrowser.as
index a966dfbb00..3523f91b12 100644
---
a/frameworks/projects/Network/src/main/royale/org/apache/royale/file/beads/FileBrowser.as
+++
b/frameworks/projects/Network/src/main/royale/org/apache/royale/file/beads/FileBrowser.as
@@ -108,8 +108,10 @@ package org.apache.royale.file.beads
COMPILE::JS
private var focusedButton:WrappedHTMLElement;
+ //COMPILE::JS
+ //private var interval:int = -1;
COMPILE::JS
- private var interval:int = -1;
+ private var proxyButton:HTMLButtonElement;
/**
* Open up the system file browser. A user selection will
trigger a 'modelChanged' event on the strand.
@@ -130,14 +132,23 @@ package org.apache.royale.file.beads
{
focusedButton = document.activeElement as
WrappedHTMLElement;
//trace("activeElement is: " + focusedButton);
- focusedButton.addEventListener("blur",
blurHandler);
- focusedButton.addEventListener("focus",
focusHandler);
+ // Listen for cancellation
+ if (focusedButton) {
+ focusedButton.addEventListener("blur",
blurHandler);
+ focusedButton.addEventListener("focus",
focusHandler);
+ }
window.addEventListener("keydown", keyHandler);
window.addEventListener("mousemove",
mouseHandler);
window.addEventListener("mousedown",
mouseHandler);
- focusedButton.addEventListener("click",
clickHandler);
- focusedButton.click();
- focusedButton.removeEventListener("click",
clickHandler);
+
+ if (isElementClickFriendly(focusedButton)) {
+ focusedButton.addEventListener("click",
clickHandler);
+ focusedButton.click();
+
focusedButton.removeEventListener("click", clickHandler);
+ } else {
+ createProxyButton();
+ proxyButton.click();
+ }
}
}
@@ -146,7 +157,37 @@ package org.apache.royale.file.beads
{
delegate.click();
}
+
+ COMPILE::JS
+ private function createProxyButton():void
+ {
+ if (proxyButton) return;
+
+ proxyButton = document.createElement("button") as
HTMLButtonElement;
+ proxyButton.style.position = "absolute";
+ proxyButton.style.left = "-9999px";
+ proxyButton.style.width = "1px";
+ proxyButton.style.height = "1px";
+ proxyButton.style.opacity = "0";
+ proxyButton.style.pointerEvents = "auto";
+ proxyButton.onclick = function():void {
+ delegate.click();
+ };
+ document.body.appendChild(proxyButton);
+ }
+ COMPILE::JS
+ private function removeProxyButton():void
+ {
+ if (proxyButton) {
+ proxyButton.onclick = null;
+ if (proxyButton.parentElement) {
+
proxyButton.parentElement.removeChild(proxyButton);
+ }
+ proxyButton = null;
+ }
+ }
+
COMPILE::JS
private function blurHandler(e:Object):void
{
@@ -215,6 +256,7 @@ package org.apache.royale.file.beads
else {
host.dispatchEvent(new Event("cancel"));
//trace("FileReference cancel");
+ removeProxyButton();
}
}
@@ -252,6 +294,53 @@ package org.apache.royale.file.beads
var fileModel:IFileModel = host.model as IFileModel;
fileModel.fileReference = (delegate as
HTMLInputElement).files[0];
host.dispatchEvent(new Event("modelChanged"));
+
+ removeProxyButton();
+ }
+
+ COMPILE::JS
+ /**
+ * Determines whether an HTML element can be considered
click-friendly,
+ * meaning it is likely to respond properly to a programmatic
`.click()` call.
+ * Useful for detecting elements that can trigger "user
activation" in browsers like Chrome.
+ *
+ * @param el The WrappedHTMLElement to test.
+ * @return true if the element is clickable and likely to
trigger user interaction events.
+ */
+ private function
isElementClickFriendly(el:WrappedHTMLElement):Boolean
+ {
+ if (!el) return false;
+
+ // 1. Style checks: ensure the element is visible and
interactive
+ var style:CSSStyleDeclaration = getComputedStyle(el,
null);
+ if (style.pointerEvents == "none" || style.visibility
== "hidden" || style.display == "none")
+ return false;
+
+ // 2. Structural checks: avoid elements that are
disabled or not in the layout flow
+ if (el.getAttribute("disabled") || el.offsetParent ===
null)
+ return false;
+
+ // 3. Layout checks: ensure the element has a visible
bounding box inside the viewport
+ var rect:Object = el.getBoundingClientRect();
+ var inViewport:Boolean = rect.width > 0 && rect.height
> 0 &&
+
rect.bottom >= 0 && rect.top <= window.innerHeight &&
+
rect.right >= 0 && rect.left <= window.innerWidth;
+
+ if (!inViewport)
+ return false;
+
+ // 4. Semantic checks: known interactive elements or
those with keyboard/tab access
+ var tag:String = el.tagName.toLowerCase();
+ var role:String = el.getAttribute("role");
+
+ if (tag == "button" || tag == "a" || tag == "input" ||
tag == "label") return true;
+ if (el.hasAttribute("tabindex")) return true;
+ if (role == "button" || role == "link" || role ==
"checkbox" || role == "menuitem") return true;
+
+ // 5. Behavior check: detect programmatically attached
handlers
+ if (typeof(el.onclick) == "function" ||
typeof(el.click) == "function") return true;
+
+ return false;
}
}