-
Notifications
You must be signed in to change notification settings - Fork 25
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Translating high resolution timestamps between globals #22
Comments
This is a situation where the setup Gecko had with dedicated workers inheriting the time origin of their parent is much cleaner, imo. Perhaps we should keep doing that for nested workers? |
@bzbarsky that seems like a tangent and doesn't address @annevk's issue? E.g. multiple documents communicating via sharedworker, nested iframes, etc? That said, not sure what the best solution here is... MessageEvent has |
That does not work, because the |
One solution here would be to introduce some form of global timeline. Then have functions for transferring timestamps from local time to global time, and from global time to local time. Keeping a global timestamp with microsecond precision is doable in a 64bit float for well over 200 years if I'm doing my math correctly (2^53 microseconds = 285 years). So a reasonable implementation strategy could be for the browser to pick a random timestamp +/- 50 years from when the user started the browser and then use that timestamp as the global origin until the browser is restarted. This way pages could always use global timestamps when communicating with each other, but use local timestamps when reporting results back to the server. Of course, if we're hoping to get sub 1/100th microsecond precision then that strategy wouldn't work. But even then we could pass around global timestamps as opaque clonable objects. |
Web timestamps are clamped to 5us anyway, right? |
So, assuming we have a global monotonic clock (is this available on all platforms?), the time origin could snapshot a reference to that time (speaking of.. #21) and translateTime would do the math under the hood. I don't think we would ever need to expose the actual global timestamp? /cc @esprehn @ojanvafai |
You don't need any more platform support than we already have. We already need a monotonic clock which spans the the period of time when any window is open. I.e. in practice which spans the time between when the browser starts and when the browser is shut down. So all you need to do is to generate a random number and add to that timer. Note that what I'm proposing is removing the |
So, as a developer, anytime I want to send a timestamp to a different context I'd have to manually |
@igrigorik you can |
Yes.
No, it would return a number. Which is offset by X from the number passed to Actually, that points out an even simpler API. We could simply add a Application authors can then choose if they want to use global timestamps when transferring data to each other, or if they want to transfer the local time along with the
The same way that it would know that data is received is a timestamp at all. I.e. through application defined conventions. The exact same problem exists with the current |
@sicking interesting, I like that. That said, any privacy concerns here? Seems like exposing this directly to the application would provide some new fingerprinting capabilities across different sessions, etc. |
Adding @DigiTec and @DLehenbauer to review |
What information are you thinking of in particular? The only new information that I can think of that would be exposed by this is tiny adjustments to the user's clock while the user doesn't have the website open. |
@sicking exposing Or, am I barking up the wrong tree here? |
The incognito window would have a different |
Also, the incognito session could use a different time "origin". That's probably a good idea to do yeah. |
So, if I'm following, you're suggesting that each process should have own |
@sicking I absolutely love the idea of global time, at least assuming we can work through the security stuffs, and one other thorny issue: global timestamps dont work reliably on windows. Getting a timestamp that is reliable across processes on not-very-old-but-not-current Windows machines is something that has plagued us for years. Basically, on some windows machine+version combinations, you can't get a system-global high resolution timestamp, only a process-global high resolution timestamp. On those machines, if you ask for the high resolution time from different processes, the OS will actually give you a completely timebase. The wall timer is typically a bad fallback, as its quantized to the point of uselessness and non-monotonic. Most apis we've exposed to the web now are "safe" because everything still runs in a single process for the most part. As we get to more sophisticated worker architectures, that process isolation guarantee breaks. That'd then imply that either global time is sometimes not available (window.performance.isGlobalTimeSupported), or... well I'm not sure really. Thats' why I thought i'd jump In. :) If its useful, this is a doc that @zeptonaut from our team wrote about the problems with timestamps |
Each window has a different Each "world", like private browsing, picks a separate randomized point in time. If your browser supports having separate private browsing windows that are private from each other, each such window picks a different randomized point in time. I don't have any answers with regards to multiprocess, as I was not aware of the difficulties involved in that. How were we planning on handling |
Honestly, I didn't know about translateTime. If I had been paying attention I would have probably mentioned it there. I think the only two options we have are for it to fail, or for us to redefine DOMHighResTimestamp to fall back to wall clock in those cases and possibly add a getter on window.performance thats something like |
Cool. So it sounds like Another way to look at it is that Object.defineProperty(performance, "globalOffset", {
enumerable: true,
get: function() {
return magicGlobalWindow.translateTime(window, 0) +
(isPrivateBrowsing(window) ? X : Y);
}
} Where X and Y are random numbers picked at startup. And where X and Y exist to prevent the webpage from knowing the user's uptime, and to prevent correlating private and non-private windows. |
Yeah, that looks about right. I think we want to split out a fresh bug that is something ~== "translateTime is impossible to implement on some systems" and we can figure out there if we want it to throw or have another separate system to just warn folks away from the whole space? |
I still think this leaks unnecessary data between windows. Take private context out of equation for a second.. Knowing the offset allows multiple independent windows to compute the same random number, thus revealing data about the user. More concretely:
We can avoid this if we don't expose globalOffset directly. |
Yeah, I think you are right. If we can figure out which globals are reachable from one another, we can make sure to only keep the same magic number for those globals. But off the top of my head I don't know how to define that. Though it might be possible. |
After re-reading this, let's step back and consider the actual use case we're trying to solve:
As such, we have the receiver context, a MessageEvent, and a timestamp from the sender. In turn, MessageEvent has source, which is defined as
^ I think that covers it (including #23). The application receives a message event, extracts the timestamp from event.data, and passes it to translateTime with second parameter equal to event.source. Does that work? p.s. in absence of above mechanism the application can do own translation by rebasing its HRTimestamp against Date.now() and sending that to the receiver, which can then convert it against its own timeline... albeit with loss of precision due to Date.now() conversion + any messaging latency. |
A MessagePort can be translated between contexts, so it's not possible to ask what the timeorigin for a MessagePort belongs to. Also, a lot of contexts can use a BroadcastChannel, so the proposed approach doesn't work for BroadcastChannel-based messaging. What we could do is something like having an opaque object like This ends up being similar to the |
@sicking actually, is the transfer case even relevant to our use case? Consider the following case where we have three different contexts (A, B, C): A sends a message+port to B; B does some stuff and sends message+port from A to C; C does more stuff and postMessage's to A.
When C receives the message that contains a timestamp, it has no idea whether that timestamp came from A, B, or elsewhere; the fact that there is a port being transferred is completely beside the point. As such, it seems like the only reasonable approach here is to recommend (we can't enforce this) that every context translates any received timestamp into own origin before/if it wants to forward it to someone else:
|
I don't follow what you are saying. What I'm saying is that something like port.onmessage = (e) => {
var translatedTime = translateTime(e.data.timestamp, port);
log(e.data.message, translatedTime);
} because by the time that In other words, passing a Another way to look at it is that if you did var port = getPortFromSomewhere();
var t1 = translateTime(0, port);
var t2 = translateTime(0, port);
var t3 = translateTime(0, port); the three variables And sure, we can make the numbers stable, but we can't make them correct. |
Which is also why a recommendation to translate a timestamp to your own context before forwarding doesn't help. If you receive a timestamp from a MessagePort you literally can't translate it to anything in a correct non-racy way. This is even more true if you receive a timestamp from a BroadcastChannel since any number of contexts could have sent the message to the channel. I.e. this is not a problem of forwarded timestamps. This is a problem of receiving timestamps through a MessagePort or a BroadcastChannel. |
Sorry, i meant "concerning privacy implications". If the ergonomics of wrapping a list of items is bad, then that's a smaller problem than privacy to me. And possibly a problem that can be fixed with changes to the API. |
@sicking , can you clarify the specific privacy implications? I believe we've worked around them above and would love a chance to address the specific concerns directly. (It is my opinion that making globalOffset relative to the epoch is the simplest solution to help preserve privacy but I haven't yet convinced Ilya.) |
I don't see any proposal for reducing privacy implications of Though I'm not really an expert at these things and I suggest contacting the mozilla security team. A good place to do so is the webappsec WG. |
I think this assumes that we're talking about globalOffset relative to epoch -- correct? Doing a bit of research around this, seems like this is already possible via TCP timestamps and such [1], [2]. That said, yeah, we should check in with the security folks on this particular point. Alternatively, what about the other option we explored earlier, where each group of "reachable contexts" gets their own (unique) globalOffset? [1] http://sec.cs.ucl.ac.uk/users/smurdoch/talks/eurobsdcon07hotornot.pdf |
Keeping This is especially true when you consider things like cross-domain window.postMessage combined with the BroadcastChannel API. @martinthomson would be a good person to run this by to get a mozilla opinion. |
Hmm, that's true. @martinthomson any thoughts or guidance on potential implications of exposing clock-skew? For context, see #22 (comment) and read down. |
The discussion here doesn't really establish for me the case for learning the relative timing of events in one context with events in another. Is there any reason that this couldn't be reduced to passing Date() objects? The relative position of events in the same context could be learned, but their relationship to events in other contexts would be subject to the 1ms rounding (and volatility) that an absolute time translation causes. The questions of clock skew are basically irrelevant if you accept that you have 5μs precision on the clock. If you look at the papers on measuring clock skew, you learn that skew is recovered via statistical analysis of much less granular values (1-2kHz in the hot or not paper). In the end, I believe that protecting skew requires that you induce a skew of your own that masks any underlying skew. If you accept that premise, and also intend to present different skew to different origins (to avoid tracking based on the unique skew signature you just created), then you have to accept that you can't let two origins communicate about their relative time without some intermediation. Of course, we are going to let both origins send messages to servers without extra latency, so we have to accept that gross differences will be easily corrected. |
Yes, all the same reasons we outlined for high resolution timestamps here: http://w3c.github.io/hr-time/#introduction. A trivial example is Worker process attempting to drive an animation loop/audio synchronization/etc within another context -- 1ms is not sufficient.
Right, that makes sense, thank you. @sicking @toddreifsteck given the above, I propose we move forward with .globalOffset relative to epoch time. I can draft a PR for review and we can run it by more folks for a sanity check. Any objections? |
As long as @martinthomson is fine with it, then so am I |
I can live with this, with one more question. Thinking about this a little more, is there any reason that this needs to be global, and not per-origin? I say that because timing events in other origins is part of why this a security risk. Even though this would require that the other origin were to cooperate, we know that with complex and subtle things like this, people have a hard time internalizing risks and that cooperation might be given without fully comprehending the consequences. Would per-origin high resolution timing be acceptable? I can imagine that it would work for at least the trivial example. |
per-origin is probably not acceptable, since we'll have situations where google.com and youtube.com will want to collaborate on measuring timing. But do note that there is an alternative proposal. Which is that we create an opaque object which you can stick a timestamp into. You can then transfer that opaque object to another global using postMessage. The object then has a getter which enables extracting a timestamp and which automatically translates the timestamp to the new global timeline. That is option The main downside with this solution is API awkwardness if you're transferring lots of timestamps. Though it occurs to me that there is a simpler version of that solution. Imagine that we expose a So But calling x = new TimelineOffset();
someplace.postMessage({ offset: x,
timeStamps: [a, b, c],
entries: performance.getAllTheEntries()}); Allows you to transfer a bunch of local time information, as well as a single object that allows all of that time information to be translated by the receiver. |
@sicking apologies, missed your earlier comment.. it's an interesting proposal! As I'm noodling it over this morning, it's reminding me of a conversation I've had a few times now in which developers lament that a single time origin is "not sufficient" in a world where we have single page apps that are rarely reloaded - i.e. an application may want to have one or more "time origins" against which timestamps can be translated (e.g. one for each transition/navigation which happens without reloading the page). Today this is addressed by capturing a reference to a point in time and then doing manual translation if necessary.. simple enough and it works, but as I look at your sketch above, it's making me wonder if perhaps there may be a more general solution here. Roughly...
In code... // obtain a reference to the time origin ("zero time") of the current global
// ... this can be sent to another context
var defaultTimeOrigin = performance.timeOrigin();
function loadNextScreen(destination) {
// application can mint custom "time origins" against which timestamps can be translated
// by providing a custom offset (e.g. timestamp returned by performance.now())
var customTimeOrigin = performance.timeOrigin(performance.now())
// ... do work ...
// by default all metrics are still recorded against zero time of the current global
performance.mark('finished')
// application can translate timestamps and entries against any time origin..
[mark, resource, ...].forEach(e => {
// ~translate() algorithm for a timestamp:
// offset = current_global_zerotime - provided_timeorigin_zerotime
// + provided_timeorigin_offset
// return timestamp + offset
console.log(`Timestamps with respect to custom time origin:
${customTimeOrigin.translate(e)}`)
// send timestamps to another context...
someplace.postMessage({timeOrigin: customTimeOrigin, timestamps: [...]});
});
}
// receiver
source.port.onmessage = function(event) {
var msg = event.data;
// translate timestamps into document's time origin
var sourceOrigin = msg.timeOrigin;
msg.timestamps.forEach(t => {
console.log(`Time in source timeorigin: ${t}, translated to current timeorigin:
${sourceOrigin.translate(t)}`) })
});
} I think this is basically identical to what you proposed above, except under a different name (I'm exposing @toddreifsteck @sicking @DigiTec what do you guys think? Crazytalk? |
Sounds good to me. Though can we enable passing an integer to |
@sicking can you elaborate on the use case for int? |
Sorry, I missed that DOMHighResTimeStamp is just a plain number, so it covers integers as well. |
@sicking @igrigorik I think that the performance.timeOrigin() type proposal is that it can't be transferred to the cloud. performance.timeOrigin CAN be returned and transferred to the cloud. |
I don't really understand what you are saying can't be done? Can you provide a use case and a detailed description of what you're trying to do? |
I have a Service Worker and a Global Context. The GlobalContext performs a fetch and sends an ID for its "context" over. After that, the Global Context sends its entire performance timeline to FooAnalytics including ID and timeOrigin. ServiceWorker sends up its entire performance timeline to FooAnalytics including ID and timeOrigin. The proposal in the link doesn't allow the offset to be retrieved. It only allows cross-context translations. To achieve this scenario with an opaque timeOrigin object, the GlobalContext couldn't just send an ID.. it would have to send a timeOrigin to the ServiceWorker and it would then have to perform client translations. Is that a bit clearer? |
The returned value is the global time of the "zero time" of the time origin. This allows multiple contexts, each with own time origin, to translate timestamps with sub-millisecond resolution, either by communicating their global time to the other context, or first translating their timestamps against the global time of their time origin. Related discussions in #21 and #22. A polyfill for this is ~: Date.now()-performance.now(), modulo clock skew and adjustments that Date.now() is subject to.
Per discussions at TPAC, we are moving concept of translating time origin between contexts to L3. |
Closing this thread; let's merge this thread with the in-progress PR. Summary of TPAC discussion: #29 (comment) |
The returned value is the global time of the "zero time" of the time origin. This allows multiple contexts, each with own time origin, to translate timestamps with sub-millisecond resolution, either by communicating their global time to the other context, or first translating their timestamps against the global time of their time origin. Related discussions in #21 and #22. A polyfill for this is ~: Date.now()-performance.now(), modulo clock skew and adjustments that Date.now() is subject to.
Note (igrigorik), original title: "translateTime does not work for nested workers"
Through
MessageChannel
a document can directly communicate with a worker that it otherwise knows nothing about.translateTime
doesn't really work with that existing capability model.The text was updated successfully, but these errors were encountered: