Wren a web browser where WebAssembly runs everything, JavaScript included

Several windows

One Wren process can have several windows. A tab moves from one window to another with its page as it is: the document is not loaded again, its scripts, timers, videos, forms, history, zoom and (when open) its developer tools go on.

Using it

WhatHow
New windowCtrl+N, the command window.new
Tab to a window of its ownthe tab's menu Move tab to a new window, the command tab.move_to_new_window, or drag the tab away from the tab bar
Tab to another windowthe tab's menu Move to window (windows are named by the title of their current tab), or drag the tab onto the tab bar of that window (a mark shows where it will go)
From a scriptwren ctl windows, `wren ctl tab move <id> --to new

Dragging: while a tab is dragged along the bar it reorders. Further from the bar than browser.tabs.detach_distance_em (above or below a row of tabs, beside a sidebar) it leaves for a window of its own, which follows the pointer; a tab alone in its window carries that window. Released over another window's tab bar it joins that window and the empty window closes. browser.tabs.drag_detach turns this off.

A window that loses its last tab closes. Closing a window closes its tabs (they can be reopened). Closing the last window ends the browser. Tabs opened by the browser itself (a link, window.open, the address bar, a download's page, about: pages) open in the window the user is in (the front one).

How it works

  • Browser_Window (src/browser/windows.odin): a native window, its swap chain (the first is the renderer's own, the others are render.Surface values swapped in for the length of their frame), its toolkit ui.State (ui), and its Window_Ctx (tabs, current, address, page_rect, frames_requested, presented, focus_address, embedded: w.tabs). None of it is copied into the App: the shell reads the window being framed (or the front one between frames) as active_window(app) (active_window(app).tabs, .win, .ui; request_frames(app, n) asks it for frames), any other window as w.tabs (window_request(app, w, n)). window_activate only makes a window the one framed (app.active), its toolkit state the toolkit's, its surface the renderer's, and adopts its pacing. An App with no window (the tests') has a bare one (App.bare). What the chrome keeps of a window (tab strip, sidebar, find bar, form controls, selection, cursors, address bar suggestions, full screen, context menu, frames' pointer, touch input, page preparation) is Browser_Window.own (Window_Own), read through window_own(app); src\tools\test_all\lint_globals.odin fails on a new global of a window.
  • Everything that meant "every tab" reads tabs_all(app) or tab_by_id; tab_reveal, tab_close_tab, address_set and window_of_tab find a tab's own window.
  • The loop (windows_pass) sleeps when no window has a cause, then makes a frame for each window (the front one first: it runs the tasks of the loop). A minimized window does not draw. The pacer (pacing.odin) is the window's (Browser_Window.pacer, read by pacer_of(app): its monitor's rate, the cadence model's state, its frame clock); its preferences and timer are the process's (App.pacing). Nothing of it is exchanged by window_activate.
  • Moves (tab_move, tab_detach) and the choice of the drop target (drop_index, pick_drop, detach_wanted) are pure and tested in windows_test.odin and tab_drag_test.odin. The window that gets a carried tab is the topmost one under the pointer (windows_by_z: a window covering another's tabs there hides them); the place is computed from what that window laid out (window_drop_slot: drop_slot_row for a row of tabs, drop_slot_list for the sidebar's list with its group headers and collapsed groups): its index, the group it joins (between two tabs of a group, or under a group's header) and whether it is pinned (a pinned tab among the pinned ones in a row; none in the sidebar's list), and the mark is drawn there, in the group's colour. The release puts it exactly there (tab_move(..., slot = ...)). src\tools\e2e\tab_drag_windows.odin drags with an injected pointer (platform.Event.injected: positions of the event, not the system's cursor), holds the drag on its way (e2e.drag with hold, resume; wren ctl drag --hold, --resume), reads the mark (e2e.windows: dropping, drop_index, drop_mark; wren ctl windows --json: drop) and captures it.
  • Developer tools (devtools.odin, devtools_window.odin): each window has its own (Window_Own.devtools, read as dev(), the active window's), docked in it or in a window of their own, showing its current tab. F12 in one window opens them there only. A tab they show that moves to a window with none open takes them along (devtools_follow_tab: docked or in their window); a window that closes closes its developer tools' window. Where they sit, their tab and their size are saved from the window that changed them (browser.devtools.*) and are what a new window starts with; the text size and the density are every window's. The network log's bodies and the pages' console descriptions are kept while any window shows its developer tools; a speed limit or blocked addresses are those of the request's tab's window. A media preview of the Network tab plays in one window at a time.
  • The caret of a focused field shows and blinks only in the front window (window_is_front; the chrome's fields through ui.Build_Params.inactive); each window's caret and validation bubble have timers of their own (Forms_View.blink_timer, naming the window by its id). window_set_front repaints the window that was in front and the new one.
  • Scale (DPI): each window has its monitor's (platform.Window.dpi_scale, changed by the system's WM_DPICHANGED when it moves to a monitor of another scale, with the rectangle the system suggests). Its chrome is built at it (ui_scale_of(app, w)), its page laid out at it (page_scale reads the window's scale, not the last build's: no frame at the old one), and its document stays where it was scrolled to (tab_scale_follow: Tab.scroll is in device pixels, rescaled when the scale it was in changed, also for a tab moved to a window of another scale). A developer tools' window has its own. Tab icons are decoded for the largest scale of the windows and decoded again when a window's grows (favicon_decode_px). wren ctl window --dpi n sends a real window a WM_DPICHANGED, and changes a headless window's emulated scale (platform.set_offscreen_dpi: src\tools\e2e\window_dpi.odin tests the scale's consequences that way, not the system's message and the rectangle it suggests); what the screen shows while a window straddles two monitors of different scales is the system's and is not tested.
  • Remote calls with --win-id (or a tab of that window) act on that window until they answer: their waits count its frames, and a capture is of its frame (remote_capture_arm: the renderer copies the next frame it ends, so it is asked for in that window's frame).
  • Session: wren-session 3 lists the windows (window left top width height state, then their tabs); a one-window session is the old file. browser.session.max_windows caps what a restore reopens.

Limits

  • Chrome frames while a page's script holds the thread (shell_frames.odin) exist for the window whose frame runs it; the others wait for it.

Edit this page on GitHub