# How Markdown Here Detects and Converts Markdown Content in Email Compose Fields

> Learn how Markdown Here detects and converts Markdown in email fields. It parses, renders with marked and highlight.js, and injects HTML while preserving your source for easy editing.

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

---

**Markdown Here detects rich-text email compose fields by traversing the DOM from `document.activeElement` to locate editable containers, then extracts raw markdown via `MdhHtmlToText`, sends it to a background script that runs the **marked** parser and **highlight.js**, and finally injects the rendered HTML back into the editor while preserving the original source in a hidden wrapper for unrendering.**

Markdown Here enables users to write emails in Markdown within webmail clients like Gmail and Outlook. According to the adam-p/markdown-here source code, the extension employs a seven-stage pipeline that bridges content scripts, background processes, and DOM manipulation to detect and convert Markdown content in email compose fields.

## Stage 1: Locating the Focused Compose Element

In [`src/common/markdown-here.js`](https://github.com/adam-p/markdown-here/blob/main/src/common/markdown-here.js) (lines 50-98), the `findFocusedElem()` method traverses upward from `document.activeElement` to identify the target container. The implementation handles complex scenarios including same-origin iframes and Firefox/Thunderbird-specific quirks. When the traversal yields an `<html>` node, the code automatically substitutes it with the `<body>` element (lines 90-95) to ensure a valid editing context.

## Stage 2: Verifying Rich-Text Editability

Before attempting conversion, the extension validates that the discovered element supports rich-text editing. The `elementCanBeRendered()` function in [`src/common/markdown-here.js`](https://github.com/adam-p/markdown-here/blob/main/src/common/markdown-here.js) (lines 100-108) inspects the `contentEditable` and `designMode` flags, returning `true` only for rich-edit fields such as Gmail compose areas or Outlook web editors. This check prevents the extension from activating on plain-text inputs or read-only elements.

## Stage 3: Determining Render vs. Unrender State

The extension must decide whether to render fresh Markdown or revert previously rendered HTML. In [`src/common/markdown-here.js`](https://github.com/adam-p/markdown-here/blob/main/src/common/markdown-here.js) (lines 64-110), the code searches for existing `.markdown-here-wrapper` elements using `findMarkdownHereWrapper()` and `findMarkdownHereWrappersInRange()`. If wrappers exist within the current selection, the system triggers an **unrender** operation to restore the original Markdown; otherwise, it proceeds to **render** the content as HTML.

## Stage 4: Extracting Raw Markdown Text

Once rendering is confirmed, [`src/chrome/contentscript.js`](https://github.com/adam-p/markdown-here/blob/main/src/chrome/contentscript.js) (lines 57-68) initiates the extraction process. The `requestMarkdownConversion` function instantiates an `MdhHtmlToText` helper that parses the DOM selection and extracts visible text while handling email signatures. The raw Markdown is then transmitted to the privileged background script via `Utils.makeRequestToPrivilegedScript` with the action parameter set to `'render'`.

```javascript
function requestMarkdownConversion(elem, range, callback) {
  var mdhHtmlToText = new MdhHtmlToText.MdhHtmlToText(elem, range);
  Utils.makeRequestToPrivilegedScript(
    document,
    { action: 'render', mdText: mdhHtmlToText.get() },
    function (response) {
      var renderedMarkdown = mdhHtmlToText.postprocess(response.html);
      callback(renderedMarkdown, response.css);
    });
}

```

## Stage 5: Background Markdown-to-HTML Conversion

The heavy lifting occurs in [`src/chrome/backgroundscript.js`](https://github.com/adam-p/markdown-here/blob/main/src/chrome/backgroundscript.js) (lines 17-28), which receives the render request and processes it through `MarkdownRender.markdownRender` (implemented in [`src/common/markdown-render.js`](https://github.com/adam-p/markdown-here/blob/main/src/common/markdown-render.js)). This component orchestrates the **marked** parser for Markdown processing and **highlight.js** for syntax highlighting, generating both HTML output and associated CSS styles based on user preferences stored in `OptionsStore`.

```javascript
if (request.action === 'render') {
  OptionsStore.get(function (prefs) {
    responseCallback({
      html: MarkdownRender.markdownRender(
        request.mdText, prefs, marked, hljs),
      css: prefs['main-css'] + prefs['syntax-css']
    });
  });
  return true;
}

```

## Stage 6: Injecting Rendered HTML with Inline Styles

With the converted HTML ready, [`src/common/markdown-here.js`](https://github.com/adam-p/markdown-here/blob/main/src/common/markdown-here.js) (lines 64-97 and 121-168) executes the injection sequence. The `renderMarkdown` method constructs a wrapper `<div class="markdown-here-wrapper">` containing the rendered content and a hidden "raw-MD holder" `<div>` that base64-encodes the original Markdown for future unrendering. The `replaceRange` function swaps the user's selection with this wrapper. To ensure compatibility with email clients like Gmail that strip `<style>` tags, the `makeStylesExplicit` utility traverses CSS rules and applies them as inline element styles.

## Stage 7: Mutation Monitoring and Cleanup

After injection, a `MutationObserver` attaches to the wrapper element (lines 98-115 in [`src/common/markdown-here.js`](https://github.com/adam-p/markdown-here/blob/main/src/common/markdown-here.js)) to monitor for content modifications. This observer flags when users manually edit the rendered HTML, enabling the extension to display warnings during unrender operations if the converted content has been altered.

## Script Injection and Permission Architecture

The extension conserves resources by injecting scripts only when necessary. Upon installation, [`src/chrome/backgroundscript.js`](https://github.com/adam-p/markdown-here/blob/main/src/chrome/backgroundscript.js) creates a context-menu entry and monitors tab loading events. When a page loads, the script checks permissions via `ContentPermissions.hasPermission` and injects the contents of `Injector.CONTENT_SCRIPTS` (including [`contentscript.js`](https://github.com/adam-p/markdown-here/blob/main/contentscript.js)). A global flag `window.markdownHereInjected` prevents double-loading in single-page applications.

```javascript
// In a content script (e.g., after the user clicks the toolbar button)
chrome.runtime.sendMessage({action: 'button-click'}, function () {
  // markdownHere is already loaded by the injector
  markdownHere(
    document,                         // the page document
    requestMarkdownConversion,        // function that sends markdown to background
    console.log,                      // optional logger
    function (elem, rendered) {      // callback after render/unrender
      console.log('Done – rendered?', rendered);
    });
});

```

## Summary

- **DOM Traversal**: `findFocusedElem()` in [`src/common/markdown-here.js`](https://github.com/adam-p/markdown-here/blob/main/src/common/markdown-here.js) locates editable containers by walking up from `document.activeElement`, handling iframes and browser quirks.
- **Editability Check**: `elementCanBeRendered()` verifies `contentEditable` or `designMode` flags before processing.
- **State Detection**: The presence of `.markdown-here-wrapper` elements determines whether to render new Markdown or unrender existing HTML.
- **Secure Processing**: Raw Markdown extraction occurs in content scripts, but parsing via **marked** and **highlight.js** runs in the privileged background context.
- **Style Safety**: `makeStylesExplicit()` converts CSS to inline styles to prevent email clients from stripping `<style>` tags.
- **Reversibility**: Base64-encoded original Markdown is preserved in a hidden wrapper div, enabling lossless unrendering.

## Frequently Asked Questions

### How does Markdown Here identify which email compose field to convert?

Markdown Here uses `markdownHere.findFocusedElem()` in [`src/common/markdown-here.js`](https://github.com/adam-p/markdown-here/blob/main/src/common/markdown-here.js) to traverse the DOM from `document.activeElement` upward, drilling into same-origin iframes and handling Firefox/Thunderbird-specific edge cases. If the traversal reaches an `<html>` node, it substitutes the `<body>` element to ensure a valid editing target.

### Can Markdown Here convert Markdown in plain-text email fields?

No. The `elementCanBeRendered()` function explicitly checks for `contentEditable` or `designMode` properties in [`src/common/markdown-here.js`](https://github.com/adam-p/markdown-here/blob/main/src/common/markdown-here.js) (lines 100-108). Only rich-text editors like Gmail Compose or Outlook Web return `true`, preventing activation on plain-text inputs or non-editable regions.

### Where does the actual Markdown parsing happen?

The parsing occurs in the background script ([`src/chrome/backgroundscript.js`](https://github.com/adam-p/markdown-here/blob/main/src/chrome/backgroundscript.js)) rather than the content script for security. The content script extracts raw text via `MdhHtmlToText` and sends it via `Utils.makeRequestToPrivilegedScript`, while the background process executes `MarkdownRender.markdownRender()` using the **marked** library and **highlight.js** for syntax highlighting.

### How does the extension preserve the original Markdown after conversion?

During rendering, [`src/common/markdown-here.js`](https://github.com/adam-p/markdown-here/blob/main/src/common/markdown-here.js) creates a wrapper `<div class="markdown-here-wrapper">` containing the rendered HTML and a hidden "raw-MD holder" `<div>` that stores the base64-encoded original Markdown. A `MutationObserver` monitors the wrapper to detect manual edits, allowing the extension to restore the original text when unrendering only if the HTML remains unmodified.