Skip to content
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

Closed
annevk opened this issue Nov 20, 2015 · 70 comments
Closed

Translating high resolution timestamps between globals #22

annevk opened this issue Nov 20, 2015 · 70 comments
Assignees
Labels
privacy-tracker Group bringing to attention of Privacy, or tracked by the Privacy Group but not needing response.
Milestone

Comments

@annevk
Copy link
Member

annevk commented Nov 20, 2015

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.

@bzbarsky
Copy link

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?

@igrigorik
Copy link
Member

@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 source, which is one of WindowProxy or MessagePort. Would it make sense to teach translateTime to accept MessagePort? The plumbing is a bit murky to me though.

@annevk
Copy link
Member Author

annevk commented Nov 24, 2015

That does not work, because the MessagePort can be transfered across globals.

@sicking
Copy link

sicking commented Nov 24, 2015

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.

@bzbarsky
Copy link

Of course, if we're hoping to get sub 1/100th microsecond precision

Web timestamps are clamped to 5us anyway, right?

@igrigorik
Copy link
Member

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

@sicking
Copy link

sicking commented Nov 24, 2015

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 global.translateTime(otherGlobal, otherTime) function. And instead have a global.translateToGlobalTime(mytime) function, and a global.translateFromGlobalTime(globalTime) function.

@igrigorik
Copy link
Member

So, as a developer, anytime I want to send a timestamp to a different context I'd have to manually translateToGlobalTime(mytime)? What would that return, some special transferable object? How would the other end know that it received 'global time' vs..

@annevk
Copy link
Member Author

annevk commented Nov 25, 2015

@igrigorik you can postMessage() arbitrary objects, so applications could develop their own conventions.

@sicking
Copy link

sicking commented Nov 25, 2015

So, as a developer, anytime I want to send a timestamp to a different context I'd have to manually translateToGlobalTime(mytime)?

Yes.

What would that return, some special transferable object?

No, it would return a number. Which is offset by X from the number passed to translateToGlobalTime. Where X varies from global to global.

Actually, that points out an even simpler API. We could simply add a performance.globalOffset property. This would return the timestamp of the "zero time" for the current global. That way translating to global time can simply be done by adding var globalTime = localTime + performance.globalOffset. And translating to local time is simply var localTime = globalTime - performance.globalOffset.

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 globalOffset that that global is using. Or if they want to send the globalOffset in an initial message and then have all other messages just send a local time, or whatever they want to do.

How would the other end know that it received 'global time' vs..

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 translateTime proposal. When a global A receives a timestamp from global B, how does A know that the timestamp is in B's timeframe? And that B didn't already transfer the time to A's timeframe?

@igrigorik
Copy link
Member

@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.

@toddreifsteck
Copy link
Member

Adding @DigiTec and @DLehenbauer to review

@sicking
Copy link

sicking commented Nov 25, 2015

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.

@igrigorik
Copy link
Member

@sicking exposing globalOffset means the page can now get a reference to some stable cross-process/session "zero time" ~(Date.now() - globalOffset). E.g. user opens a page that grabs a reference to this global time and reports to server; user opens another incognito window and loads same or another page and said page also reports same global time.. on the server I can link the two with fairly good confidence that it's the same user?

Or, am I barking up the wrong tree here?

@sicking
Copy link

sicking commented Nov 25, 2015

The incognito window would have a different globalOffset since it was opened at a different time.

@sicking
Copy link

sicking commented Nov 25, 2015

Also, the incognito session could use a different time "origin". That's probably a good idea to do yeah.

@igrigorik
Copy link
Member

So, if I'm following, you're suggesting that each process should have own globalOffset? How would we handle out-of-process-iframes?

@natduca
Copy link

natduca commented Dec 1, 2015

@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
https://docs.google.com/document/d/1ypBZPZnshJ612FWAjkkpSBQiII7vYEEDsRkDOkVjQFw/edit#heading=h.7liaw5e2b97r

@sicking
Copy link

