LiveHTMLDocument manages a single HTML source document. Edits to the HTML are applied live in the browser, and the DOM node corresponding to the selection is highlighted.
LiveHTMLDocument relies on HTMLInstrumentation in order to map tags in the HTML source text to DOM nodes in the browser, so edits can be incrementally applied.
function LiveHTMLDocument(protocol, urlResolver, doc, editor) {
LiveDocument.apply(this, arguments);
this._instrumentationEnabled = false;
this._relatedDocuments = {
stylesheets: {},
scripts: {}
};
this._onChange = this._onChange.bind(this);
this.doc.on("change", this._onChange);
this._onRelated = this._onRelated.bind(this);
this.protocol.on("DocumentRelated", this._onRelated);
this._onStylesheetAdded = this._onStylesheetAdded.bind(this);
this.protocol.on("StylesheetAdded", this._onStylesheetAdded);
this._onStylesheetRemoved = this._onStylesheetRemoved.bind(this);
this.protocol.on("StylesheetRemoved", this._onStylesheetRemoved);
this._onScriptAdded = this._onScriptAdded.bind(this);
this.protocol.on("ScriptAdded", this._onScriptAdded);
this._onScriptRemoved = this._onScriptRemoved.bind(this);
this.protocol.on("ScriptRemoved", this._onScriptRemoved);
}
LiveHTMLDocument.prototype = Object.create(LiveDocument.prototype);
LiveHTMLDocument.prototype.constructor = LiveHTMLDocument;
LiveHTMLDocument.prototype.parentClass = LiveDocument.prototype;
EventDispatcher.makeEventDispatcher(LiveHTMLDocument.prototype);
LiveHTMLDocument.prototype._compareWithBrowser = function (change) {
// TODO: Not implemented.
};
LiveHTMLDocument.prototype._onChange = function (event, doc, 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 = "LiveHTMLDocument applyDOMEdits",
isNestedTimer = PerfUtils.isActive(perfTimerName);
if (!isNestedTimer) {
PerfUtils.markStart(perfTimerName);
}
var self = this,
result = HTMLInstrumentation.getUnappliedEditList(this.editor, change),
applyEditsPromise;
if (result.edits) {
applyEditsPromise = this.protocol.evaluate("_LD.applyDOMEdits(" + JSON.stringify(result.edits) + ")");
applyEditsPromise.always(function () {
if (!isNestedTimer) {
PerfUtils.addMeasurement(perfTimerName);
}
});
}
this.errors = result.errors || [];
this._updateErrorDisplay();
// 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);
});
}
};
LiveHTMLDocument.prototype._onRelated = function (event, msg) {
this._relatedDocuments = msg.related;
return;
};
LiveHTMLDocument.prototype._onScriptAdded = function (event, msg) {
this._relatedDocuments.scripts[msg.src] = true;
return;
};
LiveHTMLDocument.prototype._onScriptRemoved = function (event, msg) {
delete (this._relatedDocuments.scripts[msg.src]);
return;
};
LiveHTMLDocument.prototype._onStylesheetAdded = function (event, msg) {
this._relatedDocuments.stylesheets[msg.href] = true;
return;
};
LiveHTMLDocument.prototype._onStylesheetRemoved = function (event, msg) {
delete (this._relatedDocuments.stylesheets[msg.href]);
return;
};
LiveHTMLDocument.prototype.close = function () {
this.doc.off("change", this._onChange);
this.parentClass.close.call(this);
};
Returns the instrumented version of the file.
LiveHTMLDocument.prototype.getResponseData = function (enabled) {
var body;
if (this._instrumentationEnabled) {
body = HTMLInstrumentation.generateInstrumentedHTML(this.editor, this.protocol.getRemoteScript());
}
return {
body: body || this.doc.getText()
};
};
LiveHTMLDocument.prototype.isLiveEditingEnabled = function () {
return this._instrumentationEnabled;
};
For the given path, check if the document is related to the live HTML document. Related means that is an external Javascript or CSS file that is included as part of the DOM.
LiveHTMLDocument.prototype.isRelated = function (fullPath) {
return (this._relatedDocuments.scripts[this.urlResolver(fullPath)] || this._relatedDocuments.stylesheets[this.urlResolver(fullPath)]);
};
LiveHTMLDocument.prototype.getRelated = function () {
return this._relatedDocuments;
};
// Export the class
module.exports = LiveHTMLDocument;
});
LiveHTMLDocument.prototype.setInstrumentationEnabled = function (enabled) {
if (!this.editor) {
// TODO: error
return;
}
if (enabled && !this._instrumentationEnabled) {
// TODO: not clear why we do this here instead of waiting for the next time we want to
// generate the instrumented HTML. This won't work if the dom offsets are out of date.
HTMLInstrumentation.scanDocument(this.doc);
HTMLInstrumentation._markText(this.editor);
}
this._instrumentationEnabled = enabled;
};
LiveHTMLDocument.prototype.updateHighlight = function () {
if (!this.editor || !this.isHighlightEnabled()) {
return;
}
var editor = this.editor,
ids = [];
_.each(this.editor.getSelections(), function (sel) {
var tagID = HTMLInstrumentation._getTagIDAtDocumentPos(
editor,
sel.reversed ? sel.end : sel.start
);
if (tagID !== -1) {
ids.push(tagID);
}
});
if (!ids.length) {
this.hideHighlight();
} else {
this.highlightDomElement(ids);
}
};