Cruising Solo

For Christmas 2020, I was home alone. The highlight of my day was discovering that Jack in the Box was open. I enjoyed my Christmas cheeseburger dinner at a picnic table in a park down the street.

Unexpectedly, my Christmas plans fell through for 2021, and I faced a repeat of 2020. But making Jack in the Box a holiday tradition seemed a bit more grim than I was ready to accept, so I started hunting for other options with just five days left to go.

I’d recently finished booking a family holiday cruise for New Years 2023, and I idly wondered whether there were any cheap last-minute cruises out of Galveston this holiday that would work with my schedule. And, sure enough, Royal Caribbean had one leaving on Christmas Eve for relatively cheap (~3x their cheapest off-season rate).

So, that seemed like a definite possibility. I love cruising (no internet, under five minute walk to every meal and show), even if I lament the ecological impact and cringe at the economic inequality– rich tourists served by crews from poor countries, taking excursions in beautiful but impoverished areas. (Improving environmental impact mostly awaits better technology, but I believe the best approach for addressing the economic inequality isn’t in abstaining but instead tipping as heavily as you can afford.)

Still… could going on a cruise “alone” (with thousands of strangers) really be a good idea??

Over the last decade I’ve concluded that I am not, in fact, the introvert I’d always considered myself (thinking back to how as a grade schooler I was too shy to even ask for ketchup in McDonalds), but I am certainly not a social butterfly either. Beyond my normal anxiety about being out of my comfort zone, this trip promised to be one of BIG FEELINGS… After all, I did get engaged on a cruise1 and on this cruise I’d be bringing along the final paperwork for my imminent divorce. So, yeah. Weighty.

But after a moment pondering a Christmas whose highlight otherwise would be finishing my binge-rewatch of Friday Night Lights (apparently, I’m a cliche), I decided almost anything would be better and booked the trip. I went through CostCo Travel (same prices as direct, and they send you a $140 gift card after returning) and booking for one was remarkably straightforward. (It was hard to overlook the fact that inviting a cabinmate would cost nearly nothing extra, but doing that wasn’t in the cards.)

I wavered a bit about whether to splurge on a balcony, a luxury I’d never enjoyed before, and ultimately decided “What the heck, it’s Christmas!”

As you can imagine, the value of a balcony is related to how pretty the view outside happens to be.

On December 22nd, I realized that beyond full-vaccination, the cruise line also requires a negative COVID test dated within two days of the cruise. Alarmingly, I couldn’t find anywhere in Austin willing to do a test in the next five days. But fortunately the cruise terminal itself offered “testing of last resort” and there were still hundreds of testing slots open. So that was sorted.

I spent most of December 23rd shopping for clothing for the trip– I haven’t worn anything remotely formal in close to two years, and unfortunately most of my “fancy” pants no longer fit. In the course of packing, I realized I no longer owned a suit/garment bag (lost either in the move to Texas ages ago, or buried in my soon-to-be-ex’s house) so I spent a fair bit of time hunting for a new one. All of the options were expensive (~$150-$300) and most had terrible reviews on Amazon. After checking online and two malls in person, I swung by Goodwill and snagged an old red Samsonite in aged but workable condition for the princely sum of $11.

I procrastinated on finishing packing until late Thursday night, and set off for the four-hour drive to Galveston on Friday morning. It was a bit nerve-wracking to realize that if I had any sort of car trouble, I was going to miss the entire trip without a chance of a refund, and if I got to the terminal but failed the COVID test I was going to have to get back in the car and drive straight back to Austin. These thoughts occupied my mind for the first few hours of the drive.

I told myself that at least this situation couldn’t be as bad last year. On 12/23/2020, I’d set off on a quixotic last second seven-hour drive to visit a friend. Only upon arrival at the hotel did I realize that I’d left my well-packed suitcase behind atop my library sofa at my house in Austin. It was after 9PM on Christmas Eve Eve, 40F and windy out, and I had nothing to wear but basketball shorts and a light t-shirt. Adventure, amirite?

I chuckled at how dumb last year’s situation was until I abruptly realized with bemused horror that, while I had carefully ensured that all three pieces of luggage (garment bag, suitcase, daybag) were in my car, I’d never actually gotten around to putting my dress shirts into the garment bag. Fortunately, I had an hour to spare, and a Kohl’s outside of Houston got me sorted out in half that.

