Modules (188)

HTMLDocument

Description

HTMLDocument manages a single HTML source document

EDITING

Editing the document will cause the corresponding node to be updated by calling applyChanges on the DOMAgent. This will only work for altering text nodes and will break when attempting to change DOM elements or inserting or deleting nodes.

HIGHLIGHTING

HTMLDocument supports highlighting nodes from the HighlightAgent and highlighting the DOMNode corresponding to the cursor position in the editor.

Dependencies

Classes

Constructor

HTMLDocument

doc Document
The source document from Brackets
editor Editor
The editor for this document
    var HTMLDocument = function HTMLDocument(doc, editor) {
        this.doc = doc;
        if (this.doc) {
            this.doc.addRef();
        }

        this.editor = editor;
        this._instrumentationEnabled = false;

        this._onActiveEditorChange = this._onActiveEditorChange.bind(this);
        EditorManager.on("activeEditorChange", this._onActiveEditorChange);

        // Attach now
        this.attachToEditor(editor);
    };
    EventDispatcher.makeEventDispatcher(HTMLDocument.prototype);

Methods

Private

_compareWithBrowser

change Object
CodeMirror editor change data
    HTMLDocument.prototype._compareWithBrowser = function (change) {
        var self = this;

        RemoteAgent.call("getSimpleDOM").done(function (res) {
            var browserSimpleDOM = JSON.parse(res.result.value),
                edits,
                node,
                result;

            try {
                result = HTMLInstrumentation._getBrowserDiff(self.editor, browserSimpleDOM);
            } catch (err) {
                console.error("Error comparing in-browser DOM to in-editor DOM");
                console.error(err.stack);
                return;
            }

            edits = result.diff.filter(function (delta) {
                // ignore textDelete in html root element
                node = result.browser.nodeMap[delta.parentID];

                if (node && node.tag === "html" && delta.type === "textDelete") {
                    return false;
                }

                return true;
            });

            if (edits.length > 0) {
                console.warn("Browser DOM does not match after change: " + JSON.stringify(change));

                edits.forEach(function (delta) {
                    console.log(delta);
                });
            }
        });
    };
Private

_onActiveEditorChange

Triggered when the active editor changes

event $.Event
Event
newActive non-nullable Editor
The new active editor
oldActive non-nullable Editor
The old active editor
    HTMLDocument.prototype._onActiveEditorChange = function (event, newActive, oldActive) {
        this.detachFromEditor();

        if (newActive && newActive.document === this.doc) {
            this.attachToEditor(newActive);
        }
    };
Private

_onChange

Triggered on change by the editor

event $.Event
Event
editor non-nullable Editor
The editor for this document
change Object
CodeMirror editor change data
    HTMLDocument.prototype._onChange = function (event, editor, change) {
        // Make sure LiveHTML is turned on
        if (!this._instrumentationEnabled) {
            return;
        }

        // Apply DOM edits is async, so previous PerfUtils timer may still be
        // running. PerfUtils does not support running multiple timers with same
        // name, so do not start another timer in this case.
        var perfTimerName   = "HTMLDocument applyDOMEdits",
            isNestedTimer   = PerfUtils.isActive(perfTimerName);
        if (!isNestedTimer) {
            PerfUtils.markStart(perfTimerName);
        }

        // Only handles attribute changes currently.
        // TODO: text changes should be easy to add
        // TODO: if new tags are added, need to instrument them
        var self                = this,
            result              = HTMLInstrumentation.getUnappliedEditList(editor, change),
            applyEditsPromise;

        if (result.edits) {
            applyEditsPromise = RemoteAgent.call("applyDOMEdits", result.edits);

            applyEditsPromise.always(function () {
                if (!isNestedTimer) {
                    PerfUtils.addMeasurement(perfTimerName);
                }
            });
        }

        this.errors = result.errors || [];
        this.trigger("statusChanged", this);

        // Debug-only: compare in-memory vs. in-browser DOM
        // edit this file or set a conditional breakpoint at the top of this function:
        //     "this._debug = true, false"
        if (this._debug) {
            console.log("Edits applied to browser were:");
            console.log(JSON.stringify(result.edits, null, 2));
            applyEditsPromise.done(function () {
                self._compareWithBrowser(change);
            });
        }

//        var marker = HTMLInstrumentation._getMarkerAtDocumentPos(
//            this.editor,
//            editor.getCursorPos()
//        );
//
//        if (marker && marker.tagID) {
//            var range   = marker.find(),
//                text    = marker.doc.getRange(range.from, range.to);
//
//            // HACK maintain ID
//            text = text.replace(">", " data-brackets-id='" + marker.tagID + "'>");
//
//            // FIXME incorrectly replaces body elements with content only, missing body element
//            RemoteAgent.remoteElement(marker.tagID).replaceWith(text);
//        }

        // if (!this.editor) {
        //     return;
        // }
        // var codeMirror = this.editor._codeMirror;
        // while (change) {
        //     var from = codeMirror.indexFromPos(change.from);
        //     var to = codeMirror.indexFromPos(change.to);
        //     var text = change.text.join("\n");
        //     DOMAgent.applyChange(from, to, text);
        //     change = change.next;
        // }
    };
