# How Markdown Here Uses a MutationObserver to Detect User Modifications

> Learn how Markdown Here employs a MutationObserver to detect user modifications in rendered HTML, safeguarding your content from accidental data loss during re-rendering.

- Repository: [Adam Pritchard/markdown-here](https://github.com/adam-p/markdown-here)
- Tags: internals
- Published: 2026-03-05

---

**Markdown Here attaches a `MutationObserver` to the rendered HTML wrapper immediately after conversion to flag any subsequent manual edits, preventing accidental data loss during re-rendering.**

The `adam-p/markdown-here` extension transforms plain-text Markdown into formatted HTML inside email compose windows. Once the HTML is inserted, users often manually tweak fonts, colors, or text directly in the rich editor. To ensure these manual changes are not silently overwritten if the user toggles Markdown rendering again, the extension monitors the DOM for post-render modifications using the native browser `MutationObserver` API.

## Why Detect User Modifications?

When Markdown Here renders content, it wraps the generated HTML in a `<div class="markdown-here-wrapper">`. If a user edits this rendered view—typing new text, deleting nodes, or formatting existing content—and then attempts to toggle back to Markdown or re-render, the extension must decide whether to preserve the raw Markdown source or the modified HTML. Without detection logic, the extension would overwrite the user's manual edits with the original rendered output. The `MutationObserver` acts as a tripwire, setting a persistent flag on the wrapper that signals "this content has diverged from the source."

## MutationObserver Implementation in [`src/common/markdown-here.js`](https://github.com/adam-p/markdown-here/blob/main/src/common/markdown-here.js)

The detection logic resides in [`src/common/markdown-here.js`](https://github.com/adam-p/markdown-here/blob/main/src/common/markdown-here.js), where the extension creates a `MutationObserver` inside a `setTimeout` callback approximately 100 milliseconds after inserting the wrapper. This delay ensures the initial rendering DOM mutations have settled before monitoring begins.

The implementation checks for modern `MutationObserver` or the legacy `WebKitMutationObserver` fallback, then attaches the observer exclusively to the wrapper element:

```javascript
// src/common/markdown-here.js: lines 502-513
wrapper.ownerDocument.defaultView.setTimeout(function addMutationObserver() {
  var SupportedMutationObserver =
        wrapper.ownerDocument.defaultView.MutationObserver ||
        wrapper.ownerDocument.defaultView.WebKitMutationObserver;
  if (typeof(SupportedMutationObserver) !== 'undefined') {
    var observer = new SupportedMutationObserver(function (mutations) {
      // User has changed the rendered content – flag it.
      wrapper.setAttribute('markdown-here-wrapper-content-modified', true);
      // No need to keep watching after the first change.
      observer.disconnect();
    });
    // Watch for any DOM changes within the wrapper.
    observer.observe(wrapper, { childList: true, characterData: true, subtree: true });
  }
}, 100);

```

### Observer Configuration Options

The observer is configured with three specific boolean flags that ensure comprehensive detection of user activity:

- **`childList: true`** – Fires when nodes are added or removed inside the wrapper, such as when the user types new characters or deletes existing elements.
- **`characterData: true`** – Fires when text nodes inside the wrapper are modified, capturing edits within existing paragraphs or headings.
- **`subtree: true`** – Extends monitoring to all descendants of the wrapper, ensuring changes nested deep within the HTML structure are caught.

### The Modification Flag

When the observer callback fires—indicating the first detected DOM mutation—it performs two atomic operations:

1. **Sets the attribute** `markdown-here-wrapper-content-modified="true"` on the wrapper element.
2. **Disconnects itself** via `observer.disconnect()` to avoid performance overhead from continuous monitoring.

This attribute serves as a persistent marker that the extension checks later. If present, Markdown Here warns the user that re-rendering will discard their manual HTML edits, preventing silent data loss.

## Practical Detection Example

To implement similar logic in your own extension or web application, follow this pattern derived from the markdown-here source:

```javascript
function attachModificationObserver(wrapper) {
  const Obs = window.MutationObserver || window.WebKitMutationObserver;
  if (!Obs) return; // Graceful degradation for legacy browsers

  const observer = new Obs((mutations) => {
    // Flag the wrapper and stop observing
    wrapper.setAttribute('markdown-here-wrapper-content-modified', true);
    observer.disconnect();
  });

  // Monitor all structural and text changes within the tree
  observer.observe(wrapper, {
    childList: true,
    characterData: true,
    subtree: true
  });
}

// Usage check before destructive operations
function hasUserModified(wrapper) {
  return wrapper.hasAttribute('markdown-here-wrapper-content-modified');
}

```

## Browser Compatibility Considerations

The code explicitly checks for `window.MutationObserver` and falls back to `window.WebKitMutationObserver` for older Safari versions. If neither API exists, the extension simply skips detection, meaning users on very old browsers will not receive the modification warning. The 100-millisecond `setTimeout` delay prevents the observer from firing on the initial DOM insertion mutations caused by the extension itself rather than the user.

## Summary

- **Purpose**: The `MutationObserver` detects manual user edits to rendered Markdown HTML to prevent accidental overwrites during re-rendering.
- **Location**: Implemented in [`src/common/markdown-here.js`](https://github.com/adam-p/markdown-here/blob/main/src/common/markdown-here.js) immediately after wrapper insertion.
- **Mechanism**: Watches the wrapper with `{ childList: true, characterData: true, subtree: true }` and sets `markdown-here-wrapper-content-modified="true"` on first mutation.
- **Efficiency**: The observer disconnects immediately after the first detected change to minimize performance impact.
- **Fallback**: Supports legacy `WebKitMutationObserver` for older browser compatibility.

## Frequently Asked Questions

### What specific DOM changes trigger the MutationObserver in Markdown Here?

The observer fires on any **childList** changes (nodes added or removed), **characterData** changes (text content modified), or **subtree** changes (modifications to nested descendants) within the `markdown-here-wrapper` element. This covers typing, deletion, formatting, and paste operations.

### Why does the observer disconnect after detecting the first mutation?

The observer calls `disconnect()` immediately after setting the modification flag to **minimize performance overhead**. Since the extension only needs to know whether the content has been touched—not track every individual keystroke—persistent observation is unnecessary and computationally wasteful.

### How does the extension check if content has been modified later?

Before re-rendering or converting back to Markdown, the extension queries the wrapper element for the presence of the **`markdown-here-wrapper-content-modified`** attribute using `hasAttribute()`. If found, the extension displays a warning dialog to the user.

### What happens if the browser does not support MutationObserver?

If neither `MutationObserver` nor `WebKitMutationObserver` is available, the extension **silently skips the detection logic**. The rendering functionality remains intact, but the user will not receive a warning if they edit the rendered HTML and attempt to re-render, risking potential data loss on those legacy browsers.