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
·
Ship Features Fearlessly
Turn features on and off without deploys. Used by thousands of Ruby developers.
→
Matthias Le Brun
June 07, 2019
Technology
800
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
360
Simplify your UI management with (algebraic data) types
bloodyowl
0
850
Simplify your UI management with (algebraic data) types
bloodyowl
1
580
Other Decks in Technology
See All in Technology
自宅LLMの話
jacopen
1
600
【セミナー資料】Claude Code をセキュアに使うための考え方と設定の勘どころ / Claude Code Webinar 20260616
masahirokawahara
2
360
人材育成分科会.pdf
_awache
4
260
白金鉱業Meetup_Vol.24_「AIエージェントは分けるほど良い」は本当か? / Is it true that “the more you divide AI agents, the better”?
brainpadpr
1
390
20260619 私の日常業務での生成 AI 活用
masaruogura
1
220
エンジニアリング戦略の作り方 / Crafting Engineering Strategy
iwashi86
21
7k
不要なレビューをAIにまかせて AIコーディングの環境改善を加速した
shoota
1
150
iAEONの段階的リアーキテクト戦略 / iAEON's_Gradual_Re-architecture_Strategy
aeonpeople
0
120
Disciplined Vibes: Scaling AI-Assisted Engineering
sheharyar
0
150
データサイエンスを価値につなげるプロジェクト設計 〜 DS一年目が現場で得た気づき 〜
ysd113
1
260
2026TECHFRESH畢業分享會 - 原生還是跨平台? App 開發踩坑實錄
line_developers_tw
PRO
0
1.1k
スキルと MCP ツール、責務をどう分けるか? AI が迷わないインターフェース設計の戦略
cdataj
1
1.1k
Featured
See All Featured
Scaling GitHub
holman
464
140k
From Legacy to Launchpad: Building Startup-Ready Communities
dugsong
0
230
Building the Perfect Custom Keyboard
takai
2
790
Lightning talk: Run Django tests with GitHub Actions
sabderemane
0
200
Rebuilding a faster, lazier Slack
samanthasiow
85
9.5k
Organizational Design Perspectives: An Ontology of Organizational Design Elements
kimpetersen
PRO
1
720
How to optimise 3,500 product descriptions for ecommerce in one day using ChatGPT
katarinadahlin
PRO
1
3.6k
Paper Plane
katiecoart
PRO
1
51k
Amusing Abliteration
ianozsvald
1
200
Sam Torres - BigQuery for SEOs
techseoconnect
PRO
0
290
Tell your own story through comics
letsgokoyo
1
950
jQuery: Nuts, Bolts and Bling
dougneiner
66
8.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?