Fortunately, my COVID rapid test at the terminal turned out negative.

Repeatedly throughout the check-in and boarding process folks would ask “How many in your party?” and I would sheepishly respond “one,” but I soon felt a lot better about it because the universal reaction was summed up in the response I got from the first person helping me: “Great, you’ll be easy then.

Shortly after 1pm I was aboard. I explored the ship a bit while waiting for the staterooms to be released; I gawked at the massive three-story Christmas tree on the Deck 4 promenade.

At 2pm, we got the notification that our rooms were ready, and I headed to mine. I eagerly awaited the arrival of my luggage, as it wasn’t among the bags lining the hallway.

And I waited. And waited. With mounting dismay I pondered spending the entire cruise in just the workout clothes I wore for the drive. “I really need to stop travelling in such shabby stuff” I berated myself. I went out to continue exploring the ship, checking back at my stateroom periodically. One of my favorite discoveries was the peek-a-boo view down into the bridge:

Very cool

I hadn’t eaten yet all day, and at 4:30 I headed to the Windjammer restaurant to grab a bite only to realize that it had closed at 4pm; the old saw “You’ll never be hungry on a cruise ship” turns out not to be entirely accurate. Unlike Disney, the top deck doesn’t have small stands offering light fare all day; only small soft-serve ice cream cones were available2. I had two and enjoyed the beautiful weather and cool breeze on the deck.

Fortunately, at 5pm my main suitcase arrived and my stress level dropped considerably. It didn’t have my fanciest clothing, but I would be okay.

I headed to the “Welcome Aboard” show in the Lyric Theater at 7pm– the Cruise Director opened by asking the audience the usual questions: “How many of you are celebrating a birthday? A honeymoon? An anniversary?” with the expected jokes about the sparse attendance of honeymooners (“They’re busy in their rooms, I suspect”) and muted enthusiasm of the anniversary celebrators (“They’re a lot less excited than the honeymooners”). She finally joked “How many of you are celebrating a divorce?” and I couldn’t help but hoot and raise my now mostly-empty glass, the Old Fashioned it formerly contained now lightening my mood. There was a bit of laughter in the audience as she quipped “Well, y’all know where to find him this whole trip, up in the Blue Moon club” while miming a disco dance routine.

After the tame but amusing comedy show (Jackson Perdue), I went to dinner (I’d picked the “My Time” dining option, which turned out to mean “Pick from any of the few extra-late dinner slots”) and had a delicious braised beef dish at a quiet, out of the way table. As with the other staff before him, my waiter seemed happy about the relative ease of dealing with a solo traveler.

After dinner, I got back to my room and happily discovered that my garment bag had made it. Phew. I spent another hour or so exploring the ship, before heading to the “Solo Travellers” meetup at the aforementioned club. It was odd scene, to put it mildly– there were some late 30s guys milling about, and a handful of early 20s women who were travelling with their families but had aged out of the ship’s Teens club.

I nursed a drink while people-watching; after a while, a social butterfly (an Israeli immigrant CS professor at a Texas university) started pulling people together and introducing everyone to the folks he’d chatted with thus far. At one point, he asked if I was “on the prowl” and I almost giggled at the thought, but I just gestured at the kids around us and said “No.” He replied “Well, if you change your mind and need a wingman, I’m here.” I ended up drinking and chatting with him and another Israeli transplant (a real-estate agent from Chicago) until around midnight.

The next morning, Christmas, I woke up and headed upstairs for a picture-perfect breakfast:

…and finished my coffee on my balcony, enjoying the screensaver-perfect weather:

At eleven, I headed up to the ship’s fitness center. Lamely, they’d disabled the water dispenser, suggesting that exercisers head to the nearest bar to ask for water (Two years in, people, we know that COVID is airborne and water-fountains aren’t the problem!) but other than that, the gym was nice. I spent a bit over an hour on a treadmill at the bow, sweating my way across the Gulf.

Wearing a mask while working out wasn’t very pleasant, but not as bad as I’d feared, and I have some ambitious fitness goals for the coming year and reminded myself that I needed to start getting less comfortable if I’m to achieve them.

