Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Third Party Hell (BestOfWeb)
Search
Matthias Le Brun
June 07, 2019
Technology
810
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Third Party Hell (BestOfWeb)
Matthias Le Brun
June 07, 2019
More Decks by Matthias Le Brun
See All by Matthias Le Brun
GraphQL, Pothos & SQLite: a perfect match
bloodyowl
0
110
(why the hell did I) build a GraphQL client for the browser
bloodyowl
0
150
Boxed: bringing algebraic types to TypeScript
bloodyowl
0
190
leveraging (algebraic data) types to make your UI rock @ jsheroes
bloodyowl
0
330
Leveraging (algebraic data) types to make your UI rock solid
bloodyowl
0
500
La drôle d'histoire de JavaScript
bloodyowl
0
400
Healthy Code Collaboration
bloodyowl
0
370
Simplify your UI management with (algebraic data) types
bloodyowl
0
860
Simplify your UI management with (algebraic data) types
bloodyowl
1
580
Other Decks in Technology
See All in Technology
アカウントが増えてからでは遅い? ~ マルチアカウント統制の勘所 ~
kenichinakamura
0
220
勉強会企画をアプリで構造化してみた 〜そこで見えた、AIとの付き合い方〜 / I've structured a study group plan using an app.
pauli
0
340
AIを駆使した OSS脆弱性調査のすゝめ
saku0512
0
100
【Claude Code】鹿野さんに聞く 私の推しの並行開発環境 大公開 / claude-code-parallel-2026-07-15
tonkotsuboy_com
9
5.2k
プロダクトだけじゃない、社内プロセスにおける自動化・省力化ノススメ
kakehashi
PRO
1
3.5k
AI時代の EM への処方箋
staka121
PRO
0
110
AI時代のエンジニアキャリアについて今一度考える
sakamoto_582
2
1.5k
SRE依存からの脱却 運用を開 発チームへ移す、 フルサイ クル開 発体制の実践
joooee0000
0
2.5k
AIに「使われる」時代のSaaS戦略 〜既存WebAPIのMCPサーバー化における開発ノウハウ〜
ekispert_api
0
310
ボーイスカウトルールでメモリやスキルを改善しよう
azukiazusa1
1
130
最適な自走を最小限の支援で — M&Aで拡大する組織で少人数SREが挑んだ1年 / SRE NEXT 2026
genda
0
920
生成AIの活用/high_school2026
okana2ki
0
120
Featured
See All Featured
Jamie Indigo - Trashchat’s Guide to Black Boxes: Technical SEO Tactics for LLMs
techseoconnect
PRO
0
280
Writing Fast Ruby
sferik
630
63k
Primal Persuasion: How to Engage the Brain for Learning That Lasts
tmiket
0
390
Collaborative Software Design: How to facilitate domain modelling decisions
baasie
1
260
Leveraging Curiosity to Care for An Aging Population
cassininazir
1
330
What Being in a Rock Band Can Teach Us About Real World SEO
427marketing
0
1k
Darren the Foodie - Storyboard
khoart
PRO
3
3.4k
Why Our Code Smells
bkeepers
PRO
340
58k
What the history of the web can teach us about the future of AI
inesmontani
PRO
1
630
Practical Tips for Bootstrapping Information Extraction Pipelines
honnibal
25
2k
Sharpening the Axe: The Primacy of Toolmaking
bcantrill
46
2.9k
The Psychology of Web Performance [Beyond Tellerrand 2023]
tammyeverts
49
3.5k
Transcript
third party hell a journey coding in hostile environments
Matthias Le Brun @bloodyowl lead front-end developer beop.io co-founder &
podcast host putaindecode.io
third party?
example.first-party first party page
example.first-party first party page <script src="service.third-party" >!</script> #// or <iframe
src="service.third-party" %/>
None
use case?
use case? • widgets • players • analytics
why not integrate with the back-ends of our customers?
• ∞ impl. • updates • UX et capabilities
strategy
let's make a widget
None
approach #1 inject our DOM elements directly in the first
party page
approach #1 Nice what we wrote
approach #1 Nice Nice Nice -> Nice Nice what shows
up Nice
approach #1 CSS™
CSS™ • Cascading • Inheritance
approach #1 first party CSS resets & defaults will f*ck
things in unexpected ways
approach #1 inline CSS or ensure your stylesheet is always
the last one in the DOM
approach #1 • unset all properties • unset all pseudo-elements
• unset weird vendors selectors • use highest-pri selectors • !important
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) )--* approach #1 NOPE
approach #1 we need isolation
approach #1 inject DOM elements directly in the first party
page
approach #1 inject DOM elements directly in the first party
page
approach #2 web components
approach #2 scoped styles!
approach #2 • Not enough browser support • Polyfills are
sloooooow* *ever tried YouTube on a non-chrome browser?
approach #2
approach #2 web components
approach #2 web components *for now *
approach #3 iframes
approach #3 iframes add one or two requests (HTML +
JS)
approach #3 limited for communication
approach #3 iframes
approach #3 iframes
approach #4 ✨ sourceless ✨ iframes
approach #4 just use the iframe as a rendering target
approach #4 let iframe = document.createElement("iframe"); iframe.src = "about:blank"; console.log(iframe.contentWindow)
approach #4 browser issues!
browser issues! • IE can't access contentWindow without a domain
• Firefox doesn't always fire a load event
approach #4 iframe.src = isIE ? "javascript:" + `'<script> window.onload
= function() { document.domain = "first-party.domain" } !</script>'` : "about:blank";
approach #4 setTimeout( renderWithoutLoadEvent, ARBITRARY_TIMEOUT );
approach #4 design issues!
design issues! An iframe has a fixed height
set the iframe height dynamically from the first party page
approach #4
watch the iframe's body height? approach #4
approach #4 My third-party elements iframe
approach #4 My third-party elements have grown iframe
approach #4 My third-party elements have grown iframe body grows
approach #4 My third-party shrank iframe but the body didn't
shrink
watch the iframe's body height? approach #4
watch the iframe's body height? approach #4
watch a div container in the body approach #4
how do we watch? approach #4
IntersectionObserver? approach #4
approach #4
IntersectionObserver? approach #4
approach #4 IntersectionObserver?
approach #4 IntersectionObserver?
good old client rect! approach #4
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);
only use window & document when you know what you're
doing takeovers
use node.ownerDocument and document.defaultView takeovers
don't trust inheritance takeovers
don't trust inheritance arrayFromIframe instanceof Array #// false
don't trust inheritance Array.isArray(arrayFromIframe) #// true Object.prototype.toString .call(arrayFromIframe) ''=== "[object
Array]" #// true
browsers! takeovers
3rd party devs: hey, <animate /> doesn't work in sourceless
iframes firefox: you want to use like ChromiumSVGs LOL wontfix takeovers
takeovers W3C their own APIs
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
chrome: takeovers
how do we do layers/modals ? takeovers
let's build a dropdown Dropdown iframe Some first party page
contents
approach #1: grow the iframe Dropdown iframe Some first party
page contents Contents
approach #2: scrollviews Dropdown iframe Some first party page contents
Dropdown iframe Some first party page contents approach #3
Dropdown iframe Some first party page contents : more iframes™
Contents approach #3
React.createPortal is your friend takeovers
takeovers <iframe %/> Frame Loading
takeovers <iframe %/> Frame createPortal(el) Done
Environment.isThirdParty ? <Frame> child !</Frame> : child; takeovers
read-only storage no service workers takeovers
takeovers read-only storage no storage at all* ** no service
workers *in safari and firefox **only fails after some time
your SDK needs to be small takeovers
no (or very few) polyfills takeovers
because MooTools or Prototype.js are still a thing takeovers
because MooTools or Prototype.js are still a thing takeovers
avoid bundler conflicts takeovers
avoid bundler conflicts { -/* ''... )*/ output: { -/*
''... )*/ jsonpFunction: "YOUR_OWN_NAMESPACE", } }
booting takeovers
domReady? onLoad? takeovers
async loop takeovers
async loop function checkSdk() { if(window.myBootingFunction) { window.myBootingFunction() } else
{ setTimeout(checkSdk, 40) } } checkSdk()
analytics? takeovers
analytics? • needs to load the actual script • needs
to register events before the script is loaded
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");
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");
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
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
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
API takeovers
API • existing installations need to work forever • never
introduce a breaking change
when making a third- party widget: we need to keep
all that in mind, at every step takeovers
because reverting a design decision is hard takeovers
• not your page • not your domain • not
a controlled env
tl;dr; takeovers • styles: f*cked, iframes • scripts: f*cked, limitations
• API: f*cked, retrocompat • storage: f*cked, do without
thank you!
thank you! Matthias Le Brun @bloodyowl questions?