CT WEBBROWSER: Difference between revisions
Lou Montana (talk | contribs) m (Text replacement - "↵\{\{ConfigPage\| *[a-zA-Z]+ *\}\}" to "") |
(Completed some TODOs, added some links, improved some formatting) |
||
| (One intermediate revision by one other user not shown) | |||
| Line 1: | Line 1: | ||
{{CT|intro | {{CT|intro | ||
|macro = CT_WEBBROWSER | |macro= CT_WEBBROWSER | ||
|value = 106 | |value= 106 | ||
|description = | |description= | ||
{{Feature|warning|This is | {{Feature|warning| | ||
This is an experimental feature currently available for testing on Development Branch. | |||
The URL is subject to {{hl|allowedHTMLLoadURIs[]}} whitelisting in | <!-- It is not yet decided whether this will become available in Stable branch. --> | ||
[[ctrlSetURL]] | Please provide Feedback on the {{Link|https://discord.gg/arma|{{arma}} Discord}}'s {{hl|#dev_rc_branch}} channel. | ||
}} | |||
Web browser control features an embedded Chromium window.<br> | |||
The URL is subject to {{hl|allowedHTMLLoadURIs[]}} whitelisting in {{Link|Description.ext#CfgCommands}}.<br> | |||
[[ctrlSetURL]] can be used to change the URL after creation of the control. | |||
}} | }} | ||
| Line 15: | Line 20: | ||
{{CT|attribute | {{CT|attribute | ||
|name=allowExternalURL | |name= allowExternalURL | ||
|type1=Boolean | |type1= Boolean | ||
|value1=1 | |value1= 1 | ||
|description=Whether this browser will allow external URLs, as opposed to local content. | |description= Whether this browser will allow external URLs, as opposed to local content. | ||
}} | }} | ||
| Line 25: | Line 30: | ||
{{CT|attribute | {{CT|attribute | ||
|name=url | |name= url | ||
|type1=String | |type1= String | ||
|value1="https://arma3.com" | |value1= "https://arma3.com" | ||
|description= if allowExternalURL | |description= An external (if {{Link|#allowExternalURL}} is enabled) URL to be opened, or a path to a local file to load. Local files are subject to {{Link|Description.ext#CfgCommands|{{hl|allowedHTMLLoadURIs[]}}}}. | ||
}} | }} | ||
| Line 35: | Line 40: | ||
== Scripting == | == Scripting == | ||
{{Feature|informative|See [[ctrlWebBrowserAction]].}} | |||
=== Control Event Handlers === | |||
The following event handlers can only be added to [[CT_WEBBROWSER]] controls. | |||
==== JSDialog ==== | ==== JSDialog ==== | ||
* '''Use on:''' Control ([[CT_WEBBROWSER]]) | * '''Use on:''' Control ([[CT_WEBBROWSER]]) | ||
* '''Fired on:''' Fires when | * '''Fired on:''' Fires when JavaScript triggers an {{hl|alert()}} or {{hl|confirm()}} dialog. | ||
<sqf>params ["_control", "_isConfirmDialog", "_message"];</sqf> | <sqf>params ["_control", "_isConfirmDialog", "_message"];</sqf> | ||
The code handling this event can return a [[Boolean]] true/false. Which will stop the next Eventhandlers from being triggered, and will be interpreted as a reply to the dialog.<br> | The code handling this event can return a [[Boolean]] true/false. Which will stop the next Eventhandlers from being triggered, and will be interpreted as a reply to the dialog.<br> | ||
| Line 147: | Line 61: | ||
==== PageLoaded ==== | ==== PageLoaded ==== | ||
* '''Use on:''' Control ([[CT_WEBBROWSER]]) | * '''Use on:''' Control ([[CT_WEBBROWSER]]) | ||
* '''Fired on:''' Fires when the current page has finished loading. This only contains the main page, the page may contain content (scripts, images) that will load asynchronously and may not be ready. This fires multiple times, when the current page ( | * '''Fired on:''' Fires when the current page has finished loading. This only contains the main page, the page may contain content (scripts, images) that will load asynchronously and may not be ready. This fires multiple times, when the current page (URL) was changed or when the browser is Resumed. | ||
<sqf>params ["_control"];</sqf> | <sqf>params ["_control"];</sqf> | ||
Once PageLoaded is triggered, it is safe to execute | Once PageLoaded is triggered, it is safe to execute JavaScript on the page. Before PageLoaded, the page might not be ready to receive JavaScript and may drop ExecJS requests. | ||
{{CT|examples}} | {{CT|examples}} | ||
| Line 177: | Line 88: | ||
== Browser Content == | == Browser Content == | ||
The | The browser is separated into two modes: | ||
* Remote content (i.e. loading external resources using a URL), which can be enabled with the {{Link|#allowExternalURL}} attribute. | |||
* Local content (i.e. loading local HTML files from within a PBO or mission). | |||
=== Remote Content === | === Remote Content === | ||
Remote content is considered unsafe. Any browser that uses | Remote content is considered unsafe. Any browser that uses {{Link|#allowExternalURL}} will prompt the player to allow it being opened. | ||
URLs are limited by the {{Link|Description.ext#CfgCommands|{{hl|allowedHTMLLoadURIs[]}}}} configuration. | |||
=== Local Content === | === Local Content === | ||
Loading Local Content | Loading Local Content forces the browser into a sandbox mode.<br> | ||
Players will not receive a permission popup for Local Content.<br> | Players will not receive a permission popup for Local Content.<br> | ||
Local Content offers a | Local Content offers a JavaScript API to interact with the game without having to go through SQF.<br> | ||
Local files are subject to | Local files are subject to {{Link|Description.ext#CfgCommands|{{hl|allowedHTMLLoadURIs[]}}}}. | ||
==== Sandbox ==== | ==== Sandbox ==== | ||
The content is loaded into a [https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#sandbox sandboxed] iframe with a [https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP Content Security Policy].<br> | The content is loaded into a [https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#sandbox sandboxed] {{hl|<iframe>}} with a [https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP Content Security Policy].<br> | ||
<br> | <br> | ||
Any external communication via [https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API fetch] or [https://developer.mozilla.org/de/docs/Web/API/XMLHttpRequest XMLHttpRequest] is unavailable.<br> | Any external communication via [https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API fetch] or [https://developer.mozilla.org/de/docs/Web/API/XMLHttpRequest XMLHttpRequest] is unavailable.<br> | ||
Access to [https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage Local Storage] | Access to [https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage Local Storage] and [https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API IndexedDB] is unvailable.<br> | ||
[https://developer.mozilla.org/en-US/docs/Web/API/Window/alert alert()] and [https://developer.mozilla.org/en-US/docs/Web/API/Window/confirm confirm()] are not directly accessible, but the | [https://developer.mozilla.org/en-US/docs/Web/API/Window/alert alert()] and [https://developer.mozilla.org/en-US/docs/Web/API/Window/confirm confirm()] are not directly accessible, but the JavaScript API provides a wrapper. | ||
Content like | Content like images, styles, fonts, scripts and media can only be specified inline (in case of scripts and styles) or via [https://developer.mozilla.org/en-US/docs/Web/URI/Schemes/data Data URLs].<br> | ||
==== | ==== JavaScript API ==== | ||
This API is injected into every local content page automatically. | This API is injected into every local content page automatically. | ||
API offers the following functions ( | The API offers the following functions (here expressed as TypeScript to clarify argument and return types): | ||
<syntaxhighlight lang="javascript"> | <syntaxhighlight lang="javascript"> | ||
class A3API | class A3API | ||
{ | { | ||
/** | |||
* Loads file from game filesystem. | |||
* | |||
* @param filePath - Path in game filesystem, without leading backslash | |||
* @param maxSize - maximum texture width (used to select Mip) | |||
* @returns The image as a data-url | |||
*/ | |||
static RequestTexture(texturePath: string, maxSize: number): Promise<string>; | |||
/** | |||
* Loads file from game filesystem. | |||
* | |||
* @param filePath - same as loadFile SQF command | |||
* @returns The file content | |||
*/ | |||
static RequestFile(filePath: string): Promise<string>; | |||
/** | |||
* Loads and preprocesses file from game filesystem. | |||
* | |||
* @param filePath - same as preprocessFile SQF command | |||
* @returns The file content | |||
*/ | |||
static RequestPreprocessedFile(filePath: string): Promise<string>; | |||
// Triggers a alert() (Needs to be piped due to https://chromestatus.com/feature/5148698084376576) | |||
static SendAlert(content: string): void; | |||
static SendConfirm(content: string): Promise<string>; | |||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Example, this is how to load a image out of the game, into | Example, this is how to load a image out of the game, into JavaScript, and insert it into the page | ||
<syntaxhighlight lang="javascript"> | <syntaxhighlight lang="javascript"> | ||
const eWeaponImage = document.createElement('img'); | const eWeaponImage = document.createElement('img'); | ||
| Line 249: | Line 158: | ||
document.body.appendChild(eWeaponImage); | document.body.appendChild(eWeaponImage); | ||
</syntaxhighlight> | </syntaxhighlight> | ||
==== Example HUD ==== | ==== Example HUD ==== | ||
Here is an example of how to create a basic WebBrowser based HUD overlay purely inside a mission file. | Here is an example of how to create a basic WebBrowser based HUD overlay purely inside a mission file. | ||
| Line 298: | Line 204: | ||
params ["_control", "_isConfirmDialog", "_message"]; | params ["_control", "_isConfirmDialog", "_message"]; | ||
// Insert message as text into the page, by assembling | // Insert message as text into the page, by assembling JavaScript code to insert a new element and set its text. | ||
_control ctrlWebBrowserAction ["ExecJS", format ["const eSpan = document.createElement('span'); eSpan.textContent = 'Script says %1!'; document.body.appendChild(eSpan);", _message]]; | _control ctrlWebBrowserAction [ | ||
"ExecJS", | |||
format ["const eSpan = document.createElement('span'); eSpan.textContent = 'Script says %1!'; document.body.appendChild(eSpan);", _message] | |||
]; | |||
true; // We need to tell it that we handled the "dialog", by returning true or false. | true; // We need to tell it that we handled the "dialog", by returning true or false. | ||
| Line 306: | Line 215: | ||
//_ctrl ctrlWebBrowserAction ["OpenDevConsole"]; // Can open developer console here | //_ctrl ctrlWebBrowserAction ["OpenDevConsole"]; // Can open developer console here | ||
//_ctrl ctrlWebBrowserAction ["LoadFile", "hudOverlay.html"]; // Instead of using url= in description.ext, we could also load the file here, that way we can open dev console before the page is loaded | //_ctrl ctrlWebBrowserAction ["LoadFile", "hudOverlay.html"]; // Instead of using url= in description.ext, we could also load the file here, | ||
// that way we can open dev console before the page is loaded | |||
</sqf> | </sqf> | ||
| Line 315: | Line 224: | ||
<html lang="en"> | <html lang="en"> | ||
<body> | <body> | ||
<script> | <script> | ||
| Line 324: | Line 232: | ||
// Send a message to script | // Send a message to script | ||
A3API.SendAlert(" | A3API.SendAlert("JavaScript is sending an alert"); // This alert will trigger a JSDialog (Alert Dialog) eventhandler | ||
</script> | |||
</body> | </body> | ||
</html> | </html> | ||
</syntaxhighlight> | </syntaxhighlight> | ||
==== Seamless Stop / Resume ==== | |||
==== Seamless Stop/Resume ==== | |||
When you stop a browser, and resume it again, the browser will have to reload the last page which might take a couple frames. During the reload, the browser might display a few empty. | When you stop a browser, and resume it again, the browser will have to reload the last page which might take a couple frames. During the reload, the browser might display a few empty. | ||
Inside a ui2texture containing a browser, it is possible to skip/hide these empty frames by also pausing the ui2texture updates. | Inside a ui2texture containing a browser, it is possible to skip/hide these empty frames by also pausing the ui2texture updates. | ||
| Line 354: | Line 260: | ||
_ctrl setVariable ["lastDraw", diag_frameNo]; | _ctrl setVariable ["lastDraw", diag_frameNo]; | ||
private _drawHandlerIndex = _ctrl ctrlAddEventHandler ["Draw", { | private _drawHandlerIndex = _ctrl ctrlAddEventHandler ["Draw", { | ||
params ["_ctrl"]; | |||
_ctrl setVariable ["lastDraw", diag_frameNo]; // Remember when the last draw happened | |||
}]; | }]; | ||
// Note that this will not serialize into savegames, and will probably cause errors if Save/Load is involved. | |||
waitUntil { ((_ctrl getVariable "lastDraw") + 60) < diag_frameNo }; | |||
//#TODO handle the control becoming null because the UI was closed | //#TODO handle the control becoming null because the UI was closed | ||
// No draw for 60 frames, its probably stable, so we resume updating the ui2tex | // No draw for 60 frames, its probably stable, so we resume updating the ui2tex | ||
private _handlerID = addMissionEventHandler ["EachFrame", {displayUpdate (_thisArgs#0)}, [_display]]; | // Note that this will not serialize into savegames, and will probably cause errors if Save/Load is involved. | ||
private _handlerID = addMissionEventHandler ["EachFrame", {displayUpdate (_thisArgs#0)}, [_display]]; | |||
_display setVariable ["displayUpdateHandler", _handlerID]; | _display setVariable ["displayUpdateHandler", _handlerID]; | ||
_ctrl ctrlRemoveEventHandler ["Draw", _drawHandlerIndex]; // Stop listening for draw events | _ctrl ctrlRemoveEventHandler ["Draw", _drawHandlerIndex]; // Stop listening for draw events | ||
</sqf> | </sqf> | ||
==== Redirecting links into Arma files ==== | ==== Redirecting links into Arma files ==== | ||
You can build a "normal" web page, with "normal" <a href=".."> links on it | You can build a "normal" web page, with "normal" <syntaxhighlight lang="html" inline><a href=".."></syntaxhighlight> links on it and have the links navigate to files that are inside {{arma3}}. | ||
This | This JavaScript will redirect all links to the A3API which will make it load the file from the game filesystem. | ||
Just like using "LoadFile" ctrlWebBrowserAction. | Just like using "LoadFile" ctrlWebBrowserAction. | ||
| Line 379: | Line 287: | ||
<script> | <script> | ||
document.addEventListener("DOMContentLoaded", () => { | document.addEventListener("DOMContentLoaded", () => { | ||
var allLinks = document.getElementsByTagName("a"); | |||
[...allLinks].forEach((link) => { | |||
link.onclick = function (e) { | |||
e.preventDefault(); | |||
var targetUrl = e.currentTarget.getAttribute('href'); | |||
A3API.NavigateTo(targetUrl); | |||
}; | |||
}); | |||
}); | }); | ||
</script> | </script> | ||
Latest revision as of 18:35, 3 December 2025
| Control Types / MACRO (TYPE VALUE) | |
|---|---|
| Text/Image/Video |
CT_STATIC (0) | CT_EDIT (2) | CT_HTML (9) | CT_STRUCTURED_TEXT (13) |
| Buttons |
CT_BUTTON (1) | CT_ACTIVETEXT (11) | CT_SHORTCUTBUTTON (16) | CT_CHECKBOX (77) | CT_XBUTTON (41) |
| Lists |
CT_COMBO (4) | CT_TOOLBOX (6) | CT_CHECKBOXES (7) | CT_TREE (12) | CT_CONTROLS_TABLE (19) | CT_XCOMBO (44) | CT_LISTBOX (5) | CT_LISTNBOX (102) | CT_LISTNBOX_CHECKABLE (104) | CT_XLISTBOX (45) |
| 3D Objects |
CT_OBJECT (80) | CT_OBJECT_ZOOM (81) | CT_OBJECT_CONTAINER (82) | CT_OBJECT_CONT_ANIM (83) |
| Maps |
CT_MAP (100) | CT_MAP_MAIN (101) |
| Meta |
CT_SLIDER (3) | CT_XSLIDER (43) | CT_PROGRESS (8) | CT_CONTROLS_GROUP (15) | CT_WEBBROWSER (106) | CT_EXTENSION (107) |
| Menu |
CT_CONTEXT_MENU (14) | CT_MENU (46) | CT_MENU_STRIP (47) |
| Unknown |
CT_STATIC_SKEW (10) | CT_HITZONES (17) | CT_VEHICLETOGGLES (18) | CT_XKEYDESC (40) | CT_ANIMATED_TEXTURE (45) | CT_LINEBREAK (98) | CT_USER (99) | CT_ITEMSLOT (103) | CT_VEHICLE_DIRECTION (105) |
Introduction
Web browser control features an embedded Chromium window.
The URL is subject to allowedHTMLLoadURIs[] whitelisting in Description.ext - CfgCommands.
ctrlSetURL can be used to change the URL after creation of the control.
Related commands & functions
Related User Interface Eventhandlers
Alphabetical Order
#define CT_WEBBROWSER 106
A
allowExternalURL
- Type
- Boolean
- Description
- Whether this browser will allow external URLs, as opposed to local content.
allowExternalURL = 1;
U
url
- Type
- String
- Description
- An external (if allowExternalURL is enabled) URL to be opened, or a path to a local file to load. Local files are subject to allowedHTMLLoadURIs[].
url = "https://arma3.com";
Scripting
Control Event Handlers
The following event handlers can only be added to CT_WEBBROWSER controls.
JSDialog
- Use on: Control (CT_WEBBROWSER)
- Fired on: Fires when JavaScript triggers an alert() or confirm() dialog.
The code handling this event can return a Boolean true/false. Which will stop the next Eventhandlers from being triggered, and will be interpreted as a reply to the dialog.
If the code does not immediately provide a reply, it should return Nothing.
If the code does not return bool, then the script must call ctrlWebBrowserAction with the "JSDialog" action to reply to the dialog, the WebBrowser will be unresponsive until a reply has been sent.
Draw
- Use on: Control (CT_WEBBROWSER)
- Fired on: Fires when the WebBrowser has drawn a new frame (NOT when the game control displays a new frame)
PageLoaded
- Use on: Control (CT_WEBBROWSER)
- Fired on: Fires when the current page has finished loading. This only contains the main page, the page may contain content (scripts, images) that will load asynchronously and may not be ready. This fires multiple times, when the current page (URL) was changed or when the browser is Resumed.
Once PageLoaded is triggered, it is safe to execute JavaScript on the page. Before PageLoaded, the page might not be ready to receive JavaScript and may drop ExecJS requests.
Default Classes
RscWebBrowser
Baseline RscWebBrowser example
class RscWebBrowser
{
type = CT_WEBBROWSER; // 106
idc = -1;
deletable = 0;
style = 0;
x = 0;
y = 0;
w = 0.3;
h = 0.3;
allowExternalURL = 1;
url = "https://arma3.com";
};
Browser Content
The browser is separated into two modes:
- Remote content (i.e. loading external resources using a URL), which can be enabled with the allowExternalURL attribute.
- Local content (i.e. loading local HTML files from within a PBO or mission).
Remote Content
Remote content is considered unsafe. Any browser that uses allowExternalURL will prompt the player to allow it being opened. URLs are limited by the allowedHTMLLoadURIs[] configuration.
Local Content
Loading Local Content forces the browser into a sandbox mode.
Players will not receive a permission popup for Local Content.
Local Content offers a JavaScript API to interact with the game without having to go through SQF.
Local files are subject to allowedHTMLLoadURIs[].
Sandbox
The content is loaded into a sandboxed <iframe> with a Content Security Policy.
Any external communication via fetch or XMLHttpRequest is unavailable.
Access to Local Storage and IndexedDB is unvailable.
alert() and confirm() are not directly accessible, but the JavaScript API provides a wrapper.
Content like images, styles, fonts, scripts and media can only be specified inline (in case of scripts and styles) or via Data URLs.
JavaScript API
This API is injected into every local content page automatically.
The API offers the following functions (here expressed as TypeScript to clarify argument and return types):
class A3API
{
/**
* Loads file from game filesystem.
*
* @param filePath - Path in game filesystem, without leading backslash
* @param maxSize - maximum texture width (used to select Mip)
* @returns The image as a data-url
*/
static RequestTexture(texturePath: string, maxSize: number): Promise<string>;
/**
* Loads file from game filesystem.
*
* @param filePath - same as loadFile SQF command
* @returns The file content
*/
static RequestFile(filePath: string): Promise<string>;
/**
* Loads and preprocesses file from game filesystem.
*
* @param filePath - same as preprocessFile SQF command
* @returns The file content
*/
static RequestPreprocessedFile(filePath: string): Promise<string>;
// Triggers a alert() (Needs to be piped due to https://chromestatus.com/feature/5148698084376576)
static SendAlert(content: string): void;
static SendConfirm(content: string): Promise<string>;
}
Example, this is how to load a image out of the game, into JavaScript, and insert it into the page
const eWeaponImage = document.createElement('img');
A3API.RequestTexture("A3\\weapons_F\\Rifles\\MX\\data\\UI\\gear_mx_rifle_X_CA.paa", 512).then(imageContent => eWeaponImage.src = imageContent);
document.body.appendChild(eWeaponImage);
Example HUD
Here is an example of how to create a basic WebBrowser based HUD overlay purely inside a mission file.
Create a basic mission, and place the following files next to the mission.sqm
description.ext:
import RscText;
class RscTitles
{
class HudOverlayUI
{
idd = 13371337;
fadein = 0; // Required parameters for RscTitles
fadeout = 0;
duration = 1e+011;
onLoad = "GHUDOverlay = _this"; // Store our Display in a variable so we can access it from script
class controls
{
class Texture: RscText
{
type = 106; // CT_WEBBROWSER
idc = 1337;
x = safeZoneX; // Full screen from corner to corner
y = safeZoneY;
w = safeZoneW;
h = safeZoneH;
url = "file://hudOverlay.html"; // Reference to a file inside our mission
};
};
};
};
init.sqf
hudOverlay.html
<!DOCTYPE html>
<html lang="en">
<body>
<script>
// Load the MX Rifle inventory image and add it to the page
const eWeaponImage = document.createElement('img');
A3API.RequestTexture("A3\\weapons_F\\Rifles\\MX\\data\\UI\\gear_mx_rifle_X_CA.paa", 512).then(imageContent => eWeaponImage.src = imageContent);
document.body.appendChild(eWeaponImage);
// Send a message to script
A3API.SendAlert("JavaScript is sending an alert"); // This alert will trigger a JSDialog (Alert Dialog) eventhandler
</script>
</body>
</html>
Seamless Stop / Resume
When you stop a browser, and resume it again, the browser will have to reload the last page which might take a couple frames. During the reload, the browser might display a few empty. Inside a ui2texture containing a browser, it is possible to skip/hide these empty frames by also pausing the ui2texture updates.
This code assumes that the displayUpdate calls on the ui2texture, are done with a EachFrame Mission EH. And also the WebBrowser control is directly inside the ui2texture display.
Redirecting links into Arma files
You can build a "normal" web page, with "normal" <a href=".."> links on it and have the links navigate to files that are inside Arma 3.
This JavaScript will redirect all links to the A3API which will make it load the file from the game filesystem.
Just like using "LoadFile" ctrlWebBrowserAction.
<script>
document.addEventListener("DOMContentLoaded", () => {
var allLinks = document.getElementsByTagName("a");
[...allLinks].forEach((link) => {
link.onclick = function (e) {
e.preventDefault();
var targetUrl = e.currentTarget.getAttribute('href');
A3API.NavigateTo(targetUrl);
};
});
});
</script>
<body>
<a href="arma://MissionSubFolder/index.html">Click me to get to page</a>
</body>