Private

_onCursorActivity

Triggered on cursor activity by the editor

event $.Event
Event
editor non-nullable Editor
The editor for this document
    HTMLDocument.prototype._onCursorActivity = function (event, editor) {
        if (this.editor !== editor) {
            return;
        }
        this.updateHighlight();
    };
Private

_onDestroy

Triggered when the editor is being destroyed

event $.Event
Event
editor non-nullable Editor
The editor being destroyed
    HTMLDocument.prototype._onDestroy = function (event, editor) {
        if (this.editor === editor) {
            this.detachFromEditor();
        }
    };
Private

_onHighlight

Triggered by the HighlightAgent to highlight a node in the editor

event $.Event
Event
node DOMElement
Element to highlight
    HTMLDocument.prototype._onHighlight = function (event, node) {
        this._removeHighlight();
        if (!node || !node.location || !this.editor) {
            return;
        }

        var codeMirror = this.editor._codeMirror;
        var to, from = codeMirror.posFromIndex(node.location);
        if (node.closeLocation) {
            to = node.closeLocation + node.closeLength;
        } else {
            to = node.location + node.length;
        }

        to = codeMirror.posFromIndex(to);
        this._highlight = codeMirror.markText(from, to, { className: "highlight" });
    };
Private

_removeHighlight

Remove all highlighting

    HTMLDocument.prototype._removeHighlight = function () {
        if (this._highlight) {
            this._highlight.clear();
            this._highlight = null;
        }
    };

    // Export the class
    module.exports = HTMLDocument;
});

attachToEditor

Attach new editor

editor non-nullable Editor
The editor for this document
    HTMLDocument.prototype.attachToEditor = function (editor) {
        var self = this;
        this.editor = editor;

        // Performance optimization to use closures instead of Function.bind()
        // to improve responsiveness during cursor movement and keyboard events
        this.editor.on("cursorActivity.HTMLDocument", function (event, editor) {
            self._onCursorActivity(event, editor);
        });

        this.editor.on("change.HTMLDocument", function (event, editor, change) {
            self._onChange(event, editor, change);
        });

        this.editor.on("beforeDestroy.HTMLDocument", function (event, editor) {
            self._onDestroy(event, editor);
        });

        // Experimental code
        if (LiveDevelopment.config.experimental) {
            HighlightAgent.on("highlight.HTMLDocument", function (event, node) {
                self._onHighlight(event, node);
            });
        }

        if (this._instrumentationEnabled) {
            // Resync instrumentation with editor
            HTMLInstrumentation._markText(this.editor);
        }
    };

close

Close the document

    HTMLDocument.prototype.close = function close() {
        if (this.editor) {
            this.editor.off(".HTMLDocument");
        }

        if (this.doc) {
            this.doc.releaseRef();
        }

        EditorManager.off("activeEditorChange", this._onActiveEditorChange);

        // Experimental code
        if (LiveDevelopment.config.experimental) {
            // Force highlight teardown
            this._onHighlight();
        }
    };

detachFromEditor

Detach current editor

    HTMLDocument.prototype.detachFromEditor = function () {
        if (this.editor) {
            HighlightAgent.hide();
            this.editor.off(".HTMLDocument");
            this._removeHighlight();
            this.editor = null;
        }
    };

getResponseData

Returns a JSON object with HTTP response overrides

enabled boolean
(Unused)
Returns: {body: string}
    HTMLDocument.prototype.getResponseData = function getResponseData(enabled) {
        var body;
        if (this._instrumentationEnabled) {
            if (this.editor) {
                body = HTMLInstrumentation.generateInstrumentedHTML(this.editor);
            } else {
                this.doc._ensureMasterEditor();
                body = HTMLInstrumentation.generateInstrumentedHTML(this.doc._masterEditor);
            }
        }

        return {
            body: body || this.doc.getText()
        };
    };

isLiveEditingEnabled

Returns true if document edits appear live in the connected browser

Returns: boolean
    HTMLDocument.prototype.isLiveEditingEnabled = function () {
        return this._instrumentationEnabled;
    };

setInstrumentationEnabled

Enable or disable instrumented HTML

enabled boolean
Whether to enable or disable
    HTMLDocument.prototype.setInstrumentationEnabled = function setInstrumentationEnabled(enabled) {
        if (enabled && !this._instrumentationEnabled && this.editor) {
            HTMLInstrumentation.scanDocument(this.doc);
            HTMLInstrumentation._markText(this.editor);
        }

        this._instrumentationEnabled = enabled;
    };

updateHighlight

Update the highlight

    HTMLDocument.prototype.updateHighlight = function () {
        var editor = this.editor,
            ids = [];

        if (Inspector.config.highlight) {
            if (editor) {
                _.each(editor.getSelections(), function (sel) {
                    var tagID = HTMLInstrumentation._getTagIDAtDocumentPos(
                        editor,
                        sel.reversed ? sel.end : sel.start
                    );
                    if (tagID !== -1) {
                        ids.push(tagID);
                    }
                });
            }

            if (!ids.length) {
                HighlightAgent.hide();
            } else {
                HighlightAgent.domElement(ids);
            }
        }
    };