Third Party Hell

Third Party Hell

37500337ba5d2aebc962959ed83928e5?s=128

Matthias Le Brun

March 28, 2018
Tweet

Transcript

  1. Third-party hell Making code for hostile environments

  2. Matthias Le Brun @bloodyowl Be

  3. third-party?

  4. First party page You

  5. None
  6. use cases: widgets, players

  7. not your page not your domain not controlled

  8. strategy?

  9. Inject DOM elements in the first party page?

  10. First party CSS resets & defaults will fuck your page

    up in unexpected ways
  11. What you wrote Nice Nice What shows up Nice Nice

    -> Nice Nice
  12. Inline styles or ensure your stylesheet is always last in

    the DOM
  13. • unset all properties • unset all pseudo elements properties

    • unset weird vendor selectors (some use them) You need to:
  14. Alpha() AlphaImageLoader() :active additive-symbols (@counter-style) !::after (:after) align-content align-items align-self

    all <an-plus-b> <angle> 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 <basic- shape> !::before (:before) <blend-mode> 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> 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> counter-increment counter-reset @counter-style cross-fade() cubic-bezier() !::cue cursor <custom-ident> 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 <filter-function> :first :first-child !::first-letter (:first- letter) !::first-line (:first-line) :first-of-type fit-content() <flex> 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() <frequency> :fullscreen Glow() Gradient() GradientWipe() Gray() grad <gradient> 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() <ident> <image> image() image-orientation image-rendering image-resolution image-set() @import in :in-range :indeterminate inherit initial inline- size inset() <integer> :invalid invert() isolation justify-content kHz @keyframes Light() :lang :last-child :last-of-type leader() :left left @left-bottom <length> 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 <number> 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 <percentage> perspective perspective() perspective-origin place-content !::placeholder pointer-events polygon() <position> position prefix (@counter-style) pt px Q quotes RadialWipe() RandomBars() RandomDissolve() Redirect() RevealTrans() rad radial-gradient() range (@counter-style) <ratio> :read-only :read-write rect() rem repeat() repeating-linear-gradient() repeating-radial-gradient() :required resize <resolution> 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> shape-image-threshold shape-margin shape-outside skew() skewX() skewY() speak-as (@counter-style) src (@font- face) steps() <string> @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 <time> <timing-function> top @top- center touch-action transform transform-box <transform-function> 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> 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) #--* NOPE
  15. you need isolation

  16. Inject DOM elements in the first party page?

  17. Web Components?

  18. scoped styles isolation

  19. Not enough browser support + unstable when it works

  20. Web Components?

  21. iframe?

  22. if you have a fixed ratio or height? sure

  23. iframe adds two requests (HTML + inner JS) and limited

    for communication
  24. iframe?

  25. sourceless iframe?

  26. just use the iframe as a rendering target

  27. let iframe = document.createElement("iframe"); iframe.src = "javascript:;";

  28. Browser issues • IE can't access contentWindow without a domain

    • Firefox doesn't always fire a load event
  29. iframe.src = isTrident ? "javascript:" + `'<script> window.onload = function()

    { document.domain = "first-party.domain" } %</script>'` : "javascript:;";
  30. setTimeout( renderWithoutLoadEvent, ARBITRARY_TIMEOUT );

  31. an iframe has a fixed height Design issue

  32. you need a JS running on the first party to

    set the iframe height
  33. watch the iframe body size?

  34. body content content iframe

  35. watch the iframe body size?

  36. watch a container div in the body size

  37. let lastHeight = 0; function tick() { let height =

    root.getBoundingClientRect().height; if(height ##!== lastHeight) { lastHeight = height; iframe.style.height = height + "px"; } requestAnimationFrame(tick); } requestAnimationFrame(tick);
  38. Try not to use global window & document

  39. node.ownerDocument document.defaultView Use these

  40. arrayFromIframe instanceof Array Array.isArray(arrayFromIframe) ({}).toString.call(arrayFromIframe) !!=== "[object Array]" Don’t trust

    inheritance
  41. Layers? Modals?

  42. Dropdown ▼

  43. Dropdown ▼ solution 1: expand iframe contents of the first-party

    page
  44. Dropdown ▼ solution 2: scroll view

  45. Dropdown ▼ solution 3: more iframes™ r + pageYOffset

  46. let layerManager: (module LayerManager) = environment )== "THIRD_PARTY" ? ((module

    ThirdPartyLayerManager): (module LayerManager)) : ((module DefaultLayerManager): (module LayerManager)); module LayerManager = (val layerManager);
  47. ReactDOM.createPortal

  48. No persistent storage No service workers ...

  49. SDK needs to be small

  50. no (or very few) polyfills

  51. Can't break MooTools or Prototype.js

  52. Bundler conflicts { +/* !!... #*/ output: { +/* !!...

    #*/ jsonpFunction: "namespaced__define", } }
  53. Booting

  54. domReady?

  55. when DOMContentLoaded or load fire, you don't know if your

    booting script is ready
  56. async loop

  57. function checkSdk() { if(window.myBootingFunction) { window.myBootingFunction() } else { setTimeout(checkSdk,

    40) } } checkSdk()
  58. third-party analytics?

  59. (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");
  60. register events before your actual script has loaded

  61. If you're going to make a third-party widget: keep all

    that in mind
  62. the code you made your client install on their page

    needs to work forever
  63. you can only add non-breaking changes

  64. tl;dr; styles: you're fucked scripts: you're fucked API: no breaking

    change
  65. Thank you Got any questions? ✋