A content level ExecutionContext class should be pretty trivial to write. We could convert the PM ExecutionContext, or deprecate it in favour of the content/ one, or just keep two parallel impls for a while.
For background, Performance Manager already has some classes that correspond to Frames (FrameNode <-> RenderFrameHost) and Workers (WorkerNode <-> *WorkerHost, representing any type of worker), whose purpose is two-fold:
1. Provide a bit more simplified / uniform interface than RenderFrameHost / ServiceWorkerHost / etc.
2. Originally PM ran on a separate sequence, because we thought that running algorithms over collections of nodes might block the main thread., which meant we needed "proxy classes" for RFH etc. that lived on the main thread. (We've since decided that the overhead of posting data back and forth between main thread and PM sequence is worse, so we're in the middle of moving PM to the main thread, but until that's done and cleaned up the proxy classes are still needed.)
So ExecutionContext is just a class holding a FrameNode or WorkerNode pointer. It's implemented as an abstract class with a few methods returning data common to frames and workers: blink::ExecutionContextToken (basically variant<FrameToken, DedicatedWorkerToken, SharedWorkerToken, ServiceWorkerToken>), URL, process the frame/worker is running in, and process priority. Origin and SiteInstance would be good additions to that list.
It has 2 subclasses, FrameExecutionContext and WorkerExecutionContext, implementing the functions by forwarding to a wrapped FrameNode or WorkerNode.
That design would be easy to copy over to content/ (with 4 subclasses wrapping RenderFrameHost, DedicatedWorkerHost, SharedWorkerHost, ServiceWorkerHost), or it could just be a concrete class with a variant<RenderFrameHost*, DedicatedWorkerHost*, ...>.
PM's ExecutionContext has a registry so that a single wrapping ExecutionContext object exists for a FrameNode. Another approach would be to give ExecutionContext comparators that compare equal iff the wrapped frames / workers are equal, so that calling `ExecutionContext::From(frame)` multiple times creates multiple thin wrapper objects that are logically equal.