 So it's been a while. It has been a while. It's good to be back. Is it? Let me be the judge of that. Well, I thought we would start by talking about, we have built two apps. That's basically what I'm coming from. We have built two apps. In our lifetime. Yes, nothing ever else we built than accept this. No, in this team that we have established here. Little team, yeah. We built Squoosh. Yes. We have talked about that about a million times, one, two, three. And we recently built Prox. Yes. Minesweeper, clone, reimagined, on the web, modern visuals, all that. Yep. And it turns out that when you build a real app, you sometimes have to make some workarounds happen. But the problems that happen in the odd browser that doesn't quite be here. The odd browser. Are you talking about specifically one browser? All the browsers are fine, except the odd browser. That would be a great name for a browser. Yeah, we'll release the odd browser. Brow Safer with odd browser. Or maybe it's odd version numbers. It's like the Star Trek films, like the odd version numbers are the bad ones, and even ones are fine. It would make a lot of sense of how Chrome behaves sometimes. Yeah, yes it would. So I thought I would go through our code. OK. Ah, interesting. Figure out the top four stupidest hacks. Oh, so these are all going to be from Prox? Prox and Squoosh. Prox and Squoosh. Yes. That does sound like a terrible kid's TV show, doesn't it? Prox and Squoosh. Welcome to Prox and Squoosh. Yay! That's pretty much how I felt looking at the code samples. Excellent, OK. So yeah, let's start with the first thing, which is about the canvas, um. And you remember this because you kind of fixed it. Right. I'm still going to explain what it was. So in Prox, we render our game field, the grid, where the tiles are. Some might be mines, or black holes in our case, 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. Right. Oh, so this is every frame of the animation. We draw a rectangle over the top of it, right, for the outer balance. 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. That means you don't actually have to have like this is like four or five squares rotate independently. We could do it with a DOM. That would be a lot of DOM and a lot of layers and I would not be fast. So in this case, we're just, you know, copying pixels, which is really fast. Right. So for every frame, we were just taking part of that. Is this all of them? No, this is a quarter or half of the frames. A quarter or half of them. So yes, it's every frame, 60 times a second, we were essentially like taking a different section of this sprite sheet. And put it on screen. 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. Because how many, on screen at a time, we would have like 500 of these going at once. Potentially, if not more. Doing that in the DOM. No, just it's not what it was built for. It's written. Yes. And because we didn't know the device that you were running on, you know, 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. Oh, show off. Just because you've got an iMac 5K monitor. I do. I've got my, I know 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 rock in with your like massive 4K. It's more like 5K screen. Yes, I'm on my 1K. Anyway, like I'm jealous, but carry on. Yes, it's great. Thanks. But basically, we could either like generate one spreadsheet and just have everyone down on the same spreadsheet, which will be rather big. And we thought that's not good. So rather, we use Canvas to generate the spreadsheet on the client side when you load the exact resolution that this device needs. Yeah, so we use Canvas 2D to draw these. Yes. But we use Canvas WebGL to put them on the screen. Am I jumping your content? No, that's exactly what's happening. So we're using Canvas WebGL and just drawing these rectangles because that is actually what Canvas 2D is really fast at. 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. Right, yes. And so we create a Canvas 2D, get the context. We scale the Canvas to match the device pixel ratio because on the 3X screen, it should look, you know. On your 5K monitor. Exactly. OK. And then we just go through a loop with all the frames. We move our Canvas to 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. And then we have this big canvas, and we move it to draw on it. Excellent. And then now we have a canvas that contains the entire spreadsheet. And we have multiple animations. So we have multiple spreadsheets. And so this is what we did. And it worked great on all phones, except. The odd browser. The odd browser. Which in this case. In this case, it was iOS Safari. It was. Which for some reason decided, you know what? You're creating too many canvases. I'm going to kill your page. It took us quite a while to figure out that that's what was going on. Like all that we saw was a full crash. Literally saying, this page has been reloaded due to a problem, and it would do that four or five times, and it would give up. Yes. Not a great experience. Not a great experience. 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 buffer, like a chunk of memory that represents the pixels. And having too many of those apparently safaris, like, you know what? This is over my threshold. I'm going to kill this page. If you get a tab in a browser that just crashes out, I would say more than 50% of the time it's memory. You've ever 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, like, you've got a memory. It's like, I need that memory back, else the whole system's going to, yeah. So the thing that we thought, how do we fix it? I guess you were like, well, but I can load images that have the same dimensions as our canvas. So our spreadsheet was like 2,000 by 2,000 pixels, I think. Yeah. We can load an image on a website like that. Why is the canvas suddenly too much? You can load loads of images like that. And so here is our great fix. After having a sprite, we generate that sprite, we turn it into a blob. Yes, we do. We turn that blob into an image. Yes, we did. And we keep that image. And then we keep the image. And so we only ever have one canvas at a time. And so Safari is now fine. And to do this, oh, it's really annoying, because we are having to encode as PNG. Yeah, just to decode. So then we can decode as PNG. Which surely must be more memory pressure than just keeping the canvas around. Yes, but presumably, like, Safari on iOS must be smarter with images than it is with canvases. Maybe. Like, the image it can put down onto disk rather than memory or something like that. And it fixed it. It now works perfectly fine. And it was purely just by finding something that we knew, like 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, like, canvases it crashed? Images it didn't. OK. Fine. Told you. This is going to be stupid hacks. Yes, OK. So next one is the worker murder. Because sometimes workers are allowed to die. Sometimes they're not. And it still happens. So we both Scrooge and Procs have off-main thread architecture going. Like, they do work off-main thread. We didn't run into this problem with Scrooge. And we will figure out why throughout this. So what we do is, usually, we start with a 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. In the odd browser. In the odd browser in iOS Safari. Which was iOS Safari. We should say, like, many browsers have bugs, right? It just so happened that we encountered most of them on iOS Safari. Not on Safari desktop? No, only iOS Safari. Only iOS Safari. Also, yeah, the reason I picked these is because the solutions are so stupid, like, right? We have bugs in other browsers, but the solution is like, oh, they don't support blah, just too damn much, or something. But here, we actually had to be creative and come up with very weird solutions. So basically, what we figured out is that the worker got killed. It's got killed, isn't it? Even though it still existed and had things to do, it was basically waiting for a message. And that is spec compliant to wait for a message. But iOS was like, you know what, the workers are expensive. We want that memory back. So then we were kind of like, well, why is this? Well, we wanted to confirm our theory. Is this worker being killed? And so what we thought is like, well, let's do things like, we'll ping the worker so we can see those messages coming back. Here it is. I see. So we did that. And it made it better. It made it better. But it didn't quite solve it. So basically, sending a message was like, 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, we did. And that made it even better. But we still found corner cases. Well, we settled on this fix for a long time. This was our fix. 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. This is OK. Walk away slowly. That's our solution. Fine. And then we still eventually found some corner cases where it would not work. The worker would still disappear. And well, I mean, our theory was it was disappearing. We could never fully confirm it. No. Yeah, because the additional 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 of R behaves. And on real devices, we didn't have proper port forwarding. And so we always had to deploy. And it slowed down the entire debugging process. It was cumbersome, to say the least. 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. 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. 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, we just put it on a global. 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 it in the console. And then it disappeared. And I was like, I couldn't. I was so angry. I can't recreate the problem. And it wasn't so, hang on. Let me just remove that one line I added. Bug comes back. To be fair, we did try to make some reduced test cases. And it never happened. So our theory is because Comlink uses proxies, that the combination of sending workers when something is done by a proxy, that there's something about their garbage collection marking algorithm that might just be a weird corner casebook, which is why the workers get collected when you use Comlink. Yeah, it thinks because it's going for a proxy, it's not carrying, it's not counting that as a reference when it should. Maybe that's a hunch. But yeah, we're only guessing. But putting it on a global, that solved it. Ta-da. It looks even worse in our code because we are using TypeScript. So of course, we have to ensure TypeScript. And this is actually OK. Yeah, yeah. Just chill TypeScript. Chill. We know we're doing the bad thing. We know what we're doing. Yeah. Number three, I thought it was quite interesting, is event inheritance. I know what this is. So in Polymer 0.5 and one time, everything was around custom events. Yes. The custom event class and putting your data in there and bubbling that up. Custom events since has been kind of deprecated. It's still around. I don't think it's implemented everywhere, actually. Well, custom event is weird, isn't it? Because when you do new custom event, you get an object to put on whatever you want, 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. Right. And so what you usually end up doing is you build your own event that extends an event. That looks all fine to me. So the same arguments. Now you can just put stuff whatever you want on the event in a dictionary and grab the things that you want, and now you can dispatch that event like any normal event. Great. Turns out, though, that in Edge and Iosafari. Edge is also for Iosafari, not just Iosafari. This extends event turns out to not actually be inheriting from events. It doesn't, does it? No. 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. Fixed, yes. Just say, this is your prototype. You are actually in event. Yeah, swap that over. This bug, the behavior, it comes from a very weird feature of JavaScript, where a constructor can return a value. Right? Don't say right like that's normal. Right, question mark dot dot dot. OK, thank you. Well, a constructor function, so you can return like? Four. Yes, or another object or something. And so when you do new thing, you actually get a completely different object back. And JavaScript is like, why not? Why not? Do what you want. And we think that's what the implementations in Edge and Safari are doing in terms of, that's my theory anyway. Well, so we used our own events in Prox as well. And so I just ripped this out of Squoosh, put it in Prox, and it still didn't work. What? Because it turns out that in Prox, we're compiling to ES5, because we're 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. So it literally just panics at construction. So what we did instead in Prox is we just wrote our own factory function. State change event factory, brilliant. So instead of creating a proper state change event function or class, we just wrote a function that creates a new event and then changes the prototype. Right. So we went from doing this horrible hack for just Edge and Safari to doing it for everyone. Right. Fine. I mean, it still works. But it's just a function that exists. And it wasn't necessary because ES5. I think it's on the developer coat of arms, isn't it? But it works. Yeah, pretty much. Pretty much. 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. I just couldn't get it to work. And so I was like, oh, you know what? Factory function. Just one place where it's usually just only have one custom event, I think. Don't care enough. Job done. Fine. And for the grand finale, we have the Heidi address bar. Heidi address bar. Excellent. Everybody probably knows that if you are on a blog website with your mobile browser and you scroll down, the address bar hides. Heidi. Yep. It's Heidi. And if you scroll back up, it comes back into view. Come back here. The problem with that is that different browsers seem to be behaving differently what 100% height or 100 VH means in this context. Yes. Some browsers seem to think the address bar is an overlay. Some don't. Sometimes only the VH. For VH, it's not an overlay, but for 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. And so you was using normal browser scrollings. That means if you scroll down, the address bar was hide. Everything would rejiggle and jank. And that was stupid. You just scroll back up. Everything came back into view. And so we thought, you know what? We're going to disable the Heidi address bar. 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 business CSS. So I'm trying to remember it off the top of my head. Like 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 kind of what we were seeing. So we had a bottom bar that was position fixed. And that was kind of like. Oh, it was jumping around. Jumping around. Either way, it was annoying. It was annoying. And so we thought the easiest way to fix this is just to do scrolling on an element. Yeah, we abandoned the full page natural scrolling thing. So we did. We used our main element, which is what the game field was in. Our entire UI was in, basically. Just push it up a little, made it the size of the screen, overflow auto, boom. Scrolling. I know. I hate this. I hate that it's 2019. And we still have to do a special thing to make scrolling work properly. But it's the web. It's homely. Right. Yes. And so this. Ooh, this is a bit new, isn't it? Like, I didn't know much about this until we did it. Yeah. This is basically just saving the over scroll. Yes. The thing where when you scroll, we are at the top and try to scroll further top. It bends it down a little bit or snaps back into view. A shadow or something. Which in this case we didn't want. So we set contain. Yes. But it's more about, you know, it's a screen filling element that can scroll. And it fixed it in iOS Safari, I think. But Chrome, the odd browser, was like, you know what? This looks like you're trying. I've forgotten about this. I blocked this out. Chrome was like, I think you're trying to do a page scroller. So I'm going to give you our great Heidi address bar behavior for free. It did. Oh, of course it did. I really had blocked this out. You've gone through all the commits and probably just gone for the. I remembered this. You actually remembered this. This is the first one I put in. Oh, brilliant. OK. And if you remember our fix, you know how great it is. Yes, I do. We looked at this line and we said, we can spare a pixel. There we go. So this was clearly Chrome trying to be helpful, trying to do the right thing. And failing. It made us so angry. Yes. Because we still got the address bar Heidi janky jumpy thing and we didn't want it at all. Yes, it's like we're doing this so you don't do that. So there must be a heuristic somewhere in our code business says, if you're a position absolutely that fills the entire screen, do the address Heidi bar attach to this scroller, the root scroller. Yes. And so we added a pixel. We added a pixel and that was enough. Oh, my God. I like it. So PTSD going on here from the weird, weird work arounds that we have encountered. Yes. During our time. We should file a bug for that because we should have a way to avoid that. So that's four stupid hacks to fix four different browsers. Is it four browsers? Two, three. Three browsers. And yeah, it's really just 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. You've got the correct path, but you have the fast path. The road's out. You got them. That's web development. Bottom one pixel. That's what that is. That was stupid. That's what I'm saying. That's a good ending for this episode. Four stupid things by four stupid browsers. Actually, we're only sort of blaming two browsers, isn't it?