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
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
【Cyber-sec+】経営層を"動かす"ための考え方
hssh2_bin
0
190
Oracle AI Database@Azure:サービス概要のご紹介
oracle4engineer
PRO
6
2k
LayerX コーポレートエンジニアリング室におけるサプライチェーンセキュリティへの取り組み / Supply Chain Security at LayerX Corporate Engineering
yuyatakeyama
2
570
Oracle AI Database@Google Cloud:サービス概要のご紹介
oracle4engineer
PRO
6
1.5k
非エンジニアがClaudeと挑んだ「1ヶ月間プロダクト30本ノック」
askokc
0
560
「エンジニア進化論」2028年の開発完全自動化、エンジニアはどう進化するか
cyberagentdevelopers
PRO
6
5.3k
2026TECHFRESH畢業分享會 - 原生還是跨平台? App 開發踩坑實錄
line_developers_tw
PRO
0
1.1k
自宅LLMの話
jacopen
1
600
AIエージェントが名古屋の猛暑からあなたを守る
happysamurai294
0
120
20260619 私の日常業務での生成 AI 活用
masaruogura
1
220
スキルと MCP ツール、責務をどう分けるか? AI が迷わないインターフェース設計の戦略
cdataj
1
1.1k
入門!AWS Blocks
ysuzuki
1
130
Featured
See All Featured
Getting science done with accelerated Python computing platforms
jacobtomlinson
2
230
BBQ
matthewcrist
89
10k
Mozcon NYC 2025: Stop Losing SEO Traffic
samtorres
1
250
Reality Check: Gamification 10 Years Later
codingconduct
0
2.2k
Visual Storytelling: How to be a Superhuman Communicator
reverentgeek
2
560
職位にかかわらず全員がリーダーシップを発揮するチーム作り / Building a team where everyone can demonstrate leadership regardless of position
madoxten
62
54k
Utilizing Notion as your number one productivity tool
mfonobong
4
320
What Being in a Rock Band Can Teach Us About Real World SEO
427marketing
0
250
Neural Spatial Audio Processing for Sound Field Analysis and Control
skoyamalab
0
330
Self-Hosted WebAssembly Runtime for Runtime-Neutral Checkpoint/Restore in Edge–Cloud Continuum
chikuwait
0
590
Design and Strategy: How to Deal with People Who Don’t "Get" Design
morganepeng
133
19k
Bootstrapping a Software Product
garrettdimon
PRO
307
120k
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?