royale-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Alex Harui <aha...@adobe.com.INVALID>
Subject Re: Layout optimizations
Date Sun, 25 Mar 2018 07:03:18 GMT
Interesting approach, but unfortunately I'm having trouble believing this
will work in general.  I thought that the browsers had immediate updates
after the initial screen draw, so a setTimeout to defer measuring and/or
layout will result in stuff showing up on screen in some weird state until
setTimeout finally kicks in.  Like when you change states and pile a bunch
of new components on the screen.  That's one reason Royale has not had a
LayoutManager and deferred invalidation/validation so far.  Nor does
Royale do what Flex did and start components out as invisible and only
make them visible at the end of deferred validation.  I think if you do
that in the browser it doesn't finish setting some properties on the HTML
elements until visible.

I'm glad it improved performance for your app.  But I'm concerned your
optimizations may not apply everywhere.

In Flex, the LayoutManager goes even further to sort the order of
validation based on depth in the DOM.  Otherwise in complex situations you
can end up redrawing a few times or getting stuck in a loop.  But again,
I'm still uncertain that deferred rendering is the right direction.

IMO, we should avoid the tendency to replicate the Flex way in Basic.
Might be good or even required for the emulation components, but the Basic
philosophy is to optimize the creation of optimal DOMs and if I'm right
about immediate visual updates in the browser, I think we have to accept
that and write code for that.

I think you've identified a few things that should be examined in small
separate changes.  If every layout asks for width/height, that's sounds
wrong.  It isn't clear why VerticalLayout and HorizontalLayout that only
set the display style should need to do that.  In my prior reply, my point
was that the goal is to generate the best DOM.  The JS code should only
create HTMLElements and set attributes on them in the least amount of code
possible.  More on that below.

Then, if layouts rarely ask for width/height after the above change, we
can then consider how to generate the correct width/height, not
necessarily what is fastest for your application.  It is super important,
IMO, for framework developers to take the time to understand the concept
of any feature and try to think of how it would be used in at least one
other scenario.  In application development, you don't always have to stop
to do that.  The goal is often just to get your app out the door.  In
looking at the current and proposed width getter in UIBase, for example,
really, both versions seem wrong.  The browser is going to layout the
component with a width based on ALL css involved not just what is in the
style block, and adding the check for _explicitWidth to the front only
helps if you've set _explicitWidth in most of your widgets.  I think
_explicitWidth will mirror style.width, but again, how often are folks
setting style.width, and who is reading it? If you scanned HTML DOMs in
existing JS apps, I don't think we see it too often.  Did we rule out just
always returning HTMLElement.offsetWidth?  Shouldn't it be possible to
create a full DOM without ever reading width if you are completely relying
on browser layout?  If so, the Basic code should allow you to do that.
The emulation code won't, BTW, because Flex used %width differently than
the browser does, but that is mostly independent of this issue.

So I would like us to first agree on how the browser works, get a sense of
how often folks explicitly set sizes, figure out if there is a good reason
for Layout or other code to be reading width/height on every component,
and then evaluate this and other approaches with that understanding.

To try to illustrate:  If I have a Label of a TextInput:

<js:Application>
  <js:initialView>
    <js:VView>
      <js:Label text="type below..." />
      <js:TextInput />
    </js:VView>
  </js:initialView>
</js:Application>

I'm looking for an HTML DOM like this:

<body>
  <div>
    <span style="display:block">type below...</span>
    <input />
  </div>
</body>

The smallest amount of JS code to generate this is something like:

var div = document.createElement('div');
document.body.appendChild(div);
var span = document.createElement('span');
span.text = "type below..."
div.appendChild(span)
var input = document.createElement('input');
div.appendChild(input);

If more than these 7 lines of JS runs in Royale, (which it does) then all
of those other lines are unnecessary.  So adding more lines and timing via
LayoutManager and setTimeout doesn't feel right.

Now that isn't a fair comparison because these 7 lines are not reusable
and framework code must be reusable, so really, the smallest amount of
reusable code is something like this:

var bodyChildren = ['div'];
var divChildren = ['span', 'input']
var divProps = [['text', 'type below...']]
for (var child in bodyChildren)
{
  var e = document.createElement(child);
  document.body.appendChild(e);
  if (divChildren)
  {
    for (var child1 in divChildren)
    {
      var e1 = document.createElement(child1);
      e.appendChild(e1)
      var props = divProps.shift();
      while (props && props.length)
      {
        e1[props.shift()] = props.shift();
      }
    }
  }
}