sicking commented Dec 1, 2015

Each window has a different globalOffset. It is the difference in time between when the window was opened, and the randomized point in time that the browser picked on startup.

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 translateTime(otherWindowFromAnotherProcess, someTime)?

@natduca
Copy link

natduca commented Dec 1, 2015

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 isHighResolutionTimeALieItsJustWallTimeSorryKThxBai...

@sicking
Copy link

sicking commented Dec 1, 2015

Cool. So it sounds like translateTime and globalOffset faces the same implementation problems. So, if I understand you correctly, those problems doesn't seem like an argument in favor of one or the other API?

Another way to look at it is that globalOffset can use the same implementation strategy as translateTime. Simply do something like:

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 magicGlobalWindow is a window created on startup.

X and Y exist to prevent the webpage from knowing the user's uptime, and to prevent correlating private and non-private windows.

@natduca
Copy link

natduca commented Dec 2, 2015

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?

@igrigorik
Copy link
Member

X and Y exist to prevent the webpage from knowing the user's uptime, and to prevent correlating private and non-private windows.

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:

  • User visits site A, which records said magic number (current time - globaloffset).
  • User visits site B, which computes same magic number and identifies the user. It's a cookie, of sorts.

We can avoid this if we don't expose globalOffset directly.

@sicking
Copy link

sicking commented Dec 4, 2015

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.

@plehegar plehegar added the privacy-tracker Group bringing to attention of Privacy, or tracked by the Privacy Group but not needing response. label Dec 4, 2015
@igrigorik
Copy link
Member

After re-reading this, let's step back and consider the actual use case we're trying to solve:

  • Each context (window/worker) has its own time origin
  • Some context's can communicate with each other via postMessage, MessageChannel, BroadcastChannel.
  • After receiving a message with a timestamp from another context (which has own time origin), the receiver may want to map the received timestamp into its own time origin.

As such, we have the receiver context, a MessageEvent, and a timestamp from the sender. In turn, MessageEvent has source, which is defined as (WindowProxy or MessagePort)? source.

DOMHighResTimeStamp translateTime(DOMHighResTimeStamp time, 
     (WindowProxy or ServiceWorker or Worker or SharedWorker or MessagePort)? timeSource);

^ 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.

@sicking
Copy link

sicking commented Dec 16, 2015

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 navigator.performance.timeOrigin. This object can then be sent along any time a timestamp is sent between contexts. translateTime would then take a timeOrigin as its second argument.

This ends up being similar to the globalOffset proposal. Except rather than exposing a numeric value we'd expose an opaque object which encapsulate that numeric value. The only way you can use the object is to pass it to translateTime which wouldn't allow getting the actual numeric value, just get the delta compared to the current window.

@igrigorik
Copy link
Member

@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.

   +------------------------+
   |                        |
+--v--+      +-----+     +--+--+
|     |      |     |     |     |
|  A  +------>  B  +----->  C  |
|     |      |     |     |     |
+-----+      +-----+     +-----+

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:

  • Receiver has access to the message event, which contains event.data + event.source.
  • Receiver extracts the timestamp and translates it against event.source.

@sicking
Copy link

sicking commented Dec 17, 2015

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 translateTime is called, the "other side" of port might have been transferred to a different thread. Which would result in the translation being done against the wrong global.

In other words, passing a MessagePort to translateTime is a footgun, and one which is racy.

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 t1, t2 and t3 might all have three different values. The "other side" of port might have been transferred across different threads between when the three traslate-calls are executed. This breaks the spirit of JS's run-to-completion.

And sure, we can make the numbers stable, but we can't make them correct.

@sicking
Copy link

sicking commented Dec 18, 2015

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.

@sicking
Copy link

sicking commented Jun 1, 2016

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.

@toddreifsteck
Copy link
Member

@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.)

@sicking
Copy link

sicking commented Jun 1, 2016

