diff --git a/WIP.md b/WIP.md index c5c84d24..4de49c06 100644 --- a/WIP.md +++ b/WIP.md @@ -2,18 +2,23 @@ Jekyll site (`just-the-docs` theme) deploying to `docs.twinbasic.com`. Source under `docs/`. -## Current Task +## Status -Fill out reference documentation by adapting Microsoft VBA-Docs (CC-BY-4.0) for twinBASIC, and document the twinBASIC-specific packages (`VB`, `WebView2Package`, `Assert`, …) from their `.twin` source. Always work from a primary source — never paraphrase from memory. +Initial reference documentation is **complete**. All seven packages have full reference coverage adapted from primary sources (Microsoft VBA-Docs CC-BY-4.0 for the runtime library, `.twin` source for the twinBASIC-specific packages); the CEF and WebView2 packages also carry a tutorial set. -Status: +| Package | Reference | Tutorials | +|--------------------------------------|-----------|-----------| +| VBA package | done | — | +| VBRUN package | done | — | +| VB package | done | — | +| WebView2Package | done | done | +| Assert package | done | — | +| CustomControls / CustomControlsPackage | done | — | +| cefPackage (CEF) | done | done | -- **VBA package** — done. -- **VBRUN package** — done. -- **VB package** — done. -- **WebView2Package** — done. -- **Assert package** — done. -- **CustomControls / CustomControlsPackage** — in progress. +The rest of this file is the maintenance guide for adding new pages or updating existing ones — primary-source paths, page templates, cross-section linking conventions, the per-symbol workflow, and the integrity check. + +When working from a primary source: always read it first — **never paraphrase from memory.** ## Where things live @@ -22,9 +27,9 @@ Status: - `docs/Reference///index.md` — module landing page listing its members. - `docs/Reference/VB/.md` — single-file class page (e.g. [`CheckBox.md`](docs/Reference/VB/CheckBox.md)). - `docs/Reference/VB//index.md` — folder-style class page when sub-pages may follow (e.g. [`CheckMark/index.md`](docs/Reference/VB/CheckMark/index.md)). -- `docs/Reference/VB/todo.md` — backlog tracker for the VB package; see [Backlog discovery](#backlog-discovery). - `docs/Reference/WebView2/` — WebView2 package: the **WebView2** control class plus its small wrapper classes (request / response / headers / environment options) and the `wv2…` enumerations. - `docs/Reference/CustomControls/` — CustomControls package: the eight **Waynes…** custom controls, their shared `Styles/` helper classes (`Fill`, `Borders`, `Corners`, `TextRendering`, …), the `Framework/` DESIGNER surface (interfaces, CoClasses, the `Canvas` / `SerializeInfo` UDTs), and the `Enumerations/` (`CornerShape`, `FillPattern`, `DockMode`, …). +- `docs/Reference/CEF/` — CEF (Chromium Embedded Framework) package: the **CefBrowser** control, its `EnvironmentOptions` sub-page, and the two user-facing enumerations (`CefLogSeverity`, `cefPrintOrientation`). This is a much smaller surface than WebView2 — the package is currently BETA and many WebView2-equivalent features are not yet exposed. - `docs/Reference/Statements.md` — alphabetical index of language statements. - `docs/Reference/Procedures and Functions.md` — alphabetical index of procedures/functions. - `docs/_includes/footer_custom.html` — overrides the theme's footer slot; renders the copyright line and, when `vba_attribution: true` is set in a page's frontmatter, an additional CC-BY-4.0 attribution line beneath it. @@ -58,9 +63,16 @@ All of twinbasic's package sources are at: etc. ``` +For the CEF package, the sources live in a separate sibling tree (not under `tb-export`): + +``` +..\tbrepro\cef\CEFSampleProject\Packages\cefPackage\Sources\ +..\tbrepro\cef\CEFSampleProject\Sources\ ← four worked examples + MainForm +``` + ### VB Controls -The `STANDARD/` folder is the primary backlog. The `BASE/` folder defines the inheritance chain (e.g. `BaseControlWindowlessNoFocus` → `BaseControlRectDockable` → `BaseControlRect` → `BaseControl`); read those alongside the leaf class to know which `Public` members are actually visible. Members marked `Protected` or hidden behind `[Unimplemented]` should be flagged with a `> [!NOTE]` callout. +The `STANDARD/` folder holds the leaf control classes. The `BASE/` folder defines the inheritance chain (e.g. `BaseControlWindowlessNoFocus` → `BaseControlRectDockable` → `BaseControlRect` → `BaseControl`); read those alongside the leaf class to know which `Public` members are actually visible. Members marked `Protected` or hidden behind `[Unimplemented]` should be flagged with a `> [!NOTE]` callout. These pages are fully original content — **omit** the `vba_attribution: true` frontmatter flag. @@ -171,8 +183,6 @@ That's 4 pages total. (If a future release of the package adds more modules or n **License:** MIT (copyright Wayne Phillips T/A iTech Masters, 2022) — same situation as WebView2Package. Pages are fully original; **omit** the `vba_attribution: true` flag. -**No backlog file** — three modules listed in this section are the entire backlog. - ### CustomControls / CustomControlsPackage Two source-side packages, **one** doc-side package. They always ship together and are co-versioned with twinBASIC — `CustomControlsPackage` lists `CustomControls` as a `isCompilerPackage` reference in its `Settings`, and neither is usable without the other. Document the union as `docs/Reference/CustomControls/`. @@ -303,7 +313,87 @@ docs/Reference/CustomControls/ **License:** MIT (copyright Wayne Phillips T/A iTech Masters, 2022) — same situation as WebView2Package and Assert. Pages are fully original; **omit** the `vba_attribution: true` flag. -**No backlog file** — the eight controls + the three sub-groups enumerated above are the entire backlog; track inline here. +### cefPackage (CEF) + +Layout of `..\tbrepro\cef\CEFSampleProject\Packages\cefPackage\Sources\`: + +- `CefControl.twin` — contains three classes: the private `CefBrowserBaseCtl` (where every event / method / property is declared), the private `CefEnvironmentOptions` (a bare-field options class), and the public `CefBrowser` control which only inherits from `CefBrowserBaseCtl` and sets the design-time icon. This single file is the entire user-facing surface of the package. +- `CefControlGlobalWnd.twin` — private internal message-window plumbing; no doc page. +- `APIs.twin` — private Win32 API declarations; no doc page. +- `MainModule.twin` — `PreSubMain` module that intercepts CEF sub-process launches; no doc page. +- `Registry.twin` — Win32 registry helpers; no doc page. +- `SpecialFolders.twin` — `KnownFolders_CSIDLs` enum + `GetSpecialFolder` helper; `Private Module`, no doc page (used only by the sample project, not exported from the package). +- `CEF/Aliases.twin`, `CEF/ApiEntryPoints.twin`, `CEF/Globals.twin`, `CEF/Initialize.twin`, `CEF/Misc.twin` — `Private Module` implementation detail; no doc pages. +- `CEF/Enums/_cef_*_t.twin` — internal C-ABI enums wrapped in `Private Module _cef_*_t`; one file (`_cef_log_severity_t.twin`) also declares the user-facing `CefLogSeverity` enum. The other 29 `_cef_*_t` enums never surface in the public API and get **no doc page**. +- `CEF/Structs/_cef_*_t.twin` — internal C-ABI structs (`cef_browser_settings_t`, `cef_pdf_print_settings_t`, …) wrapped in `Private Module`; no doc pages. `Structs/TODO.twin` declares an empty placeholder `Class CefRequestHeaders` — also no doc page (see the alias note below). +- `CEF/CrossProcessIPC/BrowserOM.twin`, `RendererOM.twin`, `RendererAsyncOM.twin` — `Private Class` (or unmarked-but-effectively-private) classes that broker IPC between the host and the CEF browser / renderer processes; no doc pages. **Exception:** `BrowserOM.twin` declares the user-facing `cefPrintOrientation` enum inline (used by `CefBrowser.PrintToPdf`); document it under `Enumerations/`. +- `CEF/Implementations/*.twin` — every file is `Private Class` / `Private Module`; CEF callback handlers (`Client`, `ClientLifeSpanHandler`, `ClientLoadHandler`, `AppRender…`, the `Exposed*Javascript*` and `*Task` classes, the `PrintToPDFCallback`, …). All implementation detail; no doc pages. + +Public user-facing surface (one control + one options class + two enums): + +| Symbol | Kind | Role | +|------------------------------|---------------------------------------|-----------------------------------------------------------------------------------------------| +| `CefBrowser` | Class (inherits `CefBrowserBaseCtl`) | the control itself, tagged `[WindowsControl("/Miscellaneous/cef64.png")]` | +| `CefEnvironmentOptions` | `Private Class`, exposed | reached via `Public EnvironmentOptions As CefEnvironmentOptions = New CefEnvironmentOptions` on the control | +| `CefLogSeverity` | `Enum` (in `_cef_log_severity_t.twin`)| used by `CefEnvironmentOptions.LogSeverity` | +| `cefPrintOrientation` | `Enum` (in `BrowserOM.twin`) | used by the `Orientation` parameter of `CefBrowser.PrintToPdf` | + +`CefBrowserBaseCtl` is `Private Class` but is where every public member is *declared* — `CefBrowser` itself adds nothing beyond inheriting from it. Document everything on the `CefBrowser/index.md` page (folder-style, parallel to `WebView2/`) and treat the base class as an internal split that doesn't surface. + +`CefBrowser` inherits from `VB.BaseControlRectDockable`, so its Properties listing must fold in the dockable-rect surface (`Name`, `Left`, `Top`, `Width`, `Height`, `Anchors`, `Dock`, ...) the same way VB-package control pages and CustomControls control pages do. + +`CefBrowserRequestHeaders` is declared at the top of `CefControl.twin` as `Alias CefBrowserRequestHeaders As Object` and appears in the `NavigationStarting` event signature. The underlying `Class CefRequestHeaders` lives at the bottom of `Structs/TODO.twin` with an empty body — it's a placeholder for a future header collection. **No doc page;** mention on the `NavigationStarting` event entry that the parameter is currently typed `Object` and reserved for future use. + +The `CefBrowser` public surface, from `grep '^\s*Public' CefControl.twin` plus a walk through `CefBrowserBaseCtl`: + +- **Events (12):** `Create`, `Ready`, `Error`, `NavigationStarting`, `NavigationComplete`, `SourceChanged`, `DocumentTitleChanged`, `DOMContentLoaded`, `PrintToPdfCompleted`, `PrintToPdfFailed`, `JsAsyncResult`, `JsMessage`. +- **Methods (14):** `Initialize`, `Navigate`, `NavigateToString`, `Reload`, `GoBack`, `GoForward`, `ExecuteScript`, `JsRun`, `JsRunAsync`, `PostWebMessage`, `SetVirtualHostNameToFolderMapping`, `ClearVirtualHostNameToFolderMapping`, `OpenDevToolsWindow`, `PrintToPdf`. +- **Properties (12):** `DocumentURL` (Get/Let), `DocumentTitle` (Get), `ZoomFactor` (Get/Let), `UserAgent` (Get/Let), `CanGoBack` (Get), `CanGoForward` (Get), `CefMajorVersion` (Get), `Visible` (Get/Let), `hWnd` (Get), `Parent` (Get), `EnvironmentOptions` (field), `CreateInitialized` (field, `Boolean`, defaults `True`). Plus a `Hidden` `Align` (Get/Let) inherited boilerplate. Plus the inherited rect-dockable surface (`Name`, `Left`, `Top`, `Width`, `Height`, `Anchors`, `Dock`, …). + +`CefEnvironmentOptions` is four bare `Public` fields: + +| Field | Type | +|----------------------------|------------------| +| `BrowserExecutableFolder` | `String` | +| `UserDataFolder` | `String` | +| `LogFilePath` | `String` | +| `LogSeverity` | `CefLogSeverity` | + +`CefLogSeverity` members: `CefLogDisable = 0`, `CefLogVerbose = 1`, `CefLogInfo = 2`, `CefLogWarning = 3`, `CefLogError = 4`, `CefLogFatal = 5`. + +`cefPrintOrientation` members: `cefPrintPortrait = 0`, `cefPrintLandscape = 1`. + +**WebView2-parity gap list** (call out on `CEF/index.md`, drawn from `Sources\Example1..4.twin` where these are commented out as *"Sorry, this feature is not yet available in the CEF package"*): + +- Methods: `OpenTaskManagerWindow`, `AddObject` (host-object publication), the request-filter machinery (`AddWebResourceRequestedFilter`). +- Events: `AcceleratorKeyPressed`, `PermissionRequested`, `WebResourceRequested`, `ProcessFailed`, `ScriptDialogOpening`, `UserContextMenu`, `SuspendCompleted`, `SuspendFailed`, `DownloadStarting`, `NewWindowRequested`. + +`CefBrowser.NavigationComplete` carries `IsSuccess` and `WebErrorStatus` parameters, but the source (`OnNavigationComplete_UI` in `CefControl.twin`) currently hard-codes `IsSuccess = True` and `WebErrorStatus = 0` with `FIXME` comments — note this on the event page. + +**Multi-version source.** The same `.twin` sources compile against three CEF runtimes (v49 / v109 / v145) selected via the `CEF_VERSION` conditional-compilation argument on the project. At runtime, `CefBrowser.CefMajorVersion` returns the value picked at compile time. The user picks a runtime at deploy time by downloading the matching ZIP from `github.com/twinbasic/cef-runtimes` and extracting to `%LocalAppData%\twinBASIC_CEF_Runtime\`, or by overriding `CefBrowser.EnvironmentOptions.BrowserExecutableFolder` before / during the `Create` event. The wiki entry will redirect to the new docs, so the runtime download + version-picking section lives on `CEF/index.md`. + +#### Doc-side layout (folders / files) + +Six pages total: + +``` +docs/Reference/CEF/ + index.md ← package landing; intro + WebView2 comparison + version table + runtime install + WebView2-parity gap list + class & enum lists + CefBrowser/index.md ← the control (folder-style, like WebView2/WebView2/index.md) + CefBrowser/EnvironmentOptions.md ← parallel to WebView2/WebView2/EnvironmentOptions.md + Enumerations/index.md + Enumerations/CefLogSeverity.md + Enumerations/cefPrintOrientation.md +``` + +**Naming:** + +- Folder / URL segment: `CEF/` (uppercase acronym, matches the wiki spelling; drops the `Package` suffix, same simplification as WebView2 and Assert). +- Index title: `CEF Package` — the ` Package` convention. +- Permalinks: `/tB/Packages/CEF/` for the landing, `/tB/Packages/CEF/CefBrowser/` for the control (folder-style), `/tB/Packages/CEF/CefBrowser/EnvironmentOptions` for the sub-page, `/tB/Packages/CEF/Enumerations/` for the enums. +- `parent: CEF Package` on every top-level child page. The two enum pages set `parent: Enumerations` and `grand_parent: CEF Package` (the grouped-page pattern; mirror exactly the structure WebView2 uses for its `Enumerations/`). The `EnvironmentOptions` sub-page sets `parent: CefBrowser` and `grand_parent: CEF Package`. + +**License:** MIT (Wayne Phillips T/A iTech Masters) — same situation as WebView2Package, Assert, and CustomControls. The Settings file doesn't carry an explicit `licence:` field but every other package by this author is MIT and the wiki implies the same. Pages are fully original; **omit** the `vba_attribution: true` flag. ## Page template @@ -316,6 +406,7 @@ Match the existing style. Worked examples to imitate: - VB control class (single-file): `docs/Reference/VB/CheckBox.md`. - VB control class (folder-style): `docs/Reference/VB/CheckMark/index.md`. - Assert module page (single-file, all members inline): `docs/Reference/Assert/Exact.md`. +- CEF control class (folder-style with a sub-page): `docs/Reference/CEF/CefBrowser/index.md` + `docs/Reference/CEF/CefBrowser/EnvironmentOptions.md`. Skeleton: @@ -382,6 +473,9 @@ The URL prefixes are *not* uniform across packages — VBA pages live one segmen - CustomControls style helper → `/tB/Packages/CustomControls/Styles/` - CustomControls framework symbol → `/tB/Packages/CustomControls/Framework/` - CustomControls enumeration → `/tB/Packages/CustomControls/Enumerations/` +- CEF `CefBrowser` class → `/tB/Packages/CEF/CefBrowser/` (folder-style — has the `EnvironmentOptions` sub-page) +- CEF `EnvironmentOptions` sub-page → `/tB/Packages/CEF/CefBrowser/EnvironmentOptions` +- CEF enumeration → `/tB/Packages/CEF/Enumerations/` Common patterns: @@ -436,12 +530,26 @@ Common patterns: | CC `Packages/CustomControls/Framework/X` | `Packages/CustomControls/` (single-file) | `[Y](../)` | | CC `Packages/CustomControls/Enumerations/X` | sibling `Enumerations/Y` | `[Y](Y)` | | CC `Packages/CustomControls/Enumerations/X` | `Packages/CustomControls/` (single-file) | `[Y](../)` | +| CEF `Packages/CEF/index` | CEF `Packages/CEF/CefBrowser/` | `[Y](CefBrowser/)` | +| CEF `Packages/CEF/index` | CEF `Packages/CEF/Enumerations/Y` | `[Y](Enumerations/Y)` | +| CEF `Packages/CEF/index` | WebView2 `Packages/WebView2/Y` | `[Y](../WebView2/Y)` | +| CEF `Packages/CEF/CefBrowser/index` | CEF `Packages/CEF/CefBrowser/EnvironmentOptions` | `[Y](EnvironmentOptions)` | +| CEF `Packages/CEF/CefBrowser/index` | CEF `Packages/CEF/Enumerations/Y` | `[Y](../Enumerations/Y)` | +| CEF `Packages/CEF/CefBrowser/index` | WebView2 `Packages/WebView2/Y` | `[Y](../../WebView2/Y)` | +| CEF `Packages/CEF/CefBrowser/index` | VB `Packages/VB/Y` | `[Y](../../VB/Y)` | +| CEF `Packages/CEF/CefBrowser/index` | `Core/Y` | `[Y](../../../Core/Y)` | +| CEF `Packages/CEF/CefBrowser/EnvironmentOptions` | CEF `Packages/CEF/CefBrowser/` (parent)| `[Y](.)` | +| CEF `Packages/CEF/CefBrowser/EnvironmentOptions` | CEF `Packages/CEF/Enumerations/Y` | `[Y](../Enumerations/Y)` | +| CEF `Packages/CEF/Enumerations/X` | sibling `Enumerations/Y` | `[Y](Y)` | +| CEF `Packages/CEF/Enumerations/X` | CEF `Packages/CEF/CefBrowser/` (folder-style) | `[Y](../CefBrowser/)` | +| CEF `Packages/CEF/Enumerations/X` | CEF `Packages/CEF/CefBrowser/EnvironmentOptions` | `[Y](../CefBrowser/EnvironmentOptions)` | | `Core/X` | VBA `Modules//Y` | `[Y](../Modules//Y)` | | `Core/X` | VBRUN `Packages/VBRUN//Y` | `[Y](../Packages/VBRUN//Y)` | | `Core/X` | VB `Packages/VB/Y` | `[Y](../Packages/VB/Y)` | | `Core/X` | WebView2 `Packages/WebView2/Y` | `[Y](../Packages/WebView2/Y)` | | `Core/X` | Assert `Packages/Assert/` | `[Y](../Packages/Assert/)` | | `Core/X` | CC `Packages/CustomControls/Y` | `[Y](../Packages/CustomControls/Y)` | +| `Core/X` | CEF `Packages/CEF/Y` | `[Y](../Packages/CEF/Y)` | | `Core/X` | `Core/Y` (sibling) | `[Y](Y)` | Always link to the **canonical** location (the page's `permalink:`), not to a `redirect_from` alias. Pages that have moved out of `Core/` retain a `redirect_from: /tB/Core/` so legacy links still work, but forward-style links should point at the new home. @@ -454,6 +562,7 @@ Always link to the **canonical** location (the page's `permalink:`), not to a `r - WebView2Package items → `..\tb-export\NewProject\Packages\WebView2Package\Sources\Classes\.twin`, with enumerations in `Support\Enumerations.twin` and the one user-type in `Support\Types.twin`. Ignore everything under `Abstract\` (private COM interfaces). - Assert package → `..\tb-export\NewProject\Packages\Assert\Sources\.twin` (one file per module — `Exact.twin`, `Strict.twin`, `Permissive.twin`). - CustomControls — framework half: `..\tb-export\NewProject\Packages\CustomControls\Sources\CustomControls.twin` (a single file with `Module Constants`, the interfaces, and the CoClasses). Runtime half: `..\tb-export\NewProject\Packages\CustomControlsPackage\Sources\Waynes.twin` for each control + `zTemporarySupport.twin` for the shared style helpers and the mixin base classes. + - CEF package → `..\tbrepro\cef\CEFSampleProject\Packages\cefPackage\Sources\CefControl.twin` for the whole public surface (the `CefBrowser` control, its `CefBrowserBaseCtl` base, and `CefEnvironmentOptions`). For the two surfaced enums: `CEF\Enums\_cef_log_severity_t.twin` (declares both the internal `cef_log_severity_t` and the user-facing `CefLogSeverity`) and `CEF\CrossProcessIPC\BrowserOM.twin` (declares `cefPrintOrientation` inline, around line 29). Everything else under `cefPackage\Sources\` and `cefPackage\Sources\CEF\` is `Private Class` / `Private Module` plumbing — skip. The sample project's `Sources\Example1..4.twin` are the source-of-truth for which features are *not* yet exposed (commented-out event handlers with *"Sorry, this feature is not yet available in the CEF package"*). 2. **Decide placement**: - Pure language keyword (parsed by the compiler, no runtime call) → `docs/Reference/Core/`. - Runtime function/property → `docs/Reference///`. Add `redirect_from: /tB/Core/` so legacy `tB/Core/` links still work. @@ -466,6 +575,7 @@ Always link to the **canonical** location (the page's `permalink:`), not to a `r - CustomControls shared style helper → `docs/Reference/CustomControls/Styles/.md`. Pair small helpers with their containers on a single page (`Corner` inline under `Corners.md`, `Border` under `Borders.md`, `FillColorPoint` + `FillColorPoints` under `Fill.md`, `FontStyle` under `TextRendering.md`). - CustomControls framework symbol (interface, CoClass, UDT) → `docs/Reference/CustomControls/Framework/.md`. - CustomControls enumeration → `docs/Reference/CustomControls/Enumerations/.md` (mirrors `WebView2/Enumerations/` and `VBRUN/Constants/`). The three `Long`-alias enums (`ColorRGBA`, `PixelCount`, `PointSize`) live here too, even though they're really typedefs. + - CEF control → `docs/Reference/CEF/CefBrowser/index.md` (folder-style; carries the `EnvironmentOptions` sub-page). Pre-creation options class → `docs/Reference/CEF/CefBrowser/EnvironmentOptions.md` (parallel to `WebView2/WebView2/EnvironmentOptions.md`). CEF enumeration → `docs/Reference/CEF/Enumerations/.md`. - Pick `` from VBA's grouping (Information, Interaction, Strings, FileSystem, DateTime, Math, Financial, Conversion, ...) and the existing folders under `Reference//`. 3. **Adapt content** (VBA-Docs sources): - Strip MS frontmatter (`ms.assetid`, `f1_keywords`, `keywords`, `ms.date`, `ms.localizationpriority`). @@ -500,9 +610,21 @@ Always link to the **canonical** location (the page's `permalink:`), not to a `r - For `Customtate`: document the enum, but include a `> [!NOTE]` callout flagging the typo (the name appears to be a slip for `CustomState`) and pointing readers to the active `WindowState` enum, which carries identical members. - For `BaseControl` / `BaseControlFocusable` / `BaseForm`, `TextDecorator(s)`, the `UDTs` wrapper class (`MouseEvent`, `KeyEvent`, `FocusEvent`, `ElementDescriptor`, `CaretPosition`, `SpecialKeyCodes`), and `MathSupport` / `ColorSupport` modules: **no doc page** — implementation-detail private content. The mixin bases' members surface on the controls; the UDT-class members only matter for someone authoring a *new* custom control and can wait for an "authoring tutorial" pass. - Omit the `vba_attribution: true` frontmatter flag — these pages are fully original (both source packages are MIT-licensed). -8. **Flag tB deviations** with a `> [!NOTE]` callout (see next section). -9. **Update the parent index** (`//index.md`, `docs/Reference/VB/index.md`, `docs/Reference/WebView2/index.md`, `docs/Reference/Assert/index.md`, `docs/Reference/CustomControls/index.md` (and its `Styles/`, `Framework/`, `Enumerations/` sub-indices), `Reference/Statements.md`, or `Reference/Procedures and Functions.md`) — turn an unlinked bullet into a link with a short blurb. Match the existing style of the page. Also extend `docs/Reference/Packages.md` to list the new package once the landing page exists. -10. **Remove the symbol's path from `docs/Reference/VB/todo.md`** `redirect_from:` array (VB controls only — VBA/VBRUN backlogs are closed; WebView2Package, Assert, and CustomControls have small enough backlogs to track inline in this file rather than via a `todo.md`). +8. **Adapt content** (CEF `.twin` sources): + - `CefBrowser` is `Class CefBrowser` at the bottom of `CefControl.twin`, but inherits *everything* from the private `CefBrowserBaseCtl` declared at the top of the same file. Document the union as the `CefBrowser` page; don't surface the base-class split. Then walk `Inherits VB.BaseControlRectDockable` (`..\tb-export\NewProject\Packages\VB\Sources\BASE\BaseControlRectDockable.twin` and ancestors) to fold the inherited rect-dockable surface (`Name`, `Left`, `Top`, `Width`, `Height`, `Anchors`, `Dock`, …) into the Properties listing, the same way VB-package and CustomControls control pages do. + - List own + inherited members alphabetically within Properties / Methods / Events sections (mirror `CheckBox.md`, `WebView2/WebView2/index.md`). + - The `[Description("…")]` attribute on each `Public Event` / `Public` method / `Public Property` gives the user-visible one-liner from the IDE — use it as the basis for the page entry, then expand. The `[Description("")]` blocks with empty strings (the `Initialize` method, the bare-field properties like `CreateInitialized`) need fully original prose. + - `CefEnvironmentOptions` is `Private Class` but reached via `Public EnvironmentOptions As CefEnvironmentOptions = New CefEnvironmentOptions` on the control — same pattern as WebView2's `EnvironmentOptions`. Document it as the **sub-page** `CefBrowser/EnvironmentOptions.md` (folder-style layout on the parent). Its four fields are bare `Public` (no `Property Get`/`Let` pairs) — list them as properties. + - Settings on `CefEnvironmentOptions` only take effect *before or during* the `Create` event (the source `CreateCEFBrowser` reads them once when launching the helper process); call this out on the sub-page. + - For the two enums: `CefLogSeverity` and `cefPrintOrientation` (note the lowercase `c` on the second — it's declared `Enum cefPrintOrientation` in `BrowserOM.twin`; document it with its source-side spelling). Format pages like `WebView2/Enumerations/wv2PrintOrientation.md` — single intro paragraph, a value table with `{: #cefXxx }` anchors on each row for deep-linking. + - The `cefPrintOrientation` enum is declared *inside* the private `BrowserOM` class but accessed from user code unqualified (`cefPrintOrientation.cefPrintPortrait`) — it surfaces because `CefBrowser.PrintToPdf` exposes it as a parameter type. Don't surface the `BrowserOM` enclosing class; document the enum at top level under `Enumerations/`. + - `NavigationStarting` carries a `RequestHeaders As CefBrowserRequestHeaders` parameter. `CefBrowserRequestHeaders` is `Alias … As Object` (the underlying class is an empty placeholder) — call this out on the event entry as *"currently typed Object; reserved for a future request-headers collection"* and don't link out to a non-existent page. + - `NavigationComplete` carries `IsSuccess` and `WebErrorStatus` — but `OnNavigationComplete_UI` in `CefControl.twin` hard-codes both to placeholder values with `FIXME` comments. Document the signature as designed but add a `> [!NOTE]` saying the values are currently fixed pending implementation. + - WebView2-parity gap list lives on `CEF/index.md` (one bulleted section). Methods / events not yet exposed get **no per-page stub** — they don't exist on `CefBrowser` and have no place to land. + - Multi-version source: the same `.twin` files compile for CEF v49 / v109 / v145 via the `CEF_VERSION` compiler constant. Mention this on `CEF/index.md` together with the runtime download story; reference `CefBrowser.CefMajorVersion` as the runtime-side query. + - Omit the `vba_attribution: true` frontmatter flag — these pages are fully original (the package is MIT-licensed, same as WebView2 / Assert / CustomControls). +9. **Flag tB deviations** with a `> [!NOTE]` callout (see next section). +10. **Update the parent index** (`//index.md`, `docs/Reference/VB/index.md`, `docs/Reference/WebView2/index.md`, `docs/Reference/Assert/index.md`, `docs/Reference/CustomControls/index.md` (and its `Styles/`, `Framework/`, `Enumerations/` sub-indices), `docs/Reference/CEF/index.md` (and its `Enumerations/` sub-index), `Reference/Statements.md`, or `Reference/Procedures and Functions.md`) — turn an unlinked bullet into a link with a short blurb. Match the existing style of the page. If a new package is being added, also extend `docs/Reference/Packages.md` to list it. 11. **Add the page** to `Reference/Statements.md` or `Reference/Procedures and Functions.md` if it's a statement or callable and not already listed there. 12. **Run the [site integrity check](#site-integrity-check)** after the batch and before committing. @@ -520,7 +642,7 @@ When in doubt about a tB-specific behavior, check `docs/Features/` and `docs/Ref ## Scripts and tooling -Any new helper script (backlog reconciliation, content conversion, link checks beyond htmlproofer, etc.) should be written in **Python**. Do not add new Ruby code to this repo. The only Ruby allowed is the existing Jekyll/`just-the-docs` build chain (`Gemfile`, `Gemfile.lock`, `_plugins/`) — that stays as-is. +Any new helper script (content conversion, link checks beyond `check.bat`, etc.) should be written in **Python**. Do not add new Ruby code to this repo. The only Ruby allowed is the existing Jekyll/`just-the-docs` build chain (`Gemfile`, `Gemfile.lock`, `_plugins/`) — that stays as-is. ## Build / preview @@ -528,19 +650,19 @@ From `docs/`: - `bundle exec jekyll build` (or `build.bat`) — build to `_site/`. - `bundle exec jekyll serve` (or `serve.bat`) — local server at `localhost:4000`. -- `bundle exec htmlproofer ./_site --disable-external --no-enforce-https` (or `check.bat`) — link check. See [Site integrity check](#site-integrity-check). +- `check.bat` — link check (offline Lychee against `_site/`). ## Site integrity check -After a batch of changes, verify the site builds clean and all links resolve. From the `docs/` folder, run **exactly** this command: +After a batch of changes, verify the site builds clean and all links resolve. From the `docs/` folder, run: ```sh -bundle exec htmlproofer ./_site --disable-external --no-enforce-https +build.bat && check.bat ``` -Do not add, remove, or substitute flags. This catches broken intra-site links, missing pages, and malformed `redirect_from` entries — the most common breakage when adding new pages or moving content between sections. A clean run is the bar for "ready to commit". +`check.bat` runs Lychee in offline mode against the built `_site/` tree — it catches broken intra-site links, missing pages, and malformed `redirect_from` entries (the most common breakage when adding new pages or moving content between sections). A clean run is the bar for "ready to commit". -Requires a prior `bundle exec jekyll build` so `_site/` is current. +Requires `build.bat` to have produced an up-to-date `_site/`. ## Repository Use diff --git a/docs/Reference/CEF/CefBrowser/EnvironmentOptions.md b/docs/Reference/CEF/CefBrowser/EnvironmentOptions.md new file mode 100644 index 00000000..273bdfd8 --- /dev/null +++ b/docs/Reference/CEF/CefBrowser/EnvironmentOptions.md @@ -0,0 +1,73 @@ +--- +title: EnvironmentOptions +parent: CefBrowser +grand_parent: CEF Package +permalink: /tB/Packages/CEF/CefBrowser/EnvironmentOptions +has_toc: false +--- + +# CefEnvironmentOptions class +{: .no_toc } + +Carries the host's pre-creation configuration for the CEF environment — runtime folder, user-data folder, and the optional debug-log destination. Surfaces on every [**CefBrowser**](.) control as its **EnvironmentOptions** property; the control instantiates one automatically before raising the [**Create**](.#create) event. + +The fields below take effect only while the CEF runtime is being launched — that is, *before or during* the control's [**Create**](.#create) event. Assigning them after that point has no effect on the live environment. + +```tb +Private Sub CefBrowser1_Create() + CefBrowser1.EnvironmentOptions.UserDataFolder = _ + Environ$("APPDATA") & "\MyApp\CEF\" + CefBrowser1.EnvironmentOptions.LogFilePath = _ + Environ$("APPDATA") & "\MyApp\CEF\debug.log" + CefBrowser1.EnvironmentOptions.LogSeverity = CefLogWarning +End Sub +``` + +The type itself is `Private Class` — you reach instances only through the control's **EnvironmentOptions** property and cannot declare a variable typed as **CefEnvironmentOptions** from outside the package. + +## Properties + +### BrowserExecutableFolder +{: .no_toc } + +Path to the folder containing `libcef.dll` and its accompanying runtime files. **String**. Default: empty (the runtime is loaded from `%LocalAppData%\twinBASIC_CEF_Runtime\` — see [Installing runtime files](../#installing-runtime-files)). + +Set this to point at a portable side-by-side deployment, e.g. a CEF folder shipped beside the application executable: + +```tb +Private Sub CefBrowser1_Create() + CefBrowser1.EnvironmentOptions.BrowserExecutableFolder = _ + App.Path & "\cef145_win64" +End Sub +``` + +If `libcef.dll` is not found at the configured (or default) location, the [**Error**](.#error) event fires with the exact path that was searched. + +### LogFilePath +{: .no_toc } + +Path to a writable file CEF will append its debug log to. **String**. Default: empty (no log file is written, regardless of [**LogSeverity**](#logseverity)). + +Used together with [**LogSeverity**](#logseverity) — messages at or above the chosen severity are written to this file. The log is appended across runs; rotate or delete the file as needed. + +### LogSeverity +{: .no_toc } + +The minimum severity at which CEF records messages to the log file named by [**LogFilePath**](#logfilepath). [**CefLogSeverity**](../Enumerations/CefLogSeverity). Default: **CefLogDisable** (logging off). + +Set to **CefLogWarning** or **CefLogError** when investigating runtime issues, and back to **CefLogDisable** for normal use. + +### UserDataFolder +{: .no_toc } + +Path to the folder CEF uses for the user profile — cache, cookies, history, local storage, and so on. **String**. Default: empty (the runtime picks a folder under `%LocalAppData%\twinBASIC_CEF\\`). + +Set a writable, application-specific path when the default would land in a read-only location, or when multiple deployments of the same application must keep their profiles separate. The same folder cannot be opened by two CEF processes simultaneously — if it's already locked, the [**Error**](.#error) event fires with *"CEF cache path already locked by another process"*. + +### See Also + +- [CefBrowser control class](.) +- [Create event](.#create) +- [Installing runtime files](../#installing-runtime-files) +- [Overriding the runtime location](../#overriding-the-runtime-location) +- [WebView2EnvironmentOptions](../../WebView2/WebView2/EnvironmentOptions) -- the WebView2 counterpart diff --git a/docs/Reference/CEF/CefBrowser/index.md b/docs/Reference/CEF/CefBrowser/index.md new file mode 100644 index 00000000..8941f767 --- /dev/null +++ b/docs/Reference/CEF/CefBrowser/index.md @@ -0,0 +1,560 @@ +--- +title: CefBrowser +parent: CEF Package +permalink: /tB/Packages/CEF/CefBrowser/ +has_toc: false +--- + +# CefBrowser class +{: .no_toc } + +A **CefBrowser** is a twinBASIC control that hosts the Chromium Embedded Framework — drop one onto a [**Form**](../../VB/Form/) and Chromium renders web content inside its rectangle. Application code can navigate to URLs, run JavaScript, exchange messages with the loaded page, register virtual-host folders, and print the document to PDF. + +The control spawns a separate browser process the first time it is used in a session and communicates with it across an IPC channel; many properties and methods raise *"CefBrowser control is not ready"* (run-time error 5) when called before the [**Ready**](#ready) event has fired. + +```tb +Private Sub Form_Load() + CefBrowser1.Navigate "https://www.twinbasic.com" +End Sub + +Private Sub CefBrowser1_Ready() + Debug.Print "CEF ready: runtime v" & CefBrowser1.CefMajorVersion +End Sub + +Private Sub CefBrowser1_NavigationComplete( _ + ByVal IsSuccess As Boolean, ByVal WebErrorStatus As Long) + Debug.Print "Navigated to: " & CefBrowser1.DocumentURL +End Sub +``` + +The control inherits the rect-dockable surface (size, layout, **Anchors**, **Dock**) from `BaseControlRectDockable`. It does *not* inherit a focusable layer, so the keyboard / mouse / focus events you might find on [**WebView2**](../../WebView2/WebView2/) are not part of its surface — keystrokes go straight into the page once Chromium has focus. + +* TOC +{:toc} + +## Lifecycle + +A **CefBrowser** control progresses through three distinct phases, each driven by an asynchronous step in the CEF runtime: + +| Event | When | +|--------------------------------|-------------------------------------------------------------------------------------------------------------------| +| [**Create**](#create) | After the container window exists, before the CEF runtime is launched. Last chance to set [**EnvironmentOptions**](#environmentoptions). | +| [**Error**](#error) | The runtime could not be launched — typically because `libcef.dll` is missing or the user-data folder is locked. | +| [**Ready**](#ready) | The browser process is live and IPC has connected. The control is now fully functional. | + +Calling navigation, scripting, or setting accessors before [**Ready**](#ready) raises run-time error 5 with the message *"CefBrowser control is not ready."* Once **Ready** fires, the control auto-navigates to the [**DocumentURL**](#documenturl) field if it has a non-empty value (the design-time default is `https://www.twinbasic.com`). + +The first **CefBrowser** to initialise in a process launches the shared browser helper executable; subsequent **CefBrowser** instances share that helper. Closing the last **CefBrowser** does not terminate the helper — it lingers for the life of the host process so that a future control can attach to it without re-launching. + +## Deferred startup + +By default the control launches the browser helper as soon as the form is loaded (the `WS_VISIBLE` style is set on the host window and the first resize event triggers the helper). Set [**CreateInitialized**](#createinitialized) to **False** before the form loads, then call [**Initialize**](#initialize) at the moment you want the browser to start — useful when several **CefBrowser** controls live on tabs and you want to defer the cost until the tab is shown. + +## JavaScript interop + +The control offers three families of BASIC ↔ JavaScript bridges: + +- **Posting messages** — [**PostWebMessage**](#postwebmessage) sends a value to the page where it surfaces via `window.chrome.webview.addEventListener('message', …)`. The page replies with `window.chrome.webview.postMessage(…)`, which fires the [**JsMessage**](#jsmessage) event. +- **Executing script** — [**JsRun**](#jsrun) calls a named JavaScript function and waits for the result, [**JsRunAsync**](#jsrunasync) calls one and fires [**JsAsyncResult**](#jsasyncresult) when the result arrives, and [**ExecuteScript**](#executescript) fires-and-forgets a snippet without awaiting a result. + +Synchronous [**JsRun**](#jsrun) blocks the BASIC thread until the renderer replies — which means that re-entrancy from the page (a JavaScript handler that posts back into BASIC during the call) can cause a UI freeze. Use [**JsRunAsync**](#jsrunasync) whenever the call is non-trivial. + +## Mapping virtual hostnames + +[**SetVirtualHostNameToFolderMapping**](#setvirtualhostnametofoldermapping) installs a virtual hostname that serves files from a local folder — so the page can `fetch('https://my.app/index.html')` instead of `file:///...` (avoiding the `file://` origin's CORS restrictions). [**ClearVirtualHostNameToFolderMapping**](#clearvirtualhostnametofoldermapping) removes a mapping. + +Properties +---------- + +The control inherits the standard rect-dockable surface from `BaseControlRectDockable` — size, position, **Anchors**, **Dock**, **Container**, the design-time **Name** / **Index** / **Tag**. + +### Anchors +{: .no_toc } + +The container-edge anchors that drive automatic resizing when the parent **Form** is resized. Inherited from `BaseControlRectDockable`. + +### CanGoBack +{: .no_toc } + +Whether the browsing history has an entry behind the current document. **Boolean**. Read-only. Available after [**Ready**](#ready). + +### CanGoForward +{: .no_toc } + +Whether the browsing history has an entry ahead of the current document. **Boolean**. Read-only. Available after [**Ready**](#ready). + +### CefMajorVersion +{: .no_toc } + +The CEF runtime major-version number selected at compile time (`49`, `109`, or `145`). **Long**. Read-only. Resolves from the `CEF_VERSION` conditional-compilation argument on the compiler-package reference — see [Supported runtimes](../#supported-runtimes). + +### Container +{: .no_toc } + +The parent **Form** / **Frame** / **PictureBox** / **UserControl** that hosts this control. **Object**. Inherited. + +### ControlType +{: .no_toc } + +Always **vbCefBrowser** ([**ControlTypeConstants**](../../VBRUN/Constants/ControlTypeConstants)). Read-only. Inherited. + +### CreateInitialized +{: .no_toc } + +Whether the browser helper is launched automatically when the form first lays the control out. **Boolean**. Default: **True**. Set to **False** in code (or in the property sheet) to defer the launch until [**Initialize**](#initialize) is called. + +### DocumentTitle +{: .no_toc } + +The current document's `` text. **String**. Read-only. Updated each time the page changes its title — the [**DocumentTitleChanged**](#documenttitlechanged) event fires on every update. + +### DocumentURL +{: .no_toc } + +The current document's URL. **String**. Reading returns the live URL after every navigation; assigning is equivalent to calling [**Navigate**](#navigate). The design-time default is `https://www.twinbasic.com`, used as the auto-navigation target once [**Ready**](#ready) fires. + +### Dock +{: .no_toc } + +How the control docks against its container. A member of [**DockModeConstants**](../../VBRUN/Constants/DockModeConstants). Inherited. + +### EnvironmentOptions +{: .no_toc } + +The [**CefEnvironmentOptions**](EnvironmentOptions) object that configures the runtime — executable folder, user-data folder, log file, log severity. The control auto-creates one on initialization; assign to its fields before or during the [**Create**](#create) event for them to take effect. + +### Height +{: .no_toc } + +The control's height. **Single**. Inherited. + +### hWnd +{: .no_toc } + +The Win32 window handle of the *container* window that hosts the CEF surface — not the HWND of the Chromium browser tab itself, which lives in a separate process. **LongPtr**. Read-only. + +### Index +{: .no_toc } + +The control-array index when the control is part of an array. **Long**. Read-only. Inherited. + +### Left +{: .no_toc } + +The control's x-position inside its container. **Single**. Inherited. + +### Name +{: .no_toc } + +The design-time name of the control. **String**. Read-only at run time. Inherited. + +### Parent +{: .no_toc } + +The **Form** (or other container) that hosts this control. **Object**. Read-only. + +### Tag +{: .no_toc } + +A user-defined string carried by the control. **String**. Inherited. + +### Top +{: .no_toc } + +The control's y-position inside its container. **Single**. Inherited. + +### UserAgent +{: .no_toc } + +The `User-Agent` string Chromium sends with HTTP requests. **String**. Read/write. The design-time default is empty, in which case Chromium uses its built-in user-agent string. Assigning at run time takes effect immediately. + +### Visible +{: .no_toc } + +Whether the control is visible. **Boolean**, default **True**. + +### Width +{: .no_toc } + +The control's width. **Single**. Inherited. + +### ZoomFactor +{: .no_toc } + +The overall page zoom factor, where `1.0` is 100%. **Double**. Default: `1.0` (design-time default; reads as `0.0` until the browser is [**Ready**](#ready)). + +> [!NOTE] +> Because the value reads as `0.0` until the browser is ready, arithmetic that multiplies the current value silently starts from zero unless the host clamps it to `1` first: +> +> ```tb +> If CefBrowser1.ZoomFactor = 0 Then CefBrowser1.ZoomFactor = 1 +> CefBrowser1.ZoomFactor *= 1.1 ' 110% on first click, 121% on second, … +> ``` + +Methods +------- + +### ClearVirtualHostNameToFolderMapping +{: .no_toc } + +Removes a virtual hostname → local-folder mapping previously installed by [**SetVirtualHostNameToFolderMapping**](#setvirtualhostnametofoldermapping). + +Syntax: *object*.**ClearVirtualHostNameToFolderMapping** *hostName* + +*hostName* +: *required* A **String** matching the hostname passed to **SetVirtualHostNameToFolderMapping**. + +### ExecuteScript +{: .no_toc } + +Evaluates JavaScript in the page without waiting for it to finish and without surfacing its result. Use [**JsRun**](#jsrun) or [**JsRunAsync**](#jsrunasync) when you need the value. + +Syntax: *object*.**ExecuteScript** *jsCode* + +*jsCode* +: *required* A **String** of JavaScript to evaluate in the page's global scope. + +### GoBack +{: .no_toc } + +Navigates one entry back in the browsing history. Silently does nothing when [**CanGoBack**](#cangoback) is **False**. + +Syntax: *object*.**GoBack** + +### GoForward +{: .no_toc } + +Navigates one entry forward in the browsing history. Silently does nothing when [**CanGoForward**](#cangoforward) is **False**. + +Syntax: *object*.**GoForward** + +### Initialize +{: .no_toc } + +Launches the browser helper process explicitly. Only needed when [**CreateInitialized**](#createinitialized) is **False**; otherwise the helper starts automatically on the first form-layout pass. + +Syntax: *object*.**Initialize** + +A second call after the helper is already running is a no-op. + +### JsRun +{: .no_toc } + +Calls a named JavaScript function with the given arguments and returns the result synchronously. Blocks the BASIC thread until the renderer replies. + +Syntax: *object*.**JsRun** ( *FuncName*, [ *args* ] ) **As Variant** + +*FuncName* +: *required* A **String** naming the JavaScript function — e.g. `"document.querySelector"`. + +*args* +: *optional* Any number of **Variant** arguments. Each is JSON-encoded before being passed to the function. + +```tb +' Calls the page-side function `multiplyTheseNumbers(a, b)` and waits for the result. +Dim product As Long = CefBrowser1.JsRun("multiplyTheseNumbers", 5, 6) +Debug.Print product ' 30 +``` + +> [!WARNING] +> A page-side handler that posts back into BASIC during the call can deadlock the UI. Prefer [**JsRunAsync**](#jsrunasync) for non-trivial calls. See the [Re-entrancy tutorial](../../../../Tutorials/CEF/Re-entrancy) for the full discussion. + +### JsRunAsync +{: .no_toc } + +Calls a named JavaScript function asynchronously and returns immediately. When the result arrives, [**JsAsyncResult**](#jsasyncresult) fires carrying the result and an error string. + +Syntax: *object*.**JsRunAsync** *FuncName*, [ *args* ] + +*FuncName* +: *required* A **String** naming the JavaScript function. + +*args* +: *optional* Any number of **Variant** arguments, JSON-encoded as in [**JsRun**](#jsrun). + +```tb +Private Sub btnRun_Click() + CefBrowser1.JsRunAsync "multiplyTheseNumbers", 5, 6 +End Sub + +Private Sub CefBrowser1_JsAsyncResult( _ + ByVal Result As Variant, Token As LongLong, ErrString As String) + If LenB(ErrString) = 0 Then + Debug.Print "Async result: "; Result + Else + Debug.Print "Async error: "; ErrString + End If +End Sub +``` + +If **JsRunAsync** is called before the renderer IPC has connected, the call is queued and dispatched once the connection comes up. + +### Move +{: .no_toc } + +Repositions and resizes the control in a single call. Inherited. + +Syntax: *object*.**Move** *Left* [, *Top* [, *Width* [, *Height* ] ] ] + +### Navigate +{: .no_toc } + +Loads a URL into the browser. Fires [**NavigationStarting**](#navigationstarting) and then [**NavigationComplete**](#navigationcomplete). The URI must include the protocol prefix (`http://`, `https://`, `file://`, …) — there is no automatic prefix insertion. + +Syntax: *object*.**Navigate** *uri* + +*uri* +: *required* A **String** with the full URI to load. + +### NavigateToString +{: .no_toc } + +Loads the given HTML string as if it had been served from `about:blank`. Fires [**NavigationComplete**](#navigationcomplete) when the document is fully loaded. + +Syntax: *object*.**NavigateToString** *html* + +*html* +: *required* A **String** containing a full HTML document. + +### OpenDevToolsWindow +{: .no_toc } + +Opens the Chromium DevTools window for the currently loaded page in a separate top-level window. + +Syntax: *object*.**OpenDevToolsWindow** + +### PostWebMessage +{: .no_toc } + +Sends a value to the page; it surfaces via `window.chrome.webview.addEventListener('message', …)`. The page can reply with `window.chrome.webview.postMessage(…)`, which fires the [**JsMessage**](#jsmessage) event. + +Syntax: *object*.**PostWebMessage** *Message* + +*Message* +: *required* A **Variant** carrying the value to send. Strings, numbers, **Boolean**, **Null**, and **Empty** are JSON-encoded for the page; objects and arrays are not currently supported. + +If **PostWebMessage** is called before the renderer IPC has connected, the call is queued and dispatched once the connection comes up. + +### PrintToPdf +{: .no_toc } + +Writes the current document to a PDF file. Fires [**PrintToPdfCompleted**](#printtopdfcompleted) on success or [**PrintToPdfFailed**](#printtopdffailed) on failure. + +Syntax: *object*.**PrintToPdf** *outputPath* [, *Orientation* [, *ScaleFactor* [, *PageWidth* [, *PageHeight* [, *MarginTop* [, *MarginBottom* [, *MarginLeft* [, *MarginRight* [, *ShouldPrintBackgrounds* [, *ShouldPrintSelectionOnly* [, *ShouldPrintHeaderAndFooter* [, *HeaderTitle* [, *FooterUri* ] ] ] ] ] ] ] ] ] ] ] ] ] + +*outputPath* +: *required* A **String** with the destination path. Must be a writable absolute file path. An existing file is overwritten. + +*Orientation* +: *optional* A member of [**cefPrintOrientation**](../Enumerations/cefPrintOrientation). Default: **cefPrintPortrait**. + +*ScaleFactor* +: *optional* A **Variant** containing the print scaling factor (e.g. `1.0` for 100%). When omitted, the CEF runtime's default is used. + +*PageWidth* +: *optional* A **Variant** with the page width in microns. When omitted, the CEF runtime's default is used. + +*PageHeight* +: *optional* A **Variant** with the page height in microns. When omitted, the CEF runtime's default is used. + +*MarginTop* / *MarginBottom* / *MarginLeft* / *MarginRight* +: *optional* **Variant** values for the page margins in microns. When omitted, the runtime's defaults are used. + +*ShouldPrintBackgrounds* +: *optional* A **Boolean** controlling whether CSS background colors and images are included in the output. Default: **False**. + +*ShouldPrintSelectionOnly* +: *optional* A **Boolean** that limits the output to the current selection. Default: **False**. + +*ShouldPrintHeaderAndFooter* +: *optional* A **Boolean** controlling whether the page header (title) and footer (URL) are rendered. Default: **True**. + +*HeaderTitle* +: *optional* A **Variant** **String**. When provided, overrides the document title in the header. Otherwise the document's `<title>` is used. + +*FooterUri* +: *optional* A **Variant** **String**. When provided, overrides the URL printed in the footer. Otherwise the live document URL is used. + +```tb +Private Sub btnPDF_Click() + Dim outputPath As String + outputPath = Environ$("USERPROFILE") & "\Documents\cefDemo.pdf" + CefBrowser1.PrintToPdf outputPath +End Sub + +Private Sub CefBrowser1_PrintToPdfCompleted() + MsgBox "PDF saved.", vbInformation +End Sub +``` + +### Reload +{: .no_toc } + +Reloads the current document, equivalent to pressing **F5** in the browser. + +Syntax: *object*.**Reload** + +### SetVirtualHostNameToFolderMapping +{: .no_toc } + +Installs a virtual hostname that serves files from a local folder, so the page can reference local content over an `https://` origin instead of `file://`. + +Syntax: *object*.**SetVirtualHostNameToFolderMapping** *hostName*, *folderPath* + +*hostName* +: *required* A **String** with the hostname to install (e.g. `"my.app"`). + +*folderPath* +: *required* A **String** with the absolute path of the folder whose contents should be served under that hostname. Must end with a trailing path separator. + +```tb +Private Sub CefBrowser1_Ready() + CefBrowser1.SetVirtualHostNameToFolderMapping _ + "my.app", App.Path & "\web\" + CefBrowser1.Navigate "https://my.app/index.html" +End Sub +``` + +Events +------ + +### Create +{: .no_toc } + +Raised after the container window exists but before the CEF runtime is launched. The host's last chance to populate [**EnvironmentOptions**](#environmentoptions). + +Syntax: *object*\_**Create**( ) + +### DocumentTitleChanged +{: .no_toc } + +Raised when the document changes its title — typically right after a navigation, but also when client-side JavaScript writes to `document.title`. Read [**DocumentTitle**](#documenttitle) for the new value. + +Syntax: *object*\_**DocumentTitleChanged**( ) + +### DOMContentLoaded +{: .no_toc } + +Raised when the page reaches the `DOMContentLoaded` lifecycle event — the DOM tree is built and JavaScript can safely walk it, but external resources may still be loading. + +Syntax: *object*\_**DOMContentLoaded**( ) + +### Error +{: .no_toc } + +Raised when the CEF runtime fails to launch — most commonly because `libcef.dll` was not found at the configured location, or because the user-data folder is locked by another process. + +Syntax: *object*\_**Error**( *code* **As Long**, *msg* **As String** ) + +```tb +Private Sub CefBrowser1_Error(ByVal code As Long, ByVal msg As String) + MsgBox "CEF error " & Hex$(code) & ": " & msg, vbExclamation, "CEF" +End Sub +``` + +### JsAsyncResult +{: .no_toc } + +Raised when an earlier [**JsRunAsync**](#jsrunasync) call returns. *ErrString* is a description of any runtime error, or an empty string on success. + +Syntax: *object*\_**JsAsyncResult**( *Result* **As Variant**, *Token* **As LongLong**, *ErrString* **As String** ) + +### JsMessage +{: .no_toc } + +Raised when JavaScript on the page calls `window.chrome.webview.postMessage(value)`. + +Syntax: *object*\_**JsMessage**( *Message* **As Variant** ) + +```tb +Private Sub CefBrowser1_JsMessage(ByVal Message As Variant) + Debug.Print "From page: "; Message + CefBrowser1.PostWebMessage "Hello from BASIC" +End Sub +``` + +### NavigationComplete +{: .no_toc } + +Raised after a navigation initiated by [**Navigate**](#navigate), [**NavigateToString**](#navigatetostring), or by user interaction in the page has finished. + +Syntax: *object*\_**NavigationComplete**( *IsSuccess* **As Boolean**, *WebErrorStatus* **As Long** ) + +> [!NOTE] +> *IsSuccess* and *WebErrorStatus* are part of the event signature but currently return placeholder values (`True` and `0`) — the underlying CEF callbacks that would populate them have not yet been wired in. Use the document state ([**DocumentURL**](#documenturl), [**CanGoBack**](#cangoback)) to determine the outcome. + +### NavigationStarting +{: .no_toc } + +Raised before a navigation begins. Set *Cancel* to **True** to abort the navigation; leave it **False** to let it proceed. + +Syntax: *object*\_**NavigationStarting**( *Uri* **As String**, *IsUserInitiated* **As Boolean**, *IsRedirected* **As Boolean**, *RequestHeaders* **As Object**, *Cancel* **As Boolean** ) + +*Uri* +: The destination URI. + +*IsUserInitiated* +: **True** when the navigation was triggered by a user gesture (click, **Enter** in the address bar); **False** when it was script-initiated. + +*IsRedirected* +: **True** when this navigation is a server-side redirect from a previous one. + +*RequestHeaders* +: **Object**. Currently typed as **Object** (the underlying `CefRequestHeaders` collection is a placeholder reserved for future use). + +*Cancel* +: Set to **True** to abort the navigation. + +```tb +Private Sub CefBrowser1_NavigationStarting( _ + ByVal Uri As String, ByVal IsUserInitiated As Boolean, _ + ByVal IsRedirected As Boolean, ByVal RequestHeaders As Object, _ + Cancel As Boolean) + If InStr(Uri, "ads.example.com") > 0 Then Cancel = True +End Sub +``` + +### PrintToPdfCompleted +{: .no_toc } + +Raised when an earlier [**PrintToPdf**](#printtopdf) call finishes writing the PDF. + +Syntax: *object*\_**PrintToPdfCompleted**( ) + +### PrintToPdfFailed +{: .no_toc } + +Raised when an earlier [**PrintToPdf**](#printtopdf) call fails — e.g. because the output path was not writable. + +Syntax: *object*\_**PrintToPdfFailed**( ) + +### Ready +{: .no_toc } + +Raised after the browser helper process has launched, its IPC channel is connected, and the control is ready to accept navigation and scripting commands. If [**DocumentURL**](#documenturl) has a non-empty value when **Ready** fires (the design-time default is `https://www.twinbasic.com`), the control auto-navigates to it. + +Syntax: *object*\_**Ready**( ) + +### SourceChanged +{: .no_toc } + +Raised when [**DocumentURL**](#documenturl) has been updated — typically after a navigation. Used to keep an address-bar control in sync with the browser. + +Syntax: *object*\_**SourceChanged**( *IsNewDocument* **As Boolean** ) + +*IsNewDocument* +: **True** when the change reflects a fresh document load (rather than a same-document fragment / `history.pushState` update). + +```tb +Private Sub CefBrowser1_SourceChanged(ByVal IsNewDocument As Boolean) + AddressBar.Text = CefBrowser1.DocumentURL +End Sub +``` + +### See Also + +- [CefEnvironmentOptions](EnvironmentOptions) -- pre-creation configuration carried by [**EnvironmentOptions**](#environmentoptions) +- [CefLogSeverity](../Enumerations/CefLogSeverity), [cefPrintOrientation](../Enumerations/cefPrintOrientation) -- the package's two user-facing enumerations +- [WebView2](../../WebView2/WebView2/) -- the WebView2-runtime counterpart with a larger feature surface +- [WebView2 parity](../#webview2-parity) -- features available on **WebView2** that are not yet exposed on **CefBrowser** +- [ControlTypeConstants](../../VBRUN/Constants/ControlTypeConstants) -- where **vbCefBrowser** lives diff --git a/docs/Reference/CEF/Enumerations/CefLogSeverity.md b/docs/Reference/CEF/Enumerations/CefLogSeverity.md new file mode 100644 index 00000000..86dcacf9 --- /dev/null +++ b/docs/Reference/CEF/Enumerations/CefLogSeverity.md @@ -0,0 +1,19 @@ +--- +title: CefLogSeverity +parent: Enumerations +grand_parent: CEF Package +permalink: /tB/Packages/CEF/Enumerations/CefLogSeverity +--- +# CefLogSeverity +{: .no_toc } + +The minimum severity at which the CEF runtime records messages to its debug log. Assigned to [**EnvironmentOptions.LogSeverity**](../CefBrowser/EnvironmentOptions#logseverity) before or during the [**Create**](../CefBrowser/#create) event; messages below the chosen level are discarded, messages at or above it are written to the file named by [**LogFilePath**](../CefBrowser/EnvironmentOptions#logfilepath). + +| Constant | Value | Description | +|----------|-------|-------------| +| **CefLogDisable**{: #cefLogDisable } | 0 | Default — logging is disabled. | +| **CefLogVerbose**{: #cefLogVerbose } | 1 | All messages, including verbose tracing. | +| **CefLogInfo**{: #cefLogInfo } | 2 | Informational messages and above. | +| **CefLogWarning**{: #cefLogWarning } | 3 | Warnings and above. | +| **CefLogError**{: #cefLogError } | 4 | Errors and above. | +| **CefLogFatal**{: #cefLogFatal } | 5 | Fatal errors only. | diff --git a/docs/Reference/CEF/Enumerations/cefPrintOrientation.md b/docs/Reference/CEF/Enumerations/cefPrintOrientation.md new file mode 100644 index 00000000..9030eb89 --- /dev/null +++ b/docs/Reference/CEF/Enumerations/cefPrintOrientation.md @@ -0,0 +1,15 @@ +--- +title: cefPrintOrientation +parent: Enumerations +grand_parent: CEF Package +permalink: /tB/Packages/CEF/Enumerations/cefPrintOrientation +--- +# cefPrintOrientation +{: .no_toc } + +Page orientation passed to [**PrintToPdf**](../CefBrowser/#printtopdf) when writing the current document to a PDF file. + +| Constant | Value | Description | +|----------|-------|-------------| +| **cefPrintPortrait**{: #cefPrintPortrait } | 0 | Default — pages are laid out with the long side vertical. | +| **cefPrintLandscape**{: #cefPrintLandscape } | 1 | Pages are laid out with the long side horizontal. | diff --git a/docs/Reference/CEF/Enumerations/index.md b/docs/Reference/CEF/Enumerations/index.md new file mode 100644 index 00000000..55a7933e --- /dev/null +++ b/docs/Reference/CEF/Enumerations/index.md @@ -0,0 +1,15 @@ +--- +title: Enumerations +parent: CEF Package +permalink: /tB/Packages/CEF/Enumerations/ +has_toc: false +--- + +# Enumerations + +The two user-facing enumerations the **CEF** package exposes. The package's larger surface of internal `cef_*_t` enums (mirroring the CEF C API) lives in `Private Module` wrappers and is not part of the public API. + +| Enumeration | Used by | +|-------------|---------| +| [CefLogSeverity](CefLogSeverity) | [**EnvironmentOptions.LogSeverity**](../CefBrowser/EnvironmentOptions#logseverity) | +| [cefPrintOrientation](cefPrintOrientation) | `Orientation` on [**PrintToPdf**](../CefBrowser/#printtopdf) | diff --git a/docs/Reference/CEF/index.md b/docs/Reference/CEF/index.md new file mode 100644 index 00000000..a8c9c408 --- /dev/null +++ b/docs/Reference/CEF/index.md @@ -0,0 +1,128 @@ +--- +title: CEF Package +parent: Packages +grand_parent: Reference Section +nav_order: 6 +permalink: /tB/Packages/CEF/ +has_toc: false +--- + +# CEF Package +{: .no_toc } + +The **cefPackage** wraps the [Chromium Embedded Framework](https://chromiumembedded.github.io/cef/) and exposes it as an ordinary twinBASIC control. Drop a [**CefBrowser**](CefBrowser/) onto a form and a Chromium browser renders web content inside it — navigate to URLs, run JavaScript, print pages to PDF, and exchange messages with the loaded page. + +The package is a built-in package shipped with twinBASIC, but the CEF runtime itself is distributed *separately* — applications must ship the matching runtime ZIP alongside the executable. See [Runtime files](#runtime-files) below. + +> [!IMPORTANT] +> The CEF package is currently in **BETA**. Several features available on [**WebView2**](../WebView2/) are not yet exposed; see [WebView2 parity](#webview2-parity) below. + +* TOC +{:toc} + +## Why CEF instead of WebView2? + +CEF and [**WebView2**](../WebView2/) both wrap a Chromium-based browser inside a twinBASIC control. CEF brings advantages that matter for some applications: + +- **Cross-platform ready.** CEF runs on Windows, Linux, and macOS. [**WebView2**](../WebView2/) is Windows-only. +- **Full control over the runtime stack.** The application targets a specific Chromium build and distributes it alongside the software. There is no runtime auto-update behind the developer's back, so behavior stays consistent across deployments. +- **Deeper runtime integration.** CEF allows hosting twinBASIC code inside the renderer / JavaScript process — something the locked-down WebView2 object model cannot do. + +Choose [**WebView2**](../WebView2/) when targeting only modern Windows and willing to rely on the system-installed Edge runtime; choose **CEF** when control over the Chromium version or cross-platform readiness matters. + +## Supported runtimes + +Three CEF versions are supported, each carrying a different Chromium baseline and different OS reach: + +| Runtime version | Supported OS | Notes | +|-----------------|---------------|----------------------------------------------------------------| +| **v49** | Windows XP+ | Last Chromium version that supports Windows XP. | +| **v109** | Windows 7+ | Last Chromium version that supports Windows 7. | +| **v145** | Windows 10+ | Recommended modern runtime. | + +> [!WARNING] +> Older Chromium versions should not generally be used for unrestricted internet browsing — they carry unpatched security vulnerabilities. They remain appropriate for tightly controlled environments where the browser loads only trusted local or internal content. + +The user picks a runtime in two places that must agree: + +- **At compile time** — by adding the matching `[COMPILER PACKAGE] twinBASIC - Chromium Embedded Framework Package v<N>` reference to the project. This sets the `CEF_VERSION` conditional-compilation constant (49, 109, or 145) that the package's own sources compile against. [**CefBrowser.CefMajorVersion**](CefBrowser/#cefmajorversion) returns this value at run time. +- **At deploy time** — by shipping the matching runtime ZIP, extracted into [the discovery folder](#installing-runtime-files) or pointed at via [**EnvironmentOptions.BrowserExecutableFolder**](CefBrowser/EnvironmentOptions#browserexecutablefolder). + +The runtime bitness must match the application bitness — a 32-bit application needs the 32-bit runtime ZIP, a 64-bit application needs the 64-bit ZIP. + +## Runtime files + +The runtime ships separately from the package. Download the ZIP that matches both the CEF version and the application bitness: + +| Version | Win32 | Win64 | +| ------- | ------------------------------------------------------------ | ------------------------------------------------------------ | +| v49 | [cefRuntime49_win32.zip](https://github.com/twinbasic/cef-runtimes/releases/download/v1.0.0/cefRuntime49_win32.zip) | [cefRuntime49_win64.zip](https://github.com/twinbasic/cef-runtimes/releases/download/v1.0.0/cefRuntime49_win64.zip) | +| v109 | [cefRuntime109_win32.zip](https://github.com/twinbasic/cef-runtimes/releases/download/v1.0.0/cefRuntime109_win32.zip) | [cefRuntime109_win64.zip](https://github.com/twinbasic/cef-runtimes/releases/download/v1.0.0/cefRuntime109_win64.zip) | +| v145 | [cefRuntime145_win32.zip](https://github.com/twinbasic/cef-runtimes/releases/download/v1.0.0/cefRuntime145_win32.zip) | [cefRuntime145_win64.zip](https://github.com/twinbasic/cef-runtimes/releases/download/v1.0.0/cefRuntime145_win64.zip) | + +See also [CEF Runtime Releases](https://github.com/twinbasic/cef-runtimes/releases/) for the latest release. + +### Installing runtime files + +{: .no_toc } + +Extract the ZIP into: + +```text +%LocalAppData%\twinBASIC_CEF_Runtime\ +``` + +For example, the v145 Win64 runtime ends up at: + +```text +%LocalAppData%\twinBASIC_CEF_Runtime\145_0_7632_160_Win64\ +``` + +The version-stamped folder must contain `libcef.dll` and its sibling runtime files. + +At launch, [**CefBrowser**](CefBrowser/) searches for the runtime in this default location. If `libcef.dll` cannot be found, the [**Error**](CefBrowser/#error) event fires with the exact path that was searched. + +### Overriding the runtime location +{: .no_toc } + +To point at a different folder — for example a portable side-by-side deployment — assign [**EnvironmentOptions.BrowserExecutableFolder**](CefBrowser/EnvironmentOptions#browserexecutablefolder) before or during the [**Create**](CefBrowser/#create) event: + +```tb +Private Sub CefBrowser1_Create() + CefBrowser1.EnvironmentOptions.BrowserExecutableFolder = _ + "D:\MyApp\CEF\145_0_7632_160_Win64" +End Sub +``` + +The folder must contain `libcef.dll`. + +## WebView2 parity + +These [**WebView2**](../WebView2/) features are not yet exposed on **CefBrowser** and have no documented counterpart: + +- Methods: **OpenTaskManagerWindow**, **AddObject** (host-object publication for JavaScript), **AddWebResourceRequestedFilter** and the surrounding request-interception machinery. +- Events: **AcceleratorKeyPressed**, **PermissionRequested**, **WebResourceRequested**, **ProcessFailed**, **ScriptDialogOpening**, **UserContextMenu**, **SuspendCompleted**, **SuspendFailed**, **DownloadStarting**, **NewWindowRequested**. + +The [**NavigationComplete**](CefBrowser/#navigationcomplete) event carries **IsSuccess** and **WebErrorStatus** parameters in its signature but currently returns placeholder values (`True` and `0`) — the underlying CEF callbacks that would populate them have not yet been wired in. + +The surface will continue to grow; treat this list as a snapshot of the current beta and not a long-term limitation. + +## Classes + +- [CefBrowser](CefBrowser/) -- the control: navigation, scripting, virtual-host mapping, PDF printing, and lifecycle events driven by the matching CEF runtime +- [CefEnvironmentOptions](CefBrowser/EnvironmentOptions) -- pre-creation configuration for the CEF environment (executable folder, user-data folder, log file, log severity); reached via the control's **EnvironmentOptions** property + +## Enumerations + +- [CefLogSeverity](Enumerations/CefLogSeverity) -- the verbosity threshold for the CEF debug log; carried by [**EnvironmentOptions.LogSeverity**](CefBrowser/EnvironmentOptions#logseverity) +- [cefPrintOrientation](Enumerations/cefPrintOrientation) -- page orientation passed to [**PrintToPdf**](CefBrowser/#printtopdf) + +## Tutorials + +- [Getting started](../../../Tutorials/CEF/Getting-Started) -- package reference, runtime download, install path +- [Customize the UserDataFolder](../../../Tutorials/CEF/Customize-UserDataFolder) -- relocating the runtime's working folder +- [Re-entrancy](../../../Tutorials/CEF/Re-entrancy) -- the deferred-event model and the one place ([**JsRun**](CefBrowser/#jsrun)) you still have to think about it +- [Building a browser shell](../../../Tutorials/CEF/Building-A-Browser-Shell) -- back / forward / reload / zoom / PDF +- [Hosting local web assets](../../../Tutorials/CEF/Hosting-Local-Web-Assets) -- virtual-host folder mappings +- [JavaScript interop](../../../Tutorials/CEF/JavaScript-Interop) -- messages and scripted calls between BASIC and the page +- [Driving Monaco from twinBASIC](../../../Tutorials/CEF/Driving-Monaco) -- case study combining everything above diff --git a/docs/Reference/Packages.md b/docs/Reference/Packages.md index 91febe3a..fe9620fb 100644 --- a/docs/Reference/Packages.md +++ b/docs/Reference/Packages.md @@ -24,4 +24,5 @@ These packages are built into twinBASIC and are always available, even offline. - [Assert Package](Assert/) -- assertion functions for unit tests — three modules (**Exact**, **Strict**, **Permissive**) sharing the same fifteen-member API with different comparison strictness - [CustomControls Package](CustomControls/) -- owner-drawn `Waynes…` custom controls (button, form, frame, grid, label, slider, textbox, timer), the shared `Styles/` helpers that paint them, and the DESIGNER framework (interfaces, callback objects, **Canvas**, **SerializeInfo**) for authoring new custom controls +- [CEF Package](CEF/) -- the **CefBrowser** control wrapping the Chromium Embedded Framework: cross-platform-ready browser embedding with a choice of three Chromium runtimes (v49 / v109 / v145); currently in BETA - [WebView2 Package](WebView2/) -- the **WebView2** control wrapping the Microsoft Edge runtime, plus its surrounding wrapper objects (request / response / headers / environment options) and the `wv2…` enumerations diff --git a/docs/Reference/VBRUN/Constants/ControlTypeConstants.md b/docs/Reference/VBRUN/Constants/ControlTypeConstants.md index 35c87d70..950b79f8 100644 --- a/docs/Reference/VBRUN/Constants/ControlTypeConstants.md +++ b/docs/Reference/VBRUN/Constants/ControlTypeConstants.md @@ -48,3 +48,4 @@ Identifiers for the standard intrinsic and bundled control types, used by runtim | **vbReport**{: #vbReport } | 34 | A report (data-report) control. | | **vbCheckMark**{: #vbCheckMark } | 35 | A check-mark control. | | **vbTwinBridge**{: #vbTwinBridge } | 36 | A TwinBridge interop wrapper (twinBASIC). | +| **vbCefBrowser**{: #vbCefBrowser } | 37 | A [**CefBrowser**](../../CEF/CefBrowser/) control. | diff --git a/docs/Reference/WebView2/index.md b/docs/Reference/WebView2/index.md index a5d47707..a1ddd4f3 100644 --- a/docs/Reference/WebView2/index.md +++ b/docs/Reference/WebView2/index.md @@ -2,7 +2,7 @@ title: WebView2 Package parent: Packages grand_parent: Reference Section -nav_order: 6 +nav_order: 7 permalink: /tB/Packages/WebView2/ has_toc: false --- @@ -42,3 +42,13 @@ Beyond the control itself, the package exposes a small set of wrapper objects th ## Types - [COREWEBVIEW2_PHYSICAL_KEY_STATUS](Types/COREWEBVIEW2_PHYSICAL_KEY_STATUS) -- decoded `WM_KEYDOWN` / `WM_KEYUP` `lParam` bit-fields; surfaced via the **AcceleratorKeyPressed** event + +## Tutorials + +- [Getting started](../../../Tutorials/WebView2/Getting-Started) -- adding the package references and dropping a control onto a form +- [Customize the UserDataFolder](../../../Tutorials/WebView2/Customize-UserDataFolder) -- relocating the runtime's working folder for hosted scenarios (Office add-ins, kiosk installs) +- [Re-entrancy](../../../Tutorials/WebView2/Re-entrancy) -- what the control's deferred-event machinery does for you, and the **AddObject** synchronous-vs-deferred trade-off +- [Building a browser shell](../../../Tutorials/WebView2/Building-A-Browser-Shell) -- address bar, back / forward / reload, zoom, PDF export +- [Hosting local web assets](../../../Tutorials/WebView2/Hosting-Local-Web-Assets) -- serve HTML / JS / CSS from a project resource folder, without an HTTP server +- [JavaScript interop](../../../Tutorials/WebView2/JavaScript-Interop) -- the three bridges between BASIC and the page: host objects, messages, and scripted calls +- [Driving Monaco from twinBASIC](../../../Tutorials/WebView2/Driving-Monaco) -- case study combining everything above diff --git a/docs/Tutorials/CEF/Building a browser shell.md b/docs/Tutorials/CEF/Building a browser shell.md new file mode 100644 index 00000000..41803348 --- /dev/null +++ b/docs/Tutorials/CEF/Building a browser shell.md @@ -0,0 +1,134 @@ +--- +title: Building a browser shell +parent: CEF +grand_parent: Tutorials +nav_order: 4 +permalink: /Tutorials/CEF/Building-A-Browser-Shell +--- + +# Building a browser shell + +A short worked tutorial: turn a [**CefBrowser**](../../tB/Packages/CEF/CefBrowser/) control into a working browser with an address bar, back / forward / reload buttons, zoom, and a few helpers (DevTools, PDF export). + +The complete project ships as *Sample 1b — Chromium Embedded Framework Examples* in the New-Project dialog (form *Example 1*). This tutorial walks through its key pieces. + +## The form + +Drop a [**CefBrowser**](../../tB/Packages/CEF/CefBrowser/) control onto a Form and rename it `WebView`. Around it, add a `TextBox` named `AddressBar` plus six `CommandButton`s — `btnBack`, `btnForward`, `btnRefresh`, `btnZoomIn`, `btnZoomOut`, `btnPDF`, `btnDevTools`. + +## Navigating + +The bare-bones navigation surface — [**Navigate**](../../tB/Packages/CEF/CefBrowser/#navigate), [**GoBack**](../../tB/Packages/CEF/CefBrowser/#goback), [**GoForward**](../../tB/Packages/CEF/CefBrowser/#goforward), [**Reload**](../../tB/Packages/CEF/CefBrowser/#reload) — is one-liners: + +```tb +Private Sub btnBack_Click() Handles btnBack.Click + WebView.GoBack() +End Sub + +Private Sub btnForward_Click() Handles btnForward.Click + WebView.GoForward() +End Sub + +Private Sub btnRefresh_Click() Handles btnRefresh.Click + WebView.Reload() +End Sub +``` + +To make the back / forward buttons follow the actual history state, sync them against [**CanGoBack**](../../tB/Packages/CEF/CefBrowser/#cangoback) and [**CanGoForward**](../../tB/Packages/CEF/CefBrowser/#cangoforward) after every navigation: + +```tb +Private Sub WebView_NavigationComplete( _ + ByVal IsSuccess As Boolean, ByVal WebErrorStatus As Long) _ + Handles WebView.NavigationComplete + btnBack.Enabled = WebView.CanGoBack + btnForward.Enabled = WebView.CanGoForward +End Sub +``` + +> [!NOTE] +> *IsSuccess* and *WebErrorStatus* are part of the event signature but currently return placeholder values (`True` and `0`) — use [**DocumentURL**](../../tB/Packages/CEF/CefBrowser/#documenturl) to confirm where the browser actually landed. + +## The address bar + +Pressing **Enter** in the address bar triggers a navigation. The reverse direction — keeping the visible URL in sync with the page — is the [**SourceChanged**](../../tB/Packages/CEF/CefBrowser/#sourcechanged) event, which fires whenever [**DocumentURL**](../../tB/Packages/CEF/CefBrowser/#documenturl) changes (including same-document `history.pushState` updates): + +```tb +Private Sub AddressBar_KeyDown(KeyCode As Integer, Shift As Integer) _ + Handles AddressBar.KeyDown + If KeyCode = vbKeyReturn Then WebView.Navigate AddressBar.Text +End Sub + +Private Sub WebView_SourceChanged(ByVal IsNewDocument As Boolean) _ + Handles WebView.SourceChanged + AddressBar.Text = WebView.DocumentURL +End Sub +``` + +[**Navigate**](../../tB/Packages/CEF/CefBrowser/#navigate) requires a full URI with scheme — `http://`, `https://`, `file://`, … Unlike [**WebView2**](../../tB/Packages/WebView2/WebView2/#navigate), no automatic `https://` prefix is added when the scheme is missing. + +## Zoom + +[**ZoomFactor**](../../tB/Packages/CEF/CefBrowser/#zoomfactor) is a **Double** — `1.0` is 100%, `1.5` is 150%. The value reads as `0` until the browser has reached [**Ready**](../../tB/Packages/CEF/CefBrowser/#ready), so arithmetic that multiplies the current value silently starts from zero unless you clamp first: + +```tb +Private Sub btnZoomIn_Click() Handles btnZoomIn.Click + If WebView.ZoomFactor = 0 Then WebView.ZoomFactor = 1 + On Error Resume Next + WebView.ZoomFactor *= 1.1 +End Sub + +Private Sub btnZoomOut_Click() Handles btnZoomOut.Click + If WebView.ZoomFactor = 0 Then WebView.ZoomFactor = 1 + On Error Resume Next + WebView.ZoomFactor /= 1.1 +End Sub +``` + +The `On Error Resume Next` catches the "control not ready" error that fires when the button is clicked before [**Ready**](../../tB/Packages/CEF/CefBrowser/#ready) has fired. + +## PDF export + +[**PrintToPdf**](../../tB/Packages/CEF/CefBrowser/#printtopdf) saves the current document to disk asynchronously — the result surfaces as [**PrintToPdfCompleted**](../../tB/Packages/CEF/CefBrowser/#printtopdfcompleted) or [**PrintToPdfFailed**](../../tB/Packages/CEF/CefBrowser/#printtopdffailed): + +```tb +Private Sub btnPDF_Click() Handles btnPDF.Click + Dim outputPath As String = _ + Environ$("USERPROFILE") & "\Documents\page.pdf" + WebView.PrintToPdf(outputPath) +End Sub + +Private Sub WebView_PrintToPdfCompleted() Handles WebView.PrintToPdfCompleted + MsgBox "PDF saved.", vbInformation +End Sub +``` + +The optional parameters that follow *outputPath* — [**cefPrintOrientation**](../../tB/Packages/CEF/Enumerations/cefPrintOrientation), page size in microns, margins, header/footer toggles — let the host override Chromium's defaults. See the [**PrintToPdf** reference](../../tB/Packages/CEF/CefBrowser/#printtopdf) for the full signature. + +## DevTools + +The Chromium DevTools window opens in its own top-level window: + +```tb +Private Sub btnDevTools_Click() Handles btnDevTools.Click + WebView.OpenDevToolsWindow() +End Sub +``` + +The CEF package does not currently expose **WebView2**'s **OpenTaskManagerWindow** equivalent — see the [WebView2 parity](../../tB/Packages/CEF/#webview2-parity) section of the reference for the current gap list. + +## Form-title sync + +To make the host window's caption track the page's `<title>`, listen for [**DocumentTitleChanged**](../../tB/Packages/CEF/CefBrowser/#documenttitlechanged) and read [**DocumentTitle**](../../tB/Packages/CEF/CefBrowser/#documenttitle): + +```tb +Private Sub WebView_DocumentTitleChanged() Handles WebView.DocumentTitleChanged + Me.Caption = WebView.DocumentTitle +End Sub +``` + +## Where next + +- [Hosting local web assets](Hosting-Local-Web-Assets) — serve HTML / JS / CSS from a folder without an HTTP server. +- [JavaScript interop](JavaScript-Interop) — pass values and method calls between BASIC and the page. +- [Re-entrancy](Re-entrancy) — the one thing to know about [**JsRun**](../../tB/Packages/CEF/CefBrowser/#jsrun) before you use it. +- [CefBrowser reference](../../tB/Packages/CEF/CefBrowser/) — every property, method, and event. diff --git a/docs/Tutorials/CEF/Customize the UserDataFolder.md b/docs/Tutorials/CEF/Customize the UserDataFolder.md new file mode 100644 index 00000000..a459fa5b --- /dev/null +++ b/docs/Tutorials/CEF/Customize the UserDataFolder.md @@ -0,0 +1,70 @@ +--- +title: Customize the UserDataFolder +parent: CEF +grand_parent: Tutorials +nav_order: 2 +permalink: /Tutorials/CEF/Customize-UserDataFolder +--- + +# Customize the UserDataFolder + +At runtime, CEF needs a working folder for the user profile — cache, cookies, history, local storage, password manager, and the per-instance lock file that prevents two browser processes from sharing the same profile. By default the runtime picks a folder under `%LocalAppData%\twinBASIC_CEF\<ProjectName>\instance-<N>\`, but that default is not always appropriate. + +A few situations where the default goes wrong: + +- **Office add-ins**, where the host process is `MSACCESS.EXE` or `EXCEL.EXE` — the default per-process layout interferes with the host's own profile. +- **Kiosk installations**, where the application runs under a low-privilege account that can't write under `%LocalAppData%`. +- **Portable deployments**, where all state must live next to the executable on a USB stick or network share. +- **Multi-user / hosted scenarios**, where each end-user needs an isolated profile. + +In every one of these, override the default by assigning [**EnvironmentOptions.UserDataFolder**](../../tB/Packages/CEF/CefBrowser/EnvironmentOptions#userdatafolder) during the control's [**Create**](../../tB/Packages/CEF/CefBrowser/#create) event: + +```tb +Private Sub CefBrowser1_Create() + CefBrowser1.EnvironmentOptions.UserDataFolder = _ + Environ$("APPDATA") & "\MyApp\CEF\" +End Sub +``` + +The folder is created automatically if it doesn't exist. The path must be writable by the current user — a read-only path raises the [**Error**](../../tB/Packages/CEF/CefBrowser/#error) event when the helper browser process tries to launch. + +## Why the Create event + +CEF reads the environment options *once*, when the helper browser process is launched. The [**Create**](../../tB/Packages/CEF/CefBrowser/#create) event fires immediately before that launch, which makes it the right place to override the defaults. Assigning [**UserDataFolder**](../../tB/Packages/CEF/CefBrowser/EnvironmentOptions#userdatafolder) any later (e.g. inside [**Ready**](../../tB/Packages/CEF/CefBrowser/#ready)) has no effect on the running browser. + +## Sharing a folder across instances + +A single user-data folder cannot be opened by two CEF processes at once — the runtime takes an exclusive lock on it for the lifetime of the browser process. Two **CefBrowser** controls in the *same* application share the helper process and therefore the same lock, so they cooperate fine; two *separate* applications pointing at the same folder collide. + +When a collision is detected and [**UserDataFolder**](../../tB/Packages/CEF/CefBrowser/EnvironmentOptions#userdatafolder) is left at its default, the control automatically retries with the next `instance-N` sub-folder. When the host has explicitly set a path, the lock failure instead surfaces as a CEF initialisation error (*"CEF cache path already locked by another process"*) — handle it in the [**Error**](../../tB/Packages/CEF/CefBrowser/#error) event: + +```tb +Private Sub CefBrowser1_Error(ByVal code As Long, ByVal msg As String) + If InStr(msg, "already locked") > 0 Then + MsgBox "Another copy of this application is already running. " & _ + "Close it before opening another window.", _ + vbExclamation + End If +End Sub +``` + +## Logging the runtime's output + +Two related fields on [**EnvironmentOptions**](../../tB/Packages/CEF/CefBrowser/EnvironmentOptions) configure the CEF debug log, useful when investigating runtime issues: + +```tb +Private Sub CefBrowser1_Create() + CefBrowser1.EnvironmentOptions.UserDataFolder = _ + Environ$("APPDATA") & "\MyApp\CEF\" + CefBrowser1.EnvironmentOptions.LogFilePath = _ + Environ$("APPDATA") & "\MyApp\CEF\debug.log" + CefBrowser1.EnvironmentOptions.LogSeverity = CefLogWarning +End Sub +``` + +[**LogFilePath**](../../tB/Packages/CEF/CefBrowser/EnvironmentOptions#logfilepath) is appended to across runs — rotate or delete it from your own code if it needs to be capped. [**LogSeverity**](../../tB/Packages/CEF/CefBrowser/EnvironmentOptions#logseverity) controls the threshold; **CefLogDisable** (the default) writes nothing regardless of the path. + +## See also + +- [CefEnvironmentOptions](../../tB/Packages/CEF/CefBrowser/EnvironmentOptions) — full reference for the pre-creation options. +- [Customize the UserDataFolder (WebView2)](../WebView2/Customize-UserDataFolder) — the same idea applied to the [**WebView2**](../../tB/Packages/WebView2/WebView2/) control. diff --git a/docs/Tutorials/CEF/Driving Monaco.md b/docs/Tutorials/CEF/Driving Monaco.md new file mode 100644 index 00000000..36e7709d --- /dev/null +++ b/docs/Tutorials/CEF/Driving Monaco.md @@ -0,0 +1,151 @@ +--- +title: Driving Monaco from twinBASIC +parent: CEF +grand_parent: Tutorials +nav_order: 7 +permalink: /Tutorials/CEF/Driving-Monaco +--- + +# Driving Monaco from twinBASIC + +A case study combining everything from the previous tutorials: a form with **two** [**CefBrowser**](../../tB/Packages/CEF/CefBrowser/) controls — the Microsoft Monaco editor on the left, a live HTML preview on the right. As the user types, Monaco posts the edited source to twinBASIC, which mirrors it into the preview pane. + +The complete project ships as *Sample 1b — Chromium Embedded Framework Examples* in the New-Project dialog (form *Example 3*). + +## Architecture + +![](Images/MonacoArchitecture.svg) + +The editor runs as a local web app under a virtual hostname; the preview pane is fed raw HTML through [**NavigateToString**](../../tB/Packages/CEF/CefBrowser/#navigatetostring). + +## Runtime version requirement + +Monaco uses modern JavaScript features that don't exist in older Chromium versions. The sample checks at startup and warns if the loaded runtime is too old: + +```tb +If WebView.CefMajorVersion < 109 Then + MsgBox "Sorry, Monaco is not supported by this old version of CEF." +End If +``` + +In practice this means **v109** or **v145** for this tutorial — **v49** lacks the JavaScript surface Monaco depends on. See [Getting started](Getting-Started) for picking the right package reference. + +## Setting up the editor's assets + +The Monaco editor ships as a ~2 MB collection of JavaScript, CSS, and font files. Drop them into a `Resources` sub-folder of your project — call it `MONACO_DEMO` — alongside an `index.html` and a small bootstrap `script.js`. The [Hosting local web assets](Hosting-Local-Web-Assets) tutorial describes the layout. + +The page itself is a single `<div id='container'>` plus the bootstrap script that listens for an *initial-content* message from the host: + +```html +<!DOCTYPE html> +<html> + <head> + <script src="/vs/loader.js"></script> + <script src="/script.js"></script> + <link rel="stylesheet" href="/styles.css"> + </head> + <body> + <div id="container"></div> + </body> +</html> +``` + +```js +window.chrome.webview.addEventListener('message', (event) => { + let initialHTML = event.data; + + require.config({ paths: { 'vs': 'https://monaco.example/vs' } }); + require(["vs/editor/editor.main"], () => { + let editor = monaco.editor.create(document.getElementById('container'), { + value: initialHTML, + language: 'html', + theme: 'vs-dark', + minimap: { enabled: false } + }); + + editor.onDidChangeModelContent(() => { + // Inform the host of every edit. + window.chrome.webview.postMessage(editor.getValue()); + }); + }); +}); +``` + +## The BASIC side + +Drop two `CefBrowser` controls on a form — `WebView` (the editor) and `WebViewPreview` (the renderer). The `Ready` handler deploys the assets, registers the virtual host, and navigates: + +```tb +Private localPath As String + +Private Sub WebView_Ready() Handles WebView.Ready + localPath = Environ$("USERPROFILE") & "\Documents\tbMonacoDemo" + CopyResourcesFolderContentsToLocalPath "MONACO_DEMO", localPath + + WebView.SetVirtualHostNameToFolderMapping _ + "monaco.example", localPath & "\" + WebView.Navigate "https://monaco.example/index.html" +End Sub +``` + +(`CopyResourcesFolderContentsToLocalPath` is the helper from [Hosting local web assets](Hosting-Local-Web-Assets).) + +The two controls share a single helper browser process — the first **CefBrowser** to reach [**Ready**](../../tB/Packages/CEF/CefBrowser/#ready) launches it, the second one attaches to the existing process. That sharing is what makes the two-pane pattern cheap. + +## Pushing the initial content + +Once Monaco has finished loading, the bootstrap script listens for a `message` event carrying the HTML to seed the editor with. Fire that message after the editor's [**NavigationComplete**](../../tB/Packages/CEF/CefBrowser/#navigationcomplete): + +```tb +Private Sub WebView_NavigationComplete( _ + ByVal IsSuccess As Boolean, ByVal WebErrorStatus As Long) _ + Handles WebView.NavigationComplete + + If WebView.DocumentURL <> "https://monaco.example/index.html" Then Exit Sub + + Dim initialHTML As String = _ + StrConv(LoadResData("initial-editor-html.html", "MONACO_DEMO"), vbFromUTF8) + + WebView.PostWebMessage(initialHTML) + WebViewPreview.NavigateToString(initialHTML) +End Sub +``` + +[**LoadResData**](../../tB/Packages/VB/Global/#loadresdata) returns the resource bytes; `StrConv(..., vbFromUTF8)` decodes them. [**PostWebMessage**](../../tB/Packages/CEF/CefBrowser/#postwebmessage) hands the string to Monaco's `message` listener; [**NavigateToString**](../../tB/Packages/CEF/CefBrowser/#navigatetostring) seeds the preview pane with the same text rendered as HTML. + +The `If` guard at the top is important — [**NavigationComplete**](../../tB/Packages/CEF/CefBrowser/#navigationcomplete) fires for *every* navigation, including internal Monaco asset loads. Only seed the editor on the navigation to `index.html`. + +## Live preview + +Every keystroke in Monaco fires its `onDidChangeModelContent` callback, which `postMessage`s the new content back to BASIC. That arrives as the [**JsMessage**](../../tB/Packages/CEF/CefBrowser/#jsmessage) event — feed it straight into the preview: + +```tb +Private Sub WebView_JsMessage(ByVal Message As Variant) Handles WebView.JsMessage + WebViewPreview.NavigateToString(Message) +End Sub +``` + +That's it — the preview pane re-renders on every edit. + +## Detecting a missing runtime + +A reasonable fraction of users will run the application on a machine where the CEF runtime ZIP has not been installed. The [**Error**](../../tB/Packages/CEF/CefBrowser/#error) event surfaces this case with the exact path the control searched: + +```tb +Private Sub WebView_Error(ByVal code As Long, ByVal msg As String) _ + Handles WebView.Error + MsgBox "Failed to initialize the CEF control." & vbCrLf & vbCrLf & _ + "Code: " & Hex$(code) & vbCrLf & _ + msg, vbExclamation, "CEF" +End Sub +``` + +The fix is to install the matching runtime ZIP from [github.com/twinbasic/cef-runtimes](https://github.com/twinbasic/cef-runtimes/releases/), or to ship the runtime alongside the application and point [**EnvironmentOptions.BrowserExecutableFolder**](../../tB/Packages/CEF/CefBrowser/EnvironmentOptions#browserexecutablefolder) at it during the [**Create**](../../tB/Packages/CEF/CefBrowser/#create) event. See [Getting started](Getting-Started) for the install path and the ZIPs. + +## Where next + +- [Hosting local web assets](Hosting-Local-Web-Assets) — the `CopyResourcesFolderContentsToLocalPath` helper and virtual-host pattern this tutorial builds on. +- [JavaScript interop](JavaScript-Interop) — the two bridges between BASIC and JavaScript. +- [Re-entrancy](Re-entrancy) — why the live-preview pattern is safe even though it's mostly synchronous-looking. +- [CefBrowser reference](../../tB/Packages/CEF/CefBrowser/) — every property, method, and event. +- [Driving Monaco (WebView2)](../WebView2/Driving-Monaco) — the parallel implementation using the [**WebView2**](../../tB/Packages/WebView2/WebView2/) control. diff --git a/docs/Tutorials/CEF/Getting started.md b/docs/Tutorials/CEF/Getting started.md new file mode 100644 index 00000000..f1b378a9 --- /dev/null +++ b/docs/Tutorials/CEF/Getting started.md @@ -0,0 +1,91 @@ +--- +title: Getting Started +parent: CEF +grand_parent: Tutorials +nav_order: 1 +permalink: /Tutorials/CEF/Getting-Started +--- + +# Getting Started + +## Package requirements + +To create a project that uses the CEF package, add the right compiler-package reference to your project. The package ships in three flavours — one per supported Chromium version — and you pick exactly one: + +| Reference | Chromium baseline | Supported OS | +|-----------------------------------------------------------------|-------------------|---------------| +| **twinBASIC - Chromium Embedded Framework Package v49** | Chromium 49 | Windows XP+ | +| **twinBASIC - Chromium Embedded Framework Package v109** | Chromium 109 | Windows 7+ | +| **twinBASIC - Chromium Embedded Framework Package v145** | Chromium 145 | Windows 10+ | + +Use **v145** unless you specifically need to support older operating systems. The package source compiles against all three — picking the reference sets the `CEF_VERSION` compiler constant, which selects the matching API surface. + +Add the reference through **Project** → **References** (Ctrl-T) → **TWINPACK PACKAGES**. Tick the desired CEF package, close the dialog, and restart the compiler. Once added, **CefBrowser** appears in the form-designer toolbox. + +> [!WARNING] +> Older Chromium versions should not be used for browsing untrusted content from the public Internet — they carry unpatched security vulnerabilities. v49 and v109 remain appropriate for tightly controlled environments where the browser loads only trusted local or internal content; for general web browsing, use v145. + +## Downloading the runtime + +Unlike [**WebView2**](../../tB/Packages/WebView2/WebView2/), CEF does not rely on a system-installed runtime. The Chromium binaries (`libcef.dll` and friends) ship as a separate download and must be installed alongside the application — both during development and at deploy time. + +Download the runtime ZIP that matches both the CEF version and the application bitness: + +| Version | Win32 | Win64 | +|---------|--------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------| +| v49 | [cefRuntime49_win32.zip](https://github.com/twinbasic/cef-runtimes/releases/download/latest/cefRuntime49_win32.zip) | [cefRuntime49_win64.zip](https://github.com/twinbasic/cef-runtimes/releases/download/latest/cefRuntime49_win64.zip) | +| v109 | [cefRuntime109_win32.zip](https://github.com/twinbasic/cef-runtimes/releases/download/latest/cefRuntime109_win32.zip) | [cefRuntime109_win64.zip](https://github.com/twinbasic/cef-runtimes/releases/download/latest/cefRuntime109_win64.zip) | +| v145 | [cefRuntime145_win32.zip](https://github.com/twinbasic/cef-runtimes/releases/download/latest/cefRuntime145_win32.zip) | [cefRuntime145_win64.zip](https://github.com/twinbasic/cef-runtimes/releases/download/latest/cefRuntime145_win64.zip) | + +See [CEF Runtime Releases](https://github.com/twinbasic/cef-runtimes/releases/) for the full version list and release notes. + +Extract the ZIP into `%LocalAppData%\twinBASIC_CEF_Runtime\`. The version-stamped folder inside the ZIP — for example `145_0_7632_160_Win64` — must land directly under that path, containing `libcef.dll` and its sibling files: + +```text +%LocalAppData%\twinBASIC_CEF_Runtime\145_0_7632_160_Win64\libcef.dll +%LocalAppData%\twinBASIC_CEF_Runtime\145_0_7632_160_Win64\chrome_elf.dll +%LocalAppData%\twinBASIC_CEF_Runtime\145_0_7632_160_Win64\… +``` + +At startup, [**CefBrowser**](../../tB/Packages/CEF/CefBrowser/) searches this default location automatically. If `libcef.dll` cannot be found, the [**Error**](../../tB/Packages/CEF/CefBrowser/#error) event fires with the exact path that was searched. + +To point at a different folder — for example a portable side-by-side deployment shipped with your installer — assign [**EnvironmentOptions.BrowserExecutableFolder**](../../tB/Packages/CEF/CefBrowser/EnvironmentOptions#browserexecutablefolder) during the [**Create**](../../tB/Packages/CEF/CefBrowser/#create) event: + +```tb +Private Sub CefBrowser1_Create() + CefBrowser1.EnvironmentOptions.BrowserExecutableFolder = _ + App.Path & "\cef145_win64" +End Sub +``` + +## Bitness must match + +The runtime bitness must match the application bitness — a 32-bit twinBASIC build needs the Win32 runtime, a 64-bit build needs the Win64 runtime. Mixing them produces a `libcef.dll` load failure surfaced through the [**Error**](../../tB/Packages/CEF/CefBrowser/#error) event. + +## Create a CefBrowser control on a form + +With the package reference and runtime in place, **CefBrowser** is available in the form-designer toolbox. Drop it onto a form like any other control: + +```tb +Private Sub Form_Load() + CefBrowser1.Navigate "https://www.twinbasic.com" +End Sub +``` + +The control starts up asynchronously — the first user-visible event is [**Ready**](../../tB/Packages/CEF/CefBrowser/#ready), which fires once the helper browser process has launched and IPC has connected. Navigation, scripting, and most property accessors raise *"CefBrowser control is not ready"* (run-time error 5) before then. + +## CefBrowser control properties + +Toggle the **Properties** pane to see the design-time-visible surface: [**DocumentURL**](../../tB/Packages/CEF/CefBrowser/#documenturl) (the initial URL the control auto-navigates to once **Ready** fires), [**ZoomFactor**](../../tB/Packages/CEF/CefBrowser/#zoomfactor), [**UserAgent**](../../tB/Packages/CEF/CefBrowser/#useragent), and the standard rect-dockable surface (size, **Anchors**, **Dock**). + +For the full reference, see the [**CefBrowser** class reference](../../tB/Packages/CEF/CefBrowser/); for what the underlying Chromium runtime supports, consult the [Chromium Embedded Framework documentation](https://bitbucket.org/chromiumembedded/cef/wiki/Home). + +## Samples + +If you prefer to start with a sample, **Sample 1b — Chromium Embedded Framework Examples** is available in the new-project dialog. It mirrors **Sample 1a — WebView2 Examples** almost feature-for-feature, with the differences called out where the CEF package doesn't yet expose a WebView2 equivalent. + +## Where next + +- [Customize the UserDataFolder](Customize-UserDataFolder) — relocate the user-profile folder for Office add-ins, kiosks, or portable installs. +- [Building a browser shell](Building-A-Browser-Shell) — back / forward / reload / zoom / PDF. +- [Re-entrancy](Re-entrancy) — what the package protects you from and the one place you still have to think about. diff --git a/docs/Tutorials/CEF/Hosting local web assets.md b/docs/Tutorials/CEF/Hosting local web assets.md new file mode 100644 index 00000000..0f1aa3a6 --- /dev/null +++ b/docs/Tutorials/CEF/Hosting local web assets.md @@ -0,0 +1,140 @@ +--- +title: Hosting local web assets +parent: CEF +grand_parent: Tutorials +nav_order: 5 +permalink: /Tutorials/CEF/Hosting-Local-Web-Assets +--- + +# Hosting local web assets + +A [**CefBrowser**](../../tB/Packages/CEF/CefBrowser/) control can serve HTML, JavaScript, CSS, and any other assets straight from a folder on disk — no embedded HTTP server required. Chromium's [**SetVirtualHostNameToFolderMapping**](../../tB/Packages/CEF/CefBrowser/#setvirtualhostnametofoldermapping) routes a virtual `https://` hostname to a local folder so that resources behave as if they came from a real origin: same-origin `fetch`, Content Security Policy, service workers, and so on all work as expected. + +This tutorial walks through the pattern used by *Sample 1b — Chromium Embedded Framework Examples* (forms *Example 2*, *Example 3*, *Example 4*). + +## The three-step pattern + +1. **Choose a folder.** It must exist on disk and contain `index.html` (plus whatever assets the page wants — scripts, styles, images). +2. **Register a virtual host** mapping to that folder. +3. **Navigate** to a URL under the virtual hostname. + +Hook into the [**Ready**](../../tB/Packages/CEF/CefBrowser/#ready) event so the control is fully initialised before the mapping is installed: + +```tb +Private Sub WebView_Ready() Handles WebView.Ready + Dim folderPath As String = _ + Environ$("USERPROFILE") & "\Documents\MyApp" + WebView.SetVirtualHostNameToFolderMapping _ + "myapp.example", folderPath & "\" + WebView.Navigate "https://myapp.example/index.html" +End Sub +``` + +Once mapped, every request to `https://myapp.example/<path>` is served from `folderPath\<path>`. A `<script src="/script.js">` on the page resolves to `folderPath\script.js` exactly as if a real web server were sitting on `myapp.example`. + +The trailing backslash on the folder path is required — the runtime concatenates the incoming URL path onto the folder string verbatim, so a missing separator turns `folderPath` + `/index.html` into a nonsense path. + +## Picking a hostname + +The safe convention is to pick a hostname under a TLD that will never resolve on the public Internet: + +| Recommended | Avoid | +|---------------------|-----------------------------| +| `myapp.example` | `myapp.com`, `app.local` | +| `editor.invalid` | `editor.dev` | +| `assets.test` | `assets.io` | + +The `.example`, `.invalid`, and `.test` TLDs are formally reserved by IANA and will never be allocated to a real domain, so they're safe to use indefinitely. + +## Bundling assets in the project's Resources folder + +Most applications want to ship their HTML / JS / CSS *inside* the executable and drop them onto disk on first run. twinBASIC's `Resources` folder is the right place to keep them. + +1. In the IDE's Project explorer, expand **Resources** and add a sub-folder (right-click → *Add new subfolder*). Name it something memorable like `WEB_APP`. +2. Drop the assets in — `index.html`, `script.js`, `styles.css`, plus any sub-directories you need. + +At runtime, the helper below copies the contents of a `Resources` sub-folder out to a local path. Drop it into a `.twin` module in your project: + +```tb +Module Files + + Private Sub CreateFile(ByVal Path As String, ByRef Data() As Byte) + On Error Resume Next : Kill Path : On Error GoTo 0 + Dim fileNum As Integer = FreeFile + Open Path For Binary As fileNum + Put fileNum, 1, Data + Close fileNum + End Sub + + Private Sub CreateLocalFileFromResource( _ + ByVal OutputLocalFolderPath As String, _ + ByVal InputResourceSubFolderName As String, _ + ByVal ResourceName As String) + + Dim splitPath As Variant = Split(ResourceName, "~") + On Error Resume Next : MkDir OutputLocalFolderPath : On Error GoTo 0 + + Dim i As Long + For i = 0 To UBound(splitPath) - 1 + OutputLocalFolderPath &= "\" & splitPath(i) + On Error Resume Next : MkDir OutputLocalFolderPath : On Error GoTo 0 + Next + + Dim Data() As Byte + Data = LoadResData(ResourceName, InputResourceSubFolderName) + CreateFile(OutputLocalFolderPath & "\" & splitPath(i), Data) + End Sub + + [Description("Copy every file from a Resources subfolder onto disk. " & _ + "'~' characters in resource names represent subfolders.")] + Public Sub CopyResourcesFolderContentsToLocalPath( _ + ByVal InputResourceSubFolderName As String, _ + ByVal OutputLocalFolderPath As String) + + Dim resourceId As Variant + For Each resourceId In LoadResIdList(InputResourceSubFolderName) + CreateLocalFileFromResource _ + OutputLocalFolderPath, InputResourceSubFolderName, resourceId + Next + End Sub + +End Module +``` + +[**LoadResIdList**](../../tB/Packages/VB/Global/#loadresidlist) returns every resource ID under the named sub-folder; [**LoadResData**](../../tB/Packages/VB/Global/#loadresdata) hands back the bytes. The helper splits each resource name on `~` to reconstruct the original sub-directory tree on disk — the twinBASIC IDE flattens nested folders by joining their names with `~` when the resources are compiled in. + +## Putting it together + +The complete deploy-on-`Ready` pattern looks like this: + +```tb +Private Sub WebView_Ready() Handles WebView.Ready + ' Resources/WEB_APP/* is copied here on every launch. + Dim folderPath As String = _ + Environ$("USERPROFILE") & "\Documents\MyApp" + + CopyResourcesFolderContentsToLocalPath "WEB_APP", folderPath + + WebView.SetVirtualHostNameToFolderMapping _ + "myapp.example", folderPath & "\" + WebView.Navigate "https://myapp.example/index.html" +End Sub +``` + +Once deployed, the application can launch DevTools ([**OpenDevToolsWindow**](../../tB/Packages/CEF/CefBrowser/#opendevtoolswindow)) to inspect the loaded files, and users can edit `index.html` directly on disk and hit **Refresh** — useful for rapid iteration during development. + +## Removing a mapping + +[**ClearVirtualHostNameToFolderMapping**](../../tB/Packages/CEF/CefBrowser/#clearvirtualhostnametofoldermapping) removes a mapping previously installed by [**SetVirtualHostNameToFolderMapping**](../../tB/Packages/CEF/CefBrowser/#setvirtualhostnametofoldermapping): + +```tb +WebView.ClearVirtualHostNameToFolderMapping "myapp.example" +``` + +The browser keeps cached assets until a hard reload, so a navigation that hits the just-removed hostname may still succeed for a short while. + +## Where next + +- [JavaScript interop](JavaScript-Interop) — how a hosted page exchanges values and method calls with the BASIC application. +- [Driving Monaco from twinBASIC](Driving-Monaco) — a full case study built on top of this pattern. +- [SetVirtualHostNameToFolderMapping](../../tB/Packages/CEF/CefBrowser/#setvirtualhostnametofoldermapping) — full reference. diff --git a/docs/Tutorials/CEF/Images/MonacoArchitecture.svg b/docs/Tutorials/CEF/Images/MonacoArchitecture.svg new file mode 100644 index 00000000..6e0d5cc1 --- /dev/null +++ b/docs/Tutorials/CEF/Images/MonacoArchitecture.svg @@ -0,0 +1 @@ +<svg id="mermaidChart0" width="100%" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" class="flowchart mermaid-svg" style="max-width: 1254.79px; background: rgb(54, 59, 64); color: rgb(184, 191, 198); fill: rgb(204, 204, 204); stroke: none; stroke-width: 1px; opacity: 1; stroke-dasharray: none; font-size: 16px; font-family: "trebuchet ms", verdana, arial, sans-serif; text-anchor: start; padding: 0px; margin: 0px;" viewBox="0 0 1254.7916259765625 188" role="graphics-document document" aria-roledescription="flowchart-v2"><style style="background-color: rgba(0, 0, 0, 0); color: rgb(184, 191, 198); fill: rgb(204, 204, 204); stroke: none; stroke-width: 1px; opacity: 1; stroke-dasharray: none; font-size: 16px; font-family: "trebuchet ms", verdana, arial, sans-serif; text-anchor: start; padding: 0px; margin: 0px;">#mermaidChart0{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#ccc;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaidChart0 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaidChart0 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaidChart0 .error-icon{fill:#a44141;}#mermaidChart0 .error-text{fill:#ddd;stroke:#ddd;}#mermaidChart0 .edge-thickness-normal{stroke-width:1px;}#mermaidChart0 .edge-thickness-thick{stroke-width:3.5px;}#mermaidChart0 .edge-pattern-solid{stroke-dasharray:0;}#mermaidChart0 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaidChart0 .edge-pattern-dashed{stroke-dasharray:3;}#mermaidChart0 .edge-pattern-dotted{stroke-dasharray:2;}#mermaidChart0 .marker{fill:lightgrey;stroke:lightgrey;}#mermaidChart0 .marker.cross{stroke:lightgrey;}#mermaidChart0 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaidChart0 p{margin:0;}#mermaidChart0 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#ccc;}#mermaidChart0 .cluster-label text{fill:#F9FFFE;}#mermaidChart0 .cluster-label span{color:#F9FFFE;}#mermaidChart0 .cluster-label span p{background-color:transparent;}#mermaidChart0 .label text,#mermaidChart0 span{fill:#ccc;color:#ccc;}#mermaidChart0 .node rect,#mermaidChart0 .node circle,#mermaidChart0 .node ellipse,#mermaidChart0 .node polygon,#mermaidChart0 .node path{fill:#1f2020;stroke:#ccc;stroke-width:1px;}#mermaidChart0 .rough-node .label text,#mermaidChart0 .node .label text,#mermaidChart0 .image-shape .label,#mermaidChart0 .icon-shape .label{text-anchor:middle;}#mermaidChart0 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaidChart0 .rough-node .label,#mermaidChart0 .node .label,#mermaidChart0 .image-shape .label,#mermaidChart0 .icon-shape .label{text-align:center;}#mermaidChart0 .node.clickable{cursor:pointer;}#mermaidChart0 .root .anchor path{fill:lightgrey!important;stroke-width:0;stroke:lightgrey;}#mermaidChart0 .arrowheadPath{fill:lightgrey;}#mermaidChart0 .edgePath .path{stroke:lightgrey;stroke-width:2.0px;}#mermaidChart0 .flowchart-link{stroke:lightgrey;fill:none;}#mermaidChart0 .edgeLabel{background-color:hsl(0, 0%, 34.4117647059%);text-align:center;}#mermaidChart0 .edgeLabel p{background-color:hsl(0, 0%, 34.4117647059%);}#mermaidChart0 .edgeLabel rect{opacity:0.5;background-color:hsl(0, 0%, 34.4117647059%);fill:hsl(0, 0%, 34.4117647059%);}#mermaidChart0 .labelBkg{background-color:rgba(87.75, 87.75, 87.75, 0.5);}#mermaidChart0 .cluster rect{fill:hsl(180, 1.5873015873%, 28.3529411765%);stroke:rgba(255, 255, 255, 0.25);stroke-width:1px;}#mermaidChart0 .cluster text{fill:#F9FFFE;}#mermaidChart0 .cluster span{color:#F9FFFE;}#mermaidChart0 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(20, 1.5873015873%, 12.3529411765%);border:1px solid rgba(255, 255, 255, 0.25);border-radius:2px;pointer-events:none;z-index:100;}#mermaidChart0 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#ccc;}#mermaidChart0 rect.text{fill:none;stroke-width:0;}#mermaidChart0 .icon-shape,#mermaidChart0 .image-shape{background-color:hsl(0, 0%, 34.4117647059%);text-align:center;}#mermaidChart0 .icon-shape p,#mermaidChart0 .image-shape p{background-color:hsl(0, 0%, 34.4117647059%);padding:2px;}#mermaidChart0 .icon-shape rect,#mermaidChart0 .image-shape rect{opacity:0.5;background-color:hsl(0, 0%, 34.4117647059%);fill:hsl(0, 0%, 34.4117647059%);}#mermaidChart0 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaidChart0 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaidChart0 :root{--mermaid-alt-font-family:sans-serif;}</style><g style="background-color: rgba(0, 0, 0, 0); color: rgb(184, 191, 198); fill: rgb(204, 204, 204); stroke: none; stroke-width: 1px; opacity: 1; stroke-dasharray: none; font-size: 16px; font-family: "trebuchet ms", verdana, arial, sans-serif; text-anchor: start; padding: 0px; margin: 0px;"><marker id="mermaidChart0_flowchart-v2-pointEnd" class="marker flowchart-v2" viewBox="0 0 10 10" refX="5" refY="5" markerUnits="userSpaceOnUse" markerWidth="8" markerHeight="8" orient="auto" style="background-color: rgba(0, 0, 0, 0); color: rgb(184, 191, 198); fill: rgb(211, 211, 211); stroke: rgb(211, 211, 211); stroke-width: 1px; opacity: 1; stroke-dasharray: none; font-size: 16px; font-family: "trebuchet ms", verdana, arial, sans-serif; text-anchor: start; padding: 0px; margin: 0px;"><path d="M 0 0 L 10 5 L 0 10 z" class="arrowMarkerPath" style="stroke-width: 1px; stroke-dasharray: 1px, 0px; background-color: rgba(0, 0, 0, 0); color: rgb(184, 191, 198); fill: rgb(211, 211, 211); stroke: rgb(211, 211, 211); opacity: 1; font-size: 16px; font-family: "trebuchet ms", verdana, arial, sans-serif; text-anchor: start; padding: 0px; margin: 0px;"></path></marker><marker id="mermaidChart0_flowchart-v2-pointStart" class="marker flowchart-v2" viewBox="0 0 10 10" refX="4.5" refY="5" markerUnits="userSpaceOnUse" markerWidth="8" markerHeight="8" orient="auto" style="background-color: rgba(0, 0, 0, 0); color: rgb(184, 191, 198); fill: rgb(211, 211, 211); stroke: rgb(211, 211, 211); stroke-width: 1px; opacity: 1; stroke-dasharray: none; font-size: 16px; font-family: "trebuchet ms", verdana, arial, sans-serif; text-anchor: start; padding: 0px; margin: 0px;"><path d="M 0 5 L 10 10 L 10 0 z" class="arrowMarkerPath" style="stroke-width: 1px; stroke-dasharray: 1px, 0px; background-color: rgba(0, 0, 0, 0); color: rgb(184, 191, 198); fill: rgb(211, 211, 211); stroke: rgb(211, 211, 211); opacity: 1; font-size: 16px; font-family: "trebuchet ms", verdana, arial, sans-serif; text-anchor: start; padding: 0px; margin: 0px;"></path></marker><marker id="mermaidChart0_flowchart-v2-circleEnd" class="marker flowchart-v2" viewBox="0 0 10 10" refX="11" refY="5" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto" style="background-color: rgba(0, 0, 0, 0); color: rgb(184, 191, 198); fill: rgb(211, 211, 211); stroke: rgb(211, 211, 211); stroke-width: 1px; opacity: 1; stroke-dasharray: none; font-size: 16px; font-family: "trebuchet ms", verdana, arial, sans-serif; text-anchor: start; padding: 0px; margin: 0px;"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width: 1px; stroke-dasharray: 1px, 0px; background-color: rgba(0, 0, 0, 0); color: rgb(184, 191, 198); fill: rgb(211, 211, 211); stroke: rgb(211, 211, 211); opacity: 1; font-size: 16px; font-family: "trebuchet ms", verdana, arial, sans-serif; text-anchor: start; padding: 0px; margin: 0px;"></circle></marker><marker id="mermaidChart0_flowchart-v2-circleStart" class="marker flowchart-v2" viewBox="0 0 10 10" refX="-1" refY="5" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto" style="background-color: rgba(0, 0, 0, 0); color: rgb(184, 191, 198); fill: rgb(211, 211, 211); stroke: rgb(211, 211, 211); stroke-width: 1px; opacity: 1; stroke-dasharray: none; font-size: 16px; font-family: "trebuchet ms", verdana, arial, sans-serif; text-anchor: start; padding: 0px; margin: 0px;"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width: 1px; stroke-dasharray: 1px, 0px; background-color: rgba(0, 0, 0, 0); color: rgb(184, 191, 198); fill: rgb(211, 211, 211); stroke: rgb(211, 211, 211); opacity: 1; font-size: 16px; font-family: "trebuchet ms", verdana, arial, sans-serif; text-anchor: start; padding: 0px; margin: 0px;"></circle></marker><marker id="mermaidChart0_flowchart-v2-crossEnd" class="marker cross flowchart-v2" viewBox="0 0 11 11" refX="12" refY="5.2" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto" style="background-color: rgba(0, 0, 0, 0); color: rgb(184, 191, 198); fill: rgb(211, 211, 211); stroke: rgb(211, 211, 211); stroke-width: 1px; opacity: 1; stroke-dasharray: none; font-size: 16px; font-family: "trebuchet ms", verdana, arial, sans-serif; text-anchor: start; padding: 0px; margin: 0px;"><path d="M 1,1 l 9,9 M 10,1 l -9,9" class="arrowMarkerPath" style="stroke-width: 2px; stroke-dasharray: 1px, 0px; background-color: rgba(0, 0, 0, 0); color: rgb(184, 191, 198); fill: rgb(211, 211, 211); stroke: rgb(211, 211, 211); opacity: 1; font-size: 16px; font-family: "trebuchet ms", verdana, arial, sans-serif; text-anchor: start; padding: 0px; margin: 0px;"></path></marker><marker id="mermaidChart0_flowchart-v2-crossStart" class="marker cross flowchart-v2" viewBox="0 0 11 11" refX="-1" refY="5.2" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto" style="background-color: rgba(0, 0, 0, 0); color: rgb(184, 191, 198); fill: rgb(211, 211, 211); stroke: rgb(211, 211, 211); stroke-width: 1px; opacity: 1; stroke-dasharray: none; font-size: 16px; font-family: "trebuchet ms", verdana, arial, sans-serif; text-anchor: start; padding: 0px; margin: 0px;"><path d="M 1,1 l 9,9 M 10,1 l -9,9" class="arrowMarkerPath" style="stroke-width: 2px; stroke-dasharray: 1px, 0px; background-color: rgba(0, 0, 0, 0); color: rgb(184, 191, 198); fill: rgb(211, 211, 211); stroke: rgb(211, 211, 211); opacity: 1; font-size: 16px; font-family: "trebuchet ms", verdana, arial, sans-serif; text-anchor: start; padding: 0px; margin: 0px;"></path></marker><g class="root" style="background-color: rgba(0, 0, 0, 0); color: rgb(184, 191, 198); fill: rgb(204, 204, 204); stroke: none; stroke-width: 1px; opacity: 1; stroke-dasharray: none; font-size: 16px; font-family: "trebuchet ms", verdana, arial, sans-serif; text-anchor: start; padding: 0px; margin: 0px;"><g class="clusters" style="background-color: rgba(0, 0, 0, 0); color: rgb(184, 191, 198); fill: rgb(204, 204, 204); stroke: none; stroke-width: 1px; opacity: 1; stroke-dasharray: none; font-size: 16px; font-family: "trebuchet ms", verdana, arial, sans-serif; text-anchor: start; padding: 0px; margin: 0px;"></g><g class="edgePaths" style="background-color: rgba(0, 0, 0, 0); color: rgb(184, 191, 198); fill: rgb(204, 204, 204); stroke: none; stroke-width: 1px; opacity: 1; stroke-dasharray: none; font-size: 16px; font-family: "trebuchet ms", verdana, arial, sans-serif; text-anchor: start; padding: 0px; margin: 0px;"></g><g class="edgeLabels" style="background-color: rgba(0, 0, 0, 0); color: rgb(184, 191, 198); fill: rgb(204, 204, 204); stroke: none; stroke-width: 1px; opacity: 1; stroke-dasharray: none; font-size: 16px; font-family: "trebuchet ms", verdana, arial, sans-serif; text-anchor: start; padding: 0px; margin: 0px;"></g><g class="nodes" style="background-color: rgba(0, 0, 0, 0); color: rgb(184, 191, 198); fill: rgb(204, 204, 204); stroke: none; stroke-width: 1px; opacity: 1; stroke-dasharray: none; font-size: 16px; font-family: "trebuchet ms", verdana, arial, sans-serif; text-anchor: start; padding: 0px; margin: 0px;"><g class="root" transform="translate(0, 0)" style="background-color: rgba(0, 0, 0, 0); color: rgb(184, 191, 198); fill: rgb(204, 204, 204); stroke: none; stroke-width: 1px; opacity: 1; stroke-dasharray: none; font-size: 16px; font-family: "trebuchet ms", verdana, arial, sans-serif; text-anchor: start; padding: 0px; margin: 0px;"><g class="clusters" style="background-color: rgba(0, 0, 0, 0); color: rgb(184, 191, 198); fill: rgb(204, 204, 204); stroke: none; stroke-width: 1px; opacity: 1; stroke-dasharray: none; font-size: 16px; font-family: "trebuchet ms", verdana, arial, sans-serif; text-anchor: start; padding: 0px; margin: 0px;"><g class="cluster " id="form" data-look="classic" style="background-color: rgba(0, 0, 0, 0); color: rgb(184, 191, 198); fill: rgb(204, 204, 204); stroke: none; stroke-width: 1px; opacity: 1; stroke-dasharray: none; font-size: 16px; font-family: "trebuchet ms", verdana, arial, sans-serif; text-anchor: start; padding: 0px; margin: 0px;"><rect style="background-color: rgba(0, 0, 0, 0); color: rgb(184, 191, 198); fill: rgb(71, 73, 73); stroke: rgba(255, 255, 255, 0.25); stroke-width: 1px; opacity: 1; stroke-dasharray: none; font-size: 16px; font-family: "trebuchet ms", verdana, arial, sans-serif; text-anchor: start; padding: 0px; margin: 0px;" x="8" y="8" width="1238.7916717529297" height="172"></rect><g class="cluster-label " transform="translate(570.0989608764648, 8)" style="background-color: rgba(0, 0, 0, 0); color: rgb(184, 191, 198); fill: rgb(204, 204, 204); stroke: none; stroke-width: 1px; opacity: 1; stroke-dasharray: none; font-size: 16px; font-family: "trebuchet ms", verdana, arial, sans-serif; text-anchor: start; padding: 0px; margin: 0px;"><foreignObject width="114.59375" height="24" style="background-color: rgba(0, 0, 0, 0); color: rgb(184, 191, 198); fill: rgb(204, 204, 204); stroke: none; stroke-width: 1px; opacity: 1; stroke-dasharray: none; font-size: 16px; font-family: "trebuchet ms", verdana, arial, sans-serif; text-anchor: start; padding: 0px; margin: 0px;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center; background-color: rgba(0, 0, 0, 0); color: rgb(184, 191, 198); fill: rgb(204, 204, 204); stroke: none; stroke-width: 1px; opacity: 1; stroke-dasharray: none; font-size: 16px; font-family: "trebuchet ms", verdana, arial, sans-serif; text-anchor: start; padding: 0px; margin: 0px;"><span class="nodeLabel " style="background-color: rgba(0, 0, 0, 0); color: rgb(249, 255, 254); fill: rgb(204, 204, 204); stroke: none; stroke-width: 1px; opacity: 1; stroke-dasharray: none; font-size: 16px; font-family: "trebuchet ms", verdana, arial, sans-serif; text-anchor: start; padding: 0px; margin: 0px;"><p style="background-color: rgba(0, 0, 0, 0); color: rgb(249, 255, 254); fill: rgb(204, 204, 204); stroke: none; stroke-width: 1px; opacity: 1; stroke-dasharray: none; font-size: 16px; font-family: "trebuchet ms", verdana, arial, sans-serif; text-anchor: start; padding: 0px 2px; margin: 0px;">twinBASIC form</p></span></div></foreignObject></g></g></g><g class="edgePaths" style="background-color: rgba(0, 0, 0, 0); color: rgb(184, 191, 198); fill: rgb(204, 204, 204); stroke: none; stroke-width: 1px; opacity: 1; stroke-dasharray: none; font-size: 16px; font-family: "trebuchet ms", verdana, arial, sans-serif; text-anchor: start; padding: 0px; margin: 0px;"><path d="M305.5,94L409.677,94L509.854,94" id="L_WebView_handler_0" class=" edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style="background-color: rgba(0, 0, 0, 0); color: rgb(184, 191, 198); fill: none; stroke: rgb(211, 211, 211); stroke-width: 1px; opacity: 1; stroke-dasharray: 0px; font-size: 16px; font-family: "trebuchet ms", verdana, arial, sans-serif; text-anchor: start; padding: 0px; margin: 0px;" marker-end="url(#mermaidChart0_flowchart-v2-pointEnd)"></path><path d="M709.448,94L829.37,94L945.292,94" id="L_handler_WebViewPreview_0" class=" edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style="background-color: rgba(0, 0, 0, 0); color: rgb(184, 191, 198); fill: none; stroke: rgb(211, 211, 211); stroke-width: 1px; opacity: 1; stroke-dasharray: 0px; font-size: 16px; font-family: "trebuchet ms", verdana, arial, sans-serif; text-anchor: start; padding: 0px; margin: 0px;" marker-end="url(#mermaidChart0_flowchart-v2-pointEnd)"></path></g><g class="edgeLabels" style="background-color: rgba(0, 0, 0, 0); color: rgb(184, 191, 198); fill: rgb(204, 204, 204); stroke: none; stroke-width: 1px; opacity: 1; stroke-dasharray: none; font-size: 16px; font-family: "trebuchet ms", verdana, arial, sans-serif; text-anchor: start; padding: 0px; margin: 0px;"><g class="edgeLabel" transform="translate(409.67708587646484, 94)" style="background-color: rgb(88, 88, 88); color: rgb(184, 191, 198); fill: rgb(204, 204, 204); stroke: none; stroke-width: 1px; opacity: 1; stroke-dasharray: none; font-size: 16px; font-family: "trebuchet ms", verdana, arial, sans-serif; text-anchor: start; padding: 0px; margin: 0px;"><g class="label" transform="translate(-66.67708587646484, -12)" style="background-color: rgba(0, 0, 0, 0); color: rgb(204, 204, 204); fill: rgb(204, 204, 204); stroke: none; stroke-width: 1px; opacity: 1; stroke-dasharray: none; font-size: 16px; font-family: "trebuchet ms", verdana, arial, sans-serif; text-anchor: start; padding: 0px; margin: 0px;"><foreignObject width="133.3541717529297" height="24" style="background-color: rgba(0, 0, 0, 0); color: rgb(204, 204, 204); fill: rgb(204, 204, 204); stroke: none; stroke-width: 1px; opacity: 1; stroke-dasharray: none; font-size: 16px; font-family: "trebuchet ms", verdana, arial, sans-serif; text-anchor: start; padding: 0px; margin: 0px;"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center; background-color: rgba(88, 88, 88, 0.5); color: rgb(204, 204, 204); fill: rgb(204, 204, 204); stroke: none; stroke-width: 1px; opacity: 1; stroke-dasharray: none; font-size: 16px; font-family: "trebuchet ms", verdana, arial, sans-serif; text-anchor: start; padding: 0px; margin: 0px;"><span class="edgeLabel " style="background-color: rgb(88, 88, 88); color: rgb(204, 204, 204); fill: rgb(204, 204, 204); stroke: none; stroke-width: 1px; opacity: 1; stroke-dasharray: none; font-size: 16px; font-family: "trebuchet ms", verdana, arial, sans-serif; text-anchor: start; padding: 0px; margin: 0px;"><p style="background-color: rgb(88, 88, 88); color: rgb(204, 204, 204); fill: rgb(204, 204, 204); stroke: none; stroke-width: 1px; opacity: 1; stroke-dasharray: none; font-size: 16px; font-family: "trebuchet ms", verdana, arial, sans-serif; text-anchor: start; padding: 0px; margin: 0px;">postMessage(html)</p></span></div></foreignObject></g></g><g class="edgeLabel" transform="translate(829.3697967529297, 94)" style="background-color: rgb(88, 88, 88); color: rgb(184, 191, 198); fill: rgb(204, 204, 204); stroke: none; stroke-width: 1px; opacity: 1; stroke-dasharray: none; font-size: 16px; font-family: "trebuchet ms", verdana, arial, sans-serif; text-anchor: start; padding: 0px; margin: 0px;"><g class="label" transform="translate(-82.421875, -12)" style="background-color: rgba(0, 0, 0, 0); color: rgb(204, 204, 204); fill: rgb(204, 204, 204); stroke: none; stroke-width: 1px; opacity: 1; stroke-dasharray: none; font-size: 16px; font-family: "trebuchet ms", verdana, arial, sans-serif; text-anchor: start; padding: 0px; margin: 0px;"><foreignObject width="164.84375" height="24" style="background-color: rgba(0, 0, 0, 0); color: rgb(204, 204, 204); fill: rgb(204, 204, 204); stroke: none; stroke-width: 1px; opacity: 1; stroke-dasharray: none; font-size: 16px; font-family: "trebuchet ms", verdana, arial, sans-serif; text-anchor: start; padding: 0px; margin: 0px;"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center; background-color: rgba(88, 88, 88, 0.5); color: rgb(204, 204, 204); fill: rgb(204, 204, 204); stroke: none; stroke-width: 1px; opacity: 1; stroke-dasharray: none; font-size: 16px; font-family: "trebuchet ms", verdana, arial, sans-serif; text-anchor: start; padding: 0px; margin: 0px;"><span class="edgeLabel " style="background-color: rgb(88, 88, 88); color: rgb(204, 204, 204); fill: rgb(204, 204, 204); stroke: none; stroke-width: 1px; opacity: 1; stroke-dasharray: none; font-size: 16px; font-family: "trebuchet ms", verdana, arial, sans-serif; text-anchor: start; padding: 0px; margin: 0px;"><p style="background-color: rgb(88, 88, 88); color: rgb(204, 204, 204); fill: rgb(204, 204, 204); stroke: none; stroke-width: 1px; opacity: 1; stroke-dasharray: none; font-size: 16px; font-family: "trebuchet ms", verdana, arial, sans-serif; text-anchor: start; padding: 0px; margin: 0px;">NavigateToString(html)</p></span></div></foreignObject></g></g></g><g class="nodes" style="background-color: rgba(0, 0, 0, 0); color: rgb(184, 191, 198); fill: rgb(204, 204, 204); stroke: none; stroke-width: 1px; opacity: 1; stroke-dasharray: none; font-size: 16px; font-family: "trebuchet ms", verdana, arial, sans-serif; text-anchor: start; padding: 0px; margin: 0px;"><g class="node default " id="flowchart-WebView-0" transform="translate(175.5, 94)" style="background-color: rgba(0, 0, 0, 0); color: rgb(184, 191, 198); fill: rgb(204, 204, 204); stroke: none; stroke-width: 1px; opacity: 1; stroke-dasharray: none; font-size: 16px; font-family: "trebuchet ms", verdana, arial, sans-serif; text-anchor: start; padding: 0px; margin: 0px;"><rect class="basic label-container" style="background-color: rgba(0, 0, 0, 0); color: rgb(184, 191, 198); fill: rgb(31, 32, 32); stroke: rgb(204, 204, 204); stroke-width: 1px; opacity: 1; stroke-dasharray: none; font-size: 16px; font-family: "trebuchet ms", verdana, arial, sans-serif; text-anchor: start; padding: 0px; margin: 0px;" x="-130" y="-51" width="260" height="102"></rect><g class="label" style="background-color: rgba(0, 0, 0, 0); color: rgb(204, 204, 204); fill: rgb(204, 204, 204); stroke: none; stroke-width: 1px; opacity: 1; stroke-dasharray: none; font-size: 16px; font-family: "trebuchet ms", verdana, arial, sans-serif; text-anchor: start; padding: 0px; margin: 0px;" transform="translate(-100, -36)"><rect style="background-color: rgba(0, 0, 0, 0); color: rgb(204, 204, 204); fill: rgb(31, 32, 32); stroke: rgb(204, 204, 204); stroke-width: 1px; opacity: 1; stroke-dasharray: none; font-size: 16px; font-family: "trebuchet ms", verdana, arial, sans-serif; text-anchor: start; padding: 0px; margin: 0px;"></rect><foreignObject width="200" height="72" style="background-color: rgba(0, 0, 0, 0); color: rgb(204, 204, 204); fill: rgb(204, 204, 204); stroke: none; stroke-width: 1px; opacity: 1; stroke-dasharray: none; font-size: 16px; font-family: "trebuchet ms", verdana, arial, sans-serif; text-anchor: start; padding: 0px; margin: 0px;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: table; white-space: break-spaces; line-height: 1.5; max-width: 200px; text-align: center; width: 200px; background-color: rgba(0, 0, 0, 0); color: rgb(204, 204, 204); fill: rgb(204, 204, 204); stroke: none; stroke-width: 1px; opacity: 1; stroke-dasharray: none; font-size: 16px; font-family: "trebuchet ms", verdana, arial, sans-serif; text-anchor: start; padding: 0px; margin: 0px;"><span class="nodeLabel " style="background-color: rgba(0, 0, 0, 0); color: rgb(204, 204, 204); fill: rgb(204, 204, 204); stroke: none; stroke-width: 1px; opacity: 1; stroke-dasharray: none; font-size: 16px; font-family: "trebuchet ms", verdana, arial, sans-serif; text-anchor: start; padding: 0px; margin: 0px;"><p style="background-color: rgba(0, 0, 0, 0); color: rgb(204, 204, 204); fill: rgb(204, 204, 204); stroke: none; stroke-width: 1px; opacity: 1; stroke-dasharray: none; font-size: 16px; font-family: "trebuchet ms", verdana, arial, sans-serif; text-anchor: start; padding: 0px 2px; margin: 0px;"><b style="background-color: rgba(0, 0, 0, 0); color: rgb(222, 222, 222); fill: rgb(204, 204, 204); stroke: none; stroke-width: 1px; opacity: 1; stroke-dasharray: none; font-size: 16px; font-family: "trebuchet ms", verdana, arial, sans-serif; text-anchor: start; padding: 0px; margin: 0px;">WebView</b><br/><i style="background-color: rgba(0, 0, 0, 0); color: rgb(204, 204, 204); fill: rgb(204, 204, 204); stroke: none; stroke-width: 1px; opacity: 1; stroke-dasharray: none; font-size: 16px; font-family: "trebuchet ms", verdana, arial, sans-serif; text-anchor: start; padding: 0px; margin: 0px;">CefBrowser hosting Monaco editor</i></p></span></div></foreignObject></g></g><g class="node default " id="flowchart-handler-1" transform="translate(611.6510467529297, 94)" style="background-color: rgba(0, 0, 0, 0); color: rgb(184, 191, 198); fill: rgb(204, 204, 204); stroke: none; stroke-width: 1px; opacity: 1; stroke-dasharray: none; font-size: 16px; font-family: "trebuchet ms", verdana, arial, sans-serif; text-anchor: start; padding: 0px; margin: 0px;"><rect class="basic label-container" style="background-color: rgba(0, 0, 0, 0); color: rgb(184, 191, 198); fill: rgb(31, 32, 32); stroke: rgb(204, 204, 204); stroke-width: 1px; opacity: 1; stroke-dasharray: none; font-size: 16px; font-family: "trebuchet ms", verdana, arial, sans-serif; text-anchor: start; padding: 0px; margin: 0px;" x="-97.796875" y="-39" width="195.59375" height="78"></rect><g class="label" style="background-color: rgba(0, 0, 0, 0); color: rgb(204, 204, 204); fill: rgb(204, 204, 204); stroke: none; stroke-width: 1px; opacity: 1; stroke-dasharray: none; font-size: 16px; font-family: "trebuchet ms", verdana, arial, sans-serif; text-anchor: start; padding: 0px; margin: 0px;" transform="translate(-67.796875, -24)"><rect style="background-color: rgba(0, 0, 0, 0); color: rgb(204, 204, 204); fill: rgb(31, 32, 32); stroke: rgb(204, 204, 204); stroke-width: 1px; opacity: 1; stroke-dasharray: none; font-size: 16px; font-family: "trebuchet ms", verdana, arial, sans-serif; text-anchor: start; padding: 0px; margin: 0px;"></rect><foreignObject width="135.59375" height="48" style="background-color: rgba(0, 0, 0, 0); color: rgb(204, 204, 204); fill: rgb(204, 204, 204); stroke: none; stroke-width: 1px; opacity: 1; stroke-dasharray: none; font-size: 16px; font-family: "trebuchet ms", verdana, arial, sans-serif; text-anchor: start; padding: 0px; margin: 0px;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center; background-color: rgba(0, 0, 0, 0); color: rgb(204, 204, 204); fill: rgb(204, 204, 204); stroke: none; stroke-width: 1px; opacity: 1; stroke-dasharray: none; font-size: 16px; font-family: "trebuchet ms", verdana, arial, sans-serif; text-anchor: start; padding: 0px; margin: 0px;"><span class="nodeLabel " style="background-color: rgba(0, 0, 0, 0); color: rgb(204, 204, 204); fill: rgb(204, 204, 204); stroke: none; stroke-width: 1px; opacity: 1; stroke-dasharray: none; font-size: 16px; font-family: "trebuchet ms", verdana, arial, sans-serif; text-anchor: start; padding: 0px; margin: 0px;"><p style="background-color: rgba(0, 0, 0, 0); color: rgb(204, 204, 204); fill: rgb(204, 204, 204); stroke: none; stroke-width: 1px; opacity: 1; stroke-dasharray: none; font-size: 16px; font-family: "trebuchet ms", verdana, arial, sans-serif; text-anchor: start; padding: 0px 2px; margin: 0px;">JsMessage handler<br/><i style="background-color: rgba(0, 0, 0, 0); color: rgb(204, 204, 204); fill: rgb(204, 204, 204); stroke: none; stroke-width: 1px; opacity: 1; stroke-dasharray: none; font-size: 16px; font-family: "trebuchet ms", verdana, arial, sans-serif; text-anchor: start; padding: 0px; margin: 0px;">(twinBASIC code)</i></p></span></div></foreignObject></g></g><g class="node default " id="flowchart-WebViewPreview-2" transform="translate(1079.2916717529297, 94)" style="background-color: rgba(0, 0, 0, 0); color: rgb(184, 191, 198); fill: rgb(204, 204, 204); stroke: none; stroke-width: 1px; opacity: 1; stroke-dasharray: none; font-size: 16px; font-family: "trebuchet ms", verdana, arial, sans-serif; text-anchor: start; padding: 0px; margin: 0px;"><rect class="basic label-container" style="background-color: rgba(0, 0, 0, 0); color: rgb(184, 191, 198); fill: rgb(31, 32, 32); stroke: rgb(204, 204, 204); stroke-width: 1px; opacity: 1; stroke-dasharray: none; font-size: 16px; font-family: "trebuchet ms", verdana, arial, sans-serif; text-anchor: start; padding: 0px; margin: 0px;" x="-130" y="-51" width="260" height="102"></rect><g class="label" style="background-color: rgba(0, 0, 0, 0); color: rgb(204, 204, 204); fill: rgb(204, 204, 204); stroke: none; stroke-width: 1px; opacity: 1; stroke-dasharray: none; font-size: 16px; font-family: "trebuchet ms", verdana, arial, sans-serif; text-anchor: start; padding: 0px; margin: 0px;" transform="translate(-100, -36)"><rect style="background-color: rgba(0, 0, 0, 0); color: rgb(204, 204, 204); fill: rgb(31, 32, 32); stroke: rgb(204, 204, 204); stroke-width: 1px; opacity: 1; stroke-dasharray: none; font-size: 16px; font-family: "trebuchet ms", verdana, arial, sans-serif; text-anchor: start; padding: 0px; margin: 0px;"></rect><foreignObject width="200" height="72" style="background-color: rgba(0, 0, 0, 0); color: rgb(204, 204, 204); fill: rgb(204, 204, 204); stroke: none; stroke-width: 1px; opacity: 1; stroke-dasharray: none; font-size: 16px; font-family: "trebuchet ms", verdana, arial, sans-serif; text-anchor: start; padding: 0px; margin: 0px;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: table; white-space: break-spaces; line-height: 1.5; max-width: 200px; text-align: center; width: 200px; background-color: rgba(0, 0, 0, 0); color: rgb(204, 204, 204); fill: rgb(204, 204, 204); stroke: none; stroke-width: 1px; opacity: 1; stroke-dasharray: none; font-size: 16px; font-family: "trebuchet ms", verdana, arial, sans-serif; text-anchor: start; padding: 0px; margin: 0px;"><span class="nodeLabel " style="background-color: rgba(0, 0, 0, 0); color: rgb(204, 204, 204); fill: rgb(204, 204, 204); stroke: none; stroke-width: 1px; opacity: 1; stroke-dasharray: none; font-size: 16px; font-family: "trebuchet ms", verdana, arial, sans-serif; text-anchor: start; padding: 0px; margin: 0px;"><p style="background-color: rgba(0, 0, 0, 0); color: rgb(204, 204, 204); fill: rgb(204, 204, 204); stroke: none; stroke-width: 1px; opacity: 1; stroke-dasharray: none; font-size: 16px; font-family: "trebuchet ms", verdana, arial, sans-serif; text-anchor: start; padding: 0px 2px; margin: 0px;"><b style="background-color: rgba(0, 0, 0, 0); color: rgb(222, 222, 222); fill: rgb(204, 204, 204); stroke: none; stroke-width: 1px; opacity: 1; stroke-dasharray: none; font-size: 16px; font-family: "trebuchet ms", verdana, arial, sans-serif; text-anchor: start; padding: 0px; margin: 0px;">WebViewPreview</b><br/><i style="background-color: rgba(0, 0, 0, 0); color: rgb(204, 204, 204); fill: rgb(204, 204, 204); stroke: none; stroke-width: 1px; opacity: 1; stroke-dasharray: none; font-size: 16px; font-family: "trebuchet ms", verdana, arial, sans-serif; text-anchor: start; padding: 0px; margin: 0px;">CefBrowser hosting HTML preview</i></p></span></div></foreignObject></g></g></g></g></g></g></g></svg> \ No newline at end of file diff --git a/docs/Tutorials/CEF/JavaScript interop.md b/docs/Tutorials/CEF/JavaScript interop.md new file mode 100644 index 00000000..63fbcc8a --- /dev/null +++ b/docs/Tutorials/CEF/JavaScript interop.md @@ -0,0 +1,147 @@ +--- +title: JavaScript interop +parent: CEF +grand_parent: Tutorials +nav_order: 6 +permalink: /Tutorials/CEF/JavaScript-Interop +--- + +# JavaScript interop + +The [**CefBrowser**](../../tB/Packages/CEF/CefBrowser/) control offers two complementary bridges between twinBASIC and the JavaScript running in the page: + +1. **Messages** — push a value (string, number, …) in either direction and listen for it on the other side. +2. **Scripted calls** — call a named JavaScript function from BASIC and (optionally) wait for its return value. + +> [!NOTE] +> [**WebView2**](../../tB/Packages/WebView2/WebView2/) also exposes a third bridge — *host objects*, where a BASIC class is published under `chrome.webview.hostObjects.<Name>` for the page to call into. The CEF package does not yet expose an equivalent — see the [WebView2 parity](../../tB/Packages/CEF/#webview2-parity) section of the reference. + +This tutorial covers both bridges, with the matching JavaScript side shown next to each BASIC side. The worked code comes from *Sample 1b — Chromium Embedded Framework Examples* (form *Example 2*). + +## Bridge 1 — Messages + +Messages are values that travel in either direction. Use them for notifications and ad-hoc payloads where you don't want to define a method signature ahead of time. + +### BASIC → page + +[**PostWebMessage**](../../tB/Packages/CEF/CefBrowser/#postwebmessage) sends a value to the page; the page receives it through a `message` event on `window.chrome.webview`: + +```tb +WebView.PostWebMessage "Hello from twinBASIC!" +``` + +```js +window.chrome.webview.addEventListener('message', (e) => { + alert("Host sent: " + e.data); +}); +``` + +Strings arrive as JavaScript strings; numerics, **Boolean**, **Null**, and **Empty** are JSON-encoded for the page. Objects and arrays are not currently supported. + +If [**PostWebMessage**](../../tB/Packages/CEF/CefBrowser/#postwebmessage) is called before the renderer IPC has connected, the call is queued and dispatched once the connection comes up — there's no need to wait for [**Ready**](../../tB/Packages/CEF/CefBrowser/#ready) explicitly. + +### Page → BASIC + +The page calls `window.chrome.webview.postMessage(value)`; BASIC receives it as the [**JsMessage**](../../tB/Packages/CEF/CefBrowser/#jsmessage) event: + +```js +function sendHostAMessage() { + window.chrome.webview.postMessage("This is a message from JavaScript."); +} +``` + +```tb +Private Sub WebView_JsMessage(ByVal Message As Variant) _ + Handles WebView.JsMessage + Debug.Print "Page sent: "; Message +End Sub +``` + +The two halves combine cleanly into a request / reply exchange — the page posts a query string, BASIC processes it and posts a result back: + +```tb +Private Sub WebView_JsMessage(ByVal Message As Variant) _ + Handles WebView.JsMessage + If Left$(Message, 6) = "QUERY:" Then + WebView.PostWebMessage "ANSWER:" & LookupAnswer(Mid$(Message, 7)) + End If +End Sub +``` + +## Bridge 2 — Scripted calls + +When the page exposes named JS functions, BASIC can call them directly. There are three variants: + +| Method | Returns | Use it when | +|-----------------------------------------------------------------------------------|--------------------------------------------------|-------------------------------------------------------------------| +| [**JsRun**](../../tB/Packages/CEF/CefBrowser/#jsrun) | **Variant**, synchronously | You need the result inline and the JS is **pure** (no callbacks). | +| [**JsRunAsync**](../../tB/Packages/CEF/CefBrowser/#jsrunasync) | nothing; result via `JsAsyncResult` | The JS may take a while and you don't want to block the UI. | +| [**ExecuteScript**](../../tB/Packages/CEF/CefBrowser/#executescript) | nothing (fire-and-forget) | You just want to trigger something — no return value needed. | + +### JsRun (synchronous) + +Given a page-side function: + +```js +function multiplyTheseNumbers(a, b) { + return a * b; +} +``` + +BASIC can call it and read the result on the same line: + +```tb +Dim product As Long = WebView.JsRun("multiplyTheseNumbers", 5, 6) +Debug.Print product ' 30 +``` + +The call blocks the BASIC thread until the renderer process replies. + +> [!WARNING] +> If the JavaScript function calls back into BASIC during the call — via `window.chrome.webview.postMessage(...)`, for instance — the result is a deadlock. Use [**JsRun**](../../tB/Packages/CEF/CefBrowser/#jsrun) only for pure functions; reach for [**JsRunAsync**](../../tB/Packages/CEF/CefBrowser/#jsrunasync) the moment that's not true. See the [Re-entrancy tutorial](Re-entrancy) for the full discussion. + +### JsRunAsync (asynchronous) + +```tb +Private Sub btnRun_Click() Handles btnRun.Click + WebView.JsRunAsync "multiplyTheseNumbers", 5, 6 +End Sub + +Private Sub WebView_JsAsyncResult( _ + ByVal Result As Variant, Token As LongLong, ErrString As String) _ + Handles WebView.JsAsyncResult + If LenB(ErrString) = 0 Then + Debug.Print "Async result: "; Result + Else + Debug.Print "Async error: "; ErrString + End If +End Sub +``` + +The [**JsAsyncResult**](../../tB/Packages/CEF/CefBrowser/#jsasyncresult) event carries a *Token* parameter so a single handler can demultiplex multiple in-flight calls. *ErrString* is empty on success. + +Calls made before the renderer IPC has connected are queued and dispatched once the connection comes up. + +### ExecuteScript (fire-and-forget) + +```tb +WebView.ExecuteScript "startTimer()" +``` + +No return value, no event. The simplest way to nudge the page into doing something. + +## Re-entrancy + +The discussion of when calling synchronous JavaScript from BASIC is safe — and what to do when it isn't — lives in its own tutorial. The short summary: + +- **Pure JS** (input → output, no side effects that touch the host): [**JsRun**](../../tB/Packages/CEF/CefBrowser/#jsrun) is fine. +- **JS that might post back, await a host object, or otherwise re-enter BASIC**: use [**JsRunAsync**](../../tB/Packages/CEF/CefBrowser/#jsrunasync). + +See the [Re-entrancy tutorial](Re-entrancy) for the full picture. + +## Where next + +- [Hosting local web assets](Hosting-Local-Web-Assets) — bundle and serve the JavaScript that talks to the host. +- [Driving Monaco from twinBASIC](Driving-Monaco) — a full case study using both bridges. +- [Re-entrancy](Re-entrancy) — the deeper story behind synchronous vs. asynchronous calls. +- [CefBrowser reference](../../tB/Packages/CEF/CefBrowser/) — every property, method, and event. diff --git a/docs/Tutorials/CEF/Re-entrancy.md b/docs/Tutorials/CEF/Re-entrancy.md new file mode 100644 index 00000000..1c498837 --- /dev/null +++ b/docs/Tutorials/CEF/Re-entrancy.md @@ -0,0 +1,79 @@ +--- +title: Re-entrancy +parent: CEF +grand_parent: Tutorials +nav_order: 3 +permalink: /Tutorials/CEF/Re-entrancy +--- + +# Re-entrancy + +The Chromium Embedded Framework runs the browser and renderer in *separate processes*, with cross-process IPC sitting between BASIC and the page. That model is fundamentally different from an in-process API, and it forces a particular discipline on host code: while a CEF callback is executing on the BASIC thread, the browser / renderer process is waiting for it to return — and calling *back* into the [**CefBrowser**](../../tB/Packages/CEF/CefBrowser/) control during that wait can deadlock. + +For the most part you don't have to think about any of this. The control raises every event onto the BASIC message loop via a posted message, so a handler that runs in response to a CEF callback has already returned control to the browser process by the time your code runs. The one place the rule still applies is [**JsRun**](../../tB/Packages/CEF/CefBrowser/#jsrun) — the *synchronous* JavaScript bridge. + +## How the control protects you + +When the browser or renderer process raises a CEF callback that needs to surface in BASIC, the control: + +1. Captures the callback's arguments into a `Type` instance. +2. Calls `PostMessageW` to push a custom message onto the main thread's message queue. +3. Returns immediately — the browser process is unblocked. +4. Later, when the form's message loop picks up the posted message, the control raises the event on the BASIC side. + +The handler runs *outside* the original CEF callback. By the time it executes, the browser process has moved on; the handler is free to call any [**CefBrowser**](../../tB/Packages/CEF/CefBrowser/) method or property — [**Navigate**](../../tB/Packages/CEF/CefBrowser/#navigate), [**ExecuteScript**](../../tB/Packages/CEF/CefBrowser/#executescript), [**JsRunAsync**](../../tB/Packages/CEF/CefBrowser/#jsrunasync), even another [**JsRun**](../../tB/Packages/CEF/CefBrowser/#jsrun) — without any re-entrancy concern. + +This covers every event the control raises today: + +- [**Create**](../../tB/Packages/CEF/CefBrowser/#create), [**Ready**](../../tB/Packages/CEF/CefBrowser/#ready), [**Error**](../../tB/Packages/CEF/CefBrowser/#error) +- [**NavigationComplete**](../../tB/Packages/CEF/CefBrowser/#navigationcomplete), [**SourceChanged**](../../tB/Packages/CEF/CefBrowser/#sourcechanged), [**DocumentTitleChanged**](../../tB/Packages/CEF/CefBrowser/#documenttitlechanged), [**DOMContentLoaded**](../../tB/Packages/CEF/CefBrowser/#domcontentloaded) +- [**PrintToPdfCompleted**](../../tB/Packages/CEF/CefBrowser/#printtopdfcompleted), [**PrintToPdfFailed**](../../tB/Packages/CEF/CefBrowser/#printtopdffailed) +- [**JsAsyncResult**](../../tB/Packages/CEF/CefBrowser/#jsasyncresult), [**JsMessage**](../../tB/Packages/CEF/CefBrowser/#jsmessage) + +## The NavigationStarting exception + +[**NavigationStarting**](../../tB/Packages/CEF/CefBrowser/#navigationstarting) is the one event that *cannot* be fully deferred — its `Cancel` parameter is **ByRef**, so the BASIC handler has to set it *before* the browser process can decide whether to proceed with the navigation. The control still uses `SendMessageW` (synchronous) rather than `PostMessageW` to surface this event, which means the handler runs while the browser process is blocked waiting for the answer. + +The control adds an extra safety net for one specific case: if the renderer-IPC channel happens to be busy connecting at the same moment **NavigationStarting** fires (which can happen in older CEF versions during early page loads), a straight `SendMessageW` would deadlock — BASIC is waiting on the renderer; the renderer is waiting on BASIC. The control detects the situation and uses an interrupt-style mechanism to dispatch *just* the **NavigationStarting** handler on the UI thread without waiting for the renderer IPC. + +The practical consequence: **NavigationStarting** handlers should keep their work small — read [**Uri**](../../tB/Packages/CEF/CefBrowser/#navigationstarting), make a decision, set or leave [**Cancel**](../../tB/Packages/CEF/CefBrowser/#navigationstarting), return. Avoid synchronous round-trips of any kind from inside the handler — including [**JsRun**](../../tB/Packages/CEF/CefBrowser/#jsrun), [**MsgBox**](../../tB/Modules/Interaction/MsgBox), and file dialogs. + +## JsRun — the explicit warning + +[**JsRun**](../../tB/Packages/CEF/CefBrowser/#jsrun) is the synchronous JavaScript bridge: + +```tb +Dim product As Long = CefBrowser1.JsRun("multiplyTheseNumbers", 5, 6) +``` + +The call blocks the BASIC thread until the renderer process replies with the result. During that block, the renderer is running JavaScript; if that JavaScript calls back into BASIC — via `window.chrome.webview.postMessage(...)`, or via any host-object call that lands on the BASIC thread — there is no thread available to *receive* the call. The renderer waits on BASIC; BASIC waits on the renderer. Deadlock. + +The control's source carries this warning directly on the method: + +> **!!!WARNING!!!** be careful not to introduce re-entrancy when using this synchronous function, otherwise UI freezes can occur. + +The safe rule of thumb: + +- Use [**JsRun**](../../tB/Packages/CEF/CefBrowser/#jsrun) for **pure** JavaScript functions — ones that take inputs, compute, and return a value. No `postMessage`, no host-object calls, no `await` of anything that touches the host. +- Use [**JsRunAsync**](../../tB/Packages/CEF/CefBrowser/#jsrunasync) for anything else — anywhere the JavaScript side might end up wanting to talk to BASIC mid-call. + +```tb +' Safe — pure JavaScript: takes two numbers, returns one number. +Dim html As String = CefBrowser1.JsRun("renderMarkdownToHtml", source) + +' Prefer JsRunAsync when the JS could call back into BASIC. +CefBrowser1.JsRunAsync "uploadAndReturnUrl", filePath +' ... result arrives later via JsAsyncResult event. +``` + +## Why the model is simpler than WebView2's + +[**WebView2**](../../tB/Packages/WebView2/WebView2/) supports `AddObject` — publishing a BASIC COM object that JavaScript can call directly. That feature carries its own re-entrancy story ([**UseDeferredInvoke**](../../tB/Packages/WebView2/WebView2/#addobject)) because page-initiated host-object calls have to land somewhere. + +CEF's host-object equivalent isn't exposed yet — see the [WebView2 parity](../../tB/Packages/CEF/#webview2-parity) section of the reference. The only synchronous BASIC ↔ JavaScript boundary CEF currently offers is **JsRun**, so the entire re-entrancy story reduces to *"don't post messages back to BASIC from inside a **JsRun** target"*. + +## See also + +- [JavaScript interop](JavaScript-Interop) — practical patterns for choosing between [**JsRun**](../../tB/Packages/CEF/CefBrowser/#jsrun), [**JsRunAsync**](../../tB/Packages/CEF/CefBrowser/#jsrunasync), and the message bridge. +- [CefBrowser reference](../../tB/Packages/CEF/CefBrowser/) — every property, method, and event. +- [WebView2 Re-entrancy tutorial](../WebView2/Re-entrancy) — the parallel story for the [**WebView2**](../../tB/Packages/WebView2/WebView2/) control, including the **AddObject** trade-off CEF doesn't yet have. diff --git a/docs/Tutorials/CEF/_Images/MonacoArchitecture.md b/docs/Tutorials/CEF/_Images/MonacoArchitecture.md new file mode 100644 index 00000000..8d5c04fb --- /dev/null +++ b/docs/Tutorials/CEF/_Images/MonacoArchitecture.md @@ -0,0 +1,11 @@ +```mermaid +flowchart LR + subgraph form["twinBASIC form"] + direction LR + WebView["<b>WebView</b><br/><i>CefBrowser hosting Monaco editor</i>"] + handler["JsMessage handler<br/><i>(twinBASIC code)</i>"] + WebViewPreview["<b>WebViewPreview</b><br/><i>CefBrowser hosting HTML preview</i>"] + WebView -- "postMessage(html)" --> handler + handler -- "NavigateToString(html)" --> WebViewPreview + end +``` diff --git a/docs/Tutorials/CEF/index.md b/docs/Tutorials/CEF/index.md new file mode 100644 index 00000000..6468a249 --- /dev/null +++ b/docs/Tutorials/CEF/index.md @@ -0,0 +1,26 @@ +--- +title: CEF +parent: Tutorials +permalink: /Tutorials/CEF/ +--- + +# CEF + +The [**CefBrowser**](../../tB/Packages/CEF/CefBrowser/) control hosts a Chromium browser inside a twinBASIC form — navigate to web pages, run local web apps, exchange messages and method calls with JavaScript, and print pages to PDF. Unlike [**WebView2**](../WebView2/), the Chromium runtime ships *alongside* the application rather than being a system component, so the browser version is under the developer's control and the same package works on machines without Edge installed. + +These tutorials walk through the most common patterns: + +- [Getting started](Getting-Started) — adding the package reference, downloading the matching CEF runtime, and dropping a control onto a form. +- [Customize the UserDataFolder](Customize-UserDataFolder) — relocating the runtime's working folder for hosted scenarios (Office add-ins, kiosk installs, portable deployments). +- [Re-entrancy](Re-entrancy) — what the control's deferred-event machinery does for you, and the one place ([**JsRun**](../../tB/Packages/CEF/CefBrowser/#jsrun)) where you still have to think about it. +- [Building a browser shell](Building-A-Browser-Shell) — address bar, back / forward / reload, zoom, PDF export — turning the control into a working browser. +- [Hosting local web assets](Hosting-Local-Web-Assets) — serve HTML / JS / CSS from a project resource folder, without an HTTP server. +- [JavaScript interop](JavaScript-Interop) — the two bridges between BASIC and the page: messages and scripted calls. +- [Driving Monaco from twinBASIC](Driving-Monaco) — a case study combining everything above: embed Microsoft's Monaco editor next to a live HTML preview pane. + +The complete sample code for the last four tutorials ships as *Sample 1b — Chromium Embedded Framework Examples* in the New-Project dialog, mirroring *Sample 1a — WebView2 Examples* almost feature-for-feature. + +> [!IMPORTANT] +> The CEF package is currently in **BETA**. Several features available on [**WebView2**](../../tB/Packages/WebView2/WebView2/) are not yet exposed — see the [WebView2 parity](../../tB/Packages/CEF/#webview2-parity) section of the reference for the current gap list. + +For the full surface area of the control itself, see the [**CefBrowser** class reference](../../tB/Packages/CEF/CefBrowser/).