After the gym, I spent a half hour walking the track on deck in the strong breeze to cool down…

…then grabbed a quick shower in my stateroom and headed to lunch. One of the Israelis from the night before invited me to join him and his family and after mild protestations I gratefully did so. We had a great lunch; his six year old delighted in my Dad jokes that my own kids no longer appreciate. They’d apparently missed their original cruise (a seven-night cruise that had left a few days before) because they’d taken their pre-trip COVID tests a day too early (!!!).

I putzed around all afternoon, opened a bottle of wine, left a holiday/thank you card for the cabin steward, put on a fancy shirt and slacks, and headed to dinner.

Enjoying cheap wine from my trusty mug :)
I’m classy like that.

After dinner, I wandered around topside for hours, enjoying the perfect breeze and night sky.

A new friend greeted me back at my cabin

I finished reading my book and fell asleep around midnight. It was an almost perfect day.

The next two days were slated for adventure.

We arrived at Puerto Costa Maya around noon on December 26th under perfect skies.

I’d booked a “Chill River Rafting” excursion, which involved a longish (90 minutes) van ride out to a ranch where we put on life jackets and boarded rafts in groups of 4 to 6. The activity was described as “Activity Level: Moderate” which most of us took to mean that it wasn’t going to be white-water rafting, but it would at least involve some paddling. Alas, it did not; it was more akin to a gondola ride in Venice, with a guide pushing the raft down the smooth river with a long pole. My seatmate (a ripped senior majoring in history at Kansas State and sporting massive tattoos) and I chatted and snarked at the tameness of our river “adventure.” I was a bit disappointed that I hadn’t brought my phone because I was afraid of losing it in rough waters. Lol.

We rode the small river about half a mile through the jungle, past a public park where the locals snorted and called out to their swimming kids in Spanish “Look, the Americans are afraid of the water”. To be fair, we did look a bit ridiculous. We mused about whether the rafting company was paying for access to the river and concluded “Naw, but they sell tickets to the locals to see the Americans in the Zoo.”

Eventually, we reached a small lagoon where we were offered a glass of sparkling wine and a thirty-minute break for swimming. Most of us doffed our wildly unnecessary life jackets and got down into the water (it wasn’t too cold) and swam around a bit. One couple remained on their raft; I suspect they were newly engaged. Most of us were dressed in swimsuits and other athletic wear, but they looked like they’d just stepped out of central casting for a 40s movie– her with bright red lips and him in an Irish flat cap; both were wearing clothes and hairstyles that could’ve easily been of that era. I suddenly wondered whether they were famous– the last time I’d encountered some suspiciously attractive people, I much later learned that they were super famous. I almost swam over to talk to them, but decided I had no business bothering them. I idly spent the rest of the cruise elaborating an imagined backstory about how this anachronistic couple ended up on our ship.

With our swimming break over, we headed back to our departure point for sandwiches and Pepsi before getting back in the van for the long ride back to the ship. It was a relaxing and pleasant excursion, but definitely not what I’d expected.

Back on the boat, I showered and went topside to enjoy the weather. The Cowboys vs. Washington Football team game was on the big screen and I watched my family’s favorite team get absolutely demolished by Texas while I sipped a discounted Mai Tai (the drink of the day).

Dinner was Beef Stroganoff which was prepared much differently than I’d had as a kid– it was really good. After dinner, I went to the late (10:15) song and dance show featuring Bobby Brooks Wilson, son of Jackie Wilson, a famous performer of the early 50s to 70s.

Sadly, the show was sparsely attended, but we cheered louder than our numbers would have predicted for his flamboyant performance of music of his father’s era. The final song was by Bruno Mars, a bandmate of Bobby’s decades ago when Wilson was in the Navy stationed in Hawaii. After the show, I tried to get to bed before midnight because I had a 6:15am wakeup for the next day’s excursion.

The next morning we arrived on the island of Cozumel, but my excursion group wasn’t staying– We immediately boarded a ferry for a 20 minute ride to the mainland. It was a pleasant ride although much rockier than on the dramatically larger cruise ship. Seated topside, I only found out later that some folks below deck were puking their guts out.

We then had a very long van ride (over two hours) out to the Chichen Itza ruins, but it was made more pleasant by an hour-long lecture by our guide about the history of the Mayan city.

