Repository: huhu/rust-search-extension (language: rust)

Issue:
Integrate with `macro_railroad_ext`
See https://github.com/lukaslueg/macro_railroad_ext/issues/7


Discussion / hints:

Fix patch:
diff --git a/.gitignore b/.gitignore
index 99d08116..7f43e563 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,9 +2,11 @@
 .DS_Store
 *.crx
 *.pem
+Cargo.lock
 web-ext-artifacts
 node_modules
 target
 docs/public
 manifest.json
-extension/manage/
\ No newline at end of file
+extension/manage/
+macro_railroad/pkg
\ No newline at end of file
diff --git a/Makefile b/Makefile
index 74609733..619a861b 100644
--- a/Makefile
+++ b/Makefile
@@ -9,3 +9,14 @@ assert:
 # Build manage pages
 manage:
 	@cd manage && cargo run
+
+macro-railroad: extension/script/macro-railroad-wasm.js extension/wasm/macro-railroad.wasm
+
+extension/script/macro-railroad-wasm.js: macro-railroad/pkg/macro-railroad.js
+	cp $< $@
+
+extension/wasm/macro-railroad.wasm: macro-railroad/pkg/macro-railroad.wasm
+	cp $< $@
+
+macro-railroad/pkg/macro-railroad.wasm: macro-railroad/Cargo.lock macro-railroad/Cargo.toml macro-railroad/build.rs macro-railroad/src/lib.rs
+	cd macro-railroad && wasm-pack build -t no-modules --out-name macro-railroad && mv pkg/macro-railroad_bg.wasm pkg/macro-railroad.wasm
\ No newline at end of file
diff --git a/core b/core
index 057f688d..84c1e30c 160000
--- a/core
+++ b/core
@@ -1,1 +1,1 @@
-Subproject commit 057f688d6e5db163e91ff54dbe42b48f47aa6b95
+Subproject commit 84c1e30c0f163bf67abd4f0e4b6c15248a65bfab
diff --git a/extension/assets/fullscreen.svg b/extension/assets/fullscreen.svg
new file mode 100644
index 00000000..43456e7a
--- /dev/null
+++ b/extension/assets/fullscreen.svg
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!-- Generator: Adobe Illustrator 18.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 32 32" style="enable-background:new 0 0 32 32;" xml:space="preserve">
+<g>
+	<g>
+		<path style="fill:#010002;" d="M32,26V2H0v24h14v2H8v2h16v-2h-6v-2H32z M2,24V4h28v20H2z"/>
+		<polygon style="fill:#010002;" points="28,14 28,6 20,6"/>
+		<polygon style="fill:#010002;" points="4,22 12,22 4,14"/>
+	</g>
+</g>
+</svg>
diff --git a/extension/assets/options.svg b/extension/assets/options.svg
new file mode 100644
index 00000000..233bf217
--- /dev/null
+++ b/extension/assets/options.svg
@@ -0,0 +1,1 @@
+<?xml version="1.0" ?><!DOCTYPE svg  PUBLIC '-//W3C//DTD SVG 1.1//EN'  'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'><svg height="32px" id="Layer_1" style="enable-background:new 0 0 32 32;" version="1.1" viewBox="0 0 32 32" width="32px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M4,10h24c1.104,0,2-0.896,2-2s-0.896-2-2-2H4C2.896,6,2,6.896,2,8S2.896,10,4,10z M28,14H4c-1.104,0-2,0.896-2,2  s0.896,2,2,2h24c1.104,0,2-0.896,2-2S29.104,14,28,14z M28,22H4c-1.104,0-2,0.896-2,2s0.896,2,2,2h24c1.104,0,2-0.896,2-2  S29.104,22,28,22z"/></svg>
\ No newline at end of file
diff --git a/extension/script/macro-railroad-wasm.js b/extension/script/macro-railroad-wasm.js
new file mode 100644
index 00000000..addb394d
--- /dev/null
+++ b/extension/script/macro-railroad-wasm.js
@@ -0,0 +1,430 @@
+let wasm_bindgen;
+(function() {
+    const __exports = {};
+    let wasm;
+
+    const cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
+
+    cachedTextDecoder.decode();
+
+    let cachedUint8Memory0 = new Uint8Array();
+
+    function getUint8Memory0() {
+        if (cachedUint8Memory0.byteLength === 0) {
+            cachedUint8Memory0 = new Uint8Array(wasm.memory.buffer);
+        }
+        return cachedUint8Memory0;
+    }
+
+    function getStringFromWasm0(ptr, len) {
+        return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len));
+    }
+
+    const heap = new Array(32).fill(undefined);
+
+    heap.push(undefined, null, true, false);
+
+    let heap_next = heap.length;
+
+    function addHeapObject(obj) {
+        if (heap_next === heap.length) heap.push(heap.length + 1);
+        const idx = heap_next;
+        heap_next = heap[idx];
+
+        heap[idx] = obj;
+        return idx;
+    }
+
+function getObject(idx) { return heap[idx]; }
+
+function dropObject(idx) {
+    if (idx < 36) return;
+    heap[idx] = heap_next;
+    heap_next = idx;
+}
+
+function takeObject(idx) {
+    const ret = getObject(idx);
+    dropObject(idx);
+    return ret;
+}
+/**
+*/
+__exports.start = function() {
+    wasm.start();
+};
+
+/**
+* @returns {any}
+*/
+__exports.getRailroadDefaultCss = function() {
+    const ret = wasm.getRailroadDefaultCss();
+    return takeObject(ret);
+};
+
+/**
+* @returns {any}
+*/
+__exports.getRailroadDigramCss = function() {
+    const ret = wasm.getRailroadDigramCss();
+    return takeObject(ret);
+};
+
+let cachedInt32Memory0 = new Int32Array();
+
+function getInt32Memory0() {
+    if (cachedInt32Memory0.byteLength === 0) {
+        cachedInt32Memory0 = new Int32Array(wasm.memory.buffer);
+    }
+    return cachedInt32Memory0;
+}
+
+const u32CvtShim = new Uint32Array(2);
+
+const int64CvtShim = new BigInt64Array(u32CvtShim.buffer);
+
+let WASM_VECTOR_LEN = 0;
+
+const cachedTextEncoder = new TextEncoder('utf-8');
+
+const encodeString = (typeof cachedTextEncoder.encodeInto === 'function'
+    ? function (arg, view) {
+    return cachedTextEncoder.encodeInto(arg, view);
+}
+    : function (arg, view) {
+    const buf = cachedTextEncoder.encode(arg);
+    view.set(buf);
+    return {
+        read: arg.length,
+        written: buf.length
+    };
+});
+
+function passStringToWasm0(arg, malloc, realloc) {
+
+    if (realloc === undefined) {
+        const buf = cachedTextEncoder.encode(arg);
+        const ptr = malloc(buf.length);
+        getUint8Memory0().subarray(ptr, ptr + buf.length).set(buf);
+        WASM_VECTOR_LEN = buf.length;
+        return ptr;
+    }
+
+    let len = arg.length;
+    let ptr = malloc(len);
+
+    const mem = getUint8Memory0();
+
+    let offset = 0;
+
+    for (; offset < len; offset++) {
+        const code = arg.charCodeAt(offset);
+        if (code > 0x7F) break;
+        mem[ptr + offset] = code;
+    }
+
+    if (offset !== len) {
+        if (offset !== 0) {
+            arg = arg.slice(offset);
+        }
+        ptr = realloc(ptr, len, len = offset + arg.length * 3);
+        const view = getUint8Memory0().subarray(ptr + offset, ptr + len);
+        const ret = encodeString(arg, view);
+
+        offset += ret.written;
+    }
+
+    WASM_VECTOR_LEN = offset;
+    return ptr;
+}
+
+function _assertClass(instance, klass) {
+    if (!(instance instanceof klass)) {
+        throw new Error(`expected instance of ${klass.name}`);
+    }
+    return instance.ptr;
+}
+/**
+* Parse the given macro_rules!()-source, returns an SVG and it's preferred width
+* @param {string} src
+* @param {DiagramOptions} options
+* @returns {Diagram}
+*/
+__exports.toDiagram = function(src, options) {
+    try {
+        const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
+        const ptr0 = passStringToWasm0(src, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+        const len0 = WASM_VECTOR_LEN;
+        _assertClass(options, DiagramOptions);
+        wasm.toDiagram(retptr, ptr0, len0, options.ptr);
+        var r0 = getInt32Memory0()[retptr / 4 + 0];
+        var r1 = getInt32Memory0()[retptr / 4 + 1];
+        var r2 = getInt32Memory0()[retptr / 4 + 2];
+        if (r2) {
+            throw takeObject(r1);
+        }
+        return Diagram.__wrap(r0);
+    } finally {
+        wasm.__wbindgen_add_to_stack_pointer(16);
+    }
+};
+
+/**
+*/
+class Diagram {
+
+    static __wrap(ptr) {
+        const obj = Object.create(Diagram.prototype);
+        obj.ptr = ptr;
+
+        return obj;
+    }
+
+    __destroy_into_raw() {
+        const ptr = this.ptr;
+        this.ptr = 0;
+
+        return ptr;
+    }
+
+    free() {
+        const ptr = this.__destroy_into_raw();
+        wasm.__wbg_diagram_free(ptr);
+    }
+    /**
+    * @returns {bigint}
+    */
+    get width() {
+        try {
+            const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
+            wasm.__wbg_get_diagram_width(retptr, this.ptr);
+            var r0 = getInt32Memory0()[retptr / 4 + 0];
+            var r1 = getInt32Memory0()[retptr / 4 + 1];
+            u32CvtShim[0] = r0;
+            u32CvtShim[1] = r1;
+            const n0 = int64CvtShim[0];
+            return n0;
+        } finally {
+            wasm.__wbindgen_add_to_stack_pointer(16);
+        }
+    }
+    /**
+    * @returns {string}
+    */
+    get svg() {
+        try {
+            const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
+            wasm.diagram_svg(retptr, this.ptr);
+            var r0 = getInt32Memory0()[retptr / 4 + 0];
+            var r1 = getInt32Memory0()[retptr / 4 + 1];
+            return getStringFromWasm0(r0, r1);
+        } finally {
+            wasm.__wbindgen_add_to_stack_pointer(16);
+            wasm.__wbindgen_free(r0, r1);
+        }
+    }
+}
+__exports.Diagram = Diagram;
+/**
+*/
+class DiagramOptions {
+
+    static __wrap(ptr) {
+        const obj = Object.create(DiagramOptions.prototype);
+        obj.ptr = ptr;
+
+        return obj;
+    }
+
+    __destroy_into_raw() {
+        const ptr = this.ptr;
+        this.ptr = 0;
+
+        return ptr;
+    }
+
+    free() {
+        const ptr = this.__destroy_into_raw();
+        wasm.__wbg_diagramoptions_free(ptr);
+    }
+    /**
+    * @returns {boolean}
+    */
+    get hideInternal() {
+        const ret = wasm.__wbg_get_diagramoptions_hideInternal(this.ptr);
+        return ret !== 0;
+    }
+    /**
+    * @param {boolean} arg0
+    */
+    set hideInternal(arg0) {
+        wasm.__wbg_set_diagramoptions_hideInternal(this.ptr, arg0);
+    }
+    /**
+    * @returns {boolean}
+    */
+    get keepGroups() {
+        const ret = wasm.__wbg_get_diagramoptions_keepGroups(this.ptr);
+        return ret !== 0;
+    }
+    /**
+    * @param {boolean} arg0
+    */
+    set keepGroups(arg0) {
+        wasm.__wbg_set_diagramoptions_keepGroups(this.ptr, arg0);
+    }
+    /**
+    * @returns {boolean}
+    */
+    get foldCommonTails() {
+        const ret = wasm.__wbg_get_diagramoptions_foldCommonTails(this.ptr);
+        return ret !== 0;
+    }
+    /**
+    * @param {boolean} arg0
+    */
+    set foldCommonTails(arg0) {
+        wasm.__wbg_set_diagramoptions_foldCommonTails(this.ptr, arg0);
+    }
+    /**
+    * @returns {boolean}
+    */
+    get showLegend() {
+        const ret = wasm.__wbg_get_diagramoptions_showLegend(this.ptr);
+        return ret !== 0;
+    }
+    /**
+    * @param {boolean} arg0
+    */
+    set showLegend(arg0) {
+        wasm.__wbg_set_diagramoptions_showLegend(this.ptr, arg0);
+    }
+    /**
+    */
+    constructor() {
+        const ret = wasm.diagramoptions_new();
+        return DiagramOptions.__wrap(ret);
+    }
+}
+__exports.DiagramOptions = DiagramOptions;
+
+async function load(module, imports) {
+    if (typeof Response === 'function' && module instanceof Response) {
+        if (typeof WebAssembly.instantiateStreaming === 'function') {
+            try {
+                return await WebAssembly.instantiateStreaming(module, imports);
+
+            } catch (e) {
+                if (module.headers.get('Content-Type') != 'application/wasm') {
+                    console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e);
+
+                } else {
+                    throw e;
+                }
+            }
+        }
+
+        const bytes = await module.arrayBuffer();
+        return await WebAssembly.instantiate(bytes, imports);
+
+    } else {
+        const instance = await WebAssembly.instantiate(module, imports);
+
+        if (instance instanceof WebAssembly.Instance) {
+            return { instance, module };
+
+        } else {
+            return instance;
+        }
+    }
+}
+
+function getImports() {
+    const imports = {};
+    imports.wbg = {};
+    imports.wbg.__wbg_log_97290aafbe6479da = function(arg0, arg1) {
+        console.log(getStringFromWasm0(arg0, arg1));
+    };
+    imports.wbg.__wbindgen_string_new = function(arg0, arg1) {
+        const ret = getStringFromWasm0(arg0, arg1);
+        return addHeapObject(ret);
+    };
+    imports.wbg.__wbg_new_693216e109162396 = function() {
+        const ret = new Error();
+        return addHeapObject(ret);
+    };
+    imports.wbg.__wbg_stack_0ddaca5d1abfb52f = function(arg0, arg1) {
+        const ret = getObject(arg1).stack;
+        const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+        const len0 = WASM_VECTOR_LEN;
+        getInt32Memory0()[arg0 / 4 + 1] = len0;
+        getInt32Memory0()[arg0 / 4 + 0] = ptr0;
+    };
+    imports.wbg.__wbg_error_09919627ac0992f5 = function(arg0, arg1) {
+        try {
+            console.error(getStringFromWasm0(arg0, arg1));
+        } finally {
+            wasm.__wbindgen_free(arg0, arg1);
+        }
+    };
+    imports.wbg.__wbindgen_object_drop_ref = function(arg0) {
+        takeObject(arg0);
+    };
+    imports.wbg.__wbindgen_throw = function(arg0, arg1) {
+        throw new Error(getStringFromWasm0(arg0, arg1));
+    };
+
+    return imports;
+}
+
+function initMemory(imports, maybe_memory) {
+
+}
+
+function finalizeInit(instance, module) {
+    wasm = instance.exports;
+    init.__wbindgen_wasm_module = module;
+    cachedInt32Memory0 = new Int32Array();
+    cachedUint8Memory0 = new Uint8Array();
+
+    wasm.__wbindgen_start();
+    return wasm;
+}
+
+function initSync(bytes) {
+    const imports = getImports();
+
+    initMemory(imports);
+
+    const module = new WebAssembly.Module(bytes);
+    const instance = new WebAssembly.Instance(module, imports);
+
+    return finalizeInit(instance, module);
+}
+
+async function init(input) {
+    if (typeof input === 'undefined') {
+        let src;
+        if (typeof document === 'undefined') {
+            src = location.href;
+        } else {
+            src = document.currentScript.src;
+        }
+        input = src.replace(/\.js$/, '_bg.wasm');
+    }
+    const imports = getImports();
+
+    if (typeof input === 'string' || (typeof Request === 'function' && input instanceof Request) || (typeof URL === 'function' && input instanceof URL)) {
+        input = fetch(input);
+    }
+
+    initMemory(imports);
+
+    const { instance, module } = await load(await input, imports);
+
+    return finalizeInit(instance, module);
+}
+
+wasm_bindgen = Object.assign(init, __exports);
+
+})();
diff --git a/extension/script/macro-railroad.css b/extension/script/macro-railroad.css
new file mode 100644
index 00000000..5f4f1304
--- /dev/null
+++ b/extension/script/macro-railroad.css
@@ -0,0 +1,72 @@
+div.railroad_container {
+    height: auto;
+    max-height: 100%;
+    max-width: 100%;
+    position: relative;
+}
+
+svg.railroad {
+    border: 1px solid grey;
+}
+
+div.railroad_modal {
+    z-index: 10000;
+    position: fixed;
+    width: 100vw;
+    height: 100vh;
+    top: 0;
+    left: 0;
+	background: rgba(0, 0, 0, 0.5);
+    visibility: hidden;
+    opacity: 0;
+    transition: opacity 0.5s, visibility 0s 0.5s;
+}
+
+div.railroad_modal_content > svg {
+    margin: auto;
+    position: absolute;
+    top: 0;
+    bottom: 0;
+    left: 0;
+    right: 0;
+    max-width: 95vw;
+    max-height: 95vh;
+    border: 2px solid black;
+}
+
+div.railroad_modal.railroad_active {
+    opacity: 1;
+    visibility: visible;
+    transition: opacity 0.5s;
+}
+
+div.railroad_icons {
+    position: absolute;
+    bottom: 8px;
+    right: 8px;
+}
+
+img.railroad_icon {
+    opacity: 0.3;
+    transition: opacity 1s;
+    width: 25px;
+}
+
+img.railroad_icon:hover {
+    opacity: 1.0;
+}
+
+div.railroad_dropdown_content {
+    border: 1px solid black;
+    visibility: hidden;
+    position: absolute;
+    overflow: auto;
+    z-index: 1;
+    background-color: rgba(240,240,240,0.95);
+    white-space: nowrap;
+    padding: 5px;
+}
+
+div.railroad_dropdown_show {
+    visibility: visible;
+}
diff --git a/extension/script/macro-railroad.js b/extension/script/macro-railroad.js
new file mode 100644
index 00000000..246969a7
--- /dev/null
+++ b/extension/script/macro-railroad.js
@@ -0,0 +1,162 @@
+document.addEventListener("DOMContentLoaded", async () => {
+    await load();
+});
+
+async function load() {
+    if (!isRustDoc()) return;
+
+    await wasm_bindgen(chrome.runtime.getURL('wasm/macro-railroad.wasm'));
+    injectCss();
+
+    for (let macro of document.querySelectorAll('pre.macro')) {
+        let parentNode = macro.parentNode;
+        if (!parentNode) continue;
+
+        const macroSrc = macro.innerText;
+        // The div that the `pre.macro` get's moved into, together with the new diagram nodes
+        let newNode = document.createElement('div');
+        newNode.setAttribute('style', 'width: 100%;');
+        // The container which holds the inline-svg on the page
+        let svgContainer = document.createElement('div');
+        svgContainer.setAttribute('class', 'railroad_container');
+        svgContainer.appendChild(document.createElement('svg'));
+        // Append svg container ahead macro element to prevent noisy overflow.
+        newNode.appendChild(svgContainer);
+        newNode.appendChild(macro);
+
+        let modalContainer = createModal();
+        newNode.appendChild(modalContainer);
+
+        const diagramOptions = new wasm_bindgen.DiagramOptions();
+        let iconsContainer = createIcons(macroSrc, diagramOptions);
+        svgContainer.appendChild(iconsContainer);
+
+        parentNode.appendChild(newNode);
+        updateDiagram(macroSrc, diagramOptions);
+    }
+}
+
+// Returns `true` if the document's generator is "rustdoc"
+function isRustDoc() {
+    let gen = document.querySelector('head > meta[name="generator"]');
+    return gen && gen.getAttribute('content') === 'rustdoc';
+}
+
+// Injects the relevant CSS into the document's <head>
+function injectCss() {
+    let head = document.head;
+
+    let rrCss = document.createElement('style');
+    rrCss.setAttribute('type', 'text/css');
+    rrCss.textContent = wasm_bindgen.getRailroadDefaultCss();
+    head.appendChild(rrCss);
+
+    let mrrCss = document.createElement('style');
+    mrrCss.setAttribute('type', 'text/css');
+    mrrCss.textContent = wasm_bindgen.getRailroadDigramCss();
+    head.appendChild(mrrCss);
+}
+
+// The modal to go fullscreen
+function createModal() {
+    let modalContent = document.createElement('div');
+    modalContent.appendChild(document.createElement('svg'));
+    modalContent.setAttribute('class', 'railroad_modal_content');
+
+    let modalContainer = document.createElement('div');
+    modalContainer.appendChild(modalContent);
+    modalContainer.setAttribute('class', 'railroad_modal');
+    modalContainer.onclick = function () {
+        modalContainer.classList.remove('railroad_active');
+    };
+
+    return modalContainer;
+}
+
+// The icons in the lower-right corner, including the options-dialog
+function createIcons(macroSrc, diagramOptions) {
+    // The icons in the bottom-right corner
+    let iconsContainer = document.createElement('div');
+    iconsContainer.setAttribute('class', 'railroad_icons');
+
+    // The options-thingy and the options
+    let optionsContainer = document.createElement('div');
+    optionsContainer.setAttribute('style', 'position: relative; display: inline');
+
+    // The container that holds the options-list
+    let dropdownContainer = document.createElement('div');
+    dropdownContainer.setAttribute('style', 'position: absolute');
+    dropdownContainer.setAttribute('class', 'railroad_dropdown_content');
+
+    let optionsList = document.createElement('ul');
+    optionsList.setAttribute('style', 'list-style-type: none; padding: 0px; margin: 0px');
+
+    const createOption = function (key, label) {
+        let listItem = document.createElement('li');
+        let inputItem = document.createElement('input');
+        inputItem.setAttribute('type', 'checkbox');
+        // Generate random id for inputItem
+        let randomId = Array.from(Array(8), () => Math.floor(Math.random() * 36).toString(36)).join('');
+        let inputItemId = `railroad_${randomId}`;
+        inputItem.setAttribute('id', inputItemId);
+        inputItem.setAttribute('checked', diagramOptions[key]);
+        inputItem.onchange = () => {
+            diagramOptions[key] = inputItem.checked;
+            updateDiagram(macroSrc, diagramOptions);
+        };
+
+        listItem.appendChild(inputItem);
+
+        let labelItem = document.createElement('label');
+        labelItem.setAttribute('style', 'padding-left: 8px');
+        labelItem.setAttribute('for', inputItemId);
+        labelItem.textContent = label;
+        listItem.appendChild(labelItem);
+
+        return listItem;
+    };
+    optionsList.appendChild(createOption('hideInternal', 'Hide macro-internal rules'));
+    optionsList.appendChild(createOption('keepGroups', 'Keep groups bound'));
+    optionsList.appendChild(createOption('foldCommonTails', 'Fold common sections'));
+    optionsList.appendChild(createOption('showLegend', 'Generate legend'));
+
+    dropdownContainer.appendChild(optionsList);
+
+    let optionsIcon = document.createElement('img');
+    optionsIcon.setAttribute('class', 'railroad_icon');
+    optionsIcon.setAttribute('src', chrome.runtime.getURL('assets/options.svg'));
+    optionsIcon.onclick = function () {
+        dropdownContainer.classList.toggle('railroad_dropdown_show');
+    };
+    optionsContainer.appendChild(optionsIcon);
+    optionsContainer.appendChild(dropdownContainer);
+    iconsContainer.appendChild(optionsContainer);
+
+    // The fullscreen-toggle
+    let fullscreenIcon = document.createElement('img');
+    fullscreenIcon.setAttribute('class', 'railroad_icon');
+    fullscreenIcon.setAttribute('src', chrome.runtime.getURL('assets/fullscreen.svg'));
+    fullscreenIcon.onclick = function () {
+        let modalContainer = document.querySelector('.railroad_modal');
+        modalContainer.classList.add('railroad_active');
+    };
+    iconsContainer.appendChild(fullscreenIcon);
+    return iconsContainer;
+}
+
+// The update function, called when options are set and to create the initial diagram
+function updateDiagram(macroSrc, diagramOptions) {
+    let diagram = wasm_bindgen.toDiagram(macroSrc, diagramOptions);
+    let svgContainer = document.querySelector('div.railroad_container');
+    let modalContent = document.querySelector('div.railroad_modal_content');
+    svgContainer.replaceChild(htmlToElement(diagram.svg), svgContainer.firstChild);
+    svgContainer.setAttribute('style', `max-width: ${diagram.width}px`);
+    modalContent.replaceChild(htmlToElement(diagram.svg), modalContent.firstChild);
+}
+
+// Convert plain HTML text to element
+function htmlToElement(html) {
+    let div = document.createElement('div');
+    div.innerHTML = html;
+    return div.firstChild
+}
\ No newline at end of file
diff --git a/extension/wasm/macro-railroad.wasm b/extension/wasm/macro-railroad.wasm
new file mode 100644
index 00000000..9a0293f3
Binary files /dev/null and b/extension/wasm/macro-railroad.wasm differ
diff --git a/macro-railroad/Cargo.toml b/macro-railroad/Cargo.toml
new file mode 100644
index 00000000..38e82c55
--- /dev/null
+++ b/macro-railroad/Cargo.toml
@@ -0,0 +1,31 @@
+[package]
+name = "macro_railroad_ext"
+version = "0.1.0"
+authors = ["Lukas Lueg <lukas.lueg@gmail.com>", "lyshuhow@gmail.com"]
+build = "build.rs"
+edition = "2021"
+publish = false
+
+[lib]
+crate-type = ["cdylib"]
+
+[dependencies]
+wasm-bindgen = "0.2"
+railroad = "0.1"
+macro_railroad = "0.1"
+
+# The `console_error_panic_hook` crate provides better debugging of panics by
+# logging them with `console.error`. This is great for development, but requires
+# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for
+# code size when deploying. However, we don't actually care all that much about 
+# code-size.
+console_error_panic_hook = "0.1"
+
+
+[build-dependencies]
+built = { version = "0.5", features = ["chrono"], default_features = false }
+
+[profile.release]
+# Tell `rustc` to optimize for small code size.
+opt-level = "s"
+lto = true
diff --git a/macro-railroad/build.rs b/macro-railroad/build.rs
new file mode 100644
index 00000000..3e6d9c76
--- /dev/null
+++ b/macro-railroad/build.rs
@@ -0,0 +1,14 @@
+extern crate built;
+
+use std::env;
+use std::path;
+
+fn main() {
+    let mut opts = built::Options::default();
+    opts.set_dependencies(true);
+
+    let src = env::var("CARGO_MANIFEST_DIR").unwrap();
+    let dst = path::Path::new(&env::var("OUT_DIR").unwrap()).join("built.rs");
+    built::write_built_file_with_opts(&opts, src.as_ref(), &dst)
+        .expect("Failed to acquire build-time information.");
+}
diff --git a/macro-railroad/src/lib.rs b/macro-railroad/src/lib.rs
new file mode 100644
index 00000000..72bc2423
--- /dev/null
+++ b/macro-railroad/src/lib.rs
@@ -0,0 +1,118 @@
+use railroad::RailroadNode;
+use wasm_bindgen::prelude::*;
+
+#[wasm_bindgen]
+extern "C" {
+    #[wasm_bindgen(js_namespace = console)]
+    fn log(s: &str);
+}
+
+#[allow(dead_code)]
+mod built_info {
+    include!(concat!(env!("OUT_DIR"), "/built.rs"));
+}
+
+#[wasm_bindgen(start)]
+pub fn start() {
+    // Set panic hook to get better error messages if our code ever panics.
+    //
+    // For more details see
+    // https://github.com/rustwasm/console_error_panic_hook#readme
+    std::panic::set_hook(Box::new(console_error_panic_hook::hook));
+
+    log(&format!(
+        "macro_railroad built {} on {} using {}",
+        built_info::BUILT_TIME_UTC,
+        built_info::RUSTC_VERSION,
+        built_info::DEPENDENCIES_STR
+    ));
+}
+
+#[wasm_bindgen(js_name = getRailroadDefaultCss)]
+pub fn get_railroad_default_css() -> JsValue {
+    railroad::DEFAULT_CSS.into()
+}
+
+#[wasm_bindgen(js_name = getRailroadDigramCss)]
+pub fn get_railroad_digram_css() -> JsValue {
+    macro_railroad::diagram::CSS.into()
+}
+
+#[derive(Debug)]
+#[wasm_bindgen]
+pub struct DiagramOptions {
+    #[wasm_bindgen(js_name = hideInternal)]
+    pub hide_internal: bool,
+    #[wasm_bindgen(js_name = keepGroups)]
+    pub keep_groups: bool,
+    #[wasm_bindgen(js_name = foldCommonTails)]
+    pub foldcommontails: bool,
+    #[wasm_bindgen(js_name = showLegend)]
+    pub show_legend: bool,
+}
+
+#[wasm_bindgen]
+pub struct Diagram {
+    #[wasm_bindgen(readonly)]
+    pub width: i64,
+    // We can't make svg pub since wasm-bindgen require each public field to be Copy.
+    // See rustwasm/wasm-bindgen#1985 and rustwasm/wasm-bindgen#2775.
+    svg: String,
+}
+
+#[wasm_bindgen]
+impl Diagram {
+    #[wasm_bindgen(getter)]
+    pub fn svg(&self) -> String {
+        self.svg.clone()
+    }
+}
+
+#[wasm_bindgen]
+impl DiagramOptions {
+    #[wasm_bindgen(constructor)]
+    pub fn new() -> Self {
+        DiagramOptions {
+            hide_internal: true,
+            keep_groups: true,
+            foldcommontails: true,
+            show_legend: true,
+        }
+    }
+}
+
+impl Default for DiagramOptions {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+/// Parse the given macro_rules!()-source, returns an SVG and it's preferred width
+#[wasm_bindgen(js_name = toDiagram)]
+pub fn to_diagram(src: &str, options: &DiagramOptions) -> Result<Diagram, JsValue> {
+    let macro_rules = macro_railroad::parser::parse(src)
+        .map_err(|e| format!("macro_railroad parse failed: {}", e))?;
+    let mut tree = macro_railroad::lowering::MacroRules::from(macro_rules);
+
+    if options.hide_internal {
+        tree.remove_internal();
+    }
+    if !options.keep_groups {
+        tree.ungroup();
+    }
+    if options.foldcommontails {
+        tree.foldcommontails();
+    }
+    tree.normalize();
+
+    let dia = macro_railroad::diagram::into_diagram(tree, options.show_legend);
+    let mut svg = dia.to_string();
+    if svg.ends_with('\n') {
+        svg.pop();
+    }
+
+    Ok(Diagram {
+        width: dia.width(),
+        svg,
+    })
+}
diff --git a/manifest.jsonnet b/manifest.jsonnet
index 5a936361..613dddac 100644
--- a/manifest.jsonnet
+++ b/manifest.jsonnet
@@ -20,6 +20,13 @@ local json = if browser == 'chrome' then
   .addWebAccessibleResources(
     resources=utils.js_files('script', ['lib', 'add-search-index']),
     matches=['*://docs.rs/*', '*://doc.rust-lang.org/*'],
+  ).addWebAccessibleResources(
+    resources=['wasm/macro-railroad.wasm', 'assets/fullscreen.svg', 'assets/options.svg'],
+    matches=[
+      '*://docs.rs/*',
+      '*://doc.rust-lang.org/*',
+      'file:///*',
+    ],
   ) {
     description: 'A handy browser extension to search Rust docs and crates, etc in the address bar instantly!',
     // The production extension public key to get the constant extension id during development.
@@ -29,6 +36,7 @@ else
   local manifest_v2 = import 'core/manifest.libsonnet';
   manifest_v2.new(name, keyword, description, version)
   .addWebAccessibleResources(utils.js_files('script', ['lib', 'add-search-index']))
+  .addWebAccessibleResources(['wasm/macro-railroad.wasm', 'assets/fullscreen.svg', 'assets/options.svg'])
   .addBackgroundScripts(['migration.js', 'settings.js', 'deminifier.js'])
   .addBackgroundScripts(utils.js_files('search', ['algorithm', 'book', 'crate', 'attribute', 'caniuse', 'lint']))
   .addBackgroundScripts(utils.js_files('search/docs', ['base', 'crate-doc', 'rustc']))
@@ -63,4 +71,12 @@ json.addIcons(icons())
   matches=['*://github.com/rust-lang/rust/blob/master/RELEASES.md*'],
   js=utils.js_files('script', ['lib', 'rust-lang-release']),
   css=['script/github.css'],
+).addContentScript(
+  matches=[
+    '*://docs.rs/*',
+    '*://doc.rust-lang.org/*',
+    'file:///*',
+  ],
+  js=utils.js_files('script', ['macro-railroad', 'macro-railroad-wasm']),
+  css=['script/macro-railroad.css'],
 )


Test patch: