Skip to content

Commit

Permalink
feat: Improve notifications of stylesheet status for session replay (#…
Browse files Browse the repository at this point in the history
…1190)

Co-authored-by: Chunwai Li <[email protected]>
  • Loading branch information
metal-messiah and cwli24 authored Sep 19, 2024
1 parent fd033a7 commit a21b939
Show file tree
Hide file tree
Showing 7 changed files with 30 additions and 41 deletions.
4 changes: 2 additions & 2 deletions docs/supportability-metrics.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,8 +192,6 @@ A timeslice metric is harvested to the JSE/XHR consumer. An aggregation service
* Config/SessionReplay/AutoStart/Modified
<!--- init.Session_replay.collect_fonts was Changed from the default --->
* Config/SessionReplay/CollectFonts/Modified
<!--- init.Session_replay.inline_stylesheet was Changed from the default --->
* Config/SessionReplay/InlineStylesheet/Modified
<!--- init.Session_replay.inline_images was Changed from the default --->
* Config/SessionReplay/InlineImages/Modified
<!--- init.Session_replay.mask_all_inputs was Changed from the default --->
Expand Down Expand Up @@ -228,6 +226,8 @@ A timeslice metric is harvested to the JSE/XHR consumer. An aggregation service
* SessionReplay/Payload/Missing-Inline-Css/Failed
<!--- SessionReplay Detected missing inline CSS contents but was able to fix them --->
* SessionReplay/Payload/Missing-Inline-Css/Fixed
<!--- SessionReplay Detected missing inline CSS contents but skipped fixing them due to configuration --->
* SessionReplay/Payload/Missing-Inline-Css/Skipped

### Soft Nav
<!--- Soft Nav initial page load Interaction Duration in Ms --->
Expand Down
3 changes: 1 addition & 2 deletions src/common/config/init.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,7 @@ const model = () => {
error_sampling_rate: 100, // float from 0 - 100
collect_fonts: false, // serialize fonts for collection without public asset url, this is currently broken in RRWeb -- https://github.com/rrweb-io/rrweb/issues/1304. When fixed, revisit with test cases
inline_images: false, // serialize images for collection without public asset url -- right now this is only useful for testing as it easily generates payloads too large to be harvested
inline_stylesheet: true, // serialize css for collection without public asset url
fix_stylesheets: true, // fetch missing stylesheet resources for inlining, only works if 'inline_stylesheet' is also true
fix_stylesheets: true, // fetch missing stylesheet resources for inlining
// recording config settings
mask_all_inputs: true,
// this has a getter/setter to facilitate validation of the selectors
Expand Down
3 changes: 1 addition & 2 deletions src/features/session_replay/aggregate/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ export class Aggregate extends AggregateBase {
this.handleError(e)
}, this.featureName, this.ee)

const { error_sampling_rate, sampling_rate, autoStart, block_selector, mask_text_selector, mask_all_inputs, inline_stylesheet, inline_images, collect_fonts } = getConfigurationValue(this.agentIdentifier, 'session_replay')
const { error_sampling_rate, sampling_rate, autoStart, block_selector, mask_text_selector, mask_all_inputs, inline_images, collect_fonts } = getConfigurationValue(this.agentIdentifier, 'session_replay')