When we finally arrived, the entry was extremely crowded (but apparently much less crowded than the prior day) but once we got out to the ruins, everyone was able to spread out.

The pyramid was really impressive, and I was astonished that the chirping bird echo really happens. Very cool. We also spent a lot of time talking about the various symbolic aspects of the pyramid (its serpents light up on the equinox, there are 91 steps on each side, and 1 on top for a total of 365, etc).

Given our time constraints, we weren’t supposed to have any time for souvenir shopping (vendors ringed the site, and some of their wares were pretty cool looking) but I quickly overpaid $20 for a cool lucite pyramid:

… on the walk from the temple over to the Great Ball Court.

One of the two hoops at the Great Ball Court.
Victors of the game won the honor of being beheaded as human sacrifices.

We finished our hour-long tour and headed back to the van for the long drive back to the ferry. On the ferry, tired travellers napped; I almost drowned in nostalgia at the sight of a 3yo boy sleeping on his dad’s chest, and the lovebird time travellers from the 1940s dozed nuzzled into each other on the next bench.

Back on the boat, I enjoyed the sunset with a Mai Tai.

Before dinner, I headed to the “Invitation to Dance” song and dance show, and was amazed at the rapid fire spectacle of back-to-back numbers (some of the costume changes must’ve taken under fifteen seconds). It was a really impressive performance. I’ve always loved live performances of almost every form (plays > music > magic > comedy > opera > ballet) and while I don’t think I’d ever just randomly go downtown to see a song and dance show, I was incredibly glad that I didn’t miss this one. As with all of the shows I saw on the ship, I easily got second-row seats (the front row was closed off in a nod to social distancing).

After dinner, I went back topside to enjoy the night air. Because there are few lights above the front of the ship, the view of the night sky from the helipad on Deck Five Forward was amazing. I spent an hour looking skyward as a cool breeze blew over the deck. It’s hard to capture the majesty of the sky, even with the Pixel 6’s cool “Night Sight” mode:

I’ve never thought of myself as a city boy, but in a world rife with light pollution, it’s notable that I can think of almost every time I could see the stars reaching all the way down to the horizon. In the summer of 1991 after Grade 6, laying out on a lakeside dock in Michigan with my “girlfriend”3; in 1996 in rural Minnesota, driving cross-country with my friend Anson; in February 2010, the night before my wedding in a beachside hot tub with our friends. And now, December 2021, sailing across the Gulf of Mexico.

After my stargazing, I went to Jackson Perdue’s “Adult” comedy show, which was much funnier than his tame opening night all-ages show; this was more like the standup I’m used to from Netflix specials and the like.

The show was followed by a disappointing announcement from the cruise director that the Ice Show I’d booked for the following day (there’s a full ice skating rink on-board) had been canceled due to technical problems. Nevertheless, I went to bed looking forward to another relaxing day at sea as we headed back to Galveston.

In the morning, I had breakfast and headed to the gym, determined to push myself. After over an hour on the treadmill, I walked another two miles or so on the deck, happily enjoying the breeze as sweat poured everywhere.

The trick to burning a thousand calories on a treadmill is a 10.0 elevation and being heavy AF.

I ended up sore for the rest of the trip. Ah well. A Pina Colada helped.

That night, I packed my bags before heading to my usual dining room for appetizers only (eating far too much) before a late dinner at the on-ship steakhouse (yummy, but probably not worth the upcharge).

The Farewell show’s comedian was Cary Long (whose act appears to be pretty standard, with a bunch of word-for-word overlap with this YouTube video from eight years ago), but he did one pretty impressive trick– he spent five minutes of the act greeting folks (and chatting briefly) in their native languages (apparently, there were speakers of over 30 languages aboard). Not comedy perhaps, but it was very cool to see.

The next morning, we docked in Galveston and I reluctantly turned cell service back on (and was promptly flooded with messages). After a quick breakfast at the Windjammer buffet, it was time to leave the ship for the long drive back to Austin.

All in all, the trip went better than I dreamed.

Happy New Year, y’all. May 2022 bring you great things!

-Eric

1 A seven-night Carnival cruise from Rome around the Mediterranean, paid for by Fiddler’s Engineering Excellence Award; a $5000 expense account accompanied the glass trophy.

