[LAUGHTER] [MUSIC PLAYING] SURMA: So-- JAKE: So. SURMA: It's been a while. JAKE: It has been a while. SURMA: It's-- ah! It's good to-- good to back. JAKE: Is it? SURMA: Eh. JAKE: Well, let me be the-- I'll be the judge of that. SURMA: OK. Well, I thought we
would start by talking about-- we've built two apps. That's basically
where I'm coming from. We have built two apps-- JAKE: --in our lifetime. SURMA: --in-- yes. JAKE: Yeah. SURMA: Nothing every else
we built then, except this. No, in this team that we
have established here-- JAKE: Little team, yep. SURMA: --we built Squoosh. JAKE: Yes. SURMA: We have talked about
that about a million times. JAKE: Mm-hmm. SURMA: One, two, or three? JAKE: Mm-hmm. SURMA: And we
recently built PROXX-- JAKE: Yes. SURMA: --our little Minesweeper
clone, reimagined on the web, modern visuals-- all that. JAKE: Yep. SURMA: And it turns out that
when you build a real app, you sometimes have to make
some workarounds happen-- about the problems that
happen in the odd browser that doesn't quite fit-- JAKE: The odd browser? SURMA: Yes. JAKE: Are you talking about
specifically one browser? SURMA: [LAUGHS] JAKE: All the browsers are
fine except the odd browser. [LAUGHTER] SURMA: That would be a
great name for a browser. JAKE: Yep. we'll
release the Odd Browser. SURMA: Browse safer
with Odd Browser. JAKE: Or maybe it's
odd version numbers. It's like the "Star Trek" films. The odd version numbers
are the bad ones. SURMA: Yeah. JAKE: And the even
ones are fine. SURMA: It would make
a lot sense of how Chrome behaves sometimes. JAKE: Yeah. Yes, it would. [LAUGHTER] SURMA: So I thought I
would go through our code. JAKE: OK. Ah, interesting. SURMA: And figure out the top
four stupidest hacks that-- JAKE: Oh, so this these are
all going to be from PROXX? Or-- SURMA: PROXX and Squoosh. JAKE: PROXX and Squoosh. SURMA: Yes. JAKE: All right. That does sound like a terrible
kid's TV show, doesn't it? [LAUGHS] SURMA: PROXX and Squoosh. JAKE: Welcome to PROXX and
Squoosh. (HIGH VOICE) Yay! (SINGING) Bop-de-bop,
da-da-da, bop-de-bop. SURMA: That's pretty
much how I felt looking at the code samples. JAKE: Excellent. OK. SURMA: So yeah, let's start
with the first thing, which is about the Canvas OOM. JAKE: OOM. SURMA: And-- JAKE: Um-- SURMA: Um-- JAKE: Right? SURMA: You remember this,
because you fixed it. JAKE: Right. SURMA: But I'm still going
to explain what it was. So in PROXX, we render
our game field-- the grid where the tiles are. Some might be mines, or
black holes, in our case. JAKE: Yep. SURMA: Or not black holes. And we have animations running. And so we thought to make
it as fast as possible, we will be using
sprites that have every frame of an animation. They look like this. JAKE: Right. SURMA: And this-- JAKE: Oh, so this is the-- OK, so this is every
frame of the animation. SURMA: Well, it's not-- JAKE: We draw a rectangle
over the top of it, right? For the outer bounds. SURMA: Right. So we crop out one of
the things that we need and put that on the tile. And so in the next
frame, we just crop out the next square
from this sprite sheet and put that on screen. And if that happens every frame,
it looks like an animation. JAKE: Right. SURMA: And that means
you don't actually have to have-- this is
four or five squares that rotate independently. JAKE: Yep. SURMA: We could
do it with a DOM. That would be a lot of
DOM, and a lot of layers, and it would not be fast. JAKE: Yes. SURMA: So in this case,
we're just copying pixels, which is really fast. JAKE: Right. So for every frame, we were
just taking part of that. Is just all of them? SURMA: No. This is a quarter or
half of the frames. JAKE: A quarter or half of them. And so yes, it's every
frame, 60 times a second. We were essentially
taking a different section of this sprite. SURMA: Yeah, and put it
on screen for one tile. JAKE: Right. OK, OK. Yes, yes, yes. SURMA: And that would happen
for each tile of the game field. So that you can see if
that was actual DOM, that would be too expensive. So we used sprites, and-- JAKE: How many-- on screen at
a time, we would have, like, 500 of these going at once. SURMA: Potentially,
or a bit more. JAKE: And doing
that in the DOM-- SURMA: No. Just-- JAKE: Too slow. SURMA: It's not what
it was built for. It's [INAUDIBLE]. JAKE: Right. SURMA: Yes. And because we didn't
know the device that you are running on-- the web can run anywhere. It can be a low-end feature
phone with, like, 320 by 240 pixels. It can be my iMac 5K monitor. JAKE: Oh, show off. Just 'cause you've got
an iMac 5K monitor. SURMA: Yes, I do. I do. JAKE: I've got my-- I know what this sounds like. It's terrible complaining that
I have my poor 32-inch monitor at work. But it's only, like,
one DPI, and you rocking with your massive, 4K-- it's more like-- SURMA: Like 5K? JAKE: 4-- 5K screen, yes. I'm on my 1K. Anyway, I'm jealous,
but carry on. SURMA: Yes. It's great. JAKE: Thanks. SURMA: But basically, we could
either generate one sprite sheet and just have everyone
down on the same sprite sheet, which will be rather big. And we thought, that's not good. So rather, we used Canvas
to generate the sprite sheet on the client side, when you
load the exact resolution that this device needs. JAKE: Yeah. So we use Canvas
2D to draw these. SURMA: Yes. JAKE: But we use Canvas WebGL
to put them on the screen. SURMA: Right. JAKE: Am I crimping
your content? SURMA: No. That's good. JAKE: Oh, OK. SURMA: That's exactly
what's happening. JAKE: Good. SURMA: So we're using Canvas and
just drawing these rectangles, because that is actually what
Canvas 2D is really fast at. JAKE: Yep. SURMA: And we know we can figure
out what the resolution is and what the pixel density is. And so what we have is basically
a function, "generate sprite." JAKE: Right. Yes. SURMA: And so we created
Canvas 2D, get the context. We scale the canvas to match
the device pixel ratio, because if it's a 3X
screen, it should look-- JAKE: Like a 5K monitor. SURMA: Exactly. Pixelated. JAKE: OK. SURMA: And then we
just go through a loop with all the frames. We move our canvas
to a position where this new frame is to 0, 0. And then call this
draw frame function, which just draws a
frame of the animation. So we have a
function that draws-- is capable of drawing every
frame, always at 0, 0. JAKE: Right. SURMA: And then we
have this big canvas, and we move it to draw on it. JAKE: Excellent. SURMA: And then, now we
have a canvas that contains the entire sprite sheet. And we have multiple animations,
so we have multiple sprite sheets. And so this is what we did, and
it worked great on all phones, except-- JAKE: The odd browser. SURMA: The odd browser. JAKE: Which in this case-- SURMA: In this case,
it was iOS Safari. JAKE: It was. SURMA: Which for some reason,
decided, you know what? You're creating
too many canvases. I'm going to kill your page. JAKE: It took us quite
a while to figure out that that's what was going. SURMA: Yeah. JAKE: Like, all we
saw was a full crash. SURMA: Literally
saying, this page has been reloaded
due to a problem. And it would do that
four or five times, and then it would give up. JAKE: Yes. SURMA: Not a great experience. JAKE: Not a great experience. SURMA: But eventually, we
figured out that, well, so far we have to do this-- it
is probably a memory pressure thing that we were-- because
all these canvases are backed by a frame bot, or the chunk of
memory represents the pixels. And having too many of
those, apparently, Safari is like, you know what? This is over my threshold. I'm going to kill this page. JAKE: If you get a tab in a
browser that just crashes out, I would say, more than
50% of time, it's memory. SURMA: Yeah. JAKE: Right? You've either found a
bug in the browser that has caused it to crash
out, but the time that a browser will
willfully crash out is when you've
run out of memory. SURMA: Something-- yeah. JAKE: It's like, I
need that memory back, else the whole system
is going to-- yeah. SURMA: So the thing that we
thought, how do we fix it, we-- or I guess,
you were like, well, but I can load images that
have the same dimensions as our canvas. So our sprite sheet was like
2,000 by 2,000 pixels, I think. JAKE: Yeah. SURMA: We can load an image
on a website like that. Why is the canvas
suddenly too much? JAKE: You can load loads
of images like that. SURMA: And so here
is our great fix. JAKE: Right. SURMA: And for having a sprite,
we generate that sprite. We turn it into a blob. JAKE: Yes, we do. SURMA: We turn that
blob into an image. JAKE: Yes, we did. SURMA: And we keep that image. JAKE: And then we
keep the image. SURMA: And so we only ever
have one canvas at a time. JAKE: Yeah. SURMA: And so
Safari is now fine. JAKE: And to do this-- oh, it's
really annoying, because we are having to encode as PNG. SURMA: Yep, just to decode it. JAKE: So then we
can decode as PN-- SURMA: [LAUGHS] JAKE: And now-- but now, it's-- SURMA: Which surely must
be more memory pressure. They're just keeping
the canvas around. JAKE: Yes, but presumably
the Safari on iOS must be smarter with images
than it is with canvases. SURMA: Maybe. JAKE: Like the image
it can put down onto disk, rather with memory
or something like that, and-- SURMA: It fixed it. It now works perfectly fine. JAKE: Oh, it was so annoying And
it was purely just by finding something that we knew-- something that was similar
that the browser could do. You can still write
images to a canvas. You can write canvases
or images to canvases, but canvases, it crashed. Images, it didn't. SURMA: OK. JAKE: Fine. SURMA: Told you, this is
going to be stupid hacks. JAKE: Carry on. Yes. OK. SURMA: So the next one is-- JAKE: Worker Murder. SURMA: --is the Worker Murder. Because sometimes, workers
are allowed to die. JAKE: [LAUGHS] SURMA: Sometimes, they are not. JAKE: OK. SURMA: And it still happens. So we-- both Squoosh and
PROXX have off-main thread architecture going-- JAKE: Yes, they do. SURMA: Like, they do
work off main thread. JAKE: Yes. SURMA: We didn't run into
this problem with Squoosh, and we will figure out
why throughout this. JAKE: Oh. SURMA: So what we do is
usually, server worker, and we often use Comlink
to make the interaction with the worker really nice. But for some reason,
when we did this, our app would stop working. Just not react anymore. JAKE: In the odd browser. SURMA: In the odd browser-- in iOS Safari. JAKE: Which was iOS Safari. And we should say, many
browsers have bugs, right? It just so happened that
we encountered most of them on iOS Safari. SURMA: Yeah. JAKE: Not on Safari desktop. SURMA: Also-- No,
only iOS Safari. JAKE: Only iOS Safari. Right. SURMA: Also-- yeah. The reason I picked these
is because the solutions are so stupid. Like-- right? JAKE: Yes. SURMA: We have bugs
in other browsers, but their solution is like,
oh, they don't support blah. Just do dash [INAUDIBLE]. JAKE: Put an if around it. SURMA: --or something. But here, we actually had
to be creative and come up with very weird solutions. JAKE: Yes. SURMA: So basically,
what we figured out is that the worker got killed. JAKE: Just got
killed, didn't it? SURMA: Even though it still
existed and had things to do, it was basically
waiting for a message. And then it's spec compliant
to wait for a message. But iOS was like, you know what? The workers are expensive. We want that memory back. And [POPS LIPS] JAKE: So then, we were kind
of like, well, what is this-- well, we wanted to
confirm our theory. SURMA: Yeah. JAKE: Is this
worker being killed? And so what if it was like,
well, let's do things like, we'll ping the worker. So we can see those
messages coming back. Here it is. [LAUGHTER] I see. SURMA: So we did that. JAKE: Yes. SURMA: And it made it better. JAKE: It made it better. SURMA: But it didn't
quite solve it. So basically,
sending a message was like, oh, it seems to
be around now, but then on a more constrained
iOS device, it would still get killed. And then we're
like, you know what? We're just going to send
a message every three seconds, because you know-- JAKE: Yes, we did. SURMA: --and we did. And that made it even
better, but we still found corner cases where-- JAKE: Well, we settled on
this fix for a long time. SURMA: Yeah. JAKE: This was
our fix, because-- and it was really just as
we were trying to debug it-- this trying to debug it
made it kind of work again. And we thought, ah. No one touch it. [LAUGHTER] Walk-- SURMA: This is OK. JAKE: Walk away slowly. SURMA: [LAUGHS] JAKE: That's our solution. Fine. SURMA: And then we still,
eventually found some corner cases where it would not work. The worker would
still disappear. JAKE: Yep. SURMA: And-- JAKE: Well, our theory
was it was disappearing. We could never fully confirm it. SURMA: No. It-- yeah. Because the initial problem
is that the simulator-- the iOS simulator is very
slow, and it's actually behaving differently from
an actual device in terms of how iOS Safari behaves. JAKE: Yes. SURMA: And on
earlier devices, we didn't have proper
[INAUDIBLE] forwarding, and so we always
had to deploy it. And that slowed down the
entire debugging process. It was cumbersome,
to say the least. JAKE: It always--
debugging this, every time we saw
the bug again it felt like, during the
process of debugging it, it would start working again. SURMA: Yeah. JAKE: And we'd be like, right. Let's just-- let's move away. And then it would fall over
again when we weren't looking. SURMA: And then we
actually found something that seemed to resolve it. We still think it
has resolved it, and it's so stupid
that I kind of love it. Instead of pinging it
every three seconds-- JAKE: [HUMMING FANFARE] SURMA: We just put
it on the global. JAKE: Yeah, we did. And again, this was
me trying to debug it. And I thought, well, if
I can put it on a global, then I can just
debug the console. SURMA: You can de-console it. JAKE: And-- SURMA: But then it disappeared. JAKE: And then I was
like, I couldn't-- I was so angry. It was like, I can't
recreate the problem. And it wasn't till, hang on. Let me just remove
that one line I added. Bug comes back. SURMA: To be fair, we did try
to make some reduced test cases, and it never happened. So our theory is,
because Comlink uses-- JAKE: Proxies. SURMA: --proxies. That the combination of
sending workers when something is done by a proxy, that there's
something about that garbage collection marking
algorithm that might just be a weird corner case bug,
which is why the workers get collected when you use Comlink. JAKE: Yeah. It thinks, because it's
going through a proxy, it's not counting that as
a reference when it should. SURMA: Maybe. That's a hunch. But yeah, it could-- JAKE: We're only guessing. Brilliant. SURMA: But putting in a
global, that solved it. JAKE: Ta-da. SURMA: It looks even
worse in our code, because we are using TypeScript. So of course, we
have to ensure type. But this is actually OK? JAKE: Yeah, yeah. SURMA: It's great. JAKE: Chill. TypeScript, chill. We know we're doing
the bad thing. SURMA: We know what we're doing. JAKE: Yeah. SURMA: Number three-- I thought
it was quite interesting. JAKE: Oh, yes. SURMA: It's event inheritance. JAKE: I know what this one is. SURMA: So in Polymer
0.5 and 1.0 time, everything was
around custom events. JAKE: Yes. SURMA: CustomEvent class, and
putting your data in there and bubbling up. CustomEvent since has
been kind of deprecated. It's still around. I don't think it's implemented
everywhere, actually. JAKE: Well, CustomEvent
is weird, isn't it? Because when you
do New CustomEvent, you get an object to put
on whatever you want. SURMA: Yeah. JAKE: Which a lot of
stuff is moving away from these "whatever you want"
objects, especially if you're using things like TypeScript. You want to use an
event of a type. SURMA: Right. And so what you
usually end up doing is you build your own event
that extends an event. JAKE: That looks all fine to me. Yeah. SURMA: So same arguments. Now you can just put stuff-- whatever you want to on
the event in a dictionary and grab the things
that you want. And now you can dispatch that
event, like any normal event. JAKE: Yep. SURMA: Great. Turns out, though, that
in Edge and iOS Safari-- Edge is also-- JAKE: Yep. It's all Safari. SURMA: All Safari this
time, not just iOS. JAKE: And Edge. Yep. SURMA: This extend event
turns out to not actually be inheriting from events. JAKE: It doesn't, does it? SURMA: No. JAKE: Yep. SURMA: And so in Squoosh,
we had a very simple fix. We basically just fixed it. After we have an instance
created with New, we just threw it
into another function that just fixed the prototype. JAKE: Fixed-- yes. SURMA: Just say, this
is your prototype. This is-- you are
actually in Event. JAKE: Yep. Swap that over. This bug-- the
behavior-- it comes from a very weird feature of
JavaScript, where a constructor can return a value. SURMA: Right. JAKE: Don't say "right"
like that's normal. SURMA: Right, question
mark, dot dot dot. JAKE: OK, thank you. [LAUGHTER] Well, a constructive function--
so you can return, like-- SURMA: Four. JAKE: Yes. SURMA: [LAUGHS] JAKE: Or any number of
objects or something. And so when you do New
Thing, you actually get a completely
different object back. SURMA: Yeah. JAKE: And JavaScript is like-- SURMA: [LAUGHS] JAKE: Why not? Why not? Do what you want. And we think that's what
the implementations in Edge and Safari are doing into-- SURMA: Right. OK. JAKE: That's my theory, anyway. SURMA: Well, so we used our
own events in PROXX as well. JAKE: Yes. SURMA: And so I just
ripped this out of Squoosh, put it in PROXX, and
it still didn't work. JAKE: What? SURMA: Because it turns
out that in PROXX, we're compiling to ES5. JAKE: Oh! SURMA: Because we are
targeting Firefox 48 and older browsers, and so we needed ES5. And the way that both TypeScript
and also Babel, I guess, transpile classes to
functions with prototypes isn't quite compatible with
how the browsers expect the new event
constructor to work. JAKE: Oh! SURMA: So it literally just
panics at construction. JAKE: I see. SURMA: So what we
did instead in PROXX, is we just rolled our
own factory functions. JAKE: State change
event factory. Brilliant. SURMA: So instead of saying,
creating a proper state change event function or class,
we just wrote a function that creates a new event and
then changes the prototype. JAKE: Right. So we went from doing
this horrible hack for just Edge and Safari
to doing it for everyone. SURMA: Right. JAKE: Brilliant. Fine. SURMA: It still works, but it's
just a function that exists, and it wasn't
necessary, because ES5-- JAKE: That is-- I think that's-- it's on
the developer coat of arms, isn't it? "But it works." SURMA: Yeah, pretty much. JAKE: It's fine. SURMA: Pretty much. JAKE: Excellent. SURMA: I tried, I think, for an
hour to somehow actually have a proper ES5
constructor function that is a proper event. I couldn't get it to work. JAKE: I-- yeah. SURMA: I just couldn't
get it to work. And so I was like, all right. You know what? Factory function. Just one place where [INAUDIBLE]
you just only have one custom event, I think. JAKE: Mm-hmm. SURMA: Don't care enough. JAKE: Job done. SURMA: Right. JAKE: Fine. OK. SURMA: And for the grand finale,
we have the hide-y address bar. JAKE: The hide-y address bar. Excellent. OK. SURMA: Everybody probably knows
that if you are on like a blog website with your mobile
browser and you scroll down, the address bar-- SURMA: Hides. JAKE: Hide-y. Yeah. SURMA: Hide-y. JAKE: It's hiding. SURMA: And you scroll back
up, it comes back into view. JAKE: Come back-y. SURMA: The problem with that
is that different browsers seem to be behaving differently--
what 100% height or what 100 vh means
in this context. JAKE: Yes. SURMA: Some browsers
seem to think the address box is overlay. Some don't. Sometimes only the vh
for vh is on overlay, but the percent it is. And then there is web views,
which again behave differently on both Android and iOS. And we ran into
this with our game, because we wanted to
use natural scrolling. So our game field is actually
the actual site content, and the bars are
just fixed position. So it was using normal
browser scrolling. So that means if you scroll
down, [INAUDIBLE] will hide. Everything would re-jiggle
and jank, and that was stupid. JAKE: Yeah. SURMA: Then you scroll back up. Everything came back into view. And so we thought,
you know what? We're going to disable
the hide-y address bar. JAKE: So the differences
across browsers at this point-- I think iOS and Chrome have
made a conscious decision to try and do the same
thing as each other. But iOS and Chrome both
do different things with different bits of CSS. So-- ah. I'm trying to remember-- SURMA: Ah! JAKE: Ah! Bah! Bah! SURMA: [LAUGHS] JAKE: I'm trying to remember
off the top of my head. It's 100 vh-- means a particular
thing, unless the element is position fixed. In which case, it means
a different thing. And that is-- position
fixed changes the rules, and that's what we were seeing. So we had the bottom bar
that was position fixed, and that was kind of like-- SURMA: Oh, it was
jumping around. JAKE: Jumping around. SURMA: Either way,
it was annoying. JAKE: It was annoying. Yeah. SURMA: And so we
thought the easiest way to fix this is just to do
scrolling on an element. JAKE: Yeah. We abandoned the full page
natural scrolling thing. SURMA: So what we did, we
used our main element, which is where the game field was in. Or our entire UI
was in, basically. Just [INAUDIBLE] made it
the size of the screen, overflow auto, boom. Scrolling. JAKE: [GROWLS] SURMA: I know. JAKE: I hate this. I hate that it's 2019, and
we still have to do a special thing to make scrolling
work properly, and it's-- SURMA: It's the web. JAKE: OK. [SIGHS] SURMA: It's homely. JAKE: Right. Yes. SURMA: And so this-- JAKE: This is a
bit new, isn't it? I didn't know much about
this until we did it. SURMA: Yeah. This is basically
saying to overscroll. JAKE: Yes. SURMA: The thing where when
you scroll, you're at the top and try to scroll further top,
it bends it down a little bit or snaps back into view. JAKE: Like a shadow
or something. SURMA: Which in this
case, we didn't want. So we [INAUDIBLE]. JAKE: Yes. SURMA: But it's more about-- it's a screen-filling
element that can scroll, and it fixed it in
iOS Safari, I think. JAKE: Mm-hmm. SURMA: But Chrome,
the odd browser-- JAKE: Oh. SURMA: --was like,
you know what? This looks like you're trying-- JAKE: I'd forgotten about this. I blocked this out. SURMA: [LAUGHS]
Chrome was like, I think you're trying
to do a page scroller, so I'm going to give you
our great hide-y address bar behavior for free. JAKE: It did. Oh, of course it did. I really had blocked this out. You've gone through
all the commits then, where we've
just gone through-- SURMA: No. I remembered this. JAKE: You actually
remembered this. SURMA: Yeah. It's first one I put in. JAKE: Oh, brilliant. OK. Yeah. SURMA: And if you remember our
fix, you know how great it is. JAKE: Yes, I do. SURMA: We looked at this line-- JAKE: Yep, I did. SURMA: And we said,
we can spare a pixel. JAKE: There we go. [LAUGHTER] SURMA: So this was clearly
Chrome trying to be helpful-- trying to do the right thing. JAKE: And failing. It was-- SURMA: It made us so angry. JAKE: Yes. SURMA: Because we still
got that address bar hide-y janky jumpy thing, and
we didn't want it at all. JAKE: Yes. It's like, we're doing
this so you don't do that. SURMA: So there must be a
heuristic somewhere in our code that says, if you're
position [INAUDIBLE] fills the entire screen, do the
[INAUDIBLE] hide-y bar attached to this scroller. You know, the root scroller. JAKE: Yes. SURMA: And so we
added the pixel. JAKE: We added the pixel,
and that was enough. Oh, my-- oh. Ah. SURMA: I like it. It's still PTSD going on here-- JAKE: I am-- SURMA: --from the weird,
weird workarounds that we have encountered-- JAKE: Yes. SURMA: --during-- JAKE: I'd forgotten about that. We should file a bug
for that, because-- or we should have a
way to avoid that. So that's four stupid hacks
to fix four different bro-- is it four browsers? SURMA: Two or three. JAKE: Three browsers. And-- yeah. SURMA: It's really just
a web developer life. This is, I feel
like what, if you're trying to get
something out in time, you have to make these
stupid shortcuts. JAKE: You've got the
correct path, but-- SURMA: You have the fast path. JAKE: The road's out. SURMA: [LAUGHS] JAKE: And you've got
to-- (MOANING) blah. SURMA: Yep. JAKE: That's web development. That (MOANING) blah. SURMA: Bottom one pixel. That's what that is. [MUSIC PLAYING] Yes. JAKE: Because that was stupid. SURMA: Right. JAKE: That's what I'm saying. SURMA: That's a good
ending for this episode. JAKE: [LAUGHS] SURMA: Stupid. JAKE: Four stupid things
by four stupid browsers. Actually, we're only blaming
two browsers, isn't it?