Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions injected/src/features/page-context.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,23 @@ function collapseWhitespace(str) {
return typeof str === 'string' ? str.replace(/\s+/g, ' ') : '';
}

function checkNodeIsVisible(node) {
// Note that we're not checking if the node is connected to the document
// we are cloning the node so it's never connected.

try {
const style = window.getComputedStyle(node);

// Check primary visibility properties
if (style.display === 'none' || style.visibility === 'hidden' || parseFloat(style.opacity) === 0) {
return false;
}
return true;
} catch (e) {
return false;
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Styling Checks Fail on Detached Nodes

The checkNodeIsVisible() function uses getComputedStyle() on cloned, disconnected DOM nodes. Since getComputedStyle() returns default values for disconnected elements, the visibility checks are unreliable, potentially filtering out visible content or including hidden content.

Fix in Cursor Fix in Web


function domToMarkdown(node, maxLength = Infinity) {
if (node.nodeType === Node.TEXT_NODE) {
return collapseWhitespace(node.textContent);
Expand All @@ -16,6 +33,9 @@ function domToMarkdown(node, maxLength = Infinity) {
}

const tag = node.tagName.toLowerCase();
if (!checkNodeIsVisible(node)) {
return '';
}

// Build children string incrementally to exit early when maxLength is exceeded
let children = '';
Expand Down Expand Up @@ -74,6 +94,8 @@ export default class PageContext extends ContentFeature {
mutationObserver = null;
lastSentContent = null;
listenForUrlChanges = true;
/** @type {ReturnType<typeof setTimeout> | null} */
#delayedRecheckTimer = null;

init() {
if (!this.shouldActivate()) {
Expand All @@ -92,10 +114,15 @@ export default class PageContext extends ContentFeature {
}
window.addEventListener('load', () => {
this.handleContentCollectionRequest();
this.scheduleDelayedRecheck();
});
if (this.getFeatureSettingEnabled('subscribeToHashChange', 'enabled')) {
window.addEventListener('hashchange', () => {
// Immediate collection
this.handleContentCollectionRequest();

// Schedule delayed recheck after DOM settles
this.scheduleDelayedRecheck();
});
}
if (this.getFeatureSettingEnabled('subscribeToPageShow', 'enabled')) {
Expand Down Expand Up @@ -145,7 +172,11 @@ export default class PageContext extends ContentFeature {
if (!this.shouldActivate()) {
return;
}
// Immediate collection
this.handleContentCollectionRequest();

// Schedule delayed recheck after DOM settles
this.scheduleDelayedRecheck();
}

setup() {
Expand All @@ -171,6 +202,16 @@ export default class PageContext extends ContentFeature {
this.stopObserving();
}

/**
* Clear all pending timers
*/
clearTimers() {
if (this.#delayedRecheckTimer) {
clearTimeout(this.#delayedRecheckTimer);
this.#delayedRecheckTimer = null;
}
}

set cachedContent(content) {
if (content === undefined) {
this.invalidateCache();
Expand Down Expand Up @@ -198,6 +239,35 @@ export default class PageContext extends ContentFeature {
}
}

/**
* Schedule a delayed recheck after navigation events
*/
scheduleDelayedRecheck() {
// Clear any existing delayed recheck
if (this.#delayedRecheckTimer) {
clearTimeout(this.#delayedRecheckTimer);
}

const delayMs = this.getFeatureSetting('navigationRecheckDelayMs') || 1500;

this.log.info('Scheduling delayed recheck', { delayMs });
this.#delayedRecheckTimer = setTimeout(() => {
this.log.info('Performing delayed recheck after navigation');

// Invalidate existing cache
this.invalidateCache();
this.clearTimers();

// Collect fresh content
const freshContent = this.collectPageContent();

// Only send if content has meaningfully changed
this.sendContentResponse(freshContent);

this.#delayedRecheckTimer = null;
}, delayMs);
}

startObserving() {
this.log.info('Starting observing', this.mutationObserver, this.#cachedContent);
if (this.mutationObserver && this.#cachedContent && !this.isObserving) {
Expand Down Expand Up @@ -300,6 +370,7 @@ export default class PageContext extends ContentFeature {

this.log.info('Calling domToMarkdown', clone.innerHTML);
content += domToMarkdown(clone, upperLimit);
this.log.info('Content markdown', content, clone, contentRoot);
}
content = content.trim();

Expand Down