2 It wasn’t until the very last evening of the cruise that I realized that the coffeeshop on Deck 5 midship offers free pizza all day.

3 Amy H. asked me to be her boyfriend. I had no particular feelings for her, but I said “sure” because I was twelve and what else are you supposed to do?

Thirty years later, I still remember my feeling of wonder that night out on the dock– “I’ve never felt this way before. Is this love after all?” In the cold light of morning I realized, “Nope, that was the beginnings of hypothermia.”

Microsoft Edge’s Many Processes

Chromium-based browsers like Microsoft Edge use a multi-process architecture for reliability and security reasons.

tl;dr

For reliability, Process isolation means that if one process crashes, the entire browser need not go down. For example, if a page on leaky.com has a memory leak that’s so bad that its tab crashes with an out-of-memory error, your other tabs remain functional.

For security, Process Isolation means that each processes’ sandbox can be tailored to the minimal privileges needed for its task, ensuring that in the event of a compromise, the badness is limited to the privileges of that processes’ sandbox. A renderer sandbox cannot read or write files on your disk, for example.

Additionally, Process Isolation enables isolating data by site, such that if a tab at evil.com manages to get arbitrary native code execution (allowing it to read all of the memory in its own process), content from another site (e.g. good.com) is in a different process and thus not accessible to steal.

A blog post from 2020 helps explain what each of Edge’s processes is used for.

You can view all of the active processes in the browser’s task manager, opened by hitting Shift+Esc (or on the system menu shown after hitting Alt+Spacebar):

The new Windows 11 Task Manager exposes similar process detail information from Microsoft Edge. (The API mechanisms used to expose the enhanced process purpose information to the task manager are not yet documented.)

Beyond the information shown in the Task Managers, you can also see information about the security restrictions used to sandbox each process by visiting edge://sandbox:

-Eric

Great Bug Reports via “Recreate My Problem” in Microsoft Edge

When you encounter a problem in Microsoft Edge, you can let the team know about it using the … Menu > Help and Feedback > Send Feedback command.

Clicking this menu item will open Edge’s feedback wizard, which provides tons of options about what information will be submitted along with your bug report. Generally speaking, the more data you provide, the more likely it is that we will be able to resolve the problem you encountered.

  • A good description of the problem (What happened? What did you expect?) is the basis of a good feedback report.
  • If you omit or alter the URL of the page with a problem, it can make it almost impossible for us to do anything about it.
  • If you omit your email address, we won’t be able to contact you to share a workaround or ask for more information.
  • Diagnostic data is often very useful for helping to understand what browser configuration choices (installed extensions, configured permissions, Enterprise policies, etc) might be impacting your scenario.
  • A picture is worth a thousand words, so a screenshot is super-useful for helping us understand what you’re seeing.

The most complete and useful feedback report is one with a Recreate my problem trace attached to it. This trace allows you to reproduce the problem while recording a video of your tab (or screen) and a network trace.

To add the repro trace, click the Recreate my problem button at the bottom of the feedback screen:

…or click the diagnostic data link and click the Recreate my problem tab.

Tick the checkbox to indicate that you understand that the capture is likely to contain personal data (which will be transmitted and stored securely by Microsoft), and then click the Start recording button:

A prompt will appear to allow you to select which portion of the screen to record. You can either choose to record the whole screen, the Edge window (use this one to report problems in the browser’s menus/toolbars/etc) or just the tab exhibiting a problem:

Then, go back to the tab and reproduce the problem you encountered. After doing so, return to the feedback window and choose Stop Recording and Save and Include.

If you later change your mind and want to remove the video or network traffic log before submitting your feedback report, you can do so using the Remove links shown on the Recreate my problem screen.

Finally, click the Send button on your feedback report. Your report will be securely uploaded to Microsoft over HTTPS and will be placed in our secure bug-reporting database. Triage teams will soon examine your report, attempting to reproduce and root cause the problem so that we can fix it for you and everyone else in an update to Microsoft Edge.

Thanks for your help!

-Eric

View-Source

Chromium offers two ways for an end-user to view the source code of a web page: 1) the Developer Tools, and 2) The longstanding view-source viewer. Of these, the Developer Tools have received almost all of the attention over the last decade, but in this post I want to take a quick look at the older view-source feature.

