Third-party hellMaking code for hostile environments
View Slide
Matthias Le Brun@bloodyowlBe
third-party?
First party pageYou
use cases:widgets, players
not your pagenot your domainnot controlled
strategy?
Inject DOM elementsin the first partypage?
First party CSS resets& defaults will fuckyour page up inunexpected ways
What you wroteNiceNiceWhat shows upNiceNice-> NiceNice
Inline stylesorensure your stylesheetis always last in the DOM
• unset all properties• unset all pseudo elementsproperties• unset weird vendorselectors (some use them)You need to:
Alpha() AlphaImageLoader() :active additive-symbols (@counter-style) !::after (:after) align-content align-items align-self all animationanimation-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-attachmentbackground-blend-mode background-clip background-color background-image background-origin background-position background-repeat background-size shape> !::before (:before) block-size blur() border border-block-end border-block-end-color border-block-end-style border-block-end-widthborder-block-start border-block-start-color border-block-start-style border-block-start-width border-bottom border-bottom-color border-bottom-left-radiusborder-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-widthborder-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-afterbreak-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 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-increment counter-reset @counter-style cross-fade() cubic-bezier() !::cuecursor 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 :first :first-child !::first-letter (:first-letter) !::first-line (:first-line) :first-of-type fit-content() flex flex-basis flex-direction flex-flow flex-grow flex-shrink flex-wrap float :focusfont @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-positionfont-variation-settings (@font-face) font-weight font-weight (@font-face) format() format() (@font-face) fr frames() :fullscreen Glow()Gradient() GradientWipe() Gray() grad grayscale() grid grid-area grid-auto-columns grid-auto-flow grid-auto-rows grid-column grid-column-endgrid-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() image() image-orientation image-rendering image-resolution image-set() @import in :in-range :indeterminate inherit initial inline-size inset() :invalid invert() isolation justify-content kHz @keyframes Light() :lang :last-child :last-of-type leader() :left left @left-bottom 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-topmask 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 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-rangeoutline outline-color outline-offset outline-style outline-width overflow overflow-wrap overflow-x overflow-y Pixelate() pad (@counter-style) paddingpadding-block-end padding-block-start padding-bottom padding-inline-end padding-inline-start padding-left padding-right padding-top @page page-break-afterpage-break-before page-break-inside pc perspective perspective() perspective-origin place-content !::placeholder pointer-events polygon() position prefix (@counter-style) pt px Q quotes RadialWipe() RandomBars() RandomDissolve() Redirect() RevealTrans() rad radial-gradient() range(@counter-style) :read-only :read-write rect() rem repeat() repeating-linear-gradient() repeating-radial-gradient() :required resize 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-destinationscroll-snap-type !::selection sepia() shape-image-threshold shape-margin shape-outside skew() skewX() skewY() speak-as (@counter-style) src (@font-face) steps() @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-styletext-indent text-justify text-orientation text-overflow text-rendering text-shadow text-transform text-underline-position top @top-center touch-action transform transform-box transform-origin transform-style transition transition-delay transition-durationtransition-property transition-timing-function translate() translate3d() translateX() translateY() translateZ() turn unicode-bidi unicode-range (@font-face)unset url() user-zoom (@viewport) :valid var() vertical-align vh @viewport visibility :visited vmax vmin vw Wave() Wheel() white-space widows widthwidth (@viewport) will-change word-break word-spacing word-wrap writing-mode Xray() Zigzag() z-index zoom (@viewport) #--*NOPE
you need isolation
Web Components?
scoped stylesisolation
Not enough browsersupport + unstablewhen it works
iframe?
if you have a fixedratio or height? sure
iframe adds tworequests (HTML +inner JS) and limitedfor communication
sourceless iframe?
just use the iframe asa rendering target
let iframe = document.createElement("iframe");iframe.src = "javascript:;";
Browser issues• IE can't accesscontentWindowwithout a domain• Firefox doesn't alwaysfire a load event
iframe.src = isTrident ?"javascript:" +`'<br/>window.onload = function() {<br/>document.domain = "first-party.domain"<br/>}<br/>%'`: "javascript:;";
setTimeout(renderWithoutLoadEvent,ARBITRARY_TIMEOUT);
an iframe has a fixedheightDesign issue
you need a JS runningon the first party toset the iframe height
watch the iframebody size?
bodycontentcontentiframe
watch a container divin the body size
let lastHeight = 0;function tick() {let height = root.getBoundingClientRect().height;if(height ##!== lastHeight) {lastHeight = height;iframe.style.height = height + "px";}requestAnimationFrame(tick);}requestAnimationFrame(tick);
Try not to use globalwindow & document
node.ownerDocumentdocument.defaultViewUse these
arrayFromIframe instanceof ArrayArray.isArray(arrayFromIframe)({}).toString.call(arrayFromIframe)!!=== "[object Array]"Don’t trust inheritance
Layers? Modals?
Dropdown ▼
Dropdown ▼solution 1:expand iframecontents of the first-party page
Dropdown ▼solution 2:scroll view
Dropdown ▼solution 3:more iframes™r+ pageYOffset
let layerManager: (module LayerManager) =environment )== "THIRD_PARTY" ?((module ThirdPartyLayerManager): (module LayerManager)) :((module DefaultLayerManager): (module LayerManager));module LayerManager = (val layerManager);
ReactDOM.createPortal
No persistent storageNo service workers...
SDK needs to be small
no (or very few)polyfills
Can't break MooToolsor Prototype.js
Bundler conflicts{+/* !!... #*/output: {+/* !!... #*/jsonpFunction: "namespaced__define",}}
Booting
domReady?
whenDOMContentLoaded orload fire, you don't knowif your booting script isready
async loop
function checkSdk() {if(window.myBootingFunction) {window.myBootingFunction()} else {setTimeout(checkSdk, 40)}}checkSdk()
third-party 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");
register eventsbefore your actualscript has loaded
If you're going tomake a third-partywidget:keep all that in mind
the code you madeyour client install ontheir page needs towork forever
you can only addnon-breakingchanges
tl;dr;styles: you're fuckedscripts: you're fuckedAPI: no breaking change
Thank youGot any questions?✋