As you can see, there is going to be a price for reusable framework code.
We tend to use more loops and data structures.  But we want to be as PAYG
as possible.  I doubt this small example benefits from deferred layout.
Hence I have trouble believing that deferred layout is a required solution
for all Royale apps.  But our current code is not getting this DOM built
in as few lines.  What are those other lines doing?  Question every other
line and I think you will more easily determine which ones need to be
there and which ones can be moved out of the way until someone needs it.
That's PAYG.  Now if after all of this, deferred layout really does help,
then we have to find a PAYG way to make it plug-in, but I'm pretty sure
you've pointed out other optimizations for lots of other apps that don't
require deferred layout.

We also might find that a solution is to provide alternate absolute
position layout that make assumptions.  For example, I think if your app
always set sizes on img tags and flow containers, that you can drop a
whole bunch of code in Royale that is watching for "sizedToContent"
components to change their size.

Can you post part of your DOM, and explain why you needed to set
width/height and figure out why Royale code ever needed to read it back?
That might help us sync up on what the issues are.

My 2 cents,
-Alex

PS:  Unfortunately, that's all the time I have to think about this for
another 20 hours or so.

On 3/24/18, 2:33 PM, "Harbs" <harbs.lists@gmail.com> wrote:

>Explicit gave me about a 10% performance boost. I did that before I added
>the queue.
>
>For layouts, the major performance boost came from the queue.
>
>I do think it’s a good idea to leave the optimization in the width and
>height getters to prevent reflows when client code calls those getters. I
>did not do a full audit of my app, but I’m pretty sure that the
>optimization makes a difference.
>
>Thanks,
>Harbs
>
>> On Mar 25, 2018, at 12:29 AM, Piotr Zarzycki
>><piotrzarzycki21@gmail.com> wrote:
>> 
>> Hi Harbs,
>> 
>> As I see your major changes is adding _explicitHeight/width and this
>>queue.
>> Am I understand right all ? Which one actually helps so much ? Adding
>> _explicitHeight/width or that queue ?
>> 
>> Thanks,
>> Piotr
>> 
>> 2018-03-24 22:04 GMT+01:00 Harbs <harbs.lists@gmail.com
>><mailto:harbs.lists@gmail.com>>:
>> 
>>> I just pushed some changes to a layout-optimization branch.
>>> 
>>> I’m seeing about a 5 *times* improvement in performance with layouts
>>>and I
>>> don’t see any issues with the layout. It seems to me like this
>>>optimization
>>> was a lot more painless than I feared.
>>> 
>>> Where I was seeing a layout take about 150 ms, it’s not taking about 30
>>> ms. The only place I’m seeing the dreaded "Forced reflow” warning now
>>>is in
>>> my own code where it needs optimization.
>>> 
>>> I don’t see any down-side to the code I committed, but I’ll wait for
>>> feedback before I merge it in.
>>> 
>>> Harbs
>>> 
>>>> On Mar 24, 2018, at 10:17 PM, Harbs <harbs.lists@gmail.com> wrote:
>>>> 
>>>> This is all good info.
>>>> 
>>>> Here’s the problem as I see it:
>>>> 
>>>> 1. Measuring width and height cause reflows if css properties were set
>>> and/or the Dom structure was changed.
>>>> 2. EVERY layout reads the host width and height before and after
>>>>layout.
>>> Every layout (currently) subclasses LayoutBase.
>>>> 3. It’s pretty unlikely for any layout so not set some property, so
>>>>what
>>> happens in #2 will cause a reflow every time unless the width and
>>>height is
>>> explicitly set (using a modification to the width and height getters).
>>>> 
>>>> I’m looking to solve this by the following:
>>>> 1. Optimize the width and height getters to return the explicit values
>>> if they exist. In my case, this seems to yield about a 10% improvement.
>>> That’s not bad, but I think we can do much better.
>>>> 2. Delay the layout until the entire DOM structure is measured.
>>>> 3. The measuring might also need to be delayed until the whole
>>>>structure
>>> is created.
>>>> 
>>>> Basically, I want to make a measuring stage and then a setting stage.
>>> I’m not sure whether I should use requestAnimationFrame or some other
>>> method. The trick will be getting the measurements and the layouts
>>>executed
>>> in the correct order.
>>>> 
>>>> Some links on the topic:
>>>> 
>>>> 
>>>>https://na01.safelinks.protection.outlook.com/?url=http%3A%2F%2Fblog.fo
>>>>gcreek.com%2Fwe-spent-a-week-making-trello-&data=02%7C01%7Caharui%40ado
>>>>be.com%7Cf6ec15b6bc144a10b72708d591cefba2%7Cfa7b1b5a7b34438794aed2c178d
>>>>ecee1%7C0%7C0%7C636575240532889715&sdata=1b5TU%2FF4rySpEQ135WbR5CVYC%2F
>>>>PsHr4I67JGW3%2Bk5OU%3D&reserved=0
>>> boards-load-extremely-fast-heres-how-we-did-it/ <
>>> 
>>>https://na01.safelinks.protection.outlook.com/?url=http%3A%2F%2Fblog.fog
>>>creek.com%2Fwe-spent-a-week-making-trello-&data=02%7C01%7Caharui%40adobe
>>>.com%7Cf6ec15b6bc144a10b72708d591cefba2%7Cfa7b1b5a7b34438794aed2c178dece
>>>e1%7C0%7C0%7C636575240532889715&sdata=1b5TU%2FF4rySpEQ135WbR5CVYC%2FPsHr
>>>4I67JGW3%2Bk5OU%3D&reserved=0
>>><https://na01.safelinks.protection.outlook.com/?url=http%3A%2F%2Fblog.fo
>>>gcreek.com%2Fwe-spent-a-week-making-trello-&data=02%7C01%7Caharui%40adob
>>>e.com%7Cf6ec15b6bc144a10b72708d591cefba2%7Cfa7b1b5a7b34438794aed2c178dec
>>>ee1%7C0%7C0%7C636575240532889715&sdata=1b5TU%2FF4rySpEQ135WbR5CVYC%2FPsH
>>>r4I67JGW3%2Bk5OU%3D&reserved=0>
>>> boards-load-extremely-fast-heres-how-we-did-it/>
>>>> 
>>>>https://na01.safelinks.protection.outlook.com/?url=http%3A%2F%2Fwww.php
>>>>ied.com%2Frendering-repaint-reflowrelayout-restyle%2F&data=02%7C01%7Cah
>>>>arui%40adobe.com%7Cf6ec15b6bc144a10b72708d591cefba2%7Cfa7b1b5a7b3443879
>>>>4aed2c178decee1%7C0%7C0%7C636575240532889715&sdata=P%2FDcF5djzfe19XM2Kg
>>>>1d3SNeojus%2BsZ4daB9Y7PU0N0%3D&reserved=0
>>>><https://na01.safelinks.protection.outlook.com/?url=http%3A%2F%2Fwww.ph
>>>>pied.com%2Frendering-repaint-reflowrelayout-restyle%2F&data=02%7C01%7Ca
>>>>harui%40adobe.com%7Cf6ec15b6bc144a10b72708d591cefba2%7Cfa7b1b5a7b344387
>>>>94aed2c178decee1%7C0%7C0%7C636575240532889715&sdata=P%2FDcF5djzfe19XM2K
>>>>g1d3SNeojus%2BsZ4daB9Y7PU0N0%3D&reserved=0> <
>>> 
>>>https://na01.safelinks.protection.outlook.com/?url=http%3A%2F%2Fwww.phpi
>>>ed.com%2Frendering-repaint-reflowrelayout-restyle%2F&data=02%7C01%7Cahar
>>>ui%40adobe.com%7Cf6ec15b6bc144a10b72708d591cefba2%7Cfa7b1b5a7b34438794ae
>>>d2c178decee1%7C0%7C0%7C636575240532889715&sdata=P%2FDcF5djzfe19XM2Kg1d3S
>>>Neojus%2BsZ4daB9Y7PU0N0%3D&reserved=0
>>><https://na01.safelinks.protection.outlook.com/?url=http%3A%2F%2Fwww.php
>>>ied.com%2Frendering-repaint-reflowrelayout-restyle%2F&data=02%7C01%7Caha
>>>rui%40adobe.com%7Cf6ec15b6bc144a10b72708d591cefba2%7Cfa7b1b5a7b34438794a
>>>ed2c178decee1%7C0%7C0%7C636575240532889715&sdata=P%2FDcF5djzfe19XM2Kg1d3
>>>SNeojus%2BsZ4daB9Y7PU0N0%3D&reserved=0>>
>>>> 
>>>>https://na01.safelinks.protection.outlook.com/?url=http%3A%2F%2Fwilsonp
>>>>age.co.uk%2Fpreventing-layout-thrashing%2F&data=02%7C01%7Caharui%40adob
>>>>e.com%7Cf6ec15b6bc144a10b72708d591cefba2%7Cfa7b1b5a7b34438794aed2c178de
>>>>cee1%7C0%7C0%7C636575240532889715&sdata=o4KtRyn9ehkdHYbPfeguIsGY3%2FwJM
>>>>%2FlGfnOK4QLPGWY%3D&reserved=0
>>>><https://na01.safelinks.protection.outlook.com/?url=http%3A%2F%2Fwilson
>>>>page.co.uk%2Fpreventing-layout-thrashing%2F&data=02%7C01%7Caharui%40ado
>>>>be.com%7Cf6ec15b6bc144a10b72708d591cefba2%7Cfa7b1b5a7b34438794aed2c178d
>>>>ecee1%7C0%7C0%7C636575240532889715&sdata=o4KtRyn9ehkdHYbPfeguIsGY3%2FwJ
>>>>M%2FlGfnOK4QLPGWY%3D&reserved=0> <
>>> 
>>>https://na01.safelinks.protection.outlook.com/?url=http%3A%2F%2Fwilsonpa
>>>ge.co.uk%2Fpreventing-layout-thrashing%2F&data=02%7C01%7Caharui%40adobe.
>>>com%7Cf6ec15b6bc144a10b72708d591cefba2%7Cfa7b1b5a7b34438794aed2c178decee
>>>1%7C0%7C0%7C636575240532889715&sdata=o4KtRyn9ehkdHYbPfeguIsGY3%2FwJM%2Fl
>>>GfnOK4QLPGWY%3D&reserved=0
>>><https://na01.safelinks.protection.outlook.com/?url=http%3A%2F%2Fwilsonp
>>>age.co.uk%2Fpreventing-layout-thrashing%2F&data=02%7C01%7Caharui%40adobe
>>>.com%7Cf6ec15b6bc144a10b72708d591cefba2%7Cfa7b1b5a7b34438794aed2c178dece
>>>e1%7C0%7C0%7C636575240532889715&sdata=o4KtRyn9ehkdHYbPfeguIsGY3%2FwJM%2F
>>>lGfnOK4QLPGWY%3D&reserved=0>>
>>>> 
>>>> I’m planning on spending time on this tomorrow. I’ll see what I can
>>>>come
>>> up with…
>>>> 
>>>> Harbs
>>>> 
>>>>> On Mar 23, 2018, at 7:11 PM, Alex Harui <aharui@adobe.com.INVALID
>>>>><mailto:aharui@adobe.com.INVALID>
>>> <mailto:aharui@adobe.com.INVALID <mailto:aharui@adobe.com.INVALID>>>
>>>wrote:
>>>>> 
>>>>> Hi Harbs,
>>>>> 
>>>>> For sure, it would be great for you to take a look at layout.
>>>>> 
>>>>> Besides what Peter wrote below, there are other things to consider:
>>>>> 
>>>>> 1) Like Flex, Royale layouts assume that (when necessary) parents
>>>>>size
>>>>> their children.  That just makes sense for responsive apps that
>>>>>respond
>>> to
>>>>> the size of the application window.
>>>>> 2) Like Flex, Royale's UIBase has APIs that "temporarily" set a size.
>>>>> Setting width/height also sets explicitWidth/explicitHeight, but the
>>>>> setWidth/setHeight/setWidthAndHeight calls only set the width/height
>>> and
>>>>> do not set explicitWidth/explicitHeight in order for that layout
>>>>>pass to
>>>>> set the size but not have the explicitWidth/Height factored into the
>>> next
>>>>> layout pass.
>>>>> 3) The code snippet you posted I'm pretty sure is used for when a
>>> child's
>>>>> size changes and the parent's size should then change.  As Peter
>>>>>said,
>>>>> some components are sized to content and the layout should be
>>>>>responsive
>>>>> to the content.
>>>>> 
>>>>> But what I think is most important is really what the DOM looks like.
>>> The
>>>>> only goal for Royale layouts for JS is to set up the DOM.  In theory,
>>> the
>>>>> default VerticalLayout and HorizontalLayouts do not measure anything
>>>>>nor
>>>>> do they set the width and height on the children.  They merely set
>>>>>the
>>>>> display style to block or inline-block.  Then it is up to the
>>>>>browser to
>>>>> decide when/how to layout.  Ideally, our JS code is creating widgets
>>>>>and
>>>>> setting display styles in the most optimal way to create the same DOM
>>> you
>>>>> would by hand with an HTML editor.
>>>>> 
>>>>> So, for your app, the first question is: did Royale set up the DOM in
>>> the
>>>>> best possible way?  I think you said there was a fair amount of
>>>>>absolute
>>>>> positioning in your app.  If the Royale absolute positioning layout
>>>>>is
>>> not
>>>>> allowing the construction of the DOM in the best possible way, the
>>>>>next
>>>>> question is then: should the current layout algorithm be changed
>>>>>because
>>>>> it is just plain wrong, or should you create a different absolute
>>>>> positioning layout that is optimized for the kinds of DOM sub-trees
>>>>>you
>>>>> like to use?  Or should you be revising your app to use a layout
>>>>>based
>>> on
>>>>> FlexBox or CSSGrid?
>>>>> 
>>>>> So, it might turn out that BasicLayout is always going to be slow
>>>>>but it
>>>>> will handle just about any absolute positioning from both top-down
>>>>> percentage sizing as well as sizing to content of children, but that
>>>>>new
>>>>> layouts should be created that drastically reduce the number of folks
>>> that
>>>>> need to use BasicLayout in production.
>>>>> 
>>>>> My 2 cents,
>>>>> -Alex
>>>>> 
>>>>> On 3/23/18, 7:25 AM, "Peter Ent" <pent@adobe.com.INVALID
>>>>><mailto:pent@adobe.com.INVALID> <mailto:
>>> pent@adobe.com.INVALID <mailto:pent@adobe.com.INVALID>>> wrote:
>>>>> 
>>>>>> The code looks familiar, but I'm not 100% sure.
>>>>>> 
>>>>>> The trick with layouts is that there are the following things to
>>> consider:
>>>>>> 
>>>>>> If everything has an explicit width/height, it should be pretty
>>>>>>easy.
>>>>>> 
>>>>>> The percentWidth/height when present changes the _width, _height
>>> without
>>>>>> changing the explicit size. This allows a layout to work again on
>>>>>>the
>>>>>> percent value and calculate a new size. If the explicit sizes are
>>>>>>set,
>>>>>> then the next layout pass will take the explicit over the percent
>>> (setting
>>>>>> explicit sizes sets the percent sizes to NaN and vice-versa) and
>>>>>>what
>>> was
>>>>>> once 50% will now be fixed in size.
>>>>>> 
>>>>>> The tricky part - for me anyway - is when an item has no size set.
>>>>>>Then
>>>>>> its supposed be sized by its content. Labels and Buttons sized by
>>>>>>their
>>>>>> text or icons while containers are sized by their children and so
>>> forth.
>>>>>> 
>>>>>> Let's say you have one container nested inside of another and in
the
>>> inner
>>>>>> most container is a single button. The outer most layout cannot
>>> determine
>>>>>> its container-child's size because it doesn't have one yet. The
>>>>>>inner
>>>>>> container needs to get the size of its children (the button) and
>>>>>>that
>>> then
>>>>>> becomes its size. But it should not be setting the explicit sizes.
>>>>>>That
>>>>>> allows the button to be resized which means its container becomes
>>> bigger
>>>>>> which means the outer container becomes bigger. Once you set those
>>>>>> explicit sizes, then its game over - use case #1 essentially.
>>>>>> 
>>>>>> So - I think that code has to do with determining size-by-content
>>>>>>and
>>> so
>>>>>> isLayoutRunning was created to prevent a recursion.
>>>>>> 
>>>>>> For me, working with Flex and Royale - determining the size of
>>> something
>>>>>> has been one of the biggest challenges.
>>>>>> 
>>>>>> ‹peter
>>>>>> 
>>>>>> On 3/23/18, 7:46 AM, "Harbs" <harbs.lists@gmail.com
>>>>>><mailto:harbs.lists@gmail.com> <mailto:
>>> harbs.lists@gmail.com>> wrote:
>>>>>> 
>>>>>>> I¹m working on making layouts more efficient. Currently, there¹s
>>>>>>>lots
>>> of
>>>>>>> setting and reading of width and height which causes many browser
>>>>>>> reflows. While profiling performance in my app, reflows is a
major
>>>>>>> bottleneck. In one area, it¹s taking about 150ms (on my I7 2.8
Ghz
>>>>>>> MacBook Pro ‹ on tablets it¹s painfully slow) to execute on
area of
>>>>>>> layout. Almost all of that time is being spent measuring with
and
>>> height
>>>>>>> of components. The width and height getters trigger reflow because
>>>>>>> properties are recursively set in layout.
>>>>>>> 
>>>>>>> I was able to get about a 10% improvement by optimizing the width
>>>>>>>and
>>>>>>> height getters to return the explicitWdith and explicitHeight
if
>>>>>>>set.
>>> It
>>>>>>> looks to me like almost all of this bottleneck could be eliminated
>>>>>>>by
>>>>>>> delaying the property setting until after the measurements are
>>>>>>>done.
>>> I¹m
>>>>>>> working on doing that, but I have a question:
>>>>>>> 
>>>>>>> LayoutBase.performLayout has the following code:
>>>>>>> 
>>>>>>>                                   // check sizes to see if layout
>>> changed the size or not
>>>>>>>                                   // and send an event to re-layout
>>> parent of host
>>>>>>>                                   if (host.width != oldWidth
||
>>>>>>>                                           host.height != oldHeight)
>>>>>>>                                   {
>>>>>>>                                           isLayoutRunning = true;
>>>>>>>                                           host.dispatchEvent(new
>>> Event("sizeChanged"));
>>>>>>>                                           isLayoutRunning = false;
>>>>>>>                                   }
>>>>>>> 
>>>>>>> Under what circumstances does this code get executed? This appears
>>>>>>>to
>>> be
>>>>>>> causing a recursive layout. Can I assume that there will be an
>>>>>>> explicitWidth/height when this will be executed?
>>>>>> 
>>>>> 
>>>> 
>>> 
>>> 
>> 
>> 
>> -- 
>> 
>> Piotr Zarzycki
>> 
>> Patreon: 
>>*https://na01.safelinks.protection.outlook.com/?url=https%3A%2F%2Fwww.pat
>>reon.com%2Fpiotrzarzycki&data=02%7C01%7Caharui%40adobe.com%7Cf6ec15b6bc14
>>4a10b72708d591cefba2%7Cfa7b1b5a7b34438794aed2c178decee1%7C0%7C0%7C6365752
>>40532889715&sdata=ppeA04CS3tfBBKOSxNxvG60o%2B2WH01MkwBFh8W1eIhg%3D&reserv
>>ed=0 
>><https://na01.safelinks.protection.outlook.com/?url=https%3A%2F%2Fwww.pat
>>reon.com%2Fpiotrzarzycki&data=02%7C01%7Caharui%40adobe.com%7Cf6ec15b6bc14
>>4a10b72708d591cefba2%7Cfa7b1b5a7b34438794aed2c178decee1%7C0%7C0%7C6365752
>>40532889715&sdata=ppeA04CS3tfBBKOSxNxvG60o%2B2WH01MkwBFh8W1eIhg%3D&reserv
>>ed=0>
>> 
>><https://na01.safelinks.protection.outlook.com/?url=https%3A%2F%2Fwww.pat
>>reon.com%2Fpiotrzarzycki&data=02%7C01%7Caharui%40adobe.com%7Cf6ec15b6bc14
>>4a10b72708d591cefba2%7Cfa7b1b5a7b34438794aed2c178decee1%7C0%7C0%7C6365752
>>40532889715&sdata=ppeA04CS3tfBBKOSxNxvG60o%2B2WH01MkwBFh8W1eIhg%3D&reserv
>>ed=0 
>><https://na01.safelinks.protection.outlook.com/?url=https%3A%2F%2Fwww.pat
>>reon.com%2Fpiotrzarzycki&data=02%7C01%7Caharui%40adobe.com%7Cf6ec15b6bc14
>>4a10b72708d591cefba2%7Cfa7b1b5a7b34438794aed2c178decee1%7C0%7C0%7C6365752
>>40532889715&sdata=ppeA04CS3tfBBKOSxNxvG60o%2B2WH01MkwBFh8W1eIhg%3D&reserv
>>ed=0>>*
>

Mime
View raw message