How to simulate Chrome is running in a TTY
Edit on GitHubI’ve always loved terminals and retro-computing. I find they were a technology that didn’t got fully their full potential due to graphical interfaces (it’s strange I say this since my first computer was a Macintosh LC II at a time where everybody else had at most a PC with Windows 3.11…). That’s the main reason I added support for Unicode BPM plain in Linux kernel for NodeOS, specially to have available the Braille patters used by blessed-contrib to draw graphical diagrams in the terminal. That’s the reason why when I discovered BOOTSTRA.386 project, a Bootstrap theme that mimics a text-mode interface in a website similar to old BBSs (fathers of web forums, and grandfathers of current online walls), I got enthusiastic about the idea of making it compatible with real terminal web browsers like Links, w3m or Lynx.
Web pages are independent of the platform they are rendered, being a screen or
printed in paper or using a screen reader for visually imparied persons, so
doing the adaptation for a terminal output using CSS stylesheets made totally
sense (and it’s the correct way to do it, and yes, text-mode web browsers
support CSS too). The key here to make the text-mode detection work was the
tty
media type,
but it got deprecated in benefict of using
media features, that offer
a more fine-grained control of the representation features of web browsers, in
our case the grid media feature.
The problem is that the
Chrome DevTools just
only allow by default to simulate print
media type, and usage of real
text-mode web browsers like links
, lynx
or w3m
are not so much developer
friendly, so how can be able to simulate other media devices? Using directly the
Chrome DevTools protocol
by hand to force Chrome browser to show the media features that we want, that’s
how Chrome DevTools inpect and modify the web pages, but first we need to get
access to it. We can use a custom client, or hack the Chrome DevTools. Since
it’s already a web page itself, we need to
open the devtools-on-devtools
page. This way we will have two instances of Chrome DevTools, one of the page we
are testing as usual, and another one of it DevTools page, from where we have
access to the DevTools Protocol client in the console to be able to send low
level commands to the web browser (yes, some of the web browser automation tools
are using the DevTools Protocol under the hood, but now they are moving to the
browser agnostic WebDriver standard).
Once we have access to the DevTools Protocol, the first thing is to be sure we can send emulation commands to the web browser. To do so, we use the Emulation.canEmulate command, so in the devtools-on-devtools console we write:
let Main = await import('./main/main.js');
await Main.MainImpl.sendOverProtocol('Emulation.canEmulate');
This will get us a reference to the DevTools Protocol client in Main.MainImpl
and return us an array with result objects (seems the protocol allow to send
several commands in batch) like this:
[{result: true}]
If it returns true
, then we can send emulation commands. The one we are
interested about is
Emulation.setEmulatedMedia,
that allow us to emulate the media type or features that we want, in this case
the grid
media feature. We open the MDN example page for the
grid
media feature
and call the Emulation.setEmulatedMedia
in the console to enable it…
await Main.MainImpl.sendOverProtocol('Emulation.setEmulatedMedia', {
features: [{name: 'grid', value: '1'}],
});
…and if it went well, we would get an array with an empty object as response.
Note that value
needs to be a string independently of the value the media
feature accept (in the case of grid
media feature, it’s
mq-boolean), or if
not, we would get an error instead:
{code: -32602, message: "Invalid parameters", data: "features.0.value: string value expected"}
(Descriptive and context aware error messages, kudos.)
Now the tricky part, how to check that it worked? One could think about using
the (maybe undocumented?) Emulation.getEmulatedMedia
command…
await Main.MainImpl.sendOverProtocol('Emulation.getEmulatedMedia');
but it didn’t exist at all:
{code: -32601, message: "'Emulation.getEmulatedMedia' wasn't found"}
On the other hand, CSS.getMediaQueries
allow to get the actual state of all
the web page CSS queries…
await Main.MainImpl.sendOverProtocol('CSS.getMediaQueries');
…but it shows us that the query for the grid
media feature is still disabled
:-/
[
{
medias: [
{text: "print", source: "mediaRule", styleSheetId: "30394.6"},
{
mediaList: [
{expressions: [{value: 0, unit: "", feature: "grid"}], active: true}
],
range: {startLine: 5, startColumn: 7, endLine: 5, endColumn: 16},
source: "mediaRule",
sourceURL: "https://mdn.mozillademos.org/en-US/docs/Web/CSS/@media/grid$samples/Example?revision=1506717",
styleSheetId: "30394.5",
text: "(grid: 0)"
},
{
mediaList: [
{expressions: [{value: 1, unit: "", feature: "grid"}], active: false}
],
range: {startLine: 16, startColumn: 7, endLine: 16, endColumn: 16},
source: "mediaRule",
sourceURL: "https://mdn.mozillademos.org/en-US/docs/Web/CSS/@media/grid$samples/Example?revision=1506717",
styleSheetId: "30394.5",
text: "(grid: 1)"
}
]
}
]
Reloading the page didn’t work (being a device feature that usually doesn’t
change in runtime, it made sense that would be evaluated only at page load…),
so I checked for the
monochrome
media feature,
since it use an integer value in case the problem was with the mq-boolean
type, but it didn’t work too. Just to be sure, I decided to check with a
feature that’s currently possible to emulate on Chrome DevTools,
prefers-color-scheme:
await Main.MainImpl.sendOverProtocol('Emulation.setEmulatedMedia', {
features: [{name: 'prefers-color-scheme', value: 'light'}],
});
And this worked, so seems to me that Chrome only detect the other ones as valid ones, but their values are hardcoded and can’t be modified at runtime. In any case, Chrome is focused for modern desktop computers with full-color graphical interfaces… :-/
I was going to desist and get to the conclusion that it was not possible, when I
got an idea: grid
media feature is the new way to detect that a browser is
working on a device with fixed width characters, so maybe it could be still
unimplemented, but maybe the deprecated tty
media is still available? It’s
possible to change it with the Chrome DevTools anyway, so it’s a matter of send
the command with a different string, so I changed the media queries in the web
page…
@media tty {
body {
font-family: monospace;
}
}
…and tried it…
await Main.MainImpl.sendOverProtocol('Emulation.setEmulatedMedia', {
media: 'tty'
});
And it worked!!! :-D Now when changing the CSS media between tty
, screen
or
print
it correctly detected and stylesheets change, yeah! :-D Mostly a useless
thing because there’s almost no grid web devices out there, but being capable of
properly support them in a standard way has removed me an ich in the back of my
head. At least, now this blog is one of the few sites that support them… :-D
Next step, create a terminal inspired CSS stylesheet to use as base one when designing terminal compatible websites… and find a way to override some of the values (more specifically, the font sizes) over the ones defined by the web site (AKA user stylesheets).
BONUS: adding a CSS stylesheet in Chrome DevTools by editing the elements
panel as HTML doesn’t works (or at least it didn’t worked to me) because by
doing so it seems you are modifying the text
attribute, while it’s needed to
modify the textContent
one instead. It’s possible to do it in Javascript by
creating an script
element and
appending it to the document HEAD
:-)
Comment on Twitter
You can leave a comment by replying this tweet.