I don't see any proposal for reducing privacy implications of .globalOffset, so maybe I've missed it. But the short of it is that .globalOffset seems to enable measuring a clock skew which is constant at least until the browser is restarted. This enables fingerprinting.

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.

@igrigorik
Copy link
Member

But the short of it is that .globalOffset seems to enable measuring a clock skew which is constant at least until the browser is restarted. This enables fingerprinting.

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
[2] https://www.caida.org/publications/papers/2005/fingerprinting/KohnoBroidoClaffy05-devicefingerprinting.pdf

@sicking
Copy link

sicking commented Jun 1, 2016

Keeping .globalOffset unique within a group of "reachable contexts" was what I was originally hoping for. However this thread convinced me that in practice it's very hard to determine what is reachable and the only implementation strategy I can think of is that a group of "reachable contexts" is the whole browsing session between the browser is started and completely shut down.

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.

@igrigorik
Copy link
Member

This is especially true when you consider things like cross-domain window.postMessage combined with the BroadcastChannel API.

Hmm, that's true.

@martinthomson any thoughts or guidance on potential implications of exposing clock-skew? For context, see #22 (comment) and read down.

@martinthomson
Copy link
Member

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.

@igrigorik
Copy link
Member

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?

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.

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.

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?

@sicking
Copy link

sicking commented Jun 3, 2016

As long as @martinthomson is fine with it, then so am I

@martinthomson
Copy link
Member

martinthomson commented Jun 3, 2016

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.

@sicking
Copy link

sicking commented Jun 3, 2016

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 2 from this comment.

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 TimelineOffset constructor. Calling new TimelineOffset returns an object with a single function .get(). When called, it returns the timeline delta between the current global, and the global in which the object was created.

So (new TimelineOffset()).get() always returns 0.

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.

@igrigorik igrigorik added this to the V2: worker support milestone Jun 19, 2016
@igrigorik igrigorik self-assigned this Jun 19, 2016
igrigorik added a commit that referenced this issue Jul 13, 2016
Background discussions in #21 and #22.
@igrigorik
Copy link
Member

@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...

// Allow applications to mint TimeOrigin objects, each of which contains:
// - hidden reference to the "zero time" of the global in which it was created
// - an optional offset value, which is 0 if missing
TimeOrigin performance.timeOrigin(optional DOMHighResTimeStamp offset); 

// TimeOrigin exposes translate method which can map a provided timestamp or perf entry
// against the hidden time origin reference and offset
interface TimeOrigin {
    DOMHighResTimeStamp translate(DOMHighResTimeStamp timestamp);
    PerformanceEntry translate(PerformanceEntry entry);
};

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 TimeOrigin as a first class thing), and the translate() method instead of get() to abstract away the calculations and provide support for translating performance entry objects.

@toddreifsteck @sicking @DigiTec what do you guys think? Crazytalk?

@sicking
Copy link

sicking commented Jul 15, 2016

Sounds good to me. Though can we enable passing an integer to translate() as well?

@igrigorik
Copy link
Member

@sicking can you elaborate on the use case for int?

@sicking
Copy link

sicking commented Jul 15, 2016

Sorry, I missed that DOMHighResTimeStamp is just a plain number, so it covers integers as well.

@toddreifsteck
Copy link
Member

@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.

@sicking
Copy link

sicking commented Jul 26, 2016

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?

@toddreifsteck
Copy link
Member

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?

igrigorik added a commit that referenced this issue Aug 11, 2016
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.
@toddreifsteck
Copy link
Member

Per discussions at TPAC, we are moving concept of translating time origin between contexts to L3.

@toddreifsteck toddreifsteck modified the milestones: Level 3, Level 2 Oct 3, 2016
@igrigorik
Copy link
Member

Closing this thread; let's merge this thread with the in-progress PR.

Summary of TPAC discussion: #29 (comment)

igrigorik added a commit that referenced this issue Oct 24, 2016
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
privacy-tracker Group bringing to attention of Privacy, or tracked by the Privacy Group but not needing response.
Projects
None yet
Development

No branches or pull requests

9 participants