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
Sponsored
·
Your Podcast. Everywhere. Effortlessly.
Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
→
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
120
(why the hell did I) build a GraphQL client for the browser
bloodyowl
0
160
Boxed: bringing algebraic types to TypeScript
bloodyowl
0
190
leveraging (algebraic data) types to make your UI rock @ jsheroes
bloodyowl
0
340
Leveraging (algebraic data) types to make your UI rock solid
bloodyowl
0
510
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
41歳でAWSが好きすぎてITエンジニアになったおっさんの話
yama3133
1
770
Flutter研修【MIXI 26新卒技術研修】
mixi_engineers
PRO
1
100
AI Agent を本番環境へ―― Microsoft Foundry × Azure Serverless で作る Enterprise-Ready な基盤
shibayan
PRO
1
450
Git 研修【MIXI 26新卒技術研修】
mixi_engineers
PRO
1
300
AI時代におけるエンジニアの新たな役割──FDEとクオリアの探求/登壇資料(戸井田 裕貴)
hacobu
PRO
0
410
reFACToring
moznion
0
340
WEBフロントエンド研修【MIXI 26新卒技術研修】
mixi_engineers
PRO
1
220
PHPで作って学ぶリアルタイム音声対話AIとWebSocket入門 by ムナカタ
munakata
0
150
モバイル研修【MIXI 26新卒技術研修】
mixi_engineers
PRO
1
130
なぜ、あなたのAPIは使われないのか? AX時代の設計原則、ガードレール、運用体制
yokawasa
1
230
AIが当たり前の組織で エンジニアはどう育つか
nishihira
1
1.3k
データと地図で読む 大井町の「かわるもの、かわらないもの」
yoshiyama_hana
0
300
Featured
See All Featured
The Cost Of JavaScript in 2023
addyosmani
55
10k
技術選定の審美眼(2025年版) / Understanding the Spiral of Technologies 2025 edition
twada
PRO
118
120k
The agentic SEO stack - context over prompts
schlessera
0
850
A Soul's Torment
seathinner
6
3.1k
Automating Front-end Workflow
addyosmani
1370
210k
Raft: Consensus for Rubyists
vanstee
141
7.6k
"I'm Feeling Lucky" - Building Great Search Experiences for Today's Users (#IAC19)
danielanewman
230
23k
SEO in 2025: How to Prepare for the Future of Search
ipullrank
3
3.7k
Thoughts on Productivity
jonyablonski
76
5.3k
We Analyzed 250 Million AI Search Results: Here's What I Found
joshbly
1
1.6k
Put a Button on it: Removing Barriers to Going Fast.
kastner
60
4.5k
Bash Introduction
62gerente
615
220k
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?