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 Slide

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

    View Slide

  3. third party?

    View Slide

  4. example.first-party
    first party page

    View Slide

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

    View Slide

  6. View Slide

  7. use case?

    View Slide

  8. use case?
    • widgets
    • players
    • analytics

    View Slide

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

    View Slide

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

    View Slide

  11. strategy

    View Slide

  12. let's make a widget

    View Slide

  13. View Slide

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

    View Slide

  15. approach #1
    Nice
    what we wrote

    View Slide

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

    View Slide

  17. approach #1
    CSS™

    View Slide

  18. CSS™
    • Cascading
    • Inheritance

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  22. 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 Slide

  23. approach #1
    we need isolation

    View Slide

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

    View Slide

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

    View Slide

  26. approach #2
    web components

    View Slide

  27. approach #2
    scoped styles!

    View Slide

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

    View Slide

  29. approach #2

    View Slide

  30. approach #2
    web components

    View Slide

  31. approach #2
    web components
    *for now
    *

    View Slide

  32. approach #3
    iframes

    View Slide

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

    View Slide

  34. approach #3
    limited for
    communication

    View Slide

  35. approach #3
    iframes

    View Slide

  36. approach #3
    iframes

    View Slide

  37. approach #4
    ✨ sourceless ✨
    iframes

    View Slide

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

    View Slide

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

    View Slide

  40. approach #4
    browser issues!

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  44. approach #4
    design issues!

    View Slide

  45. design issues!
    An iframe has a
    fixed height

    View Slide

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

    View Slide

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

    View Slide

  48. approach #4
    My third-party elements
    iframe

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  55. how do we watch?
    approach #4

    View Slide

  56. IntersectionObserver?
    approach #4

    View Slide

  57. approach #4

    View Slide

  58. IntersectionObserver?
    approach #4

    View Slide

  59. approach #4
    IntersectionObserver?

    View Slide

  60. approach #4
    IntersectionObserver?

    View Slide

  61. good old client rect!
    approach #4

    View Slide

  62. 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 Slide

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

    View Slide

  64. use
    node.ownerDocument
    and
    document.defaultView
    takeovers

    View Slide

  65. don't trust inheritance
    takeovers

    View Slide

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

    View Slide

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

    View Slide

  68. browsers!
    takeovers

    View Slide

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

    View Slide

  70. takeovers
    W3C
    their own APIs

    View Slide

  71. 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 Slide

  72. chrome:
    takeovers

    View Slide

  73. how do we do
    layers/modals ?
    takeovers

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  79. React.createPortal
    is your friend
    takeovers

    View Slide

  80. takeovers

    Frame
    Loading

    View Slide

  81. takeovers

    Frame
    createPortal(el)
    Done

    View Slide

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

    View Slide

  83. read-only storage
    no service workers
    takeovers

    View Slide

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

    View Slide

  85. your SDK needs to
    be small
    takeovers

    View Slide

  86. no (or very few) polyfills
    takeovers

    View Slide

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

    View Slide

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

    View Slide

  89. avoid bundler conflicts
    takeovers

    View Slide

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

    View Slide

  91. booting
    takeovers

    View Slide

  92. domReady?
    onLoad?
    takeovers

    View Slide

  93. async loop
    takeovers

    View Slide

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

    View Slide

  95. analytics?
    takeovers

    View Slide

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

    View Slide

  97. 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 Slide

  98. 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 Slide

  99. 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 Slide

  100. 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 Slide

  101. 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 Slide

  102. API
    takeovers

    View Slide

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

    View Slide

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

    View Slide

  105. because reverting a
    design decision is hard
    takeovers

    View Slide

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

    View Slide

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

    View Slide

  108. thank you!

    View Slide

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

    View Slide