To get to the View Page Source feature, you can use the the Ctrl+U keyboard shortcut, the View page source context menu item, or add view-source: prefix before a URL in the omnibox. No matter what mechanism you use, you’ll get a new tab containing a simple view of the original source code of the page, not the current document’s DOM (which may have been extensively modified by script or the user).

Implementation

Over the years, I’ve landed a few fixes to the View-Source code, including security fixes and user-experience fixes (e.g. support for rendering Dark Mode). The feature doesn’t get a lot of love, but its implementation is interesting. Why?

First, let’s start with the URL. Chromium uses a BrowserURLHandler to rewrite all URLs with a view-source: prefix by stripping off the prefix; a VirtualUrl is set on the navigation entry so the user continues to see the view-source prefix in the omnibox, even as the renderer uses the inner url as the source location:

If the user tries to use view-source against an unsupported scheme like javascript:, the rewriter changes the inner url to about:blank.

If the user invokes View page source on a given document, the WebContents’ ViewSource() function takes the current document’s URL, slaps a view-source: prefix on the front, massages a few things, and opens the resulting URL in a new tab.

Most interestingly, View Source is implemented as a mode for a Document loaded inside Blink– you simply call SetIsViewSource(true) on the Document, and then the ComputeDocumentType sets things up such that the CreateDocument() function creates a HTMLViewSourceDocument instead of a plain HTMLDocument. The ViewSource document uses a simpler parser and it does not do much beyond adding markup to display basic color highlighting on HTML elements and their attributes. The resulting “source-formatted” HTML is displayed as text to the user.

Deprecation?

Generally, products try not to expose multiple features to achieve the same task, especially when they use different code to do it. For a while, the View-Source viewer had some unique features that justified its existence; e.g. it integrated with the XSS Auditor to mark blocks of code that the Auditor had deemed reflected XSS attacks:

However, the XSS Auditor was removed more than two years ago. Similarly, many years ago, a site could link directly to a view-source URL (e.g. a HTML tutorial website might do this), but that capability was removed in 2016 for security reasons.

Nowadays, the viewer offers a subset of the features of the Developer Tools’ Sources tab. The Sources tab offers several cool features, including Pretty Print, red-underlines for script errors, and the ability to switch between all of the sources used by the currently loaded page:

The Elements tab is different than both the Sources tab and the view-source viewer because it reflects the current state of the DOM of the currently-loaded page– modifications made to the page by JavaScript are shown in the Elements tab.

At this point, you might naturally wonder why the old viewer still exists.

Part of the answer is “Well, it’s been there forever, and doesn’t cost too much to maintain.” It’s been proposed that perhaps the CTRL+U shortcut should just open the Developer Tools’ Sources tab. However, there are some non-obvious reasons that the old viewer continues to live on:

  1. For Chromium on Android1, adding the view-source prefix in the omnibox is the only convenient way to view source without first tethering your device to a DevTools instance running on a desktop PC.
  2. The HTMLViewSourceDocument code that makes up the bulk of the feature cannot be deleted because the default XML rendering view depends upon it.

    If the user navigates to an XML file that lacks an XSLT, the XML parser calls GetDocument()->SetIsViewSource(true); which results in the browser rendering the plain XML as a document tree:

May the Source be with you!

-Eric

1 Unfortunately, Chromium on iOS doesn’t support either view-source or the Developer Tools. If the iOS browser syncs a view-source url from another platform, the prefix is simply stripped off.

Spooky: Enhancing Dark Mode in Chromium

I am not really a fan of Dark Mode — I like my screens bright and shiny. But it’s October, and it’s sometimes fun to make things dark and spooky.

Some users of my Show Browser Version extension wanted it to better support Dark Mode– the default text colors didn’t work well when the browser frame was black. I landed a simple change to select bright text colors when the browser is in Dark Mode and called it done.

However, after testing my updates, I lazily left my Dev Channel browser in Dark Mode. Over the following months, I noticed that a surprising number of Chromium’s built-in pages were still rendering in brilliant white. Encountering a bright white screen after surfing around in dark mode can be jarring, to say the least.

I wondered how hard it would be to fix some of these pages. The answer, it turns out, is “it’s mostly trivial.”

