Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Third Party Hell (BestOfWeb)

Third Party Hell (BestOfWeb)

Matthias Le Brun

June 07, 2019
Tweet

More Decks by Matthias Le Brun

Other Decks in Technology

Transcript

  1. third party hell
    a journey coding in hostile environments

    View full-size slide

  2. Matthias Le Brun
    @bloodyowl
    lead front-end developer
    beop.io
    co-founder & podcast host
    putaindecode.io

    View full-size slide

  3. third party?

    View full-size slide

  4. example.first-party
    first party page

    View full-size slide

  5. example.first-party
    first party page
    src="service.third-party"
    >!
    #// or
    src="service.third-party"
    %/>

    View full-size slide

  6. use case?
    • widgets
    • players
    • analytics

    View full-size slide

  7. why not integrate
    with the back-ends
    of our customers?

    View full-size slide

  8. • ∞ impl.
    • updates
    • UX et capabilities

    View full-size slide

  9. let's make a widget

    View full-size slide

  10. approach #1
    inject our DOM
    elements directly in
    the first party page

    View full-size slide

  11. approach #1
    Nice
    what we wrote

    View full-size slide

  12. approach #1
    Nice Nice
    Nice
    -> Nice
    Nice
    what shows up
    Nice

    View full-size slide

  13. approach #1
    CSS™

    View full-size slide

  14. CSS™
    • Cascading
    • Inheritance

    View full-size slide

  15. approach #1
    first party CSS
    resets & defaults
    will f*ck things in
    unexpected ways

    View full-size slide

  16. approach #1
    inline CSS
    or
    ensure your stylesheet
    is always the last one
    in the DOM

    View full-size slide

  17. approach #1
    • unset all properties
    • unset all pseudo-elements
    • unset weird vendors
    selectors
    • use highest-pri selectors
    • !important

    View full-size slide

  18. Alpha() AlphaImageLoader() :active additive-symbols (@counter-style) '::after (:after) align-content align-items align-self all animation
    animation-delay animation-direction animation-duration animation-fill-mode animation-iteration-count animation-name animation-play-state animation-timing-
    function @annotation annotation() attr() Barn() BasicImage() BlendTrans() Blinds() Blur() '::backdrop backface-visibility background background-attachment
    background-blend-mode background-clip background-color background-image background-origin background-position background-repeat background-size shape> '::before (:before) block-size blur() border border-block-end border-block-end-color border-block-end-style border-block-end-width
    border-block-start border-block-start-color border-block-start-style border-block-start-width border-bottom border-bottom-color border-bottom-left-radius
    border-bottom-right-radius border-bottom-style border-bottom-width border-collapse border-color border-image border-image-outset border-image-repeat border-
    image-slice border-image-source border-image-width border-inline-end border-inline-end-color border-inline-end-style border-inline-end-width border-inline-
    start border-inline-start-color border-inline-start-style border-inline-start-width border-left border-left-color border-left-style border-left-width
    border-radius border-right border-right-color border-right-style border-right-width border-spacing border-style border-top border-top-color border-top-left-
    radius border-top-right-radius border-top-style border-top-width border-width bottom @bottom-center box-decoration-break box-shadow box-sizing break-after
    break-before break-inside brightness() CheckerBoard() Chroma() Compositor() calc() caption-side caret-color ch @character-variant character-variant()
    @charset :checked circle() clear clip clip-path cm color column-count column-fill column-gap column-rule column-rule-color column-rule-style column-
    rule-width column-span column-width columns content contrast() counter-increment counter-reset @counter-style cross-fade() cubic-bezier() '::cue
    cursor DropShadow() :default deg :dir direction :disabled display dpcm dpi dppx drop-shadow() Emboss() Engrave() element() ellipse()
    em :empty empty-cells :enabled ex Fade() FlipH() FlipV() fallback (@counter-style) filter :first :first-child '::first-letter (:first-
    letter) '::first-line (:first-line) :first-of-type fit-content() flex flex-basis flex-direction flex-flow flex-grow flex-shrink flex-wrap float :focus
    font @font-face font-family font-family (@font-face) font-feature-settings font-feature-settings (@font-face) @font-feature-values font-kerning font-
    language-override font-size font-size-adjust font-stretch font-stretch (@font-face) font-style font-style (@font-face) font-synthesis font-variant font-
    variant (@font-face) font-variant-alternates font-variant-caps font-variant-east-asian font-variant-ligatures font-variant-numeric font-variant-position
    font-variation-settings (@font-face) font-weight font-weight (@font-face) format() format() (@font-face) fr frames() :fullscreen Glow()
    Gradient() GradientWipe() Gray() grad grayscale() grid grid-area grid-auto-columns grid-auto-flow grid-auto-rows grid-column grid-column-end
    grid-column-gap grid-column-start grid-gap grid-row grid-row-end grid-row-gap grid-row-start grid-template grid-template-areas grid-template-columns grid-
    template-rows Hz hanging-punctuation height height (@viewport) @historical-forms :hover hsl() hsla() hue-rotate() hyphens ICMFilter() Inset() Invert()
    Iris() image() image-orientation image-rendering image-resolution image-set() @import in :in-range :indeterminate inherit initial inline-
    size inset() :invalid invert() isolation justify-content kHz @keyframes Light() :lang :last-child :last-of-type leader() :left left @left-bottom
    letter-spacing line-break line-height linear-gradient() :link list-style list-style-image list-style-position list-style-type local() MaskFilter()
    Matrix() MotionBlur() margin margin-block-end margin-block-start margin-bottom margin-inline-end margin-inline-start margin-left margin-right margin-top
    mask mask-clip mask-composite mask-image mask-mode mask-origin mask-position mask-repeat mask-size mask-type matrix() matrix3d() max-height max-height
    (@viewport) max-width max-width (@viewport) max-zoom (@viewport) @media min-block-size min-height min-height (@viewport) min-inline-size min-width min-width
    (@viewport) min-zoom (@viewport) minmax() mix-blend-mode mm ms ms-filter-alpha() ms-filter-basicimage() ms-filter-blendtrans() ms-filter-blur() ms-filter-
    chroma() ms-filter-compositor() ms-filter-dropshadow() ms-filter-emboss() ms-filter-engrave() ms-filter-fliph() ms-filter-flipv() ms-filter-glow() ms-
    filter-gray() ms-filter-icmfilter() ms-filter-invert() ms-filter-light() ms-filter-maskfilter() ms-filter-matrix() ms-filter-motionblur() ms-filter-
    redirect() ms-filter-revealtrans() ms-filter-shadow() ms-filter-wave() ms-filter-xray() ms-proceduralsurface-alphaimageloader() ms-proceduralsurface-
    gradient() ms-transition-barn() ms-transition-blinds() ms-transition-checkerboard() ms-transition-fade() ms-transition-gradientwipe() ms-transition-inset()
    ms-transition-iris() ms-transition-pixelate() ms-transition-radialwipe() ms-transition-randombars() ms-transition-randomdissolve() ms-transition-slide() ms-
    transition-spiral() ms-transition-stretch() ms-transition-strips() ms-transition-wheel() ms-transition-zigzag() @namespace negative (@counter-
    style) :not :nth-child :nth-last-child :nth-last-of-type :nth-of-type object-fit object-position offset-block-end offset-block-start offset-inline-
    end offset-inline-start :only-child :only-of-type opacity opacity() :optional order orientation (@viewport) @ornaments ornaments() orphans :out-of-range
    outline outline-color outline-offset outline-style outline-width overflow overflow-wrap overflow-x overflow-y Pixelate() pad (@counter-style) padding
    padding-block-end padding-block-start padding-bottom padding-inline-end padding-inline-start padding-left padding-right padding-top @page page-break-after
    page-break-before page-break-inside pc perspective perspective() perspective-origin place-content '::placeholder pointer-events polygon()
    position prefix (@counter-style) pt px Q quotes RadialWipe() RandomBars() RandomDissolve() Redirect() RevealTrans() rad radial-gradient() range
    (@counter-style) :read-only :read-write rect() rem repeat() repeating-linear-gradient() repeating-radial-gradient() :required resize
    revert rgb() rgba() :right right @right-bottom :root rotate() rotate3d() rotateX() rotateY() rotateZ() ruby-align ruby-merge ruby-position Shadow() Slide()
    Spiral() Stretch() Strips() s saturate() scale() scale3d() scaleX() scaleY() scaleZ() :scope scroll-behavior scroll-snap-coordinate scroll-snap-destination
    scroll-snap-type '::selection sepia() shape-image-threshold shape-margin shape-outside skew() skewX() skewY() speak-as (@counter-style) src (@font-
    face) steps() @styleset styleset() @stylistic stylistic() suffix (@counter-style) @supports @swash swash() symbols (@counter-style) symbols()
    system (@counter-style) tab-size table-layout :target target-counter() target-counters() target-text() text-align text-align-last text-combine-upright text-
    decoration text-decoration-color text-decoration-line text-decoration-style text-emphasis text-emphasis-color text-emphasis-position text-emphasis-style
    text-indent text-justify text-orientation text-overflow text-rendering text-shadow text-transform text-underline-position top @top-
    center touch-action transform transform-box transform-origin transform-style transition transition-delay transition-duration
    transition-property transition-timing-function translate() translate3d() translateX() translateY() translateZ() turn unicode-bidi unicode-range (@font-face)
    unset url() user-zoom (@viewport) :valid var() vertical-align vh @viewport visibility :visited vmax vmin vw Wave() Wheel() white-space widows width
    width (@viewport) will-change word-break word-spacing word-wrap writing-mode Xray() Zigzag() z-index zoom (@viewport) )--*
    approach #1
    NOPE

    View full-size slide

  19. approach #1
    we need isolation

    View full-size slide

  20. approach #1
    inject DOM elements
    directly in the first
    party page

    View full-size slide

  21. approach #1
    inject DOM elements
    directly in the first
    party page

    View full-size slide

  22. approach #2
    web components

    View full-size slide

  23. approach #2
    scoped styles!

    View full-size slide

  24. approach #2
    • Not enough browser support
    • Polyfills are sloooooow*
    *ever tried YouTube on a non-chrome browser?

    View full-size slide

  25. approach #2
    web components

    View full-size slide

  26. approach #2
    web components
    *for now
    *

    View full-size slide

  27. approach #3
    iframes

    View full-size slide

  28. approach #3
    iframes add one
    or two requests
    (HTML + JS)

    View full-size slide

  29. approach #3
    limited for
    communication

    View full-size slide

  30. approach #3
    iframes

    View full-size slide

  31. approach #3
    iframes

    View full-size slide

  32. approach #4
    ✨ sourceless ✨
    iframes

    View full-size slide

  33. approach #4
    just use the iframe
    as a rendering target

    View full-size slide

  34. approach #4
    let iframe =
    document.createElement("iframe");
    iframe.src = "about:blank";
    console.log(iframe.contentWindow)

    View full-size slide

  35. approach #4
    browser issues!

    View full-size slide

  36. browser issues!
    • IE can't access contentWindow
    without a domain
    • Firefox doesn't always fire a
    load event

    View full-size slide

  37. approach #4
    iframe.src = isIE ?
    "javascript:" +
    `'<br/>window.onload = function() {<br/>document.domain = "first-party.domain"<br/>}<br/>!'`
    : "about:blank";

    View full-size slide

  38. approach #4
    setTimeout(
    renderWithoutLoadEvent,
    ARBITRARY_TIMEOUT
    );

    View full-size slide

  39. approach #4
    design issues!

    View full-size slide

  40. design issues!
    An iframe has a
    fixed height

    View full-size slide

  41. set the iframe height
    dynamically from
    the first party page
    approach #4

    View full-size slide

  42. watch the iframe's
    body height?
    approach #4

    View full-size slide

  43. approach #4
    My third-party elements
    iframe

    View full-size slide

  44. approach #4
    My third-party elements
    have grown
    iframe

    View full-size slide

  45. approach #4
    My third-party elements
    have grown
    iframe
    body grows

    View full-size slide

  46. approach #4
    My third-party shrank
    iframe
    but the body didn't shrink

    View full-size slide

  47. watch the iframe's
    body height?
    approach #4

    View full-size slide

  48. watch the iframe's
    body height?
    approach #4

    View full-size slide

  49. watch a
    div container
    in the body
    approach #4

    View full-size slide

  50. how do we watch?
    approach #4

    View full-size slide

  51. IntersectionObserver?
    approach #4

    View full-size slide

  52. IntersectionObserver?
    approach #4

    View full-size slide

  53. approach #4
    IntersectionObserver?

    View full-size slide

  54. approach #4
    IntersectionObserver?

    View full-size slide

  55. good old client rect!
    approach #4

    View full-size slide

  56. approach #4
    let lastHeight = 0;
    function tick() {
    let height = root.getBoundingClientRect().height;
    if(height ))!== lastHeight) {
    lastHeight = height;
    iframe.style.height = height + "px";
    }
    requestAnimationFrame(tick);
    }
    requestAnimationFrame(tick);

    View full-size slide

  57. only use
    window & document
    when you know what
    you're doing
    takeovers

    View full-size slide

  58. use
    node.ownerDocument
    and
    document.defaultView
    takeovers

    View full-size slide

  59. don't trust inheritance
    takeovers

    View full-size slide

  60. don't trust inheritance
    arrayFromIframe instanceof Array
    #// false

    View full-size slide

  61. don't trust inheritance
    Array.isArray(arrayFromIframe)
    #// true
    Object.prototype.toString
    .call(arrayFromIframe) ''===
    "[object Array]"
    #// true

    View full-size slide

  62. browsers!
    takeovers

    View full-size slide

  63. 3rd party devs: hey,
    doesn't work in
    sourceless iframes
    firefox: you want to use like
    ChromiumSVGs LOL wontfix
    takeovers

    View full-size slide

  64. takeovers
    W3C
    their own APIs

    View full-size slide

  65. 3rd party devs: hey, can we get a
    setting to let our logged customers say
    they want us to access their cookie so
    that we know if we need to show their
    admin interface?
    safari: no fuck off all third-party is trash
    takeovers

    View full-size slide

  66. chrome:
    takeovers

    View full-size slide

  67. how do we do
    layers/modals ?
    takeovers

    View full-size slide

  68. let's build a dropdown
    Dropdown
    iframe
    Some first party page contents

    View full-size slide

  69. approach #1: grow the iframe
    Dropdown
    iframe
    Some first party page contents
    Contents

    View full-size slide

  70. approach #2: scrollviews
    Dropdown
    iframe
    Some first party page contents

    View full-size slide

  71. Dropdown
    iframe
    Some first party page contents
    approach #3

    View full-size slide

  72. Dropdown
    iframe
    Some first party page contents
    : more iframes™
    Contents
    approach #3

    View full-size slide

  73. React.createPortal
    is your friend
    takeovers

    View full-size slide

  74. takeovers

    Frame
    Loading

    View full-size slide

  75. takeovers

    Frame
    createPortal(el)
    Done

    View full-size slide

  76. Environment.isThirdParty ?
    child ! :
    child;
    takeovers

    View full-size slide

  77. read-only storage
    no service workers
    takeovers

    View full-size slide

  78. takeovers
    read-only storage
    no storage at all* **
    no service workers
    *in safari and firefox
    **only fails after some time

    View full-size slide

  79. your SDK needs to
    be small
    takeovers

    View full-size slide

  80. no (or very few) polyfills
    takeovers

    View full-size slide

  81. because MooTools or
    Prototype.js are still a
    thing
    takeovers

    View full-size slide

  82. because MooTools or
    Prototype.js are still a
    thing
    takeovers

    View full-size slide

  83. avoid bundler conflicts
    takeovers

    View full-size slide

  84. avoid bundler conflicts
    {
    -/* ''... )*/
    output: {
    -/* ''... )*/
    jsonpFunction: "YOUR_OWN_NAMESPACE",
    }
    }

    View full-size slide

  85. booting
    takeovers

    View full-size slide

  86. domReady?
    onLoad?
    takeovers

    View full-size slide

  87. async loop
    takeovers

    View full-size slide

  88. async loop
    function checkSdk() {
    if(window.myBootingFunction) {
    window.myBootingFunction()
    } else {
    setTimeout(checkSdk, 40)
    }
    }
    checkSdk()

    View full-size slide

  89. analytics?
    takeovers

    View full-size slide

  90. analytics?
    • needs to load the actual
    script
    • needs to register events
    before the script is loaded

    View full-size slide

  91. analytics?
    (function(_, b, e, o, p) {
    if (_.myTracker) {
    return;
    }
    _.myTracker = function myTracker() {
    myTracker.events.push([].slice.call(arguments));
    };
    myTracker.events = [];
    o = b.createElement(e);
    o.src = "https:#//example.com/path-to-real-script.js";
    p = b.querySelector(e);
    p.parentNode.insertBefore(o, p);
    })(window, document, "script");

    View full-size slide

  92. analytics?
    (function(_, b, e, o, p) {
    if (_.myTracker) {
    return;
    }
    _.myTracker = function myTracker() {
    myTracker.events.push([].slice.call(arguments));
    };
    myTracker.events = [];
    o = b.createElement(e);
    o.src = "https:#//example.com/path-to-real-script.js";
    p = b.querySelector(e);
    p.parentNode.insertBefore(o, p);
    })(window, document, "script");

    View full-size slide

  93. analytics?
    (function(_, b, e, o, p) {
    if (_.myTracker) {
    return;
    }
    _.myTracker = function myTracker() {
    myTracker.events.push([].slice.call(arguments));
    };
    myTracker.events = [];
    o = b.createElement(e);
    o.src = "https:#//example.com/path-to-real-script.js";
    p = b.querySelector(e);
    p.parentNode.insertBefore(o, p);
    })(window, document, "script");
    dedupe

    View full-size slide

  94. analytics?
    (function(_, b, e, o, p) {
    if (_.myTracker) {
    return;
    }
    _.myTracker = function myTracker() {
    myTracker.events.push([].slice.call(arguments));
    };
    myTracker.events = [];
    o = b.createElement(e);
    o.src = "https:#//example.com/path-to-real-script.js";
    p = b.querySelector(e);
    p.parentNode.insertBefore(o, p);
    })(window, document, "script");
    dedupe
    register

    View full-size slide

  95. analytics?
    (function(_, b, e, o, p) {
    if (_.myTracker) {
    return;
    }
    _.myTracker = function myTracker() {
    myTracker.events.push([].slice.call(arguments));
    };
    myTracker.events = [];
    o = b.createElement(e);
    o.src = "https:#//example.com/path-to-real-script.js";
    p = b.querySelector(e);
    p.parentNode.insertBefore(o, p);
    })(window, document, "script");
    dedupe
    register
    load

    View full-size slide

  96. API
    takeovers

    View full-size slide

  97. API
    • existing installations
    need to work forever
    • never introduce a
    breaking change

    View full-size slide

  98. when making a third-
    party widget:
    we need to keep all that
    in mind, at every step
    takeovers

    View full-size slide

  99. because reverting a
    design decision is hard
    takeovers

    View full-size slide

  100. • not your page
    • not your domain
    • not a controlled env

    View full-size slide

  101. tl;dr;
    takeovers
    • styles: f*cked, iframes
    • scripts: f*cked, limitations
    • API: f*cked, retrocompat
    • storage: f*cked, do without

    View full-size slide

  102. thank you!
    Matthias Le Brun
    @bloodyowl
    questions?

    View full-size slide