this.waitForFlags(['srs', 'sr']).then(([srMode, entitled]) => {
this.entitled = !!entitled
Expand All @@ -129,7 +129,6 @@ export class Aggregate extends AggregateBase {
/** Detect if the default configs have been altered and report a SM. This is useful to evaluate what the reasonable defaults are across a customer base over time */
if (!autoStart) handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/AutoStart/Modified'], undefined, FEATURE_NAMES.metrics, this.ee)
if (collect_fonts === true) handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/CollectFonts/Modified'], undefined, FEATURE_NAMES.metrics, this.ee)
if (inline_stylesheet !== true) handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/InlineStylesheet/Modified'], undefined, FEATURE_NAMES.metrics, this.ee)
if (inline_images === true) handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/InlineImages/Modifed'], undefined, FEATURE_NAMES.metrics, this.ee)
if (mask_all_inputs !== true) handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/MaskAllInputs/Modified'], undefined, FEATURE_NAMES.metrics, this.ee)
if (block_selector !== '[data-nr-block]') handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/BlockSelector/Modified'], undefined, FEATURE_NAMES.metrics, this.ee)
Expand Down
34 changes: 20 additions & 14 deletions src/features/session_replay/shared/recorder.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,8 @@ export class Recorder {
this.lastMeta = false
/** The parent class that instantiated the recorder */
this.parent = parent
/** Config to inform to inline stylesheet contents (true default) */
this.shouldInlineStylesheets = getConfigurationValue(this.parent.agentIdentifier, 'session_replay.inline_stylesheet')
/** A flag that can be set to false by failing conversions to stop the fetching process */
this.shouldFix = this.shouldInlineStylesheets && getConfigurationValue(this.parent.agentIdentifier, 'session_replay.fix_stylesheets')
this.shouldFix = getConfigurationValue(this.parent.agentIdentifier, 'session_replay.fix_stylesheets')
/** The method to stop recording. This defaults to a noop, but is overwritten once the recording library is imported and initialized */
this.stopRecording = () => { /* no-op until set by rrweb initializer */ }
}
Expand Down Expand Up @@ -73,10 +71,14 @@ export class Recorder {
/** Begin recording using configured recording lib */
startRecording () {
this.recording = true
const { block_class, ignore_class, mask_text_class, block_selector, mask_input_options, mask_text_selector, mask_all_inputs, inline_stylesheet, inline_images, collect_fonts } = getConfigurationValue(this.parent.agentIdentifier, 'session_replay')
const { block_class, ignore_class, mask_text_class, block_selector, mask_input_options, mask_text_selector, mask_all_inputs, inline_images, collect_fonts } = getConfigurationValue(this.parent.agentIdentifier, 'session_replay')
const customMasker = (text, element) => {
if (element?.type?.toLowerCase() !== 'password' && (element?.dataset.nrUnmask !== undefined || element?.classList.contains('nr-unmask'))) return text
return '*'.repeat(text.length)
try {
if (typeof element?.type === 'string' && element.type.toLowerCase() !== 'password' && (element?.dataset?.nrUnmask !== undefined || element?.classList?.contains('nr-unmask'))) return text
} catch (err) {
// likely an element was passed to this handler that was invalid and was missing attributes or methods
}
return '*'.repeat(text?.length || 0)
}
// set up rrweb configurations for maximum privacy --
// https://newrelic.atlassian.net/wiki/spaces/O11Y/pages/2792293280/2023+02+28+Browser+-+Session+Replay#Configuration-options
Expand All @@ -91,7 +93,7 @@ export class Recorder {
maskTextFn: customMasker,
maskAllInputs: mask_all_inputs,
maskInputFn: customMasker,
inlineStylesheet: inline_stylesheet,
inlineStylesheet: true,
inlineImages: inline_images,
collectFonts: collect_fonts,
checkoutEveryNms: CHECKOUT_MS[this.parent.mode],
Expand Down Expand Up @@ -119,13 +121,17 @@ export class Recorder {
* @param {*} isCheckout - Flag indicating if the payload was triggered as a checkout
*/
audit (event, isCheckout) {
/** only run the audit if inline_stylesheets is configured as on (default behavior) */
if (this.shouldInlineStylesheets === false || !this.shouldFix) {
this.currentBufferTarget.inlinedAllStylesheets = false
return this.store(event, isCheckout)
}
/** An count of stylesheet objects that were blocked from accessing contents via JS */
const incompletes = stylesheetEvaluator.evaluate()
const missingInlineSMTag = 'SessionReplay/Payload/Missing-Inline-Css/'
/** only run the full fixing behavior (more costly) if fix_stylesheets is configured as on (default behavior) */
if (!this.shouldFix) {
if (incompletes > 0) {
this.currentBufferTarget.inlinedAllStylesheets = false
handle(SUPPORTABILITY_METRIC_CHANNEL, [missingInlineSMTag + 'Skipped', incompletes], undefined, FEATURE_NAMES.metrics, this.parent.ee)
}
return this.store(event, isCheckout)
}
/** Only stop ignoring data if already ignoring and a new valid snapshap is taking place (0 incompletes and we get a meta node for the snap) */
if (!incompletes && this.#fixing && event.type === RRWEB_EVENT_TYPES.Meta) this.#fixing = false
if (incompletes > 0) {
Expand All @@ -135,8 +141,8 @@ export class Recorder {
this.currentBufferTarget.inlinedAllStylesheets = false
this.shouldFix = false
}
handle(SUPPORTABILITY_METRIC_CHANNEL, ['SessionReplay/Payload/Missing-Inline-Css/Failed', failedToFix], undefined, FEATURE_NAMES.metrics, this.parent.ee)
handle(SUPPORTABILITY_METRIC_CHANNEL, ['SessionReplay/Payload/Missing-Inline-Css/Fixed', incompletes - failedToFix], undefined, FEATURE_NAMES.metrics, this.parent.ee)
handle(SUPPORTABILITY_METRIC_CHANNEL, [missingInlineSMTag + 'Failed', failedToFix], undefined, FEATURE_NAMES.metrics, this.parent.ee)
handle(SUPPORTABILITY_METRIC_CHANNEL, [missingInlineSMTag + 'Fixed', incompletes - failedToFix], undefined, FEATURE_NAMES.metrics, this.parent.ee)
this.takeFullSnapshot()
})
/** Only start ignoring data if got a faulty snapshot */
Expand Down
9 changes: 5 additions & 4 deletions src/features/session_replay/shared/stylesheet-evaluator.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { isBrowserScope } from '../../../common/constants/runtime'

class StylesheetEvaluator {
#evaluated = new WeakSet()
#fetchProms = []
#brokenSheets = []
/**
* Flipped to true if stylesheets that cannot be natively inlined are detected by the stylesheetEvaluator class
* Used at harvest time to denote that all subsequent payloads are subject to this and customers should be advised to handle crossorigin decoration
Expand All @@ -17,6 +17,7 @@ class StylesheetEvaluator {
*/
evaluate () {
let incompletes = 0
this.#brokenSheets = []
if (isBrowserScope) {
for (let i = 0; i < Object.keys(document.styleSheets).length; i++) {
if (!this.#evaluated.has(document.styleSheets[i])) {
Expand All @@ -27,7 +28,7 @@ class StylesheetEvaluator {
} catch (err) {
if (!document.styleSheets[i].href) return
incompletes++
this.#fetchProms.push(this.#fetchAndOverride(document.styleSheets[i]))
this.#brokenSheets.push(document.styleSheets[i])
}
}
}
Expand All @@ -41,8 +42,8 @@ class StylesheetEvaluator {
* @returns {Promise}
*/
async fix () {
await Promise.all(this.#fetchProms)
this.#fetchProms = []
await Promise.all(this.#brokenSheets.map(sheet => this.#fetchAndOverride(sheet)))
this.#brokenSheets = []
const failedToFix = this.failedToFix
this.failedToFix = 0
return failedToFix
Expand Down
17 changes: 1 addition & 16 deletions tests/specs/session-replay/rrweb-configuration.e2e.js
Original file line number Diff line number Diff line change
Expand Up @@ -321,22 +321,7 @@ describe('RRWeb Configuration', () => {
expect(imageNodes[0].attributes.rr_dataURL).toBeUndefined()
})

it('inline_stylesheet false DOES NOT add inline text', async () => {
const [sessionReplaysHarvests] = await Promise.all([
sessionReplaysCapture.waitForResult({ timeout: 10000 }),
browser.url(await browser.testHandle.assetURL('rrweb-instrumented.html', srConfig({ session_replay: { inline_stylesheet: false } })))
.then(() => browser.waitForAgentLoad())
])

expect(decodeAttributes(sessionReplaysHarvests[0].request.query.attributes).inlinedAllStylesheets).toEqual(false)

const linkNodes = JSONPath({ path: '$.[*].request.body.[?(!!@ && @.tagName===\'link\' && @.attributes.type===\'text/css\')]', json: sessionReplaysHarvests })
linkNodes.forEach(linkNode => {
expect(linkNode.attributes._cssText).toBeUndefined()
})
})

it('inline_stylesheet true DOES add inline text', async () => {
it('agent always inlines stylesheets', async () => {
const [sessionReplaysHarvests] = await Promise.all([
sessionReplaysCapture.waitForResult({ timeout: 10000 }),
browser.url(await browser.testHandle.assetURL('rrweb-instrumented.html', srConfig()))
Expand Down
1 change: 0 additions & 1 deletion tests/unit/common/config/init.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,6 @@ test('init props exist and return expected defaults', () => {
harvestTimeSeconds: 60,
ignore_class: 'nr-ignore',
inline_images: false,
inline_stylesheet: true,
mask_all_inputs: true,
mask_input_options: {
color: false,
Expand Down

0 comments on commit a21b939

Please sign in to comment.