- From: Dael Jackson <daelcss@gmail.com>
- Date: Sun, 5 Mar 2017 11:00:39 -0500
- To: public-houdini@w3.org
- Cc: www-style@w3.org
================================================= These are the official Houdini Task Force minutes. Unless you're correcting the minutes, please respond by starting a new thread with an appropriate subject line. ================================================= CSS Layout ---------- - iank walked everyone through the updated explainer: https://github.com/w3c/css-houdini-drafts/blob/master/css-layout-api/EXPLAINER.md - RESOLVED: Failure to layout results in 'display: block flow-root' layout (BFC). - RESOLVED: Change blockifyChildren to childrenMode which accepts blockify, lines, and future values, names TBD, and lines representing the root inline box (and not individual inline-level elements). - RESOLVED: Hook into structure algorithm on how to structure breakToken and make the breakToken structure cloneable. - RESOLVED: Add a new method on class that receives children array and size - engine may call this to calculate minMaxSizes on parenthesis. - RESOLVED: For an element that uses custom layout overflow: auto computes to preserve space for scrollbar. Next F2F Meeting ---------------- - RESOLVED: Houdini meets Tuesday 18 April 2017 in Tokyo ===== FULL MINUTES BELOW ====== Agenda: https://github.com/w3c/css-houdini-drafts/wiki/Seattle-F2F-Jan-10-2017 Scribe: fantasai CSS Layout ========== Review of the explainer ----------------------- iank: I updated the explainer. iank: Explainer in spec is a stripped down version of what we had at TPAC. iank: Doesn't have exclusions, fragmenting is simpler. iank: Anyone want to see the API currently? iank: I'll step through explainer. <iank> https://github.com/w3c/css-houdini-drafts/blob/master/css-layout-api/EXPLAINER.md iank: As we discussed before, everything is in logical coordinates. iank: This section is for random web dev who doesn't know what logical coordinates are. iank: The layout worklet is basically exactly the same as paint worklet. iank: Similar to custom paint, you have a function called registerLayout. iank: Here it's registering one called 'centering'. iank: In most cases you'll want to extend the basic Layout class. iank: It has things like width, height, min-width, min-height, etc. iank: If you want to use the standard set of heading border, etc. that'll all be here. iank: Similarly as we talked about before, have a layout function. iank: This takes your constraint space, list of children, and computed style. iank: First thing this list does is a typical CSS layout; iank: First this example resolves inline size. iank: This takes the constraint space and the style map. iank: Basically resolves whatever width is. iank: Next computes the available inline size: inline size minus borders & padding and scrollbar iank: Then resolve block size. iank: This takes an optional third argument, which is the content size- iank: similarly subtract out borders/padding/scrollbar. dbaron: Is there a way to consider min-height and max-height if your height is auto? iank: That's done at the end. iank: If it's auto, because you haven't filled in third argument, it'll just return infinity in this case. iank: Down below you try to resolve again, can pass in content size dbaron: Will this get invoked again if scrollbars cause changes? iank: Next we lay out of all of our children. iank: Added ability to blockify all children iank: Similar to how grid and flex blockify their children. iank: Here we're setting child constraint space. iank: Percentage resolution size will default to available. iank: These two vars capture biggest child. iank: This loop lays out all the children. iank: Each child produces one fragment, push it onto the list. iank: At this point it does the second block size resolving to settle e.g. auto with min-height. iank: Last step here centers everything. iank: Finally, you return your fragment: inline size, block size, overflow sizes, child fragments. Rossen: Why return child fragments? iank: In case where you're fragmenting, may want to speculatively lay out some children. Rossen: What is the return type here? iank: Called it fragmentOptions, a dictionary type. surma: One syntactic question. surma: Child layout fragment- surma: That call could return multiple fragments, shouldn't it yield? iank: It will only return one. surma: Was wondering if you're laying out a text node, might need multiple fragments surma: but that's the next example. fremy: Can you return in a function that does yield? iank: When you return in a function that does yield, then exit iank: So jump to IDL iank: To show you all the options on various things iank: When you get a child fragment from laying something out, has these properties: inlineSize, blockSize, inlineOverflowSize, blockOverflowSize. Only things you can set (not readonly) inlineOffset, blockOffset. Also breakToken and dominantBaseline (readonly), fantasai: Do you want to return the dominant baseline or alignment baseline? fantasai: I think you want the alignment baseline here. iank: Options for the constraint space: inlineSize, blockSize two booleans inlineSizeFixed, blockSizeFixed, these indicate that the fragment should be exactly the inlineSize and blockSize, not stretched iank: Next boolean is inlineShrinkToFit iank: You can use this in e.g. block formatting context, auto consumes all the available space. In flex e.g. shrink-to-fit instead of filling available space. fantasai: You have a boolean here, but we have many ways of computing auto. Not sure you're covering it all properly. Rossen: This flag basically is a hint which tells you that this particular constrained space has been created during shrink to fit calculations, and it's given to you expecting to do layout and produce results of shrink to fit. Rossen: After this layout pass is compete and you return from this custom layout function. Rossen: Should be able to return your minimum and maximum preferred size (min-content and max-content sizes). Rossen: For example, in all layout shrink-to-fit modes, we resolve percentage values to zero. Rossen: On margins or padding. [....] iank: We have the sizes to resolve percentages against. iank: inlineOverflow and blockOverflow booleans say whether a scrollbar is present. iank: Finally, you've got an option to set the blockFragmentationType. iank: At the block size, if the lbockFragmentationType is not none, you should try to fragment at the given blockSize. fantasai: So blockSize is actually the remaining space until the end of the fragmentainer fantasai: and percentageBlockSize is the size of the containing block. iank: Yeah. iank: Passing in dictionary of these things, initial values are falsy/infinity. iank: This is the fragmentResultOptions dictionary- iank: Has inline size and block size iank: overflow sizes iank: defaults to inline and block size if no overflow. iank: List of children fragments iank: breakToken iank: and the alignment baseline. iank: It'll by default return alignment baseline of your first child, or bottom in case you have no children. fantasai: We have two different kinds of overflow: scrollable and ink, which kind is this? iank: Scrollable. Bert: Not an error if overflow is less than size of the box? iank: No, messed up calculation maybe but no error. iank: Here are some utility functions iank: for resolving inline size & block size iank: and resolving borders and padding, and the scrollbar size. iank: We can probably put them on the layout class instead of global scope. iank: To use this, you set it by 'display: layout(foo)' iank: Take an ident iank: There's open question of what to do if function throws an error? TabAtkins: I'm unhappy with something that would cause content to break and become invisible TabAtkins: Could maybe make it behave as 'display: flow-root' iank: Should we resolve on that? TabAtkins: A valid layout() is always a formatting context root, so this should also be a context root. * fantasai strongly agrees with this RESOLVED: failure to layout results in 'display: block flow-root' layout (BFC) <br type=tea> Text Layout/Fragmentation ------------------------- iank: Let's talk about fragmentation! :D iank: In previous example we blockified children. What happens when we make this false? iank: There's two types of layout children- iank: We've got the current ... LayoutChild. iank: Two types is an InlineLayoutChild- iank: this allows text-shaping across element boundaries and things like that. iank: Other is BoxLayoutChild- iank: everything which is atomic. iank: Main difference in API is that one you can query styleMap and one you can't. iank: Simple reason for this is that exposing what the style is for all of the inlines inside the InlineLayoutChild is kind of hard API design problem. iank: I cheated and said you can't. dbaron: So InlineLayoutChild is a block that wraps all the inlines? iank: Depends on impl. iank: You could imagine we'd create an anonymous block which handles that wrapping, have to fragment it. iank: Conceptually all these return ... iank: For the block child, can query the child styles. iank: Does everyone see why we made this distinction? fantasai: no... iank: It's if you want to do something like [shows example] iank: Here is some Arabic text that's two elements (works correctly in Firefox, not Chrome) iank: Don't want to represent as two different children in layout API. iank: Want to represent them as one child, handled as a single fragment. iank: Allows for shaping across boundary. fantasai: So if you have consecutive inline boxes, they are represented as InlineLayoutChild? iank: yeah dbaron: ok, so it's just for consecutive runs of non-atomic inlines iank: If you implemented block flow layout with this API, you would ... iank: inline block would be a separate box. Rossen: So you are returning two different types of boxes. Rossen: And they both derive from LayoutChild. Rossen: And the distinction that you're drawing between the two is that the InlineLayoutBox is what corresponds to, in CSS, a 'display: inline' fragment. fantasai: Is this a box or a fragment? iank: It's a box. fantasai: So it can be multiple fragments. Rossen: Not clear. Rossen: Say you have a span, which has some border. Rossen: Let's say you can only fit part of the span on the line. Rossen: Your layout function, what does it return? <Rossen> <span style='border: 10px solid">foo bar</span> iank: What happens here, this whole element is one InlineLayoutChild iank: and it can potentially be two fragments, if you gave it available space only enough for foo. iank: Produce second fragment with bar fantasai: That's all one InlineLayoutChild. iank: Yeah iank: Let's say there's another span in here, say with color: red for example iank: That would also be part of that single InlineLayoutChild. iank: Because you want to perform text shaping across those two boundaries. iank: If the span didn't have border, would interact with text outside of it, all part of single InlineLayoutChild. iank: But if you've got in here a separate inline-block iank: This would be a separate BoxLayoutChild. iank: If the inline-block is inside the span, wouldn't see it, it's embedded in the InlineLayoutChild. iank: Wait hang on might need to break it? iank: No, you need to break it. [iank writes <span>foo <div>bar</div> quux</span>] iank: I think we'd want to represent this as [InlineLayoutChild] [BoxLayoutChild][InlineLayoutChild] Rossen: Back to my question, why drawing distinction between the two? iank: Because you can't query the style on InlineLayoutChild. Rossen: When you're implementing queries from CSSOM, like getChildRects Rossen: If that span is not identifiable in this collection you have fun times. dbaron: Not entirely sure how relevant this is, but dbaron: I know why a bunch of those test cases worked in Gecko. dbaron: It's because we have this concept called text runs. dbaron: For certain types of things, text within inlines, we will do the text layout across inline boundaries dbaron: But a whole bunch of rules must hold, e.g. same font, etc. dbaron: would need to define that. fantasai: It's specced in css-text, though there's "must", "must not", and "up to UA". Rossen: Let's say you've applied all the combining rules Rossen: suppose we have well-defined rules for this. Rossen: Why would you care if this is an inline box vs block? iank: If you're combining a whole bunch of elements.. the difference between the two interfaces is existence of styleMap. ... jack: In Servo we have all these elements in a vector to compute text runs. jack: Have indices into vector for the different elements jack: Can get the full run. Rossen: How are you going to compute Rossen: your fonts? Rossen: If you don't have the styleMap here? Rossen: At any given time, in order to make layout for this particular run... iank: This is an input. iank: You're going to call layoutNextFragment on the child, and that happens internally in the browser engine. You're not laying out the child. iank: This will just produce a fragment, gives you its size, etc. iank: Internally it has all this style info, but as far as the layout API here is concerned, it's just some geometry. Rossen: The layout child is just the callback. iank: It's the input into your layout. iank: You're iterating over the children, getting the next fragment for that child. [looking at an example of some text] Rossen: So where do you do custom inline layout? iank: You don't. iank: We can add that later, but it's much harder problem. iank: If we want InlineLayoutChild to have a styleMap, would have to specify which styles we're able to return and which ones we can't. fremy: Which properties would work? I can't think of any you could return that would be useful. iank: Are people fine with having these two different concepts? iank: Or should we have a styleMap that only returns values for some properties? fantasai: It's an inline layout black box. Can't see inside. iank: Could maybe request styleMap for "first part", "second part", etc. gregwhitworth: Are you worried that the ergonomics of the API will be weird once we tackle inline layout? iank: My intuition of how we'd do inline layout style, you'd get a bunch of text runs which have a style map. ... Rossen: Let's say you're making flexbox layout, if I have loose text I don't want to deal with that. iank: That's why we have blockifyChildren. Rossen: If we don't allow user to initially override the fact that something will produce inline or block layout internally in the engine. Rossen: Then let's not expose this API. Rossen: They don't get to see into or participate in that. Rossen: When we give them the option to call explicitly layout children without the parameters meaning do text layout, because I want you to and this is supposed to be text layout Rossen: vs "do some kind of layout, dunno what you are doing". Rossen: The only thing I care about is, the final result, which will be a box and potentially some geometry data Rossen: If we tell them "oh, but there are two different ones, and use this one for text and this other one for everything else". iank: They both have the same fragment, the only difference is the ability to pull style. iank: The instance type of the object is different. iank: I'm fine with both of them being called LayoutChild and having the styleMap and just for some properties we just lie or something. fremy: For all of them! fantasai: I don't like that. fantasai: What if we made blockifyChildren always true? fantasai: What's the use case you're trying to solve with this system here? iank: Case is if you want to do, e.g. CSS Shapes yourself. iank: An InlineLayoutChild with a lot of text. iank: In this version for the API you can create text fragments which step down like this... iank: And then they step back up. iank: You'd be able to do that in this version of the API. [iank draws a shapes thing with differently-length line boxes] fantasai: OK, so you can do that without having a mix of InlineLayoutChild and BoxLayoutChild. Just treat the entire stream of inline-level content as such, don't distinguish between inline boxes and atomic inlines. Rossen: That's what I'm saying. Rossen: Keep going until you get transition from inline to block Rossen: and return the styles as if it was an anonymous inline. iank: So what do we call this flag? Rossen: yieldOnInlineLevelContent iank: produceLine? Rossen: buildLine? fantasai: Can't get rid of the flag, because it generates a different box tree: blockification makes individual box objects out of each element and run of text, whereas a run of inlines would be wrapped into a single anonymous box object that represents the root inline of the block container. iank: So group all inline-level children into same layoutChild, which has computed style of the anonymous inline that it represents iank: And we'll add a buildLine option to ConstraintSpace Options? fantasai: I don't think it belongs on ConstraintSpaceOptions. fantasai: It's not about space geometry, it's about layout model. fantasai: Should be on Layout object. fantasai: How you do layout depends on whether you blockify or not. [fantasai draws example on the whiteboard] [fantasai explains that laying out a child that's been blockified would be different from laying out a stream of content that isn't] [Rossen explains that the parameter would be passed into the child's nextFragment() function, not into the container's Layout function] ... iank: Flex etc. will blockify children, which changes computed style of all children. iank: If we pass this flag to the children instead as a param of constraint, wouldn't be recomputing style. fantasai: If you blockify the children, you're operating on a list of, say, 15 boxes. fantasai: If you don't, if it's a stream of inline-level boxes, then it would be treated as one single abstract box. TabAtkins: Huge difference between 15 blocks vs one anonymous inline. You have to know this up front. esprehn: We also need to know up front to handle white space correctly. esprehn: We don't make boxes for white space between blocks. esprehn: We don't make boxes for that today. [fantasai draws on the board: <div> A B <span>C</span> D <em>E <strong>F</strong></em> <div> ] fantasai: This is either one anonymous inline inheriting from the parent with no directly cascaded declarations fantasai: or four children, two of which are anonymous inlines and two of which correspond to actual elements with their own styles. fantasai: You cannot make this decision the moment you call nextFrag on the first child -- you need to know that when you start the loop fantasai: Because what that child is that you are calling nextFrag on, depends on whether you are blockifying the children or not. dbaron: I'm a little concerned about doing this merge-all-children-into-one-object thing. dbaron: In the future we will want to expose a richer API to do custom inline layout [missed details] dbaron: Maybe we should just do blockified children for now. iank: There's potentially another layout API which allows building up text runs from a series of children. iank: I think that building individual text runs is a separate thing. dbaron: I guess it feels a little weird to me that you aren't doing the thing I thought you were doing when you started to describe this. dbaron: You are giving layout information that are within atomic inlines that are in a run. fantasai: That was what I was saying earlier that we shouldn't do, just treat it as a single stream. dbaron: Is there a more sensible way we could defer more of inline layout. iank: The thing we could do is blockify all the children by default. fantasai: I'm not sure this is that inconsistent with expansion later. We do have this concept of a root inline box that wraps around all inline-level content, and this is just representing that. dbaron: I think getting into the root inline is getting into inline layout. dbaron: Two things won't get into inline layout. dbaron: Blockifying the individual children, or wrapping inline content in an anonymous block. fremy: But wrapping in an anonymous block won't let you solve the Shapes use case. fantasai: We could, instead of having a single blockifyChildren boolean, we could have a multi-state flag (or maybe multiple booleans if that's the correct style?) fantasai: one state would be blockifying children fantasai: another would be handling the root inline as a box as we're discussing fantasai: and in the future a third would give a richer API exposing full inline layout. fantasai: I think each of these three is useful, and even when we have full inline layout, the root inline version will still be useful of itself. iank: So instead of blockifyChildren we'd have childrenMode, which could have blockify, ... not sure what to call it. TabAtkins: Blocks only, blocks and lines, and future values. iank: Blockify will blockify all the children, which we all understand. iank: Lines version will wrap all the inlines in an anonymous box and spit out line by line. iank: Then don't get option to spit out individual fragments or boxes. fantasai: They'll be fragments, but fragment representing entire contents of a line. iank: And then future would get into nitty gritty of inline layout. iank: This also makes it much easier to spec. iank: OK, I'm happy with that! RESOLVED: Change blockifyChildren to childrenMode which accepts blockify, lines, and future values, names TBD, and lines representing the root inline box (and not individual inline-level elements) iank: In general, if anyone has suggestions for better names for any of this stuff feel free to suggest... Next F2F Meeting ================ Rossen: Next F2F is Tokyo in April, do we want to have a Houdini meeting? TabAtkins: I wouldn't mind <dbaron> The Houdini meeting would presumably be Tuesday April 18, immediately preceding CSS (19-21). shane: I should be able to have typedOM and properties and values ready by then. iank: I should be able to have an update too. Rossen: OK, sounds like we'll have Houdini for one day before the CSSWG meetings. RESOLVED: Houdini meets Tuesday 18 April 2017 in Tokyo <br duration=10min> CSS Layout (con't) ================== Scribe: nainar Break tokens ------------ TabAtkins: I want to rename break tokens. <TabAtkins> "break" token doesn't tell you wtf it's doing. It's holding the information necessary to resume layout of that child, to generate the next fragments. Need a better name for it that actually communicates that. iank: When you get a fragment it potentially has a break token - pass to the layout iank: so for yourself if you want to fragement in the block direction in current API you can read of constrained space if fragmentation is not null. iank: In that case if you can return in you dict the property break token which is a dict itself iank: and you add a list of child break tokens. iank: When the engine calls ??? back it returns the data you passed it. gregwhitworth: Aren't we each time calling on the box we are laying out. gregwhitworth: Need for multiple break tokens if concern is laying out box. iank: You get one break token back but you may need to take care of child break tokens. iank: <explains break token on board> iank: Data is engine stashing the info you pass to it. Rossen: You could, for example, ask space? iank: Data is a nice thing to stash state instead. RESOLVED: Hook into structure algorithm on how to structure breakToken and make the breakToken structure cloneable. gregwhitworth: I can stash whatever I want but it feels like its a sign of missing something. TabAtkins: It's not, you will get called once and you need to know how much you have already done so as to resume. eae: We don't know what kind of layout people will perform on top of this. Rossen: You can add gradient info to resume on next frame. Min/Max Content --------------- iank: Next min and max content. iank: There are two models you can do for min max sizing. iank: Resolved inline size needs to know min max size to do stuff like shrinkToFit iank: so if you need to do things like grdiContent you need to know available size min size max size. iank: One way to do it don't need to do anything special call min size with 0 and max size with infinite. fantasai: If you are specified size then that will be your min content. iank: My understanding is that they resolve to zero - min max. dbaron: Replaced elements there are some cases where they resolve to 0 iank: Option 2 introduces a new callback and returns a tuple of min max. Rossen: This is something we should punt to CSSOM - Rossen: we get requests for this - we just want to know what the min max size of my element is Rossen: if we expose this on an element level - curious to know if there are scenario we wont be able to ??? Rossen: One thing you will say I want to have this API on a range - give me the min and max. dbaron: I feel like we can live with something similar to it - element one flushes layout houdini doesn't. Rossen: You don't have to flush layout. dbaron: You have to flush style at least. dbaron: The stuff in custom layout should only have layout. Rossen: Your style is not changing ... dbaron: There may be cases where you do layout with non current style. dbaron: There are cases where we stop doing relayout and paint a partial new/old layout. dbaron: There are cases where we do that with style. esprehn: Where? dbaron: In cases where it takes too long. Rossen: We had incremental layout where we could resume and push partial frames - user experience wasn't great. dbaron: Custom layout should be separate from api for elements. Rossen: If we assume for a given subtree on which we are doing custom layout - then the api we talked about is sufficient for level 1. iank: The engine would provide for min max size on element level and child has that available? Rossen: Sure, and engines figure out how to do this - we already do this. <nainar> Please check issue https://github.com/w3c/css-houdini-drafts/issues/343 for details <nainar> https://www.irccloud.com/pastebin/ShmkAr9J/ Rossen: Assume this API exists today on CSOM level. Rossen: I can say minContentSize like offsetHeight. iank: Inside of layout it becomes a simple function - children have the min and maxsize as well. iank: Add a new method on class that receives children array and size - engine may call this to calculate minMaxSizes on parent. dbaron: How does this relate to ho a element with custom layout provides custom min and max content. iank: If you have an element that needs to know min and max size - engine would invoke this on the child. iank: This can be invoked by the engine or you can do it yourself. RESOLVED: Add a new method on class that receives children array and size - engine may call this to calculate minMaxSizes on paren dbaron: One orthogonal thing this should have separate property dependencies as opposed to layout - as there are some that don't effect layout - make explicit? Rossen: At this level? dbaron: No, but less efficient as it would have to compute for things that don't effect. Rossen: Can we add this as a note? these are the properties that will recalc min and max. Rossen: Addition of these will also force recalc. dbaron: Should the class have a separate input prop list - for the ones that effect min and max sizes. dbaron: Don't have idea of how big this list? TabAtkins: File an issue to look into this - call this function less number of time. Rossen: Optimize the function call it as many times as needed. dbaron: Servo architecture is related to min max thing. esprehn: Benefit of added function? esprehn: They share a lot of stuff - closer to merge better. esprehn: I'm questioning the resolution - we can cache this and add needsMinMaxSizes to our 3rd plan. iank: <adds edits to the plan in the issue> esprehn: Doesn't work if you want to query the min/max size iank: assumes that children have both minSize and maxSize on them. dbaron: If in the example in the explainer - the code loops over the layout. dbaron: Servo splits up the subtree of the children. iank: <typing> const fragments = yield children .map(c => c.layoutNextFragment(space)); <dbaron> https://github.com/w3c/css-houdini-drafts/issues/347 iank: files issues about explainer mentioning that we use parallel layout when possible <dbaron> ok, so yielding one layout request at a time doesn't allow the engine to parallelize, but yielding an array of them lets the engine parallelize them <dbaron> it would be useful to have that explained in the explainer [Summery of un-minuted conversation: Jack was saying it would be better for performance in parallel engines to structure the api to be like the three callbacks in Servo Ian says the developer ergonomics of that are bad Jack would be interested to see the code that they tried ] iank: How to handle scrolling 1) on the constraint space there are two bools telling you if inline or block scrollbar present. iank: You can't set this on your child - handles by the engine. iank: If you return a fragment that has an overflow size bigger than the one. iank: We talked about having scrollLines - this is better. iank: Other approach is what Tab suggested - you cant turn on the overflow: auto for custom properties. iank: overflow: auto will resolve to allow us to layout once only. iank: If we go for Tab's option it simplifies it from authors perspective. TabAtkins: I'm pretty sure few people would write code to restart when they become too large. iank: If you have overflow size bigger than your size we will call you with boolean. Rossen: Tab's option forces us to have better implementation of that value. Rossen: If all of a sudden we reserve space for overflow: auto even though we don't need it - we have different layout. RESOLVED: For an element that uses custom layout overflow: auto computes to preserve space for scrollbar. Bert: Would it be possible to specify initial offset of scrollbar. Rossen: This should be in the scroll spec and not the layout one - as has nothing to do with it. Bert: The layout function is the only one that knows. Bert: If its a different api how will know the layout of the element. iank: If you scroll 100px down and then there is a child inserted - its not layouts job to adjust scrollbar. iank: Stays at where engine thinks it should be.<div id="DAB4FAD8-2DD7-40BB-A1B8-4E2AA1F9FDF2"><br /> <table style="border-top: 1px solid #D3D4DE;"> <tr> <td style="width: 55px; padding-top: 13px;"><a href="https://www.avast.com/sig-email?utm_medium=email&utm_source=link&utm_campaign=sig-email&utm_content=webmail&utm_term=icon" target="_blank"><img src="https://ipmcdn.avast.com/images/icons/icon-envelope-tick-round-orange-animated-no-repeat-v1.gif" width="46" height="29" style="width: 46px; height: 29px;" /></a></td> <td style="width: 470px; padding-top: 12px; color: #41424e; font-size: 13px; font-family: Arial, Helvetica, sans-serif; line-height: 18px;">Virus-free. <a href="https://www.avast.com/sig-email?utm_medium=email&utm_source=link&utm_campaign=sig-email&utm_content=webmail&utm_term=link" target="_blank" style="color: #4453ea;">www.avast.com</a> </td> </tr> </table><a href="#DAB4FAD8-2DD7-40BB-A1B8-4E2AA1F9FDF2" width="1" height="1"></a></div>
Received on Sunday, 5 March 2017 16:01:49 UTC