To indicate that a page supports dark mode styling, simply add a color-scheme meta tag to the head section of the HTML:

<meta name="color-scheme" content="light dark">

Alternatively, add the following rule to the CSS:

:root {
  color-scheme: light dark;
}

Either change alone is enough for simple pages to render nicely in Dark Mode.

However, many pages use more colors than just the default text and background color, so new colors to be used in Dark Mode must be selected. To adjust colors based on the dark mode preference, use the prefers-color-scheme media query to override the default color in your stylesheet. For example:

#tab-list a:hover {
  color: white;
}

@media (prefers-color-scheme: dark) {
  #tab-list a:hover {
    color: black;
  }
}

Tip: Use CSS Variables for Dynamic Updates

When I first updated the about:Net-Internals page, I added dark mode detection and colors to a JavaScript function used to temporarily highlight a div:

function highlightFade(element) {
  const isDarkMode = 
      window.matchMedia('(prefers-color-scheme: dark)').matches;
  element.style.transitionProperty = 'background-color';
  element.style.transitionDuration = '0s';
  element.style.backgroundColor = 
               isDarkMode ? '#03DCB0' : '#fffccf';
  setTimeout(function() {
    element.style.transitionDuration = '1s';
    element.style.backgroundColor = 
               isDarkMode ? '#121212' : '#fff';
  }, 0);
}

However, I noticed a downside to this approach– if I changed my OS color theme from Light to Dark after this function had run without reloading the page, the old white (#fff) background color lingered on the div— it didn’t update to dark like the rest of the background of the page.

The fix was to specify the light and dark colors using CSS Variables within the stylesheet:

 :root {
  --color-active-tab: black;
  --color-background: #fff;
  --color-highlight: #fffccf;
}

@media (prefers-color-scheme: dark) {
  :root {
    --color-active-tab: white;
    --color-background: #121212;
    --color-highlight:  #03DCB0;
  }
}

…and update the JavaScript function to reference them:

function highlightFade(element) {
  element.style.transitionProperty = 'background-color';
  element.style.transitionDuration = '0s';
  element.style.backgroundColor = "var(--color-highlight)";
  setTimeout(function() {
    element.style.transitionDuration = '1s';
    element.style.backgroundColor = "var(--color-background)";
  }, 0);
}

Now, as the OS switches between light/dark, the CSS variable is recalculated and the colors in the page are updated automatically.

Tip: UA Stylesheets are Special

Updating Chromium’s View-Source to support Dark Mode was a bit trickier, because the styles used in View-Source rendering come from a User-Agent stylesheet. Unlike a regular stylesheet, UA Stylesheets’ support for Media Queries is very limited, and prefers-color-scheme is not allowed.

The solution turns out to be pretty simple: instead of using a media query, we express colors using a special (only usable in UA Stylesheets) function, LightDarkValuePair that returns one of its two color arguments based on whether light or dark theme is in use:

color: -internal-light-dark(#00e, rgb(159, 180, 214));

This allows the View-Source page to respect dark mode without needing a media query.

View-Source also uses a bunch of different text colors for syntax-highlighting, so I had to override those– to avoid picking my own colors, I borrowed the colors from the dark mode support in the Developer Tools.

Changelists and Bugs

There’s still more to do (e.g. text, json) but here’s a list of updates I’ve made so far:

Some of these changes are so simple you can even make them using Chromium’s web-based editor!

-Eric

Appendix: Other Dark Mode trivia

The edge://settings UI for themes is the correct way to set Dark Mode for the browser, and it will affect pages that are designed to respect Dark Mode using the prefers-color-scheme CSS media query. (Today, there’s unfortunately not a good way to select a dark browser UI theme without also changing the content area into dark mode, or vice versa. Some users have requested such a feature.)

A big limitation of Dark Mode when it comes to web content is that there are a great many pages that are not designed to support Dark Mode and as a consequence changing the browser to Dark Mode has no impact at all on the rendering of the website. That’s where the “Auto Dark Mode for Web Contents” feature flag on edge://flags comes into play. That experimental feature forces a site into Dark Mode, replacing its color scheme with one computed by the browser. This works well on some sites, but less well on others. A site can manually opt-out of the automatic mode, but few will.