diff --git a/src/LiveDevelopment/Agents/CSSAgent.js b/src/LiveDevelopment/Agents/CSSAgent.js index 768ce36827e..94eacf7fc38 100644 --- a/src/LiveDevelopment/Agents/CSSAgent.js +++ b/src/LiveDevelopment/Agents/CSSAgent.js @@ -28,8 +28,10 @@ /** * CSSAgent keeps track of loaded style sheets and allows reloading them * from a {Document}. + * + * CSSAgent dispatches styleSheetAdded and styleSheetRemoved events, passing + * the URL for the added/removed style sheet. */ - define(function CSSAgent(require, exports, module) { "use strict"; @@ -37,8 +39,14 @@ define(function CSSAgent(require, exports, module) { var Inspector = require("LiveDevelopment/Inspector/Inspector"); - var _load; // {$.Deferred} load promise - var _urlToStyle; // {url -> loaded} style definition + /** @type {Object.} */ + var _urlToStyle = {}; + + /** @type {Object.} */ + var _styleSheetIdToUrl; + + /** @type {boolean} */ + var _getAllStyleSheetsNotFound = false; /** * Create a canonicalized version of the given URL, stripping off query strings and hashes. @@ -49,34 +57,31 @@ define(function CSSAgent(require, exports, module) { return PathUtils.parseUrl(url).hrefNoSearch; } - // WebInspector Event: Page.loadEventFired - function _onLoadEventFired(event, res) { - // res = {timestamp} + /** + * @private + * WebInspector Event: Page.frameNavigated + * @param {jQuery.Event} event + * @param {frame: Frame} res + */ + function _onFrameNavigated(event, res) { + // Clear maps when navigating to a new page _urlToStyle = {}; - Inspector.CSS.enable().done(function () { - Inspector.CSS.getAllStyleSheets(function onGetAllStyleSheets(res) { - var i, header; - for (i in res.headers) { - header = res.headers[i]; - _urlToStyle[_canonicalize(header.sourceURL)] = header; - } - _load.resolve(); - }); - }); + _styleSheetIdToUrl = {}; } - /** Get a style sheet for a url + /** + * Get a style sheet for a url * @param {string} url + * @return {CSS.CSSStyleSheetHeader} */ function styleForURL(url) { - if (_urlToStyle) { - return _urlToStyle[_canonicalize(url)]; - } - - return null; + return _urlToStyle[_canonicalize(url)]; } - /** Get a list of all loaded stylesheet files by URL */ + /** + * @deprecated Use styleSheetAdded and styleSheetRemoved events + * Get a list of all loaded stylesheet files by URL + */ function getStylesheetURLs() { var urls = [], url; for (url in _urlToStyle) { @@ -87,37 +92,112 @@ define(function CSSAgent(require, exports, module) { return urls; } - /** Reload a CSS style sheet from a document + /** + * Reload a CSS style sheet from a document * @param {Document} document + * @return {jQuery.Promise} */ function reloadCSSForDocument(doc) { var style = styleForURL(doc.url); console.assert(style, "Style Sheet for document not loaded: " + doc.url); - Inspector.CSS.setStyleSheetText(style.styleSheetId, doc.getText()); + return Inspector.CSS.setStyleSheetText(style.styleSheetId, doc.getText()); } - /** Empties a CSS style sheet given a document that has been deleted + /** + * Empties a CSS style sheet given a document that has been deleted * @param {Document} document + * @return {jQuery.Promise} */ function clearCSSForDocument(doc) { var style = styleForURL(doc.url); console.assert(style, "Style Sheet for document not loaded: " + doc.url); - Inspector.CSS.setStyleSheetText(style.styleSheetId, ""); + return Inspector.CSS.setStyleSheetText(style.styleSheetId, ""); + } + + /** + * @private + * @param {jQuery.Event} event + * @param {header: CSSStyleSheetHeader} + */ + function _styleSheetAdded(event, res) { + var url = _canonicalize(res.header.sourceURL), + existing = _urlToStyle[url]; + + // detect duplicates + if (existing && existing.styleSheetId === res.header.styleSheetId) { + return; + } + + _urlToStyle[url] = res.header; + _styleSheetIdToUrl[res.header.styleSheetId] = url; + + $(exports).triggerHandler("styleSheetAdded", [url, res.header]); + } + + /** + * @private + * @param {jQuery.Event} event + * @param {styleSheetId: StyleSheetId} + */ + function _styleSheetRemoved(event, res) { + var url = _styleSheetIdToUrl[res.styleSheetId], + header = url && _urlToStyle[url]; + + if (url) { + delete _urlToStyle[url]; + } + + delete _styleSheetIdToUrl[res.styleSheetId]; + + $(exports).triggerHandler("styleSheetRemoved", [url, header]); + } + + /** + * @private + * Attempt to use deleted API CSS.getAllStyleSheets + * @param {jQuery.Event} event + * @param {frameId: Network.FrameId} + */ + function _onFrameStoppedLoading(event, res) { + // Manually fire getAllStyleSheets since it will be removed from + // Inspector.json in a future update + Inspector.send("CSS", "getAllStyleSheets").done(function (res) { + res.headers.forEach(function (header) { + // _styleSheetAdded will ignore duplicates + _styleSheetAdded(null, { header: header }); + }); + }).fail(function (err) { + // Disable getAllStyleSheets if the first call fails + _getAllStyleSheetsNotFound = (err.code === -32601); + $(Inspector.Page).off("frameStoppedLoading.CSSAgent", _onFrameStoppedLoading); + }); + } + + /** Enable the domain */ + function enable() { + return Inspector.CSS.enable(); } /** Initialize the agent */ function load() { - _load = new $.Deferred(); - $(Inspector.Page).on("loadEventFired.CSSAgent", _onLoadEventFired); - return _load.promise(); + $(Inspector.Page).on("frameNavigated.CSSAgent", _onFrameNavigated); + $(Inspector.CSS).on("styleSheetAdded.CSSAgent", _styleSheetAdded); + $(Inspector.CSS).on("styleSheetRemoved.CSSAgent", _styleSheetRemoved); + + // getAllStyleSheets was deleted beginning with Chrome 34 + if (!_getAllStyleSheetsNotFound) { + $(Inspector.Page).on("frameStoppedLoading.CSSAgent", _onFrameStoppedLoading); + } } /** Clean up */ function unload() { $(Inspector.Page).off(".CSSAgent"); + $(Inspector.CSS).off(".CSSAgent"); } // Export public functions + exports.enable = enable; exports.styleForURL = styleForURL; exports.getStylesheetURLs = getStylesheetURLs; exports.reloadCSSForDocument = reloadCSSForDocument; diff --git a/src/LiveDevelopment/Documents/CSSDocument.js b/src/LiveDevelopment/Documents/CSSDocument.js index bd76cd67690..032e6e631c3 100644 --- a/src/LiveDevelopment/Documents/CSSDocument.js +++ b/src/LiveDevelopment/Documents/CSSDocument.js @@ -48,7 +48,8 @@ define(function CSSDocumentModule(require, exports, module) { "use strict"; - var CSSAgent = require("LiveDevelopment/Agents/CSSAgent"), + var _ = require("thirdparty/lodash"), + CSSAgent = require("LiveDevelopment/Agents/CSSAgent"), CSSUtils = require("language/CSSUtils"), EditorManager = require("editor/EditorManager"), HighlightAgent = require("LiveDevelopment/Agents/HighlightAgent"), @@ -69,6 +70,7 @@ define(function CSSDocumentModule(require, exports, module) { this.doc.addRef(); this.onChange = this.onChange.bind(this); this.onDeleted = this.onDeleted.bind(this); + $(this.doc).on("change.CSSDocument", this.onChange); $(this.doc).on("deleted.CSSDocument", this.onDeleted); @@ -81,33 +83,37 @@ define(function CSSDocumentModule(require, exports, module) { } }; - /** Get the browser version of the StyleSheet object */ - CSSDocument.prototype.getStyleSheetFromBrowser = function getStyleSheetFromBrowser() { - var deferred = new $.Deferred(); - - // WebInspector Command: CSS.getStyleSheet - Inspector.CSS.getStyleSheet(this.styleSheet.styleSheetId, function callback(res) { - // res = {styleSheet} - if (res.styleSheet) { - deferred.resolve(res.styleSheet); - } else { - deferred.reject(); - } - }); + /** + * @private + * Get the CSSStyleSheetHeader for this document + */ + CSSDocument.prototype._getStyleSheetHeader = function () { + return CSSAgent.styleForURL(this.doc.url); + }; - return deferred.promise(); + /** + * @deprecated + * CSSStyleSheetBody was removed in protocol 1.1. This method is unused in Brackets 36. + * Get the browser version of the StyleSheet object + * @return {jQuery.promise} + */ + CSSDocument.prototype.getStyleSheetFromBrowser = function getStyleSheetFromBrowser() { + return new $.Deferred().reject().promise(); }; - /** Get the browser version of the source */ + /** + * Get the browser version of the source + * @return {jQuery.promise} Promise resolved with the text content of this CSS document + */ CSSDocument.prototype.getSourceFromBrowser = function getSourceFromBrowser() { - var deferred = new $.Deferred(); - - this.getStyleSheetFromBrowser().done(function onDone(styleSheet) { - deferred.resolve(styleSheet.text); - }).fail(function onFail() { - deferred.reject(); - }); - + var deferred = new $.Deferred(), + styleSheetId = this._getStyleSheetHeader().styleSheetId, + inspectorPromise = Inspector.CSS.getStyleSheetText(styleSheetId); + + inspectorPromise.then(function (res) { + deferred.resolve(res.text); + }, deferred.reject); + return deferred.promise(); }; @@ -118,17 +124,16 @@ define(function CSSDocumentModule(require, exports, module) { this.doc.releaseRef(); this.detachFromEditor(); }; - + /** - * Force the browser to update if the file is dirty + * @private + * Update the style sheet text content and redraw highlights */ CSSDocument.prototype._updateBrowser = function () { - // get the style sheet - this.styleSheet = CSSAgent.styleForURL(this.doc.url); + var reloadPromise = CSSAgent.reloadCSSForDocument(this.doc); - // If the CSS document is dirty, push the changes into the browser now - if (this.doc.isDirty) { - CSSAgent.reloadCSSForDocument(this.doc); + if (Inspector.config.highlight) { + reloadPromise.done(HighlightAgent.redraw); } }; @@ -200,11 +205,7 @@ define(function CSSDocumentModule(require, exports, module) { /** Triggered whenever the Document is edited */ CSSDocument.prototype.onChange = function onChange(event, editor, change) { - // brute force: update the CSS - CSSAgent.reloadCSSForDocument(this.doc); - if (Inspector.config.highlight) { - HighlightAgent.redraw(); - } + this._updateBrowser(); }; /** Triggered if the Document's file is deleted */ @@ -241,11 +242,13 @@ define(function CSSDocumentModule(require, exports, module) { // WebInspector Command: CSS.getMatchedStylesForNode Inspector.CSS.getMatchedStylesForNode(node.nodeId, function onGetMatchesStyles(res) { // res = {matchedCSSRules, pseudoElements, inherited} - var codeMirror = this.editor._codeMirror; + var codeMirror = this.editor._codeMirror, + styleSheetId = this._getStyleSheetHeader().styleSheetId; + var i, rule, from, to; for (i in res.matchedCSSRules) { rule = res.matchedCSSRules[i]; - if (rule.ruleId && rule.ruleId.styleSheetId === this.styleSheet.styleSheetId) { + if (rule.ruleId && rule.ruleId.styleSheetId === styleSheetId) { from = codeMirror.posFromIndex(rule.selectorRange.start); to = codeMirror.posFromIndex(rule.style.range.end); this._highlight.push(codeMirror.markText(from, to, { className: "highlight" })); diff --git a/src/LiveDevelopment/Inspector/Inspector.js b/src/LiveDevelopment/Inspector/Inspector.js index c9b263f5a59..6baac318dcb 100644 --- a/src/LiveDevelopment/Inspector/Inspector.js +++ b/src/LiveDevelopment/Inspector/Inspector.js @@ -134,8 +134,12 @@ define(function Inspector(require, exports, module) { } else { var deferred = new $.Deferred(); promise = deferred.promise(); - callback = function (result) { - deferred.resolve(result); + callback = function (result, error) { + if (error) { + deferred.reject(error); + } else { + deferred.resolve(result); + } }; } @@ -144,16 +148,30 @@ define(function Inspector(require, exports, module) { // verify the parameters against the method signature // this also constructs the params object of type {name -> value} - for (i in signature) { - if (_verifySignature(args[i], signature[i])) { - params[signature[i].name] = args[i]; + if (signature) { + for (i in signature) { + if (_verifySignature(args[i], signature[i])) { + params[signature[i].name] = args[i]; + } } } + _socket.send(JSON.stringify({ method: method, id: id, params: params })); return promise; } + /** + * Manually send a message to the remote debugger + * All passed arguments after the command are passed on as parameters. + * If the last argument is a function, it is used as the callback function. + * @param {string} domain + * @param {string} command + */ + function send(domain, command, varargs) { + return _send(domain + "." + command, null, varargs); + } + /** WebSocket did close */ function _onDisconnect() { _socket = undefined; @@ -186,20 +204,27 @@ define(function Inspector(require, exports, module) { * @param {object} message */ function _onMessage(message) { - var response = JSON.parse(message.data); + var response = JSON.parse(message.data), + callback = _messageCallbacks[response.id]; + + if (callback) { + // Messages with an ID are a response to a command, fire callback + callback(response.result, response.error); + delete _messageCallbacks[response.id]; + } else if (response.method) { + // Messages with a method are an event, trigger event handlers + var domainAndMethod = response.method.split("."), + domain = domainAndMethod[0], + method = domainAndMethod[1]; + + $(exports[domain]).triggerHandler(method, response.params); + } + + // Always fire event handlers for all messages/errors $exports.triggerHandler("message", [response]); + if (response.error) { $exports.triggerHandler("error", [response.error]); - } else if (response.result) { - if (_messageCallbacks[response.id]) { - _messageCallbacks[response.id](response.result); - delete _messageCallbacks[response.id]; - } - } else { - var domainAndMethod = response.method.split("."); - var domain = domainAndMethod[0]; - var method = domainAndMethod[1]; - $(exports[domain]).triggerHandler(method, response.params); } } @@ -363,5 +388,6 @@ define(function Inspector(require, exports, module) { exports.connect = connect; exports.connectToURL = connectToURL; exports.connected = connected; + exports.send = send; exports.init = init; }); \ No newline at end of file diff --git a/src/LiveDevelopment/LiveDevelopment.js b/src/LiveDevelopment/LiveDevelopment.js index d0787fe438e..e99787caa70 100644 --- a/src/LiveDevelopment/LiveDevelopment.js +++ b/src/LiveDevelopment/LiveDevelopment.js @@ -104,12 +104,14 @@ define(function LiveDevelopment(require, exports, module) { var SYNC_ERROR_CLASS = "live-preview-sync-error"; // Agents + var CSSAgent = require("LiveDevelopment/Agents/CSSAgent"); + var agents = { "console" : require("LiveDevelopment/Agents/ConsoleAgent"), "remote" : require("LiveDevelopment/Agents/RemoteAgent"), "network" : require("LiveDevelopment/Agents/NetworkAgent"), "dom" : require("LiveDevelopment/Agents/DOMAgent"), - "css" : require("LiveDevelopment/Agents/CSSAgent"), + "css" : CSSAgent, "script" : require("LiveDevelopment/Agents/ScriptAgent"), "highlight" : require("LiveDevelopment/Agents/HighlightAgent"), "goto" : require("LiveDevelopment/Agents/GotoAgent"), @@ -147,8 +149,12 @@ define(function LiveDevelopment(require, exports, module) { // store the names (matching property names in the 'agent' object) of agents that we've loaded var _loadedAgentNames = []; - var _liveDocument; // the document open for live editing. - var _relatedDocuments; // CSS and JS documents that are used by the live HTML document + /** @type {HTMLDocument} */ + var _liveDocument; + + /** @type {Object.} */ + var _relatedDocuments = {}; + var _openDeferred; // promise returned for each call to open() /** @@ -201,23 +207,6 @@ define(function LiveDevelopment(require, exports, module) { } return getLiveDocForPath(editor.document.file.fullPath); } - - /** - * Removes the given CSS/JSDocument from _relatedDocuments. Signals that the - * given file is no longer associated with the HTML document that is live (e.g. - * if the related file has been deleted on disk). - */ - function _handleRelatedDocumentDeleted(event, liveDoc) { - var index = _relatedDocuments.indexOf(liveDoc); - - if (index !== -1) { - _relatedDocuments.splice(index, 1); - - if (_server) { - _server.remove(liveDoc); - } - } - } /** * @private @@ -247,6 +236,38 @@ define(function LiveDevelopment(require, exports, module) { }); } + /** + * @private + * Close a live document + */ + function _closeDocument(liveDocument) { + _doClearErrors(liveDocument); + liveDocument.close(); + + if (liveDocument.editor) { + $(liveDocument.editor).off(".livedev"); + } + + $(liveDocument).off(".livedev"); + } + + /** + * Removes the given CSS/JSDocument from _relatedDocuments. Signals that the + * given file is no longer associated with the HTML document that is live (e.g. + * if the related file has been deleted on disk). + */ + function _handleRelatedDocumentDeleted(event, liveDoc) { + if (_relatedDocuments[liveDoc.doc.url]) { + delete _relatedDocuments[liveDoc.doc.url]; + } + + if (_server) { + _server.remove(liveDoc); + } + + _closeDocument(liveDoc); + } + /** * Update the status. Triggers a statusChange event. * @param {number} status new status @@ -305,21 +326,6 @@ define(function LiveDevelopment(require, exports, module) { }); } - /** - * @private - * Close a live document - */ - function _closeDocument(liveDocument) { - _doClearErrors(liveDocument); - liveDocument.close(); - - if (liveDocument.editor) { - $(liveDocument.editor).off(".livedev"); - } - - $(liveDocument).off(".livedev"); - } - /** * @private * Close all live documents @@ -330,13 +336,10 @@ define(function LiveDevelopment(require, exports, module) { _liveDocument = undefined; } - if (_relatedDocuments) { - _relatedDocuments.forEach(function (liveDoc) { - _closeDocument(liveDoc); - }); - - _relatedDocuments = undefined; - } + Object.keys(_relatedDocuments).forEach(function (url) { + _closeDocument(_relatedDocuments[url]); + delete _relatedDocuments[url]; + }); // Clear all documents from request filtering if (_server) { @@ -365,55 +368,6 @@ define(function LiveDevelopment(require, exports, module) { return liveDocument; } - - /** - * @private - * Populate array of related documents reported by the browser agent(s) - */ - function _getRelatedDocuments() { - function createLiveStylesheet(url) { - var stylesheetDeferred = $.Deferred(), - promise = stylesheetDeferred.promise(), - path = _server && _server.urlToPath(url); - - // path may be null if loading an external stylesheet - if (path) { - DocumentManager.getDocumentForPath(path) - .fail(function () { - // A failure to open a related file is benign - stylesheetDeferred.resolve(); - }) - .done(function (doc) { - // CSSAgent includes containing HTMLDocument in list returned - // from getStyleSheetURLS() (which could be useful for collecting - // embedded style sheets) but we need to filter doc out here. - if ((_classForDocument(doc) === CSSDocument) && - (!_liveDocument || (doc !== _liveDocument.doc))) { - var liveDoc = _createDocument(doc); - if (liveDoc) { - _relatedDocuments.push(liveDoc); - _server.add(liveDoc); - - $(liveDoc).on("deleted.livedev", _handleRelatedDocumentDeleted); - } - } - stylesheetDeferred.resolve(); - }); - } else { - stylesheetDeferred.resolve(); - } - - return promise; - } - - // Gather related CSS documents. - // FUTURE: Gather related JS documents as well. - _relatedDocuments = []; - - return Async.doInParallel(agents.css.getStylesheetURLs(), - createLiveStylesheet, - false); // don't fail fast - } /** Enable an agent. Takes effect next time a connection is made. Does not affect * current live development sessions. @@ -474,7 +428,34 @@ define(function LiveDevelopment(require, exports, module) { // Show the message, but include the error object for further information (e.g. error code) console.error(message, error); - _setStatus(STATUS_ERROR); + } + + function _styleSheetAdded(event, url) { + var path = _server && _server.urlToPath(url), + exists = !!_relatedDocuments[url]; + + // path may be null if loading an external stylesheet. + // Also, the stylesheet may already exist and be reported as added twice + // due to Chrome reporting added/removed events after incremental changes + // are pushed to the browser + if (!path || exists) { + return; + } + + var docPromise = DocumentManager.getDocumentForPath(path); + + docPromise.done(function (doc) { + if ((_classForDocument(doc) === CSSDocument) && + (!_liveDocument || (doc !== _liveDocument.doc))) { + var liveDoc = _createDocument(doc); + if (liveDoc) { + _server.add(liveDoc); + _relatedDocuments[doc.url] = liveDoc; + + $(liveDoc).on("deleted.livedev", _handleRelatedDocumentDeleted); + } + } + }); } /** Unload the agents */ @@ -563,30 +544,17 @@ define(function LiveDevelopment(require, exports, module) { allAgentsPromise = Async.withTimeout(allAgentsPromise, 10000); allAgentsPromise.done(function () { - // After (1) the interstitial page loads, (2) then browser navigation - // to the base URL is completed, and (3) the agents finish loading - // gather related documents and finally set status to STATUS_ACTIVE. var doc = (_liveDocument) ? _liveDocument.doc : null; if (doc) { - var status = STATUS_ACTIVE, - relatedDocumentsPromise; - - // Note: the following promise is never explicitly rejected, so there - // is no failure handler. If _getRelatedDocuments is changed so that rejection - // is possible, failure should be managed accordingly. - relatedDocumentsPromise = Async.withTimeout(_getRelatedDocuments(), 5000); - - relatedDocumentsPromise - .done(function () { - if (_docIsOutOfSync(doc)) { - status = STATUS_OUT_OF_SYNC; - } - _setStatus(status); + var status = STATUS_ACTIVE; - result.resolve(); - }) - .fail(result.reject); + if (_docIsOutOfSync(doc)) { + status = STATUS_OUT_OF_SYNC; + } + + _setStatus(status); + result.resolve(); } else { result.reject(); } @@ -605,9 +573,6 @@ define(function LiveDevelopment(require, exports, module) { ); }); - // resolve/reject the open() promise after agents complete - result.then(_openDeferred.resolve, _openDeferred.reject); - return result.promise(); } @@ -810,18 +775,15 @@ define(function LiveDevelopment(require, exports, module) { deferred.resolve(); }); } - - if (_openDeferred) { - _doInspectorDisconnect(doCloseWindow).done(cleanup); - - if (_openDeferred.state() === "pending") { - _openDeferred.reject(); - } + + if (_openDeferred && _openDeferred.state() === "pending") { + // Reject calls to open if requests are still pending + _openDeferred.reject(); + } else if (exports.status === STATUS_INACTIVE) { + // Ignore close if status is inactive + deferred.resolve(); } else { - // Deferred may not be created yet - // We always close attempt to close the live dev connection on - // ProjectManager beforeProjectClose and beforeAppClose events - cleanup(); + _doInspectorDisconnect(doCloseWindow).done(cleanup); } return deferred.promise(); @@ -950,7 +912,9 @@ define(function LiveDevelopment(require, exports, module) { // navigate to the page first before loading can complete. // To accomodate this, we load all agents and navigate in // parallel. - loadAgents(); + + // resolve/reject the open() promise after agents complete + loadAgents().then(_openDeferred.resolve, _openDeferred.reject); _getInitialDocFromCurrent().done(function (doc) { if (doc) { @@ -1187,6 +1151,11 @@ define(function LiveDevelopment(require, exports, module) { function open() { _openDeferred = new $.Deferred(); + // Cleanup deferred when finished + _openDeferred.always(function () { + _openDeferred = null; + }); + // TODO: need to run _onDocumentChange() after load if doc != currentDocument here? Maybe not, since activeEditorChange // doesn't trigger it, while inline editors can still cause edits in doc other than currentDoc... _getInitialDocFromCurrent().done(function (doc) { @@ -1209,7 +1178,6 @@ define(function LiveDevelopment(require, exports, module) { }) .fail(function () { _showWrongDocError(); - _openDeferred.reject(); }); }); @@ -1220,7 +1188,7 @@ define(function LiveDevelopment(require, exports, module) { function showHighlight() { var doc = getLiveDocForEditor(EditorManager.getActiveEditor()); - if (doc.updateHighlight) { + if (doc && doc.updateHighlight) { doc.updateHighlight(); } } @@ -1249,8 +1217,6 @@ define(function LiveDevelopment(require, exports, module) { if (!doc || !Inspector.connected()) { return; } - - hideHighlight(); // close the current session and begin a new session if the current // document changes to an HTML document that was not loaded yet @@ -1262,6 +1228,9 @@ define(function LiveDevelopment(require, exports, module) { // TODO (jasonsanjose): optimize this by reusing the same connection // no need to fully teardown. close().done(open); + } else if (wasRequested) { + // Update highlight + showHighlight(); } } @@ -1324,13 +1293,19 @@ define(function LiveDevelopment(require, exports, module) { /** Initialize the LiveDevelopment Session */ function init(theConfig) { exports.config = theConfig; + $(Inspector).on("error", _onError); $(Inspector.Inspector).on("detached", _onDetached); + + // Only listen for styleSheetAdded + // We may get interim added/removed events when pushing incremental updates + $(CSSAgent).on("styleSheetAdded.livedev", _styleSheetAdded); + $(DocumentManager).on("currentDocumentChange", _onDocumentChange) .on("documentSaved", _onDocumentSaved) .on("dirtyFlagChange", _onDirtyFlagChange); $(ProjectManager).on("beforeProjectClose beforeAppClose", close); - + // Register user defined server provider LiveDevServerManager.registerServer({ create: _createUserServer }, 99); LiveDevServerManager.registerServer({ create: _createFileServer }, 0); diff --git a/src/LiveDevelopment/Servers/BaseServer.js b/src/LiveDevelopment/Servers/BaseServer.js index 010a4f7eee6..aab6c66edfd 100644 --- a/src/LiveDevelopment/Servers/BaseServer.js +++ b/src/LiveDevelopment/Servers/BaseServer.js @@ -84,7 +84,7 @@ define(function (require, exports, module) { // TODO: Better workflow of liveDocument.doc.url assignment // Force sync the browser after a URL is assigned - if (liveDocument._updateBrowser) { + if (doc.isDirty && liveDocument._updateBrowser) { liveDocument._updateBrowser(); } }; diff --git a/test/spec/LiveDevelopment-test.js b/test/spec/LiveDevelopment-test.js index c11c760abe6..383e003c264 100644 --- a/test/spec/LiveDevelopment-test.js +++ b/test/spec/LiveDevelopment-test.js @@ -103,6 +103,19 @@ define(function (require, exports, module) { // wrap with a timeout to indicate loadEventFired was not fired return Async.withTimeout(deferred.promise(), 2000); } + + function waitForLiveDoc(path, callback) { + var liveDoc; + + waitsFor(function () { + liveDoc = LiveDevelopment.getLiveDocForPath(path); + return !!liveDoc; + }, "Waiting for LiveDevelopment document", 10000); + + runs(function () { + callback(liveDoc); + }); + } function doOneTest(htmlFile, cssFile) { var localText, @@ -129,10 +142,7 @@ define(function (require, exports, module) { }); var liveDoc; - waitsFor(function () { - liveDoc = LiveDevelopment.getLiveDocForPath(tempDir + "/" + cssFile); - return !!liveDoc; - }, "Waiting for LiveDevelopment document", 10000); + waitForLiveDoc(tempDir + "/" + cssFile, function (doc) { liveDoc = doc; }); var doneSyncing = false; runs(function () { @@ -273,7 +283,7 @@ define(function (require, exports, module) { // module spies spyOn(CSSAgentModule, "styleForURL").andReturn(""); - spyOn(CSSAgentModule, "reloadCSSForDocument").andCallFake(function () {}); + spyOn(CSSAgentModule, "reloadCSSForDocument").andCallFake(function () { return new $.Deferred().resolve(); }); spyOn(HighlightAgentModule, "redraw").andCallFake(function () {}); spyOn(HighlightAgentModule, "rule").andCallFake(function () {}); InspectorModule.CSS = { @@ -637,8 +647,9 @@ define(function (require, exports, module) { openLiveDevelopmentAndWait(); var liveDoc, doneSyncing = false; + waitForLiveDoc(tempDir + "/simple1.css", function (doc) { liveDoc = doc; }); + runs(function () { - liveDoc = LiveDevelopment.getLiveDocForPath(tempDir + "/simple1.css"); liveDoc.getSourceFromBrowser().done(function (text) { browserText = text; }).always(function () { @@ -714,13 +725,13 @@ define(function (require, exports, module) { }); // Grab the node that we've modified in Brackets. - var updatedNode, doneSyncing = false; + var liveDoc, updatedNode, doneSyncing = false; + waitForLiveDoc(tempDir + "/simple1.css", function (doc) { liveDoc = doc; }); + runs(function () { // Inpsector.Page.reload should not be called when saving an HTML file expect(Inspector.Page.reload).not.toHaveBeenCalled(); - updatedNode = DOMAgent.nodeAtLocation(501); - var liveDoc = LiveDevelopment.getLiveDocForPath(tempDir + "/simple1.css"); liveDoc.getSourceFromBrowser().done(function (text) { browserCssText = text; @@ -792,8 +803,6 @@ define(function (require, exports, module) { }, LiveDevelopmentModule.STATUS_SYNC_ERROR, 11); }); - waits(1000); - runs(function () { // Undo syntax errors _setTextAndCheckStatus(doc, function () {